Using @Observable on Older iOS Versions with Perception

Andy Kolean
4 min readJul 13, 2024

--

Learn how to use the @Observable macro in SwiftUI applications on iOS 13+ with the Perception library from Point-Free, ensuring modern state management on older platforms.

Unlock Modern State Management on Older iOS Versions with @Observable and the Perception Library

Mastering Swift Observation Series

This article is part of the Mastering Swift Observation series. Here’s what we cover in the series:

  1. Mastering @Observable: Introduction to Swift Observation
  2. Simplifying State Management with @Observable and @ObservedObject
  3. Using @Observable on Older iOS Versions with Perception

1. Introduction

Swift 5.9 introduced the @Observable macro, a powerful tool for state management in SwiftUI applications. However, its use is limited to the latest Apple platforms: iOS 17, macOS 14, tvOS 17, and watchOS 10. Given that a significant portion of users still operate on older versions, many developers are unable to utilize these new features. The Perception library from Point-Free addresses this by back-porting the capabilities of @Observable to work on older platforms, starting from iOS 13, macOS 10.15, tvOS 13, and watchOS 6.

In this post, we will explore how to use the @Perceptible macro from the Perception library, the motivations behind it, and its significance. This guide will help you implement @Perceptible in your projects, allowing you to take advantage of modern state management techniques on older platform versions.

2. Overview

The Perception library replicates the functionality of @Observable and withObservationTracking from Swift 5.9, but extends support to older Apple platforms. This means you can begin using these advanced observation tools in your SwiftUI projects without waiting for broader adoption of the latest OS versions.

3. Usage of @Perceptible

Step-by-Step Guide

To integrate @Perceptible into your project, follow these steps:

  1. Import the Perception library:

import Perception

2. Mark your view model as @Perceptible:

@Perceptible class SettingsViewModel { 
var name: String = "John Doe"
var age: Int = 30
}

3. Use the view model in your SwiftUI view, and wrap your content with WithPerceptionTracking:

struct SettingsView: View {
let viewModel = SettingsViewModel()

var body: some View {
WithPerceptionTracking {
VStack {
Text("Username: \(viewModel.name)")
Button(action: {
viewModel.name = "Jane Doe"
}) {
Text("Change Username")
}
}
.padding()
}
}
}

In this example, the SettingsViewModel class is marked as @Perceptible, making its properties observable and allowing the SettingsView to react to changes in SettingsViewModel. The WithPerceptionTracking wrapper ensures the view subscribes to the model’s changes. If you forget to wrap your view, a runtime warning will alert you to the oversight.

4. Integration with SwiftUI

@Perceptible integrates seamlessly with SwiftUI, enabling effective state management even on older platform versions. This ensures your user interface remains responsive and up-to-date with minimal code changes.

Example

Here’s an example demonstrating how @Perceptible can be used in a SwiftUI view:

@Perceptible
class SettingsViewModel {
var name: String = "John Doe"
var age: Int = 30
}

struct ContentView: View {
@Perception.Bindable var viewModel = SettingsViewModel()
var body: some View {
WithPerceptionTracking {
VStack {
TextField("Name", text: $viewModel.name)
Stepper("Age: \(viewModel.age)", value: $viewModel.age)
}
.padding()
}
}
}

In this setup, the ContentView automatically updates when the name or age properties of SettingsViewModel change, showcasing how @Perceptible aids in state management within SwiftUI.

5. How the Perception Library Works

The Perception library leverages the open-source nature of Swift’s Observation framework. By adapting the @Observable macro to @Perceptible, it ensures compatibility with older platforms. The library differentiates its tools from Apple's native ones while also enabling a seamless transition to native tools when running on the latest platforms.

The Perception library includes a PerceptionRegistrar type, which determines whether to use the native ObservationRegistrar or a custom back-ported implementation based on the platform version. When running on iOS 17 and newer, the library defers to the native observation tools. On older platforms, it uses its own implementation to handle observation.

Methods such as access, willSet, didSet, and withMutation in PerceptionRegistrar are dynamically selected at runtime, ensuring that the appropriate observation tools are used depending on the platform capabilities. This allows developers to use modern state management techniques across a wide range of iOS and macOS versions.

Conclusion

The @Perceptible macro provides a powerful solution for back-porting the capabilities of @Observable to earlier versions of iOS and macOS. By leveraging @Perceptible, you can implement modern state management techniques and maintain a responsive application, regardless of the platform version.

Implementing @Perceptible in your projects ensures compatibility with older devices while benefiting from the enhanced state management features provided by the Observation framework. Try integrating @Perceptible into your projects to simplify state management and improve application performance.

--

--

Andy Kolean

Hi there! I’m Andy Kolean, an iOS Developer who loves creating high-quality applications for Apple platforms.