iOS 16 Features Every Developer Should Implement
iOS 16 has been in the hands of users for several months now, and adoption rates are climbing steadily. Apple reported that over 70% of all iPhones are running iOS 16 as of late 2022. For developers, this adoption rate means the new APIs and features are no longer “nice to have” — they are expected by your users.
After shipping iOS 16 updates across multiple client apps, here are the features that deliver the most impact for user engagement and app quality. These are not experimental novelties. They are practical enhancements that users notice and appreciate.
Lock Screen Widgets with WidgetKit
The redesigned Lock Screen is the most visible change in iOS 16, and Lock Screen widgets give your app prime real estate on the most-viewed screen of any iPhone. Users glance at their Lock Screen dozens of times daily, and your widget can be right there alongside the time and date.
Lock Screen widgets come in three sizes: circular, rectangular, and inline (above the clock). They use the same WidgetKit framework introduced for Home Screen widgets, but with a few key differences.
Implementation Approach
Lock Screen widgets use the accessoryCircular, accessoryRectangular, and accessoryInline widget families. They render in a limited colour space — essentially monochrome with some opacity variations — so your designs need to work without colour:
struct StepCountWidget: Widget {
let kind: String = "StepCountWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: StepCountProvider()) { entry in
StepCountWidgetView(entry: entry)
}
.configurationDisplayName("Step Count")
.description("Track your daily steps")
.supportedFamilies([
.accessoryCircular,
.accessoryRectangular,
.accessoryInline
])
}
}
struct StepCountWidgetView: View {
var entry: StepCountEntry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .accessoryCircular:
Gauge(value: Double(entry.steps), in: 0...Double(entry.goal)) {
Image(systemName: "figure.walk")
} currentValueLabel: {
Text("\(entry.steps)")
}
.gaugeStyle(.accessoryCircularCapacity)
case .accessoryRectangular:
VStack(alignment: .leading) {
Text("Steps Today")
.font(.headline)
Text("\(entry.steps) / \(entry.goal)")
.font(.body)
ProgressView(value: Double(entry.steps), total: Double(entry.goal))
}
case .accessoryInline:
Text("\(entry.steps) steps today")
default:
Text("Unsupported")
}
}
}
Design Tips
The Gauge view is particularly effective for circular widgets. Use it for any metric that has a clear progress toward a goal: fitness targets, budget tracking, project completion. The rectangular format works well for brief text summaries with a supporting visual element.
Keep updates reasonable. Lock Screen widgets refresh on a timeline you define, but battery impact matters. For most apps, updates every 15-30 minutes strike the right balance between freshness and efficiency.
Live Activities and the Dy
namic Island
Live Activities are the standout feature for apps with real-time information. They display ongoing events on the Lock Screen and, on iPhone 14 Pro models, in the Dynamic Island. Think delivery tracking, sports scores, ride-sharing status, or workout timers.
Setting Up Live Activities
Live Activities require the ActivityKit framework and a specific configuration in your app’s Info.plist:
// Define your activity attributes
struct DeliveryActivity: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var status: String
var estimatedArrival: Date
var driverName: String
}
var orderNumber: String
var restaurantName: String
}
Starting a Live Activity from your app:
func startDeliveryTracking(order: Order) throws {
let attributes = DeliveryActivity(
orderNumber: order.id,
restaurantName: order.restaurant
)
let initialState = DeliveryActivity.ContentState(
status: "Preparing your order",
estimatedArrival: order.estimatedArrival,
driverName: ""
)
let content = ActivityContent(state: initialState, staleDate: nil)
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token
)
}
Updating via push notifications is the recommended approach for server-driven updates. You receive an Activity push token when the activity starts, which you send to your backend. Updates arrive through a dedicated APNs channel with minimal latency.
Dynamic Island Presentation
For iPhone 14 Pro users, Live Activities automatically appear in the Dynamic Island. You define three presentations: compact leading, compact trailing, and expanded:
struct DeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryActivity.self) { context in
// Lock Screen presentation
DeliveryLockScreenView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded presentation
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "bicycle")
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.estimatedArrival, style: .timer)
}
DynamicIslandExpandedRegion(.bottom) {
Text(context.state.status)
.font(.caption)
}
} compactLeading: {
Image(systemName: "bicycle")
} compactTrailing: {
Text(context.state.estimatedArrival, style: .timer)
} minimal: {
Image(systemName: "bicycle")
}
}
}
}
Live Activities are a significant differentiator for apps with real-time data. If your app tracks anything over time — deliveries, workouts, travel, cooking timers — implement Live Activities now.
SwiftUI 4 Improvements
SwiftUI received substantial improvements in iOS 16 that address many pain points from earlier versions.
Navigation Overhaul
The new NavigationStack and NavigationSplitView replace the aging NavigationView with a programmatic, type-safe navigation system:
struct ProductListView: View {
@State private var navigationPath = NavigationPath()
var body: some View {
NavigationStack(path: $navigationPath) {
List(products) { product in
NavigationLink(value: product) {
ProductRow(product: product)
}
}
.navigationDestination(for: Product.self) { product in
ProductDetailView(product: product)
}
.navigationDestination(for: Category.self) { category in
CategoryView(category: category)
}
}
}
func navigateToProduct(_ product: Product) {
navigationPath.append(product)
}
func popToRoot() {
navigationPath.removeLast(navigationPath.count)
}
}
This is a game-changer for complex navigation flows. Deep linking, programmatic navigation, and state restoration all become straightforward.
Swift Charts
Charts are one of the most common UI requirements in mobile apps, and Apple now provides a first-party solution:
import Charts
struct SalesChartView: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(by: .value("Category", item.category))
}
.chartYAxis {
AxisMarks(format: .currency(code: "AUD"))
}
}
}
Swift Charts supports bar, line, point, area, rule, and rectangle marks with composable modifiers. For most app charting needs, it eliminates the dependency on third-party libraries.
Transferable Protocol
The new Transferable protocol simplifies drag-and-drop, copy-paste, and sharing operations:
struct Photo: Transferable {
let image: Image
let caption: String
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.image)
}
}
This dramatically reduces the boilerplate needed for data transfer between apps and within your app’s interface.
Passkeys
Apple is pushing passkeys as the future of authentication, and iOS 16 provides the framework to implement them. Passkeys use public-key cryptography to replace passwords entirely. They are phishing-resistant, cannot be reused across sites, and sync securely via iCloud Keychain.
For apps that handle authentication, implementing passkey support positions you ahead of the curve:
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com"
)
let registrationRequest = provider.createCredentialRegistrationRequest(
challenge: serverChallenge,
name: "[email protected]",
userID: userId
)
let controller = ASAuthorizationController(
authorizationRequests: [registrationRequest]
)
controller.delegate = self
controller.performRequests()
The backend requirements are standard WebAuthn, so if your server already supports FIDO2, passkey integration is straightforward.
Focus Filters
Focus Filters allow your app to present different content based on the user’s active Focus mode. A productivity app could show work tasks during the Work focus and personal tasks during Personal:
struct WorkFocusFilter: SetFocusFilterIntent {
static var title: LocalizedStringResource = "Work Filter"
static var description: IntentDescription = "Show only work-related content"
@Parameter(title: "Account")
var account: AccountEntity?
func perform() async throws -> some IntentResult {
// Configure app to show work content
return .result()
}
}
This is a subtle feature, but it demonstrates awareness of how users actually interact with their devices throughout the day.
Priority Implementation Order
If you are planning your iOS 16 adoption roadmap, here is the order I recommend based on user impact and implementation effort:
- Lock Screen widgets — High visibility, moderate effort, works on all iOS 16 devices
- NavigationStack migration — Improves code quality and enables deep linking
- Swift Charts — Eliminates third-party dependencies for charting
- Live Activities — High impact for apps with real-time data, higher effort
- Passkeys — Forward-looking investment in authentication
- Focus Filters — Nice enhancement for power users
Each of these features signals to your users that your app is actively maintained and takes advantage of the latest platform capabilities. In a competitive App Store, that signal matters.
Building or updating an iOS app? Our team at eawesome helps Australian businesses leverage the latest Apple technologies to create standout mobile experiences.