Next steps: widgets, watchOS, visionOS and the Swift ecosystem
Add extension targets correctly, share code across Apple platforms, depend on Swift packages safely, and choose where to take the project next.
Extension targets and shared containers
- A widget, watch app or share extension is a separate process with its own binary. It cannot reach the app's in-memory state.
- Share data through an App Group: give both targets the same group identifier and use
FileManager.containerURL(forSecurityApplicationGroupIdentifier:). - Widgets get a strict memory budget and are killed if they exceed it. Return a small, pre-computed snapshot.
- Extension background refreshes are scheduled by the system; treat every refresh as best-effort, never as a timer.
import WidgetKit
import SwiftUI
struct Snapshot: TimelineEntry {
let date: Date
let openCount: Int
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> Snapshot {
Snapshot(date: .now, openCount: 3)
}
func getSnapshot(in context: Context, completion: @escaping (Snapshot) -> Void) {
completion(Snapshot(date: .now, openCount: SharedStore.openCount))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Snapshot>) -> Void) {
let entry = Snapshot(date: .now, openCount: SharedStore.openCount)
let next = Calendar.current.date(byAdding: .hour, value: 1, to: .now)!
completion(Timeline(entries: [entry], policy: .after(next)))
}
}Dependencies and cross-platform Swift
// Package.swift for a shared module used by the app and the watch app
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Core",
platforms: [.iOS(.v17), .watchOS(.v10), .macOS(.v14)],
products: [
.library(name: "Core", targets: ["Core"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0")
],
targets: [
.target(name: "Core", dependencies: [.product(name: "Logging", package: "swift-log")]),
.testTarget(name: "CoreTests", dependencies: ["Core"])
]
)| Target | Reuses | Cannot |
|---|---|---|
| iOS app | Core, design system, networking | watchOS-only APIs |
| watch app | Core, simplified views | Heavy background work |
| visionOS | Core, most SwiftUI | Assumptions about a flat window |
| Server (Vapor) | Core, Codable models | UIKit or SwiftUI |
Pin dependencies with a resolved file committed to the repository and update deliberately. A floating branch dependency turns someone else's force-push into your broken build.
Choosing what to learn next
// A design system is the highest-leverage shared package
public struct PrimaryButton: View {
public let title: String
public let action: () -> Void
public init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
public var body: some View {
Button(title, action: action)
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
}💡
The fastest way to grow is to ship something small end to end — a widget, a watch complication, a server endpoint in Vapor — rather than reading about each platform. Each one exercises the same core Swift skills you already have.
FAQ
Should I make an app universal or add a separate watch app?
Add the watch app only when the interaction genuinely benefits from the wrist — glanceable data or a quick action. Porting the whole app usually produces a slow, cramped experience.
How do I know a Swift package is safe to depend on?
Check that it is actively maintained, has a version tag rather than only a branch, ships tests, and has a clear licence. Then pin it by version range and review the resolved file when it changes.
Related
Localization, accessibility and system integration Release engineering: TestFlight, CI and App Store Connect
Last refreshed 2026-09-18.