[SC]()

iOS. Apple. Indies. Plus Things.

My Real Challenge with SwiftUI

// Written by Jordan Morgan // Nov 23rd, 2022 // Read it in about 4 minutes // RE: SwiftUI

Before I begin, this is not one of those “Is SwiftUI ready for production!?” or “SwiftUI is just a buggy mess” posts. I can make my stance on those topics rather clear:

  • Yes, it is.
  • And, no, it’s not.

I am a very happy SwiftUI developer. In fact, at Buffer we’ve shipped no less than four major features built using it - all at a blistering speed made possible by the framework. So if you’re expecting a bash post full of hysterics, the exit is at the upper right of your browser tab 👋.

What this post is, though, is an honest assessment of my biggest challenge with SwiftUI.

And, it’s not that it works in ways I don’t expect. It’s not that it’s unreliable or too fragile as others have posited. It’s this:

SwiftUI simply gives me indecision and pause while developing software.

That, by a wide margin, is my biggest hangup as an iOS developer. Not the freedom of choice two interface frameworks bring, but the constant juggling I’m having to do with UIKit and SwiftUI to use all of the amazing APIs that Apple has to offer.

The indecision and pause comes from the simple fact that some of the frameworks do things that the other one can’t. That’s it. And, I’m talking about the little quality of life improvements iOS sees year over year. So, why is that such a sticker for me?

Well, do you as a consumer want to select a bunch of stuff at once on iPad? Or, said differently, do you as a developer want to support band selection patterns like this one:

Of course you do! One thousand times yes. That’s the sort of thing where if an app I was using didn’t have it, I’d immediately notice and think that the developers didn’t care, know the platform well or perhaps were using some other means to build their iOS app (i.e. React Native).

But, these days it might not be due to any of those reasons at all. To wit, the reason it won’t be in my next app is simply because I can’t. It’s not available in SwiftUI1.

Now, let’s hop over to the other side of the fence. How about out-of-the-box markdown? Simple in SwiftUI, almost criminally so:

struct EZMarkdownView: View {
    let markdown: LocalizedStringKey = """
**Incredible Offer**
Visit this [link](https://bestinclassiosapp.com) to claim your discount!
"""
    var body: some View {
        ScrollView {
            Text(markdown)
            .padding()
        }
    }
}

Done. But, that doesn’t fly in UIKit as far as I know.

Now, extrapolate this out a bit. How about the new context menu preferred element sizes, custom iPadOS pointer shapes or accessories, customizing the edit menu in text views - the list goes on.

These are things of which I’m fairly certain you simply cannot do in SwiftUI today. And that hurts because, to the surprise of nobody who has read this website at all before this moment, this is the crap I absolutely freakin’ live for when developing iOS apps. These are the sort of things that keep me up at night, knowing I shipped an app without them.

To demonstrate, I think Spend Stack was one of the first apps to support, or at least advertise, support for a new multi-select pattern introduced in iOS 13. I remember it getting a five to ten second callout in a W.W.D.C. session and my heart skipped a beat, I couldn’t wait to implement it! In fact, I had a silly tweet showing how to do it in Mail, and it went TinyViral2. Not long after, it was turned into an article at The Verge:

So, for a lot us, being a great platform citizen is dearly important. It’s what makes the platform the amazing place that it is today. Compromising by missing some of those things is not something indie developers should have to do, and I certainly don’t want to.

To that end, we reach the second pain point I have - which is that some of these APIs are available in both frameworks, but it’s hard to know that because the documentation is segmented by SwiftUI or UIKit. This makes sense by most accounts, but if one “core” feature of the platform is available in both, please, please Apple - just link to it from each page.

Example? The slick new document editing features on iPadOS:

I googled, wildcard searched, browsed the developer app transcripts, looked at notes and more to figure out how this worked in SwiftUI. I found it in UIKit here. In fact, I only found out this was possible in SwiftUI due to Twitter. And, well, we all know how well that place’s future is looking these days.

If Apple would just link to the “Here’s how it’s done in SwiftUI”, or maybe “To implement X in SwiftUI, please see Y” and vice-versa, that would already be a huge win for me. The same goes for W.W.D.C. session videos. Even if the video only covers the “thing” in UIKit, at least call out that it’s possible to do in SwiftUI.

It’s not a crazy idea either. Look at the standup, quality work that the Apple designers have done with their Human Interface Guidelines this year. They centralized them in a way that makes sense. They present a big idea, and then link to more details for each respective platform.

I realize that design guidance and technical documentation don’t directly overlap, but I still stand behind the spirit of the point I’m trying to make. And that is, if I can do the cool new thing in both frameworks, make it stupid simple for me to know that, no matter which interface documentation page I end up on.

In summation: Core, central U.X. flows and features should be in both interface frameworks from day one. New enhancements to find and replace, iPadOS pointer stuff, band interactions, pencil interactions - all of it. I want to be able to do all of those small little things in both worlds.

What I’m not saying is that I should be able to build widgets in UIKit. From the start, Apple has been clear in saying “This is only done in SwiftUI.” But, if that is not the case, then the API surface area has to cover both worlds.

And, again, link to both frameworks in the documentation if it can be done in either SwiftUI or UIKit. Ditto for session videos.

Final Thoughts

When I see see something amazing at the next dub dub, I don’t want my first thought to be “Will I only be able to do this in UIKit or SwiftUI?”

While Apple has done phenomenal work with interop, I want that to be a bridge solution. It’s true you could wrap this or that to get X or Y to work, as is the case with my first example with band interactions. But, I’m kind of hoping for a future that provides all of these APIs whether or not I use SwiftUI or UIKit.

No matter, I remain a huge fan of SwiftUI. Once you stand up a U.I. in it, it’s almost painful to do the same thing in UIKit. Then, there are times where I need to prod and pry precisely at some code, and I happily turn to UIKit. So, if we’ve got both frameworks, and both aren’t going anywhere - then both of them need to be able to do a lot of the same things.

If the story of SwiftUI is that you can do the big and large things in it (i.e. make an entire app), but you can’t polish off the remaining 10% (i.e. use band selection to quickly select a bunch of stuff) unless you use UIKit, that’s honestly not an ideal story. It’s almost more of an indictment.

So, as the stereotypical phrasing goes, I criticize the platform because I feel so passionately about it, I care about it a great deal. Here’s hoping that the new “small things” APIs end up in both frameworks next year.

Until next time ✌️

  1. Yes, you can use representables. And that does work most of the time, but in some cases, it’s just not as easy to do - especially in core, navigational components. 

  2. TinyViral: Verb. To have a tweet do modest-ish numbers. 

···

Basic Keyboard Navigation for Collection & Tableview.

// Written by Jordan Morgan // Nov 7th, 2022 // Read it in about 1 minutes // RE: Snips

let concatenatedThoughts = """

Welcome to Snips! Here, you'll find a short, fully code complete sample with two parts. The first is the entire code sample which you can copy and paste right into Xcode. The second is a step by step explanation. Enjoy!

"""

The Scenario

Use the focus system to support basic keyboard navigation in collection and table views.

import UIKit

class KBNavViewController: UIViewController {

    private lazy var collectionView: UICollectionView = {
        var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        
        let layout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        collectionView.backgroundColor = .systemGroupedBackground
        collectionView.delegate = self
        
        // 1
        collectionView.allowsFocus = true

        // 2
        collectionView.selectionFollowsFocus = true
        
        return collectionView
    }()
    
    lazy var datasource: UICollectionViewDiffableDataSource<Int, VideoGame> = {
        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
            
            cell.accessories = []
        }
                
        let datasource = UICollectionViewDiffableDataSource<Int, VideoGame>(collectionView: collectionView) { collectionView, indexPath, model in
            let configType = videoGameCellConfig
            return collectionView.dequeueConfiguredReusableCell(using: configType,
                                                                for: indexPath,
                                                                item: model)
        }
            
        return datasource
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(collectionView)
        
        var snap = datasource.snapshot()
        snap.appendSections([0])
        snap.appendItems(VideoGame.data)
        datasource.apply(snap)
    }

}

extension KBNavViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
    }
    
    // 3
    func collectionView(_ collectionView: UICollectionView, performPrimaryActionForItemAt indexPath: IndexPath) {
        let vc = UIViewController()
        vc.view.backgroundColor = .purple
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

With that in place, you can now use the arrow keys to select rows, and then perform their primary action:

The Breakdown

Step 1
The first step is to opt into the focus system by setting allowsFocus to true. This allows the collection or table view cells to become focused.

Step 2
Next, set selectionFollowsFocus to true. This means that when a cell becomes selected, it is also focused.

Step 3
Finally, put the logic that is the primary action of the cell when tapped or clicked into the delegate method func collectionView(_ collectionView: UICollectionView, performPrimaryActionForItemAt indexPath: IndexPath). Of note, this code was traditionally in didSelectItemAtIndexPath - but remember, that will now fire when the cell is focused. So, this new method solves that problem, and it’s only called when a user taps or clicks on the cell.

Until next time ✌️

···

Running Code Only Once in SwiftUI

// Written by Jordan Morgan // Nov 1st, 2022 // Read it in about 2 minutes // RE: SwiftUI

I’ve had a few situations in SwiftUI where I only want things to fire once and initially. At face value, this seems like a job for either .onAppear or the more nascent .task.

Consider the following:

struct FirstAppearView: View {
    @State private var greeting: String = "Initial"
    
    var body: some View {
        NavigationStack {
            VStack {
                Text(greeting)
                    .onTapGesture {
                        greeting = "Tapped"
                    }
                NavigationLink(destination: Text("Pushed Text")) {
                    Text("Push a View")
                }
                .padding()
            }
            .onAppear {
                greeting = "On Appear"
            }
        }
    }
}

Here’s what happens:

  1. The Text(greeting) view begins with “Initial”, but we never see it. By the time the view is drawn, .onAppear has been invoked…
  2. …which means that the first thing the user sees in that Text view is “On Appear”.
  3. Now, if I tap the Text view - it reads “Tapped”.
  4. Finally, if I push another view onto the navigation stack and come back - the Text view now reads “On Appear” again - likely not what I wanted. I’d want its last set text to persist, so “Tapped”.

Here’s a gif of this in action (notice how the Text changes when the navigation stack pops off, since .onAppear fires again):

Demo of the outlined scenario above running via Xcode Previews.

At first blush, you might consider moving to .task. But in reality, it by and large has the same heuristics as .onAppear does, just simply more suited to Swift’s concurrency model. As such, we’d get the exact same result as we did above. Further, setting values in the init is also not what you want - something like this also wouldn’t work:

struct FirstAppearView: View {
    @State private var greeting: String = "Initial"

    init(greeting: String) {
        self.greeting = greeting
    }
}

This would still result in the Text view displaying “Initial”. So, taking stock of our current predicament, what we need is:

  1. Some code to fire initially.
  2. And, to only do it once.

I looked at the problem a few different ways, and I didn’t really devise any stratagem that seemed viable. So, to solve this, I did what I always do when I don’t know how to achieve something in SwiftUI, ask Ian Keen. He had a slick modifier that achieves exactly this. The idea is simple but practical: you track a private variable to see if you’ve done the work you want to do, and tie that to the View life cycle. Here’s what it looks like:

public extension View {
    func onFirstAppear(_ action: @escaping () -> ()) -> some View {
        modifier(FirstAppear(action: action))
    }
}

private struct FirstAppear: ViewModifier {
    let action: () -> ()
    
    // Use this to only fire your block one time
    @State private var hasAppeared = false
    
    func body(content: Content) -> some View {
        // And then, track it here
        content.onAppear {
            guard !hasAppeared else { return }
            hasAppeared = true
            action()
        }
    }
}

With that in place, our issue is solved:

struct FirstAppearView: View {
    @State private var greeting: String = "Initial"
    
    var body: some View {
        NavigationStack {
            VStack {
                Text(greeting)
                    .onTapGesture {
                        greeting = "Tapped"
                    }
                NavigationLink(destination: Text("Pushed Text")) {
                    Text("Push a View")
                }
                .padding()
            }
            .onFirstAppear {
                greeting = "On Appear"
            }
        }
    }
}

Now, we can push and pop the view on and off the navigation stack all day long, and our Text view’s text persists, no longer hampered by code firing again in .onAppear or within a .task.

I wish SwiftUI had first class support for this situation, it seems like a fairly common scenario. I’ve seen a lot of SwiftUI code shove the entire kitchen sink into .onAppear without really grasping that it’ll likely fire more than once. It leads to some odd bugs.

Until next time ✌️

···

Presenting Sheets: Item, or a Boolean Binding?

// Written by Jordan Morgan // Oct 25th, 2022 // Read it in about 3 minutes // RE: SwiftUI

Quite often in SwiftUI, you’ve got to present another view in response to tapping some domain-specific data. Consider a simple model representing people:

struct Person: Identifiable {
    let id = UUID()
    var name: String
    var age: Int

    static var empty: Person {
        get {
            Person(name: "", age: 0)
        }
    }
}

And with it, the canonical list showing them:

struct PeopleView: View {
    @State private var people: [Person]
    
    var body: some View {
        List(people) { person in
            HStack {
                VStack(alignment: .leading) {
                    Text(person.name)
                        .padding(.bottom, 2)
                    Text("\(person.age)")
                        .font(.caption)
                }
            }
        }
        .onAppear {
            // Fetch `people` from an API, db, etc.
        }
    }
}

Now inevitably, there comes a time to edit a person. So, you show a modally presented view to do just that:

struct PersonEditor: View {
    let person: Person
    
    var body: some View {
        // Omitted for brevity, 
        // But any sort of editing UI here would exist.
    }
}

Here comes the crux of this little blurb. Previously, I had done something like this:

struct DirectoryView: View {
    @State private var people: [Person] 

    // One bool to present the editor. 
    // One property to track the tapped person.
    @State private var presentEditor: Bool = false
    @State private var selectedPerson: Person = .empty
        
    var body: some View {
        List(people) { person in
            Button {
                selectedPerson = person
                presentEditor.toggle()
            } label: {
                VStack(alignment: .leading) {
                    Text(person.name)
                        .padding(.bottom, 2)
                    Text("\(person.age)")
                        .font(.caption)
                }
            }
        }
        .sheet(isPresented: $presentEditor) {
            PersonEditor(person: selectedPerson)
        }
        .onAppear {
            // Fetch `people` from an API, db, etc.
        }
    }
}

And, while it works most of the time - it certainly has caused me several frustrating issues, and I don’t use it anymore for this particular scenario. The most prominent of these would be that the passed in @State variable would be incorrect when the sheet was presented. I stepped through LLDB, wondering where I might’ve gone wrong.

I’d become even more flummoxed when I saw that my local @State variable would appear correct before I toggled the boolean to present the sheet. In our example, that would be selectedPerson.

Further still, I noticed that other times, it might be nil. What gives?

If your presentation requires a local data variable to control whether or not the view should present, use Sheet’s item modifier instead of the above route. Here’s what that looks like:

struct DirectoryView: View {
    @State private var people: [Person] = Person.peeps()
    @State private var selectedPerson: Person? = nil
        
    var body: some View {
        List(people) { person in
            Button {
                selectedPerson = person
            } label: {
                VStack(alignment: .leading) {
                    Text(person.name)
                        .padding(.bottom, 2)
                    Text("\(person.age)")
                        .font(.caption)
                }
            }
        }
        .sheet(item: $selectedPerson) { person in
            PersonEditor(person: person)
        }
    }
}

Bingo. Now, the relevant variable is always in the state that it should be, no surprises - and we get to lose the extra boolean @State variable to control presentation. The “item” approach inherently controls presentation and dependency injection at the same time based off of whether or not its relevant binding is nil.

let concatenatedThoughts = """

It's also funny that I use this approach all the time with `enum` cases. For whatever reason, it didn't stick in my head to use it with data models.

"""

There are multiple explanations floating around about why this works and the other way doesn’t, long threads over SwiftUI’s internals and what not - but all I know is this, it does work.

Sure enough, if you R.T.F.M.1 - that’s exactly what it says on the tin, too:

Use this method when you need to present a modal view with content from a custom data source.

As soon as I realized this, I went back and ameliorated swathes of code. Maybe this is common knowledge in the community, but it took a good hour from my development life. I hope this post gives you one back.

Until next time ✌️

  1. Read the fancy manual, of course. 

···

Swift's print(), debugPrint(), String(reflecting:), Mirror(reflecting:) and dump()

// Written by Jordan Morgan // Sep 26th, 2022 // Read it in about 9 minutes // RE: Swift

It’s truly incredible to think of all the advancements Cupertino & Friends™️ have brought forth in the name of debugging over the years. Need to explode an app’s hierarchy and pick it apart on the fly? No problem1:

Xcode's view debugging.

Or, maybe you’ve got to diagnose some slow code, debug CoreML, optimize app launch times or figure out the source of a hitch? Bam, Instruments:

Instrument's template page.

Heck - even retain counts have a hard time hiding out if you know how to fire up good ol’ mem graph:

Xcode's memory graph debugging tool.

And yet - in light of those amazing tools, I often find myself doing something like this to diagnose layout issues in my code:

view.backgroundColor = .purple

There’s nothing quite like having a giant purple view look at you right in the face to figure out the true size of a view. And, the code version of that?

The humble print(). While it’s used often across the Apple ecosystem, the ethnography of print debugging expands far beyond it. As such, today we’ll look at some other alternatives you might be unaware of:

Swift.debugPrint(), String(reflecting:), Mirror(reflecting:) and dump().

Size Up

At first blush, in the case of debugPrint() there’s not really anything eye catching about its signature. In fact, its arguments mimic that of print() to the letter:

// Ol' faithful
func print(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n"
)

// And its close cousin
func debugPrint(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n"
)

Going further, if you were to test them out in LLDB, depending on what you’re printing - you may not find any difference at all:

let numbers: [Int] = [0,1,2]
print(numbers)
Swift.debugPrint(numbers)

// Resulting in...
[0, 1, 2]
[0, 1, 2]

Even Apple proclaims that print() “Writes the textual representations of the given items…” while debugPrint() “Writes the textual representations of the given items most suitable for debugging” (emphasis my own). Hmmm, what exactly is suitable for debugging then?

Moving on, if you check out dump - it immediately gives off a different vibe:

@discardableResult func dump<T>(
    _ value: T,
    name: String? = nil,
    indent: Int = 0,
    maxDepth: Int = .max,
    maxItems: Int = .max
) -> T

The same is true, to a lesser extent, for String(reflecting:) and Mirror(reflecting:):

init<Subject>(reflecting subject: Subject)

init(reflecting subject: Any)

Okay…so…what’s suitable for debugging? What gives? And why does debugPrint() even exist if its so similar to print()? What about the other three options? To understand that, it helps to know about reflection, and how Swift’s Mirror struct works.

Mirror, Mirror on the Console

The concept of reflection in programming is primarily referring to the practice of inspecting types. Whether you’re in a statically typed language or a dynamic one, reflection can help solve all sorts of interesting problems. Not more than five or so years ago before Swift rose to its de facto standard for iOS development, many Objective-C developers used the concept of reflection to figure out stuff like what methods a class would implement, or even add new ones altogether2!

But, we’re talking Swift - not Objective-C. And that brings us back to Mirror, which helps Swift perform somewhat similar functions.

When you mirror a type in Swift, you can see things like its stored properties, its enumerations cases and more. Mirror extends into other parts of Swift, all building on the concept of reflecting types. There are functions specifically built for print debugging purposes from the get-go, complete with display customization options to tweak how output is rendered, making them well suited for print-based debugging. Putting it all together, reflection is quite simple and not really all that mysterious even though we often think of it that way.

With that said, for the rest of the post consider the following struct:

struct VideoGame: Identifiable {
    enum Genre: Int {
        case Adventure, Sports, Shooter
    }
    var id = UUID()
    var genre: Genre
    var name: String
    var releaseDate: Date
}

Now, with a bit of background on Mirror and reflection in general, let’s see what all of these different printing methods produce.

The Great Print() Off

Let’s get to it and use all five of these methods on the following instance and see what we get:

let originalReleaseDate = Date(timeIntervalSince1970: 1195250594)
let massEffect = VideoGame(genre: .Adventure,
                           name: "Mass Effect",
                           releaseDate: originalReleaseDate)

Without further ado…

print(massEffect)
debugPrint(massEffect)
print(String(reflecting: massEffect))
print(Mirror(reflecting: massEffect))
dump(massEffect)

What would you expect to happen here? Let’s see:

// Print
VideoGame(id: 56B4811E-0306-4974-A2D8-AA010311FC07, genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure, name: "Mass Effect", releaseDate: 2007-11-16 22:03:14 +0000)

// debugPrint
SwiftUI_Playgrounds.VideoGame(id: 56B4811E-0306-4974-A2D8-AA010311FC07, genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure, name: "Mass Effect", releaseDate: 2007-11-16 22:03:14 +0000)

// Reflection
SwiftUI_Playgrounds.VideoGame(id: 56B4811E-0306-4974-A2D8-AA010311FC07, genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure, name: "Mass Effect", releaseDate: 2007-11-16 22:03:14 +0000)

// Mirror
Mirror for VideoGame

// dump
 SwiftUI_Playgrounds.VideoGame
   id: 56B4811E-0306-4974-A2D8-AA010311FC07
    - uuid: "56B4811E-0306-4974-A2D8-AA010311FC07"
  - genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure
  - name: "Mass Effect"
   releaseDate: 2007-11-16 22:03:14 +0000
    - timeIntervalSinceReferenceDate: 216943394.0

Some are pretty much the same, but there are some interesting differences. Let’s pick em’ apart.

For print()...
We’re getting pretty much what we expect. Under the hood, it’ll call String(item) and in most cases, it’s perfect for debugging code in LLDB. For our struct type, we see its properties and their values.

For debugPrint()...
Things look a little bit different. Its output is strikingly similar to the print(), but it has a fully qualified print out. In this case, I’m running code in a little project I dump in stuff for my blog posts - SwiftUI Playgrounds. In short, it has more accurate type information.

This is where a good ol’ fashioned print() versus debugPrint() really differentiate. The former gives you what you probably expect, while the formatter does that and provides more type information. That’s why these look just a teeny bit different:

let name = "Jordan"

print(name) --> Jordan 
debugPrint(name) --> "Jordan", representing the actual type

let rangeExample = 1...10

print(rangeExample) --> 1...10 // Just the value, not the type 
debugPrint(rangeExample) --> ClosedRange(1...10), includes the type and value

In short, I’ve found it helps to think of it this way. One gives me the values, and the other gives me the type and the values.

For String(reflecting:)
This one seems boring, but it’s actually quite interesting. I haven’t found in the official docs that the following statement is true, but in my experience, it seems true: debugPrint() is basically calling String(reflecting:) under the hood. In our case, you’ll notice that their output is identical.

Even if that’s the case, debugPrint() is more than just syntactical sugar and there are reasons to use it over simply using String(reflecting:). With all of the arguments it supports (remember, the same ones that print() does) you can tweak output much to your liking while getting the same information that String(reflecting:) brings to the table.

So, instead of having to write this:

print(String(reflecting:"Jordan"), String(reflecting:"Jansyn"), separator: " -- ")

// "Jordan" -- "Jansyn"

You can simlpy opt for debugPrint()

debugPrint("Jordan", "Jansyn", separator: " -- ")

// "Jordan" -- "Jansyn"

…and get the same result.

In short, you’ve got the niceties of text formatting along with type information. It does, however, bring about the question of “How exactly does String(reflecting:) figure out what to print?”

If you look at the docs for init(reflecting:) you’ll see a similar refrain we read earlier, emphasis again my own: “Creates a string with a detailed representation of the given value, suitable for debugging.”

There’s that phrase again, suitable for debugging. Seriously, what the __ is suitable for debugging!?

The answer lies in the type’s protocol conformance. This function follows a little flow chart of sorts to figure out what exactly is “suitable for debugging”, and it goes like this:

Step 1:
Does the type conform to CustomDebugStringConvertible? If it does, then you’ll get the type’s debugDescription. Swift supplies this for its own types, which is one reason you may not have reached for it yourself. This is what Swift considers “most useful for debugging.”

If you don’t like what Swift comes up with for a type, you’re free to tweak it to whatever you want. For example, for the VideoGame, maybe you just want the raw value of its enumeration and the name for some reason. Perhaps those are the only two interesting fields you’re trying to fix a bug centered around. Rolling your own CustomDebugStringConvertible would get you there:

extension VideoGame: CustomDebugStringConvertible {
    var debugDescription: String {
        return "Genre: \(self.genre.rawValue), name: \(name)"
    }
}

// Prints out as Genre: 0, name: Mass Effect

Step 2:
However, if the type doesn’t conform to that - then String(reflecting:) dips down into CustomStringConvertible, which you’ve likely used in your Swift adventures. It’s particularly valuable for enum types, take our Genre enum from earlier, whose raw value is an Int:

enum Genre: Int {
    case Adventure, Sports, Shooter
}

print(VideoGame.Genre.Adventure) --> "VideoGame.Genre.Adventure"

// Add CustomDebugStringConvertible conformance
enum Genre: Int, CustomStringConvertible {
    var description: String {
        switch self {
        case .Adventure: return "Adventure"
        case .Sports: return "Sports"
        case .Shooter: return "Shooter"
        }
    }
    
    case Adventure, Sports, Shooter
}

print(VideoGame.Genre.Adventure) --> "Adventure"

So, we can start to see why something may be more “suitable for debugging” now. However, there is still one more stop that String(reflecting:) will take if neither of those protocols are used - and that stop is to…

Step 3:
TextOutputStreamable. This little guy is suited towards text-streaming ops, unsurprisingly, and as such - String, Character, and Unicode.Scalar all conform to it out of the box.

Bonus Step:
However, if String(reflecting:) encounters none of those, then you get a grab-bag result of who knows what. An “unspecified result” determined by the Swift standard library gets returned.

For Mirror(reflecting:)
This one is quite easy to understand, but can be incredibly useful. In short, you’re getting an instance’s type information. That’s why we simply got “Hey, this is a VideoGame” from it:

Mirror for VideoGame

If you aren’t sure of instance’s type - simply put, reach for Mirror(reflecting:).

For dump()
Finally, we’ve got dump() - one of the more interesting options. It’s similar to the mirror API, and in fact - it uses mirroring to produce whatever you pass in for value to create a standard textual representation of it. For a refresher, this is what it produced:

 SwiftUI_Playgrounds.VideoGame
   id: 56B4811E-0306-4974-A2D8-AA010311FC07
    - uuid: "56B4811E-0306-4974-A2D8-AA010311FC07"
  - genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure
  - name: "Mass Effect"
   releaseDate: 2007-11-16 22:03:14 +0000
    - timeIntervalSinceReferenceDate: 216943394.0

Using dump you can print out entire class hierarchies and type information. In short, it gives you all the freakin’ information it can find about whatever you pass to it. For example, if you tacked on a Rating struct to our VideoGame and dumped it - it would break apart Rating and show its values too:

// Added as a property to VideoGame...
struct Rating {
    let average: Double
    let weighted: Double
}

let originalReleaseDate = Date(timeIntervalSince1970: 1195250594)
let massEffect = VideoGame(rating: .init(average: 9, weighted: 8.5),
                           genre: .Adventure,
                           name: "Mass Effect",
                           releaseDate: originalReleaseDate)

dump(massEffect)

// Rating is shown as well
 SwiftUI_Playgrounds.VideoGame
   rating: SwiftUI_Playgrounds.Rating
    - average: 9.0
    - weighted: 8.5
   id: 0DB0A374-031C-4211-8A00-C9BDCD6A59AF
    - uuid: "0DB0A374-031C-4211-8A00-C9BDCD6A59AF"
  - genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure
  - name: "Mass Effect"
   releaseDate: 2007-11-16 22:03:14 +0000
    - timeIntervalSinceReferenceDate: 216943394.0

But, using its formatting options, you can tweak indentation, the depth of the print out according to the value’s nested types and properties or even label it:

dump(massEffect, name: "Best Role Playing Game", indent: 4, maxDepth: 1)

// Resulting in 4 spaces of indentation, and just top level information of the types
     Best Role Playing Game: SwiftUI_Playgrounds.VideoGame
       rating: SwiftUI_Playgrounds.Rating
       id: 6814EDC9-73E4-4F72-9111-8F7CBEAC3712
      - genre: SwiftUI_Playgrounds.VideoGame.Genre.Adventure
      - name: "Mass Effect"
       releaseDate: 2007-11-16 22:03:14 +0000

Here, since out maxDepth was 1, complex types like Rating no longer displayed its property values - just that it was a Rating instance. This is great, because when you use dump() on something like UIView….you’ll find a boatload of type information. Maybe you want to pare it down a bit, ya know?

And with that, our little TL;DR of some different ways you can go about your print debugging adventures has concluded.

Final Thoughts

Which one of these you’d want to use is all about context. For the most part, print() is likely just fine. But, if you need more and want to know why they work the way they do and what their differences are - well, now you do!

I think with stuff like this, it’s simply about improving your Swift lexicon. Maybe you won’t use most of these in your day to day, but when the times comes that you need it - well hey, you know it exists now 😃.

Until next time ✌️

  1. Crazy enough, that’s been around since Xcode 6. More than half of Xcode’s lifespan. 

  2. In you want to see this in action, try calling private API. I just wrote about that fairly recently here

···