The line between UIKit and SwiftUI continues to blur in the most delightful way. With iOS 26, Apple has brought one of SwiftUI's most beloved features—automatic observation tracking—directly into UIKit. Combined with the new updateProperties()
lifecycle method, UIKit developers can now enjoy the reactive programming benefits that SwiftUI developers have been raving about since long.
The End of Manual UI Updates
Remember this familiar UIKit code?
class ProfileViewController: UIViewController {
var user: User? {
didSet { updateUI() }
}
func updateUI() {
nameLabel.text = user?.name
avatarImageView.image = user?.avatar
setNeedsLayout()
}
}
Forget to call updateUI()
? Your UI stays stale. Call it too often? Performance takes a hit. This tedious pattern has been the bane of UIKit developers for years.
Those days are officially over.
Introducing Automatic Observation Tracking
iOS 26 brings Swift's @Observable
macro to UIKit with full automatic tracking capabilities. When you access observable properties in specific UIKit methods, the framework automatically establishes dependencies and invalidates views when those properties change.
Basic Observable Integration
Here's how simple it becomes:
@Observable class UnreadMessagesModel {
var showStatus: Bool
var statusText: String
}
class MessageListViewController: UIViewController {
var unreadMessagesModel: UnreadMessagesModel
var statusLabel: UILabel
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// UIKit automatically tracks these dependencies
statusLabel.alpha = unreadMessagesModel.showStatus ? 1.0 : 0.0
statusLabel.text = unreadMessagesModel.statusText
}
}
That's it! No manual setNeedsLayout()
calls, no property observers, no update methods. UIKit automatically:
-
Tracks dependencies on
showStatus
andstatusText
during the first layout - Invalidates the view whenever these properties change
-
Reruns
viewWillLayoutSubviews()
to keep your UI in sync
Collection Views Made Effortless
The real magic happens with collection views. Configure cells once, and they automatically update when their models change:
@Observable class ListItemModel {
var icon: UIImage
var title: String
var subtitle: String
}
func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
let listItemModel = listItemModel(for: indexPath)
cell.configurationUpdateHandler = { cell, state in
var content = UIListContentConfiguration.subtitleCell()
content.image = listItemModel.icon // Automatically tracked
content.text = listItemModel.title // Automatically tracked
content.secondaryText = listItemModel.subtitle // Automatically tracked
cell.contentConfiguration = content
}
return cell
}
Any change to your ListItemModel
properties while the cell is visible will automatically trigger the configuration handler. No more manual cell reloading!
Meet updateProperties(): The New Lifecycle Method
iOS 26 introduces a game-changing addition to the UIKit lifecycle: updateProperties()
. This method runs just before layoutSubviews()
but operates independently, giving you finer-grained control over your updates.
Why updateProperties() Matters
Traditional UIKit forces you to handle both property updates and layout in layoutSubviews()
. This leads to:
- Unnecessary work when only properties change
- Performance bottlenecks from over-invalidation
- Coupling between property logic and layout logic
The new updateProperties()
method solves this by creating a dedicated phase for property updates:
@Observable class BadgeModel {
var badgeCount: Int?
}
class MyViewController: UIViewController {
var model: BadgeModel
let folderButton: UIBarButtonItem
override func updateProperties() {
super.updateProperties()
if let badgeCount = model.badgeCount {
folderButton.badge = .count(badgeCount)
} else {
folderButton.badge = nil
}
}
}
Understanding the Update Cycle
Here's how the enhanced UIKit update cycle works:
- Trait Updates - UIKit updates the trait collection
- updateProperties() - Your property configuration runs (NEW!)
- layoutSubviews() - Layout calculations and positioning
- Display Pass - Drawing and rendering
This separation means:
- Property changes don't force unnecessary layout passes
- Layout changes don't trigger redundant property updates
- Better performance through targeted invalidation
You can manually trigger property updates using:
-
setNeedsUpdateProperties()
- Schedule an update for the next cycle -
updatePropertiesIfNeeded()
- Force an immediate update if pending
Seamless Animations with flushUpdates
iOS 26 also introduces the flushUpdates
animation option, which automatically handles observable updates within animation blocks:
// Before iOS 26 - Manual approach
UIView.animate(withDuration: 0.3) {
model.badgeColor = .red
view.layoutIfNeeded() // Required manual call
}
// iOS 26 - Automatic approach
UIView.animate(withDuration: 0.3, options: [.flushUpdates]) {
model.badgeColor = .red
// UIKit automatically handles the rest!
}
The flushUpdates
option works with any invalidating changes, including Auto Layout constraints:
UIView.animate(options: [.flushUpdates]) {
topSpacingConstraint.constant = 20
leadingEdgeConstraint.isActive = false
trailingEdgeConstraint.isActive = true
}
Backwards Compatibility
The best part? You can start using automatic observation tracking today, even on iOS 18! Simply add this key to your Info.plist:
<key>UIObservationTrackingEnabled</key>
<true/>
In iOS 26, this feature is enabled by default, so the plist key becomes unnecessary.
Best Practices and Considerations
Where Observation Works
Automatic tracking is supported in specific UIKit methods:
-
viewWillLayoutSubviews()
in view controllers -
layoutSubviews()
in views -
updateProperties()
in both (iOS 26+) - Cell configuration handlers
- Various update-related callbacks
Performance Tips
- Cache expensive computations since update methods may run frequently
- Keep mutations on the main thread to avoid UI inconsistencies
- Be mindful of retain cycles - Observable objects are retained while observed
The Bigger Picture
This integration represents more than just a convenience feature—it's a fundamental shift in how UIKit and SwiftUI interoperate. Apple is clearly committed to bringing the best ideas from both frameworks together, creating a unified development experience.
With automatic observation tracking and updateProperties()
, UIKit developers can now:
- Write less boilerplate code for UI updates
- Achieve better performance through intelligent invalidation
- Create more maintainable apps with clearer data flow
- Gradually adopt SwiftUI patterns without full migration
Ready to modernize your UIKit apps?
- Update to iOS 26 SDK and compile your existing apps
- Identify manual update patterns in your codebase
-
Convert appropriate models to use
@Observable
- Implement updateProperties() for property-specific logic
- Leverage flushUpdates for smoother animations
The future of UIKit is here, and it's more reactive, more performant, and more delightful than ever. Welcome to the new era of iOS development!
Top comments (0)