Francisco José García Navarro
Francisco José García Navarro
June 6, 2023

WWDC 2023: Everything an iOS Developer Needs to Know

WWDC 2023: Everything an iOS Developer Needs to Know
" The day-after technical summary — macros, SwiftData, @Observable, Vision Pro and the biggest SwiftUI leap yet "

I just finished digesting the WWDC 2023 keynote and State of the Union, and I need to process what just happened. Apple has unveiled a new device (Vision Pro), rewritten the foundations of how we manage data in SwiftUI, introduced macros in Swift, and launched a replacement for Core Data. All in a single afternoon.

If WWDC 2022 was the year of navigation and concurrency, WWDC 2023 is the year of observation, persistence and macros. These are changes that will redefine how we write Swift code for the next five years.

I'm going to break down everything relevant for those of us working on production iOS apps. Vision Pro dominates the headlines, but the updates to Swift 5.9, SwiftUI and SwiftData are what will truly impact your day-to-day as a developer.

Swift 5.9: macros change everything

The headline feature of Swift 5.9 is macros. And I'm not exaggerating when I say they fundamentally change what's possible with the language.

Macros transform your source code by adding additional code at compile time. They never remove or modify existing code — they only add. There are two main categories:

Freestanding macros (prefixed with #): used anywhere in code. The canonical example is #Predicate, which lets you write type-safe predicates with closures for use with SwiftData and collection operations.

let predicate = #Predicate<User> { user in
    user.age > 18 && user.name.contains("García")
}

Attached macros (prefixed with @): attached to declarations. The most important ones today are @Observable and @Model, which compose multiple macro roles to transform entire classes.

But the most powerful aspect is that you can create your own macros. A simple example: a #URL macro that validates hard-coded URLs at compile time, converting a URL? into a non-optional URL because the compiler has already verified the URL is valid.

// Before: optional URL, can fail at runtime
let url = URL(string: "https://api.example.com/users")!

// With macro: validated at compile time, never fails
let url = #URL("https://api.example.com/users")

Macros are implemented as separate Swift packages using SwiftSyntax. They run in a sandbox and can only inspect and generate code — they cannot access the filesystem or network. In Xcode 15, you can use "Expand Macro" to see exactly what code each macro generates. Full transparency.

For enterprise teams, this opens the door to massively reducing boilerplate: mock generation, automatic conformances, compile-time validations. The potential is enormous.

Parameter packs (variadic generics)

Swift 5.9 also introduces parameter packs (SE-0393, SE-0398, SE-0399), the most requested language feature for years. They allow writing generic functions that accept an arbitrary number of type parameters, each with its own type.

func all<each T: Equatable>(
    _ original: repeat each T,
    equal copy: repeat each T
) -> Bool {
    (repeat (each original) == (each copy))
}

This seems abstract, but it's what allows Apple to build APIs like the new SwiftUI views that accept variable content without needing overloads for 2, 3, 4... 10 parameters. It's language infrastructure that enables better frameworks.

More language updates

  • if/switch as expressions (SE-0380): you can use if and switch directly as return values or assignments. let value = if condition { "a" } else { "b" }.
  • Swift/C++ interoperability: Xcode 15 supports bidirectional interop between Swift and C++. For legacy projects with C++ code, this is a game changer.
  • Noncopyable types (~Copyable): allow expressing exclusive ownership, useful for system resource wrappers.

@Observable: SwiftUI breaks free from Combine

This is, for me, the most important change of WWDC 2023. The @Observable macro and the new Observation framework radically simplify the data flow in SwiftUI.

Until now, for a view to respond to model changes, you needed: conform to ObservableObject, mark every property with @Published, and use @ObservedObject or @StateObject in the view. Forget a @Published and the UI wouldn't update. Use @ObservedObject when you needed @StateObject and you'd get lifecycle bugs. It was a constant source of errors.

With @Observable, all of that disappears:

import Observation

@Observable
final class UserStore {
    var users: [User] = []
    var isLoading = false

    func fetch() async {
        isLoading = true
        users = await api.fetchUsers()
        isLoading = false
    }
}

The view simply uses the model, without any special property wrappers:

struct UserListView: View {
    var store: UserStore

    var body: some View {
        List(store.users) { user in
            Text(user.name)
        }
    }
}

SwiftUI automatically detects which properties each view accesses during body execution, and only invalidates the view when those specific properties change. If isLoading changes but the view only reads users, it doesn't redraw. This fixes at the root the performance problem that plagued ObservableObject.

The new property wrapper model is simplified:

  • @State: for value types and @Observable reference types the view owns.
  • @Environment: to inject @Observable into the environment (replaces @EnvironmentObject).
  • @Bindable: to create two-way bindings from an @Observable type.

The confusion between @StateObject, @ObservedObject and @EnvironmentObject is over. Limitation: @Observable requires iOS 17. For apps supporting iOS 16 or earlier, you'll stay with ObservableObject. But for new projects, this enormously simplifies the architecture.

SwiftData: Core Data for the 21st century

Apple launches SwiftData, a native Swift persistence framework built on Core Data foundations. If you've suffered through Core Data (and who hasn't), this is what we've been waiting years for.

The model is defined with the @Model macro:

import SwiftData

@Model
class Project {
    var name: String
    var deadline: Date
    var isCompleted: Bool

    @Relationship(deleteRule: .cascade)
    var tasks: [Task] = []

    init(name: String, deadline: Date) {
        self.name = name
        self.deadline = deadline
        self.isCompleted = false
    }
}

@Model automatically generates conformance to PersistentModel and Observable. SwiftData infers the schema from properties, supports primitive types, Codable, enums and relationships. Attributes are customised with @Attribute(.unique), @Relationship and @Transient.

SwiftUI integration is direct:

struct ProjectListView: View {
    @Query(sort: \.deadline)
    var projects: [Project]

    @Environment(\.modelContext)
    var context

    var body: some View {
        List(projects) { project in
            ProjectRow(project: project)
        }
    }
}

@Query replaces @FetchRequest with support for the new type-safe predicates via #Predicate (goodbye NSPredicate with strings). SwiftData supports schema versioning with automatic lightweight migrations, iCloud persistence (CloudKit), and SwiftData class generation from existing Core Data models for gradual migration.

My take: the API is elegant and modern. However, it's a 1.0 release, which always calls for caution in production. For new apps targeting iOS 17+, it's a very attractive option. For enterprise production apps with Core Data, I'd wait for it to mature. Core Data is still there and isn't going anywhere.

SwiftUI 5: interactive widgets, keyframe animations and native MapKit

Interactive widgets

The most requested WidgetKit feature has finally arrived: widgets can now contain Button and Toggle that execute app code via App Intents, without launching the app in the foreground. SwiftUI transitions and animations also work in widgets.

Widgets also come to new places: iPadOS Lock Screen, iPhone StandBy, and the macOS Sonoma desktop. The same WidgetKit code works across all these surfaces.

For enterprise apps that already have widgets, adding interactivity is relatively straightforward and has a direct engagement impact. A "mark as complete" button in a task widget, or a "start tracking" toggle in a logistics app, are features with immediate ROI.

Animations: keyframes and phases

SwiftUI 5 introduces two powerful animation APIs:

KeyframeAnimator allows multi-property animations with precise timing control. You define independent tracks for each property with different interpolation curves:

KeyframeAnimator(initialValue: AnimationValues(), trigger: trigger) { values in
    MyView()
        .scaleEffect(values.scale)
        .rotationEffect(values.rotation)
} keyframes: { _ in
    KeyframeTrack(\.scale) {
        SpringKeyframe(1.2, duration: 0.3)
        SpringKeyframe(1.0, spring: .bouncy)
    }
    KeyframeTrack(\.rotation) {
        LinearKeyframe(.degrees(0), duration: 0.2)
        CubicKeyframe(.degrees(360), duration: 0.5)
    }
}

PhaseAnimator defines sequences of animation states that run cyclically or by trigger. Spring animations are now the default in iOS 17, improving the feeling of fluidity without additional effort.

MapKit for SwiftUI

MapKit receives a completely new native SwiftUI API: Map with Marker, Annotation, MapPolyline, MapPolygon, and MapCircle. Support for inline Look Around, map feature selection, and styles (standard, imagery, hybrid). If you were using UIViewRepresentable to embed MKMapView, you no longer need to.

Improved ScrollView

ScrollView gains .scrollPosition(id:) for programmatic scrolling, .scrollTargetBehavior(.paging) for native pagination, and .containerRelativeFrame() for container-relative sizing. Visual effects with .scrollTransition() allow position-based transformations. These improvements solve real problems that previously required UIScrollView with UIViewRepresentable.

Xcode 15: #Preview, String Catalogs and dependency signing

Xcode 15 brings improvements that directly impact daily productivity:

#Preview macro: the new preview syntax is cleaner and works with SwiftUI, UIKit and AppKit:

#Preview("User Profile") {
    UserProfileView(user: .mock)
}

// UIKit too:
#Preview("Settings") {
    let vc = SettingsViewController()
    vc.user = .mock
    return vc
}

String Catalogs (.xcstrings): replace .strings and .stringsdict files with a unified format that shows translation status per language. Migration via right-click from existing files. Back-deployable to any OS. Improved grammatical agreement support for Spanish, Portuguese and German.

UIKit: viewIsAppearing: a new lifecycle callback called between viewWillAppear and viewDidAppear, when the view is already in the hierarchy with correct traits and geometry. Back-deploys to iOS 13. For those maintaining UIKit apps, this is gold. It solves the eternal problem of needing real geometry before the transition animation finishes.

Dependency signature verification: Xcode 15 verifies the digital signatures of SPM packages, alerting if a dependency has been modified. Supply-chain security that arrives late but is welcome for enterprise projects.

Other improvements: ML-enhanced code completion, bookmarks navigator, redesigned test reports with pattern insights, OSLog integration in the console, and optional simulators to reduce download size.

TipKit: native tooltips for onboarding

A new framework for showing contextual tooltips that help users discover app features. Available on iOS 17, macOS Sonoma, watchOS 10 and tvOS 17.

struct FavoritesTip: Tip {
    var title: Text { Text("Add to favourites") }
    var message: Text? { Text("Tap the heart to save this item.") }
    var image: Image? { Image(systemName: "heart") }
}

// In the view
TipView(FavoritesTip())

TipKit includes a rules system to show tips at the right moment (based on user events or parameters), frequency control (maximum one tip per day), cross-device synchronisation via iCloud, and automatic invalidation when the user has already used the feature.

For complex enterprise apps with flows users don't discover on their own, this is infinitely better than building your own onboarding system. And the UI is consistent with Apple's native tips.

UIKit is still alive and kicking

Apple hasn't abandoned UIKit. In iOS 17:

  • Redesigned trait system: closure-based API for working with UITraitCollection. You can define custom traits and register callbacks when they change. Much cleaner than overriding traitCollectionDidChange.
  • Animated SF Symbols: symbol animations are now configurable with effects like .bounce, .pulse, .variableColor, .replace. Works in both UIKit and SwiftUI.
  • Empty state views: native configuration for empty states in collection views.
  • UIPageControl with progress: fractional progress indicator between pages.
  • Palette menus: UIMenu with .displayAsPalette option to show items in a row.
  • Adaptive status bar: in iOS 17, the status bar automatically adapts between light and dark based on the content below.

And don't forget: viewIsAppearing back-deploys to iOS 13. It's the most practical UIKit improvement of this WWDC.

performAccessibilityAudit(): automated accessibility testing

This is a feature I'm particularly excited about. You can now run automated accessibility audits from UI tests:

func testAccessibility() throws {
    let app = XCUIApplication()
    app.launch()
    try app.performAccessibilityAudit()
}

With a single line, Xcode verifies labels, traits, contrast, touch target sizes and more. You can filter by category and exclude known issues. This makes integrating accessibility verification into your CI/CD pipeline trivial.

For teams that need to comply with accessibility regulations (WCAG, Section 508, or European EN 301 549 standards), being able to automate these audits is a fundamental change. There's no longer any excuse not to test accessibility.

Vision Pro and visionOS: the new platform

Apple unveils Vision Pro, their "spatial computer" with M2 + R1 chips and visionOS, the first spatial operating system. It's Apple's first entirely new hardware since the Apple Watch in 2015.

For iOS developers, what matters is:

  • Existing SwiftUI apps run on visionOS as 2D windows without modification. This validates the investment in SwiftUI.
  • RealityKit becomes the primary framework for 3D content, with a new declarative SwiftUI-style API.
  • UIKit and SwiftUI provide visionOS-specific behaviours (hover effects, materials, depth) automatically when compiling for the platform.
  • The Xcode 15 simulator allows testing visionOS experiences without the hardware.

My take: Vision Pro is fascinating as a platform, but at $3,499 and with limited availability, its immediate impact on enterprise apps is minimal. However, if you're already in SwiftUI, much of the adaptation work is done. Watch this platform, but don't take your eyes off what matters today: iOS 17, Swift 5.9 and SwiftData.

Hardware and more updates

  • 15" MacBook Air: 15.3" Liquid Retina display, M2 chip, 18-hour battery life, $1,299. The best value portable development machine.
  • Mac Studio and Mac Pro: updated with M2 Max and M2 Ultra. The Mac Pro with M2 Ultra completes the Apple Silicon transition — no more Intel Macs.
  • watchOS 10: significant visual redesign, stacked widgets accessible with Digital Crown, and vertical navigation by default. Unified WidgetKit across watchOS, iOS and macOS.
  • macOS Sonoma: desktop widgets, Game Porting Toolkit for bringing Windows games, video screensavers, and improved Stage Manager.
  • StoreKit for SwiftUI: new declarative views for in-app purchases (ProductView, StoreView, SubscriptionStoreView). Building a professional subscription screen goes from weeks to hours.

Conclusion: what to do now

WWDC 2023 is possibly the biggest leap SwiftUI has taken since its launch in 2019. The @Observable macro solves fundamental architecture and performance problems. SwiftData promises to modernise persistence. Swift 5.9 macros open up possibilities that previously required external code generation.

If I had to choose three concrete actions for an enterprise iOS team:

  1. Adopt @Observable in your next iOS 17-targeting feature. The data flow simplification is immediate and performance improves significantly. If your architecture is MVVM, ViewModels become simpler without ObservableObject and @Published.
  2. Add performAccessibilityAudit() to your UI tests today. It doesn't require iOS 17 in production — you run it on the simulator in your CI. One line of code that automatically detects accessibility issues.
  3. Make your existing widgets interactive. A Button or Toggle in a widget with App Intents has a disproportionate engagement impact, and the implementation effort is low.

On SwiftData: evaluate it seriously for new projects, but don't migrate your production Core Data stack yet. Give the 1.0 time to mature.

And on Vision Pro: it's the future, but your iOS app is the present. If you're already investing in SwiftUI, you'll be ready when the time comes.

It's been a memorable WWDC. Macros and @Observable will define how we write Swift for the coming years. Time to start studying.

Need help with adoption?

💡 Do you have a project that needs migration? We can help you make the transition efficiently and without interrupting your development flow.

📩 Contact us here for more information.

Share:
About the author
Francisco José García Navarro

Francisco José García Navarro

Francisco José García Navarro is the co-founder and CEO of AtalayaSoft and an experienced iOS software engineer with over 25 years in software development. Specializing in native iOS applications, Francisco has a rich background working with high-profile clients such as Banco Santander, Fox International Channel, Repsol, and National Geographic.