NEW: Join my free 100 Days of SwiftUI challenge today! >>

Adding items to an order with @EnvironmentObject

Paul Hudson    @twostraws   

Fully updated for Xcode 11 GM

So, what did we actually just do?

Well, we want a way for users to look at items in the menu and add them to an order. But we also want their ordered items to appear somewhere else in the app.

Environment objects are SwiftUI’s way of sharing data across many places, but by themselves they aren’t a complete solution because it would be easy for different parts of our UI to show different things based on when they loaded. With the ObservableObject protocol we’ve given our Order class the ability to announce that it has changed, and we can now make SwiftUI watch for those announcements and reload the UI.

We just created an instance of Order and placed it into the environment. As a result, any view that comes from ContentView can read that order back out and manipulate it somehow.

We want to add items to our order from the detail screen, so head back to ItemDetail.swift and give it this property:

@EnvironmentObject var order: Order

We haven’t given that a default value, so you might think it will cause problems thanks to Swift’s strict initialization rules. However, the @EnvironmentObject property wrapper does some magic: it allows this variable not to have a value in code, because we’re saying it will already be set in the environment.

When this view is shown, SwiftUI will automatically look in its list of environment objects for something that is of type Order, and attach it to that property. If no Order object can be found then we have a problem: something we said would be there isn’t, and our code will crash. This is just like an implicitly unwrapped optional, so be careful with it.

@EnvironmentObject is another property wrapper in Swift, just like @Published. This one means we get that automatic attachment ability I just mentioned, but also tells SwiftUI to watch the object for any changes and refresh its UI when a change announcement comes through.

Before we add some code to manipulate that order in ItemDetail, we need to fix another previewing problem. You see, we’re now promising the an object of type Order will be in the environment by the time our ItemDetail is shown, and we create and pass that in from SceneDelegate.swift. That works great when our app is running for real, but in the Xcode preview we aren’t launched from the scene delegate – we’re created by that PreviewProvider code at the end of our view files.

This preview code will only get built when we’re in debug mode – when we’re building from Xcode, as opposed to for the App Store. This means it’s safe to put code in there that only relates to our previews, which in this case will be a temporary Order instance so everything works correctly.

struct ItemDetail_Previews: PreviewProvider {
    static let order = Order()

    static var previews: some View {
        NavigationView {
            ItemDetail(item: MenuItem.example).environmentObject(order)
        }
    }
}

That replicates the same setup we have with the scene delegate, which means our preview should work again.

Now that works we can get on with the real deal: adding a button that adds our current menu item to the order. Buttons in SwiftUI have two parts: a title string, and an action closure that contains code to run when the button is tapped.

The Order class already has an add() method that takes a menu item, so we’ll use that for the action. As for the title, we’ll just add some text saying “Order This” – you’re welcome to add more styling if you want!

Put this into the body of ItemDetail, just before the spacer:

Button("Order This") {
    self.order.add(item: self.item)
}.font(.headline)

That’s all it takes to add things to the shared order, but we can’t actually see anything yet.

To make that happen we need to create a new screen that shows the user’s order so far, then put that into a tab bar with our existing content view.

So, press Cmd+N to make a new SwiftUI View, calling this one “OrderView”. Because this needs to have the same Order instance as the rest of our app, you’ll need to give it the same property we gave ItemDetail:

@EnvironmentObject var order: Order

As well as similar code in its preview to make sure that works too:

struct OrderView_Previews: PreviewProvider {
    static let order = Order()

    static var previews: some View {
        OrderView().environmentObject(order)
    }
}

As for the body of our OrderView, this is all stuff you know already:

  • A List view that gives us a scrolling table.
  • Some Section blocks to let us split up our information.
  • A ForEach and a HStack to show our array of order items, displaying both the name and price of each item.
  • A second Section at the end showing a navigation link to place the order.
  • A navigation bar title saying “Order”.
  • “Grouped” styling for the table.

Putting all that together gives us this OrderView struct:

struct OrderView : View {
    @EnvironmentObject var order: Order

    var body: some View {
        NavigationView {
            List {
                Section {
                    ForEach(order.items) { item in
                        HStack {
                            Text(item.name)
                            Spacer()
                            Text("$\(item.price)")
                        }
                    }
                }

                Section {
                    NavigationLink(destination: Text("Check out")) {
                        Text("Place Order")
                    }
                }
            }
            .navigationBarTitle("Order")
            .listStyle(GroupedListStyle())
        }
    }
}

We’ll come back to that shortly, but first we need to make sure it’s working by making it accessible through our user interface.

Further reading

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

Similar solutions…

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift 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!