Adding a new feature to a product is always a tradeoff. Will the added utility of a new feature be enough to offset the added complexity? Shortcuts would seem to side-step this issue—after all, they’re simply a quicker alternative for features already in your app. But that creates another dilemma: what if a new feature is added and no one knows it’s there?
When key commands for external keyboards debuted in iOS 7, there was no intrinsic way to learn of their existence. Unlike in OS X, where a user can gradually discover shortcuts for the menu items they use most often, an iOS app had few ways to communicate what key commands are available. Initial tours flash by and fade from memory; help screens are hidden out of sight. Without a way to make shortcuts visible in a timely and relevant manner, users were sure to miss out on useful features that developers had taken the time to implement.
No longer. As part of the push for greater productivity on the iPad, iOS 9 adds Discoverability, an overlay showing the currently available key commands inside an app. This small change suddenly makes key commands far more viable on the iPad and, with it, makes UIKeyCommand a necessary addition to your app.
UIKeyCommand
The UIKeyCommand class is in fact quite simple, with only four properties to configure:
input: The character of the key you’d like to recognize, or the correct constant for the arrow and escape keys, which do not have characters themselves. The available constants are:
UIKeyInputUpArrow
UIKeyInputDownArrow
UIKeyInputLeftArrow
UIKeyInputRightArrow
UIKeyInputEscape
modifierFlags: One or more UIKeyModifierFlags, describing the modifier keys that should be pressed in combination with input:
.Command, .Alternate, .Shift, .Control: The Command, Option, Shift, and Control keys, respectively.
.NumericPad: Indicates that input should come from the numeric keypad rather than the top row of the standard keyboard.
.AlphaShift: Indicates that the CapsLock key should be pressed as part of the combination, rather than just engaged.
action: The selector to call when the key command is invoked, called with a UIKeyCommand as its only argument. The key event will travel up the responder chain until a matching selector is found.
discoverabilityTitle(iOS 9 only): An optional label to display for the key command in the Discoverability layover. Only key commands with a title set will be listed.
Responding to Key Commands
Enabling key commands is as simple as providing an array of UIKeyCommand instances somewhere in the responder chain. Text inputs are automatically first responders, but perhaps more usefully, a view controller can respond to key commands by implementing canBecomeFirstResponder():
In the Discoverability layover, accessed by holding down the Command key, key commands are listed in the order you specified:
Voila! Secrets, revealed!
Context Sensitivity
The keyCommands property is accessed whenever a key is pressed, making it possible to provide context-sensitive responses depending on the state of your application. While this is similar to the way a menu item and its active/inactive state are configured in OS X, the recommendation for iOS is to omit inactive commands completely—that is, there are no grayed out commands in the Discoverability layover.
Here, a set of commands that are available to logged in users of an app are included only when appropriate:
Although we don’t take shortcuts when creating our apps, that doesn’t mean our users won’t find shortcuts useful. Adding key commands lets control of your app shift from the screen to the keyboard—your users will love the option.
“We should do (as wise programmers aware of our limitations) our utmost best to … make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.”
Recently, Swift 2.0 introduced two new control statements that aim to simplify and streamline the programs we write: guard and defer. While the first by its nature makes our code more linear, the other defers execution of its contents. How should we approach these new control statements? How can guard and defer help us clarify the correspondence between the program and the process?
Let’s defer defer and first take on guard.
guard
If the multiple optional bindings syntax introduced in Swift 1.2 heralded a renovation of the pyramid of doom, guard statements tear it down altogether.
guard is a new conditional statement that requires execution to exit the current block if the condition isn’t met. Any new optional bindings created in a guard statement’s condition are available for the rest of the function or block, and the mandatory else must exit the current scope, by using return to leave a function, continue or break within a loop, or a @noreturn function like fatalError():
forimageNameinimageNamesList{guardletimage=UIImage(named:imageName)else{continue}// do something with image}
Let’s take a before-and-after look at how guard can improve our code and help prevent errors. As an example, we’ll build a new string-to-UInt8 initializer. UInt8 already declares a failable initializer that takes a String, but if the conversion fails we don’t learn the reason—was the format invalid or was the value out of bounds for the numeric type? Our new initializer throws a ConversionError that provides more information.
enumConversionError:ErrorType{caseInvalidFormat,OutOfBounds,Unknown}extensionUInt8{init(fromStringstring:String)throws{// check the string's formatiflet_=string.rangeOfString("^\\d+$",options:[.RegularExpressionSearch]){// make sure the value is in boundsifstring.compare("\(UInt8.max)",options:[.NumericSearch])!=NSComparisonResult.OrderedAscending{throwConversionError.OutOfBounds}// do the built-in conversionifletvalue=UInt8(string){self.init(value)}else{throwConversionError.Unknown}}throwConversionError.InvalidFormat}}
Note how far apart the format check and the invalid format throw are in this example. Not ideal. Moreover, the actual initialization happens two levels deep, inside a nested if statement. And if that isn’t enough, there’s a bug in the logic of this initializer that isn’t immediately apparent. Can you spot the flaw? What’s really going to bake your noodle later on is, would you still have noticed it if I hadn’t said anything?
Next, let’s take a look at how using guard transforms this initializer:
extensionUInt8{init(fromStringstring:String)throws{// check the string's formatguardlet_=string.rangeOfString("^\\d+$",options:[.RegularExpressionSearch])else{throwConversionError.InvalidFormat}// make sure the value is in boundsguardstring.compare("\(UInt8.max)",options:[.NumericSearch])!=NSComparisonResult.OrderedDescendingelse{throwConversionError.OutOfBounds}// do the built-in conversionguardletvalue=UInt(string)else{throwConversionError.Unknown}self.init(value)}}
Much better. Each error case is handled as soon as it has been checked, so we can follow the flow of execution straight down the left-hand side.
Even more importantly, using guard prevents the logic flaw in our first attempt: that final throw is called every time because it isn’t enclosed in an else statement. With guard, the compiler forces us to break scope inside the else-block, guaranteeing the execution of that particular throw only at the right times.
Also note that the middle guard statement isn’t strictly necessary. Since it doesn’t unwrap an optional value, an if statement would work perfectly well. Using guard in this case simply provides an extra layer of safety—the compiler ensures that you leave the initializer if the test fails, leaving no way to accidentally comment out the throw or introduce another error that would lose part of the initializer’s logic.
defer
Between guard and the new throw statement for error handling, Swift 2.0 certainly seems to be encouraging a style of early return (an NSHipster favorite) rather than nested if statements. Returning early poses a distinct challenge, however, when resources that have been initialized (and may still be in use) must be cleaned up before returning.
The new defer keyword provides a safe and easy way to handle this challenge by declaring a block that will be executed only when execution leaves the current scope. Consider this snippet of a function working with vImage from the Accelerate framework, taken from the newly-updated article on image resizing:
funcresizeImage(url:NSURL)->UIImage?{// ...letdataSize:Int=...letdestData=UnsafeMutablePointer<UInt8>.alloc(dataSize)vardestBuffer=vImage_Buffer(data:destData,...)// scale the image from sourceBuffer to destBuffervarerror=vImageScale_ARGB8888(&sourceBuffer,&destBuffer,...)guarderror==kvImageNoErrorelse{destData.dealloc(dataSize)// 1returnnil}// create a CGImage from the destBufferguardletdestCGImage=vImageCreateCGImageFromBuffer(&destBuffer,&format,...)else{destData.dealloc(dataSize)// 2returnnil}destData.dealloc(dataSize)// 3// ...}
Here an UnsafeMutablePointer<UInt8> is allocated for the destination data early on, but we need to remember to deallocate at both failure points and once we no longer need the pointer.
A defer statement removes any chance of forgetting to clean up after ourselves while also simplifying our code. Even though the defer block comes immediately after the call to alloc(), its execution is delayed until the end of the current scope:
funcresizeImage(url:NSURL)->UIImage?{// ...letdataSize:Int=...letdestData=UnsafeMutablePointer<UInt8>.alloc(dataSize)defer{destData.dealloc(dataSize)}vardestBuffer=vImage_Buffer(data:destData,...)// scale the image from sourceBuffer to destBuffervarerror=vImageScale_ARGB8888(&sourceBuffer,&destBuffer,...)guarderror==kvImageNoErrorelse{returnnil}// create a CGImage from the destBufferguardletdestCGImage=vImageCreateCGImageFromBuffer(&destBuffer,&format,...)else{returnnil}// ...}
Thanks to defer, destData will be properly deallocated no matter which exit point is used to return from the function.
Safe and clean. Swift at its best.
defer blocks are executed in the reverse order of their appearance. This reverse order is a vital detail, ensuring everything that was in scope when a deferred block was created will still be in scope when the block is executed.
(Any Other) Defer Considered Harmful
As handy as the defer statement is, be aware of how its capabilities can lead to confusing, untraceable code. It may be tempting to use defer in cases where a function needs to return a value that should also be modified, as in this typical implementation of the postfix ++ operator:
Clever indeed, yet this inversion of the function’s flow harms readability. Using defer to explicitly alter a program’s flow, rather than to clean up allocated resources, will lead to a twisted and tangled execution process.
“As wise programmers aware of our limitations,” we must weigh the benefits of each language feature against its costs. A new statement like guard leads to a more linear, more readable program; apply it as widely as possible. Likewise, defer solves a significant challenge but forces us to keep track of its declaration as it scrolls out of sight; reserve it for its minimum intended purpose to guard against confusion and obscurity.
With 2015 behind us and the new year begun, it’s time again for an NSHipster tradition: reader submissions! As inyear’spast, this installment is chock full of tips and tricks that can help ease your days working with Xcode, Swift, and Objective-C.
With the excellent addition of defer to Swift we Objective-C holdouts can’t help but feel envious of the improvements happening so rapidly to the Swift language. Until Apple officially adds @defer devs can actually implement defer support simply enough with a macro in Objective-C. Below I’ve outlined how one can go about doing a defer today in Objective-C. Personally, having Apple add @defer seem like an easy win for Objective-C, but we’ll see what happens. :)
Nolan’s macro uses the GCC (cleanup()) attribute to execute a block when scope exits:
// some helper declarations#define _nob_macro_concat(a, b) a##b#define nob_macro_concat(a, b) _nob_macro_concat(a, b)typedefvoid(^nob_defer_block_t)();NS_INLINEvoidnob_deferFunc(__strongnob_defer_block_t*blockRef){nob_defer_block_tactualBlock=*blockRef;actualBlock();}// the core macro#define nob_defer(deferBlock) \__strong nob_defer_block_t nob_macro_concat(__nob_stack_defer_block_, __LINE__) __attribute__((cleanup(nob_deferFunc), unused)) = deferBlock
Blocks used with nob_defer are executed in reverse order, just like Swift defer statements:
#include <nob_defer.h>-(void)dealWithFile{FILE*file=fopen(…);nob_defer(^{if(file){fclose(file);}});// continue code where any scope exit will // lead to the defer being executed}-(void)dealWithError{__blockNSError*scopeError=nil;nob_defer(^{if(scopeError){[selfperformCustomErrorHandling:scopeError];}});// assign any errors to "scopeError" to handle the error// on exit, no matter how you exit}#define NOBDeferRelease(cfTypeRef) nob_defer(^{ if (cfTypeRef) { CFRelease(cfTypeRef); } })-(void)cleanUpCFTypeRef{CFStringRefstringRef=...somecodetocreateaCFStringRef...;NOBDeferRelease(stringRef);// continue working without having to worry// about the CFTypeRef needing to be released}
I’ve been using my custom defer macro in production code since June and it is really the Bee’s Knees!
The addition of the where clause has made my code simple and compact while remaining readable. In addition, it has a broad application in Swift, so that it can be applied in nearly any kind of control-flow statement, such as for loop, while loop, if, guard, switch, and even in extension declarations. One simple way I like to use it is in my prepareForSegue method:
The combo of unwrapping and performing a condition check is most commonly where I use the where clause. The where clause is not going to change your life, but it should be an easy and useful addition to your Swift skills.
Using xcpretty because the output of xcodebuild test is unreadable? Unfortunately, the output of the test results becomes buffered when piped. Solution: set the NSUnbufferedIO environment variable for a smooth experience. 😎
With extensions in iOS, it is critical that frameworks that can be linked to both extensions and apps be cognizant of their uses so they don’t call any APIs that might not be available to an extension (like UIApplication). Here’s a function to help determine if you are running in an extension at runtime:
(Per Apple, an extension will have a top level “NSExtension” dictionary in the info.plist.)
This has been my year of truly appreciating Xcode breakpoints, beyond the standard “break at this line” type.
It breaks my heart to see other developers not using them to their potential.
Right click on a breakpoint and choose Edit Breakpoint… for access to advanced features:
Particularly the ones with actions (such as logging to the console) that continue automatically without pausing any threads, because you can add/edit them without recompiling. I’ll never accidentally commit NSLogs again. :)
Using Swift’s _ObjectiveCBridgeable (implicit castability between types) to create a generic protocol for Obj-C compatible objects that wrap pure Swift structs (keeping Swift framework API clean, but Obj-C compatible for as long as desired).
This first part defines an extension with default implementations for the bridging bookkeeping methods:
Josip Ćavar writes in about getting additional type safety with phantom types. In the example below, Kilometer and Meter are used to constrain what kinds of DistanceT instances can be added together:
Make no mistake, a tiny keyboard on a slab of glass doesn’t always lend itself to perfect typing. Whether for accuracy or hilarity, anyone typing on an iOS device notices when autocorrect steps in to help out. You might not know, however, that UIKit includes a class to help you with your user’s typing inside your app.
First introduced in iOS 3.2 (or should we call it iPhone OS 3.2, given the early date?), UITextChecker does exactly what it says: it checks text. Read on to learn how you can use this class for spell checking and text completion.
Spell Checking
What happens if you mistype a word in iOS? Type “hipstar” into a text field and iOS will offer to autocorrect to “hipster” most of the time.
We can find the same suggested substitution using UITextChecker:
importUIKitletstr="hipstar"lettextChecker=UITextChecker()letmisspelledRange=textChecker.rangeOfMisspelledWordInString(str,range:NSRange(0..<str.utf16.count),startingAt:0,wrap:false,language:"en_US")ifmisspelledRange.location!=NSNotFound,letguesses=textChecker.guessesForWordRange(misspelledRange,inString:str,language:"en_US")as?[String]{print("First guess: \(guesses.first)")// First guess: hipster}else{print("Not found")}
NSString*str=@"hipstar";UITextChecker*textChecker=[[UITextCheckeralloc]init];NSRangemisspelledRange=[textCheckerrangeOfMisspelledWordInString:strrange:NSMakeRange(0,[strlength])startingAt:0wrap:NOlanguage:@"en_US"];NSArray*guesses=[NSArrayarray];if(misspelledRange.location!=NSNotFound){guesses=[textCheckerguessesForWordRange:misspelledRangeinString:strlanguage:@"en_US"];NSLog(@"First guess: %@",[guessesfirstObject]);// First guess: hipster}else{NSLog(@"Not found");}
The returned array of strings might look like this one:
Or it might not—UITextChecker produces context- and device-specific guesses. According to the documentation, guessesForWordRange(_:inString:language:)“returns an array of strings, in the order in which they should be presented, representing guesses for words that might have been intended in place of the misspelled word at the given range in the given string.”
So no guarantee of idempotence or correctness, which makes sense for a method with guesses... in the name. How can NSHipsters trust a method that changes its return value? We’ll find the answer if we dig further.
Learning New Words
Let’s assume that you want your users to be able to type "hipstar" exactly. Let your app know that by telling it to learn the word, using the UITextChecker.learnWord(_:) class method:
UITextChecker.learnWord(str)
[UITextCheckerlearnWord:str];
"hipstar" is now a recognized word for the whole device and won’t show up as misspelled in further checks.
As expected, the search above returns NSNotFound, for UITextChecker has learned the word we created. UITextChecker also provides class methods for checking and unlearning words: UITextChecker.hasLearnedWord(_:) and UITextChecker.unlearnWord(_:).
Suggesting Completions
There’s one more UITextChecker API, this time for finding possible completions for a partial word:
completionsForPartialWordRange gives you an array of possible words from a group of initial characters. Although the documentation states that the returned array of strings will be sorted by probability, UITextChecker only sorts the completions alphabetically. UITextChecker’s OS X-based sibling, NSSpellChecker, does behave as it describes.
You won’t see any of the custom words you’ve taught UITextChecker show up as possible completions. Why not? Since vocabulary added via UITextChecker.learnWord(_:) is global to the device, this prevents your app’s words from showing up in another app’s autocorrections.
Building an app that leans heavily on a textual interface? Use UITextChecker to make sure the system isn’t flagging your own vocabulary. Writing a keyboard extension? With UITextChecker and UILexicon, which provides common and user-defined words from the system-wide dictionary and first and last names from the user’s address book, you can support nearly any language without creating your own dictionaries!
This year’s WWDC edition of the NSHipster Pub Quiz was held on June 14th, once again testing the assembled developers with questions both random and obscure. We’re enormously grateful to Realm, who hosted the quiz for the second year in a row, with delicious food and drink and enough tables to seat nearly two hundred contestants.
Now it’s time for you to play along with the home edition—sharpen your pencil and give it your best!
Four rounds of ten questions
Record your answers on a separate sheet of paper
Each correct answer earns 1 point (unless otherwise specified)
Play with friends for maximum enjoyment
Don’t be lame and look things up on the internet or in Xcode
Round 1: General Knowledge
In the WWDC keynote, Apple introduced the new OS X, er… macOS Sierra. The actual Sierra mountain range is home to the highest peak in the contiguous US. What is the name of that mountain?
The Sierra were one focal point of a mass migration to California. What San Francisco sports team has ties to the Sierra during that period in history?
Another highlight of the keynote was when Bozoma Saint John introduced the new Apple Music and got the crowd singing along to “Rapper’s Delight"—who recorded the song, and in what year? (2 points)
Which version of iPhoto first introduced “Faces and Places?”
As part of Foundation’s Swiftification, many classes have lost their NS prefixes. Which of these classes remains unchanged so far: NSBundle, NSCalendar, NSExpression, or NSOperation?
More than just class names have changed—write the new Swift signature for this NSString method:
Write the Swift 3 code to execute an asynchronous “Hello, world!” using GCD.
Swift went open source in November and the pace of community contributions has been amazing to see. Within 100, how many pull requests (open, closed, or merged) has the Swift project received on GitHub?
Swift was released to the public just over two years ago, but was clearly under long before that at Apple. What were the month and year of the first commit to the Swift repository?
Who was the second contributor to Swift? When did they begin?
Round 2: Name That Framework
Foundation classes are losing their NS prefixes left and right. What would it look like if we got rid of prefixes in every framework? For each question in this round, you’ll be given three classes with their identifying prefix removed. Name the framework that contains all three.
Many Apple advertisements over the years have featured celebrity voiceovers intoning words of wisdom, inspiration, or at times something else entirely. So pop in your earbuds and for each of the ads below, name the person(s) providing their voice talents.
Round 4: Easy as 1, 2, 3
Swift is an easy language to learn and use, but its breakneck speed of development has meant breaking changes with each release. For the following snippets of code, answer with the version of Swift that will compile and give the desired result. Because some snippets can run in more than one version, some questions may be worth up to 2 points. Only the major versions are required—for example, if a snippet will run in Swift 2.2, "Swift 2” is a scoring answer.
1
leta=["1","2","3","four","5"]letnumbers=map(a){$0.toInt()}letonlyNumbers=filter(numbers){$0!=nil}letsum=reduce(onlyNumbers,0){$0+$1!}// sum == 11
2
leta=["1","2","3","four","5"]letsum=a.flatMap{Int($0)}.reduce(0,combine:+)// sum == 11
enumMyError:ErrorProtocol{caseOverflowcaseNegativeInput}funcsquare(_value:inoutInt)throws{guardvalue>=0else{throwMyError.NegativeInput}let(result,overflow)=Int.multiplyWithOverflow(value,value)guard!overflowelse{throwMyError.Overflow}value=result}varnumber=11try!square(&number)// number == 121
9
enumMyError:ErrorType{caseOverflowcaseNegativeInput}funcsquareInPlace(inoutvalue:Int)throws{guardvalue>=0else{throwMyError.NegativeInput}let(result,overflow)=Int.multiplyWithOverflow(value,value)guard!overflowelse{throwMyError.Overflow}value=result}varnumber=11try!squareInPlace(&number)// number == 121
10
vara:Int[]=[1,2,3,4,5]letb=aa[0]=100// b == [100, 2, 3, 4, 5]
That’s all! When you’re finished, scroll down a bit for the answers.
.
.
.
Answers
Round 1: General Knowledge
Mount Whitney
San Francisco 49ers
The Sugarhill Gang, 1979 (2 points for both)
iPhoto ’09
NSExpression
One of: “`swift
// 1
replacingCharacters(in: NSRange, with: String)
// 2
func replacingCharacters(
in range: NSRange,
with replacement: String) -> String”`
One of:“`swift
// 1
let queue = DispatchQueue(label: "quiz”)
queue.async {
print(“Hello, world!”)
}
“Some people, when confronted with a problem, think ‘I know, I’ll use NSRegularExpression.’ Now they have three problems.”
Regular expressions fill a controversial role in the programming world. Some find them impenetrably incomprehensible, thick with symbols and adornments, more akin to a practical joke than part of a reasonable code base. Others rely on their brevity and their power, wondering how anyone could possibly get along without such a versatile tool in their arsenal.
Happily, on one thing we can all agree. In NSRegularExpression, Cocoa has the most long-winded and byzantine regular expression interface you’re ever likely to come across. Don’t believe me? Let’s try extracting the links from this snippet of HTML, first using Ruby:
htmlSource="Questions? Corrections? <a href=\"https://twitter.com/NSHipster\">@NSHipster</a> or <a href=\"https://github.com/NSHipster/articles\">on GitHub</a>."linkRegex=/<a\s+[^>]*href="([^"]*)"[^>]*>/ilinks=htmlSource.scan(linkRegex)puts(links)# https://twitter.com/NSHipster# https://github.com/NSHipster/articles
Two or three lines, depending on how you count—not bad. Now we’ll try the same thing in Swift using NSRegularExpression:
lethtmlSource="Questions? Corrections? <a href=\"https://twitter.com/NSHipster\">@NSHipster</a> or <a href=\"https://github.com/NSHipster/articles\">on GitHub</a>."letlinkRegexPattern="<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>"letlinkRegex=try!NSRegularExpression(pattern:linkRegexPattern,options:.caseInsensitive)letmatches=linkRegex.matches(in:htmlSource,range:NSMakeRange(0,htmlSource.utf16.count))letlinks=matches.map{result->StringinlethrefRange=result.rangeAt(1)letstart=String.UTF16Index(hrefRange.location)letend=String.UTF16Index(hrefRange.location+hrefRange.length)returnString(htmlSource.utf16[start..<end])!}print(links)// ["https://twitter.com/NSHipster", "https://github.com/NSHipster/articles"]
The prosecution rests.
This article won’t get into the ins and outs of regular expressions themselves (you may need to learn about wildcards, backreferences, lookaheads and the rest elsewhere), but read on to learn about NSRegularExpression, NSTextCheckingResult, and a particularly sticky point when bringing it all together in Swift.
NSString Methods
The simplest way to use regular expressions in Cocoa is to skip NSRegularExpression altogether. The range(of:...) method on NSString (which is bridged to Swift’s native String type) switches into regular expression mode when given the .regularExpression option, so lightweight searches can be written easily:
letsource="For NSSet and NSDictionary, the breaking..."// Matches anything that looks like a Cocoa type: // UIButton, NSCharacterSet, NSURLSession, etc.lettypePattern="[A-Z]{3,}[A-Za-z0-9]+"iflettypeRange=source.range(of:typePattern,options:.regularExpression){print("First type: \(source[typeRange])")// First type: NSSet}
NSString*source=@"For NSSet and NSDictionary, the breaking...";// Matches anything that looks like a Cocoa type: // UIButton, NSCharacterSet, NSURLSession, etc.NSString*typePattern=@"[A-Z]{3,}[A-Za-z0-9]+";NSRangetypeRange=[sourcerangeOfString:typePatternoptions:NSRegularExpressionSearch];if(typeRange.location!=NSNotFound){NSLog(@"First type: %@",[sourcesubstringWithRange:typeRange]);// First type: NSSet}
Replacement is also a snap using replacingOccurrences(of:with:...) with the same option. Watch how we surround each type name in our text with Markdown-style backticks using this one weird trick:
letmarkedUpSource=source.replacingOccurrences(of:typePattern,with:"`$0`",options:.regularExpression)print(markedUpSource)// "For `NSSet` and `NSDictionary`, the breaking...""
NSString*markedUpSource=[sourcestringByReplacingOccurrencesOfString:typePatternwithString:@"`$0`"options:NSRegularExpressionSearchrange:NSMakeRange(0,source.length)];NSLog(@"%@",markedUpSource);// "For `NSSet` and `NSDictionary`, the breaking...""
This approach to regular expressions can even handle subgroup references in the replacement template. Lo, a quick and dirty Pig Latin transformation:
These two methods will suffice for many places you might want to use regular expressions, but for heavier lifting, we’ll need to work with NSRegularExpression itself. First, though, let’s sort out a minor complication when using this class from Swift.
NSRange and Swift
Swift provides a more comprehensive, more complex interface to a string’s characters and substrings than does Foundation’s NSString. The Swift standard library provides four different views into a string’s data, giving you quick access to the elements of a string as characters, Unicode scalar values, or UTF-8 or UTF-16 code units.
How does this relate to NSRegularExpression? Well, many NSRegularExpression methods use NSRanges, as do the NSTextCheckingResult instances that store a match’s data. NSRange, in turn, uses integers for its location and length, while none of String’s views use integers as an index:
letrange=NSRange(location:4,length:5)// Not one of these will compile:source[range]source.characters[range]source.substring(with:range)source.substring(with:range.toRange()!)
Confusion. Despair.
But don’t give up! Everything isn’t as disconnected as it seems—the utf16 view on a Swift String is meant specifically for interoperability with Foundation’s NSString APIs. As long as Foundation has been imported, you can create new indices for a utf16 view directly from integers:
letstart=String.UTF16Index(range.location)letend=String.UTF16Index(range.location+range.length)letsubstring=String(source.utf16[start..<end])!// substring is now "NSSet"
With that in mind, here are a few additions to String that will make straddling the Swift/Objective-C divide a bit easier:
extensionString{/// An `NSRange` that represents the full range of the string.varnsrange:NSRange{returnNSRange(location:0,length:utf16.count)}/// Returns a substring with the given `NSRange`, /// or `nil` if the range can't be converted.funcsubstring(withnsrange:NSRange)->String?{guardletrange=nsrange.toRange()else{returnnil}letstart=UTF16Index(range.lowerBound)letend=UTF16Index(range.upperBound)returnString(utf16[start..<end])}/// Returns a range equivalent to the given `NSRange`,/// or `nil` if the range can't be converted.funcrange(fromnsrange:NSRange)->Range<Index>?{guardletrange=nsrange.toRange()else{returnnil}letutf16Start=UTF16Index(range.lowerBound)letutf16End=UTF16Index(range.upperBound)guardletstart=Index(utf16Start,within:self),letend=Index(utf16End,within:self)else{returnnil}returnstart..<end}}
We’ll put these to use in the next section, where we’ll finally see NSRegularExpression in action.
NSRegularExpression& NSTextCheckingResult
If you’re doing more than just searching for the first match or replacing all the matches in your string, you’ll need to build an NSRegularExpression to do your work. Let’s build a miniature text formatter that can handle *bold* and _italic_ text.
Pass a pattern and, optionally, some options to create a new instance. miniPattern looks for an asterisk or an underscore to start a formatted sequence, one or more characters to format, and finally a matching character to end the formatted sequence. The initial character and the string to format are both captured:
letminiPattern="([*_])(.+?)\\1"letminiFormatter=try!NSRegularExpression(pattern:miniPattern,options:.dotMatchesLineSeparators)// the initializer throws an error if the pattern is invalid
The initializer throws an error if the pattern is invalid. Once constructed, you can use an NSRegularExpression as often as you need with different strings.
lettext="MiniFormatter handles *bold* and _italic_ text."letmatches=miniFormatter.matches(in:text,options:[],range:text.nsrange)// matches.count == 2
NSString*text=@"MiniFormatter handles *bold* and _italic_ text.";NSArray<NSTextCheckingResult*>*matches=[miniFormattermatchesInString:textoptions:kNilOptionsrange:NSMakeRange(0,text.length)];// matches.count == 2
Calling matches(in:options:range:) fetches an array of NSTextCheckingResult, the type used as the result for a variety of text handling classes, such as NSDataDetector and NSSpellChecker. The resulting array has one NSTextCheckingResult for each match.
The information we’re most interested are the range of the match, stored as range in each result, and the ranges of any capture groups in the regular expression. You can use the numberOfRanges property and the rangeAt(_:)method to find the captured ranges—range 0 is always the full match, with the ranges at indexes 1 up to, but not including, numberOfRanges covering each capture group.
Using the NSRange-based substring method we declared above, we can use these ranges to extract the capture groups:
formatchinmatches{letstringToFormat=text.substring(with:match.rangeAt(2))!switchtext.substring(with:match.rangeAt(1))!{case"*":print("Make bold: '\(stringToFormat)'")case"_":print("Make italic: '\(stringToFormat)'")default:break}}// Make bold: 'bold'// Make italic: 'italic'
for(NSTextCheckingResult*matchinmatches){NSString*delimiter=[textsubstringWithRange:[matchrangeAtIndex:1]];NSString*stringToFormat=[textsubstringWithRange:[matchrangeAtIndex:2]];if([delimiterisEqualToString:@"*"]){NSLog(@"Make bold: '%@'",stringToFormat);}elseif([delimiterisEqualToString:@"_"]){NSLog(@"Make italic: '%@'",stringToFormat);}}// Make bold: 'bold'// Make italic: 'italic'
For basic replacement, head straight to stringByReplacingMatches(in:options:range:with:), the long-winded version of String.replacingOccurences(of:with:options:). In this case, we need to use different replacement templates for different matches (bold vs. italic), so we’ll loop through the matches ourselves (moving in reverse order, so we don’t mess up the ranges of later matches):
varformattedText=textFormat:formatchinmatches.reversed(){lettemplate:Stringswitchtext.substring(with:match.rangeAt(1))??""{case"*":template="<strong>$2</strong>"case"_":template="<em>$2</em>"default:breakFormat}letmatchRange=formattedText.range(from:match.range)!// see aboveletreplacement=miniFormatter.replacementString(for:match,in:formattedText,offset:0,template:template)formattedText.replaceSubrange(matchRange,with:replacement)}// 'formattedText' is now:// "MiniFormatter handles <strong>bold</strong> and <em>italic</em> text."
NSMutableString*formattedText=[NSMutableStringstringWithString:text];for(NSTextCheckingResult*matchin[matchesreverseObjectEnumerator]){NSString*delimiter=[textsubstringWithRange:[matchrangeAtIndex:1]];NSString*template=[delimiterisEqualToString:@"*"]?@"<strong>$2</strong>":@"<em>$2</em>";NSString*replacement=[miniFormatterreplacementStringForResult:matchinString:formattedTextoffset:0template:template];[formattedTextreplaceCharactersInRange:[matchrange]withString:replacement];}// 'formattedText' is now:// @"MiniFormatter handles <strong>bold</strong> and <em>italic</em> text."
Calling miniFormatter.replacementString(for:in:...) generates a replacement string specific to each NSTextCheckingResult instance with our customized template.
Expression and Matching Options
NSRegularExpression is highly configurable—you can pass different sets of options when creating an instance or when calling any method that performs matching.
NSRegularExpression.Options
Pass one or more of these as options when creating a regular expression.
.caseInsensitive: Turns on case insensitive matching. Equivalent to the i flag.
.allowCommentsAndWhitespace: Ignores any whitespace and comments between a # and the end of a line, so you can format and document your pattern in a vain attempt at making it readable. Equivalent to the x flag.
.ignoreMetacharacters: The opposite of the .regularExpression option in String.range(of:options:)—this essentially turns the regular expression into a plain text search, ignoring any regular expression metacharacters and operators.
.dotMatchesLineSeparators: Allows the . metacharacter to match line breaks as well as other characters. Equivalent to the s flag.
.anchorsMatchLines: Allows the ^ and $ metacharacters (beginning and end) to match the beginnings and ends of lines instead of just the beginning and end of the entire input string. Equivalent to the m flag.
.useUnixLineSeparators, .useUnicodeWordBoundaries: These last two opt into more specific line and word boundary handling: UNIX line separators
NSRegularExpression.MatchingOptions
Pass one or more of these as options to any matching method on an NSRegularExpression instance.
.anchored: Only match at the start of the search range.
.withTransparentBounds: Allows the regex to look past the search range for lookahead, lookbehind, and word boundaries (though not for actual matching characters).
.withoutAnchoringBounds: Makes the ^ and $ metacharacters match only the beginning and end of the string, not the beginning and end of the search range.
.reportCompletion, .reportProgress: These only have an effect when passed to the method detailed in the next section. Each option tells NSRegularExpression to call the enumeration block additional times, when searching is complete or as progress is being made on long-running matches, respectively.
Partial Matching
Finally, one of the most powerful features of NSRegularExpression is the ability to scan only as far into a string as you need. This is especially valuable on a large string, or when using an pattern that is expensive to run.
Instead of using the firstMatch(in:...) or matches(in:...) methods, call enumerateMatches(in:options:range:using:) with a closure to handle each match. The closure receives three parameters: the match, a set of flags, and a pointer to a Boolean that acts as an out parameter, so you can stop enumerating at any time.
We can use this method to find the first several names in Dostoevsky’s Brothers Karamazov, where names follow a first and patronymic middle name style (e.g., “Ivan Fyodorovitch”):
letnameRegex=try!NSRegularExpression(pattern:"([A-Z]\\S+)\\s+([A-Z]\\S+(vitch|vna))")letbookString=...varnames:Set<String>=[]nameRegex.enumerateMatches(in:bookString,range:bookString.nsrange){(result,_,stopPointer)inguardletresult=resultelse{return}letname=nameRegex.replacementString(for:result,in:bookString,offset:0,template:"$1 $2")names.insert(name)// stop once we've found six unique namesstopPointer.pointee=ObjCBool(names.count==6)}// names.sorted(): // ["Adelaïda Ivanovna", "Alexey Fyodorovitch", "Dmitri Fyodorovitch", // "Fyodor Pavlovitch", "Pyotr Alexandrovitch", "Sofya Ivanovna"]
NSString*namePattern=@"([A-Z]\\S+)\\s+([A-Z]\\S+(vitch|vna))";NSRegularExpression*nameRegex=[NSRegularExpressionregularExpressionWithPattern:namePatternoptions:kNilOptionserror:&error];NSString*bookString=...NSMutableSet*names=[NSMutableSetset];[nameRegexenumerateMatchesInString:bookStringoptions:kNilOptionsrange:NSMakeRange(0,[bookStringlength])usingBlock:^(NSTextCheckingResult*result,NSMatchingFlagsflags,BOOL*stop){if(result==nil)return;NSString*name=[nameRegexreplacementStringForResult:resultinString:bookStringoffset:0template:@"$1 $2"];[namesaddObject:name];// stop once we've found six unique names*stop=(names.count==6);}];
With this approach we only need to look at the first 45 matches, instead of nearly 1300 in the entirety of the book. Not bad!
Once you get to know it, NSRegularExpression can be a truly useful tool. In fact, you may have used it already to find dates, addresses, or phone numbers in user-entered text—NSDataDetector is an NSRegularExpression subclass with patterns baked in to identify useful info. Indeed, as we’ve come to expect of text handling throughout Foundation, NSRegularExpression is thorough, robust, and has surprising depth beneath its tricky interface.
Years ago,
we remarked that the “at sign” (@) —
along with square brackets and ridiculously-long method names —
was as characteristic of Objective-C
as parentheses are to Lisp
or punctuation is to Perl.
Then came Swift,
and with it an end to these curious little 🥨-shaped glyphs.
Or so we thought.
At first,
the function of @ was limited to Objective-C interoperability:@IBAction, @NSCopying, @UIApplicationMain, and so on.
But in time,
Swift has continued to incorporate an ever-increasing number of @-prefixedattributes.
We got our first glimpse of Swift 5.1 at WWDC 2019
by way of the SwiftUI announcement.
And with each “mind-blowing” slide came a hitherto unknown attribute:
@State, @Binding, @EnvironmentObject…
We saw the future of Swift,
and it was full of @s.
We’ll dive into SwiftUI once it’s had a bit longer to bake.
But this week,
we wanted to take a closer look at a key language feature for SwiftUI —
something that will have arguably the biggest impact on the
«je ne sais quoi» of Swift in version 5.1 and beyond:property wrappers
About Property DelegatesWrappers
Property wrappers were firstpitched to the Swift forums
back in March of 2019 —
months before the public announcement of SwiftUI.
In his original pitch,
Swift Core Team member Douglas Gregor
described the feature (then called “property delegates”)
as a user-accessible generalization of functionality
currently provided by language features like the lazy keyword.
Laziness is a virtue in programming,
and this kind of broadly useful functionality
is characteristic of the thoughtful design decisions
that make Swift such a nice language to work with.
When a property is declared as lazy,
it defers initialization of its default value until first access.
For example,
you could implement equivalent functionality yourself
using a private property whose access is wrapped by a computed property,
but a single lazy keyword makes all of that unnecessary.
Expand to lazily evaluate this code expression.
structStructure{// Deferred property initialization with lazy keywordlazyvardeferred=...// Equivalent behavior without lazy keywordprivatevar_deferred:Type?vardeferred:Type{get{ifletvalue=_deferred{returnvalue}letinitialValue=..._deferred=initialValuereturninitialValue}set{_deferred=newValue}}}
SE-0258: Property Wrappers
is currently in its third review
(scheduled to end yesterday, at the time of publication),
and it promises to open up functionality like lazy
so that library authors can implement similar functionality themselves.
The proposal does an excellent job outlining its design and implementation.
So rather than attempt to improve on this explanation,
we thought it’d be interesting to look at
some new patterns that property wrappers make possible —
and, in the process,
get a better handle on how we might use this feature in our projects.
So, for your consideration,
here are four potential use cases for the new @propertyWrapper attribute:
SE-0258 offers plenty of practical examples, including@Lazy, @Atomic, @ThreadSpecific, and @Box.
But the one we were most excited about
was that of the @Constrained property wrapper.
Swift’s standard library offer correct,
performant, floating-point number types,
and you can have it in any size you want —
so long as it’s
32 or64(or 80) bits long(to paraphrase Henry Ford).
If you wanted to implement a custom floating-point number type
that enforced a valid range of values,
this has been possible sinceSwift 3.
However, doing so would require conformance to a
labyrinth of protocol requirements:
Pulling this off is no small feat,
and often far too much work to justify
for most use cases.
Fortunately,
property wrappers offer a way to parameterize
standard number types with significantly less effort.
Implementing a value clamping property wrapper
Consider the following Clamping structure.
As a property wrapper (denoted by the @propertyWrapper attribute),
it automatically “clamps” out-of-bound values
within the prescribed range.
A @Positive / @NonNegative property wrapper
that provides the unsigned guarantees to signed integer types.
A @NonZero property wrapper
that ensures that a number value is either greater than or less than 0.
@Validated or @Whitelisted / @Blacklisted property wrappers
that restrict which values can be assigned.
Transforming Values on Property Assignment
Accepting text input from users
is a perennial headache among app developers.
There are just so many things to keep track of,
from the innocent banalities of string encoding
to malicious attempts to inject code through a text field.
But among the most subtle and frustrating problems
that developers face when accepting user-generated content
is dealing with leading and trailing whitespace.
A single leading space can
invalidate URLs,
confound date parsers,
and sow chaos by way of off-by-one errors:
When it comes to user input,
clients most often plead ignorance
and just send everything as-is to the server.¯\_(ツ)_/¯.
While I’m not advocating for client apps to take on more of this responsibility,
the situation presents another compelling use case for Swift property wrappers.
Foundation bridges the trimmingCharacters(in:) method to Swift strings,
which provides, among other things,
a convenient way to lop off whitespace
from both the front or back of a String value.
Calling this method each time you want to ensure data sanity is, however,
less convenient.
And if you’ve ever had to do this yourself to any appreciable extent,
you’ve certainly wondered if there might be a better approach.
In your search for a less ad-hoc approach,
you may have sought redemption through the willSet property callback…
only to be disappointed that you can’t use this
to change events already in motion.
structPost{vartitle:String{willSet{title=newValue.trimmingCharacters(in:.whitespacesAndNewlines)/* ⚠️ Attempting to store to property 'title' within its own willSet,
which is about to be overwritten by the new value */}}}
From there,
you may have realized the potential of didSet
as an avenue for greatness…
only to realize later that didSet isn’t called
during initial property assignment.
structPost{vartitle:String{// 😓 Not called during initializationdidSet{self.title=title.trimmingCharacters(in:.whitespacesAndNewlines)}}}
Undeterred,
you may have tried any number of other approaches…
ultimately finding none to yield an acceptable combination of
ergonomics and performance characteristics.
If any of this rings true to your personal experience,
you can rejoice in the knowledge that your search is over:
property wrappers are the solution you’ve long been waiting for.
Implementing a Property Wrapper that Trims Whitespace from String Values
Consider the following Trimmed struct
that trims whitespaces and newlines from incoming string values.
By marking each String property in the Post structure below
with the @Trimmed annotation,
any string value assigned to title or body—
whether during initialization or via property access afterward —
automatically has its leading or trailing whitespace removed.
structPost{@Trimmedvartitle:String@Trimmedvarbody:String}letquine=Post(title:" Swift Property Wrappers ",body:"...")quine.title// "Swift Property Wrappers" (no leading or trailing spaces!)quine.title=" @propertyWrapper "quine.title// "@propertyWrapper" (still no leading or trailing spaces!)
Related Ideas
A @Transformed property wrapper that appliesICU transforms
to incoming string values.
A @Normalized property wrapper that allows a String property
to customize its normalization form.
A @Quantized / @Rounded / @Truncated property
that quantizes values to a particular degree (e.g. “round up to nearest ½”),
but internally tracks precise intermediate values
to prevent cascading rounding errors.
Changing Synthesized Equality and Comparison Semantics
In Swift,
two String values are considered equal
if they are canonically equivalent.
By adopting these equality semantics,
Swift strings behave more or less as you’d expect in most circumstances:
if two strings comprise the same characters,
it doesn’t matter whether any individual character is composed or precomposed
— that is,
“é” (U+00E9 LATIN SMALL LETTER E WITH ACUTE)
is equal to“e” (U+0065 LATIN SMALL LETTER E) +“◌́” (U+0301 COMBINING ACUTE ACCENT).
But what if your particular use case calls for different equality semantics?
Say you wanted a case insensitive notion of string equality?
There are plenty of ways you might go about implementing this today
using existing language features:
You could take the lowercased() result anytime you do == comparison,
but as with any manual process, this approach is error-prone.
You could create a custom CaseInsensitive type that wraps a String value,
but you’d have to do a lot of additional work to make it
as ergonomic and functional as the standard String type.
You could define a custom comparator function to wrap that comparison —
heck, you could even define your own
custom operator for it —
but nothing comes close to an unqualified == between two operands.
None of these options are especially compelling,
but thanks to property wrappers in Swift 5.1,
we’ll finally have a solution that gives us what we’re looking for.
Implementing a case-insensitive property wrapper
The CaseInsensitive type below
implements a property wrapper around a String / SubString value.
The type conforms to Comparable (and by extension, Equatable)
by way of the bridged NSString APIcaseInsensitiveCompare(_:):
Construct two string values that differ only by case,
and they’ll return false for a standard equality check,
but true when wrapped in a CaseInsensitive object.
So far, this approach is indistinguishable from
the custom “wrapper type” approach described above.
And this is normally where we’d start the long slog of
implementing conformance to ExpressibleByStringLiteral
and all of the other protocols
to make CaseInsensitive start to feel enough like String
to feel good about our approach.
Property wrappers allow us to forego all of this busywork entirely:
Here, Account objects are checked for equality
by a case-insensitive comparison on their name property value.
However, when we go to get or set the name property,
it’s a bona fideString value.
That’s neat, but what’s actually going on here?
Since Swift 4,
the compiler automatically synthesizes Equatable conformance
to types that adopt it in their declaration
and whose stored properties are all themselves Equatable.
Because of how compiler synthesis is implemented (at least currently),
wrapped properties are evaluated through their wrapper
rather than their underlying value:
// Synthesized by Swift CompilerextensionAccount:Equatable{staticfunc==(lhs:Account,rhs:Account)->Bool{lhs.$name==rhs.$name}}
Related Ideas
Defining @CompatibilityEquivalence
such that wrapped String properties with the values "①" and "1"
are considered equal.
A @Approximate property wrapper to refine
equality semantics for floating-point types
(See also SE-0259)
A @Ranked property wrapper that takes a function
that defines strict ordering for, say, enumerated values;
this could allow, for example,
the playing card rank .ace to be treated either low or high
in different contexts.
Auditing Property Access
Business requirements may stipulate certain controls
for who can access which records when
or prescribe some form of accounting for changes over time.
Once again,
this isn’t a task typically performed by, say, an iOS app;
most business logic is defined on the server,
and most client developers would like to keep it that way.
But this is yet another use case too compelling to ignore
as we start to look at the world through property-wrapped glasses.
Implementing a Property Value Versioning
The following Versioned structure functions as a property wrapper
that intercepts incoming values and creates a timestamped record
when each value is set.
A hypothetical ExpenseReport class could
wrap its state property with the @Versioned annotation
to keep a paper trail for each action during processing.
An @Audited property wrapper
that logs each time a property is read or written to.
A @Decaying property wrapper
that divides a set number value each time
the value is read.
However,
this particular example highlights a major limitation in
the current implementation of property wrappers
that stems from a longstanding deficiency of Swift generally:Properties can’t be marked as throws.
Without the ability to participate in error handling,
property wrappers don’t provide a reasonable way to
enforce and communicate policies.
For example,
if we wanted to extend the @Versioned property wrapper from before
to prevent state from being set to .approved after previously being .denied,
our best option is fatalError(),
which isn’t really suitable for real applications:
This is just one of several limitations
that we’ve encountered so far with property wrappers.
In the interest of creating a balanced perspective on this new feature,
we’ll use the remainder of this article to enumerate them.
Limitations
Properties Can’t Participate in Error Handling
Properties, unlike functions,
can’t be marked as throws.
As it were,
this is one of the few remaining distinctions between
these two varieties of type members.
Because properties have both a getter and a setter,
it’s not entirely clear what the right design would be
if we were to add error handling —
especially when you consider how to play nice with syntax for other concerns
like access control, custom getters / setters, and callbacks.
As described in the previous section,
property wrappers have but two methods of recourse
to deal with invalid values:
Ignoring them (silently)
Crashing with fatalError()
Neither of these options is particularly great,
so we’d be very interested by any proposal that addresses this issue.
Wrapped Properties Can’t Be Aliased
Another limitation of the current proposal is that
you can’t use instances of property wrappers as property wrappers.
Our UnitInterval example from before,
which constrains wrapped values between 0 and 1 (inclusive),
could be succinctly expressed as:
typealiasUnitInterval=Clamping(0...1)// ❌
However, this isn’t possible.
Nor can you use instances of property wrappers to wrap properties.
All this actually means in practice is more code replication than would be ideal.
But given that this problem arises out of a fundamental distinction
between types and values in the language,
we can forgive a little duplication if it means avoiding the wrong abstraction.
Property Wrappers Are Difficult To Compose
Composition of property wrappers is not a commutative operation;
the order in which you declare them
affects how they’ll behave.
Consider the interplay between an attribute that
performs string inflection
and other string transforms.
For example,
a composition of property wrappers
to automatically normalize the URL “slug” in a blog post
will yield different results if spaces are replaced with dashes
before or after whitespace is trimmed.
structPost{...@Dasherized@Trimmedvarslug:String}
But getting that to work in the first place is easier said than done!
Attempting to compose two property wrappers that act on String values fails,
because the outermost wrapper is acting on a value of the innermost wrapper type.
@propertyWrapperstructDasherized{private(set)varvalue:String=""varwrappedValue:String{get{value}set{value=newValue.replacingOccurrences(of:" ",with:"-")}}init(initialValue:String){self.wrappedValue=initialValue}}structPost{...@Dasherized@Trimmedvarslug:String// ⚠️ An internal error occurred.}
There’s a way to get this to work,
but it’s not entirely obvious or pleasant.
Whether this is something that can be fixed in the implementation
or merely redressed by documentation remains to be seen.
A dependent type is a type defined by its value.
For instance,“a pair of integers in which the latter is greater than the former” and
“an array with a prime number of elements”
are both dependent types
because their type definition is contingent on its value.
Swift’s lack of support for dependent types in its type system
means that any such guarantees must be enforced at run time.
The good news is that property wrappers
get closer than any other language feature proposed thus far
in filling this gap.
However,
they still aren’t a complete replacement for true value-dependent types.
You can’t use property wrappers to,
for example,
define a new type with a constraint on which values are possible.
These shortcomings are by no means deal-breakers;
property wrappers are extremely useful
and fill an important gap in the language.
It’ll be interesting to see whether the addition of property wrappers
will create a renewed interest in bringing dependent types to Swift,
or if they’ll be seen as “good enough”,
obviating the need to formalize the concept further.
Property Wrappers Are Difficult to Document
Pop Quiz:
Which property wrappers are made available by the SwiftUI framework?
In fairness, this failure isn’t unique to property wrappers.
If you were tasked with determining
which protocol was responsible for a particular API in the standard library
or which operators were supported for a pair of types
based only on what was documented on developer.apple.com,
you’re likely to start considering a mid-career pivot away from computers.
This lack of comprehensibility is made all the more dire
by Swift’s increasing complexity.
Property Wrappers Further Complicate Swift
Swift is a much, much more complex language than Objective-C.
That’s been true since Swift 1.0 and has only become more so over time.
The profusion of @-prefixed features in Swift —
whether it’s @dynamicMemberLookup
and@dynamicCallable
from Swift 4,
or@differentiable and @memberwise
from Swift for Tensorflow—
makes it increasingly difficult
to come away with a reasonable understanding of Swift APIs
based on documentation alone.
In this respect,
the introduction of @propertyWrapper will be a force multiplier.
How will we make sense of it all?
(That’s a genuine question, not a rhetorical one.)
Alright, let’s try to wrap this thing up —
Swift property wrappers allow library authors access to the kind of
higher-level behavior previously reserved for language features.
Their potential for improving safety and reducing complexity of code is immense,
and we’ve only begun to scratch the surface of what’s possible.
Yet, for all of their promise,
property wrappers and its cohort of language features debuted alongside SwiftUI
introduce tremendous upheaval to Swift.
When I was a student in Japan,
I worked part-time at a restaurant —
アルバイト as the locals call it —
where I was tasked with putting away dishes during downtime.
Every plate had to be stacked neatly,
ready to serve as the canvas for the next gastronomic creation.
Lucky for me,
the universal laws of physics came in quite handy —
all I had to do was pile things up, rather indiscriminately, and move on to the next task.
In contrast,
iOS developers often have to jump through several conceptual hoops when laying out user interfaces.
After all,
placing things in an upside-down 2D coordinate system is not intuitive for anyone but geometry geeks;
for the rest of us, it’s not that cut and dried.
But wait —
what if we could take physical world concepts like gravity and elasticity
and appropriate them for UI layouts?
As it turns out,
there has been no shortage of attempts to do so since the early years ofGUIs
and personal computing —
Motif’s XmPanedWindow and Swing’s BoxLayout are notable early specimens.
These widgets are often referred to as stack-based layouts,
and three decades later,
they are alive and well on all major platforms,
including Android’s LinearLayout and CSS flexbox,
as well as Apple’s own NSStackView, UIStackView, and —
new in SwiftUI —
HStack, VStack, and ZStack.
This week on NSHipster,
we invite you to enjoy a multi-course course
detailing the most delicious morsels
of this most versatile of layout APIs: UIStackView.
Hors-d’œuvres🍱 Conceptual Overview
Stacking layout widgets come in a wide variety of flavors.
Even so, they all share one common ingredient:
leaning on our intuition of the physical world to keep the configuration layer as thin as possible.
The result is a declarative API that doesn’t concern the developer with the minutiæ of view placement and sizing.
If stacking widgets were stoves,
they’d have two distinct sets of knobs:
Knobs that affect the items it contains
Knobs that affect the stack container itself
Together, these knobs describe how the available space is allotted;
whenever a new item is added,
the stack container recalculates the size and placement of all its contained items,
and then lets the rendering pipeline take care of the rest.
In short,
the raison d’être of any stack container is to ensure that all its child items get a slice of the two-dimensional,
rectangular pie.
Appetizer 🥗 UIStackView Essentials
Introduced in iOS 9,UIStackView is the most recent addition to the UI control assortment in Cocoa Touch.
On the surface,
it looks similar to its older AppKit sibling, the NSStackView,
but upon closer inspection,
the differences between the two become clearer.
Managing Subviews
In iOS, the subviews managed by the stack view are referred to as the arranged subviews.
You can initialize a stack view with an array of arranged subviews,
or add them one by one after the fact. Let’s imagine that you have a set of magical plates, the kind that can change their size at will:
letsaladPlate=UIView(...)letappetizerPlate=UIView(...)letplateStack=UIStackView(arrangedSubviews:[saladPlate,appetizerPlate])// orletsidePlate=UIView(...)letbreadPlate=UIView(...)letanotherPlateStack=UIStackView(...)anotherPlateStack.addArrangedSubview(sidePlate)anotherPlateStack.addArrangedSubview(breadPlate)// Use the `arrangedSubviews` property to retrieve the platesanotherPlateStack.arrangedSubviews.count// 2
Adding an arranged view using any of the methods above also makes it a subview of the stack view.
To remove an arranged subview that you no longer want around,
you need to call removeFromSuperview() on it.
The stack view will automatically remove it from the arranged subview list.
In contrast,
calling removeArrangedSubview(_ view: UIView) on the stack view will only remove the view passed as a parameter from the arranged subview list,
without removing it from the subview hierarchy.
Keep this distinction in mind if you are modifying the stack view content during runtime.
One major benefit of using stack views over custom layouts is their built-in support for toggling subview visibility without causing layout ambiguity;
whenever the isHidden property is toggled for one of the arranged subviews,
the layout is recalculated,
with the possibility to animate the changes inside an animation block:
This feature is particularly useful when the stack view is part of a reusable view such as table and collection view cells;
not having to keep track of which constraints to toggle is a bliss.
Now, let’s resume our plating work, shall we?
With everything in place, let’s see what can do with our arranged plates.
Arranging Subviews Horizontally and Vertically
The first stack view property you will likely interact with is the axis property.
Through it you can specify the orientation of the main axis,
that is the axis along which the arranged subviews will be stacked.
Setting it to either horizontal or vertical will force all subviews to fit into a single row or a single column,
respectively.
This means that stack views in iOS do not allow overflowing subviews to wrap into a new row or column,
unlike other implementations such CSS flexbox and its flex-wrap property.
The orientation that is perpendicular to the main axis is often referred to as the cross axis.
Even though this distinction is not explicit in the official documentation,
it is one of the main ingredients in any stacking algorithm —
without it, any attempt at explaining how stack views work will be half-baked.
Stack view axes in horizontal and vertical orientations.
The default orientation of the main axis in iOS is horizontal;
not ideal for our dishware, so let’s fix that:
plateStack.axis=.vertical
Et voilà!
Entrée🍽 Configuring the Layout
When we layout views,
we’re accustomed to thinking in terms of origin and size.
Working with stack views, however, requires us to instead think in terms of main axis and cross axis.
Consider how a horizontally-oriented stack view works.
To determine the width and the x coordinate of the origin for each of its arranged subviews,
it refers to a set of properties that affect layout across the horizontal axis.
Likewise, to determine the height and the y coordinate,
it refers to another set of properties that affects the vertical axis.
The UIStackView class provides axis-specific properties to define the layout: distribution for the main axis, and alignment for the cross axis.
The Main Axis: Distribution
The position and size of arranged subviews along the main axis is affected in part by the value of the distribution property,
and in part by the sizing properties of the subviews themselves.
In practice, each distribution option will determine how space along the main axis is distributed between the subviews.
With all distributions,
save for fillEqually, the stack view attempts to find an optimal layout based on the intrinsic sizes of the arranged subviews.
When it can’t fill the available space, it stretches
the arranged subview with the the lowest content hugging priority.
When it can’t fit all the arranged subviews,
it shrinks the one with the lowest compression resistance priority.
If the arranged subviews share the same value for content hugging and compression resistance,
the algorithm will determine their priority based on their indices.
With that out of the way, let’s take a look at the possible outcomes,
staring the distributions that prioritize preserving the intrinsic content size of each arranged subview:
equalSpacing: The stack view gives every arranged subview its intrinsic size alongside the main axis, then introduces equally-sized paddings if there is extra space.
equalCentering: Similar to equalSpacing, but instead of spacing subviews equally, a variably sized padding is introduced in-between so as the center of each subview alongside the axis is equidistant from the two adjacent subview centers.
Examples of equalSpacing and equalCentering in both horizontal and vertical orientations. The dashed lines and values between parentheses represent the intrinsic sizes of each subview.
In contrast, the following distributions prioritize filling the stack container, regardless of the intrinsic content size of its subviews:
fill (default): The stack view ensures that the arranged subviews fill all the available space. The rules mentioned above apply.
fillProportionally: Similar to fill, but instead of resizing a single view to fill the remaining space, the stack view proportionally resizes all subviews based on their intrinsic content size.
fillEqually: The stack view ensures that the arranged views fill all the available space and are all the same size along the main axis.
Examples of fill distributions in both horizontal and vertical orientations.
The Cross Axis: Alignment
The third most important property of UIStackView is alignment.
Its value affects the positioning and sizing of arranged subviews along the cross axis.
That is, the Y axis for horizontal stacks,
and X axis for vertical stacks.
You can set it to one of the following values for both vertical and horizontal stacks:
fill (default): The stack view ensures that the arranged views fill all the available space on the cross axis.
leading/trailing: All subviews are aligned to the leading or trailing edge of the stack view along the cross axis. For horizontal stacks, these correspond to the top edge and bottom edge respectively. For vertical stacks, the language direction will affect the outcome: in left-to-right languages the leading edge will correspond to the left, while the trailing one will correspond to the right. The reverse is true for right-to-left languages.
center: The arranged subviews are centered along the cross axis.
For horizontal stacks, four additional options are available, two of which are redundant:
top: Behaves exactly like leading.
firstBaseline: Behaves like top, but uses the first baseline of the subviews instead of their top anchor.
bottom: Behaves exactly like trailing.
lastBaseline: Behaves like bottom, but uses the last baseline of the subviews instead of their bottom anchor.
Coming back to our plates,
let’s make sure that they fill the available vertical space, all while saving the unused horizontal space for other uses —
remember, these can shape-shift!
Another quirk of stack views in iOS is that they don’t directly support setting a background color. You have to go through their backing layer to do so.
Alright, we’ve come quite far,
but have a couple of things to go over before our dégustation is over.
Dessert 🍮 Spacing & Auto Layout
By default,
a stack view sets the spacing between its arranged subviews to zero.
The value of the spacing property is treated as an exact value for distributions that attempt to fill the available space
(fill, fillEqually, fillProportionally),
and as a minimum value otherwise (equalSpacing, equalCentering).
With fill distributions, negative spacing values cause the subviews to overlap and the last subview to stretch, filling the freed up space.
Negative spacing values have no effect on equal centering or spacing distributions.
plateStack.spacing=2// These plates can float too!
The spacing property applies equally between each pair of arranged subviews.
To set an explicit spacing between two particular subviews,
use the setCustomSpacing(:after:) method instead.
When a custom spacing is used alongside the equalSpacing distribution,
it will be applied on all views,
not just the one specified in the method call.
To retrieve the custom space later on, customSpacing(after:) gives that to you on a silver platter.
Sometimes you need more control over the sizing and placement of an arranged subview.
In those cases,
you may add custom constraints on top of the ones generated by the stack view.
Since the latter come with a priority of 1000,
make sure all of your custom constraints use a priority of 999 or less to avoid unsatisfiable layouts.
For vertical stack views,
the API lets you calculate distances from the subviews’ baselines,
in addition to their top and bottom edges.
This comes in handy when trying to maintain a vertical rhythm in text-heavy UIs.
plateStack.isBaselineRelativeArrangement=true// Spacing will be measured from the plates' lips, not their wells.
L’addition s’il vous plaît!
The automatic layout calculation that stack views do for us come with a performance cost.
In most cases,
it is negligible.
But when stack views are nested more than two layers deep,
the hit could become noticeable.
To be on the safe side,
avoid using deeply nested stack views,
especially in reusable views such as table and collection view cells.
After Dinner Mint 🍬 SwiftUI Stacks
With the introduction of SwiftUI during last month’s WWDC,
Apple gave us a sneak peek at how we will be laying out views in the months and years to come:
HStack, VStack, and ZStack.
In broad strokes,
these views are specialized stacking views where the main axis is pre-defined for each subtype and the alignment configuration is restricted to the corresponding cross axis.
This is a welcome change that alleviates the UIStackView API shortcomings highlighted towards the end of cross axis section above.
There are more interesting tidbits to go over, but we will leave that for another banquet.
Stack views are a lot more versatile than they get credit for.
Their API on iOS isn’t always the most self-explanatory,
nor is it the most coherent,
but once you overcome these hurdles,
you can bend them to your will to achieve non-trivial feats —
nothing short of a Michelin star chef boasting their plating prowess.
Etymologically, confetti comes from the Italian word
for the sugar-coated almond sweet thrown at celebrations,
which, in turn, get their name from the Latin conficio:con- (“with, together”) +facio (“do, make”);
in another sense, “to celebrate”.
Confetti gets tossed around a lot these days,
but not nearly as in the 20th century
with its iconic ticker-tape parades
down the streets of New York City,
like the one welcoming home the Apollo 11 astronauts
50 years ago.
Alas, the rise of digital technology made obsolete the stock tickers
whose waste paper tape comprised the substrate of those spectacles.
And as a result, the tradition has become much less commonplace today.
This week mark’s NSHipster’s 7th anniversary!
And what better way to celebrate the occasion
than to implement a fun and flexible confetti view on iOS?
Let’s dive right in with a quick refresher on
the difference between views and layers:
Views and Layers
On iOS,
each view is backed by a layer…or perhaps it’s more accurate to say that layers are fronted by view.
Because despite their reputation as the workhorse of UIKit,UIView delegates the vast majority of its functionality to CALayer.
Sure, views handle touch events and autoresizing,
but beyond that,
nearly everything else between your code and the pixels on screen
is the responsibility of layers.
Among the available CALayer subclasses
in the Quartz Core / Core Animation framework
there are APIs for displaying large amounts of content
by scrolling
and tiling,
there are APIs for doingadvancedtransformations,
and there are APIs that let you get at the baremetal.
But our focus today is a special class calledCAEmitterLayer.
Particle Emitters
Indeed, particle systems are frequently used to generate
fire, smoke, sparks, fireworks, and explosions.
But they’re also capable of modeling… less destructive phenomena like
rain, snow, sand, and —
most importantly —
confetti.
CAEmitterLayer configures the position and shape of
where particles are emitted.
As to the specific behavior and appearance of those particles,
that’s determined by the CAEmitterCell objects seeded to the emitter layer.
By analogy:
CAEmitterLayer
controls the size, position, and intensity of a confetti cannon,
CAEmitterCell
controls the size, shape, color, and movement
of each type of confetti loaded into the hopper.
If you wanted confetti with
black mustaches, orange birds, and purple aardvarks,
then you’d load a CAEmitterLayer with three different CAEmitterCell objects,
each specifying its color, contents, and other behaviors.
Particle Emitter Cells
The secret to CAEmitterLayer’s high performance
is that it doesn’t track each particle individually.
Unlike views or even layers,
emitted particles can’t be altered once they’re created.
(Also, they don’t interact with one another,
which makes it easy for them to be rendered in parallel)
Instead,CAEmitterCell has an enormous API surface area
to configure every aspect of particle appearance and behavior
before they’re generated, including
birth rate, lifetime,
emission angles, velocity, acceleration,
scale, magnification filter, color —
too many to cover in any depth here.
In general,
most emitter cell behavior is defined by either a single property
or a group of related properties
that specify a base value
along with a corresponding range and/or speed.
A range property specifies the maximum amount
that can be randomly added or subtracted from the base value.
For example,
the scale property determines the size of each particle,
and the scaleRange property specifies the
upper and lower bounds of possible sizes relative to that base value;
a scale of 1.0 and a scaleRange of 0.2
generates particles sized between
0.8× and 1.2× the original contents size.
Cell emitter behavior may also have a corresponding speed property,
which specify the rate of growth or decay over the lifetime of the particle.
For example,
with the scaleSpeed property,
positive values cause particles to grow over time
whereas negative values cause particles to shrink.
Loaded up with a solid understanding of the ins and outs of CAEmitterLayer,
now’s the time for us to let that knowledge spew forth in a flurry of code!
Implementing a Confetti View for iOS
First,
let’s define an abstraction for the bits of confetti
that we’d like to shoot from our confetti cannon.
An enumeration offers the perfect balance of constraints and flexibility
for our purposes here.
The next step is to implement the emitter layer itself.
The primary responsibility of CAEmitterLayer
is to configure its cells.
Confetti rains down from above
with just enough variation in its size, speed, and spin to make it interesting.
We use the passed array of Content values
to set the contents of the cell (a CGImage)
and a fill color (a CGColor).
We’ll call this configure(with:) method from our confetti view,
which will be our next and final step.
Implementing ConfettiView
We want our confetti view to emit confetti for a certain amount of time
and then stop.
However, accomplishing this is surprisingly difficult,
as evidenced by the questions floating around onStack Overflow.
The central problem is that
Core Animation operates on its own timeline,
which doesn’t always comport with our own understanding of time.
For instance,
if you neglect to initialize the beginTime of the emitter layer
with CACurrentMediaTime() right before it’s displayed,
it’ll render with the wrong time space.
As far as stopping goes:
you can tell the layer to stop emitting particles
by setting its birthRate property to 0.
But if you start it again up
by resetting that property to 1,
you get a flurry of particles filling the screen
instead of the nice initial burst on the first launch.
Suffice to say that there are myriad different approaches
to making this behave as expected.
But here’s the best solution we’ve found
for handling starting and stopping,
as well as having more than one emitter at the same time:
Going back to our original explanation for a moment,
each instance of UIView
(or one of its subclasses)
is backed by a single, corresponding instance of CALayer
(or one of its subclasses).
A view may also host one or more additional layers,
either as siblings or children to the backing layer.
Taking advantage of this fact,
we can create a new emitter layer each time we fire our confetti cannon.
We can add that layer as a hosted sublayer for our view,
and let the view handle animation and disposal of each layer
in a nice, self-contained way.
There’s a lot of code to unpack here,
so let’s focus on its distinct parts:
❶
First, we create an instance of our custom CAEmitterLayer subclass
(creatively named Layer here, because it’s a private, nested type).
It’s set up with our configure(with:) method from before
and added as a sublayer.
The needsDisplayOnBoundsChange property defaults to false
for whatever reason;
setting it to true here allows us to better handle device trait changes
(like rotation or moving to a new window).
❷
Next, we create a keyframe animation
to taper the birthRate property down to 0 over the specified duration.
❸
Then we add that animation in a transaction
and use the completion block to set up a fade-out transition.
(We set the view as the transition’s animation delegate,
as described in the next session)
❹
Finally, we use key-value coding
to create a reference between the emitter layer and the transition
so that it can be referenced and cleaned up later on.
The animationDidStop(_:) delegate method is called
when our CATransition finishes.
We then get the reference to the calling layer
in order to remove all animations and remove it from its superlayer.
The end result:
it’s as if NSHipster.com were parading down the streets of New York City
(or rather, Brooklyn, if you really wanted to lean into the hipster thing)
But ending on such a practical note
doesn’t make for a particularly snazzy conclusion.
So instead,
how about a quick bonus round detailing seven other ways
that you could implement confetti instead:
✨Bonus ✨ 7 Alternative Approaches to Confetti
SpriteKit Particle System
SpriteKit is the cooler, younger cousin to UIKit,
providing nodes to games rather than views to apps.
On their surface,
they couldn’t look more different from one another.
And yet both share a common, deep reliance on layers,
which makes for familiar lower-level APIs.
The comparison between these two frameworks goes even deeper,
as you’ll find if you open
File > New, scroll down to “Resource”
and create a new SpriteKit Particle System file.
Open it up, and Xcode provides a specialized editor
reminiscent of Interface Builder.
Call up your designed SKEmitterNode by name
or reimplement in code(if you’re the type to hand-roll all of your UIViews)
for a bespoke confetti experience.
SceneKit Particle System
Again with the metaphors,
SceneKit is to 3D what SpriteKit is to 2D.
In Xcode 11,
open File > New,
select SceneKit Scene File under the “Resource” heading,
and you’ll find an entire 3D scene editor —
right there in your Xcode window.
Add in a dynamic physics body and a turbulence effect,
and you can whip up an astonishingly capable simulation in no time at all
(though if you’re like me,
you may find yourself spending hours just playing around with everything)
UIKit Dynamics
At the same time that SpriteKit entered the scene,
you might imagine that UIKit started to get self-conscious about the“business only” vibe it was putting out.
So in iOS 7,
in a desperate attempt to prove itself cool to its “fellow kids”
UIKit added UIDynamicAnimator as part of a set of APIs known as“UIKit Dynamics”.
Good news!
Later this year,
AVFoundation adds support for alpha channels in HEVC video!
So if, say, you already had a snazzy After Effects composition of confetti,
you could export that with a transparent background
and composite it directly to your app or on the web.
Of course,
this time next year, we’ll still be sending animated GIFs around to each other,
despite all of their shortcomings.
Animated GIFs are especially awful for transparency.
Without a proper alpha channel,
GIFs are limited to a single, transparent matte color,
which causes unsightly artifacts around edges.
We all know to use PNGs on the web for transparent images,
but only a fraction of us are even aware thatAPNG is even a thing.
Well even fewer of us know that iOS 13 adds native support for APNG
(as well as animated GIFs — finally!).
Not only were there no announcements at WWDC this year,
but the only clue of this new feature’s existence is
an API diff between Xcode 11 Beta 2 and 3.
Here’s the documentation stub for the function,
CGAnimateImageAtURLWithBlock
And if you think that’s (unfortunately) par for the course these days,
it gets worse.
Because for whatever reason,
the relevant header file (ImageIO/CGImageAnimation.h)
is inaccessible in Swift!
Now, we don’t really know how this is supposed to work,
but here’s our best guess:
But really,
we could do away with all of this confetti
and express ourselves much more simply:
letmood="🥳"
It’s hard to believe that it’s been seven years since I started this site.
We’ve been through a lot together, dear reader,
so know that your ongoing support means the world to me.
Thanks for learning with me over the years.
Until next week:May your code continue to compile and inspire.
Conversion is a tireless errand in software development.
Most programs boil down to some variation of
transforming data into something more useful.
In the case of user-facing software,
making data human-readable is an essential task —
and a complex one at that.
A user’s preferred language, calendar, and currency
can all factor into how information should be displayed,
as can other constraints, such as a label’s dimensions.
All of this is to say:
calling description on an object just doesn’t cut it
under most circumstances.
Indeed,
the real tool for this job is Formatter:
an ancient, abstract class deep in the heart of the Foundation framework
that’s responsible for transforming data into textual representations.
Formatter’s origins trace back to NSCell,
which is used to display information and accept user input in
tables, form fields, and other views in AppKit.
Much of the API design of (NS)Formatter reflects this.
Back then,
formatters came in two flavors: dates and numbers.
But these days,
there are formatters for everything from
physical quantities and time intervals to personal names and postal addresses.
And as if that weren’t enough to keep straight,
a good portion of these have been
soft-deprecated,
or otherwise superseded by more capable APIs (that are also formatters).
To make sense of everything,
this week’s article groups each of the built-in formatters
into one of four categories:
NumberFormatter covers every aspect of number formatting imaginable.
For better or for worse(mostly for better),
this all-in-one API handles
ordinals and cardinals,
mathematical and scientific notation,
percentages,
and monetary amounts in various flavors.
It can even write out numbers in a few different languages!
So whenever you reach for NumberFormatter,
the first order of business is to establish
what kind of number you’re working with
and set the numberStyle property accordingly.
Number Styles
Number Style
Example Output
none
123
decimal
123.456
percent
12%
scientific
1.23456789E4
spellOut
one hundred twenty-three
ordinal
3rd
currency
$1234.57
currencyAccounting
($1234.57)
currencyISOCode
USD1,234.57
currencyPlural
1,234.57 US dollars
Rounding & Significant Digits
To prevent numbers from getting annoyingly pedantic(“thirty-two point three three — repeating, of course…”),
you’ll want to get a handle on NumberFormatter’s rounding behavior.
Here, you have two options:
Set usesSignificantDigits to true
to format according to the rules ofsignificant figures
Set usesSignificantDigits to false(or keep as-is, since that’s the default)
to format according to specific limits on
how many decimal and fraction digits to show
(the number of digits leading or trailing the decimal point, respectively).
If you need specific rounding behavior,
such as “round to the nearest integer” or “round towards zero”,
check out the
roundingMode,roundingIncrement, androundingBehavior properties.
Locale Awareness
Nearly everything about the formatter can be customized,
including the
grouping separator,
decimal separator,
negative symbol,
percent symbol,
infinity symbol,
and
how to represent zero values.
Although these settings can be overridden on an individual basis,
it’s typically best to defer to the defaults provided by the user’s locale.
MeasurementFormatter
MeasurementFormatter was introduced in iOS 10 and macOS 10.12
as part of the full complement of APIs for performing
type-safe dimensional calculations:
Unit subclasses represent units of measure,
such as count and ratio
Dimension subclasses represent dimensional units of measure,
such as mass and length,
(which is the case for the overwhelming majority of
the concrete subclasses provided,
on account of them being dimensional in nature)
A Measurement is a quantity of a particular Unit
A UnitConverter converts quantities of one unit to
a different, compatible unit
For the curious, here's the complete list of units supported by MeasurementFormatter:
MeasurementFormatter and its associated APIs are a intuitive —
just a delight to work with, honestly.
The only potential snag for newcomers to Swift
(or Objective-C old-timers, perhaps)
are the use of generics to constrain Measurement values
to a particular Unit type.
importFoundation// "The swift (Apus apus) can power itself to a speed of 111.6km/h"letspeed=Measurement<UnitSpeed>(value:111.6,unit:.kilometersPerHour)letformatter=MeasurementFormatter()formatter.string(from:speed)// 69.345 mph
Configuring the Underlying Number Formatter
By delegating much of its formatting responsibility to
an underlying NumberFormatter property,MeasurementFormatter maintains a high degree of configurability
while keeping a small API footprint.
Readers with an engineering background may have noticed that
the localized speed in the previous example
gained an extra significant figure along the way.
As discussed previously,
we can enable usesSignificantDigits and set maximumSignificantDigits
to prevent incidental changes in precision.
A MeasurementFormatter,
by default,
will use the preferred unit for the user’s current locale (if one exists)
instead of the one provided by a Measurement value.
Readers with a non-American background certainly noticed that
the localized speed in the original example
converted to a bizarre, archaic unit of measure known as “miles per hour”.
You can override this default unit localization behavior
by passing the providedUnit option.
DateFormatter is the OG class
for representing dates and times.
And it remains your best, first choice
for the majority of date formatting tasks.
For a while,
there was a concern that it would become overburdened with responsibilities
like its sibling NumberFormatter.
But fortunately,
recent SDK releases spawned new formatters for new functionality.
We’ll talk about those in a little bit.
Date and Time Styles
The most important properties for a DateFormatter object are itsdateStyle and timeStyle.
As with NumberFormatter and its numberStyle,
these date and time styles provide preset configurations
for common formats.
Style
Example Output
Date
Time
none
“”
“”
short
“11/16/37”
“3:30 PM”
medium
“Nov 16, 1937”
“3:30:32 PM”
long
“November 16, 1937”
“3:30:32 PM”
full
“Tuesday, November 16, 1937 AD
“3:30:42 PM EST”
letdate=Date()letformatter=DateFormatter()formatter.dateStyle=.longformatter.timeStyle=.longformatter.string(from:date)// July 15, 2019 at 9:41:00 AM PSTformatter.dateStyle=.shortformatter.timeStyle=.shortformatter.string(from:date)// "7/16/19, 9:41:00 AM"
NSDateFormatter*formatter=[[NSDateFormatteralloc]init];[formattersetDateStyle:NSDateFormatterLongStyle];[formattersetTimeStyle:NSDateFormatterLongStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// July 15, 2019 at 9:41:00 AM PST[formattersetDateStyle:NSDateFormatterShortStyle];[formattersetTimeStyle:NSDateFormatterShortStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// 7/16/19, 9:41:00 AM
dateStyle and timeStyle are set independently.
So,
to display just the time for a particular date,
for example,
you set dateStyle to none:
letformatter=DateFormatter()formatter.dateStyle=.noneformatter.timeStyle=.mediumletstring=formatter.string(from:Date())// 9:41:00 AM
NSDateFormatter*formatter=[[NSDateFormatteralloc]init];[formattersetDateStyle:NSDateFormatterNoStyle];[formattersetTimeStyle:NSDateFormatterMediumStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// 9:41:00 AM
As you might expect, each aspect of the date format can alternatively be configured individually, a la carte. For any aspiring time wizards NSDateFormatter has a bevy of different knobs and switches to play with.
ISO8601DateFormatter
When we wrote our first article about NSFormatter back in 2013,
we made a point to include discussion ofPeter Hosey’s ISO8601DateFormatter’s
as the essential open-source library
for parsing timestamps from external data sources.
Fortunately,
we no longer need to proffer a third-party solution,
because, as of iOS 10.0 and macOS 10.12,ISO8601DateFormatter is now built-in to Foundation.
letformatter=ISO8601DateFormatter()formatter.date(from:"2019-07-15T09:41:00-07:00")// Jul 15, 2019 at 9:41 AM
DateIntervalFormatter
DateIntervalFormatter is like DateFormatter,
but can handle two dates at once —
specifically, a start and end date.
“6:03:28 PM Pacific Standard Time - 7:50:08 PM Pacific Standard Time”
DateComponentsFormatter
As the name implies,DateComponentsFormatter works with DateComponents values
(previously),
which contain a combination of discrete calendar quantities,
such as “1 day and 2 hours”.
DateComponentsFormatter provides localized representations of date components
in several different, pre-set formats:
Some years ago,
formatters introduced the concept of formatting context,
to handle situations where
the capitalization and punctuation of a localized string may depend on whether
it appears at the beginning or middle of a sentence.
A context property is available for DateComponentsFormatter,
as well as DateFormatter, NumberFormatter, and others.
Formatting Context
Output
standalone
“About 2 hours”
listItem
“About 2 hours”
beginningOfSentence
“About 2 hours”
middleOfSentence
“about 2 hours”
dynamic
Depends*
*
A Dynamic context changes capitalization automatically
depending on where it appears in the text
for locales that may position strings differently
depending on the content.
RelativeDateTimeFormatter
RelativeDateTimeFormatter is a newcomer in iOS 13 —
and at the time of writing, still undocumented,
so consider this an NSHipster exclusive scoop!
Longtime readers may recall thatDateFormatter actually gave this a try circa iOS 4
by way of the doesRelativeDateFormatting property.
But that hardly ever worked,
and most of us forgot about it, probably.
Fortunately,RelativeDateTimeFormatter succeeds
where doesRelativeDateFormatting fell short,
and offers some great new functionality to make your app
more personable and accessible.
(As far as we can tell,)RelativeDatetimeFormatter takes the most significant date component
and displays it in terms of past or future tense
(“1 day ago” / “in 1 day”).
letformatter=RelativeDateTimeFormatter()formatter.localizedString(from:DateComponents(day:1,hour:1))// "in 1 day"formatter.localizedString(from:DateComponents(day:-1))// "1 day ago"formatter.localizedString(from:DateComponents(hour:3))// "in 3 hours"formatter.localizedString(from:DateComponents(minute:60))// "in 60 minutes"
For the most part,
this seems to work really well.
However, its handling of nil, zero, and net-zero values
leaves something to be desired…
formatter.localizedString(from:DateComponents(hour:0))// "in 0 hours"formatter.localizedString(from:DateComponents(day:1,hour:-24))// "in 1 day"formatter.localizedString(from:DateComponents())// ""
Styles
Style
Example
abbreviated
“1 mo. ago” *
short
“1 mo. ago”
full
“1 month ago”
spellOut
“one month ago”
*May produce output distinct from short for non-English locales.
Using Named Relative Date Times
By default,RelativeDateTimeFormatter adopts the formulaic convention
we’ve seen so far.
But you can set the dateTimeStyle property to .named
to prefer localized deictic expressions—
“tomorrow”, “yesterday”, “next week” —
whenever one exists.
importFoundationletformatter=RelativeDateTimeFormatter()formatter.localizedString(from:DateComponents(day:-1))// "1 day ago"formatter.dateTimeStyle=.namedformatter.localizedString(from:DateComponents(day:-1))// "yesterday"
This just goes to show that
beyond calendrical and temporal relativity,RelativeDateTimeFormatter is a real whiz at linguistic relativity, too!
For example,
English doesn’t have a word to describe the day before yesterday,
whereas other languages, like German, do.
formatter.localizedString(from:DateComponents(day:-2))// "2 days ago"formatter.locale=Locale(identifier:"de_DE")formatter.localizedString(from:DateComponents(day:-2))// "vorgestern"
Hervorragend!
Formatting People and Places
Class
Example Output
Availability
PersonNameComponentsFormatter
“J. Appleseed”
iOS 9.0+ macOS 10.11+
CNContactFormatter
“Appleseed, Johnny”
iOS 9.0+ macOS 10.11+
CNPostalAddressFormatter
“1 Infinite Loop\n Cupertino CA 95014”
iOS 9.0+ macOS 10.11+
PersonNameComponentsFormatter
PersonNameComponentsFormatter is a sort of high water mark for Foundation.
It encapsulates one of the hardest,
most personal problems in computer
in such a way to make it accessible to anyone
without requiring a degree in Ethnography.
The documentation
does a wonderful job illustrating the complexities of personal names
(if I might say so myself),
but if you had any doubt of the utility of such an API,
consider the following example:
CNPostalAddressFormatter provides a convenient Formatter-based API
to functionality dating back to the original AddressBook framework.
The following example formats a constructed CNMutablePostalAddress,
but you’ll most likely use existing CNPostalAddress values
retrieved from the user’s address book.
letaddress=CNMutablePostalAddress()address.street="One Apple Park Way"address.city="Cupertino"address.state="CA"address.postalCode="95014"letaddressFormatter=CNPostalAddressFormatter()addressFormatter.string(from:address)/* "One Apple Park Way
Cupertino CA 95014" */
Styling Formatted Attributed Strings
When formatting compound values,
it can be hard to figure out where each component went
in the final, resulting string.
This can be a problem when you want to, for example,
call out certain parts in the UI.
Rather than hacking together an ad-hoc,regex-based solution,CNPostalAddressFormatter provides a method that vends anNSAttributedString that lets you identify
the ranges of each component
(PersonNameComponentsFormatter does this too).
The NSAttributedString API is…
to put it politely,
bad.
It feels bad to use.
So for the sake of anyone hoping to take advantage of this functionality,
please copy-paste and appropriate the following code sample
to your heart’s content:
Rounding out our survey of formatters in the Apple SDK,
it’s another new addition in iOS 13:
ListFormatter.
To be completely honest,
we didn’t know where to put this in the article,
so we just kind of stuck it on the end here.
(Though in hindsight,
this is perhaps appropriate given the subject matter).
NSListFormatter provides locale-correct formatting of a list of items
using the appropriate separator and conjunction.
Note that the list formatter is unaware of
the context where the joined string will be used,
e.g., in the beginning of the sentence
or used as a standalone string in the UI,
so it will not provide any sort of capitalization customization on the given items,
but merely join them as-is.
The string joined this way may not be grammatically correct when placed in a sentence,
and it should only be used in a standalone manner.
tl;dr:
This is joined(by:) with locale-aware serial and penultimate delimiters.
For simple lists of strings,
you don’t even need to bother with instantiating ListFormatter—
just call the localizedString(byJoining:) class method.
importFoundationletoperatingSystems=["macOS","iOS","iPadOS","watchOS","tvOS"]ListFormatter.localizedString(byJoining:operatingSystems)// "macOS, iOS, iPadOS, watchOS, and tvOS"
ListFormatter works as you’d expect
for lists comprising zero, one, or two items.
ListFormatter.localizedString(byJoining:[])// ""ListFormatter.localizedString(byJoining:["Apple"])// "Apple"ListFormatter.localizedString(byJoining:["Jobs","Woz"])// "Jobs and Woz"
Lists of Formatted Values
ListFormatter exposes an underlying itemFormatter property,
which effectively adds a map(_:) before calling joined(by:).
You use itemFormatter whenever you’d formatting a list of non-String elements.
For example,
you can set a NumberFormatter as the itemFormatter for a ListFormatter
to turn an array of cardinals (Int values)
into a localized list of ordinals.
letnumberFormatter=NumberFormatter()numberFormatter.numberStyle=.ordinalletlistFormatter=ListFormatter()listFormatter.itemFormatter=numberFormatterlistFormatter.string(from:[1,2,3])// "1st, 2nd, and 3rd"
As some of the oldest members of the Foundation framework,NSNumberFormatter and NSDateFormatter
are astonishingly well-suited to their respective domains,
in that way only decade-old software can.
This tradition of excellence is carried by the most recent incarnations as well.
If your app deals in numbers or dates
(or time intervals or names or lists measurements of any kind),
then NSFormatter is indispensable.
And if your app doesn’t…
then the question is,
what does it do, exactly?
Invest in learning all of the secrets of Foundation formatters
to get everything exactly how you want them.
And if you find yourself with formatting logic scattered across your app,
consider creating your own Formatter subclass
to consolidate all of that business logic in one place.
Product design is about empathy.
Knowing what a user wants,
what they like,
what they dislike,
what causes them frustration,
and learning to understand and embody those motivations —
this is what it takes to make something insanely great.
There is, however,
one critical factor that app developers often miss:network condition,
or more specifically,
the latency and bandwidth of an Internet connection.
For something so essential to user experience,
it’s unfortunate that most developers take an ad-hoc approach
to field-testing their apps under different conditions
(if at all).
This week on NSHipster,
we’ll be talking about the
Network Link Conditioner,
a utility that allows macOS and iOS devices
to accurately and consistently simulate adverse networking environments.
Installation
Network Link Conditioner can be found
in the “Additional Tools for Xcode” package.
You can download this from the
Downloads for Apple Developers
page.
Search for “Additional Tools”
and select the appropriate release of the package.
Once the download has finished,
open the DMG,
navigate to the “Hardware” directory,
and double-click “Network Link Condition.prefPane”.
Click on the Network Link Conditioner preference pane
at the bottom of System Preferences.
Controlling Bandwidth, Latency, and Packet Loss
Enabling the Network Link Conditioner
changes the network environment system-wide
according to the selected configuration,
limiting uplink or downloadbandwidth,latency, and rate ofpacket loss.
You can choose from one of the following presets:
100% Loss
3G
DSL
EDGE
High Latency DNS
LTE
Very Bad Network
WiFi
WiFi 802.11ac
…or create your own according to your particular requirements.
Now try running your app with the Network Link Conditioner enabled:
How does network latency affect your app startup?
What effect does bandwidth have on table view scroll performance?
Does your app work at all with 100% packet loss?
Enabling Network Link Conditioner on iOS Devices
Although the preference pane works well for developing on the simulator,
it’s also important to test on a real device.
Fortunately,
the Network Link Conditioner is available for iOS as well.
To use the Network Link Conditioner on iOS,
set up your device for development:
Connect your iOS device to your Mac
In Xcode, navigate to Window > Devices & Simulators
Select your device in the sidebar
Click “Use for Development”
Now you’ll have access to the Developer section of the Settings app.
You can enable and configure the Network Link Conditioner
on your iOS device under Settings > Developer > Networking.
(Just remember to turn it off after you’re done testing!).
For a while now,
the distinction between “desktop” and “mobile”
has become increasingly tenuous.
As the computers in our pockets grow ever more capable,
they more closely resemble the computers typically situated on our desks and laps.
This trend was especially pronounced in this year’s WWDC,
with the announcement of
Catalyst andiPadOS.
Today, what’s the difference between a MacBook and an iPad?
Practically speaking, you might point to
the presence or absence of
a physical keyboard,
a SIM card, or
an ARM processor(and if the rumors about next year’s MacBook models are to believed,
those latter two may soon cease to be a distinction).
For many of us,
a physical keyboard is the defining trait thatmakes a computer a “desktop” computer in the traditional sense;
when you purchase an external keyboard for your iPad,
you do so to make it “desktop”-like.
But for many others —
including those of us with a physical disability —
a typewriter-like keyboard is but one of many input methods
available to desktop users.
This week on NSHipster,
we’re taking a look at the macOS Accessibility Keyboard.
Beyond its immediate usefulness as an assistive technology,
the Accessibility Keyboard challenges us to think differently
about the nature of input methods
and any remaining distinction between mobile and desktop computers.
Introduced in macOS High Sierra,
the Accessibility Keyboard
lets you type and interact with your Mac
without the use of a physical keyboard.
To turn it on,
open System Preferences,
click the Accessibility preference pane,
select “Keyboard” under the “Interactions” section in the sidebar.
(Alternatively, you can search for “Accessibility Keyboard”
and navigate to the first result).
Click the checkbox labeled “Enable Accessibility Keyboard”
to present the accessibility keyboard over the windows of the frontmost app.
The software keyboard reproduces the layout of your hardware keyboard.
The modifier keys outlined in red (⌘, ⇧, ⌥)
are “sticky keys”
and remain active until a non-“sticky” key is activated,
allowing for capital letters and keyboard shortcuts.
Along the top row are iOS-style suggestions
that update automatically as you type.
However, the most interesting feature of the Accessibility Keyboard
is tucked behind the ⚙︎ button on the top right corner —
the ability to customize and create your own keyboards!
Customizing and Creating Your Own Accessibility Keyboards
Panel Editor is a built-in app
that lets you edit Accessibility Keyboard panels.
For something so obscure,
the Panel Editor app is remarkably well made.
Adding, moving, and editing buttons on a panel is a cinch.
You can even click and drag to select and move multiple buttons at once,
and group buttons together at your convenience.
Each button has a name
as well as options for font size, color, and position.
By default, the name appears in the button itself,
but you can specify an image to display instead.
You can configure a button to perform any one of the following actions
when clicked:
None
Go Back (navigate between panels in Accessibility Keyboard)
Open Panel
Show / Hide Toolbar
Dwell (relevant to head- or eye-tracking technology, and other hardware switches)
AppleScript
Enter Text
Press Keys
Open App
System Event
Typing Suggestions
Of these,“Enter Text” is the most common.
We’ll use that in our next example
as a way to solve the problem of
creating input methods for scripts without a keyboard.
Creating an IPA Keyboard
Standard Latin script is insufficient for expressing phonetics,
how a word sounds when spoken.
As English speakers, we know this all too well.
That’s why linguists invented their own script,
the International Phonetic Alphabet
(IPA).
Whereas typical letters may have different pronunciations
across dialects (/tə.ˈme͡ɪ.do͡ʊ/, /tə.ˈmɑ.to͡ʊ/) —
or even within the same word (like the letter “a” in “application”) —
IPA symbols represent a single sound, or phoneme;
the mid-central vowel, “ə” (a.k.a “schwa”)
sounds the same whether its part of
an English word or a Japanese word or nonsensical babbling.
Working with IPA on computers has pretty much always been a PITA,
for three reasons:
Incomplete Text Encoding
Until Unicode version 6.1,
some IPA symbols didn’t have a specified code point,
forcing linguists to either use a similar-looking character
or define ad hoc encodings within aPrivate Use Area.
Limited Font Support
It’s one thing to have a specified code point.
Having a font that can shape, or render that code point correctly
is another matter entirely.
Lack of Input Methods
Just because the computer can represent and render a character
doesn’t mean that you, as a user,
can produce that character in the first place.
Typing on a QWERTY keyboard,
we take for granted being able to type the j key
to produce the letter “j”.
But what if you wanted to type “ʝ”?
For all too many people,
the answer is “Google and copy-paste”.
Fortunately,
the first and second of these three challenges are no longer an issue
on modern operating systems:
Unicode provides code points for all of theIPA characters,
and most platforms natively render them all
without having to install a custom font.
However, the problem of input methods remains an open question.
SIL International hostsan IPA keyboard layout
by Joan Wardell.
There’s also the SuperIPA keyboard —
based on CXS, a variant of X-SAMPA—
by Kreative Korporation.
You could also use
IPA Palette
by Brian “Moses” Hall.
But if none of these tick all of your boxes in terms of usability of ergonomics,
the Accessibility Keyboard Panel Editor provides an easy way
for anyone to hand-roll a bespoke solution:
This keyboard layout was created with Panel Editor
and is modeled after theofficial IPA Chart,
with symbols arranged by place and manner of articulation.
It’s not nearly as efficient as any of the aforementioned keyboard layouts
(nor is it as complete),
but anyone familiar with IPA can use it for transcription immediately
without additional training.
If you’re a developer,
there’s a good chance that your next questions are
“What does this file format look like?” and“Can I generate these with code rather than a GUI?”.
The short answers are “A Bundle of Property Lists”, and “Yes!”.
Read on for the full breakdown:
Inspecting the Accessibility Keyboard File Format
The keyboard panel bundles themselves can be tricky to find
if you don’t know what you’re looking for.
On macOS Mojave,
any custom panels you make can be found within the
~/Library/Application Support/com.apple.AssistiveControl/ directory
in bundles with a .ascconfig file extension.
The bundle comprises a top-level Info.plist file
and a Resources directory containing an index of assets
(along with any asset files, like button images)
as well as a file named PanelDefinitions.plist.
$ tree ~/Library/Application Support/com.apple.AssistiveControl/dwellControlUserPanels1.ascconfig/Contents├── Info.plist
└── Resources
├── AssetIndex.plist└── PanelDefinitions.plist
Opening up PanelDefinitions.plist reveals the inner structure
of our custom virtual keyboard layout:
The PanelObjects key is associated with an array of dictionaries,
each representing a single button.
Fortunately, he majority of the key names are self-explanatory:
The takeaway from looking at the file format is that
it’d be very easy to generate Accessibility Keyboard panels in code,
rather than using the Panel Editor app.
(In fact, we used find-and-replace to
bulk resize the buttons in the IPA keyboard,
a task that would have otherwise taken 100⨉ longer).
Additional Use Cases
There are dozens of scripts comprising hundreds of characters
that lack a dedicated keyboard layout.
And the macOS Accessibility Keyboard offers a wonderful, built-in solution
for producing these characters.
But what else could you do with this technology,
now that you know it exists?
Here are a few ideas for you to consider:
Templating Frequent Communications
Do you find yourself writing the same things over and over again
in emails or GitHub Issues?
Create a custom, virtual keyboard to summon boilerplate
with the click of your mouse or the tap of your trackpad.
Generative Text
The Accessibility Keyboard isn’t limited to canned responses.
Thanks to its AppleScript integration,
you can populate text dynamically from virtually any source.
For example,
you could create a Fortune button
that inserts a (pseudo)random entry from thefortune program,
with the following AppleScript:
setfortunetodo shell script"/usr/local/bin/fortune"set the clipboard tofortuneas textdelay0.01tellapplication"System Events"totell(nameofapplicationprocesseswhosefrontmostistrue)tokeystroke"v"usingcommanddown
Obligatory fortune output:
If your bread is stale, make toast.
Sound Board
Do you aspire to be adrive-time radio DJlive streamer?
Use the Accessibility Keyboard to trigger funny sound effects at will
to delight your throng of fans.
do shell script"afplay /System/Sounds/Sosumi.aiff"
World Domination
AppleScript gives you the closest thing to complete,
programmatic access to the entire system.
Set a button to kick off a build of your app,
or send a message on Slack,
or turn on the lights in your house,
or play your theme song!
The Accessibility Keyboard serves as a powerful, built-in, and omnipresent
interface to whatever functionality you desire —
without going through all the trouble of building an app.
Because, if you think about it,
is there any real difference between
the j key on your keyboard
and a hypothetical Party button on a virtual keyboard?
The strong connection between
the word “computer” and typewriter keyboards
is merely a historical one.
The rise of smartphones and smartwatches help illustrate this.
Any distinction between
the computers in your hand, on your wrist, or on your desk
is ultimately insignificant.
All computers are the same;
they’re all force multipliers.
Once you separate “desktop” computers from the shape of their primary interface,
you can start to fully appreciate the full capabilities
of what’s at our fingertips.
Our humble publication has frequented this topic with some regularity,
whether it was attempting to make sense ofequality in Objective-C
or appreciating the much clearer semantics of Swiftvis-à-vis the Equatable protocol.
Swift 5.1 gives us yet another occasion to ponder this old chestnut
by virtue of the new Identifiable protocol.
We’ll discuss the noumenon of this phenomenal addition to the standard library,
and help you identify opportunities to
realize its potential in your own projects.
But let’s dispense with the navel gazing and
jump right into some substance:
Swift 5.1 adds the Identifiable protocol to the standard library,
declared as follows:
Values of types adopting the Identifiable protocol
provide a stable identifier for the entities they represent.
For example,
a Parcel object may use the id property requirement
to track the package en route to its final destination.
No matter where the package goes,
it can always be looked up by its id:
The Swift Evolution proposal for Identifiable,SE-0261,
was kept small and focused in order to be incorporated quickly.
So, if you were to ask,“What do you actually get by conforming to Identifiable?”,
the answer right now is “Not much.”
As mentioned in the future directions,
conformance to Identifiable has the potential to unlock
simpler and/or more optimized versions of other functionality,
such as the new ordered collection diffing APIs.
But the question remains:“Why bother conforming to Identifiable?”
The functionality you get from adopting Identifiable is primarily semantic,
and require some more explanation.
It’s sort of like asking,
“Why bother conforming to Equatable?”
And actually, that’s not a bad place to start.
Let’s talk first about Equatable and its relation to Identifiable:
Identifiable vs. Equatable
Identifiable distinguishes the identity of an entity from its state.
A parcel from our previous example
will change locations frequently as it travels to its recipient.
Yet a normal equality check (==)
would fail the moment it leaves its sender:
While this is an expected outcome from a small, contrived example,
the very same behavior can lead to confusing results further down the stack,
where you’re not as clear about how different parts work with one another.
On the subject of Set,
let’s take a moment to talk about the Hashable protocol.
Identifiable vs. Hashable
In our article about Hashable,
we described how Set and Dictionary use a calculated hash value
to provide constant-time (O(1)) access to elements in a collection.
Although the hash value used to bucket collection elements
may bear a passing resemblance to identifiers,Hashable and Identifiable have some important distinctions
in their underlying semantics:
Unlike identifiers,
hash values are typically state-dependent,
changing when an object is mutated.
Identifiers are stable across launches,
whereas hash values are calculated by randomly generated hash seeds,
making them unstable between launches.
Identifiers are unique,
whereas hash values may collide,
requiring additional equality checks when fetched from a collection.
Identifiers can be meaningful,
whereas hash values are chaotic
by virtue of their hashing functions.
In short,
hash values are similar to
but no replacement for identifiers.
So what makes for a good identifier, anyway?
Choosing ID Types
Aside from conforming to Hashable,Identifiable doesn’t make any other demands of
its associated ID type requirement.
So what are some good candidates?
If you’re limited to only what’s available in the Swift standard library,
your best options are Int and String.
Include Foundation,
and you expand your options with UUID and URL.
Each has its own strengths and weaknesses as identifiers,
and can be more or less suited to a particular situation:
Int as ID
The great thing about using integers as identifiers
is that (at least on 64-bit systems),
you’re unlikely to run out of them anytime soon.
Most systems that use integers to identify records
assign them in an auto-incrementing manner,
such that each new ID is 1 more than the last one.
Here’s a simple example of how you can do this in Swift:
If you wanted to guarantee uniqueness across launches,
you might instead initialize the sequence with a value
read from a persistent store like UserDefaults.
And if you found yourself using this pattern extensively,
you might consider factoring everything into a self-containedproperty wrapper.
Monotonically increasing sequences have a lot of benefits,
and they’re easy to implement.
This kind of approach can provide unique identifiers for records,
but only within the scope of the device on which the program is being run
(and even then, we’re glossing over a lot with respect to concurrency
and shared mutable state).
If you want to ensure that an identifier is unique acrossevery device that’s running your app, then
congratulations —you’ve hit
a fundamental problem in computer science.
But before you start in onvector clocks andconsensus algorithms,
you’ll be relieved to know that there’s a
much simpler solution:
UUIDs.
UUID as ID
UUIDs, or
universally unique identifiers,
(mostly) sidestep the problem of consensus with probability.
Each UUID stores 128 bits —
minus 6 or 7 format bits, depending on the
version—
which, when randomly generated,
make the chances of collision,
or two UUIDs being generated with the same value,astronomically small.
As discussed in a previous article,
Foundation provides a built-in implementation of (version-4) UUIDs
by way of theUUID type.
Thus making adoption to Identifiable with UUIDs trivial:
Beyond minor ergonomic and cosmetic issues,UUID serves as an excellent alternative to Int
for generated identifiers.
However,
your model may already be uniquely identified by a value,
thereby obviating the need to generate a new one.
Under such circumstances,
that value is likely to be a String.
String as ID
We use strings as identifiers all the time,
whether it takes the form of a username or a checksum or a translation key
or something else entirely.
The main drawback to this approach is that,
thanks to The Unicode® Standard,
strings encode thousands of years of written human communication.
So you’ll need a strategy for handling identifiers like
“⽜”, “𐂌”, “”, and “🐮”
…and that’s to say nothing of the more pedestrian concerns,
like leading and trailing whitespace and case-sensitivity!
Normalization is the key to successfully using strings as identifiers.
The easiest place to do this is in the initializer,
but, again, if you find yourself repeating this code over and over,property wrappers can help you here, too.
URLs (or URIs if you want to be pedantic)
are arguably the most ubiquitous kind of identifier
among all of the ones described in this article.
Every day, billions of people around the world use URLs
as a way to point to a particular part of the internet.
So URLs a natural choice for an id value
if your models already include them.
URLs look like strings,
but they use syntax
to encode multiple components,
like scheme, authority, path, query, and fragment.
Although these formatting rules dispense with much of the invalid input
you might otherwise have to consider for strings,
they still share many of their complexities —
with a few new ones, just for fun.
The essential problem is that
equivalent URLs may not be equal.
Intrinsic, syntactic details like
case sensitivity,
the presence or absence of a trailing slash (/),
and the order of query components
all affect equality comparison.
So do extrinsic, semantic concerns like
a server’s policy to upgrade http to https,
redirect from www to the apex domain,
or replace an IP address with a
which might cause different URLs to resolve to the same webpage.
If your model gets identifier URLs for records from a trusted source,
then you may take URL equality as an article of faith;
if you regard the server as the ultimate source of truth,
it’s often best to follow their lead.
But if you’re working with URLs in any other capacity,
you’ll want to employ some combination of
URL normalizations
before using them as an identifier.
Unfortunately, the Foundation framework doesn’t provide
a single, suitable API for URL canonicalization,
but URL and URLComponents provide enough on their own
to let you roll your own
(though we’ll leave that as an exercise for the reader):
UUID and URL both look like strings,
but they use syntax rules to encode information in a structured way.
And depending on your app’s particular domain,
you may find other structured data types that
would make for a suitable identifier.
Thanks to the flexible design of the Identifiable protocol,
there’s nothing to stop you from implementing your own ID type.
For example,
if you’re working in a retail space,
you might create or repurpose an existing
UPC type
to serve as an identifier:
As Identifiable makes its way into codebases,
you’re likely to see it used in one of three different ways:
The newer the code,
the more likely it will be for id to be a stored property —
most often this will be declared as a constant (that is, with let):
importFoundation// Style 1: id requirement fulfilled by stored propertystructProduct:Identifiable{letid:UUID}
Older code that adopts Identifiable,
by contrast,
will most likely satisfy the id requirement
with a computed property
that returns an existing value to serve as a stable identifier.
In this way,
conformance to the new protocol is purely additive,
and can be done in an extension:
importFoundationstructProduct{varuuid:UUID}// Style 2: id requirement fulfilled by computed propertyextensionProduct:Identifiable{varid{uuid}}
If by coincidence the existing class or structure already has an id property,
it can add conformance by simply declaring it in an extension(assuming that the property type conforms to Hashable).
importFoundationstructProduct{varid:UUID}// Style 3: id requirement fulfilled by existing propertyextensionProduct:Identifiable{}
No matter which way you choose,
you should find adopting Identifiable in a new or existing codebase
to be straightforward and noninvasive.
As we’ve said time and again,
often it’s the smallest additions to the language and standard library
that have the biggest impact on how we write code.
(This speaks to the thoughtful,
protocol-oriented
design of Swift’s standard library.)
Because what Identifiable does is kind of amazing:it extends reference semantics to value types.
When you think about it,
reference types and value types differ not in what information they encode,
but rather how we treat them.
For reference types,
the stable identifier is the address in memory
in which the object resides.
This fact can be plainly observed
by the default protocol implementation of id for AnyObject types:
Ever since Swift first came onto the scene,
the popular fashion has been to eschew all reference types for value types.
And this neophilic tendency has only intensified
with the announcement of SwiftUI.
But taking such a hard-line approach makes a value judgment
of something better understood to be a difference in outlook.
It’s no coincidence that much of the terminology of programming
is shared by mathematics and philosophy.
As developers, our work is to construct logical universes, after all.
And in doing so,
we’re regularly tasked with reconciling our own mental models
against that of every other abstraction we encounter down the stack —
down to the very way that we understand electricity and magnetism to work.
Today is Labor Day in the United States
(and Labour Day in Canada),
a day to celebrate the achievement of the workers
who organized to bring about fair and safe conditions for employees —
protections that serve as the foundation for the modern workplace.
Labor Day is also the unofficial end of summer;
the long weekend acting as a buffer between
the lemonades, sunburns, and trashy romance novels of June and
the pumpkin spice lattes, flu shots, and textbooks of September.
However,
for the stereotypical tech worker,
who likes the heat of summer about as much as
the concept of “work/life balance”,
Labor Day frequently serves as something else entirely:
a wake-up call.
That,
after a lazy summer of ideation and experimentation,
it’s once again time to launch new products and ship new features.
And if you’re an app developer specifically,
you may know today as,
“Oh-🤬-it’s-September-and-I-still-haven’t-implemented-Dark-Mode-in-my-app” day.
We’re dedicating this week’s article to anyone out there
who’s celebrating this esteemed holiday,
whether contemporaneously or in the intervening days until iOS 13 goes GM.
We hope that a few quick tips can help shed light
against the shade of your looming deadline.
Dark Mode is an appearance preference
that tells the system and participating apps to adopt darker color palettes.
Whereas an app may display dark text on a light background by default,
it may instead show white text on a dark background.
Last year,
Dark Mode was the killer feature of macOS 10.14 Mojave,
and its contemporaneous launch with Safari 12
rippled throughout the World-Wide Web,
gaining steady adoption among websites
(like yours truly)
andother browsers.
After waiting for what felt like an eternity
(but was only, like, a year),
Dark Mode is now finally coming to the iPhone and iPad iOS 13.
No longer will we have to tinker withDisplay Accommodations
to limit our light pollution when browsing Reddit late at night.
Of course, the challenge with Apple technologies
is that short of telling you that “you’re holding it wrong”,
they’ll rarely acknowledge the existence of prior art or alternatives,
let alone provide a transition document that in any way resembles
how everyone was doing things before we got an officially-sanctioned API.
(Then again, can you really blame them?)
If you were following 100% of Apple’s guidance to the letter,
you’d barely have to change a line or code
to get your app ready for
next week’s special event.
But most apps are built on solutions we built for ourselves
to bridge the gaps in the SDK,
and it may not be clear how to get on the newhappy path
from there.
Apple’s guidance for adopting Dark Mode is fantastic for new projects
but doesn’t quite hit all of the points you should be aware of
when preparing your existing app for iOS 13.
So without further ado,
here’s 6 action items for how to get your app ready for Dark Mode.
#Cancel Color Literals
In Xcode,
a color literal
is code with the prefix #colorLiteral
that is rendered as a color swatch in the editor.
For example,
the code #colorLiteral(red: 1, green: 0.67, blue: 0, alpha: 1)
is rendered in Xcode as .
Color literals can be drag-and-dropped from
the Media Browser in Xcode 10,
which has been consolidated with Snippets into the new Library panel in Xcode 11.
Both color literals and their cousin, image literals,
were introduced in support of Xcode Playgrounds.
But don’t let their appearance fool you:
neither have a place in your app’s codebase.
Dark Mode or not,
you can replace all usage of color literals throughout your codebase
from the command line:
But before you do,
you might as well do one better
and replace it with something that will stand the test of time.
Nix UIColor Hexadecimal Initializers
Among the most common extensions you’ll find in a
Swiss Army Knife-style CocoaPod
is a category on UIColor that initializes from a hexadecimal string.
Something along the lines of this:
Setting aside any qualms about how they’re typically employed and implemented,
you’d be well-advised to have these go the way of color literals
for the same reasons we described in the previous section.
But worry not!
You’ll still have a way to define colors
using those hex codes that your designer sent over,
as we’ll see in our discussion of named colors.
Find & Replace Fixed Colors
UIColor defines several class properties
that return colors by their common name.
These properties are problematic in iOS 13,
because they don’t automatically adjust for light or dark appearance.
For example,
setting a label’s color to .black looks fine
against the default UITableViewCell background,
but it’s illegible when that background becomes black
when Dark Mode is enabled.
To make your app ready for Dark Mode on iOS 13,
you’ll most likely want to replace any instance of the following
UIColor class properties:
red
orange
yellow
brown
green
cyan
blue
purple
magenta
white
lightGray
gray
darkGray
black
Hopefully you aren’t using the built-in
ROYGBIVUIColor constants for much other than occasional layout debugging,
but chances you’ll probably find a few instances of .black or .white
peppered throughout your codebase somewhere.
In any case,
the easiest change to support Dark Mode would be to
replace any of the aforementioned fixed color properties with
the corresponding system-prefixed adaptable color below:
Name
API
Light
Dark
Default
Accessible
Default
Accessible
Red
systemRed
Orange
systemOrange
Yellow
systemYellow
Green
systemGreen
Teal
systemTeal
Blue
systemBlue
Indigo
systemIndigo
Purple
systemPurple
Pink
systemPink
Gray
systemGray
Gray (2)
systemGray2
Gray (3)
systemGray3
Gray (4)
systemGray4
Gray (5)
systemGray5
Gray (6)
systemGray6
You may notice that this table doesn’t provide direct correspondences for
black or white (or brown, but disregard that for now).
Black and white don’t have adaptive colors
because their names would cease to be descriptive in Dark Mode;
if .systemBlack existed, it’d pretty much have to be .white
to be visible in a dark color pallet.
Which gets to a deeper point about color management in an era of Dark Mode…
Use Semantic Colors
The best way to ensure consistent rendering of your UI
on any device in any appearance mode
is to use semantic colors,
named according to their function rather than appearance.
Similar to how Dynamic Type
uses semantic tags like “Headline” and “Body” to
automatically set the most suitable font for the user’s display preferences,
semantic colors —
or what Apple’s calling
UI Element Colors—
provide future-proof behavior for your views and controls.
When styling your component,
set the color to the closest UIColor class property below:
Label Colors
label
secondaryLabel
tertiaryLabel
quaternaryLabel
Text Colors
placeholderText
Link Colors
link
Separator Colors
separator
opaqueSeparator
Fill Colors
systemFill
secondarySystemFill
tertiarySystemFill
quaternarySystemFill
Background Colors
systemBackground
secondarySystemBackground
tertiarySystemBackground
Grouped Background Colors
systemGroupedBackground
secondarySystemGroupedBackground
tertiarySystemGroupedBackground
Upgrade Homegrown Semantic Color Palettes
If you’ve given any thought to color management in your app
you’ll have likely landed on some form of the following strategy,
whereby you define semantic colors according to fixed colors
within a namespace or in an extension to UIColor.
For example,
the following example shows how an app might defineUIColor constants from theMaterial UI color system
and reference them from semantic UIColor class properties:
Nothing about the fixed Material UI color constants has to change
with this approach.
Instead, the semantic color property customAccent provides a dynamic color
that uses the color most appropriate for the current rendering context.
When Dark Mode is enabled,
a lighter orange is used to contrast against the darker color palette;
otherwise, the behavior is unchanged from the original implementation.
The extra #available check creates some bloat in the implementation,
but it’s a small price to pay for the flexibility this approach provides.
Unfortunately,
there’s one crucial shortcoming to using color properties in this way:
they can’t be referenced from Interface Builder.
If your app uses either Storyboards or XIBs,
the best approach is to use color assets.
Manage Colors with an Asset Catalog
Color assets let you manage colors in an Xcode Asset Catalog
in the same way that you do for
images, data, or other resources.
To create a color set,
open an Asset Catalog in your Xcode project,
click the + button on the bottom left-hand corner,
and select “New Color Set”.
In the Attributes inspector,
select “Any, Dark” appearance.
Colors are frequently expressed in the form (“#RRGGBB”);
you can enter colors in this form by
selecting “8-bit Hexadecimal” from the “Input Method” drop-down.
Here,
we’ve done the same as before,
except instead of defining fixed UIColor constants like orange300 in code,
we set those in the color set itself.
Now when it comes time to reference the color asset
by the existing semantic class property,
we can use the UIColor named initializer:
Your opinion about color assets will largely be informed by
your preference or dispreference towards specialized Xcode document editors.
Some folks like to have everything spelled out in code,
while others appreciate the affordances provided by a bespoke UI
like the one provided by Xcode for color sets.
In fact, your opinion of color assets
is probably concomitant with your feelings about Storyboards —
which is convenient,
because the killer feature of color assets is that
they can be referenced from within Interface Builder.
(If you’re not on team IB, then you can safely skip this whole discussion.)
Replace Instances of “Custom Color” in XIBs and Storyboards
The “Custom Color” option in Interface Builder
that brings up the macOS system-native color picker
suffers the same problem as the color literals and fixed colors
we talked about earlier.
If you want your XIB-powered views to look good on iOS 13,
you’ll need to migrate to named colors.
This can be done easily:
select any component in your scene,
and you can set its color attribute using the same, named color
defined in your Asset Catalog.
For a small project,
this can be done by hand for all your screens in under an hour.
However,
for a larger app,
this is a process you’ll want to automate.
XIB Anatomy
Under the hood,
XIB and Storyboard files are merely XML files like any other:
<?xml version="1.0" encoding="UTF-8"?><documenttype="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"<#...#>><deviceid="retina6_1"orientation="portrait"><adaptationid="fullscreen"/></device><dependencies><deploymentidentifier="iOS"/><plugInidentifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin"version="14490.49"/><capabilityname="Safe area layout guides"minToolsVersion="9.0"/><capabilityname="documents saved in the Xcode 8 format"minToolsVersion="8.0"/></dependencies><scenes><!--View Controller--><scenesceneID="nsh-ips-ter"><objects><viewControllerid="dar-kmo-de1"customClass="ViewController"customModule="NSHipster"customModuleProvider="target"sceneMemberID="viewController"><viewkey="view"contentMode="scaleToFill"id="mai-nv-iew"><rectkey="frame"x="0.0"y="0.0"width="414"height="896"/><colorkey="backgroundColor"red="1"green="0.69019607843137254"blue="0.0"alpha="1"colorSpace="custom"customColorSpace="sRGB"/></view></viewController><placeholderplaceholderIdentifier="IBFirstResponder"id="dkx-z0-nzr"sceneMemberID="firstResponder"/></objects></scene></scenes></document>
We wouldn’t want to write this from scratch by hand,
but there’s nothing too mysterious going on here.
So consider what happens when you use Xcode
to switch the custom background color of the main view to a named color:
A new "Named colors" capability is added as a dependency
for opening the document
(Xcode uses this to determine whether it can edit files
created by newer versions).
❷
The red, green, blue, and alpha components on the color element
were replaced by a single name attribute.
❸
A corresponding namedColor element was added to the top-level resources element.
Based on this understanding,
we should know enough to make this change en masse
with our own tooling!
Finding All Custom Colors
The first order of business when migrating your Storyboards and XIBs for Dark Mode
is to find all of the instances of custom colors.
You could go through each by hand and click each of the visual elements…
or you could run the following command:
This command prints the name of each file
followed by each custom, unnamed color element it found
(denoted by the colorSpace="custom" attribute).
The resulting list serves as a battle plan for the next phase of attack:
Finding Each Distinct Custom Color
Apps tend to reuse a small set of colors
across their views — as they should!
By running the following command,
you can generate a sorted, unique’d list of
every custom color in every XIB or Storyboard:
Some entries may be equivalent, within a small delta of each other
(because, you know… floating-point numbers).
To account for account for this,
and to transform our output into something easier to work with,
you can write the output to a file and process it with
a Ruby script like this one:
From here,
the final step is to map each set ofRGBA values
to the corresponding named color that you want to replace it with.
Replacing Custom Colors
At this point,
we’re well beyond the point where shell one-liners seem like a good idea.
So here’s a Ruby script we wrote up
that makes all of the changes we understand to take place
when replacing a custom color with a named color in Interface Builder:
require'nokogiri'defname_for_rgba_components(red,green,blue,alpha)caseformat('#%02X%02X%02X%02X',red,green,blue,alpha)# Return named color matching RGBA components# e.g. "#F8CB00FF" => "Accent"endenddefname_for_white_and_alpha_components(white,alpha)# Return named color matching white and alpha components# e.g. 0.9 => "Off-White"end# Process each Storyboard and XIB fileDir['**/*.{storyboard,xib}'].eachdo|xib|doc=Nokogiri::XML(File.read(xib))names=[]# Collect each custom color and assign it a namedoc.xpath('//objects//color').eachdo|color|nextifcolor['name']name=nilcolor_space=color['colorSpace']color_space=color['customColorSpace']ifcolor_space=='custom'casecolor_spacewhen'sRGB','calibratedRGB'components=color.attributes.values_at('red','green','blue','alpha').map(&:value).map(&:to_f).map{|c|c*255}name=name_for_rgba_components(*components)when'genericGamma22GrayColorSpace','calibratedWhite'components=color.attributes.values_at('white','alpha').map(&:value).map(&:to_f)name=name_for_white_and_alpha_components(*components)endnextunlessnamenamed_color=doc.create_element('color',key: color['key'],name: name)color.replace(named_color)names<<nameend# Proceed to the next file if no named colors were foundnextifnames.empty?# Add the named color capability as a document dependencydependencies=doc.at('/document/dependencies')||doc.root.add_child(doc.create_element('dependencies'))unlessdependencies.at("capability[@name='Named colors']")dependencies<<doc.create_element('capability',name: 'Named colors',minToolsVersion: '9.0')end# Add each named color to the document resourcesresources=doc.at('/document/resources')||doc.root.add_child(doc.create_element('resources'))names.uniq.sort.eachdo|name|nextifresources.at("namedColor[@name='#{name}']")resources<<doc.create_element('namedColor',name: name)end# Save the changesFile.write(xib,doc.to_xml(indent: 4,encoding: 'UTF-8'))end
*Phew!*
If you’ve been facing down a deadline for Dark Mode
at the expense of enjoying one last hurrah of summer,
we hope that this article was able to get you out of the office today.
Its ironic that so many of us
are eschewing our holiday weekend in a scramble to get our apps ready
for the annualNMOSGM.
But if it’s any consolation,
know that Apple engineers rarely get to observe
Memorial Day—
the unofficial start of summer in America —
in the run-up to WWDC.
In law,
the latin phrasestare decisis (“to stand by things decided”)
is often used to refer to the doctrine of precedent —
the idea that,
when deciding a case,
a court should look to previous decisions made
for cases with similar facts and scenarios.
This principle serves as a foundation of the American legal system,
and the English common law from which it derives.
For example,
consider Apple v. Pepper,
which was argued before the Supreme Court of the United States
in its most recent session
and sought to settle the following question:
If Apple and its App Store constitute a monopoly,
can consumers sue Apple for offering apps at higher-than-competitive prices,
even when the prices are set by third-party developers?
In its decision,
the Court relied on precedent set in 1977
by a case known as Illinois Brick,
which itself affirmed a ruling made a decade earlier
in a case called Hanover Shoe.
On its face,
iPhones in 2010’s would seem to have little to do with bricks from the 1970’s
(aside from the obvious connotation),
but within the context ofUnited States antitrust law,
the connection between the two was inescapable.
Adherence to precedence confers inertia in the decision-making process.
It promotes stability throughout the legal system
and the institutions that rely on a consistent application of laws.
However,
like inertia,
precedence can also be overcome with sufficiently compelling reasons;
we are bound by the past only insofar as to give it due consideration.
Bearing all of that in mind,
let’s smash cut
to our subject for this week’s brief:
Apple Push Notification Device Tokens—
and in particular,
a single change in iOS 13 that may incidentally break push notifications
for thousands of apps.
A Push Notifications Primer
Push notifications allow apps to communicate with users
in response to remote events when they aren’t currently in use.
Unlike SMS or email,
which allows a sender to communicate with a recipient directly
using a unique identifier (a phone number and email address, respectively),
communication between the app’s remote server and the user’s local device
are facilitated by the Apple Push Notification service
(APNs).
Here’s how that works:
After launching an app,
the app calls the methodregisterForRemoteNotifications(),
prompting the user to grant the app permission to send push notifications.
The deviceToken parameter in the app delegate method
is an opaque Data value —
kind of like a really long unique phone number or email address —
that the app’s push notification provider uses
to route notifications through APNs to reach
this particular installation of the app.
In principle,
representing this parameter as a Data value makes a lot of sense —
the value itself is meaningless.
However, in practice,
this API design decision has been the source of untold amounts of heartache.
The Enduring Challenges of Sending Device Tokens Back to the Server
When the app delegate receives its deviceToken,
that’s not the end of the story.
In order for its to be used to send push notifications,
it needs to be sent from the client to the server.
The question is, “How”?
Before you jump to a particular answer,
consider the historical context of iOS 3 (circa 2009),
when push notifications were first introduced:
“Back in My Day…“
You could create an NSURLRequest object,
set its httpBody property to the deviceToken,
and send it using NSURLConnection,
but you’d probably also want to include some additional information —
like a username or email address —
to associate it with an account in the app.
That meant that the data you set as a request’s HTTP body
couldn’t just be the device token.
Sending an HTTP POST body withapplication/x-www-form-urlencoded
(e.g. username=jappleseed&deviceToken=____)
is one possibility for encoding multiple fields into a single payload,
but then the question becomes,“How do you encode binary data into text?”
Base64
is a great binary-to-text encoding strategy,
but NSData -base64EncodedStringWithOptions:
wouldn’t be available until iOS 7,
four years after push notifications were first introduced in iOS 3.
Without CocoaPods or a strong open-source ecosystem
to fill in the gaps,
you were left to followblog posts
describing how to roll your own implementation,
hoping that things would work as advertised.
Given the complexity in using Base64 encoding on iOS < 7,
most developers instead opted to take advantage of
what they saw as an easier, built-in alternative:
NSData, in its Own Words
Developers,
in an attempt to understand what exactly
this deviceToken parameter was,
would most likely have passed it into an NSLog statement:
NSLog(@"%@",deviceToken);// Prints "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"
Unfortunately,
for developers less versed in matters of data and encoding,
this output from NSLog may have led them astray: “Oh wow, so deviceToken is actually a string!
(I wonder why Apple was making this so difficult in the first place).
But no matter — I can take it from here.”
// ⚠️ Warning: Don't do thisNSString*token=[[[[deviceTokendescription]stringByReplacingOccurrencesOfString:@" "withString:@""]stringByReplacingOccurrencesOfString:@"<"withString:@""]stringByReplacingOccurrencesOfString:@">"withString:@""];
It’s unclear whether push notification service providers spurred this practice
by requiring Base16 / hexadecimal representations from the beginning,
or if they adopted it in response to how folks were
already accustomed to doing it,
but either way,
the practice stuck.
And for nearly a decade,
this was how a significant percentage of apps were handling
push notification device token registration.
That was until Swift 3 and iOS 10.
Relitigating the Past with Swift 3
By 2016,
Swift had stabilized and matured to the point that
most if not many developers were choosing to write new apps in Swift,
or at least write all new code in Swift for existing apps.
For those who did,
the transition to Swift 3
was most memorable for its painful migration from Swift 2.
As part of “the grand API renaming”
common Foundation types, including NSData,
dropped their NS prefix in APIs,
using a bridged, Swift value type in its place.
For the most part,
things worked as expected.
But there were a few differences in behavior —
largely undocumented or incidental behavior
that caused a breaking change.
For example,
consider the following change inapplication(_:didRegisterForRemoteNotificationsWithDeviceToken:):
// Swift 2: deviceToken is NSDatadeviceToken.description// "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"// Swift 3: deviceToken is DatadeviceToken.description// "32 bytes"
However,
many developers remained undeterred by what was seen as a minor annoyance,
and worked around the issue by recasting to NSData and its former behavior:
// ⚠️ Warning: Don't do thislettokenData=deviceTokenasNSDatalettoken=tokenData.descriptionlettoken="\(deviceToken)".replacingOccurrences(of:" ",with:"").replacingOccurrences(of:"<",with:"").replacingOccurrences(of:">",with:"")
Once again,
doing things the wrong way
managed to keep things working for another couple years.
But that’s all coming to an end with iOS 13.
Overturned in iOS 13
iOS 13 changes the format of descriptions
for Foundation objects,
including NSData:
Whereas previously,
you could coerce NSData to spill its entire contents
by converting it into a String,
it now reports its length and a truncated summary of its internal bytes.
So from now on,
if you need to convert your push notification registration deviceToken
into a Base16-encoded / hexadecimal string representation,
you should do the following:
For clarity, let’s break this down and explain each part:
The map method operates on each element of a sequence.
Because Data is a sequence of bytes in Swift,
the passed closure is evaluated for each byte in deviceToken.
The String(format:) initializer
evaluates each byte in the data
(represented by the anonymous parameter $0)
using the %02x format specifier,
to produce a zero-padded, 2-digit hexadecimal representation of
the byte / 8-bit integer.
After collecting each byte representation created by the map method,joined() concatenates each element into a single string.
Was Apple irresponsible in making this particular change?
We’d argue: No, not really.
Developers shouldn’t have relied on a specific format for
an object’s description.
Dumping an entire Data value’s contents becomes untenable at a certain point,
and this change to a more succinct summary
makes debugging larger data blobs significantly easier.
Like we said about laws at the start of this article,
precedence is a form of inertia,
not an immutable truth.
Stare decisis plays an important role
throughout software engineering.
Examples like the “Referer” [sic] header”—
even the conventions we have about
the direction of electricity flow—
demonstrate the value of sticking to decisions,
unless an opportunity arises to compel change.
Apple announced a lot at WWDC this year.
During the conference and the days that followed,
a lot of us walked around in a daze,
attempting to recover from have our minds “hashtag-mindblown’d” (#🤯).
But now a few months later,
after everything announced has launched(well, almost everything)
those very same keynote announcements now elicit far different reactions:
Although the lion’s share of attention
has been showered on the aforementioned features,
not nearly enough coverage has been given to the rest of iOS 13 —
and that’s a shame,
because this release is among the most exciting in terms of new functionality
and satisfying in terms of improving existing functionality.
So to mark last week’s release of iOS 13,
we’re taking a look at some obscure (largely undocumented) APIs
that you can now use in your apps.
We’ve scavenged the best bits out of the
iOS 13 Release NotesAPI diffs,
and now present them to you.
Here are some of our favorite things you can do starting in iOS 13:
Generate Rich Representations of URLs
New in iOS 13,
theLinkPresentation framework
provides a convenient, built-in way to replicate the
rich previews of URLs you see in Messages.
If your app has any kind of chat or messaging functionality,
you’ll definitely want to check this out.
Rich previews of URLs have a rich history
going at least as far back as the early ’00s,
with the spread of
Microformats
by semantic web pioneers,
and early precursors to Digg and Reddit
using khtml2png
to generate thumbnail images of web pages.
Fast forward to 2010,
with the rise of social media and user-generated content,
when Facebook created the OpenGraph protocol
to allow web publishers to customize how their pages looked
when posted on the Newsfeed.
These days,
most websites reliably have OpenGraph <meta> tags on their site
that provide a summary of their content for
social networks, search engines, and anywhere else that links are trafficked.
For example,
here’s what you would see if you did “View Source” for this very webpage:
<metaproperty="og:site_name"content="NSHipster"/><metaproperty="og:image"content="https://nshipster.com/logo.png"/><metaproperty="og:type"content="article"/><metaproperty="og:title"content="iOS 13"/><metaproperty="og:url"content="https://nshipster.com/ios-13/"/><metaproperty="og:description"content="To mark last week's release of iOS 13, we're taking a look at some obscure (largely undocumented) APIs that you can now use in your apps."/>
If you wanted to consume this information in your app,
you can now use the LinkPresentation framework’s LPMetadataProvider class
to fetch the metadata and optionally construct a representation:
After setting appropriate constraints
(and perhaps a call to sizeToFit()),
you’ll get the following,
which the user can tap to preview the linked webpage:
Alternatively,
if you already have the metadata in-app,
or otherwise can’t or don’t want to fetch remotely,
you can construct an LPLinkMetadata directly:
SFSpeechRecognizer
gets a major upgrade in iOS 13 —
most notably for its added support for on-device speech recognition.
Previously,
transcription required an internet connection
and was restricted to a maximum of 1-minute duration
with daily limits for requests.
But now,
you can do speech recognition completely on-device and offline,
with no limitations.
The only caveats are that
offline transcription isn’t as good as what you’d get with a server connection,
and is only available for certain languages.
To determine whether offline transcription is available for the user’s locale,
check the SFSpeechRecognizer propertysupportsOnDeviceRecognition.
At the time of publication, the list of supported languages are as follows:
English
United States (en-US)
Canada (en-CA)
Great Britain (en-GB)
India (en-IN)
Spanish
United States (es-US)
Mexico (es-MX)
Spain (es-ES)
Italian
Italy (it-IT)
Portuguese
Brazil (pt-BR)
Russian
Russia (ru-RU)
Turkish
Turkey (tr-TR)
Chinese
Mandarin (zh-cmn)
Cantonese (zh-yue)
But that’s not all for speech recognition in iOS 13!
SFSpeechRecognizer now provides information including
speaking rate and average pause duration,
as well as voice analytics features likejitter (variations in pitch) andshimmer (variations in amplitude).
Information about pitch and voicing and other features
could be used by your app
(perhaps in coordination with CoreML)
to differentiate between speakers
or determine subtext from a speaker’s inflection.
Send and Receive Web Socket Messages
Speaking of the Foundation URL Loading System,
we now have native support for
something that’s been at the top of our wish list for many years:
web sockets.
Thanks to the newURLSessionWebSocketTask class
in iOS 13,
you can now incorporate real-time communications in your app
as easily and reliably as sending HTTP requests —
all without any third-party library or framework:
leturl=URL(string:"wss://...")!letwebSocketTask=URLSession.shared.webSocketTask(with:url)webSocketTask.resume()// Send one messageletmessage:URLSessionWebSocketTask.Message=.string("Hello, world!")webSocketTask.send(message){errorin...}// Receive one messagewebSocketTask.receive{resultinguardcase let .success(message) = result else { return }...}// Eventually...webSocketTask.cancel(with:.goingAway,reason:nil)
For years now,
networking has been probably the fastest-moving part
of the whole Apple technology stack.
Each WWDC,
there’s so much to talk about that
that they routinely have to break their content across two separate sessions.
2019 was no exception,
and we highly recommend that you take some time to check out this year’s
“Advances in Networking” sessions
(Part 1,Part 2).
Do More With Maps
MapKit is another part of Apple SDKs
that’s consistently had a strong showing at WWDC year after year.
And it’s often the small touches that make the most impact in our day-to-day.
For instance,
the newMKMapView.CameraBoundary API
in iOS 13
makes it much easier to constrain a map’s viewport to a particular region
without locking it down completely.
And with the newMKPointOfInterestFilter API,
you can now customize the appearance of map views
to show only certain kinds of points of interest(whereas previously it was an all-or-nothing proposition).
letfilter=MKPointOfInterestFilter(including:[.cafe])mapView.pointOfInterestFilter=filter// only show cafés
Finally,
with MKGeoJSONDecoder,
we now have a built-in way to pull in GeoJSON shapes
from web services and other data sources.
letdecoder=MKGeoJSONDecoder()ifleturl=URL(string:"..."),letdata=try?Data(contentsOfURL:url),letgeoJSONObjects=try?decoder.decode(data){forcase let overlay as MKOverlay in geoJSONObjects {mapView.addOverlay(overlay)}}
For the uninitiated:
in JavaScript, a Promise
is an object that represents the eventual completion (or rejection)
of an asynchronous operation
and its resulting value.
Promises are a mainstay of modern JS development —
perhaps most notably within the fetch API.
On a lark,
we decided to see if there was anything new in Objective-C this year
and were surprised to find out aboutobjc_setHook_setAssociatedObject.
Again, we don’t have much to go on except the declaration,
but it looks like you can now configure a block to execute when
an associated object is set.
For anyone still deep in the guts of the Objective-C runtime,
this sounds like it could be handy.
Tame Activity Items (?)
On the subject of missing docs:UIActivityItemsConfigurationseems like a compelling option for managing
actions in the new iOS 13 share sheets,
but we don’t really know where to start…
Shame that we don’t have the information we need to take advantage of this yet.
Not to harp on about this,
but both of them are still undocumented,
so if you want to learn more,
we’d recommend checking out that article from July.
Or, if you’re in a rush
here’s a quick example demonstrating how to use both of them together:
importFoundationletrelativeDateTimeFormatter=RelativeDateTimeFormatter()relativeDateTimeFormatter.dateTimeStyle=.namedletlistFormatter=ListFormatter()listFormatter.string(from:[relativeDateTimeFormatter.localizedString(from:DateComponents(day:-1)),relativeDateTimeFormatter.localizedString(from:DateComponents(day:0)),relativeDateTimeFormatter.localizedString(from:DateComponents(day:1))])// "yesterday, today, and tomorrow"
Track the Progress of Enqueued Operations
Starting in iOS 13,OperationQueue now has aprogress property.
Granted,(NS)Progress
objects aren’t the most straightforward or convenient things to work with
(we’ve been meaning to write an article about them at some point),
but they have a complete and well-considered API,
and even have some convenient slots in app frameworks.
For example,
check out how easy it is to wire up a UIProgressView
to display the live-updating progress of an operation queue
by way of its observedProgress property:
One of the things that often differentiates category-defining apps from their competitors
is their use of background tasks
to make sure the app is fully synced and updated for the next time
it enters the foreground.
iOS 7 was the first release to providean official API for scheduling background tasks(though developers had employed various creative approaches prior to this).
But in the intervening years,
multiple factors —
from an increase in iOS app capabilities and complexity
to growing emphasis in performance, efficiency, and privacy for apps —
have created a need for a more comprehensive solution.
As described in this year’s WWDC session
“Advances in App Background Execution”,
the framework distinguishes between two broad classes of background tasks:
app refresh tasks:
short-lived tasks that keep an app up-to-date throughout the day
background processing tasks:
long-lived tasks for performing deferrable maintenance tasks
The WWDC session and the accompanying sample code project
do a great job of explaining how to incorporate both of these
into your app.
But if you want the quick gist of it,
here’s a small example of an app that schedules periodic refreshes
from a web server:
Annotate Text Content Types for Better Accessibility
You know how frustrating it is to hear some people read out URLs?
(“eɪʧ ti ti pi ˈkoʊlən slæʃ slæʃ ˈdʌbəlju ˈdʌbəlju ˈdʌbəlju dɑt”…)
That’s what it can be like when
VoiceOver
tries to read something without knowing more about what it’s reading.
iOS 13 promises to improve the situation considerably
with the new accessibilityTextualContext property
and UIAccessibilityTextAttributeContextNSAttributedString attribute key.
Whenever possible,
be sure to annotate views and attributed strings with
the constant that best describes the kind of text being displayed:
UIAccessibilityTextualContextConsole
UIAccessibilityTextualContextFileSystem
UIAccessibilityTextualContextMessaging
UIAccessibilityTextualContextNarrative
UIAccessibilityTextualContextSourceCode
UIAccessibilityTextualContextSpreadsheet
UIAccessibilityTextualContextWordProcessing
Remove Implicitly Unwrapped Optionals from View Controllers Initialized from Storyboards
SwiftUI may have signaled the eventual end of Storyboards,
but that doesn’t mean that things aren’t and won’t continue to get better
until if / when that day comes.
One of the most irritating anti-patterns for Swift purists
when working on iOS projects with Storyboards
has been view controller initialization.
Due to an impedance mismatch between
Interface Builder’s “prepare for segues” approach and
Swift’s object initialization rules,
we frequently had to choose between
making all of our properties non-private, variable, and (implicitly unwrapped) optionals,
or foregoing Storyboards entirely.
Xcode 11 and iOS 13 allow these paradigms to reconcile their differences
by way of the new @IBSegueAction attribute
and some new UIStoryboard class methods:
First,
the @IBSegueAction attribute
can be applied view controller method declarations
to designate itself as the API responsible for
creating a segue’s destination view controller
(i.e. the destinationViewController property of the segue parameter
in the prepare(for:sender:) method).
importUIKitstructPerson{...}classProfileViewController:UIViewController{letname:StringletavatarImageURL:URL?init?(coder:NSCoder,name:String,avatarImageURL:URL?){self.name=nameself.avatarImageURL=avatarImageURLsuper.init(coder:coder)}requiredinit?(coder:NSCoder){fatalError("init(coder:) has not been implemented")}}letstoryboard=UIStoryboard(name:"ProfileViewController",bundle:nil)storyboard.instantiateInitialViewController(creator:{decoderinProfileViewController(coder:decoder,name:"Johnny Appleseed",avatarImageURL:nil)})
Together with the new UIKit Scene APIs,
iOS 13 gives us a lot to work with
as we wait for SwiftUI to mature and stabilize.
That does it for our round-up of iOS 13 features
that you may have missed.
But rest assured —
we’re planning to cover many more new APIs in future NSHipster articles.
If there’s anything we missed that you’d like for us to cover,
please @ us on Twitter!
Working on a large iOS codebase often involves a lot of waiting:
Waiting for Xcode to index your files,
waiting for Swift and Objective-C code to compile,
waiting for the Simulator to boot and your app to launch…
And after all of that,
you spend even more time getting your app
into a particular state and onto a particular screen,
just to see whether the Auto Layout constraint you just added
fixes that regression you found.
It didn’t, of course,
so you jump back into Xcode,
tweak the Content Hugging Priority,
hit ⌘R,
and start the whole process again.
We might relate our sorry predicament tothat one xkcd comic,
but for those of us who don’t so much relish in
the stop-and-go nature of app development,
there’s an old Yiddish joke about Shlemiel the painter
(provided below with a few -specific modifications;
for the uninitiated,
please refer to Joel Spolsky’s
original telling):
Shlemiel gets a job as a software developer,
implementing a new iOS app.
On the first sprint he opens Xcode
and implements 10 new screens of the app.“That’s pretty good!” says his manager,“you’re a fast worker!” and pays him a Bitcoin.
The next sprint Shlemiel only gets 5 screens done.“Well, that’s not nearly as good as yesterday,
but you’re still a fast worker. 5 screens is respectable,”
and pays him a Bitcoin.
The next sprint Shlemiel implements 1 screen.“Only 1!” shouts his manager.“That’s unacceptable!
On the first day you did ten times that much work!
What’s going on?”
“I can’t help it,” says Shlemiel.“Each sprint I get further and further away from
application(_:didFinishLaunchingWithOptions:)!”
Over the years,
there have been some developments that’ve helped things slightly,
including
@IBInspectable and @IBDesignable
and Xcode Playgrounds.
But with Xcode 11,
our wait is finally over —
and it’s all thanks to SwiftUI.
Although many of us have taken a “wait and see” approach to SwiftUI,
we can start using its capabilities today
to radically speed up and improve our development process —
without changing a line of code in our UIKit apps.
Consider a subclass of UIButton
that draws a border around itself:
Normally,
if we wanted to test how our UI element performs,
we’d have to add it to a view in our app,
build and run,
and navigate to that screen.
But with Xcode 11,
we can now see a preview side-by-side with the code editor
by adding the following under the original declaration of BorderedButton:
Using a new feature called dynamic replacement,
Xcode can update this preview without recompiling —
within moments of your making a code change.
This lets you rapidly prototype changes like never before.
Want to see how your button handles long titles?
Bang away on your keyboard within the call to setTitle(_:for:)
in your preview,
and test out potential fixes in your underlying implementation
without so much as leaving your current file!
Previewing Multiple States
Let’s say our app had a FavoriteButton—
a distant cousin (perhaps by composition) to BorderedButton.
In its default state,
it shows has the title “Favorite”
and displays a ♡ icon.
When its isFavorited property is set to true,
the title is set to “Unfavorite”
and displays a ♡̸ icon.
We can preview both at once
by wrapping two UIViewPreview instances within a single SwiftUI Group:
With Dark Mode in iOS 13,
it’s always a good idea to double-check that your custom views
are configured with dynamic colors
or accommodate both light and dark appearance in some other way.
An easy way to do this
would be to use a ForEach element
to render a preview for each case in the ColorScheme enumeration:
Xcode Previews are especially time-saving when it comes to
localizing an app into multiple languages.
Compared to the hassle of configuring Simulator
back and forth between different languages and regions,
this new approach makes a world of difference.
Let’s say that, in addition to English,
your app supported various right-to-left languages.
You could verify that yourRTL logic worked as expected like so:
letsupportedLocales:[Locale]=["en-US",// English (United States)"ar-QA",// Arabid (Qatar)"he-IL",// Hebrew (Israel)"ur-IN"// Urdu (India)].map(Locale.init(identifier:))funclocalizedString(_key:String,forlocale:Locale)->String?{...}returnForEach(supportedLocales,id:\.identifier){localeinUIViewPreview{letbutton=BorderedButton(frame:.zero)button.setTitle(localizedString("Subscribe",for:locale),for:.normal)button.setImage(UIImage(systemName:"plus"),for:.normal)button.setTitleColor(.systemOrange,for:.normal)button.tintColor=.systemOrangereturnbutton}.environment(\.locale,locale).previewDisplayName(Locale.current.localizedString(forIdentifier:locale.identifier))}.previewLayout(.sizeThatFits).padding(10)
#if canImport(SwiftUI) && DEBUGimportSwiftUIletdeviceNames:[String]=["iPhone SE","iPad 11 Pro Max","iPad Pro (11-inch)"]@available(iOS 13.0, *)structViewController_Preview:PreviewProvider{staticvarpreviews:someView{ForEach(deviceNames,id:\.self){deviceNameinUIViewControllerPreview{UIStoryboard(name:"Main",bundle:nil).instantiateInitialViewController{coderinViewController(coder:coder)}!}.previewDevice(PreviewDevice(rawValue:deviceName)).previewDisplayName(deviceName)}}}#endif
Although most of us are still some years away from shipping SwiftUI in our apps
(whether by choice or necessity),
we can all immediately benefit from the order-of-magnitude improvement
it enables with Xcode 11 on macOS Catalina.
By eliminating so much time spent waiting for things to happen,
we not only get (literally) hours more time each week,
but we unlock the possibility of maintaining an unbroken flow state during that time.
Not only that,
but the convenience of integrated tests
fundamentally changes the calculus for testing:
instead of being a rare “nice to have,”
they’re the new default.
Plus:
these inline previews serve as living documentation
that can help teams both large and small
finally get a handle on their design system.
It’s hard to overstate how much of a game-changer Xcode Previews are for iOS development,
and we couldn’t be happier to incorporate them into our workflow.
As an undergraduate student,
I had a radio show called“Goodbye, Blue Monday”
(I was really into Vonnegut at the time).
It was nothing glamorous —
just a weekly, 2-hour slot at the end of the night
before the station switched into automation.
If you happened to be driving through the hills of Pittsburgh, Pennsylvania
late at night with your radio tuned toWRCT 88.3,
you’d have heard an eclectic mix of
Contemporary Classical,Acid Jazz,Italian Disco, andBebop.
That, and the stilting, dulcet baritone of
a college kid doing his best impersonation ofTony Mowod.
Sitting there in the booth,
waiting for tracks to play out before launching into anFCC-mandatedPSA
or on-the-hourstation identification,
I’d wonder:
Is anyone out there listening?And if they were, did they like it?
I could’ve been broadcasting static the whole time and been none the wiser.
The same thoughts come to mind whenever I submit a build to App Store Connect…
but then I’ll remember that, unlike radio,
you can actually know these things!
And the latest improvements in Xcode 11 make it easier than ever
to get an idea of how your apps are performing in the field.
We’ll cover everything you need to know in this week’s NSHipster article.
So as they say on the radio:
“Don’t touch that dial (it’s got jam on it)”.
MetricKit is a new framework in iOS 13
for collecting and processing battery and performance metrics.
It was announced at WWDC this year
along with XCTest Metrics and the Xcode Metrics Organizer
as part of a coordinated effort to bring new insights to developers
about how their apps are performing in the field.
Apple automatically collects metrics from apps installed on the App Store.
You can view them in Xcode 11
by opening the Organizer (⌥⌘⇧O)
and selecting the new Metrics tab.
MetricKit complement Xcode Organizer Metrics by providing a programmatic way to
receive daily information about how your app is performing in the field.
With this information,
you can collect, aggregate, and analyze on your own in greater detail
than you can through Xcode.
Understanding App Metrics
Metrics can help uncover issues you might not have seen while testing locally,
and allow you to track changes across different versions of your app.
For this initial release,
Apple has focused on the two metrics that matter most to users:battery usage and performance.
Battery Usage
Battery life depends on a lot of different factors.
Physical aspects like the age of the device and
the number of charge cycles are determinative,
but the way your phone is used matters, too.
Things like CPU usage,
the brightness of the display and the colors on the screen,
and how often radios are used to fetch data or get your current location —
all of these can have a big impact.
But the main thing to keep in mind is that
users care a lot about battery life.
Aside from how good the camera is,
the amount of time between charges
is the deciding factor when someone buys a new phone these days.
So when their new, expensive phone doesn’t make it through the day,
they’re going to be pretty unhappy.
Until recently,
Apple’s taken most of the heat on battery issues.
But since iOS 12 and its new
Battery Usage screen in Settings,
users now have a way to tell when their favorite app is to blame.
Fortunately,
with iOS 13 you now have everything you need to make sure
your app doesn’t run afoul of reasonable energy usage.
Performance
Performance is another key factor in the overall user experience.
Normally, we might look to stats like
processor clock speed or frame rate
as a measure of performance.
But instead,
Apple’s focusing on less abstract and more actionable metrics:
Hang Rate
How often is the main / UI thread blocked,
such that the app is unresponsive to user input?
Launch Time
How long does an app take to become usable after the user taps its icon?
Peak Memory & Memory at Suspension
How much memory does the app use at its peak
and just before entering the background?
Disk Writes
How often does the app write to disk,
which — if you didn’t already know — is a
comparatively slow operation(even with the flash storage on an iPhone!)
Using MetricKit
From the perspective of an API consumer,
it’s hard to imagine how MetricKit could be easier to incorporate.
All you need is for some part of your app to serve as
a metric subscriber
(an obvious choice is your AppDelegate),
and for it to be added to the shared MXMetricManager:
iOS automatically collects samples while your app is being used,
and once per day (every 24 hours),
it’ll send an aggregated report with those metrics.
To verify that your MXMetricManagerSubscriber
is having its delegate method called as expected,
select Simulate MetricKit Payloads from the Debug menu
while Xcode is running your app.
Annotating Critical Code Sections with Signposts
In addition to the baseline statistics collected for you,
you can use themxSignpost function
to collect metrics around the most important parts of your code.
This signpost-backed API
captures CPU time, memory, and writes to disk.
For example,
if part of your app did post-processing on audio streams,
you might annotate those regions with metric signposts
to determine the energy and performance impact of that work:
Creating a Self-Hosted Web Service for Collecting App Metrics
Now that you have this information,
what do you do with it?
How do we fill that ... placeholder in our implementation of didReceive(_:)?
You could pass that along to some paid analytics or crash reporting service,but where’s the fun in that?
Let’s build our own web service to collect these for further analysis:
Storing and Querying Metrics with PostgreSQL
The MXMetricPayload objects received by metrics manager subscribers
have a convenientjsonRepresentation() method
that generates something like this:
As you can see,
there’s a lot baked into this representation.
Defining a schema for all of this information would be a lot of work,
and there’s no guarantee that this won’t change in the future.
So instead,
let’s embrace the NoSQL paradigm
(albeit responsibly, using Postgres)
by storing payloads in a JSONB column:
We can extract individual fields from payloads
using JSON operators
like so:
SELECT(payload->'applicationTimeMetrics'->>'cumulativeForegroundTime')::INTERVALFROMmetrics;-- interval-- ═══════════════════-- @ 11 mins 40 secs-- (1 row)
Advanced: Creating Views
JSON operators in PostgreSQL can be cumbersome to work with —
especially for more complex queries.
One way to help with that is to create a view
(materialized or otherwise)
to project the most important information to you
in the most convenient representation:
In this example,
most of the heavy lifting is delegated to Postgres,
making the server-side implementation rather boring.
For completeness,
here are some reference implementations in
Ruby (Sinatra) and JavaScript (Express):
importexpressfrom'express';import{Pool}from'pg';constdb=newPool(connectionString:process.env.DATABASE_URL,ssl:process.env.NODE_ENV==='production');constapp=express();app.post('/collect',(request,response)=>{db.query('INSERT INTO metrics (payload) VALUES ($1)',[request.body],(error,results)=>{if(error){throwerror;}response.status(204);})});app.listen(process.env.PORT||5000)
Sending Metrics as JSON
Now that we have everything set up,
the final step is to implement
the required MXMetricManagerSubscriber delegate method didReceive(_:)
to pass that information along to our web service:
Metrics offer a convenient way to at least make sure that
things aren’t too slow or too draining.
And though they provide but a glimpse in the aggregate
of how our apps are being enjoyed,
it’s just enough to help us honor both our creation and our audience
with a great user experience.
For every era,
there’s a monster that embodies the anxieties of the age.
At the dawn of the Holocene,
our ancestors traced the contours of shadows cast by the campfire
as they kept watch over the darkness.
Once we learned to read the night sky for navigation,
sailors swapped stories of sea creatures likeLeviathan andSiren
to describe the dangers of open ocean
(and the perils to be found on unfamiliar shores).
More recently,
the “monster ruins a beach party”
trope of the 1960s
arose from concerns of teenager morality.
While theMartians
who invaded those same drive-in double features
served as a proxy for Cold War fears at the height of theSpace Race.
All of which begs the question:“What monster best exemplifies our present age?”
Consider the unnamed monster from the filmIt Follows:
a formless, supernatural being that relentlessly pursues its victims
anywhere on the planet.
Sounds a bit like the state ofad tech
in 2019, no?
This week on NSHipster —
in celebration of our favorite holiday
🎃—
we’re taking a look at the myriad ways that
you’re being tracked on iOS,
both sanctioned and unsanctioned,
historically and presently.
So gather around the campfire,
and allow us to trace the contours of the unseen, formless monsters
that stalk us under cover of Dark Mode.
The Cynicism of Marketing and Advertising Technology
Contrary to our intuitions about natural selection in the marketplace,
history is littered with examples of
inferior-but-better-marketed products winning out over superior alternatives:VHS vs. Betamax,Windows vs. Macintosh,
etc.
(According to the common wisdom of business folks, at least.)
Regardless,
most companies reach a point where“if you build it, they will come”
ceases to be a politically viable strategy,
and someone authorizes a marketing budget.
Marketers are tasked with growing market share
by identifying and communicating with as many potential customers as possible.
And many —
either out of a genuine belief or formulated as a post hoc rationalization —
take the potential benefit of their product
as a license to flouting long-established customs of personal privacy.
So they enlist the help of one or more
advertising firms,
who promise to maximize their allocated budget and
provide some accountability for their spending
by using technology to
target,deliver, andanalyze
messaging to consumers.
Each of these tasks is predicated on a consistent identity,
which is why advertisers go to such great lengths to track you.
Without knowing who you are,
marketers have no way to tell if you’re a likely or even potential customer.
Without knowing where you are,
marketers have no way to reach you
other than to post ads where they’re likely to be seen.
Without knowing what you do,
marketers have no way to tell if their ads worked
or were seen at all.
Apple-Sanctioned Identifiers
Apple’s provided various APIS to facilitate user identification
for various purposes:
Universal Identifiers (UDID)
In the early days of iOS,
Apple provided a uniqueIdentifier property on UIDevice—
affectionately referred to as a
UDID
(not to be confused with a UUID).
Although such functionality seems unthinkable today,
that property existed until iOS 5,
until it was
deprecated and replaced by identifierForVendor in iOS 6.
Vendor Identifiers (IDFV)
Starting in iOS 6,
developers can use theidentifierForVendor property on UIDevice
to generate a unique identifier that’s shared across apps and extensions
created by the same vendor
(IDFV).
Along with identifierForVendor came the introduction of a newAdSupport framework,
which Apple created to help distinguish
identification necessary for app functionality
from anything in the service of advertising.
The resultingadvertisingidentifier property
(affectionately referred to asIDFA by its associates)
differs from identifierForVendor
by returning the same value for everyone.
The value can change, for example,
if the user resets their Advertising Identifier
or erases their device.
If advertising tracking is limited,
the property returns a zeroed-out UUID instead.
idfa.uuidString=="00000000-0000-0000-0000-000000000000"// true if the user has limited ad tracking
DeviceCheck
identifierForVendor and advertisingIdentifier
provide all the same functionality as the uniqueIdentifier property
they replaced in iOS 6,
save for one:
the ability to persist across device resets and app uninstalls.
In iOS 11,
Apple quietly introduced theDeviceCheck framework,
which allows developers to assign two bits of information
that are persisted by Appleuntil the developer manually removes them.
Interacting with the DeviceCheck framework should be familiar to
anyone familiar with APNS:
after setting things up on App Store Connect and your servers,
the client generates tokens on the device,
which are sent to your servers to set or query two bits of information:
importDeviceCheckletdevice=DCDevice.currentifdevice.isSupported{device.generateToken{data,erroriniflettoken=data?.base64EncodedString(){send token to your server}}}
Based on the device token and other information sent by the client,
the server tells Apple to set each bit value
by sending a JSON payload like this:
Despite these affordances by Apple,
advertisers have continued to work to circumvent user privacy protections
and use any and all information at their disposal
to identify users by other means.
Over the years,
Apple’s restricted access to information about
device hardware,
installed apps,nearby WiFi networks.
They’ve required apps to request permission to
get your current location,
access your camera and microphone,
flip through your contacts, and
find and connect to Bluetooth accessories.
They’ve taken bold steps to prevent user tracking in Safari.
For lack of this information,
companies have had to get creative,
looking to forge unique identities from the scraps of what’s still available.
This process of identification by a combination of external factors
is known as fingerprinting.
The unfortunate reality is that we can be uniquely identified
by vanishingly small amounts of information.
For example,
individuals within a population can be singled out by as few as
four timestamped coordinates(de Montjoye, Hidalgo, Verleysen, & Blondel, 2013)
or little more than a birthday and a ZIP code(Sweeney, 2000).
Every WWDC since 2012 has featured a session about Privacy,
but the only mention of fingerprinting specifically wasa brief discussion in 2014
about how to avoid doing it.
By our count,
a determined party could use conventional, unrestricted APIs
to generate a few dozen bits of randomness:
Locale Information (~36 bits)
Locale information is the greatest source of identifying information
on Apple platforms.
The combination of your
preferred languages, region, calendar, time zone,
and which keyboards you have installed
say a lot about who you are —
especially if you have less conventional preferences.
Of the approximately ~25% of users who take advantage ofDynamic Type
by configuring a preferred font size,
that selection may also be used to fingerprint you:
Although most of the juiciest bits have been locked down
in OS updates over the years,
there’s just enough to contribute a few more bits for purposes of identification.
On iOS,
you can get the current model and amount of storage of a user’s device:
Knowing whether someone’s phone is on Verizon or Vodaphone
can also be factored into a fingerprint.
You can use the CTTelephonyNetworkInfo class from theCoreTelephony framework
to lookup the providers for devices with cellular service:
The number of providers varies per country,
but using the 4 major carriers in United States
as a guideline,
we can say carrier information would contribute about 2 bits
(or more if you have multiple SIM cards installed).
Communication Preferences (2 bits)
More generally,
even knowing whether someone can send texts or email at all
can be factored into a fingerprint.
This information can be gathered without permissions via
the MessageUI framework.
If the use of digital fingerprinting seems outlandish,
that’s just scratching the surface of how companies and researchers
have figured out how to circumvent your privacy.
GeoIP and Relative Network Speeds
Although access to geolocation through conventional APIs
requires explicit authorization,
third parties may be able to get a general sense of where you are in the world
based on how you access the Internet.
ping -c 5 99.24.18.13 # San Francisco, USA
--- 99.24.18.13 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 11.900/12.184/12.895/0.380 msping -c 5 203.47.10.37 # Adelaide, Australia
--- 203.47.10.37 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 202.122/202.433/203.436/0.504 ms
Battery Health
It’s unclear whether this is a concern in iOS,
but depending on how precise the results of UIDevice battery APIs are,
you may be able to use them to identify a device by its battery health.(Olejnik, Acar, Castelluccia, & Diaz, 2016)
Much as we may bemoan the current duopoly of mobile operating systems,
we might take some solace in the fact that
at least one of the players actually cares about user privacy.
Though it’s unclear whether that’s a fight that can ever be won.
At times,
our fate of being tracked and advertised to
may seem as inevitable as the victims in It Follows.
But let’s not forget that,
as technologists, as people with a voice,
we’re in a position to fight back.
Last week,
we concluded our discussion of device identifiers
with a brief foray into the ways apps usedevice fingerprinting
to work around Apple’s provided APIs
to track users without their consent or awareness.
In response,
a few readers got in touch to explain why
their use of fingerprinting
to bridge between Safari and their native app was justified.
At WWDC 2018.
Apple announced that
starting in iOS 11 apps would no longer have access to a shared cookie store.
Previously,
if a user was logged into a website in Safari on iOS
and installed the native app,
the app could retrieve the session cookie from an SFSafariViewController
to log the user in automatically.
The change was implemented as a countermeasure against
user tracking by advertisers and other third parties,
but came at the expense of certain onboarding flows used at the time.
In this week’s article,
we’ll endeavor to answer one such use case, specifically: How to do seamless “passwordless” authentication via email on iOS.
Mail and Calendar Integrations on Apple Platforms
When you view an email on macOS and iOS,
Mail underlinesdetected dates and times.
You can interact with them to create a new calendar event.
If you open such an event in Calendar,
you’ll see a “Show in Mail” link in its extended details.
Clicking on this link takes you back to the original email message.
This functionality goes all the way back to the launch of the iPhone;
its inclusion in that year’s
Mac OS X release (Leopard)
would mark the first of many mobile features
that would make their way to the desktop.
If you were to copy this “magic” URL to the pasteboard
and view in a text editor,
you’d see something like this:
Veteran iOS developers will immediately recognize this to use acustom URL scheme.
And the web-savvy among them could percent-decode the host
and recognize it to be something akin to an email address, but not.
So if not an email address, what are we looking at here?
It’s a different email field known as a Message-ID.
Message-ID
RFC 5322 §3.6.4
prescribes that every email message SHOULD
have a “Message-ID:” field
containing a single unique message identifier.
The syntax for this identifier is essentially an email address
with enclosing angle brackets (<>).
Although the specification contains no normative guidance
for what makes for a good Message-ID,
there’s a
draft IETF document
from 1998 that holds up quite well.
Let’s take a look at how to do this in Swift:
Generating a Random Message ID
The first technique described in the aforementioned document
involves generating a random Message ID with a 64-bit nonce,
which is prepended by a timestamp to further reduce the chance of collision.
We can do this rather easily
using the random number generator APIs built-in to Swift 5
and theString(_:radix:uppercase:) initializer:
We could then save the generated Message-ID with the associated record
in order to link to it later.
However,
in many cases,
a simpler alternative would be to make the Message ID deterministic,
computable from its existing state.
Generating a Deterministic Message ID
Consider a record structure that conforms toIdentifiable protocol
and whose associated ID type is aUUID.
You could generate a Message ID like so:
The stock Mail client on both iOS and macOS
will attempt to open URLs with the custom message: scheme
by launching to the foreground
and attempting to open the message
with the encoded Message-ID field.
Generating a Mail Deep Link with Message ID
With a Message-ID in hand,
the final task is to create a deep link that you can use to
open Mail to the associated message.
The only trick here is topercent-encode
the Message ID in the URL.
You could do this with theaddingPercentEncoding(withAllowedCharacters:) method,
but we prefer to delegate this all to URLComponents instead —
which has the further advantage of being able to
construct the URL full without a
format string.
If you open a message: URL on iOS
and the linked message is readily accessible from the INBOX
of one of your accounts,
Mail will launch immediately to that message.
If the message isn’t found,
the app will launch and asynchronously load the message in the background,
opening it once it’s available.
As an example,Flight School
does this with passwordless authentication system.
To access electronic copies of your books,
you enter the email address you used to purchase them.
Upon form submission,
users on iOS will see a deep link to open the Mail app
to the email containing the “magic sign-in link” ✨.
Unlike so many private integrations on Apple platforms,
which remain the exclusive territory of first-party apps,
the secret sauce of “Show in Mail” is something we can all get in on.
Although undocumented,
the feature is unlikely to be removed anytime soon
due to its deep system integration and roots in fundamental web standards.
At a time when everyone frombrowser vendors andsocial media companies togovernments— and even
Apple itself, at times —
seek to dismantle the open web and control what we can see and do,
it’s comforting to know that email,
nearly 50 years on,
remains resolute in its capacity to keep the Internet free and decentralized.