Creating Custom Parseable Format Styles in iOS 15

Since way back in iOS 2.0, the venerable Formatter class and its derivations such as DateFormatter and NumberFormatter have been the go-to for converting values to and from strings. Over the years Apple has added other cool formatters such as the ByteCountFormatter, MeasurementFormatter, RelativeDateTimeFormatter, and ListFormatter. Now, as discussed in What’s New in Foundation at WWDC21, we have a whole new way to convert values!

In this post, we’ll look at these new capabilities and dig deep to see how to extend the new ParseableFormatStyle and related protocols to make our own custom phone number formatter. Then we’ll use that to actually format SwiftUI TextField input. Please note that while I will be focusing on iOS here, these new protocols are available in macOS, watchOS, and tvOS as well as of Xcode 13.

The Old Formatter Way

Prior to iOS 15 and Xcode 13, in order to convert a number or date to a string, you would have had to do something like the following:

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .none
let formattedDate = dateFormatter.string(from: Date())
// August 14, 2021

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.maximumFractionDigits = 0
numberFormatter.currencyCode = "USD"
let formattedNumber = numberFormatter.string(from: 1234)
// $1,234

First you’d instantiate an appropriate Formatter class object, then adjust properties to control the input or output formats, and finally call a method to return the desired string. While this works (and will continue to), there are a couple downsides to this approach:

  • Formatter derived classes can be expensive to instantiate and maintain and, if not used correctly in looping, repetitive, or other situations, can lead to slowness and excess resource usage.
  • Coding formatters often requires specific syntax (date format strings, anyone?), usually takes multiple lines of code, and setting multiple properties on the Formatter object. This make the coding process more complicated/slow and less maintainable.

A Simpler Way to Create Formatted Strings

With iOS 15 (and the latest versions for watch, iPad, etc.), we can now convert certain values such as dates and numbers to strings much more easily while still maintaining specific control over the process. For example, we can now create strings like so using nice, 1-line, fluent syntax.

let formattedDate = Date().formatted(.dateTime.month(.wide).year().day())
// August 14, 2021

let formattedNumber = 1234.formatted(.currency(code: "USD").precision(.fractionLength(0)))
// $1,234

Simpler! As you can see, no Formatter variant is needed and we can do most of what we need to do in just one line of code. Code completion and the online documentation can guide you through the basics of these formatters; but we want to take things a step further.

Creating Your Own Custom Formatter

So let’s assume that we have some other type of data, such as a custom object that may contain a phone number, that we want to parse and output in a very specific but flexible way. Previously we would have had to sub-class the Formatter object, instantiate the new class, and format like we did above in our initial example. Now with several new protocols, including ParseableFormatStyle and ParseStrategy, we can write our own code that looks and behaves like Apple’s built in formatting and parsing code for dates and numbers. So let’s do it!

The Key New Formatting Protocols

At the core of the new functionality are a couple important new protocols. The first is ParseableFormatStyle which exposes, through the FormatStyle protocol, formatting and locale-specific functions. Objects that can manipulate data from one type to another can call the format(_:) method, shown below, to do so.

/// A type that can convert a given data type into a representation.
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol ParseableFormatStyle : FormatStyle {

    associatedtype Strategy : ParseStrategy where Self.FormatInput == Self.Strategy.ParseOutput, Self.FormatOutput == Self.Strategy.ParseInput

    /// A `ParseStrategy` that can be used to parse this `FormatStyle`'s output
    var parseStrategy: Self.Strategy { get }

/// A type that can convert a given data type into a representation.
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol FormatStyle : Decodable, Encodable, Hashable {

    /// The type of data to format.
    associatedtype FormatInput

    /// The type of the formatted data.
    associatedtype FormatOutput

    /// Creates a `FormatOutput` instance from `value`.
    func format(_ value: Self.FormatInput) -> Self.FormatOutput

    /// If the format allows selecting a locale, returns a copy of this format with the new locale set. Default implementation returns an unmodified self.
    func locale(_ locale: Locale) -> Self

When we create our own parser for our object type, we’ll also need to implement the new ParseStrategy protocol to control the actual parsing process from the formatted type back to the type being formatted.

/// A type that can parse a representation of a given data type.
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol ParseStrategy : Decodable, Encodable, Hashable {

    /// The type of the representation describing the data.
    associatedtype ParseInput

    /// The type of the data type.
    associatedtype ParseOutput

    /// Creates an instance of the `ParseOutput` type from `value`.
    func parse(_ value: Self.ParseInput) throws -> Self.ParseOutput

Creating our Parsing Strategy

So let’s start our new custom phone number format style! For our example, we’re going to create a simple PhoneNumber object that we need to convert into a standard U.S. formatted phone number string with the style (<area code>) <exchange>-<number>. As a bonus, we’d also like to be able to remove the parentheses, hyphen, and/or space between the area code and number as well as maybe display the number without the area code.

/// Representation of U.S. phone number
public struct PhoneNumber {
    /// Area code
    public var areaCode: String
    /// First three digits of a 7-digit phone number
    public var exchange: String
    /// Last four digits of a 7-digit phone number
    public var number: String


Now that our object type has been defined, our next step is going to be to create a ParseStrategy to allow conversion from a String type back to our custom PhoneNumber type. There are obviously many ways to implement the actual parsing but this will do for our fairly simplistic example here.

public struct PhoneNumberParseStrategy: ParseStrategy {
    /// Creates an instance of the `ParseOutput` type from `value`.
    /// - Parameter value: Value to convert to `PhoneNumber` object
    /// - Returns: `PhoneNumber` object
    public func parse(_ value: String) throws -> PhoneNumber {
        // Strip out to just numerics.  Throw out parentheses, etc. Simple version here ignores country codes, localized phone numbers, etc. and then convert to an array of characters
        let maxPhoneNumberLength = 10
        let numericValue = Array(value.filter({ $0.isWholeNumber }).prefix(maxPhoneNumberLength))
        // PUll out the phone number components
        var areaCode: String = ""
        var exchange: String = ""
        var number: String = ""
        for i in 0..<numericValue.count {
            switch i {
            case 0...2:
                // Area code
            case 3...5:
                // Exchange
                // Number

        // Output the populated object
        return PhoneNumber(areaCode: areaCode, exchange: exchange, number: number)


In the example above we’re just taking our input string, removing any non-numerics and only allowing ten characters before breaking apart the components. Please note once again this is a simple, non-localized example used to illustrate these concepts.

Adding the Custom Parseable Format Style

Now that our strategy has been defined, we can create a struct with our new PhoneNumberFormatStyle object.

public extension PhoneNumber {
    /// Phone number formatting style
    struct PhoneNumberFormatStyle {
        /// Pieces of the phone number
        enum PhoneNumberFormatStyleType: CaseIterable, Codable {
            case parentheses    // Include the parentheses around the area code
            case hyphen         // Include the hyphen in the middle of the phone number
            case space          // Include the space between area code and phone number
            case areaCode       // Area code
            case phoneNumber    // Phone number itself
        /// Type of formatting
        var formatStyleTypes: [PhoneNumberFormatStyleType] = []
        /// Placeholder character
        var placeholder: Character = "_"
        /// Constructor w/placeholder optional
        /// - Parameter placeholder: Placeholder to use instead of '_'
        init(placeholder: Character = "_") {
            self.placeholder = placeholder
        /// Constructer to allow extensions to set formatting
        /// - Parameter formatStyleTypes: Format style types
        init(_ formatStyleTypes:  [PhoneNumberFormatStyleType]) {
            self.formatStyleTypes = formatStyleTypes

There’s a little bit going on here but basically we’ve created an extension on our custom PhoneNumber struct with an enum, constructors, and properties to allow customization of the formatted output.

After creating the base object, we now need to actually implement ParseableFormatStyle and define our formatting. In the code below you can also see us exposing the ParseStrategy that we defined above (for going from String to PhoneNumber) and the format(_:) function where we output a custom string based on the enum and placeholder settings.

extension PhoneNumber.PhoneNumberFormatStyle: ParseableFormatStyle {
    /// A `ParseStrategy` that can be used to parse this `FormatStyle`'s output
    public var parseStrategy: PhoneNumberParseStrategy {
        return PhoneNumberParseStrategy()

    public func format(_ value: PhoneNumber) -> String {
        // Fill out fields with placeholder
        let stringPlaceholder = String(placeholder)
        let paddedAreaCode = value.areaCode.padding(toLength: 3, withPad: stringPlaceholder, startingAt: 0)
        let paddedExchange = 3, withPad: stringPlaceholder, startingAt: 0)
        let paddedNumber = value.number.padding(toLength: 4, withPad: stringPlaceholder, startingAt: 0)

        // Get the working style types
        let workingStyleTypes = !formatStyleTypes.isEmpty ? formatStyleTypes : PhoneNumberFormatStyleType.allCases
        var output = ""
        if workingStyleTypes.contains(.areaCode) {
            output += workingStyleTypes.contains(.parentheses) ? "(" + paddedAreaCode + ")" : paddedAreaCode

        if workingStyleTypes.contains(.space) && workingStyleTypes.contains(.areaCode) && workingStyleTypes.contains(.phoneNumber) {
            // Without the area code and phone number, no point with space
            output += " "

        if workingStyleTypes.contains(.phoneNumber) {
            output += workingStyleTypes.contains(.hyphen) ? paddedExchange + "-" + paddedNumber : paddedExchange + paddedNumber
        // All done
        return output


After doing this we also need to implement Codable (since FormatStyle implements Codable and Hashable) to persist the state of the format style. You can download the source code for this article to see this and more.

We also want to expose methods to allow us to construct a specific format fluently just like the styles built into the latest iOS system objects. Below, the area code, phone number, and punctuation-related methods are defined to do just that.

/// Publicly available format styles to allow fluent build of the style
public extension PhoneNumber.PhoneNumberFormatStyle {
    /// Return just the area code (e.g. 617)
    /// - Returns: Format style
    func areaCode() -> PhoneNumber.PhoneNumberFormatStyle {
        return getNewFormatStyle(for: .areaCode)
    /// Return just the phone number (e.g. 555-1212)
    /// - Returns: Format style
    func phoneNumber() -> PhoneNumber.PhoneNumberFormatStyle {
        return getNewFormatStyle(for: .phoneNumber)
    /// Return the space between the area code and phone number
    /// - Returns: Format style
    func space() -> PhoneNumber.PhoneNumberFormatStyle {
        return getNewFormatStyle(for: .space)

    /// Return the parentheses around the area code
    /// - Returns: Format style
    func parentheses() -> PhoneNumber.PhoneNumberFormatStyle {
        return getNewFormatStyle(for: .parentheses)

    /// Return the hyphen in the middle of the phone number
    /// - Returns: Format style
    func hyphen() -> PhoneNumber.PhoneNumberFormatStyle {
        return getNewFormatStyle(for: .hyphen)
    /// Get a new phone number format style
    /// - Parameter newType: New type
    /// - Returns: Format style
    private func getNewFormatStyle(for newType: PhoneNumberFormatStyleType) -> PhoneNumber.PhoneNumberFormatStyle {
        if !formatStyleTypes.contains(newType) {
            var newTypes = formatStyleTypes
            return PhoneNumber.PhoneNumberFormatStyle(newTypes)
        // If the user duplicated the type, just return that type
        return self


We’re done, right?! Well not quite. Just a couple little pieces are left to leverage the .formatted(_:) syntax, used by the pre-defined format styles for Date and Number for example.

public extension PhoneNumber {
    func formatted(_ formatStyle: PhoneNumberFormatStyle) -> String {

public extension FormatStyle where Self == PhoneNumber.PhoneNumberFormatStyle {
    /// Format the given string as a phone number in the format (___) ___-____ or similar
    static var phoneNumber: PhoneNumber.PhoneNumberFormatStyle {

Putting It All Together

Whew! That was a lot but hopefully you hung in there. So now let’s use the formatter we created for our PhoneNumber object.

let phoneNumber = PhoneNumber(areaCode: "123", exchange: "555", number: "1212")

// Default
print(phoneNumber.formatted(.phoneNumber))  // (123) 555-1212
// No punctuation
print(phoneNumber.formatted(.phoneNumber.areaCode().number()))  // 1235551212

// Just the last 7 digits and the hyphen
print(phoneNumber.formatted(.phoneNumber.number().hyphen())) // 555-1212

What’s really nice as well is that we can now use this custom format style in SwiftUI also to customize TextField output with the new constructors based on ParseableFormatStyle!

struct PhoneNumberTextField: View {

    @Binding var phoneNumber: PhoneNumber
    var body: some View {
        TextField("Phone Number", value: $phoneNumber, format: .phoneNumber, prompt: Text("Enter phone number"))
Using a SwiftUI TextField with Custom Formatting

There are some rough edges that we might want to clean up in this implementation but you can see how these new formatters can be leveraged in the UI directly as well as behind the scenes.

Get the Source Code and Explore

We’ve just scratched the surface here of what can be accomplished with the new formatting API’s. As you may have noticed, the default output type doesn’t have to be String and can be any type that makes sense to your use case. By exposing ParseableFormatStyle, ParseStrategy, and other protocols, you can leverage custom format styles to improve your code.

If you want to dig in and try this out for yourself, please feel free to download the source code and play around with a sample project and unit tests. As always please let me know what you think in the comments and feel free to like/follow/share my blog.

Refactor or Rewrite? Tackling Legacy Apps

If you’ve been programming for any length of time, you’ll likely run across apps or other projects that need major work and revisions to keep going. Maybe your company acquired another company with tons of legacy code. Perhaps you just started a new gig with lots of skeletons in the proverbial closet. Or you’re an indie developer and that old reliable app that has paid the bills is finally showing its age. I’m going to try to help you make the decisions about your apps (or really any code projects) that need this kind of work.

So it’s easy to get overwhelmed initially by the scope of changes needed or the amount of time required to fix everything. Where do you start? Do you just scrap the existing code and start over? Do you try to refactor the legacy code in place and modernize things without creating a new greenfield project? Only you can choose what’s right for your situation; therefore doing so with clear motivations and direction is key.

Before You Start

  • Leave your emotions at the door – Whether you’re reviewing your own code, programming that you inherited from another dev, or something in between, it’s critical not to come at the problem too attached to (or the opposite, too ready to dump) what’s already there. Assuming that you have decided to make some significant change, making decisions will be easier looking at the code objectively.
  • Try not to bring preconceived assumptions – While it’s perfectly fine to have a hunch as to which way to rewrite an app, often the discovery process of reviewing an existing piece of code will strongly guide the decision making process (as it should). Let process and analysis drive, rather than your gut, where you can. I’ve seen perfectly good, reusable code thrown out because some developer has carried the “this code sucks” chip on their shoulder entering an app review. It’s also easy to get swept up in new technology hype too early (or stick with older tech too long). While SwiftUI may be awesome, for example, it’s not the hammer that will pound every nail out there. It may turn out that reviewing the code flows the work in another direction.
  • Understand your options – For what we are discussing here, your choices will range in scale from “do nothing” to “refactor in place” to “start a brand new project”. Regardless of the choice, it’s critical that you (and your team) understand your capabilities as well as your limitations. It’s all well and good if you want to ditch that old Objective-C code and spin a new SwiftUI app but if you haven’t learned SwiftUI or don’t fully understand how Objective-C works, your task will be much more difficult.

Understand the Decision Making Drivers

While there are numerous paths to software project analysis, I’ve found that at their core, planning most coding projects does come down to a combination of the familiar three interrelated factors:

  • Resources – How much money and how many programmers, designers, etc. can be thrown at a solution? Resources can come in other forms as well (software, hardware, etc.).
  • Time – When is the project due? What’s your deadline? Obviously important but keep in mind that there are absolute minimums in many cases. The old adage about not being able to make a baby in 1 month with 9 people applies here.
  • Scope – What has to be included in the finished app or project? Where the choice is refactor or rewrite, it doesn’t have to be an absolute in many cases. You may want to refactor some of an app and totally scrap and redo other parts. Scope can be flexible.

Usually adjusting one or two of these options will force the unadjusted option to change in the other direction. For example, adding more programmers (resource) might mean you can deliver the same amount of work (scope) more quickly (time).

By assessing your options in this light, you should hopefully arrive at a deliverable , or set of deliverables that can be delivered over time (enabling a more Agile approach) that meet your goals. Making the kinds of decisions around reworking an entire app almost always involve some level of compromise on one or more of the above axes, so it’s important to be ready this up front.

As you assess the issues, try to remember these criteria and make sure that you are also weighing the benefits/drawbacks of any changes. Any business (including yourself if you’re on your own) should want to see positive benefits (financial or otherwise) from any expenditures of time or resources. Keep track of the pros and cons as you look at rewrite vs. refactor.

Analyze the Existing Code

Looking through someone else’s (or your own) legacy code may not seem like a good time, but you can learn a lot in the process. Keep an open mind and remember that there is almost always a reason for the code you are reviewing. Coders aren’t malevolent in general; we all however have knowledge gaps, operating pressures, and have to deal with the technologies available at the current time. The mobile app space especially has moved fantastically quickly since the first devices came out; and both Apple and Google continue to rapidly evolve both UI and non-UI paradigms for developers.

There are more different types of refactors and conversions than I can list. Here are some of the main ones that mobile app developers tend to hit most often, along with some related thoughts.

  • Language Conversion – For example, Objective-C to Swift or Java to Kotlin. Extremely common these days and can be fairly straightforward in some cases but extremely complicated in others. Built-in system tools to convert Java to Kotlin or Xcode add-ons like Swiftify on the iOS side are good but generally only take you part of the way. You should expect significant clean up efforts both in terms of the code as well as being forced to move to different language paradigms (async/await anyone?). Use tools like Cloc to count lines of code by language and help gauge progress.
  • UI Conversion – With the advent of SwiftUI and Jetpack Compose, it’s very common to want to take entire apps or portions of apps and modernize them. Older apps with nib files or storyboards are prime candidates for these types of refactors. Generally speaking UI conversion doesn’t have to be an all-or-nothing; so that’s probably the biggest thing to keep in mind. Depending on the application architecture as well, it may not be possible to update just the UI without additional refactoring needed to fit the new architecture.
  • Architectural Refactor – All of the existing apps out there are written with one or more of many different architectures – good, bad, and ugly. Whatever your choice of preferred architecture, you may wind up having to deal with an app that is architected differently, poorly, or inconsistently. Changing an app architecture is usually a very significant step and can have many consequences. Really think about (and comprehend) the benefits and drawbacks of this type of change before jumping off the proverbial cliff.
  • API Replacement – These types of changes can actually be some of the simplest – if you are able to just swap endpoints and leave it at that. Complications often arise though with situations like changing data models/contracts and moving from one type of API to another (e.g. REST API to GraphQL) which might require new network code or third-party SDK’s.
  • Cross-Platform to Native (or vice versa) – These are probably less common but do happen as application needs evolve to be more complex or devolve and remove features. Business acquisition and available technology expertise can also play a role here. Flutter, Xamarin, React Native, etc. all have their own unique wrinkles. If you’re new to either cross-platform or native, it’s critical to do enough prep work, proofs of concept, etc. before making hard decisions to avoid any showstoppers or other failures.

That’s enough prep work and thinking. Now get in there and do the fun stuff!

Make the Call

So you’ve decided on your initial goals, reviewed countless files and lines of code, and think you’ve got a few options. Some choices are quick and easy, some aren’t. You think you may be able to get some resources to help but aren’t sure. What’s next?

If you haven’t already done so, get organized. Lay out the pros and cons for each option you identified – both financially and otherwise. Don’t be afraid of including intangibles like improved developer morale, easier maintenance in the future, etc. Try if possible to cast these things in light of how they will help (or hurt) the business that’s running the app.

Then, make a decision! What did you decide? Scrap and rework? Refactor in place? With significant efforts like these, it’s really easy to get analysis paralysis and get sidetracked until the effort itself becomes obsolete or, worse, becomes forced upon you by some outside force (like the dreaded Apple or Google policy update). It’s totally ok to abandon the effort to refactor or rewrite if it’s just not worth it – but do so knowing why and be able to live with the consequences.

Lastly chunk the work if possible – In rare cases and with smaller apps, you might be able to sit down and bang out a new app or fix an old one in a couple days or a week. For any real significant change though, you’ll want to have a game plan. If a refactor is in the cards, try to split the work into reasonably sized pieces that can be potentially released separately to the appropriate app store. If you’re scrapping the app and performing a rewrite, you may need a project plan and a more concrete idea of the steps you will take. Consider a minimum viable/lovable product (MVP/MLP) approach where you release just enough to satisfy your customers while still maintaining velocity.

Going Forward

Future-Proof Your New Code

Think about why your current app is in its current state. Did someone leave old code unattended for too long? Was everything so tightly coupled that pulling one thread of code out collapsed the whole app? Or maybe the app just grew organically into an unmanageable Swiss Army knife of features that no one could find or use?

It seems pretty obvious but try not to make the same mistakes again. Allow for periodic maintenance and schedule regular high-level app reviews to flush out any growing technical debt. Architect new features using patterns that allow for decoupling and replacement of app components without impacting the entire app. The period after WWDC and I/O always bring changes in the mobile app space so these can also be good times to start thinking about the future.

Embrace Change!

I’m not sure about you; but one of the reasons I absolutely love the mobile application space is the relentless, rapid pace of change and learning. Even if you think you’ve written the “perfect” app, it will need to updated. Apps are not “fire and forget” pieces of software despite being deployed out to the app stores all at once. Account for the related maintenance, updates, and even rewrites – and learn to enjoy it. It’s fun and challenging!

Accessibility Gets an Upgrade in Xcode 13 Storyboards

WWDC 2021 is behind us and there are so many new and exciting features available to us as developers. One of those areas that has been getting much more love from Apple over the last few years is accessibility. Apple has been making it simpler for developers to test key tools like Dynamic Type, Dark Mode, and more in the simulator. Since Xcode 11, we’ve had the ability to change numerous accessibility settings in the simulator at run-time with the Environment Overrides pop-up sheet below.

Simulator Accessibility Sheet

This was great for debugging but took time to spin up the app in the simulator and didn’t help much in earlier phases such as storyboard setup. Now with Xcode 13, as noted in the release notes, we can finally see many of these same settings live in UIKit storyboards!

Using the Accessibility Window with Storyboards

Just click the accessibility icon at the bottom left of the storyboard window in the canvas button bar; and you can change type size, contrast/transparency, button shape, bold text, and on/off label settings on the fly. If your storyboard uses features that leverage these settings, you’ll instantly see the storyboard adjust to those changes!

Xcode 13 Accessibility pop-up window

Let’s try some of this out! Let’s look at just a small section of a storyboard with the new icon tapped and Accessibility turned off. Note that you need to flip the Accessibility toggle for any of the settings (slider or checkboxes) to take effect.

Xcode 13 Accessibility disabled

Now we’ll set the Accessibility toggle to the on state and set the Dynamic Type size. Presto! Your storyboard changes on the fly to the larger size so you can evaluate constraints, wrapping, etc.

Xcode 13 Accessibility enabled with larger text size

I have seen a few small issues around this (such as the change not being finalized correctly till you release the Dynamic Type slider) but at this point we are still just on early betas of Xcode 13.

Here’s an example of two of the other settings that can help out your users – On/Off Labels and Increase Contrast. People with color blindness and low vision can benefit from your app supporting these and other features. Note the binary symbols inside the UISwitch controls.

Xcode 13 Accessibility enabled with On/Off Labels and Increase Contrast set

Users wanting these features will typically turn them on in the Accessibility / Display & Text Size menu in iOS or iPadOS as in the image below.

Settings app Display & Text Size Accessibility options

The new Xcode 13 options will now let you preview the UI without having take extra time to build and run the app in simulator.

New Dark Mode Appearance Icon for Storyboards

In addition to the above changes, with Xcode 13 we can also click the small icon just to the right of the Accessibility icon to flip the storyboard presentation between Light and Dark Mode. The icon itself is styled identically to the Light/Dark Mode icon available in iOS in the Control Center. Previously in Xcode, you would have to open the “View as” window pane to flip between Light and Dark modes. It’s a small change but one that should save some time for developers.

UIKit Isn’t Dead Yet!

Despite announcing SwiftUI just two years ago at WWDC 2019 and all the push to get on board that train, UIKit most likely still has years of life left. While all the hype around new technologies doesn’t make our choices as developers any easier, Apple announced many new UIKit features at WWDC 2021 and continues to improve Xcode for all developers – and more importantly better the experience for all users.

Accessibility is one area in specific that keeps getting better every year with continuing improvements and refinements. Check out some of the talks on this subject from WWDC 2021. If you’re new to accessibility on Apple platforms, I’d strongly suggest the Accessibility for Developers site as a starting point.

Registering Collection View Cells in iOS 14

In my last post, I looked at handling diffable data sources with different types of object data, focusing primarily on the venerable UITableView for my examples. We looked at a simple if...else construct to swap between cell types based on the type of the item from the data source and related snapshot. While this method works with UICollectionView objects and diffable data sources, iOS 14 introduced a brand new way to define and configure your reusable collection view cells that opens up some new options.

Setting Up the Collection View

Like last time, we’ll set up Apple, Orange, and EmptyData structures to hold our sample data. These are just a couple heterogenous data structures to use with our data.

struct Apple: Hashable {
    var name: String
    var coreThickness: Int

struct Orange: Hashable {
    var name: String
    var peelThickness: Int

struct EmptyData: Hashable {
    let emptyMessage = "We're sorry! The fruit stand is closed due to inclement weather!"
    let emptyImage = "cloud.bold.rain.fill"

This time however, we want to declare and instantiate a UICollectionView inside our view controller. Below we are identifying our possible sections as well as calling our “bad weather” data retrieval function, which will simulate an asynchronous pull of data from an API or other source to populate the sections. While many of the examples out there for diffable data sources have hard coded the section data, I think it’s more useful to have some code that mimics more typical scenarios.

/// Simple sample diffable table view to demonstrate using diffable data sources. Approximately 33% of the time, it should show "bad weather" UI instead of apples and oranges
final class DiffableCollectionViewController : UIViewController {
    var collectionView: UICollectionView!
    enum Section: String, CaseIterable, Hashable {
        case apples = "Apples"
        case oranges = "Oranges"
        case empty = "Bad Weather Today!"
    private lazy var dataSource: UICollectionViewDiffableDataSource<Section, AnyHashable> = makeDataSource()
    override func viewDidLoad() {
        collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout())
        collectionView.dataSource = dataSource
        ...<layout and other code>...
        // Just a silly method to pretend we're getting empty data every 3rd or so call (for demo purposes every 3 days or so we get rain at the fruit stand)
        let badWeatherDay = Int.random(in: 0..<3) > 0
        badWeatherDay ? getData() : getEmptyData()

We’re also going to extend our UICollectionViewCell code to make our life a little easier and encapsulate the actual cell label and background changes. The function below simply takes some text and a background color for the cell using the new UIListContentConfiguration from iOS 14. This new object makes it much simpler to define collection items meant to be utilized in lists and similar scenarios.

extension UICollectionViewCell {
    /// Just set up a simple cell with text in the middle
    /// - Parameter label: Label
    /// - Parameter relatedColor: Color associated with the data
    func configure(label: String, relatedColor: UIColor) {
        var content = UIListContentConfiguration.cell()
        content.text = label
        content.textProperties.color = .white
        content.textProperties.font = UIFont.preferredFont(forTextStyle: .body)
        content.textProperties.alignment = .center
        contentConfiguration = content

        var background = UIBackgroundConfiguration.listPlainCell()
        background.cornerRadius = 8
        background.backgroundColor = relatedColor
        backgroundConfiguration = background

Defining and Using Pre-Configured Cells

Now that we’ve got our prep work out of the way, we can get to some of the new, exciting capabilities from iOS 14. Prior to these changes, you most likely would have used dequeueReusableCell(withReuseIdentifier:for:) to pull and configure a collection view cell with which to work. New in iOS 14 however is the ability to create a cell registration with UICollectionView.CellRegistration. With this we can move our configuration code out of the usual spots, refactor things, and use the registration as a variable. This generic object is defined as follows.

struct CellRegistration<Cell, Item> where Cell : UICollectionViewCell

UICollectionView.CellRegistration expects a collection view cell with which to work and an item to use to configure the cell. This is great because it vastly simplifies some of the pain associated with managing state and pulling the correct item for the currently selected index path. If we look a little more closely at the initializers for UICollectionView.CellRegistration we can also see that it can be defined with either a code-based handler or with a nib file (which can come in handy for some complex layout scenarios, legacy code, etc.).

        public typealias Handler = (Cell, IndexPath, Item) -> Void

        public init(handler: @escaping UICollectionView.CellRegistration<Cell, Item>.Handler)

        public init(cellNib: UINib, handler: @escaping UICollectionView.CellRegistration<Cell, Item>.Handler)

So getting back to our example with our fruit stand, we just need to define three unique cell configurations to handle each possible data scenario.

    /// Configured apple cell
    /// - Returns: Cell configuration
    private func appleCell() -> UICollectionView.CellRegistration<UICollectionViewCell, Apple> {
        return UICollectionView.CellRegistration<UICollectionViewCell, Apple> { (cell, indexPath, item) in
            cell.configure(label: "\(, core thickness: \(item.coreThickness)mm", relatedColor: .systemGreen)
    /// Configured orange cell
    /// - Returns: Cell configuration
    private func orangeCell() -> UICollectionView.CellRegistration<UICollectionViewCell, Orange> {
        return UICollectionView.CellRegistration<UICollectionViewCell, Orange> { (cell, indexPath, item) in
            cell.configure(label: "\(, peel thickness: \(item.peelThickness)mm", relatedColor: .systemOrange)
    /// Configured empty data cell
    /// - Returns: Cell configuration
    private func emptyCell() -> UICollectionView.CellRegistration<UICollectionViewCell, EmptyData> {
        return UICollectionView.CellRegistration<UICollectionViewCell, EmptyData> { (cell, indexPath, item) in
            cell.configure(label: item.emptyMessage, relatedColor: .systemRed)

Updating the Collection View with Pre-defined Cell Configurations

Since we have created our cell configurations, we’re now ready to dequeue these configurations as appropriate as we create the UICollectionViewDiffableDataSource. We’ll do this by calling dequeueConfiguredReusableCell(using:for:item:) with the appropriate cell configuration in the using: parameter. As with the table view example from the last blog post, we’re just examining the item to determine which cell configuration to use.

        let dataSource = UICollectionViewDiffableDataSource<Section, AnyHashable>(collectionView: collectionView) { collectionView, indexPath, item in
            if let apple = item as? Apple {
                return collectionView.dequeueConfiguredReusableCell(using: self.appleCell(), for: indexPath, item: apple)
            } else if let orange = item as? Orange {
                return collectionView.dequeueConfiguredReusableCell(using: self.orangeCell(), for: indexPath, item: orange)
            } else if let emptyData = item as? EmptyData {
                return collectionView.dequeueConfiguredReusableCell(using: self.emptyCell(), for: indexPath, item: emptyData)
            } else {
                fatalError("Unknown item type")

In the above example, appleCell(), orangeCell(), and emptyCell() are simply the functions defined earlier that return a UICollectionView.CellRegistration<UICollectionViewCell, Item> instance.

Whenever we get data back from our API or data source, we can just call our updateSnapshot function to update our UI.

    /// Update the data source snapshot
    /// - Parameters:
    ///   - apples: Apples if any
    ///   - oranges: Oranges if any
    private func updateSnapshot(apples: [Apple], oranges: [Orange]) {
        // Create a new snapshot on each load. Normally you might pull
        // the existing snapshot and update it.
        var snapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>()
        defer {
        // If we have no data, just show the empty view
        guard !apples.isEmpty || !oranges.isEmpty else {
            snapshot.appendItems([EmptyData()], toSection: .empty)
        // We have either apples or oranges, so update the snapshot with those
        snapshot.appendSections([.apples, .oranges])
        snapshot.appendItems(apples, toSection: .apples)
        snapshot.appendItems(oranges, toSection: .oranges)

Setting Up Header and Footer Configurations

Just as with collection view cell registrations, iOS 14 lets us create supplementary registrations for headers and footers as well. By creating a UICollectionView.SupplementaryRegistration with any UICollectionReusableView, you can easily modernize how your collection view sections are defined. In the code below, we’re setting up a header for each section with the value of the section type from the snapshot.

    private func configuredHeader() -> UICollectionView.SupplementaryRegistration<HeaderView> {
        return UICollectionView.SupplementaryRegistration<HeaderView>(elementKind: "section-header") { (supplementaryView, title, indexPath) in
            let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
            supplementaryView.titleLabel.text = section.rawValue

Then when we create the data source, we can call dequeueConfiguredReusableSupplementary(using:for:) to create the supplementary view as part of its supplementary view provider closure.

        dataSource.supplementaryViewProvider = { (view, kind, indexPath) in
            print("\(view), \(kind), \(indexPath)")
            return self.collectionView.dequeueConfiguredReusableSupplementary(using: self.configuredHeader(), for: indexPath)

Where To Go From Here?

With iOS 13 and especially iOS 14, Apple has totally revamped how collection views can be created with compositional layouts and diffable data sources. I’d strongly recommend checking out Implementing Modern Collection Views in the Apple developer documentation and exploring everything that has changed with collection views. While SwiftUI with the LazyHGrid and LazyVGrid are here and available for presenting collections, the trusty UICollectionView still has plenty of life left and may be able to meet your unique needs!

Get the source for this blog post from GitHub.

Using iOS Diffable Data Sources with Different Object Types

Since iOS 13, we’ve been able to revamp the way our table and collection views work by using diffable data source classes (UITableViewDiffableDataSource and UICollectionViewDiffableDataSource respectively) and snapshots. This provides the advantage of being able to write less code for these types of views, while making our state management much more robust and less likely to fail with the dreaded NSInternalConsistencyException error.

While there are numerous articles and blog posts around about the typical use case for using diffable data source, a homogenous data set with identical table cells, I found very little on the more real world scenario where a table might have two or three different data types and possibly an ’empty data’ cell as well. We’ll be looking at one approach to solving this problem here today.

Setting Up Our Data and View

For our simple scenario, we’ll just assume that we’ve got a fruit stand selling apples and oranges. Every few days some rain blows through so we need to post a message that we’re closed in that case. It’s kind of a boring fruit stand but that’s ok 🙂

The data structures that we’ll use are laid out below. Note that all of the struct objects implement the Hashable protocol, which is a requirement to use diffable data sources. Both the SectionIdentifierType and ItemIdentifierType must implement Hashable so that the data source can differentiate one row or section from other.

struct Apple: Hashable {
    var name: String
    var coreThickness: Int

struct Orange: Hashable {
    var name: String
    var peelThickness: Int

struct EmptyData: Hashable {
    let emptyMessage = "We're sorry! The fruit stand is closed due to inclement weather!"
    let emptyImage = "cloud.bold.rain.fill"

So now we just need to create our view controller and an enum for each different section we might want to use. There’s also a random function to generate data or the empty data set depending on the “weather”. Approximately 33% of the time we run the playground code, we should see the “bad weather” scenario and the empty data cell instead of a list of apples and oranges.

final class DiffableTableViewController : UIViewController {
    var tableView: UITableView!
    enum Section: String, CaseIterable, Hashable {
        case apples = "Apples"
        case oranges = "Oranges"
        case empty = "No Data Found"

    override func viewDidLoad() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "AppleCell")
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "OrangeCell")
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "EmptyDataCell")
        // Just a silly method to pretend we're getting empty data every 3rd or so call (for demo purposes every 3 days or so we get rain at the fruit stand)
        let badWeatherDay = Int.random(in: 0..<3)
        badWeatherDay > 0 ? getData() : getEmptyData()

    class DiffableViewDataSource: UITableViewDiffableDataSource<Section, AnyHashable> {
        override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            //Use the snapshot to evaluate the section title
            return snapshot().sectionIdentifiers[section].rawValue

As part of the table view setup for this example, I’ve also created three different types of cells (because apples, oranges, and empty data are different). Another important item to mention is that we’ve subclassed UITableViewDiffableDataSource. This isn’t required but is useful if you want to override any of the table or collection view data source methods. In our case, we’re using this subclass to set the title header to the raw value of the section identifier.

With what we’re trying to do here, it’s also important to note that we’re leaving the item type as AnyHashable. This will allow us to test the item type and show the appropriate UI. If you just have one item type, you can actually specify the type of the item when the data source is defined (e.g. Apple or Orange instead of AnyHashable).

Configuring the Diffable Data Source

Now that we’ve got a view and some data, let’s create the data source.

private lazy var dataSource: DiffableViewDataSource = makeDataSource()

makeDataSource is just a function that we’ve written that will create the data source and switch between the different cell types based on the type of the item.

    /// Create our diffable data source
    /// - Returns: Diffable data source
    private func makeDataSource() -> DiffableViewDataSource {
        return DiffableViewDataSource(tableView: tableView) { tableView, indexPath, item in
            if let apple = item as? Apple {
                let cell = tableView.dequeueReusableCell(withIdentifier: "AppleCell", for: indexPath)
                cell.textLabel?.text = "\(, core thickness: \(apple.coreThickness)mm"
                return cell
            } else if let orange = item as? Orange {
                let cell = tableView.dequeueReusableCell(withIdentifier: "OrangeCell", for: indexPath)
                cell.textLabel?.text = "\(, peel thickness: \(orange.peelThickness)mm"
                return cell
            } else if let emptyData = item as? EmptyData {
                let cell = tableView.dequeueReusableCell(withIdentifier: "EmptyDataCell", for: indexPath)
                cell.textLabel?.text = emptyData.emptyMessage
                return cell
            } else {
                fatalError("Unknown cell type")

This really the guts of how we are going to deal with different object types needing different heterogenous UI. We’re just testing item variable above and acting on that object if we have one. While diffable data sources do include the indexPath variable, the whole point of using them is to move away from counting sections and rows and managing that complex table state. While you might consider using indexPath to achieve much the same thing as above, that introduces risk and many of the state management problems that diffable data sources solve.

Updating the Snapshot

A key part of using diffable data sources is to use the NSDiffableDataSourceSnapshot struct to update the table or collection view when data changes (rather than trying to insert or delete table rows or sections). So let’s look at the definition of this class.

@available(iOS 13.0, tvOS 13.0, *)
public struct NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

We can see the requirement for the Hashable protocol as well for the sections and items (once again so that items can be differentiated).

Our updateTable function below is called whenever we have received some new data and are ready to update the diffable data source via the snapshot. In our simple playground, this just happens in viewDidLoad but in real life, you would probably be calling an API or populating from local data at various points.

    /// Update the data source snapshot
    /// - Parameters:
    ///   - apples: Apples if any
    ///   - oranges: Oranges if any
    private func updateTable(apples: [Apple], oranges: [Orange]) {
        // Create a new snapshot on each load. Normally you might pull
        // the existing snapshot and update it.
        var snapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>()
        defer {
        // If we have no data, just show the empty view
        guard !apples.isEmpty || !oranges.isEmpty else {
            snapshot.appendItems([EmptyData()], toSection: .empty)
        // We have either apples or oranges, so update the snapshot with those
        snapshot.appendSections([.apples, .oranges])
        snapshot.appendItems(apples, toSection: .apples)
        snapshot.appendItems(oranges, toSection: .oranges)

The defer keyword ensures that the data source snapshot is applied before the function exits.

When we’ve got good weather, we’ll see a UI like this from our playground.

UI with two different sections from a diffable data source

If we’ve got storms in the area, we’ll see this UI.

UI with empty data section from a diffable data source

Summing Things Up

That’s pretty much all there is to it with this method of using heterogenous data and a diffable data source. There are obviously different ways to approach the problem of using different types of cells and data with these data sources. However you choose to approach the problem though, it’s advisable to avoid using the indexPath to do so. By relying on the power of snapshots and hashable types to power your table or collection, you can reduce your risk of runtime errors and write less code at the same time.

Get the source for this blog post from Github.

New UIDatePicker Styles in iOS 14

One of the nice new features that came out with iOS 14 were some new options around the venerable UIDatePicker. There are now some choices other than just the slot machine-style wheels! Since iOS 2.0, the original style of UIDatePicker has been the only standard UIKit option for developers looking to let users select dates and times. The UIDatePicker got a little bit of a facelift with the new styles in iOS 7+ but really hasn’t had much love since then. With iOS 13.4 and iOS 14.0, things changed with the addition of the UIDatePickerStyle enumeration and the addition of two new styles other than the default wheels.

Default UIDatePicker style

Setting UIDatePicker Style

The new UIDatePickerStyle enum has four options:

  • wheels – Shows the standard, spinning wheel style picker. (iOS 13.4+)
  • compact – Displays a formatted label that will show a popover with more UI when tapped. (iOS 13.4+)
  • inline – An editable field meant to be used inline. (iOS 14.0+)
  • automatic – Will pick the most appropriate style based on the mode and platform. Usually on iOS this will wind up being wheels but Mac Catalyst apps might use compact or another style. (iOS 13.4+)

While you certainly could let the UIDatePicker select the style for you with automatic, I’d suggest manually setting the style to your preferred option to avoid unexpected UI changes with future iOS releases. Also there is no guarantee that the current default for datePickerStyle of wheels will stay that way forever.

Setting the date picker style is as simple as changing the preferredDatePickerStyle property on the date picker instance that you are using.

@IBOutlet var datePicker: UIDatePicker!

...code... = <set your date here>
datePicker.mode = .dateAndTime
datePicker.preferredDatePickerStyle = .inline

The above code will result in the nice new inline interface. Unfortunately the inline style takes up quite a bit of vertical space, but it does let you avoid additional UI transitions, popovers, etc. if that’s what you need.

Date and Time Inline

If you want to use the compact style, it’s just a different value for preferredDatePickerStyle.

datePicker.preferredDatePickerStyle = .compact

The compact style will initially present UI that looks like this.

Date and Time Compact Label

When the user taps on the date or the time in this case, the entire calendar and time control combination appears in a popover window.

You’ll notice of course when you use it that this looks and behaves exactly like the inline date picker style but is presented in a modal window with a translucent background.

Date and Time Only Options

The UI above all shows examples of date and time but of course you can (and most of the time) probably will just display one or the other. The Date and Time only inline styles are basically just cropped versions of the full UI.

Date Inline
Time Inline

The compact UI collapsed mode with just the date and times fit quite nicely into an existing UI with rounded text fields and definitely provide the most space-efficient option. This style also has the advantage of removing that extra label or button control that developers have had to use to trigger the existing UIDatePicker UI in the past.

Date Compact Collapsed
Time Compact Collapsed

Don’t Forget About Locale

One of the major benefits of using the Apple-provided UIDatePicker instead of trying to spin your own is out-of-the-box internationalization. By just setting the locale property on the date picker to “zh_CN” for example, we can get a Chinese language date time picker. Cool, huh?

Not Available for Countdown Timer Mode

Note that none of these new styles are available if your datePickerMode is set to countdownTimer. As of iOS 14.2, setting a countdown timer date picker to compact or inline will actually cause a run-time app crash (I’ve submitted feedback to Apple on this so hopefully it will get resolved down the road).

Which Style Do I Pick?

Which user interface for the UIDatePicker that you prefer is obviously up to you as an app developer. It is always most important to consider your users, what they expect as iOS users, and what will make their experience in your app the most delightful. The new UIDatePicker options will at least let us refresh our apps and keep them up to date going forward. Apple provides some guidance in the Human Interface Guidelines (HIG) but not a ton. As always, while new UI provides us with some choices (and some compromises), which way to go on your app’s journey is up to you.

Identify Supported Images and Create Collections in SF Symbols 2

If you’ve been doing any sort of app development with iconography since iOS 13 came out, there’s a good chance you leveraged the original SF Symbols app. With around 1,600 scalable, vector-based symbols integrating with the system San Francisco font, along with guidance for creating your own symbols, it was ground-breaking in terms of providing consistent, scalable UI on Apple platforms (similar but even more full-featured than the Android-oriented Material Design icons).

For example to define a simple image of a lasso without needing any assets in iOS 13+, you could just open the SF Symbols app, find the lasso image,

and then use it in code like this.

let image = UIImage(systemName: "lasso")

You can also use the UIImage.Configuration and UIImage.SymbolConfiguration classes to tweak the images for light or dark mode, etc. by applying different font weights, point sizes, text styles, etc. Here we assign the lasso image the Headline font style at the pre-defined large scale.

let configuration = UIImage.SymbolConfiguration(textStyle: .headline, scale: .large)
let image = UIImage(systemName: "lasso")?.applyingSymbolConfiguration(configuration)

Doing all this to an actual icon so it blends more completely with the text and UI around it is very cool!

Enter SF Symbols 2 and Even More Symbols

With iOS 14, Apple released another 750+ new symbols along with a number of multi-color symbols as well that adapt to Apple system colors. With all the new symbols it’s easy to get confused about which symbols are iOS 14+ and which are not. If you still need to support iOS 13 (like most of us through at least next fall), it’s critical not to use an iOS 14-only symbol without realizing it.

Identifying Symbol Compatibility

There are a couple ways inside the SF Symbols app that you can do this. The easiest way is just to select the image in question and tap the Info icon on the top tool bar, which will show a compatibility panel like below. Here we can see that ‘lasso’ is supported in iOS 13.0+ and should be safe.

But the newer, fancier ‘lasso.sparkles’ is only available in iOS 14.0+. Alas!

If you need to scan the list of all symbols to isolate just iOS 13.0+ compatibility, for example, you can also switch from default Gallery mode to List Mode by tapping the appropriate button in the middle of the toolbar.

Get Even More Out of SF Symbols with Custom Collections

Make sure that you squeeze everything you can out of the SF Symbols app and make a custom collection for the symbols that your app uses. Just tap the + button in the bottom left of the view to create a new collection and drag/drop any icons that you use into your collection. Here I set up a collection for a fictional weather app with some thematically consistent symbols. Having custom collections can keep you from getting overwhelmed by the sheer volume of symbols available as well.

Have fun exploring and using symbols in your app!

Better Uniform Type Identifiers with Xcode 12

If you’ve been doing any kind of file uploads, downloads, previews, and such in past versions of Xcode and needed to deal with MIME types or file extensions, you’ve probably had to deal with Uniform Type Identifers. Using CoreServices and some of its methods such as UTTypeCreatePreferredIdentifierForTag and UTTypeCopyPreferredTagWithClass could avoid some hard-coding of identifiers and such; however these methods are more difficult to work with as they involve unmanaged CFString classes as well as iteration through tag dictionaries in some cases.

Enter the New UTType Class

With Xcode 12 and iOS 14 (macOS 11, watchOS 7, or tvOS 14 as well), we now have an awesome new Swift-based UTType struct that abstracts a lot of the complexity out and makes life simpler when dealing with file extensions and MIME types for documents from Microsoft Office, Adobe Acrobat, or even your own custom file types. By providing properties such as preferredMimeType, preferredFilenameExtension, and identifier, developers will now be able to craft code more cleanly in Swift with less effort.

For example to get the key components for an Adobe Acrobat PDF document, we can just use import UniformTypeIdentifiers and do this.

import UniformTypeIdentifiers

let pdf: UTType = .pdf

print("MIME type: \(pdf.preferredMIMEType!)")   //application/pdf
print("File Extension: \(pdf.preferredFilenameExtension!)") //pdf
print("Identifier: \(pdf.identifier)")  //com.adobe.pdf

You can also infer a full UTType object from a file extension or MIME type with the static types(tag:tagClass:conformingTo:) method as follows.

import UniformTypeIdentifiers

let word: UTType = .word

print("MIME type: \(word.preferredMIMEType!)")   //application/vnd.openxmlformats-officedocument.wordprocessingml.document
print("File Extension: \(word.preferredFilenameExtension!)") //docx
print("Identifier: \(word.identifier)")  //org.openxmlformats.wordprocessingml.document

extension UTType {
    // Word documents are not an existing property on UTType
    static var word: UTType {
        // Look up the type from the file extension
        UTType.types(tag: "docx", tagClass: .filenameExtension, conformingTo: nil).first!

Using UTType with Existing Functions

Obviously it will take Apple a while to spread this new class out through all the existing areas in the app but you can still start leveraging UTType in your existing code today. Many of the existing functions that accept strings can just be altered to use the new types instead of raw, hard-coded strings or the existing UTI’s that were in CoreServices in UTCoreTypes.h. For example, when donating a user activity for Spotlight, it’s necessary to spin up a CSSearchableItemAttributeSet to append thumbnails or properties such as keywords. Previously this would have resulted in some code looking like this with kUTTypeItem in this case.

let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)

We can now replace that with this in iOS 14 code and possibly remove a reference to CoreServices.

let attributes = CSSearchableItemAttributeSet(itemContentType: UTType.item.identifier)

Fortunately some existing methods are already being updated. Where you might have done something like this in the past to initialize a UIDocumentPickerViewController for PDF and Word documents,

// Word not available in UTCoreTypes
let documentTypes = [kUTTypePDF as String, "org.openxmlformats.wordprocessingml.document"]
// Initialize document picker with the allowed types
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)

You can now do this with UTType with some type safety and less hard-coding of strings using this overload of init on UIDocumentPickerViewController.

//PDF is a known type but word needs to be looked up
var documentTypes: [UTType] = [.pdf, .word]
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: documentTypes)

extension UTType {
    //Word documents are not an existing property on UTType
    static var word: UTType {
        UTType.types(tag: "docx", tagClass: .filenameExtension, conformingTo: nil).first!

What To Do Next?

So after you fix up your code and get rid of some of those old references to UTTypeCore, you might also want to check out defining your own Uniform Type Identifier, especially if your app has a custom document format that other apps can leverage.

It’s great to see Apple modernizing some of the core functionality and introducing Swift compatibility with code that’s been baked in for years. Changes like this make it easier for us as developers to move forward and write stable, maintainable code.

Get the Source

Download sample code with some of the above examples from Github.

Manage Developer Disk Space Easily with macOS Big Sur

Prior to macOS 11 Big Sur, cleaning up development disk space by deleting old iOS or watchOS device support versions and Xcode caches was something of a pain, requiring command-line calls to directories scattered in a number of different places. For example to delete Xcode cache you might run something like this.

rm -rf ~/Library/Caches/*

You might also have to track down several other folders to do a clean job of it and then still have to find old device support versions in these directories.

~/Library/Developer/Xcode/iOS DeviceSupport
~/Library/Developer/Xcode/watchOS DeviceSupport

Trying to find development-related system files in macOS prior to Big Sur was likewise not simple in the UI. Going through the Apple menu and About This Mac then Storage and even Manage... did not help.

macOS Catalina Storage Management

Enter Big Sur and the Developer Storage Section

With macOS 11 Big Sur, Xcode developers everywhere should be ecstatic about the new Developer section on the storage management view above. Once you go to the Apple menu then About This Mac and then Storage, you’ll see the colored storage map has a new section called Developer.

macOS Big Sur Storage Map

Additionally, if you now use the Manage... button, you’ll see that new Developer section reflected as available to manage, with sections for caches/archives as well as iOS and watchOS device support.

macOS Big Sur Storage Dialog

Freeing up disk space by deleting any of the above items is as simple as right-click and selecting Delete. In the view above, some old device support versions from Xcode 11 are available to be safely deleted (assuming I don’t still want to target iOS 13 for example) since I’ve moved on to Xcode 12. In the view below, you can see the Delete menu for Xcode Caches being used.

macOS Big Sur Deleting Xcode Cache

Enjoy the Free Disk Space!

It can sometimes be a true horror story trying to free enough disk space to do something basic like updating the operating system or installing multiple versions of Xcode. The macOS Big Sur betas required a whopping 50GB free before installing, for example. It took some juggling on my 250GB SSD to make that work. I’m glad Apple is providing the tools to finally start managing some of the temporary Xcode files to make similar tasks simpler.

I’d definitely recommend all new developers start with a minimum of 500GB for disk space on new workstations or MacBooks; but that’s not always possible for budgetary or other reasons. If you’re stuck in that situation, hopefully this will help!

Using Comparable with Enumerations in Swift 5.3

As expected, there was so much goodness in WWDC 2020 last week. From exciting new software changes in Swift UI, collection views, and much more to the anticipated move away from Intel to Apple Silicon, there was something exciting for just about everyone. For this post, I’m just going to focus on a tiny sliver of the new changes in Swift 5.3 related to my last post Cleaning Up Swift Enumeration Evaluation – using the Comparable protocol to evaluate enum expressions.

Comparing Enumerations before Swift 5.3

Previously we looked at some different ways to evaluate whether an expression was present in an enumeration with switch statements, computed properties, and RawRepresentable. With Swift 5.3 and specifically proposal SE-0266, enums without an associated type or with one conforming to Comparable can now conform to Comparable themselves and gain the ability to compare values.

What does this mean? Well, prior to this change, if we had an enum that was intrinsically sorted (like ContainerLevel below), we would have had to either declare a type for it or use a private backing variable with a switch or some custom logic to implement sorting and comparability. For example, the following works but requires that the enumeration use Int raw values to conform with Comparable and we have to manually code the < operator.

// Here we have to declare the enum as a Comparable type to implement the protocol
enum ContainerLevel: Int, Comparable {
    case empty = 0
    case almostEmpty
    case halfFull
    case almostFull
    case full
    static func < (lhs: ContainerLevel, rhs: ContainerLevel) -> Bool {
        return lhs.rawValue < rhs.rawValue

//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee)  //true

What’s Changed?

With iOS 14 and Xcode 12, we can now declare Comparable with no explicit overload needed resulting in much cleaner, more succinct code.

// With iOS 14, we can just use the `Comparable` protocol and the intrinisic ordering of the enum
enum ContainerLevel: Comparable {
    case empty
    case almostEmpty
    case halfFull
    case almostFull
    case full    

//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee)   //true

Sorting By Associated Values

You can also implicitly sort enumerations with associated values as well and it works as expected. Let’s pretend we have a function to look at coffee machine water levels for example.

enum ContainerLevel: Comparable {
    case empty
    case partiallyFull(Int)
    case full
    static func isAlmostFull(_ level: ContainerLevel) -> Bool {
        //More than 90% full
        return level > partiallyFull(90)
    static func isAlmostEmpty(_ level: ContainerLevel) -> Bool {
        //Less than 10% full
        return level < partiallyFull(10)

//Running close to empty!  Yikes!
let coffeeMachine: ContainerLevel = .partiallyFull(5)

print(ContainerLevel.isAlmostFull(coffeeMachine))   //false
print(ContainerLevel.isAlmostEmpty(coffeeMachine))  //true

As we would also anticipate, sorting an array of enums works as well.

let levels: [ContainerLevel] = [.full, .empty, .partiallyFull(75), .partiallyFull(15)].sorted()

print({"\($0)"}))  //"empty", "partiallyFull(15)", "partiallyFull(75)", "full"

Nice! We’ve got a properly sorted list from the unsorted input that we provided. Obviously this is a trivial example; but hopefully you can leverage Comparable and enum sooner rather than later! Since this change is tied to Swift as well, you should be able to use it in apps on earlier versions of iOS as well with Xcode 12 and Swift 5.3. Happy coding!