UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Sharing an observed object with a new view

Paul Hudson    @twostraws   

Classes that use @Observable can be used in more than one SwiftUI view, and all of those views will be updated when the properties of the class change. SwiftUI is really smart here: it will only update those views if they actually used the properties that changed.

In this app, we’re going to design a view specially for adding new expense items. When the user is ready, we’ll add that to our Expenses class, which will automatically cause the original view to refresh its data so the expense item can be shown.

To make a new SwiftUI view you can either press Cmd+N or go to the File menu and choose New > File. Either way, you should select “SwiftUI View” under the User Interface category, then name the file AddView.swift. Xcode will ask you where to save the file, so make sure you see a folder icon next to “iExpense”, then click Create to have Xcode show you the new view, ready to edit.

As with our other views, our first pass at AddView will be simple and we’ll add to it. That means we’re going to add text fields for the expense name and amount, plus a picker for the type, all wrapped up in a form and a navigation stack.

This should all be old news to you by now, so let’s get into the code:

struct AddView: View {
    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = 0.0

    let types = ["Business", "Personal"]

    var body: some View {
        NavigationStack {
            Form {
                TextField("Name", text: $name)

                Picker("Type", selection: $type) {
                    ForEach(types, id: \.self) {
                        Text($0)
                    }
                }

                TextField("Amount", value: $amount, format: .currency(code: "USD"))
                    .keyboardType(.decimalPad)
            }
            .navigationTitle("Add new expense")
        }
    }
}

Yes, that always uses US dollars for the currency type – you’ll need to make that smarter in the challenges for this project.

We’ll come back to the rest of that code in a moment, but first let’s add some code to ContentView so we can show AddView when the + button is tapped.

In order to present AddView as a new view, we need to make three changes to ContentView. First, we need some state to track whether or not AddView is being shown, so add this as a property now:

@State private var showingAddExpense = false

Next, we need to tell SwiftUI to use that Boolean as a condition for showing a sheet – a pop-up window. This is done by attaching the sheet() modifier somewhere to our view hierarchy. You can use the List if you want, but the NavigationStack works just as well. Either way, add this code as a modifier to one of the views in ContentView:

.sheet(isPresented: $showingAddExpense) {
    // show an AddView here
}

The third step is to put something inside the sheet. Often that will just be an instance of the view type you want to show, like this:

.sheet(isPresented: $showingAddExpense) {
    AddView()
}

Here, though, we need something more. You see, we already have the expenses property in our content view, and inside AddView we’re going to be writing code to add expense items. We don’t want to create a second instance of the Expenses class in AddView, but instead want it to share the existing instance from ContentView.

So, what we’re going to do is add a property to AddView to store an Expenses object. Please add this property to AddView:

var expenses: Expenses

And now we can pass our existing Expenses object from one view to another – they will both share the same object, and will both monitor it for changes. Modify your sheet() modifier in ContentView to this:

.sheet(isPresented: $showingAddExpense) {
    AddView(expenses: expenses)
}

We’re not quite done with this step yet, for two reasons: our code won’t compile, and even if it did compile it wouldn’t work because our button doesn’t trigger the sheet.

The compilation failure happens because when we made the new SwiftUI view, Xcode also added some preview code so we can look at the design of the view while we were coding. If you find that down at the bottom of AddView.swift, you’ll see that it tries to create an AddView instance without providing a value for the expenses property.

That isn’t allowed any more, but we can just pass in a dummy value instead, like this:

#Preview
    AddView(expenses: Expenses())
}

The second problem is that we don’t actually have any code to show the sheet, because right now the + button in ContentView adds test expenses. Fortunately, the fix is trivial – just replace the existing action with code to toggle our showingAddExpense Boolean, like this:

Button("Add Expense", systemImage: "plus") {
    showingAddExpense = true
}

If you run the app now the whole sheet should be working as intended – you start with ContentView, tap the + button to bring up an AddView where you can type in the various fields, then can swipe to dismiss.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.