[SC]()

iOS. Apple. Indies. Plus Things.

Typed Payloads in SwiftUI using NSUserActivity

// Written by Jordan Morgan // Jul 4th, 2022 // Read it in about 2 minutes // RE: SwiftUI

SwiftUI and NSUserActivity tend to go together like peanut butter and…not jelly1. Around since iOS 8, good ol’ user activity does a lot in iOS. It ranges from handoff, to spotlight search, universal links, quick note, Siri Shortcuts…and that’s not even all of what it can be used for.

And so it is, to restore state in SwiftUI, you’re left vending a user activity at times and it can feel a little brutal to use:

struct BooksView: View {
    @State private var selectedBook: Book? = nil

    var body: some View {
        BooksList(selected: $selectedBook)
        .onContinueUserActivity(BookActivityType) { userActivity in
            if let userInfo: [AnyHashable:Any] = userActivity.userInfo,
               let author = userActivity["author"] as? String,
               let publishDate = userActivity["publishDate"] as? Date,
               let title = userActivity["title"] as? String,
               let rating = userActivity["rating"] as? Int {
                    // Either make a book, or set a bunch of @State vars...
               }
        }
}

The process of diving endlessly through a grabbag dictionary has always felt a bit risqué at best, and a runtime exception at worst. Further, the process of getting there wasn’t much better:

.userActivity(BookActivityType) { userActivity in
    userActivity.addUserInfoEntries(from: ["author":book.author])
    userActivity.addUserInfoEntries(from: ["publishedDate":book.publishedAt])
    userActivity.addUserInfoEntries(from: ["title":book.title])
    userActivity.addUserInfoEntries(from: ["rating":book.rating])
}

Of course, if the world was pure and we lived in simpler times, ideally we’d just forgo the whole dictionary bookkeeping ceremony and stick a model in there. After all, that’s all the data we’re after anyways, yeah? Something like this:

.userActivity(BookActivityType) { userActivity in
    userActivity.addUserInfoEntries(from: ["model":book])
}

Alas, we know how this story ends. We can’t have nice things, and trying to do this would result in a crash, Thread 1: "userInfo contained an invalid object type". And indeed, the exception is warranted - we’re restricted to using only primitive types and a trip to the documentation over user activity will tell us as much:

Discussion Each key and value must be of the following types: NSArray, NSData, NSDate, NSDictionary, NSNull, NSNumber, NSSet, NSString, or NSURL.

And so it is, I’ve often said “You don’t want to stick models into user info when restoring activities, because it won’t work” and I’ve been right along.

record scratch

Until I wasn’t! Or, to be clear - I’ve been half right, and half wrong after discovering some API added in iOS 14 that I missed. Exclusively in SwiftUI, you can absolutely use models by way of adopting Codable protocols with your user activities.

Here it is:

struct BooksView: View {
    @State private var selectedBook: Book? = nil

    var body: some View {
        BooksList(selected: $selectedBook)
        .onContinueUserActivity(BookActivityType) { userActivity in
            if let book = try? userActivity.typedPayload(Book.self) {
                self.book = book
            }
        }
}

My goodness, so much cleaner.

Plus, saving it affords us the same conscise approach:

struct BookEditorView: View {
    @StateObject private var book: Book = .default

    var body: some View {
        AnotherView()
        .userActivity(BookActivityType) { userActivity in
            try? userActivity.setTypedPayload(book)
        }
    }
}

Amazing! So, how does it work? Did Apple somehow do some black magic to shove a model, made of our own volition, into user info?

Well, kinda.

What it’s actually doing is leveraging the code contract that Codable provides. Using it, the system matches the user info keys according to the coding keys from the Codable type. So basically, it takes each property and keys them in the dictionary for us. And, in the other direction, it pulls them out and initializes your model.

I love finding stuff like this. If you’re using state restoration with SwiftUI, this is a lovely way to do it.

Until next time ✌️

  1. I recently read that peanut butter and jelly is a very American sandwich - is this true?! 

···

My Template to Research, Design and Ship iOS Apps

// Written by Jordan Morgan // Jun 27th, 2022 // Read it in about 2 minutes // RE: The Indie Dev Diaries

Today I’m sharing a template I use to help gather my thoughts anytime I start on an app:

Craft Docs template showing a guide to create iOS apps.

You can get it here. It’s a Craft Doc template, so you can either duplicate it in Craft or export it to another app of your choosing to use it.

Before I explain the gist of it, I want to be clear on when I use it.

A lot of apps start with pure excitement over an idea. Don’t lose that! Cracking open Xcode right away (especially in the age of lightning fast U.I. development with SwiftUI) is a wonderful way to test a quick idea, or let some creative energy out. Keep doing that.

But, what happens next? For me, that’s when I use this guide to feel out the idea, and sorta validate if I want to actually spend more time on it.

The Flow

The whole point of the template is to carve out the idea, see what else is out there and then finally build it if it seems worthwhile. To do that, I follow these steps (sometimes not in this exact order, but close):

Formation
After I’ve hacked around in Xcode and I’m still excited about the idea, I try to figure out what exactly it is. What’s the idea explained in one sentence? If I had to put it on the App Store, what would the title and subtitle look like?

All are subject to change, but it’s a useful thought exercise for me.

Craft Docs template showing a guide to forming an idea for an iOS app.

Research
Okay, so I know what it might be - what else is out there? Are there holes in the market I could come in and plug? Of the similar apps, what are they doing well, not well, how are they priced?

This helps me figure out monetization and any other key features I might need to look at.

Craft Docs template showing a guide to researching iOS apps.

Mocks
Now, it’s the “messy middle”, where I just sketch out the flow in very low fidelity designs. I want to have an idea of all of the screens I need for a lean MVP to ship. For me, that’s key - I want to think about every screen I’m going to show (which usually correlate to a view controller in code) because it helps me cut scope.

Craft Docs template showing a guide to mocking iOS apps.

Spec
Here, I write down a focused set of tasks to get version 1 out of the door. I also leave a little room for some “secret sauce”, for example - maybe a custom view controller transition or two.

Craft Docs template showing a guide to specifying an MVP for iOS apps.

Design
This step is hit or miss for me, sometimes I skip it altogether. But, I may flirt with making a high fidelity design in Sketch for the app. It helps me really think about those “Well what happens when I press this” kinda situations.

Plus, I know the features I’m aiming for now. I’ve got an MVP spec’d out here, so by virtue of that - I also know what I’m designing.

Craft Docs template showing a guide to designing iOS apps.

Build
Not much to say here - time to put code to compiler.

Craft Docs template showing a guide to building iOS apps.

Prepare
The time between starting the project and launching it is a great time to get the word out. This step has some thoughts on how to do that.

Craft Docs template showing a guide to researching iOS apps.

Launch
It’s here, launch day. I’ve got reminders covering things to do weeks before, the day before and day of.

Craft Docs template showing a guide to launching iOS apps.

Going Forward
Here, you’ve outgrown this document. If all went well, you’ve got a new project to carry forward and improve.

And fin.

I hope this helps! For some folks, a list of stuff like this in a template sounds stressful and rigid. For others, like me, I feel lost and frazzled if I don’t have a somewhat prescriptive set of steps to follow and see where I’m at.

Until next time ✌️

···

The Best Change to Come From W.W.D.C. 2022

// Written by Jordan Morgan // Jun 20th, 2022 // Read it in about 2 minutes // RE: The Indie Dev Diaries

The most impactful change to come out of W.W.D.C. had nothing to do with APIs, a new framework or any hardware announcement. Instead, it was a change I’ve been clamoring for the last several years - and it’s one that’s incredibly indie friendly. As you’ve no doubt heard by now, I’m of course talking about iCloud enabled apps now allowing app transfers:

If you’ve visited my internet home or have been my eFriend for the past few years, you know this is personal for me. I’ve never been one for open letters, but oh how I’ve constructed a pointed missive over and over again in my head about this. I’m glad there’s no reason to anymore.

When my last app, Spend Stack, was acquired - it took nearly four months to get settled. This was an experienced buyer who usually had things done and dusted in one week. Why did it take so long? Because I didn’t just sell Spend Stack, I had to sell my entire LLC, Dreaming In Binary, which I had owned for many years to that point. Instead of transferring the app, I had to manage a slew of logistical hurdles that neither I, or the acquirer, wanted to otherwise.

Now, there is no more creating an LLC per app (what!!!):

CloudKit is wonderful, and it offers so many advantages that most of us went through all of the riff raff you had to go through to form a LLC, open a business account, get your DUNS number and more just to freaking use it for one app! This is a directly pro-developer move from Apple, and one we can all hold hands and agree on. CloudKit gives you so much that Apple should really be pushing it 30 times harder than it has been:

  • Hardly any of us want to manage user logins or server data.
  • We (developers) all want to support sync, and all customers want sync.
  • Of our users, the majority have multiple devices they want to use our apps on.
  • No other syncing solution is as smooth as iCloud - we don’t have to tangle with logins or sign ups. People use our apps on their other device, and like magic - their data is there. That’s the most Apple “it just works” experience around. This is a rare user and developer experience win.
  • It’s maintained and trusted by one of the largest technology companies on the planet who rely on it. This won’t be another Parse.
  • And, not to mention, the best alternative is owned by Apple’s rival - Firebase from Google!

In short, it’s about time. People tend to think that an app getting acquired is only for the bigs, and that’s simply not true. This move allows indies to get a nice chunk of cash and let their project live on from another company taking it over so we can focus on new things…without selling an entire LLC along with it.

The long and short of it is this - many of us would skip iCloud sync altogether because it meant we couldn’t transfer any app using it in the future. In turn, we offered a worse user experience than we otherwise could’ve or we’d use a competitor which wasn’t as good and required user logins (with iCloud, the user is already “logged in” via their Apple ID). It was painful and agonizing every time, because we knew iCloud was the better sync option - but we couldn’t risk having to part with every single of our apps if just a single one using the iCloud entitlement was acquired. No more.

In another reality, if this one image was on a slide during W.W.D.C.’s keynote in 2022 - I venture to say it would’ve received the loudest applause out of anything else they could’ve said:

An official announcement from Apple mentioning that apps which use iCloud can now be transferred.

Until next time ✌️

···

iOS 16: Notable UIKit Additions

// Written by Jordan Morgan // Jun 8th, 2022 // Read it in about 5 minutes // RE: UIKit

As we turn the page on yet another W.W.D.C., things are definitely starting to move in that direction. The one we’ve kinda known was coming, namely:

A slide saying that Swift and SwiftUI are the best ways to build new apps.

Granted, I’ve seen this quote taken a little out of context. Apple preceded that statement by saying Swift and SwiftUI are the best way to build new apps. So, what about…not new ones?

Hey oh, UIKit! The framework of yore is alive and kickin’, and of course - better than it’s ever been. Let’s see what Cupertino & Friends™️ have added to the framework for iOS 16.

If you want to catch up on this series first, you can view the iOS 11, iOS 12, iOS 13, iOS 14 and iOS 15 versions of this article.

UICalendarView

A brand new component for iOS 16, UICalendarView brings a lot of power to date related operations. Unlike its counterpart, UIDatePicker, it allows for custom decorations for date ranges, multi date selections, font designs and more:

let calendarView = UICalendarView(frame: view.bounds)
calendarView.calendar = Calendar(identifier: .gregorian)
view.addSubview(calendarView)

let multiDateSelection = UICalendarSelectionMultiDate(delegate: self)
calendarView.selectionBehavior = multiDateSelection

And boom: A UICalendarView running on an iPhone simulator.

Using its delegate, you can vend custom views for each date. I could see this being especially useful for situations where you want to highlight a range of dates, perhaps with a “selected” background. Or, you could just make everything a rocket ship because you freakin’ can:

func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
	return .customView {
    	let emoji = UILabel()
        emoji.text = "🚀"
        return emoji
    }
}

Resulting in: A UICalendarView running on an iPhone simulator with rocket ship emojis showing at each date.

I should note, calendar view works in terms of date components, not Date instances themselves. This is different from UIDatePicker, so it’s something to be aware of before you start plugging it in in place of it.

Read more at the docs.

Variable Symbols

SF Symbols is the framework that just seems to go harder in the paint every single year. I would be impressed if Apple simply added to their collection annually (which they do) but they also add new and interesting ways for us to use them. Right on the heels of giving us different rendering styles, now we’ve got the ability to variably fill them:

let glyph = UIImage(systemName: "water.waves", variableValue: 0.2)

In short, we can “fill” the glyph in based off of a percentage value from 0 to 1. This is particularly well suited towards situations where we are trying to convey a sense or progress or status, for example - showing how strong a wifi signal is, or how much battery you have left. Here is a wave glyph using a 0.2, 0.6 and 1.0 variable fill, respectively:

SF Symbols shown using the new variable fill API.

I see a lot of opportunities to get creative with this API. For example, why not wire up Combine (yup, still love Combine) and change the variance of a glyph to show the classic typing indicator:

A message bubble showing typing indicators.

It can easily be done:

class ViewController: UIViewController {
    private var subs: [AnyCancellable] = []
    let imageView = UIImageView()
    lazy var count: Double = 0.0 {
        didSet {
            let glyph = UIImage(systemName: "ellipsis.message.fill", variableValue: count)
            imageView.image = glyph
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        Timer.publish(every: 0.35, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
            switch self?.count {
            case 0.0:
                self?.count = 0.2
            case 0.2:
                self?.count = 0.6
            case 0.6:
                self?.count = 1.0
            default:
                self?.count = 0.0
            }
        }.store(in: &subs)
        
        imageView.contentMode = .scaleAspectFit
        imageView.frame = CGRect(x: view.bounds.size.width/2 - 50, y: 100, width: 100, height: 100)
        view.addSubview(imageView)
    }
}

Resizing Cells

In a change that is bound to make those of us who’ve longed relied on UICollectionView and UITableView for years cry a mix of joy and relief, cells now automatically resize themselves when their content changes. Note, resize. We’ve had self sizing cells for a bit now. Now though, for example, if you had a label which was hiding and showing, you no longer have to do the magical incantation of invalidating the cell size, calculating its height or really anything else - now, #ItJustWorks. This is on by default:

collectionView.selfSizingInvalidation = .enabled

Worlds Collide - SwiftUI Views in Cells

Speaking of cells - my goodness, this next one slaps. A common thread among many iOS developers is that SwiftUI is amazing for several tasks, but one that maybe requires the fine-tuned touch of imperative programming techniques is when you have lists or queues that require a touch of performance tuning. You can get in the weeds and do what you need to make things fly.

Now, you can combine the ease of use of SwiftUI with your existing table or collections views. Using the recent content configuration APIs, you can vend a SwiftUI view for a cell. Best of both worlds, as it were.

Here’s an existing content configuration from my collection view snip:

let videoGameCellConfig = UICollectionView.CellRegistration<UICollectionViewListCell, VideoGame> { cell, indexPath, model in
	var contentConfiguration = cell.defaultContentConfiguration()
	contentConfiguration.text = model.name
	contentConfiguration.textProperties.font = .preferredFont(forTextStyle: .headline)
	cell.contentConfiguration = contentConfiguration
}

While that works, now you can do stuff like this:

cell.contentConfiguration = UIHostingConfiguration {
    HStack {
        Text(model.name)
            .font(.headline.bold())
        Image(systemName: "star.fill")
    }
}

Tangentially related, this also solves some of the complexity I felt when content configurations were introduced around creating custom ones. Creating custom configurations is absolutely possible, but it takes a bit of leg work. This, well, doesn’t - and that’s awesome.

Little Nuggets

With iOS’ heuristics always improving, you can now detect money or physical values from data detectors:

let textView = UITextView(frame: .init(x: 80, y: 80, width: 200, height: 200))
textView.text = "Let's chat about how much my apps that don't exist make, $0.00 ARR.\n\nSee them here:
https://www.ihavenoappsrightnow.com/becauseImWritingABook"
view.addSubview(textView)

textView.dataDetectorTypes = [.money, .physicalValue]
textView.isEditable = false

As of now, .physicalValue has no overview, so I’m not quite sure what it’ll detect. Money, however, is straightforward.

Oh - you can size sheet presentations now, yes! Personally, the detents that came out of the box last year had limited utility for my own work, but being able to tweak it so they show exactly how I want is perfect. For example, if I needed a super tiny sheet that’s only 100 points tall now, that’s perfectly valid:

let vc = ViewController()
vc.view.backgroundColor = .purple
vc.sheetPresentationController?.detents = [.custom { ctx in
	return 100 
}]

self.present(vc, animated: true)

When you use create a custom detent provider, you can reach into UISheetPresentationControllerDetentResolutionContext to see things like the trait collection or set a maximum value. Neato. Plus, you can set custom detent identifers to use along with all of the properties UISheetPresentationController had before, like setting it for the .largestUndimmedDetentIdentifier property.

More to Discover

There’s a lot that’s changed with context menus, giving them a substantial amount of power and flexibility in terms of sizing and presentation. Plus, a new paste control, page controls that can be used vertically, find and replace options and a lot has been done to the trusty navigation bar. Now it has presets for specific contexts, tailored for things like document based apps or ones that are more creative and free…form 😉:

Apple's Free Form app running on iPad.

Final Thoughts

As we see the inevitable rise of SwiftUI, I can’t help but smile as I write another article in this series each year since iOS 13. Right after dub dub, I immediately see the “Here’s what’s new in SwiftUI” articles hit - and why not? SwiftUI is exciting, and I love using it.

But, for now, UIKit is still here and doing its thing. And, wow - it’s added a lot of great stuff this year. And while it keeps doing that, yours truly will keep talking about it.

Until next time ✌️

···

How to Process WWDC

// Written by Jordan Morgan // Jun 2nd, 2022 // Read it in about 4 minutes // RE: W.W.D.C.

W.W.D.C. happens Monday, and if it’s your first dub dub - then you likely only feel one thing: massive amounts of excitement. And you should! For me, W.W.D.C. is one of the most exciting times of the year.

However, if you’ve been around the likes of McEnery, braved the halls of Moscone West or (throwback!) traveled through Santa Clara Convention Center then…you know.

Dub dub is amazing, fun and yes - kinda a little stressful. Here’s how I consume W.W.D.C. to come out with a clear mind and a focused direction.

At the risk of sounding like a Buzzfeed article, here are seven tips I live by to handle W.W.D.C..

Realize Everyone is About to be Overwhelmed

Just declare bankruptcy upfront. Not one person will be able to fill their domes with everything that’s about to be announced. The good thing is, you don’t need to know it all.

Just try to be aware of it.

For me, the main thing I want to know about first are the new frameworks added to iOS, so I’ll wait for the diffs here to be uploaded1. More recently, Apple has taken it upon themselves to show the new frameworks on their technologies page, so let’s hope that work continues.

We’ll know about the big hitters because those will be a part of the marketing, as well they should. But what about the quality of life improvements? For example, the Uniform Type Identifiers framework is killer, yet so many don’t know about it. It can make your code better, but it might be relegated to “word cloud” duty.

Next, Apple’s design pages also have also included a change log of sorts, so that’ll be my next stop. Any design updates will be highlighted, and typically, new technologies (think something like WidgetKit) will have freshly minted design guidelines. Those are key.

My goal here is to have a passing knowledge of what’s new, that way I can make better decisions on figuring out what to dive into later on. What is delayed is not denied, so take a breath and realize you’ll have time to come back to what you want to learn more about.

Take Messy Notes

Next, have a no-nonsense way of taking notes. For me, taking notes at W.W.D.C. is all about being in the frustrating part of forming ideas - the messy middle. I’m going to have a million scattered thoughts, too many ideas to follow up on and several APIs I’ll want to write about.

This is all fine, so long as you’ve come up with a system to take those notes. For me, I use Craft. Here’s what it looked like last year:

Craft Docs running on macOS with notes from W.W.D.C. 2022.

I separated everything into one of five buckets (which will look familiar if you follow anything I write), and most of the information I get will fall into one of them. I do this because I know what I’m trying to do at WW.W.D.C.:

  • Figure out what to bring to the apps I work on.
  • Learn what to write posts about and update my book series with.
  • Decide what I want to learn a lot more about.

So ask yourself - what are you trying to get out of W.W.D.C.? That’ll help inform your game plan.

The next three tips are more process orientated, but it helps to know what to do with the first day of W.W.D.C..

Watch the Keynote

This one is obvious, and no doubt - you’ll be doing it. But, I list it here because this is where you likely want to begin with the above tip. Start taking lightweight notes throughout the keynote, because those are things you’re going to want to follow up on throughout the week.

When WidgetKit was announced, I might’ve put something in my notes that simply said “WidgetKit” during the keynote, but then I knew to mark down any sessions in the Developer app released later on to learn more about it.

At this point, my only goals are to become extremely excited about the new stuff and get a 10,000 foot view of what the “big new things” are - because that’s what we will get in the keynote.

Remember, the keynote isn’t entirely for developers - it’s kinda for investors too. Apple’s stock will change after W.W.D.C., it always has. This is not a criticism at all, but it also illustrates that for many, the keynote is about what everyone on the planet should look forward to with the upcoming version of iOS.

Watch the State of the Union

So, if the keynote is for everyone, than the State of the Union is for us.

This is where we get new framework drops, information on the particulars behind some new APIs and generally how the new version of iOS fits into the ecosystem. This is likely the best TL;DR of the week, so you can’t miss it.

After this one is done, your notes should really be starting to swell with a lot of headings and threads to follow up on.

Take Note of Award Winners

Here’s something that some might sleep on, but you totally shouldn’t - watch the Apple Design Awards ceremony. Sure, you can read the recap later - but that’s like being excited about a sporting event and just checking ESPN later for the score.

let concatenatedThoughts = """

Plus, this year there are several developers from our own community who are nominated! If you have any skin in the game, you should definitely watch it live in 2022.

"""

You want the why behind everything. Try to listen for the Apple evangelist+designer presenter’s reasoning behind why something won what it did. What made it great? Why did it work? They always have gone into detail to try and justify their picks, and I appreciate that.

Winning an A.D.A. is my moonshot goal, it drives me in several ways. Beyond just intrinsically wanting to make neat things that help people, I want to reach the peak - and an A.D.A. is the peak in this business.

So, pay attention here. Even though it’s at the end of a busy day, to me it’s like a nice little reprieve from all of the business that just occurred. It’s usually short too, clocking in at 30-45 minutes if memory serves.

Remember, Apple Developers are Human

Try to have some empathy during the week and consider filing a radar if something doesn’t work as advertised. Here are some tips on how to do that from someone who literally just worked at Apple.

After Monday…

Once the excitement of day one is in the books, I’d encourage you to enjoy the community. Take in all the deluge of Twitter finds promulgated by the community, hang out with other developers and enjoy the moment.

For me, Monday evening is the “smell the roses” moment - I just try to be present and enjoy one of my favorite events of the year. I’ll have all week to learn stuff, but for now I just want swap dev stories with other indies, make new connections and talk shop with Apple engineers.

After Monday, though, I have three goals:

  • Schedule Labs: Get information and design tips.
  • Bookmark Sessions: Figure out which sessions I can’t afford to miss.
  • Community++: Set up some lunches or dinners with other developers - good ol’ fashioned networking.

With that, my week is filled up. I keep taking notes, fleshing them out and then when it’s all over I immediately come up with a game plan.

Final Thoughts

There is nothing quite like W.W.D.C. for me. I so dearly hope it’s restored to its former format of being an in-person event next year, should crazy mutating viruses allow it. My best professional memories are all from W.W.D.C., and being around so many other developers fills me up.

No matter what W.W.D.C. this is for you, don’t stress over it! Realize the information dump you just received is too much to take in at once - you just need to know how to tame it, and then hone in on what’s important to you and your team.

Until next time ✌️

  1. Though, lately that page hasn’t been updated for the latest versions of iOS 15, so I may need to find another go to. 

···