NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

Adding options with a context menu

Paul Hudson    @twostraws   

We need a way to move people between the Contacted and Uncontacted tabs, and the easiest thing to do is add a context menu to the VStack in ProspectsView. This will allow users to long press on any person in the list, then tap a single option to move them between the tabs.

Now, remember that this view is shared in three places, so we need to make sure this context menu looks correct no matter where it’s used. The easy option is to use a ternary operator when setting the button’s title, and so we could append a context menu like this one to the VStack:

.contextMenu {
    Button(prospect.isContacted ? "Mark Uncontacted" : "Mark Contacted" ) {
        prospect.isContacted.toggle()
    }
}

While the text for that is OK and the context menu displays correctly, the action doesn’t do anything. Well, that’s not strictly true: it does toggle the Boolean, but it doesn’t actually update the UI.

This problem occurs because the people array in Prospects is marked with @Published, which means if we add or remove items from that array a change notification will be sent out. However, if we quietly change an item inside the array then SwiftUI won’t detect that change, and no views will be refreshed.

To fix this, we need to tell SwiftUI by hand that something important has changed. So, rather than flipping a Boolean in ProspectsView, we are instead going to call a method on the Prospects class to flip that same Boolean while also sending a change notification out.

Start by adding this method to the Prospects class:

func toggle(_ prospect: Prospect) {
    objectWillChange.send()
    prospect.isContacted.toggle()
}

Important: You should call objectWillChange.send() before changing your property, to ensure SwiftUI gets its animations correct.

Now you can replace the prospect.isContacted.toggle() action with this:

self.prospects.toggle(prospect)

If you run the app now you’ll see it works much better – scan a user, then bring up the context menu and tap its action to see the user move between the Contacted and Uncontacted tabs.

We could leave it there, but there’s one more change I want to make. As you saw, changing isContacted directly causes problems, because although the Boolean has changed internally our UI has become stale. If we leave our code as-is, it’s possible we (or other developers) might forget about this problem and try to flip the Boolean directly from elsewhere, which will just cause more bugs.

Swift can help us mitigate this problem by stopping us from modifying the Boolean outside of Prospects.swift. There’s a specific access control option called fileprivate, which means “this property can only be used by code inside the current file.” Of course, we still want to read that property, and so we can deploy another useful Swift feature: fileprivate(set), which means “this property can be read from anywhere, but only written from the current file” – the exact combination we need to make sure the Boolean is safe to use.

So, modify the isContacted Boolean in Prospect to this:

fileprivate(set) var isContacted = false

It hasn’t affected our project here, but it does help keep us safe in the future. If you were wondering why we put the Prospect and Prospects classes in the same file, now you know!

Hacking with Swift is sponsored by NSSpain

SPONSORED Announcing NSSpain 2020: Remote Edition! An online, continuous conference for iOS developers. We’ll start on Thursday and finish on Friday, with talks, activities, and lots of fun for 36 hours, non-stop. Sound good? Join us!

Find out more

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

BUY OUR BOOKS
Buy Pro Swift 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 (Vapor Edition) 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 Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.3/5

Link copied to your pasteboard.