Examples of My SwiftUI Struggles

In my last post I talked about some of the struggles I'm having getting up to speed with SwiftUI. Let's dive in to a couple of examples.

Magic Environments

To make my Settings form view, I found a suer helpful blog post by Majid Jabrayilov where he goes over the basics of building a Form with SwiftUI and binding some controls to UserDefaults. He outlines a type called SettingsStore, which I also implemented. I diverged from the tutorial to try my hand at making a property wrapper type to handle fetching from and saving to UserDefaults. When it came time to wire a switch up with its associated value is where I slowed quite a bit. The post has this line of code in the SettingsView type:

@EnvironmentObject var settings: SettingsStore

What this says is that somewhere in the SwiftUI environment will be a SettingsStore instance. What's missed in the post is how the thing gets there in the first place. It turns out that an ancestor of my SettingsView needs to inject the property into the environment when also constructing the view. Here's what that looks like in Scorebook's case, where the SettingsView is visible as a tab item in the tab bar.

let hostingController = UIHostingController(rootView: SettingsListView().environmentObject(SettingsStore()))

So after putting my SettingsView inside of the UIHostingController, I also need to chain an environmentObject(_:) call to put the store in the environment. When I failed to do this at first I got a crash, and when I tried constructing the environment and passing it in via constructor injection I couldn't set the variable. It has to be done through the environment. For my taste, it's feeling a bit magic-y at the moment but I'm hopeful that's due to the fact that I don't know what I'm doing very well just yet.

Conditional View Navigation

I've got a button on my settings screen to send me an email. In UIKit land I have an action on the button and check that the device in question can send the email, then it presents the mail compose screen. If it cannot, then I show an alert. Easy enough.

But in SwiftUI land it's not so easy (at least to my imperatively wired mind). Thankfully I found a helpful StackOverflow question to help me get started with sending the message. I honestly don't quite understand exactly how the UIViewControllerRepresentable protocol works yet but I think the code in that answer gives me a good starting point. One thing that is kind of breaking my Objective-C brain is the usage of _ when setting the initial value of a @Binding property. It's like setting the instance variable used to be in days of old.

The trouble comes in triggering the mail view from my settings screen. I think I've gotten it working with a dual-boolean option (again, somehow managed via magic). Here's what I've come up with (and this is working-ish in the simulator):

struct SettingsListView: View {
    // Mail
    @State var mailResult: MailResult?
    @State var isShowingMailView = false
    @State var isShowingMailErrorAlert = false

    var body: some View {
        NavigationView {
                Section(header: Text("Feedback".uppercased())) {
                    Button(action: {
                        if MFMailComposeViewController.canSendMail() {
                            self.isShowingMailView.toggle()
                        }
                        else {
                            self.isShowingMailErrorAlert.toggle()
                        }
                    }) {
                        Text("Send Email")
                    }
                }
            }
            .navigationBarTitle(Text("Settings"))
            .alert(isPresented: $isShowingMailErrorAlert) {
                Alert(title: Text("Unable to send mail"),
                      message: nil,
                      dismissButton: .default(Text("OK")))
            }

            if isShowingMailView {
                MailView(isShowing: $isShowingMailView, result: $mailResult)
                    .transition(.move(edge: .bottom))
                    .animation(.default)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

I haven't tried this on a device yet as I haven't installed iOS 13 at this point on anything (those days are nearing an end) but the Alert at least shows in the simulator. What I find kind of gross about this is the fact that I have separate @State booleans for showing or presenting content. How do those get reset when I dismiss the things they're presenting? It sounds like SwiftUI handles that but I don't know how.

I also don't like taking what was one single method in my UIKit code and scattering its pieces in a declarative spew of state all around my view code. I'm hoping there's a nice, Combine-y way of doing this that can let me isolate the states in clearer fashion.

Lots to learn still, which makes this exciting and frustrating all at the same time!

Struggling With SwiftUI

SwiftUI was the big thing coming out of WWDC in June. It was the toast of the town and I saw lots of comments about how developers "wanted to rewrite their entire app" using it. Heck, I was one of those voices too. I think it's clear that SwiftUI is the future of writing interfaces for Apple's platforms.

When it came time to start working on Scorebook's iOS 13 update I decided to take a pragmatic approach. After all, my app's users won't care if the UI is SwiftUI or UIKit. They won't care if it's written in Swift or Objective-C, built in code or Interface Builder. They want the app to work, and to work well. How it gets there is my concern as the developer. So instead of doing the fun SwiftUI update I started in on things that they will care about. So I started with Dark Mode.

There are some rough spots on the main screens but when I got to Settings, everything was wrong. "Perfect!", I thought. I wanted an easy way to get started with SwiftUI, and with SwiftUI I get dark mode support for free. I built my Settings screens as static UITableView subclasses and there's a bunch of logic that can be factored out and made simpler to handle switch taps as well as the actions like sending a support email or triggering the rating dialog.

Getting the bare form up and running was pretty easy all told. I'm not running Catalina yet so I don't get the live previews. I had to shim the SwiftUI view into my app's tab bar, which I accomplished with a UIViewController which embeds a UIHostingController that has my SettingsListView as its root view. I did this to preserve getting the tab item for the Settings tab from the UIKit object rather than trying to see if the SwiftUI view can provide it. Then came binding my switches to UserDefaults, and I started feeling out of my element.

When I started writing this post it got me pouring out many more words than I thought would be triggered, so I'm going to break this up into two parts. The next part I'll go over some concrete places where I've had issues and the answers I arrived at (if I got there, that is – this is all very much a work in progress).

Xcoders talk: Don't Fear the Project

Last week I gave a talk at our Seattle Xcoders meetup group. The topic was Xcode projects and I discussed the things that make up a project (targets, schemes, etc) and dissected project files themselves a little bit. The meat of the talk was then going through using XcodeGen to generate Xcode projects programmatically so you can stop checking them in to source control. This is a talk I've wanted to give for over a year and I'm glad I finally got around to it.

The really cool thing that happened (which I was not expecting) is the level of questions & discussion after I wrapped up the talk. I've not seen as many questions come out of a talk as this one had. That can generally mean 1 of 2 things. Either 1) I could have done better at explaining things or 2) The topic generated that much genuine interest in people. From what I heard it's more the latter than the former though there are definitely areas that I could have expanded on.

Many of the questions that arose came in the area of using flags for individual files in generated Xcode projects (think of toggling ARC on a per-file basis back in the day). I didn't know if that was possible, since I mainly use the file system to organize my sources and tell XcodeGen to grab files from the correctly organized folders. Turns out it is possible to do fancy things like define custom regexes to include certain files and then apply build phases or compiler flags to those matched sources (documentation). XcodeGen is even more powerful than I thought originally!

Were I to give this talk again at a conference or something I'd probably expand it to include a demo where I take an Xcode project that's already existing and adapt it to use XcodeGen, but for this talk I wanted to avoid live coding. The main reason that I delayed giving the talk at all was because I wanted to build a conversion tool. But in the end when I tried it out it was both harder than I was expecting to be, and I didn't think that building a complicated tool for someone to run once and move on from was a great use of my limited time.

We recorded video for the talk and I'll post a link when it's ready. For now, the slides can be found here.

🔗 Where is Windmill on the iPhone?

Markos Charatzas, writing about the state of his Windmill apps:

More importantly. Apple took the stance that the Command Line Tools Package is only meant to be used by developers in-house and not by 3rd parties to provide support for continuous integration systems - continuous delivery in the case of Windmill.

As developers building things for Apple's platforms we are largely operating within the walls set forth by Apple itself. There could be a whole industry of products built to support making development easier (think of things like Fastlane but easier to use).

Windmill is one such tool that looks really interesting. It currently runs on the Mac and monitors your local git repository for changes, building and running your app to make sure things don't break while you're developing. I've yet to use it personally but more importantly think it's something that should be allowed to exist. Apple should be in the business of fostering creative solutions to lots of different problems instead of shutting them out.

If Apple's stance is that it's okay to build workflows and internal apps on these command line tools, then why is it not okay to try to make a product out of them? It seems to me that forcing every place to deal with these issues individually will waste a lot of time and add tons of friction to workflows that could be eased with these tools.

This is one case where I wish Apple would be more conversational and clear. Markos has commendably been trying to get a conversation started. But oftentimes these "discussions" are one sided, with interested third-party developers being the ones talking and patiently waiting for Apple to respond. I'd love to see that change someday but the change will have to be on Apple's side, not ours.

Reverting SwiftPM for Tools

For the last few months I've been using Swift Package Manager to manage my tooling dependencies for iOS projects. It's a technique that I learned about from Orta Therox and I adopted it in Scorebook and even made an app template that used it too. The advantages were great, namely that I didn't have to manage any version scripts or vendor tools directories. It just worked.

But over the last couple of weeks things started to fall apart a little bit. I downloaded Xcode 11 after releasing my new Scorebook update so that I could start digging in to all the cool stuff iOS 13 had to offer. I also checked my tools to see if they had new versions available (I'm using 2 different tools at the moment – Xcodegen and swift-sh). I went to update them and that's when I got my first error: dependency graph is unresolvable.

I did some digging and what seems to have happened is that my tools have conflicting dependencies. Bummer!

I had some options at this point: 1) figure out what the conflicts were and solve them by forking and opening pull requests on one or both of Xcodegen and swift-sh. 2) Create separate tools modules for each tool. 3) Roll back using SwiftPM for my tooling needs and build scripts to maintain each tool.

#1 would have taken a long time to resolve and didn't seem super worth my while. #2 would have been slightly more tolerable but maintaining that directory structure didn't seem super worthwhile, and would have likely necessitated scripts for each tool anyway. #3 felt like the right choice in the end, and that's what I went with.

The upshot of going the way that I did is that I can always ensure that I'm using a binary version of each tool, and if I come across one that's not written in Swift I can add that to my project with the same amount of effort as a Swift-based one. Using SwiftPM for tooling like this was always kind of a hack. It was meant to manage dependencies of your project and to easily bring in libraries to your codebase.

I'd love for it to work again someday, but for now I'm mostly glad to have gotten over this hurdle so I can move on to more interesting problems like adding Dark Mode to Scorebook.

I pushed the change I made to my iOS template, and if there's a better way to write the bash scripts I'm all ears (I'm sure there is, since I'm a bash noob still). Check out the updated code here.

Scorebook 2.0 Preview

I've been hard at work over the last few months on a big update to Scorebook. It's going to have a whole new interface, work on the iPad, and support the Dynamic Type accessibility feature.

The first thing that is different is the main UI. There's now a tab bar which gives easy views to your gameplays, the games you've played, and the people you've played them with. It's a really nice way to visualize the games you've been playing.

The big driver of the big UI changes was putting Scorebook on the iPad. On the bigger screen you'll get a split view, with a list of things on the left, and a detail view on the right. And with iCloud sync (which is now enabled by default) you'll be able to see your stuff on any device. It's really great.

From Scorebook's initial 1.0 launch until now, I'd been using the Avanir font across the app. I really do like how it looks but the time has come to move to the iOS system font – called San Francisco. The main reason I did this is because I wanted to support users who like the text to be a bigger (or smaller) size than the default. Accessibility is a big deal. Without it, an app is harder to use or even unusable by people who rely on those features.

With all of the work that's gone into the app, and now with being on a whole new device, I've also decided to raise the price to $4.99 USD. I'm not sure what the higher price will do for sales but I'm hopeful that people will see value in the additional work I've put in to Scorebook.

While the release date isn't set yet, I'm in the final phase of the project – screenshots. Apple has so many different devices nowadays that this is actually a really complicated part of making an app (especially as a solo developer). But I'm pleased to say that I expect to wrap the screenshots tonight and submit the app for release later this week.

Scorebook 2.0 will be a free update to all users. If you want to get in at the existing $2.99 price you can buy it on the App Store today.

Introducing the Xcoders Podcast

I've been working with Liz Marley to create a podcast for the Xcoders and we finally got our first episode out the door yesterday! Our hope is to record on a monthly basis and we'll be talking about all things Xcoders-related. As the two of us are primarily Seattle Xcoders that's where our focus will be to start. My hope is to branch out and talk Eastside and Vancouver BC as well!

You can subscribe to the feed directly at https://xcoders.org/podcast.xml. Our submission to iTunes is currently pending and I'll update this post with that link when it gets approved.

If you have any questions, comments, or topics you think we should cover, please do drop us a line at info@seattlexcoders.org

If you're an iOS developer, or want to be an iOS developer, and are interested in being mentored please fill out this form.

I'm partnering with Nic Laughter to start something really cool 🙂

WWDC 2019 Recap

I'm super thankful that I got to travel to San Jose last week for Apple's WWDC conference. Granted, I didn't get a ticket, but I did go to AltConf (which was right next door to the big show). This was my first time being in town for WWDC and it was a blast. I traveled with my buddy Seth and we took in the keynote together, as well as all the podcast recordings we could fit in.

Monday, of course, was Mac Pro Day, Tuesday we got to sit in the balcony for Gruber's interview of Craig and Joz, and Wednesday was RelayFM's Connected. Following the Connected show on Wednesday we hit up the Ritz for James Dempsey and the Breakpoints, which was a great show at a small club.

It really is true that Apple nerds take over the city during WWDC week. There were lots of Xcoders hanging around (particularly at the Fairmont and Cinebar). And we were all buzzing with the releases from Apple. We came in to the week thinking that Catalyst (née Marzipan) would be on the road to taking over the Mac and hoped for a Mac Pro preview. Boy did we get a Mac Pro preview! It looks like an absolute monster of a machine – complete with a monitor that ships without a stand. And Catalyst looks better than anyone had hoped for. But that wasn't the news of the week.

Nope, that belonged to SwiftUI. In one fell swoop and without saying so explicitly, Apple deprecated AppKit and UIKit. We now have our drawing framework that can scale from the little watch to a big screen TV, and an editor that looks unbelievably great. It's like the good parts of Interface Builder (the visual feedback) without the crappy parts of Interface Builder (the xml files). I can't wait to play with it more.

I'm really looking forward to Xcoders this week. We're doing WWDC lightning talks. It's a great opportunity for folks to give a talk under a low pressure situation, and we have 7 talk scheduled with 3 new speakers. I'm slated to give one and decided to do it on Xcode's integration of Swift Package Manager.

Time to start working on the talk!

Tonight I got bested by a home improvement project that tutorials call "easy".

I need to remember when I'm talking about a concept to not call it easy, because the reader may be at a level where it's not easy for them. The last thing I want to do is discourage someone who's trying to learn.