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...

datePicker.date = <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/com.apple.dt.Xcode/*

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(levels.map({"\($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!

Cleaning Up Swift Enumeration Evaluation

Just some quick and simple Swift stuff in this post around writing cleaner code. It’s pretty common to see a pattern like the following in existing code when evaluating enumerations.

enum IceCreamFlavor {
    case chocolate
    case chocolateChip
    case coffee
    case peach
    case strawberry
    case vanilla
}
let currentFlavor: IceCreamFlavor = .chocolate

///...code...

// Long-winded test to determine if flavor is basic, non-premium flavor.
// Let's fix this!!
let isBasicFlavor = currentFlavor == .strawberry || currentFlavor == .chocolate || currentFlavor == .vanilla   
print(isBasicFlavor) //True

While the above code works, it’s also not very legible, is repetitive, and can be hard to maintain as well as error-prone if multiple enum values need to be tested. So how can we make this better?

Using a Function and switch

It’s always important to consider your different options when coding and weigh the pros and cons of each approach. Let’s step through some different ways to make our code better!

One of the simplest scenarios to try would be to use the switch statement to at least make things more readable and reduce repetitive code. For example:

func isBasicFlavor(_ flavor: IceCreamFlavor) -> Bool {
    switch flavor {
    case .strawberry, .chocolate:
        return true
    default:
        return false
    }
}

print(isBasicFlavor(currentFlavor))

That’s a little better and encapsulates the logic; but we might be better served adding this function to the enum itself so that any caller could use it more simply and conveniently.

Using Computed Properties

Creating a computed property called isBasicFlavor on the enum lets us do just that.

enum IceCreamFlavor {
    case chocolate
    case chocolateChip
    case coffee
    case peach
    case strawberry
    case vanilla
    
    var isBasicFlavor: Bool {
        switch self {
        case .strawberry, .chocolate, .vanilla:
            return true
        default:
            return false
        }
    }
}

let isBasicFlavor = currentFlavor.isBasicFlavor
print(isBasicFlavor)

There are still other ways to make this even better and replace the possibly clunky switch statement. One approach would be to use a static array as the computed property and the contains statement to evaluate it from the calling code (and maybe show that list of basic flavors in the calling app as a bonus!).

enum IceCreamFlavor {
    case chocolate
    case chocolateChip
    case coffee
    case peach
    case strawberry
    case vanilla
    
    static var basicFlavors: [IceCreamFlavor] {
        return [.chocolate, .vanilla, .strawberry]
    }    
}

let isBasicFlavor = IceCreamFlavor.basicFlavors.contains(currentFlavor)
print(isBasicFlavor)

Still Another Option Leveraging Extensions

Another method could be to leverage the raw value of the enum to perform some form of comparison with a Swift extension. This can come in handy when you don’t control the source code or values from the extension itself (which might be in someone else’s library). While we currently cannot create a Swift extension for an enum directly, we can create one on a protocol associated with the enum.

In our example below, we have based the enum on the String type which as a raw value type means the enum will now conform to the RawRepresentable protocol. This then allows us to create a simple extension like that essentially does the reverse of the previous example.

enum IceCreamFlavor: String {
    case chocolate = "Chocolate"
    case chocolateChip = "Chocolate Chip"
    case coffee = "Coffee"
    case peach = "Peach"
    case strawberry = "Strawberry"
    case vanilla = "Vanilla"
    
    static var basicFlavors: [IceCreamFlavor] {
        return [.chocolate, .vanilla, .strawberry]
    }
}

extension RawRepresentable where RawValue: Equatable {
    /// Evaluate if input is present in array of values
    /// - Parameter input: Input
    /// - Returns: Boolean true if the input present in array
    func isContained(in input: [RawValue]) -> Bool {
        return input.contains(rawValue)
    }
}

let isBasicFlavor = currentFlavor.isContained(in: IceCreamFlavor.basicFlavors.map({ $0.rawValue }))   
print(isBasicFlavor)

This example was a little convoluted and is, in my opinion, less satisfactory but does present yet another way to skin the proverbial cat or deal with certain cases. Because the extension relies on RawRepresentable, similar functionality would work with many different types of enumerations (or custom objects implementing Equatable and RawRepresentable).

Where To Go Next?

This article will hopefully help you explore some options around Swift enumerations and some of the available tools. I’d also encourage you to explore the Swift language more deeply as well as look at other possibilities for working with enumerations such as the CaseIterable protocol, introduced a couple years ago in Swift 4.2 and Xcode 10. Try listing all of the ice cream flavors above directly from the enum with CaseIterable and allCases for example!

More importantly it’s much more important (and fun!) to realize that coding is an evolution, that there are always different approaches (both better and worse) to solving problems, and that we can always make our code better and learn at the same time! With WWDC 2020 just around the corner, knowing and believing this will put you ahead of the game.

Use the new Xcode 11 Distribution Certificate Format

If you’ve ever distributed an app to the Apple App Store, you’ve probably generated an iOS Distribution certificate (either from Xcode or directly from the Apple Developer site. With Xcode 11, Apple released new Development and Distribution certificate types and has started using those by default. This was mentioned in the Xcode 11 release notes:

While Apple did say “preexisting iOS and macOS development and distribution certificates continue to work”, they did not clarify that all new certificates needed to be generated with the new format (and not the old legacy iOS Distribution certificate format). I ran into some pain recently having picked the “iOS Distribution (App Store and Ad Hoc)” from Xcode 11 when replacing an expiring certificate. If you are manually generating your certificate in Xcode 11 (instead of using Xcode to do it for you after generating an archive), it looks like you now need to make sure that you select the Xcode 11-compatible certificate format (one of the first two choices below as appropriate):

The new certificate will now show with the prefix “Apple Distribution” instead of “iPhone Distribution” in Keychain access as well.

If you create the wrong type (or never created one), and are attempting to distribute through Xcode, you may see a frustrating message like the one below. You can obviously just let Xcode do the lifting for you here; but make sure to export and save securely the public and private keys in that case.

If you are running a command-line archive for a CI/CD process in Jenkins or similar and are exporting and uploading directly to the App Store with xcodebuild, you may see messages that your distribution certificate cannot be found (once again because you created the wrong type of certificate for Xcode 11).

As a side note, if you check and then un-check Automatically Manage Signing from the Signing and Capabilities tab, the backing code in the Xcode project is changing as well from ‘iPhone Developer’ to ‘Apple Development’

Hope this helps you out if you’re running into this issue!

Nicely Formatted Lists with ListFormatter

Last time we took a look at the very useful RelativeDateTimeFormatter to create localized strings describing date differences programmatically. In this post we’ll look at a better way to handle presenting lists to your users. As programmers, it’s pretty common to have to take a list of objects (e.g. apples, bananas, oranges) and express the list as a string (e.g. “apples, bananas, and oranges”). Doing so however, can often involve some ugly code. Let’s say we just wanted to do the above before iOS 13. We might have done something like:

var list = ["apples", "bananas", "oranges"]
var output = ""
for (index, item) in list.enumerated() {
    output.append(item)
    if index < list.count - 2 {
        //Normal separator
        output.append(", ")
    } else if index < list.count - 1 {
        //Final separator
        output.append(", and ")
    }
}
print(output)

The above is not pretty (and, yes, there are a million ways to do this), but more importantly it’s not easy to localize this, deal with linguistic idiosynchronicities, or even right-to-left formatting in Arabic. With iOS 13, Apple also introduced an incredibly useful tool to format lists that can help with all of this, the ListFormatter class!

Simple List Formatting

As with most of the classes derived from Formatter, creating and outputting a string with the ListFormatter is fairly simple.

  1. Create the formatter
  2. Set some properties on the formatter
  3. Call the appropriate string(for:) function

For example, to create a simple list of localized strings, you could simply do the following:

var fruits = [
    NSLocalizedString("apples", comment: "red or green fruit"),
    NSLocalizedString("bananas", comment: "long yellow fruit"),
    NSLocalizedString("oranges", comment: "round orange fruit")
]
//Create the formatter
let formatter = ListFormatter()
//Set the locale as appropriate
formatter.locale = Locale(identifier: "en_US")
//Get the string
print(formatter.string(for: fruits)!)

//"apples, bananas, and oranges"

If you have resource files set up with the appropriate localized strings, that’s all that’s needed. It doesn’t matter how long the list is, what it contains, etc. Pretty simple, huh? We can make the above example even simpler though with the localizedString(byJoining:) property on ListFormatter which is static and skips the formatter creation steps in the example above.

//One easy step without localized strings set up individually
var fruits = ["apples", "bananas", "oranges"]
print(ListFormatter.localizedString(byJoining: fruits))

//"apples, bananas, and oranges"

Now in this example, you don’t have the control of NSLocalizedString for creating comments, etc. but the syntax is much more succinct. This method also uses the locale of the current context and so may not be as flexible as needed.

Getting Even More out of Formatted Lists

Let’s say that you have a list of dates or numbers and these need to be output into a nicely formatted string across multiple locales. Before ListFormatter, you would have to consider writing a ton of code to first format each individual item in the list and then loop through and create the list. And even then, localizing properly across the entire globe would be extremely difficult if not near impossible to do 100% correctly. Once again, enter the ListFormatter!

Let’s say that you’ve got a set of prices that need to be output in a formatted list for a brochure or similar.

//Prices for each widget
var priceList: [Double] = [10, 100, 500, 1000]

All we have do now is generate or use a ListFormatter and pass the appropriate formatter to the itemFormatter property. This could be a DateFormatter, NumberFormatter or even your own custom implementation of the Formatter class. So first we’ll create the item formatter:

//Create the item formatter.  Don't forget to set the locale here as well
let priceFormatter = NumberFormatter()
priceFormatter.locale = Locale(identifier: localeIdentifier)
priceFormatter.numberStyle = .currency

Then we just need to create the list formatter and assign this formatter:

//Create list formatter and assign the item formatter to the list formatter
let formatter = ListFormatter()
formatter.itemFormatter = priceFormatter
formatter.locale = Locale(identifier: localeIdentifier)
return formatter.string(from: priceList)!

The list formatter controls the separators between the list items and the item formatter formats the items themselves. Lets look at the code above in a finished example of the above and examine what actually comes back.

//Prices for each widget
var prices: [Double] = [10, 100, 500, 1000]

/// Format price list
/// - Parameters:
///   - list:
///   - localeIdentifier: Locale identifier
/// - Returns: Price list all formatted
func formatList(_ prices: [Double], localeIdentifier: String) -> String {
    //Create the item formatter.  Don't forget to set the locale here as well
    let priceFormatter = NumberFormatter()
    priceFormatter.locale = Locale(identifier: localeIdentifier)
    priceFormatter.numberStyle = .currency
    
    //Create list formatter and assign the item formatter to the list formatter
    let formatter = ListFormatter()
    formatter.itemFormatter = priceFormatter
    formatter.locale = Locale(identifier: localeIdentifier)
    
    //Output the results or empty string
    guard let output = formatter.string(from: prices) else {
        return ""
    }
    return output
}

print(formatList(prices, localeIdentifier: "en_US"))  //English US
print(formatList(prices, localeIdentifier: "fr_FR"))  //French 
print(formatList(prices, localeIdentifier: "de_DE"))  //German
print(formatList(prices, localeIdentifier: "zh_CN"))  //Chinese
print(formatList(prices, localeIdentifier: "ar_AE"))  //Arabic

Output
--------------
$10.00, $100.00, $500.00, and $1,000.00
10,00 €, 100,00 €, 500,00 € et 1 000,00 €
10,00 €, 100,00 €, 500,00 € und 1.000,00 €
¥10.00、¥100.00、¥500.00和¥1,000.00
د.إ.‏ 10.00، د.إ.‏ 100.00، د.إ.‏ 500.00، ود.إ.‏ 1,000.00

Notice above that the currency symbols have been inserted as appropriate to each locale. Even more impressive, note that the list in Arabic has been properly output right-to-left.

Keep Going!

Hopefully the above will get you started generating some cool looking lists as well as save you from writing a bunch of messy, fragile boilerplate code. When you consider what auto-layout can do to word wrap your lists generated from ListFormatter, your user interfaces should look better than ever.

There are tons of possibilities with this formatter as well as all of the other formatters available through Foundation in iOS. The online documentation is still somewhat sparse when it comes to the new stuff online but the code comments around ListFormatter are actually fairly complete. Try writing your own custom formatter based on Formatter if there’s some object in your code that needs to be output to a String or AttributedString different ways. Combine that with ListFormatter for even more power!

Till next time!

Formatting Relative Dates the Easy Way

Prior to iOS 13, converting a date from the Date object to a nicely formatted string such as “10 days ago” or “next month” required a lot of custom coding or maybe third-party libraries to manage the conversion. The existing DateFormatter is very versatile as well as necessary for easy date localizations but just can’t make a nicely formatted relative date across all date ranges. The DateFormatter.doesRelativeDateFormatting property (available since iOS 4) will output some simple relative date strings like “today” and “tomorrow” but can’t handle relative seconds, minutes, hours, etc. and complex cases. For example, take the following code:

let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .long
formatter.doesRelativeDateFormatting = true
print(formatter.string(from: Date().addingTimeInterval(-120)))  //"Today"
print(formatter.string(from: Date()))                           //"Today"
print(formatter.string(from: Date().addingTimeInterval(3601)))  //"Today"

Well, that’s ok, but wouldn’t you rather see “2 minutes ago”, “now”, and “in 1 hour” instead of just “Today”? Enter iOS 13 and the RelativeDateTimeFormatter class!

Using RelativeDateTimeFormatter

Using the new class is no different that using common DateFormatter or any other Formatter-derived class. Simply instantiate the formatter, set the appropriate styles and any other properties, and then get your results.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"2 minutes ago"
print(formatter.string(for: Date())!)                           //"now"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 hour"

In the above example, we just switched formatters to RelativeDateTimeFormatter and got much more interesting and useful results!

Note that for some reason the parameter name on the string method switched from from: to for: with the new class. Also note that forward looking relative time strings seem to currently be off by 1 second (as of iOS 13.3.1); so if you want an accurate forward-looking time string (e.g. ‘in 1 minute’ instead of ‘in 59 seconds’), you’ll need to be aware of that and compensate potentially.

What about if you don’t want the word “now” and always want numeric results? Use the dateTimeStyle of .numeric to make that happen

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"2 minutes ago"
print(formatter.string(for: Date())!)                           //"in 0 seconds"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 hour"

With the unitStyle property you can also control whether the words are full-length or abbreviated as well as whether numbers are numeric or spelled out completely.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
formatter.unitsStyle = .spellOut
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"two minutes ago"
print(formatter.string(for: Date())!)                           //"in zero seconds"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in one hour"

What About Localization?

One of the great things about the RelativeDateTimeFormatter (and why you should almost always use system formatters for user-facing numbers, dates, etc.) is that we can easily get locale-specific versions of these relative dates and times with just one more line of code using the locale property.

formatter.locale = <Locale that you want to use>

That’s it! So if we want the German language version of the above, we can just use

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
formatter.locale = Locale(identifier: "de_DE")
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"vor 2 Minuten"
print(formatter.string(for: Date())!)                           //"in 0 Sekunden"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 Stunde"

Where To Go From Here?

There’s quite a bit to explore within this very cool class if you need relative dates in your iOS app. You can certainly check out the RelativeDateTimeFormatter documentation but at present, like a fair amount of the iOS 13 documentation, there are no overviews, descriptive text, etc. You’ll be better off using the code comments from the ‘Jump to Definition…’ context menu option in Xcode 11.

There are many different formatters out there as well for all kinds of things other than just numbers and dates. Check out the MeasurementFormatter, ListFormatter, and other classes for generating localized, user-friendly strings for all of your data.

Using UITabBarAppearance for Tab Bar changes in iOS 13

With iOS 13, Apple finally made some long overdue changes to support more effective customizations of the tab bars, and tool bars in your apps. The new UITabBarAppearance, UIBarAppearance, and related classes lets developers easily modify not just the basics like background and selected colors but spacing, text attributes, badging, and positioning. Creating subtle effects around tab bar item selection that would have been non-trivial in the past is much simpler now.

The Old Way

In iOS 12, you might have done something like the following to set the background color and selected/unselected tab bar item colors.

    //Set the background color
    UITabBar.appearance().backgroundColor = .red
    tabBar.backgroundImage = UIImage()   //Clear background

    //Set the item tint colors
    tabBar.tintColor = .white
    tabBar.unselectedItemTintColor = .lightGray

Using UIAppearance to set global properties such as background color worked well but no longer works as consistently with iOS 13 (although it still will generally work). I’ve noticed issues in particular with flaky tab bar appearance on iPad within UISplitViewController classes and some other scenarios in iOS 13.

New and Improved!

With the new classes in iOS 13 you now set the tab bar background color with the UITabBarAppearance class and backgroundColor property. Note the availability check on the method below as this is only available in iOS 13+. (if you are not supporting older versions of iOS, this obviously wouldn’t be needed). Here we just set the tab bar background to red and assign the standard appearance.

    //This example assumes a custom UITabBarController overridden class.  You can 
    //also use it anywhere the `tabBar` of the current `tabBarController` is available.
    let appearance = UITabBarAppearance()
    appearance.backgroundColor = .red
    tabBar.standardAppearance = appearance

Setting the color on the selected and unselected tab bar icons and text is now done through the new UITabBarItemAppearance class. You can then just use the default normal, selected, disabled, or focused properties that are available and already instantiated to set iconColor, titleTextAttributes, or other properties. Below is a function to update the colors for normal and selected tab bar items.

    @available(iOS 13.0, *)
    private func setTabBarItemColors(_ itemAppearance: UITabBarItemAppearance) {
        itemAppearance.normal.iconColor = .lightGray
        itemAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.lightGray]
        
        itemAppearance.selected.iconColor = .white
        itemAppearance.selected.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
    }

We can just call the above function for each appropriate appearance – stacked, inline, or compact inline. iOS 13 will generally select the layout most appropriate to the size class that you select (e.g. stacked for portrait iPhone view and inline for landscape iPhone view on an iPhone 11 Pro Max).

    //Set all possible tab bar item styles as necessary (based on rotation and size capabilities).  Here
    //we're setting all three available appearances
    setTabBarItemColors(appearance.stackedLayoutAppearance)
    setTabBarItemColors(appearance.inlineLayoutAppearance)
    setTabBarItemColors(appearance.compactInlineLayoutAppearance)

This will result in a UI similar to the following for landscape or portrait representations:

Landscape view of tab bar
Portrait view of tab bar

Notice the differing stacked and inline appearances based on the iPhone rotation above. If you want to adjust other characteristics such as the title position (say to move it further or closer to the icon), font, etc., it’s simple enough to do with the titleTextAttributes and titlePositionAdjustment properties.

Setting Tab Bar Item Badge Appearance

In the screenshot below we’ve got a blue badge instead of the default red one and it’s been moved so that it overlaps the top of the navigation bar.

Alternative badge style for tab bar item

To do this we just set the badgeBackgroundColor, badgeTextAttributes, and badgePositionAdjustment properties on UITabBarItemAppearance such as below:

    
    @available(iOS 13.0, *)
    private func setTabBarItemBadgeAppearance(_ itemAppearance: UITabBarItemAppearance) {
        //Adjust the badge position as well as set its color
        itemAppearance.normal.badgeBackgroundColor = .blue
        itemAppearance.normal.badgeTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        itemAppearance.normal.badgePositionAdjustment = UIOffset(horizontal: 10, vertical: -10)
    }

Conclusion

I hope you’ve enjoyed the above and learned something about the new tab bar appearance classes. Make sure to check out the documentation at the links above as well as the other derived UIBarAppearance classes such as UINavigationBarAppearance and UIToolBarAppearance. Hopefully some of the ugly subclassing and iterative loops through nav bars, tab bars, and tool bars is a thing of the past!