Simplifying Data Flow and Improving Performance in SwiftUI with Observation

Data flow is a crucial aspect of app development in SwiftUI, but it can often be challenging to manage both in terms of complexity and performance. In this video, we explore how observations can help simplify data flow and improve app performance in SwiftUI. The new framework for data persistence, SwiftData, is also using Observation.

⬇️ download the project from GitHub https://github.com/gahntpo/Swi…

Understanding Data Flow and Observations

Data flow in Swift UI involves various elements, such as property wrappers, observable objects, environments, state, and bindings. Observations provide an alternative approach to data flow, reducing the number of elements involved and making it easier to manage. By using observations, we can focus on two main property wrappers:

  • @State
  • @Bindable

Additionally, the new @Observable macro helps ensure proper data flow.

Simplifying Data Flow with Observations

Observations offer several benefits for data flow in SwiftUI. They streamline the code by eliminating the need for different types of view models and models. Instead, everything becomes a model, making the code more uniform and easier to work with. The @Observable macro simplifies the process by automatically handling data flow. This new approach makes it easier to write and manage model files in SwiftUI.

Improving App Performance with Observations

As apps become more complex with multiple views and shared state, performance issues may arise when views update too frequently. Observations help address this problem by ensuring that views only update when necessary. By properly updating views, we can enhance app performance and avoid lagging or hanging issues. Observations provide a more efficient way to update views based on their actual need for redrawing.

Transitioning to Observations – A Demo Project

To demonstrate the benefits of observations, we walk through a demo project that transitions from the old data flow style to the new observation style. We modify the model files, replacing the existing property wrappers with observations. The demo project showcases the improved performance achieved with the new observation style.

Model Definition and the new @Observation Macro

These are the model definitions for the old Observable protocol style with struct models:

import Foundation  struct Book: Identifiable {  var title: String  var author = Author()  var isAvailable = true  let id = UUID()  let iconIndex: Int = Int.random(in: 0...4) }  struct Author: Identifiable {  var name = "Sample Author"  let id = UUID() }  import SwiftUI  class Library: ObservableObject {  @Published var books: [Book] = Book.examples()   var availableBooksCount: Int {  books.filter(\.isAvailable).count  }   func delete(book: Book) {  if let index = books.firstIndex(where: { $0.id == book.id }) {  books.remove(at: index)  }  } }

Data model definitions should change from struct to class. You should add the @Observable macro to all your model classes:

import SwiftUI import Observation  @Observable class Book: Identifiable {  var title: String  var author = Author()  var isAvailable = true  let id = UUID()  let iconIndex: Int = Int.random(in: 0...4)   init(title: String) {  self.title = title  } }  @Observable class Author: Identifiable {  var name: String  let id = UUID()   init(name: String = "Sample Author") {  self.name = name  } }  @Observable class Library {  var books: [Book] = Book.examples()   var availableBooksCount: Int {  books.filter(\.isAvailable).count  }   func delete(book: Book) {  if let index = books.firstIndex(where: { $0.id == book.id }) {  books.remove(at: index)  }  } }

Because I am now using classes, I need to write my own custom initialisers.

swiftui roadmap

Feeling Lost in SwiftUI?

This SwiftUI roadmap shows you what to learn next.

  • Key concepts at a glance
  • Spot your knowledge gaps
  • Guide your learning path

Ideal for beginners and self-learners.

Defining an Environment object with Observation

You can pass observables in the environment if you want easily pass it through out your whole application. I am defining an EnvironmentValue for the ´Library´ class like so:

extension EnvironmentValues {  var library: Library {  get { self[LibraryKey.self] }  set { self[LibraryKey.self] = newValue }  } }  private struct LibraryKey: EnvironmentKey {  static var defaultValue: Library = Library() }

This allows me to replace all instances of EnvironmentObjects with the following code. The main app will create a new instance of ´Library´ and inject it in the environment like so:

@main struct BookReaderApp: App {  @State private var library = Library()   var body: some Scene {  WindowGroup {  LibraryView()  .environment(\.library, library)  }  } }

Now I can access the ´Library´ instance from anywhere in the app. For example, in ´LibraryView´ I can use the book array like so:

import SwiftUI  struct LibraryView: View {  @Environment(\.library) private var library   var body: some View {  NavigationView {  List(library.books) { book in  NavigationLink {  BookView(book: book)  } label: {  LibraryItemView(book: book,  imageName: library.iconName(for: book))  }  }  .navigationTitle("Observation")  .toolbar(content: {  Text("Books available: \(library.availableBooksCount)")  })  }  } }  #Preview {  LibraryView()  .environment(\.library, Library()) }

Converting from @StateObject to @State

ObservableObject view models have to be owned by views with the help of ´@StateObject´ property wrapper:

import SwiftUI  @main struct BookReaderApp: App {  @StateObject private var library = Library()   var body: some Scene {  WindowGroup {  LibraryView()  .environmentObject(library)  }  } }

With the new Observation you can simply replace ´@StateObject´ with ´@State´:

import SwiftUI @main struct BookReaderApp: App {  @State private var library = Library()   var body: some Scene {  WindowGroup {  LibraryView()  .environment(\.library, library)  }  } }

Passing Bindings with Observation in SwiftUI

I have in my ´BookEditView´ a ´@Binding´ for book:

import SwiftUI  struct BookEditView: View {  @Binding var book: Book   var body: some View {  ...  TextField("Title", text: $book.title)  ...  } }  #Preview {  BookEditView(book: .constant(Book(title: "title"))) }

This is necessary to allow SwiftUI views like TextField and Toggle to create a binding to data properties.

In Observation, instead of ´@Binding´ we have to use ´@Bindable´.

import SwiftUI  struct BookEditView: View {  @Bindable var book: Book  @Environment(\.dismiss) private var dismiss   var body: some View {  VStack() {  HStack {  Text("Title")  TextField("Title", text: $book.title)  .textFieldStyle(.roundedBorder)  .onSubmit {  dismiss()  }  }   Toggle(isOn: $book.isAvailable) {  Text("Book is available")  }   Button {  book.isAvailable = false  } label: {  Text("set unaba")  }   Button("Close") {  dismiss()  }  .buttonStyle(.borderedProminent)  }  .padding()  } }  #Preview {  BookEditView(book: Book(title: "title")) }

Conclusion

Observations offer a powerful solution for simplifying data flow and improving app performance in Swift UI. By adopting observations, developers can streamline their code, enhance performance, and deliver a smoother user experience. We encourage developers to explore and adopt the new observation features in their projects. Stay tuned for more content on Swift data and data flow.

Further Reading:

3 thoughts on “Simplifying Data Flow and Improving Performance in SwiftUI with Observation”

  1. Thanks for the cool article, however, I believe that you don’t need to mark the Library model with @Observable as long as its properties are @Observable.

    Reply
  2. And I have a question: If I want to fully adopt the new Observation framework by embracing State, Bindable and Environment wrappers and discard all other property wrappers (like Binding, although I can still use them as they’re not deprecated yet), how do I migrate the following view to the Observation framework in order to be able to do changes on an array of Bindable objects:

    “`
    struct MyView: View {
    @Binding var books: [Book]

    var body: some View {
    Button(“Add Book”) {
    books.append(Book())
    }
    }
    }
    “`
    I couldn’t find a way to add a book to the array without @Binding property wrapper.

    Reply

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!