[SC]()

iOS. Apple. Indies. Plus Things.

Confirmation and Result Interactive Snippets

// Written by Jordan Morgan // Jul 10th, 2025 // Read it in about 4 minutes // RE: App Intents

This post is brought to you by Emerge Tools, the best way to build on mobile.

Recently, on the Superwall blog, I wrote up a guide over interactive snippets. If you haven’t checked that out, I’d go there first. Continuing on that same beat, I wanted to also highlight an important difference in interactive snippets.

There are two types you can provide:

  1. Result snippets: These show data that you don’t have to confirm (i.e. here’s how much caffeine you’ve had today).
  2. Confirmation snippets: These show data, but also require you to make some sort of decision before continuing (i.e here’s some coffee I want to order).

Both of them are interactive, and can have buttons or toggles that fire intents. At the API level, they both have the same signatures. However, confirmation snippets request said confirmation inside of the perform function. From Apple’s sample code:

struct FindTicketsIntent: AppIntent {

    func perform() async throws -> some IntentResult & ShowsSnippetIntent {
        let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark)

        // Kicks off a confirmation snippet here
        try await requestConfirmation(
            actionName: .search,
            snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest)
        )

        // Resume searching...
    }
}

And that is where things diverge. To understand that divergence better, consider that interactive snippets are typically dismissed by the user tapping the system provided “Done” button found below them:

Result type of an interactive snippet.

All “result” interactive snippets work this way. Alternatively, they can also just swipe it away to yeet it out of there.

But when you need confirmation, the “Done” button isn’t displayed, and instead the system will show contextualized buttons based on the actionName parameter. In the code above, that was set to “.search”, and so it is — you get a “Cancel” and “Search” button:

Confirmation type of an interactive snippet.

And then the intent carries on, here’s the full sample, I’ve edited the comment to demonstrate the flow better:

struct FindTicketsIntent: AppIntent {

    static let title: LocalizedStringResource = "Find Tickets"

    static var parameterSummary: some ParameterSummary {
        Summary("Find best ticket prices for \(\.$landmark)")
    }
    
    @Dependency var searchEngine: SearchEngine

    @Parameter var landmark: LandmarkEntity

    func perform() async throws -> some IntentResult & ShowsSnippetIntent {
        let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark)

        // The new UI shows here, so you can think of `TicketRequestSnippetIntent`
        // As its own confirmation snippet. Once the user is done and they tap search,
        // We'll come back right here, and the search engine code will fire
        try await requestConfirmation(
            actionName: .search,
            snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest)
        )

        // This happens after the confirmation of the number of tickets is done
        try await searchEngine.performRequest(request: searchRequest)

        // And finally, a new snippet is shown
        return .result(
            snippetIntent: TicketResultSnippetIntent(
                searchRequest: searchRequest
            )
        )
    }
}

What can be tricky here is that this flow shown today technically uses three different interactive snippets:

1. First, there’s the ClosestLandmarkIntent, which returns that landmark view:

Result type of an interactive snippet.

2. Then, tapping “Find Best Ticket Prices” takes us to the above code, where TicketRequestSnippetIntent asks for confirmation on the ticket count:

Result type of an interactive snippet.

3. And, it circles back to TicketResultSnippetIntent to show the results. If you tap “Book Now”, we go back to first step:

Result type of an interactive snippet.

I hope that helps clear up when you’d use a “result” interactive snippet versus a confirmation one. Just because Apple refers to result interactive snippets as a result of something, they are very much well-suited to powerful tasks complete with interactivity.

Now go forth, and make all quick, impactful interactions a snippet!

Until next time ✌️

···

iOS 26: Notable UIKit Additions

// Written by Jordan Morgan // Jun 10th, 2025 // Read it in about 4 minutes // RE: UIKit

This post is brought to you by Emerge Tools, the best way to build on mobile.

2019 was the year Apple popped the lid off of SwiftUI. At the time, the responses came quick and fast and they were, well, a bit sensational. “UIKit is dead”, “It’s deprecated!”, “Useless now!”…the list goes on. But, yet again, here we are at dub dub 25’ and more UIKit improvements have landed.

Had initial reactions to SwiftUI come to fruition, you’d think UIKit would have no part in the Liquid Glassification of Apple’s ecosystem. But, in reality — of course it does. UIKit is still the framework propping up iOS, and if you peek under the hood far enough…it’s doing the same for SwiftUI, too.

As it tradition, let’s see what Cupertino & Friends™️ have for us this year in our iron-clad UI framework.

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

Observable Objects

Let’s start our notable UIKit additions post by…talking about SwiftUI. As one does. Observable made SwiftUI simply better, and now its UIKit’s turn to reap the same benefits. UIKit and Observable classes now work hand-in-hand. This is interesting, as the common line of thinking was that as SwiftUI evolved, the gap between it and UIKit would widen.

Instead, it’s shrinking.

For example, look at the animation interop changes from last year’s W.W.D.C., which allow UIKit and SwiftUI to coordinate animations.

With Observable, UIKit now tracks any changes in update methods like layoutSubviews. The implication? You don’t need to manually invalidate views and do the bookkeeping to update them:

import UIKit
import Observation

@Observable
class HitCounter {
    var hits: Int = 0
}

class TestVC: UIViewController {
    private var counter: HitCounter = .init()
    private let hitLabel: UILabel = .init(frame: .zero)

    override func viewDidLoad() {
        super.viewDidLoad()
        hitLabel.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(hitLabel)
        NSLayoutConstraint.activate([
            hitLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            hitLabel.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.counter.hits = 10
        }
    }
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        // Dependency wired up
        self.hitLabel.text = "Hits: \(self.counter.hits)"
    }
}

#Preview {
    TestVC(nibName: nil, bundle: nil)
}

let concatenatedThoughts = """

Speaking of gaps shrinking, just look at all of the SwiftUI-ness in that tiny body of work. Observable tracking. Automatic view updates. Previewing canvas macros. You love to see it.

"""

Here, UIKit automatically sets up observation tracking for the hits property. When it changes, the view gets invalidated, viewWillLayoutSubviews runs again — and just like that, the label is updated. Again, the handshakes between SwiftUI and UIKit seem to grow firmer each year, and that’s only a good thing for the ecosystem.

Also, while we’re here — what’s always been the knock on SwiftUI? It’s that large lists just don’t…list well. While changes have been made under the hood to make that situation better, the conventional wisdom from those of us who’ve been around was something like “Just wrap UICollectionView!”

Now, maybe that doesn’t ring as true with the new long list optimizations? Either way, it is easier to do now. The same technique above extends to UIListContentConfiguration, which means cells in collection views can automatically be updated from Observable models, too:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customcell", for: indexPath)
    let myModel = store.model(at: indexPath)
    cell.configurationUpdateHandler = { cell, state in
        var content = UIListContentConfiguration.cell()
        content.text = myModel.name
        cell.contentConfiguration = content
    }

    return cell
}

Even better? You can back-deploy this to iOS 18 by adding the UIObservationTrackingEnabled key to your info.plist file.

Update properties

A new lifecycle method on UIView and UIViewController!? What year is this?

I love it. Meet updateProperties(), which gets called right before layoutSubviews(). Like many life cycle functions, you can manually invoke it — setNeedsUpdateProperties(). So, what does it do and when do you use it?

In short, you can apply styling, or hydrate view-related details from models and other related tasks without forcing a full layout pass. You have one tiny thing to update? You don’t necessarily need to update the whole view hierarchy, but that’s just kinda how things have worked up until now. The key takeaway is that this runs independently from other layout methods (i.e. layoutSubviews()).

At a top level, the top-down layout pass goes like this now:

  1. Update traits
  2. Update properties.
  3. Layout subviews.

Apple wants to compartmentalize how you think about view layout now, meaning the job of updating view properties from your app’s data can happen in its own place (updateProperties()) and the job of your layout logic can happen in another (layoutSubviews()):

// Before, these two jobs were typically handled in the same place.
// Now, they can occur independently
class MyVC: UIViewController {
    override func updateProperties() {
        super.updateProperties()

        myLabel.text = myModel.text
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        myLabel.setFrameDimensions()
    }
}

Flushing animation updates

This change rocks. Keeping with the layout bookkeeping, traditionally UIKitters had to change things in an animation block, and then tell UIKit to invalidate the view to lay it out again:

// Before
UIView.animate {
    myModel.confirmationColor = validation.isValid ? .green : .red
    confirmationView.layoutIfNeeded()
}

You’ve got two jobs here — updating state, and telling your view to reflect it. An indecerous workflow in 2025. So, if you’re rolling with observable models, now you can skip the whole “Hey I updated a property, please redraw stuff in this animation block” dance:

UIView.animate(options: .flushUpdates) {
    myModel.confirmationColor = validation.isValid ? .green : .red
}

Due to the automatic change tracking UIKit has, UIKit will just take care of it for you now. We didn’t even have to reference confirmationView, UIKit knows that it depends on the confirmationColor of myModel.

This even works outside of Observable objects. You can even tweak constraint animations using the same .flushUpdates option. If you’ve been HasHTaG BleSSeD in your career to manually hold references to NSLayoutConstraint in the name of simply nudging a label down by 20 points, well…I guess you’re even more blessed now.

Bonus Points

  • Lots of new API around supporting the UIMenu changes in iPadOS.
  • Inspector API tweaks, specifically in regards to using them in a split view controller.
  • Key command now lets you support whether or not the command runs repeatedly or not, keyCommand.repeatBehavior = .nonRepeatable.
  • You can host SwiftUI scenes in UIKit apps via the UIHostingSceneDelegate protocol. Perfect for gradually adopting SwiftUI, or using the immersive spaces only found in the framework.
  • HDR extends to colors instead of only media, meaning you can crank things up when creating UIColor instances, and preferring their HDR selection in the color picker.
  • Symbols have .drawOff and .drawOn animations. It’s a nice touch, and it reminds of the magic replace stuff UIKit added last year.

Also, time marches forward. As such, some good ol’ friends of yore got slapped with a deprecation. UIApplication APIs are deprecated in favor of their UIScene counterparts, for example. To be honest, I thought that had already been done. So, if you haven’t embraced the UIScene world, well…you probably should.

What can I say? Another year, another UIKit. Like previous years, it’s much better. It continues to set the standard for what an imperative UI framework should be like, but at the same time — it’s slowly started to borrow ideas from its declarative counterpart. Interesting times! In iOS 26, UIKit is better than ever.

Until next time ✌️

···

W.W.D.C. 2025: The Pregame Quiz

// Written by Jordan Morgan // May 22nd, 2025 // Read it in about 7 minutes // RE: Trivia

This post is brought to you by Emerge Tools, the best way to build on mobile.

No sleep til’ dub dub! Or something like that? Today, I’m proud to give you the eleventh annual Swiftjective-C W.W.D.C. Pregame Quiz featuring Apple Intelligence, Jony Ive and more! If you want to test your mettle against previous years, you can hop back in to any of them:

Ground Rules

There are three rounds, and the point break down is as follows:

  • Round 1 – 1 point each answer
  • Round 2 - 2 points each answer
  • Round 3 - 3 points each answer

The last question of each round is an optional wildcard question. Get it right, and your team gets 4 points, but miss it and the team will be deducted 2 points.

Round 1 - Apple (Un)Intelligence

You just know I had to. Let’s rip the band aid off! Don’t try and cheat and ask Siri for the answers, it’ll just reply back it found web results anyways (🥁).

Question 1:
Genmoji was supposed to usher in an era of creativity in the world of emoji expression. It…hasn’t quite hit that mark. Which API is meant to display them inline within text components?
A: NSGenmojiRepresentation
B: NSAdaptiveImageGlyph
C: AttributedImageString
D: AttributedGenmojiString

Question 2:
Apple AI chief, John Giannandrea, wanted to include which chat assistant instead of OpenAI’s chatGPT in iOS 18?
A: Meta AI
B: Gemini
C: Alexa
D: Claude

Question 3:
In the now infamous (and pulled) ad featuring Bella Ramsey about Apple Intelligence, what was the name of the guy Siri allegedly found using its (seemingly made up) context aware capabilities?
A: Matt Granger
B: Nolan Quindle
C: Derek Blanchard
D: Zac Wingate

Question 4:
We all want Siri to be more conversational, and, well — basically work like our favorite LLM chatbots do. To that end, what’s the internal codename for Apple’s revamped Siri architecture aimed at delivering a similar experience?
A: Siri 2
B: LLM Siri
C: Siri Chat
D: Siri Next

Wildcard:
The creators of Workflow (acquired by Apple) eventually went on to spearhead Shortcuts in Apple. Recently, key members left Apple to start a new company to bring AI to the desktop. What’s the name of the nascent company?
A: Quantum, Inc
B: DesktopOS, Inc
C: NewAi, Inc
D: Software, Inc

Round 2 - Sir Jony Ive himself

With all the rage being AI, LLMs, and similar beats — Jony Ive recently stepped into the ring with OpenAI and their company, io. Let’s chat!

Question 1:
Okay, io. Let’s see how much you follow the rumor mill — what’s the expected form factor of their first AI device according to reports?
A: Watch
B: Wearable necklace
C: Pin-on (i.e. Humane)
D: Smart glasses

Question 2:
Here’s one that surprised me a bit. When is their first device expected to hit the market?
A: 2025
B: 2026
C: 2027
D: 2028

Question 3:
A bit lost in all of the io new is LoveFrom, Ive’s design firm — which is now taking on design at OpenAI. To date, which of these ventures has LoveFrom not been a part of?
A: Designing Ferrari’s first electric car.
B: A new seal and typography for King Charles III
C: Airbnb core products
D: Redesigned and customized headphones for Bose

Question 4:
Ive’s first project at Apple, while working at design firm Tangerine, was for “Project Juggernaut”, which was a codename for…
A: The Apple Lisa
B: Newton
C: Powerbook
D: Macintosh Portable

Wildcard:
Bro got paid. 2011 was the last time that Ive’s compensation was publicly noted. In addition to his cool $25 million stock bonus, what was his reported base salary at the time?
A: $20 million
B: $25 million
C: $30 million
D: $35 million

Round 3 - Wildcard Callbacks

Let’s go back into my lore for a moment. If you’ll indulge me, can you answer these wildcard questions from quizzes in years past?

Question 1:
From 2019’s section on Apple Lore: What original Apple employee quickly sold off their 10% share of the company in 1977 for only $800 (which would be worth billions today)?
A: Ronald Wayne
B: Patty Jobs
C: Nikola Tesla
D: Johannes Kepler

Question 2:
From 2017’s Apple Brain Teaser section: Which famous English astronomer, mathematician and physicist was originally featured on Apple’s logo?
A: Isaac Newton
B: Galileo Galilei
C: Johannes Kepler
D: Nikola Tesla

Question 3:
From 2015’s Senior Developer Only’s section: Finish the sentence. At WWDC 14, it was famously declared that Swift is “Objective-C __ ___ __!”
A: with type safety!
B: without the pointers!
C: without the C!
D: with readable syntax!

Question 4:
From 2016’s Senior Developer Only’s section: What was the first (and eventually accepted) proposal for the Swift programming language submitted from the community?
A: Constraining AnySequence.init.
B: Replace typealias keyword with associatedtype for associated type declarations.
C: Referencing the Objective-C selector of a method.
D: To allow most Swift keywords to be used as an argument label.

Wildcard:
A…wildcard in a wildcard section? Why not! From 2020’s Dub Dub Venues and History section: Paper badges were the norm for WWDC badges for much of its history. Can you name the year they finally made the switch to plastic badges for the first time?
A: 2009
B: 2008
C: 2010
D: 2012

👉

👈

Answer Key

Round 1:

  1. B. NSAdaptiveImageGlyph.
  2. B. Gemini
  3. D. Zac Wingate
  4. B. LLM Siri.
  5. Wildcard: D. Software, Inc. — delightful website, too.

Round 2:

  1. B. Necklace.
  2. B. 2026. Guess they have a head start.
  3. D. Fake, though they did make a turntable!
  4. C. It was the first Powerbook (a Macintosh Portable successor), though as an official Apple employee — it was the Newton.
  5. Wildcard: C, $30 million!

Round 3:

  1. A. One of its oft forgotten co-founders, Ronald Wayne.
  2. A. Sir Isaac Newton. Because, you know, the whole Apple thing?
  3. C. Without the C indeed, good times.
  4. D. To allow most Swift keywords to be used as an argument label. From Doug Gregor, implemented in Swift 2.2.
  5. Wildcard: A. They made the switch after 08’.
···

The Joy of Vibe Marketing

// Written by Jordan Morgan // May 18th, 2025 // Read it in about 3 minutes // RE: The Indie Dev Diaries

This post is brought to you by Emerge Tools, the best way to build on mobile.

Sometimes, you just have to go for it.

That’s my been my main sentiment since I’ve chatted with a few indies about marketing. I’m glad the conversation is opening up, because we’re all starting to learn a universal truth:

When you ask someone to pay money for something, you have to sell it.

Though, so many of us aren’t sure where to start. It’s a new world for most — so my thinking is, let’s do what all of these new entrepreneurs are doing with vibe coding. They may not full understand it, but they know it does do something.

To that end, may I suggest to you: vibe marketing.

Enter Vibe Marketing

To vibe market is to not worry about attribution for a specific install. It’s the action of putting together several creatives to run as a paid ad even though you may not know best practices (I certainly don’t). It’s trying to reach out to an influencer in your space even though you have no idea how those deals work. It’s not really knowing how to target an audience just right. Or even understanding how Meta’s comically confusing ad manager works.

You’re just going for done, and far from perfect. Just try something, and keep a sheet somewhere as simple as this:

Date Ad Spent Earned
May 18th IG Post $40 $50
May 19th IG Post $40 $41

You don’t have to full grok the hierarchy of a campaign, its ad sets and the ads within them. Or, know what to set for an auction price. Or even know what CPM stands for! Like those learning to program, there’s just too much to learn! So forget learning the ins and outs academically, just try something and learn on the job. This is certainly a situation where you learn balance by falling and scraping up your knees a bit.

If the Age of LLMs has taught me anything, it’s that I’m leaning more towards action than perfection. I’ve struggled with not letting something go until my footprint and design is all over it. And sometimes, you need that. But other times? It’s okay to pull out of the garage in a Toyota rather than a Porsche.

Vibe Marketing Ideas

So, what vibe marketing have I been trying lately? Here’s what I’m up to:

  • Boosting Instagram Posts: I try to find a post that explains Elite Hoops well, shows it off in action, and I put $10 behind it and see what happens. Today, I have this one boosted.
Boosted Instagram post
  • Exact Match with ASA: If you can find exact search terms people are looking for when it comes to your app, definitely run them with Apple Ads (seriously can’t believe these are called AA now, but I digress). These are high-intent users, looking for what we all are taught to find: someone needing a specific problem solved, and you have the app to solve it.

  • Classic Website Views: I’m a bit new to this one, but seeing as how I just rewrote my entire website for Elite Hoops, I’m trying them out. Simply serve up a Meta ad with a goal of a website visit. I think my site sells the app fairly well, so I’m curious to see what effect these will have. They run a bit cheaper than app install ads, too.

And, things I want to try, but haven’t:

  • Google search ads.
  • SEO in general. Paralysis by analysis here, there’s too much data and techniques being thrown at me when I look at this. If you have any solid ideas here, I’d love to hear them.
  • Good ol’ flyers at basketball tournaments. I want to make a compelling App Clip of the digital whiteboard, and hang up the QR code on the ubiquitous thumbtack board each basketball venue has.

Final Thoughts

“Vibe” marketing, coding — whatever you’re henceforth vibing, is a gamble. I couldn’t help but realize a few things as I rewrote Elite Hoop’s website in a brand new stack I’ve never used (modern web development), with a serverless backend I’ve traditionally not leveraged for auth (Supabase), and in a framework I don’t fully understand yet (React) — much less using another framework built on top of that one (next.js)!

But - I did do it! And there’s no way I could have without the help of Cursor and vibe coding. But, I’d be lying through my digital teeth if I didn’t proclaim how much I didn’t really understand at certain junctures. That doesn’t feel great. What was being done at some point in the next.js rewrite that was a critical error — but I was just too ignorant to know any better? Is there some massive flaw I just haven’t found yet? The feeling comes up, because I’ve spotted this type of thing with AI and iOS programming several times — because there, I do know.

I suppose that is the line we walk here. Vibe marketing is to do it but not fully grok it. And for my money? That’s a fantastic place to start. Far less a gamble here than it is with shipping production code. When it comes to this stuff, it’s not the mistakes that will kill you. It’s the not trying that will.

Until next time ✌️

···

Three Indie Marketing Tips from my Deep Dish 2025 Talk

// Written by Jordan Morgan // Apr 29th, 2025 // Read it in about 2 minutes // RE: The Indie Dev Diaries

This post is brought to you by Emerge Tools, the best way to build on mobile.

Deep Dish Swift 2025 has been a treat, as it always is. My talk over indie app marketing seemed to resonate, but if you missed it — here are the three main takeaways from it. Think of it as a cheat sheet for my talk:

Shift your mindset

The first thing we have to do as indies is to stop defaulting to only coding. We tend to use a lot of our rare free time on “the next feature” or something from WWDC. All well and good, but there is a time and a place for it — and it’s not all the time.

Try to split your indie-dev time around, ideally, 50/50. You market half the time. You do product work half the time. If that’s unreasonable for your current situation, then try something like 60/40.

The point is, marketing must become a critical and first-class component of how you do things.

Emails!

Email marketing is, I think, something I can promote in general terms - insofar as it’s applicable to any app, for any indie and at any size. The amount of purchasing intent is high on emailing lists or a newsletter. Folks are consciously choosing to be there. If you don’t have one, I’d encourage you to get started today.

I personally use Mailerlite, and it has worked pretty well. There are several options, though — and all of them have an API. I recommend popping in a sign up form tactfully in your app’s onboarding. In Elite Hoops, it’s about the third or fourth slide, depending on a few other factors:

The email sign up screen in Elite Hoops.

Yes, paid acquisition can work for indies. But, you need to come at it with the right expectations and game plan:

  1. Save up ahead of time to spend, at least, $30 a day — and for at least a month. In my experience, you need to be spending around that much to A) learn anything about if it’s working, and B) see any tangible results.
  2. Realize that setting up your first campaign sucks. It’s hard, the dashboard is confusing, there’s a bunch of esoteric errors you’ll encounter - the list can, and will, go on. It’s like submitting your first app to the App Store. A wee bit painful, but once you’re done - you’re good to go.
  3. Keep a strict spreadsheet of showing all meaningful results, how you much you’ve spent, the trial conversion rate and any other data you can think of to make it absolutely certain if things are working. The one I use is in the image below - and you can download the template here.
The Numbers spreadsheet used to track ad spend.

Be sure to change the top row to account for your trial length. The idea is that you go back and fill in the “Earned X Week/Day Later” and then the cell right after it will do a simple Income - Ad Cost = Takehome calculation.

Final Thoughts

What else can I say? Try marketing your app! If you want to check out the talk, it’s at the 5hr08min mark here.

Until next time ✌️

···