Catalystic Converter

Apple this year brought a new technology called Catalyst to the Mac. Going in to WWDC back in June, it was thought that Catalyst (née Marzipan) was going to be the future of the Mac. Essentially it lets developers write an app for iOS using the UIKit frameworks and have it work as a native Mac app. I think the results so far have been hit-or-miss but it's still super early days for this technology.

I'm starting a new project and wondered while updating my project templates – what does making an iOS app work on the Mac actually do to your project? Let's take a look!

Step 1: Make a new, empty iOS project. To make things easy, have Xcode create a git repo. This will set the stage for viewing differences.

Step 2: Add a couple extra targets. Lots of apps nowadays are moving towards a modular approach. This is great because it lets us keep our code separated in such a way that we can easier test our individual pieces and gain reuse by sharing frameworks across apps. I don't know offhand what Catalyst means for my dynamic frameworks and static libraries. Let's add a couple different types of these modules.

Step 3: Commit the current state. We want to capture the repo's place in time.

Step 4: Click the Mac checkbox. In your app's Deployment Info section, check the Mac device target to get going. You'll see the following sheet drop down.

Going Catalyst - hold on to your butts

Moving ahead will surprisingly just make a few changes to your project. There is a new entitlements file added with a couple of entries:

  • com.apple.security.app-sandbox
  • com.apple.security.network.client

I suspect if your app is using iCloud or other permissions that further entitlements may be added here as well, but in our sample project we're not using any of that.

Additionally there are 3 new build settings on only the app target:

CODE_SIGN_ENTITLEMENTS = Catalystic/Catalystic.entitlements;
SUPPORTS_MACCATALYST = YES;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;

There are a couple of interesting things here. First, only app targets need to be updated in build settings. Your other module's code may need some slight changes to source, but no build settings need to change. Super cool.

The other interesting thing is the DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER setting. I don't know what would happen if this was set to NO. Thankfully there's a handy Help button next to our Mac version's bundle identifier. It's help page contains this text:

You can change the Mac version bundle ID format but then you need manually sign the app. Set the Derive Mac Catalyst Product Bundle Identifier (DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER) build setting to NO, add a macOS conditional value for Product Bundle Identifier (PRODUCT_BUNDLE_IDENTIFIER) and Provisioning Profile (PROVISIONING_PROFILE_SPECIFIER), then manually sign the app.

So that's pretty cool. Xcode will automatically give us the maccatalyst prefixed bundle ID as a convenience to signing our apps across the embedded executables like app extensions. And if you want to manage it all yourself (say by replacing the maccatalyst prefix with a .macos suffix) you can do that too.

So there you have it. Just a few steps and you too can have a Mac app from your iPad source base.

WWDC Resources

My Desk Setup

For the last couple of years I've really dialed in my desk situation. I'm really blessed in that I can have essentially the same setup in 2 places – my office near home, and my desk at the Lyft office. In this post I'll go over the gear I've got, what works and what could be better.

My desk

The Computer

I've got a 2018 MacBook Pro. It's a 6-core i9 with 32GB of RAM – and our Lyft project really demands all those resources. Sure, I'd vastly prefer an iMac Pro on my desk(s) but the portability really is great to have on the whole.

The Dock

Here's the brains of the rest of the operation. I have a CalDigit TS3 Plus which lets me plug one cable into my laptop. From there it breaks out to video, USB, audio, and ethernet. I friggin love this thing. I used to have to plug in power, video, USB, all in separate cables. This box even sends power to my computer so it's a true single-cable plug and go.

The Display

For many years now I've been able to have a 27" 4k monitor on my desk. I've used Dell (pictured here) for quite some time – it's the P2715Q model. I know it's not 5k and the resolution could be better, but 5k monitors are a lot more expensive than I was willing to pay at the time.

I got the Autonomous monitor arm last year and it's been quite nice to have the display floating. There are probably better arms (definitely more expensive ones), but this one has been just fine for my entry in to the bigger world of monitor arms.

The Keyboard

For a long time I used the Microsoft Sculpt ergonomic keyboard, but recently I switched to the Kinesis Advangage2 LF. It's still got the nice split layout that helps with ergonomics, and the key wells help my finger travel be overall smaller. I was feeling a fair bit of pain in my right forearm and with the Advantage2 that has subsided quite a bit.

Fair warning: the layout on the Advantage2 takes a lot of adjusting to. I've been using it for a little over a month and still adjusting.

The Desk & Chair

The desk I have shown is the Autonomous SmartDesk2. It's adjustable to be tall for standing or shorter for sitting. I love the control panel (especially compared to my other desk at the Lyft office) because the programmable buttons let me press once and it will adjust to the defined height. On my other desk I have to keep the button held for it to move. There's plenty of space for all that I put on the surface, and when it's standing up isn't wobbly or rickety at all. Highly recommended - especially for the price. It's one of the least expensive powered desks I've seen.

I also got the Autonomous ErgoChair 2 for the times when my desk is lowered. It's pretty comfortable – though the Steelcase Gesture that Lyft provided is much more comfortable but a lot more money.

You can follow this link to get at least $15 off your order with Autonomous (full disclosure: this is a referral link so I get something back too).

Miscellaneous

  • I love, love, love the Magic Trackpad 2 for all my mousing needs.
  • The HomePod is a great little speaker that more than fills the space.
  • The Logitech HD Pro Webcam C920 is what I use for video calls. It more than gets the job done for me.
  • I use the Audio Technica ATR2100 USB mic for recording podcasts and video calls. I get tons of compliments of how good my audio is.
  • I also under-mounted a power strip on the front-right side of the desk (under the HomePod) so that I didn't have a bunch of cords laying on the ground.

That's my setup. I think the big thing missing from the image above is a little filing cabinet. It's not crucial but I currently stash papers & miscellaneous little stuff behind the comfy chair in the corner (not pictured).

Maverick Apps

I've really liked working on Maverick over the past few days. I want to be blogging more and the improvements I've been making will definitely lead to that end. I deployed my metadata update I wrote about and things are showing no sign of issues. So now I'm on to the next step: better authoring and site maintenance tools.

For a long time I have wanted to make a set of Mac & iOS apps that would let me more easily manage my site. But I've had soem struggles figuring out how best to structure things (I'm starting with the Mac apps because I've long been an aspiring Mac developer). I could make one app that can manage the whole site, and author the needed textbundle files. Or, I could make a couple of different apps: one for basic site maintenance that would facilitate taking in new textbundles from any app you wanted to write in, and another for actually doing the writing.

I don't think there's an app out there right now that does textbundles really well at an affordable price. I've used Ulysses but it has its own syntax for things and you'd have to export files to get them out as a textbundle. Currently I'm a BBEdit user and while I do like it for many things, I think the UX on having markdown files with inline assets could be improved. I'd like to build a really nice textbundle editor as a standalone app.

I don't have any timeline for either of these apps, and honestly I'm not sure which one to build first. I think the textbundle editor may see a broader audience (since I'm the only one using Maverick as a blog engine that I know of). So maybe I'll give that one a go first, especially since I can wrangle up scripts to help with publishing kinks. Okay, I've convinced myself 🙂

I do also want to play with Catalyst, so this would be a really nice opportunity to use iOS to make iPhone, iPad, and Mac apps all with one codebase. Let's go.

Better Procrastination Through Yak Shaving

The first step in any new project is to create the project (more on what the project is tomorrow). That's where yesterday's yak shaving adventure comes in.

via GIPHY

The app I'm starting in on is for the Mac. I have a project template for iOS already but it didn't support a Mac app. I have really come to like using Xcodegen to generate my Xcode files and not check them in to source control. So I decided to update my template to support a Mac app.

Instead of starting with the assumption of always wanting a Mac app, I updated the new-module command to output a framework, an iOS app, or a Mac app. This will let me have maximum flexibility for my project. I went about it by creating a shell project from Xcode, extracting the build settings using James Dempsey's excellent Build Settings Extractor, and modified some files to tokenize the name based on input.

I'm pretty happy with the result, even though I've been churning over my new app idea in my head. This was a fun distraction, but now the real work will begin.

I'm at the part of a new project where, before actually starting on the project, I need to update all my templates. Currently updating my iOS project template to be able to make a Mac or iOS app.

I'm working on my Maverick upgrade that I wrote about last night, and also moving up to using Swift 5.1.1 in Docker. The infrastructure upgrades for Docker/Swift/Vapor are proving the most difficult part of the project.

Rethinking Metadata

Last year when I started thinking about moving away from Ghost as my blogging platform, the first place I turned was Jekyll. It's really popular and can create great websites. Plus it's nerdy, which is usually right up my alley. I found an exporter from Ghost to Jekyll which worked quite nicely. But one thing that did rub me wrong was that posts were separated from their associated media assets. This is why I started looking at textbundle packages.

I really like encapsulating each post in a single file, with assets and text contained inside. And, because I went from Ghost to the Jekyll format directly, I left the metadata at the top of a post's text.md file inside. The metadata (or FrontMatter) contains things like the post title, date, tags, etc. As it is currently, there's a chunk of yml at the top of each of my markdown files containing this metadata. I'm starting to rethink this approach now, for a couple of reasons:

  • I want to build a textbundle editor that can work not just with files for my Maverick-powered blog, but anyone who wants to write using that format.
  • Currently I have to open the main text of each post file to get at its metadata. While I haven't done any benchmarking on the server, I suspect this isn't the most optimal way of doing things. This unknown overhead has made me hesitant to implement some new features.

So, with the decision seemingly made that I want to investigate moving metadata somewhere else, where should I start putting it? Turns out that textbundles have an info.json file inside. I'd always just hard-coded its contents when I'm creating new bundles and moved on. But when I look at the documentation for that file I found this gem:

Additionally, the meta data file can contain application-specific information. Application-specific information must be stored inside a nested dictionary. The dictionary is referenced by a key using the application bundle identifier (e.g. com.example.myapp). This dictionary should contain at least a version number to ensure backwards compatibility.

Turns out my custom metadatas source has been in front of my face all along. I'll need to write a script to update all of my existing posts (which I don't anticipate will be too difficult), and from there update my Maverick code to handle the new location of the metadata. I sure hope this will only take a few days to get done.

It's been a bit since I did much significant work on Maverick and I'm really excited to dig back in.

Radio Silence

I never intend for droughts in making new posts. Often times I'll make a post then not even realize how much time goes by before the next one. Lately finding things that I consider worthwhile to post has been difficult. Work has been crazy, I took on a side project for a client, and we got our kids in the swing of school for the first time (Atticus is in full-day kindergarten and Finnian 2 mornings of preschool per week).

I usually like writing about things that I hope will help people in some way, or at the very least will help me get to a destination that I'm seeking for (hence the SwiftUI posts most recently). But the problems I've had to solve at work and for the side project are both specific to those codebases and probably not interesting to others or things I shouldn't be sharing about. So I go silent.

I don't like being silent. I can easily find myself in funks where I just feel down for no real reason. When that happens I can be short with my kids, quick to voice displeasure or other negative thoughts at work, and get in to a pattern of feeling stuck. Like I'm not able to think effectively. I like writing. I like sharing tips with fellow devs. Sometimes I even like sharing bits about more personal things. And when I'm not, that can easily lead to a pattern of inactivity that I don't like.

So when today, on November 1, I saw several people talk about their plans to blog daily during the month. I won't make that commitment, since that's usually a recipe for me to not follow through. But I do plan on writing more. About what, I'm not sure.

At the very least, I have some ideas of how to make my blogging engine better. And when I start doing that, it's goo source material. I'd also love to finish my Scorebook update for iOS 13. And who knows what else. So, let's make this a good November.

And if you have any ideas of topics you'd like to see me talk about, please drop me a line.

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!