Quantcast
Channel: NSHipster
Viewing all 71 articles
Browse latest View live

UIKeyCommand

$
0
0

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():

overridefunccanBecomeFirstResponder()->Bool{returntrue}
-(BOOL)canBecomeFirstResponder{returnYES;}

Next, provide a list of available key commands via the keyCommands property:

overridevarkeyCommands:[UIKeyCommand]?{return[UIKeyCommand(input:"1",modifierFlags:.Command,action:"selectTab:",discoverabilityTitle:"Types"),UIKeyCommand(input:"2",modifierFlags:.Command,action:"selectTab:",discoverabilityTitle:"Protocols"),UIKeyCommand(input:"3",modifierFlags:.Command,action:"selectTab:",discoverabilityTitle:"Functions"),UIKeyCommand(input:"4",modifierFlags:.Command,action:"selectTab:",discoverabilityTitle:"Operators"),UIKeyCommand(input:"f",modifierFlags:[.Command,.Alternate],action:"search:",discoverabilityTitle:"Find…"),]}// ...funcselectTab(sender:UIKeyCommand){letselectedTab=sender.input// ...}
-(NSArray<UIKeyCommand*>*)keyCommands{return@[[UIKeyCommandkeyCommandWithInput:@"1"modifierFlags:UIKeyModifierCommandaction:@selector(selectTab:)discoverabilityTitle:@"Types"],[UIKeyCommandkeyCommandWithInput:@"2"modifierFlags:UIKeyModifierCommandaction:@selector(selectTab:)discoverabilityTitle:@"Protocols"],[UIKeyCommandkeyCommandWithInput:@"3"modifierFlags:UIKeyModifierCommandaction:@selector(selectTab:)discoverabilityTitle:@"Functions"],[UIKeyCommandkeyCommandWithInput:@"4"modifierFlags:UIKeyModifierCommandaction:@selector(selectTab:)discoverabilityTitle:@"Operators"],[UIKeyCommandkeyCommandWithInput:@"f"modifierFlags:UIKeyModifierCommand|UIKeyModifierAlternateaction:@selector(search:)discoverabilityTitle:@"Find…"]];}// ...-(void)selectTab:(UIKeyCommand*)sender{NSString*selectedTab=sender.input;// ...}

In the Discoverability layover, accessed by holding down the Command key, key commands are listed in the order you specified:

Discoverability Layover

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:

letglobalKeyCommands=[UIKeyCommand(input:...),...]letloggedInUserKeyCommands=[UIKeyCommand(input:...),...]overridevarkeyCommands:[UIKeyCommand]?{ifisLoggedInUser(){returnglobalKeyCommands+loggedInUserKeyCommands}else{returnglobalKeyCommands}}

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.


guard & defer

$
0
0

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

Edsger W. Dijkstra, “Go To Considered Harmful”

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.

Error prone? Yes. Frustratingly repetitive? Check.

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:

postfixfunc++(inoutx:Int)->Int{letcurrent=xx+=1returncurrent}

In this case, defer offers a clever alternative. Why create a temporary variable when we can just defer the increment?

postfixfunc++(inoutx:Int)->Int{defer{x+=1}returnx}

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.

Reader Submissions - New Year's 2016

$
0
0

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.

Many thanks toCédric Luthi,Josip Ćavar,Juraj Hilje,Kyle Van Essen,Luo Jie,Mathew Huusko V,Nicolás Jakubowski,Nolan O'Brien,Orta Therox,Ray Fix,Stephen Celis,Taylor Franklin,Ursu Dan,Matthew Flint,@biggercoffee, and @vlat456 for their contributions!


Swift’s defer in Objective-C

From Nolan O'Brien:

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!


Swift Protocol Extensions

From Juraj Hilje:

Keep inheritance trees shallow and use protocol composition:

protocolHello{funcsayHello()->String}extensionHello{funcsayHello()->String{return"Hello, stranger"}}classMyClass:Hello{}letc=MyClass()c.sayHello()// "Hello, stranger"

Public Read-only Variables

From Stephen Celis:

Classes commonly have public, read-only properties but need the ability privately modify them. I’ve come across the following pattern a few times:

publicclassPerson{publicvarname:String{return_name}privatevar_name:String// ...}

Luckily, there’s a better, oft-overlooked way that avoids the extra variable:

publicclassPerson{publicprivate(set)varname:String// ...}

Swift where Everywhere

From Taylor Franklin:

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:

ifletsegueID=segue.identifierwheresegueID=="mySegue"{...}

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.


Improved Optional Binding

From Ursu Dan:

The improved optional binding in Swift is amazing and I use it virtually everywhere now and avoid the pyramid of doom:

ifleturl=NSBundle.mainBundle().URLForResource("users",withExtension:"json"),data=NSData(contentsOfURL:url),deserialized=try?NSJSONSerialization.JSONObjectWithData(data,options:[]),userList=deserializedas?[[String:AnyObject]]{foruserDictinuserList{ifletid=userDict["id"]as?Int,name=userDict["name"]as?String,username=userDict["username"]as?String,email=userDict["email"]as?String,phone=userDict["phone"]as?String,address=userDict["address"]as?[String:AnyObject]{users.append(User(id:id,name:name,...))}}}

Unbuffered xcodebuild Output

From Cédric Luthi:

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

env NSUnbufferedIO=YES xcodebuild [flags]| xcpretty

Multiline Labels in a Table View

From Ray Fix:

Using autolayout to toggle a label in a table view from one line to many:

tableView.beginUpdates()label.numberOfLines=label.numberOfLines==0?1:0tableView.endUpdates()

You can see Ray’s technique in action in an example project:

Multiline demo


AmIRunningAsAnExtension

Another from Nolan O'Brien:

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

letNOBAmIRunningAsAnExtension:Bool={letextensionDictionary:AnyObject?=NSBundle.mainBundle().infoDictionary?["NSExtension"]returnextensionDictionary?.isKindOfClass(NSDictionary.self)??false}()
FOUNDATION_EXTERNBOOLAmIRunningAsAnExtension()__attribute__((const));BOOLNOBAmIRunningAsAnExtension(){staticBOOLsIsExtension;staticdispatch_once_tsOnceToken;dispatch_once(&sOnceToken,^{NSDictionary*extensionDictionary=[[NSBundlemainBundle]infoDictionary][@"NSExtension"];sIsExtension=[extensionDictionaryisKindOfClass:[NSDictionaryclass]];});returnsIsExtension;}

That frees you to do things like this:

-(void)startBackgroundTask{#if TARGET_OS_IPHONEif(!NOBAmIRunningAsAnExtension()){ClassUIApplicationClass=NSClassFromString(@"UIApplication");idsharedApplication=[UIApplicationClasssharedApplication];self.backgroundTaskIdentifier=[sharedApplicationbeginBackgroundTaskWithExpirationHandler:^{if(self.backgroundTaskIdentifier!=UIBackgroundTaskInvalid){[sharedApplicationendBackgroundTask:self.backgroundTaskIdentifier];self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;}}];}#endif}-(void)endBackgroundTask{#if TARGET_OS_IPHONEif(self.backgroundTaskIdentifier!=UIBackgroundTaskInvalid){NSAssert(!NOBAmIRunningAsAnExtension());ClassUIApplicationClass=NSClassFromString(@"UIApplication");idsharedApplication=[UIApplicationClasssharedApplication];[sharedApplicationendBackgroundTask:self.backgroundTaskIdentifier];self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;}#endif}

Beyond Breakpoints

From Matthew Flint:

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:

Breakpoint Options Popup

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


Fix Console po frame Printing

GitHub user @biggercoffee reminds us that po frame printing fails in the LLDB console by default:

Broken po frame

Fix it mid-debugging session with expr @import UIKit, or fix it once and for all by adding a couple lines to your “.lldbinit”. From the command line:

touch ~/.lldbinitecho display @import UIKit >> ~/.lldbinitecho target stop-hook add -o \"target stop-hook disable\">> ~/.lldbinit

Fixed po frame


Avoiding -DDEBUG in Swift

From GitHub user @vlat456:

For those, who like me, are trying to avoid the mess with -DDEBUG in Swift, but have to know which version of executable is running, Debug or Release.

// PreProcessorMacros.m:#include "PreProcessorMacros.h"#ifdef DEBUGBOOLconstDEBUG_BUILD=YES;#elseBOOLconstDEBUG_BUILD=NO;#endif// PreProcessorMacros.h:#ifndef PreProcessorMacros_h#define PreProcessorMacros_h#includeexternBOOLconstDEBUG_BUILD;#endif /* PreProcessorMacros_h */// in Bridged header:#import "PreProcessorMacros.h"

And then from Swift:

ifDEBUG_BUILD{debugPrint("It's Debug build")}else{debugPrint("It's Release build")}

Checking For Null Blocks

From Nicolás Jakubowski:

This macro for checking block nullability before executing them:

#define BLOCK_EXEC(block, ...) if (block) { block(__VA_ARGS__); };

Old and busted:

if(completionBlock){completionBlock(arg1,arg2);}

New and shiny:

BLOCK_EXEC(completionBlock,arg1,arg2);

Swiftier GCD

From Luo Jie:

You can use enums and protocol extensions to provide a GCD convenience API:

protocolExcutableQueue{varqueue:dispatch_queue_t{get}}extensionExcutableQueue{funcexecute(closure:()->Void){dispatch_async(queue,closure)}}enumQueue:ExcutableQueue{caseMaincaseUserInteractivecaseUserInitiatedcaseUtilitycaseBackgroundvarqueue:dispatch_queue_t{switchself{case.Main:returndispatch_get_main_queue()case.UserInteractive:returndispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0)case.UserInitiated:returndispatch_get_global_queue(QOS_CLASS_USER_INITIATED,0)case.Utility:returndispatch_get_global_queue(QOS_CLASS_UTILITY,0)case.Background:returndispatch_get_global_queue(QOS_CLASS_BACKGROUND,0)}}}enumSerialQueue:String,ExcutableQueue{caseDownLoadImage="myApp.SerialQueue.DownLoadImage"caseUpLoadFile="myApp.SerialQueue.UpLoadFile"varqueue:dispatch_queue_t{returndispatch_queue_create(rawValue,DISPATCH_QUEUE_SERIAL)}}

Downloading something then could be written like this:

Queue.UserInitiated.execute{leturl=NSURL(string:"http://image.jpg")!letdata=NSData(contentsOfURL:url)!letimage=UIImage(data:data)Queue.Main.execute{imageView.image=image}}

_ObjectiveCBridgeable

From Mathew Huusko V:

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:

publicprotocolBackedObjectType:AnyObject{typealiasBackingvarbackingObject:Backing{get}init(_backingObject:Backing)}publicprotocolObjectBackable:_ObjectiveCBridgeable{typealiasBacked:BackedObjectType}publicextensionObjectBackablewhereBacked.Backing==Self{staticfunc_isBridgedToObjectiveC()->Bool{returntrue}staticfunc_getObjectiveCType()->Any.Type{returnBacked.self}func_bridgeToObjectiveC()->Backed{returnBacked(self)}staticfunc_forceBridgeFromObjectiveC(source:Backed,inoutresult:Self?){result=source.backingObject}staticfunc_conditionallyBridgeFromObjectiveC(source:Backed,inoutresult:Self?)->Bool{_forceBridgeFromObjectiveC(source,result:&result)returntrue}functoBridgedObject()->Backed{return_bridgeToObjectiveC()}}

Here the Swift struct SomeModel and Objective-C class M5SomeModel are declared and bridged. Bridging between them is accomplished with an as cast:

publicstructSomeModel{publicletID:Intpublicletname:Stringpublicletcategory:String}extensionSomeModel:ObjectBackable{publictypealiasBacked=M5SomeModel}@objcpublicfinalclassM5SomeModel:NSObject,BackedObjectType{publicletbackingObject:SomeModelpublicinit(_backingObject:SomeModel){self.backingObject=backingObject}publicvarID:Int{returnbackingObject.ID}publicvarname:String{returnbackingObject.name}publicvarcategory:String{returnbackingObject.category}}// Usage:letmodel=SomeModel(ID:2,name:"awesome",category:"music")letobjcCompatibleModel=modelasM5SomeModelletoriginalModel=objcCompatibleModelasSomeModel

Phantom Types

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:

structKilometer{}structMeter{}structDistanceT<T>{privateletvalue:Intinit(value:Int){self.value=value}}func+<T>(left:DistanceT<T>,right:DistanceT<T>)->DistanceT<T>{returnDistanceT(value:left.value+right.value)}extensionInt{varkm:DistanceT<Kilometer>{returnDistanceT<Kilometer>(value:self)}varm:DistanceT<Meter>{returnDistanceT<Meter>(value:self)}}letdistanceKilometers=5.kmletdistanceMeters=15.mletnewDistance=distanceKilometers+distanceKilometers// OkletnewDistance=distanceKilometers+distanceMeters// Compiler error

Easier Configuration

From Kyle Van Essen by way of Orta Therox comes a function that streamlines multi-step initialization and configuration processes.

@warn_unused_resultpublicfuncInit<Type>(value:Type,@noescapeblock:(object:Type)->Void)->Type{block(object:value)returnvalue}funcexample(){letlabel=Init(UILabel()){$0.font=UIFont.boldSystemFontOfSize(13.0)$0.text="Hello, World"$0.textAlignment=.Center}}

Well, that rounds out this year’s list—thanks again to all who contributed!

Happy New Year! May your code continue to compile and inspire.

UITextChecker

$
0
0

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.

Autocorrecting 'hipstar'

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:

["hipster","hip star","hip-star","hips tar","hips-tar"]

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.

letmisspelledRange=textChecker.rangeOfMisspelledWordInString(str,range:NSRange(0..<str.utf16.count),startingAt:0,wrap:false,language:"en_US")// misspelledRange.location == NSNotFound
NSRangemisspelledRange=[textCheckerrangeOfMisspelledWordInString:strrange:NSMakeRange(0,[strlength])startingAt:0wrap:NOlanguage:@"en_US"];// misspelledRange.location == NSNotFound

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:

letpartial="hipst"letcompletions=textChecker.completionsForPartialWordRange(NSRange(0..<partial.utf16.count),inString:partial,language:"en_US")// completions == ["hipster", "hipsters"]
NSString*partial=@"hipst";NSArray*completions=[textCheckercompletionsForPartialWordRange:NSMakeRange(0,[partiallength])inString:partiallanguage:@"en_US"];// completions == ["hipster", "hipsters"]

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!

NSHipster Quiz #8

$
0
0

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.

Competition was as fierce as always. Laughs and groans were heard. And after the points were tallied, team “Hey Siri” won the evening and the mustache medallions with a score of 31 out of a possible 43 points. A hearty congratulations to Alek Åström, Cezary Wojcik, Kyle Sherman, Marcus Brissman, Marius Rackwitz, Melissa Huang, Nevyn Bengtsson, and Rob Stevenson!

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

  1. 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?
  2. 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?
  3. 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)
  4. Which version of iPhoto first introduced “Faces and Places?”
  5. 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?
  6. More than just class names have changed—write the new Swift signature for this NSString method:

    funcstringByReplacingCharactersInRange(_range:NSRange,        withStringreplacement:String)->String
  7. Write the Swift 3 code to execute an asynchronous “Hello, world!” using GCD.

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

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

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

  1. Statistic, Sample, Correlation
  2. CallObserver, Transaction, Provider
  3. Visit, Heading, Region
  4. Conversation, Session, Sticker
  5. IndexSet, ValueTransformer, Scanner
  6. Participant, Reminder, StructuredLocation
  7. Circle, LocalSearch, GeodesicPolyline
  8. LabeledValue, PhoneNumber, SocialProfile
  9. Quadtree, NoiseSource, MonteCarloStrategist
  10. RideStatus, PaymentMethod, CarAirCirculationModeResolutionResult

Round 3: Who Is That?

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

3

vara=[8,6,7,5,3,0,9]a.sort()print(a)// [0, 3, 5, 6, 7, 8, 9]

4

vara=[8,6,7,5,3,0,9]sort(a)print(a)// [0, 3, 5, 6, 7, 8, 9]

5

vara=[8,6,7,5,3,0,9]a.sort()print(a)// [8, 6, 7, 5, 3, 0, 9]

6

foriinstride(from:3,to:10,by:3){print(i)}// 3// 6// 9

7

foriin3.stride(to:10,by:3){print(i)}// 3// 6// 9

8

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

  1. Mount Whitney
  2. San Francisco 49ers
  3. The Sugarhill Gang, 1979 (2 points for both)
  4. iPhoto ’09
  5. NSExpression
  6. One of: “`swift // 1 replacingCharacters(in: NSRange, with: String)

    // 2 func replacingCharacters( in range: NSRange,         with replacement: String) -> String”`

  7. One of:“`swift // 1 let queue = DispatchQueue(label: "quiz”) queue.async { print(“Hello, world!”) }

    // 2 DispatchQueue.main.async { print(“Hello, world!”) }“`

  8. 3,012 (correct if between 2,912 and 3,112)

  9. July 2010 (1 point if year, 2 if both)

  10. Doug Gregor, July 2011 (2 points)

Round 2: Name That Framework

  1. HealthKit
  2. CallKit
  3. Core Location
  4. Messages
  5. Foundation
  6. EventKit
  7. MapKit
  8. Contacts
  9. GamePlayKit
  10. Intents

Round 3: Who Is That?

  1. Jimmy Fallon & Justin Timberlake (2 points for both)
  2. Martin Scorsese
  3. Jeff Goldblum
  4. Lake Bell
  5. Kiefer Sutherland
  6. Robin Williams
  7. Jony Ive
  8. Jeff Daniels
  9. Richard Dreyfuss
  10. Drunk Jeff Goldblum

Round 4: Easy as 1, 2, 3

If you listed multiple versions, all must be correct for the answer to score.

  1. Swift 1
  2. Swift 2 or 3 (2 points for both)
  3. Swift 3
  4. Swift 1
  5. Swift 2
  6. Swift 1 or 3 (2 points for both)
  7. Swift 2
  8. Swift 3
  9. Swift 2
  10. Initial beta release of Swift

How’d you do? Tweet out your score to see how you stack up to your peers!

NSRegularExpression

$
0
0

“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:

letourcesay=source.replacingOccurrences(of:"([bcdfghjklmnpqrstvwxyz]*)([a-z]+)",with:"$2$1ay",options:[.regularExpression,.caseInsensitive])print(ourcesay)// "orFay etNSSay anday ictionaryNSDay, ethay eakingbray..."
NSString*ourcesay=[sourcestringByReplacingOccurrencesOfString:@"([bcdfghjklmnpqrstvwxyz]*)([a-z]+)"withString:@"$2$1ay"options:NSRegularExpressionSearch|NSCaseInsensitiveSearchrange:NSMakeRange(0,source.length)];NSLog(@"%@",ourcesay);// "orFay etNSSay anday ictionaryNSDay, ethay eakingbray..."

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
NSString*miniPattern=@"([*_])(.+?)\\1";NSError*error=nil;NSRegularExpression*miniFormatter=[NSRegularExpressionregularExpressionWithPattern:miniPatternoptions:NSRegularExpressionDotMatchesLineSeparatorserror:&error];

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.

Swift Property Wrappers

$
0
0

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:


Constraining Values

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:

ExpressibleByIntegerLiteralFloatExpressibleByFloatLiteralBinaryFloatingPointFloatingPointFloat80DoubleSignedNumericSignedIntegerIntAdditiveArithmeticNumericFixedWidthIntegerBinaryIntegerComparableEquatableUnsignedIntegerUInt
Credit:Flight School Guide to Swift Numbers

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.

@propertyWrapperstructClamping<Value:Comparable>{varvalue:Valueletrange:ClosedRange<Value>init(initialValuevalue:Value,_range:ClosedRange<Value>){precondition(range.contains(value))self.value=valueself.range=range}varwrappedValue:Value{get{value}set{value=min(max(range.lowerBound,newValue),range.upperBound)}}}

You could use @Clamping to guarantee that a property modelingacidity in a chemical solution within the conventional range of 0 – 14.

structSolution{@Clamping(0...14)varpH:Double=7.0}letcarbonicAcid=Solution(pH:4.68)// at 1 mM under standard conditions

Attempting to set pH values outside that range results in the closest boundary value (minimum or maximum) to be used instead.

letsuperDuperAcid=Solution(pH:-1)superDuperAcid.pH// 0

Related Ideas

  • 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:

importFoundationURL(string:" https://nshipster.com")// nil (!)ISO8601DateFormatter().date(from:" 2019-06-24")// nil (!)letwords=" Hello, world!".components(separatedBy:.whitespaces)words.count// 3 (!)

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.

importFoundation@propertyWrapperstructTrimmed{private(set)varvalue:String=""varwrappedValue:String{get{value}set{value=newValue.trimmingCharacters(in:.whitespacesAndNewlines)}}init(initialValue:String){self.wrappedValue=initialValue}}

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(_:):

importFoundation@propertyWrapperstructCaseInsensitive<Value:StringProtocol>{varwrappedValue:Value}extensionCaseInsensitive:Comparable{privatefunccompare(_other:CaseInsensitive)->ComparisonResult{wrappedValue.caseInsensitiveCompare(other.wrappedValue)}staticfunc==(lhs:CaseInsensitive,rhs:CaseInsensitive)->Bool{lhs.compare(rhs)==.orderedSame}staticfunc<(lhs:CaseInsensitive,rhs:CaseInsensitive)->Bool{lhs.compare(rhs)==.orderedAscending}staticfunc>(lhs:CaseInsensitive,rhs:CaseInsensitive)->Bool{lhs.compare(rhs)==.orderedDescending}}

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.

lethello:String="hello"letHELLO:String="HELLO"hello==HELLO// falseCaseInsensitive(wrappedValue:hello)==CaseInsensitive(wrappedValue:HELLO)// true

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:

structAccount:Equatable{@CaseInsensitivevarname:Stringinit(name:String){$name=CaseInsensitive(wrappedValue:name)}}varjohnny=Account(name:"johnny")letJOHNNY=Account(name:"JOHNNY")letJane=Account(name:"Jane")johnny==JOHNNY// truejohnny==Jane// falsejohnny.name==JOHNNY.name// falsejohnny.name="Johnny"johnny.name// "Johnny"

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.

importFoundation@propertyWrapperstructVersioned<Value>{privatevarvalue:Valueprivate(set)vartimestampedValues:[(Date,Value)]=[]varwrappedValue:Value{get{value}set{defer{timestampedValues.append((Date(),value))}value=newValue}}init(initialValuevalue:Value){self.wrappedValue=value}}

A hypothetical ExpenseReport class could wrap its state property with the @Versioned annotation to keep a paper trail for each action during processing.

classExpenseReport{enumState{case submitted, received, approved, denied }@Versionedvarstate:State=.submitted}

Related Ideas

  • 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:

classExpenseReport{@Versionedvarstate:State=.submitted{willSet{ifnewValue==.approved,$state.timestampedValues.map{$0.1}.contains(.denied){fatalError("J'Accuse!")}}}}vartripExpenses=ExpenseReport()tripExpenses.state=.deniedtripExpenses.state=.approved// Fatal error: "J'Accuse!"

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:

  1. Ignoring them (silently)
  2. 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.

letUnitInterval=Clamping(0...1)structSolution{@UnitIntervalvarpH:Double}// ❌

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.

Property Wrappers Aren’t First-Class Dependent Types

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.

typealiaspH=@Clamping(0...14)Double// ❌funcacidity(of:Chemical)->pH{}

Nor can you use property wrappers to annotate key or value types in collections.

enumHTTP{structRequest{varheaders:[@CaseInsensitiveString:String]// ❌}}

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?

Go ahead and visitthe official SwiftUI docs and try to answer.

😬

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.

Or, as Nataliya Patsovska put it in a tweet:

iOS API design, short history:

  • Objective C - describe all semantics in the name, the types don’t mean much
  • Swift 1 to 5 - name focuses on clarity and basic structs, enums, classes and protocols hold semantics
  • Swift 5.1 - @wrapped $path @yolo

@nataliya_bg

Perhaps we’ll only know looking back whether Swift 5.1 marked a tipping point or a turning point for our beloved language.

UIStackView

$
0
0

When I was a student in Japan, I worked part-time at a restaurant — アルバイト(arubaito) 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

You can also insert subviews at a specific index:

letchargerPlate=UIView(...)anotherPlateStack.insertArrangedSubview(chargerPlate,at:1)anotherPlateStack.arrangedSubviews.count// 3

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.

plateStack.arrangedSubviews.contains(saladPlate)// trueplateStack.subviews.contains(saladPlate)// trueplateStack.removeArrangedSubview(saladPlate)plateStack.arrangedSubviews.contains(saladPlate)// falseplateStack.subviews.contains(saladPlate)// truesaladPlate.removeFromSuperview()plateStack.arrangedSubviews.contains(saladPlate)// falseplateStack.subviews.contains(saladPlate)// false

Toggling Subview Visibility

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:

UIView.animate(withDuration:0.5,animations:{plateStack.arrangedSubviews[0].isHidden=true})

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.

Main AxisCross AxisCross AxisMain AxisHorizontal StackVertical Stack

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.
(60 × 110)60 × 110(60 × 110)60 × 110(110 × 60)110 × 60(110 × 60)110 × 60(102 × 128)102 × 128(102 × 128)102 × 128(128 × 102)128 × 102(128 × 102)128 × 102(68 × 190)68 × 190(68 × 190)68 × 190(190 × 68)190 × 68(190 × 68)190 × 68Equal SpacingEqual CenteringEqual SpacingEqual CenteringHorizontal StackVertical Stack

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.
(60 × 110)60 × 254(60 × 110)66.5 × 254(110 × 60)254 × 60(110 × 60)254 × 66.5(102 × 128)126 × 254(102 × 128)112.5 × 254(102 × 128)84.5 × 254(128 × 102)254 × 126(128 × 102)254 × 112.5(110 × 60)254 × 84.5(128 × 102)254 × 84.5(68 × 190)68 × 254(68 × 190)75 × 254(68 × 190)84.5 × 254(190 × 68)254 × 68(190 × 68)254 × 75(190 × 68)254 × 84.5FillFillFill ProportionallyFill EquallyHorizontal StackVertical StackFill ProportionallyFill Equally(60 × 110)84.5 × 254

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!

plateStack.distribution=.fillplateStack.alignment=.leading

Palate Cleanser 🍧 Background Color

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.

plateStack.layer.backgroundColor=UIColor.white.cgColor

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.

plateStack.setCustomSpacing(4,after:saladPlate)plateStack.customSpacing(after:saladPlate)// 4

You can apply insets to your stack view by setting its isLayoutMarginsRelativeArrangement to true and assigning a new value to layoutMargins.

plateStack.isLayoutMarginsRelativeArrangement=trueplateStack.layoutMargins=UIEdgeInsets(...)

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.

letconstraint=saladPlate.widthAnchor.constraint(equalToConstant:200)constraint.priority=.init(999)constraint.isActive=true

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.


CAEmitterLayer

$
0
0

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.

enumContent{enumShape{case circlecase trianglecase squarecase custom(CGPath)}case shape(Shape, UIColor?)case image(UIImage, UIColor?)case emoji(Character)}

Here’s how we would configure our confetti cannon to shoot out a colorful variety of shapes and images:

letcontents:[Content]=[.shape(.circle,.purple),.shape(.triangle,.lightGray),.image(UIImage(named:"swift")!,.orange),.emoji("👨🏻"),.emoji("📱")]

Creating a CAEmitterLayer Subclass

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

privatefinalclassLayer:CAEmitterLayer{funcconfigure(withcontents:[Content]){emitterCells=contents.map{contentinletcell=CAEmitterCell()cell.birthRate=50.0cell.lifetime=10.0cell.velocity=CGFloat(cell.birthRate*cell.lifetime)cell.velocityRange=cell.velocity/2cell.emissionLongitude=.picell.emissionRange=.pi/4cell.spinRange=.pi*6cell.scaleRange=0.25cell.scale=1.0-cell.scaleRangecell.contents=content.image.cgImageifletcolor=content.color{cell.color=color.cgColor}returncell}}...}

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.

privateletkAnimationLayerKey="com.nshipster.animationLayer"finalclassConfettiView:UIView{funcemit(withcontents:[Content],forduration:TimeInterval=3.0){letlayer=Layer()layer.configure(with:contents)layer.frame=self.boundslayer.needsDisplayOnBoundsChange=trueself.layer.addSublayer(layer)guardduration.isFiniteelse{return}letanimation=CAKeyframeAnimation(keyPath:#keyPath(CAEmitterLayer.birthRate))animation.duration=durationanimation.timingFunction=CAMediaTimingFunction(name:.easeIn)animation.values=[1,0,0]animation.keyTimes=[0,0.5,1]animation.fillMode=.forwardsanimation.isRemovedOnCompletion=falselayer.beginTime=CACurrentMediaTime()layer.birthRate=1.0CATransaction.begin()CATransaction.setCompletionBlock{lettransition=CATransition()transition.delegate=selftransition.type=.fadetransition.duration=1transition.timingFunction=CAMediaTimingFunction(name:.easeOut)transition.setValue(layer,forKey:kAnimationLayerKey)transition.isRemovedOnCompletion=falselayer.add(transition,forKey:nil)layer.opacity=0}layer.add(animation,forKey:nil)CATransaction.commit()}...

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.

Adopting the CAAnimationDelegate Protocol

To extend our overarching metaphor,CAAnimationDelegate isthat little cartoon janitor from Rocky and Bullwinkle with a push broom at the end of the ticker-tape parade.

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.

// MARK: - CAAnimationDelegateextensionConfettiView:CAAnimationDelegate{funcanimationDidStop(_animation:CAAnimation,finishedflag:Bool){ifletlayer=animation.value(forKey:kAnimationLayerKey)as?CALayer{layer.removeAllAnimations()layer.removeFromSuperlayer()}}}

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)


The full ConfettiView sourceis available on GitHub, and can be easily integrated into your project usingCocoaPods.

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.

Xcode SpriteKit Particle Editor

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.

Xcode SceneKit Scene Editor

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

Feel free toread our article on UIFieldBehavior and make confetti out of your app, if you like.

HEVC Video with Alpha Channel

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.

For more details, check outWWDC 2019 Session 506.

Animated PNGs

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:

// ⚠️ Expected UsageletimageView=UIImageView()letimageURL=URL(fileURLWithPath:"path/to/animated.png")letoptions:[String:Any]=[kCGImageAnimationLoopCount:42]CGAnimateImageAtURLWithBlock(imageURL,options){(index,cgimage,stop)inimageView.image=UIImage(cgImage:cg)}

Meanwhile, animated PNGs have been supported in Safari for ages, and with a far simpler API:

<imgsrc="animated.png"/>

WebGL

Speaking of the web, let’s talk about a shiny, new(-ish) web standard called WebGL.

With just a few hundred lines of JavaScript and GL shader language, you too can render confetti to your very own blog aboutweb developmentObjective-C, Swift, and Cocoa.

Emoji

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.

Formatter

$
0
0

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:

Numbers and Quantities
NumberFormatter
MeasurementFormatter
Dates, Times, and Durations
DateFormatter
ISO8601DateFormatter
DateComponentsFormatter
DateIntervalFormatter
RelativeDateTimeFormatter
People and Places
PersonNameComponentsFormatter
CNPostalAddressFormatter
Lists and Items
ListFormatter

Formatting Numbers and Quantities

ClassExample OutputAvailability
NumberFormatter“1,234.56”iOS 2.0
macOS 10.0+
MeasurementFormatter“-9.80665 m/s²”iOS 10.0+
macOS 10.12+
ByteCountFormatter“756 KB”iOS 6.0+
macOS 10.8+
EnergyFormatter“80 kcal”iOS 8.0+
macOS 10.10+
MassFormatter“175 lb”iOS 8.0+
macOS 10.10+
LengthFormatter“5 ft, 11 in”iOS 8.0+
macOS 10.10+
MKDistanceFormatter“500 miles”iOS 7.0+
macOS 10.9+

NumberFormatter

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 StyleExample Output
none123
decimal123.456
percent12%
scientific1.23456789E4
spellOutone hundred twenty-three
ordinal3rd
currency$1234.57
currencyAccounting($1234.57)
currencyISOCodeUSD1,234.57
currencyPlural1,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:

varformatter=NumberFormatter()formatter.usesSignificantDigits=trueformatter.minimumSignificantDigits=1// defaultformatter.maximumSignificantDigits=6// defaultformatter.string(from:1234567)// 1234570formatter.string(from:1234.567)// 1234.57formatter.string(from:100.234567)// 100.235formatter.string(from:1.23000)// 1.23formatter.string(from:0.0000123)// 0.0000123
  • 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).
varformatter=NumberFormatter()formatter.usesSignificantDigits=falseformatter.minimumIntegerDigits=0// defaultformatter.maximumIntegerDigits=42// default (seriously)formatter.minimumFractionDigits=0// defaultformatter.maximumFractionDigits=0// defaultformatter.string(from:1234567)// 1234567formatter.string(from:1234.567)// 1235formatter.string(from:100.234567)// 100formatter.string(from:1.23000)// 1formatter.string(from:0.0000123)// 0

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:
MeasureUnit SubclassBase Unit
AccelerationUnitAccelerationmeters per second squared (m/s²)
Planar angle and rotationUnitAngledegrees (°)
AreaUnitAreasquare meters (m²)
Concentration of massUnitConcentrationMassmilligrams per deciliter (mg/dL)
DispersionUnitDispersionparts per million (ppm)
DurationUnitDurationseconds (sec)
Electric chargeUnitElectricChargecoulombs (C)
Electric currentUnitElectricCurrentamperes (A)
Electric potential differenceUnitElectricPotentialDifferencevolts (V)
Electric resistanceUnitElectricResistanceohms (Ω)
EnergyUnitEnergyjoules (J)
FrequencyUnitFrequencyhertz (Hz)
Fuel consumptionUnitFuelEfficiencyliters per 100 kilometers (L/100km)
IlluminanceUnitIlluminancelux (lx)
Information StorageUnitInformationStorageByte* (byte)
LengthUnitLengthmeters (m)
MassUnitMasskilograms (kg)
PowerUnitPowerwatts (W)
PressureUnitPressurenewtons per square meter (N/m²)
SpeedUnitSpeedmeters per second (m/s)
TemperatureUnitTemperaturekelvin (K)
VolumeUnitVolumeliters (L)

* Follows ISO/IEC 80000-13 standard; one byte is 8 bits, 1 kilobyte = 1000¹ bytes


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.

formatter.numberFormatter.usesSignificantDigits=trueformatter.numberFormatter.maximumSignificantDigits=4formatter.string(from:speed)// 69.35 mph

Changing Which Unit is Displayed

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.

formatter.unitOptions=[.providedUnit]formatter.string(from:speed)// 111.6 km/hformatter.string(from:speed.converted(to:.milesPerHour))// 69.35 mph

Formatting Dates, Times, and Durations

ClassExample OutputAvailability
DateFormatter“July 15, 2019”iOS 2.0
macOS 10.0+
ISO8601DateFormatter“2019-07-15”iOS 10.0+
macOS 10.12+
DateComponentsFormatter“10 minutes”iOS 8.0
macOS 10.10+
DateIntervalFormatter“6/3/19 - 6/7/19”iOS 8.0
macOS 10.10+
RelativeDateTimeFormatter“3 weeks ago”iOS 13.0+
macOS 10.15

DateFormatter

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.

StyleExample Output
DateTime
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"

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

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.

letformatter=DateIntervalFormatter()formatter.dateStyle=.shortformatter.timeStyle=.noneletfromDate=Date()lettoDate=Calendar.current.date(byAdding:.day,value:7,to:fromDate)!formatter.string(from:fromDate,to:toDate)// "7/15/19 – 7/22/19"

Date Interval Styles

StyleExample Output
DateTime
none“”“”
short“6/30/14 - 7/11/14”“5:51 AM - 7:37 PM”
medium“Jun 30, 2014 - Jul 11, 2014”“5:51:49 AM - 7:38:29 PM”
long“June 30, 2014 - July 11, 2014”“6:02:54 AM GMT-8 - 7:49:34 PM GMT-8”
full“Monday, June 30, 2014 - Friday, July 11, 2014“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:

letformatter=DateComponentsFormatter()formatter.unitsStyle=.fullletcomponents=DateComponents(day:1,hour:2)letstring=formatter.string(from:components)// 1 day, 2 hours

Date Components Unit Styles

StyleExample
positional“1:10”
abbreviated“1h 10m”
short“1hr 10min”
full“1 hour, 10 minutes”
spellOut“One hour, ten minutes”

Formatting Context

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 ContextOutput
standalone“About 2 hours”
listItem“About 2 hours”
beginningOfSentence“About 2 hours”
middleOfSentence“about 2 hours”
dynamicDepends*

* 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

StyleExample
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

ClassExample OutputAvailability
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:

letformatter=PersonNameComponentsFormatter()varnameComponents=PersonNameComponents()nameComponents.givenName="Johnny"nameComponents.familyName="Appleseed"formatter.string(from:nameComponents)// "Johnny Appleseed"

Simple enough, right? We all know names are space delimited, first-last… right?

nameComponents.givenName="约翰尼"nameComponents.familyName="苹果籽"formatter.string(from:nameComponents)// "苹果籽约翰尼"

‘nuf said.

CNPostalAddressFormatter

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:

varattributedString=addressFormatter.attributedString(from:address,withDefaultAttributes:[:]).mutableCopy()as!NSMutableAttributedStringletstringRange=NSRange(location:0,length:attributedString.length)attributedString.enumerateAttributes(in:stringRange,options:[]){(attributes,attributesRange,_)inletcolor:UIColorswitchattributes[NSAttributedString.Key(CNPostalAddressPropertyAttribute)]as?String{case CNPostalAddressStreetKey:color=.redcase CNPostalAddressCityKey:color=.orangecase CNPostalAddressStateKey:color=.greencase CNPostalAddressPostalCodeKey:color=.purpledefault:return}attributedString.addAttribute(.foregroundColor,value:color,range:attributesRange)}

One Apple Park Way
CupertinoCA95014


Formatting Lists and Items

ClassExample OutputAvailability
ListFormatter“macOS, iOS, iPadOS, watchOS, and tvOS”iOS 13.0+
macOS 10.15+

ListFormatter

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

Once again,we don’t have any official documentation to work from at the moment, but the comments in the header file give us enough to go on.

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.

Network Link Conditioner

$
0
0

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.

And so we invest in reaching beyond our own operational model of the world. We tailor our experience todifferent locales. We consider the usability implications ofscreen readers or other assistive technologies. We continuously evaluate our implementation against these expectations.

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.

Additional Tools - Hardware

Once the download has finished, open the DMG, navigate to the “Hardware” directory, and double-click “Network Link Condition.prefPane”.

Install Network Link Conditioner

Click on the Network Link Conditioner preference pane at the bottom of System Preferences.

Network Link Conditioner

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.

Preset


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:

  1. Connect your iOS device to your Mac
  2. In Xcode, navigate to Window > Devices & Simulators
  3. Select your device in the sidebar
  4. Click “Use for Development”

iOS Devices

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

macOS Accessibility Keyboard

$
0
0

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

Accessibility Keyboard Panel Editor

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.

Accessibility Keyboard Panel Editor window and icon

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:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plistversion="1.0"><dict><key>Panels</key><dict><key>USER.80B26730-BB8A-41A5-8E70-79AA134F9D0E</key><dict><key>AssociatedApplications</key><array><dict><key>ApplicationBundleID</key><string>com.apple.Notes</string><key>ApplicationDisplayName</key><string>Notes</string><key>ApplicationPath</key><string>/Applications/Notes.app</string></dict></array><key>DisplayOrder</key><integer>1</integer><key>GlidingLensSize</key><integer>5</integer><key>HasTransientPosition</key><false/><key>HideHome</key><false/><key>HideMinimize</key><false/><key>HidePanelAdjustments</key><false/><key>HideSwitchDock</key><false/><key>HideSwitchDockContextualButtons</key><false/><key>HideTitlebar</key><false/><key>ID</key><string>USER.80B26730-BB8A-41A5-8E70-79AA134F9D0E</string><key>Name</key><string>Keyboard - IPA</string><key>PanelObjects</key><array><#...#></array></dict></dict></dict>

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:

<dict><key>ButtonType</key><integer>0</integer><key>DisplayColor</key><string>0.145 0.145 0.145 1.000</string><key>DisplayImageResourceIsTemplate</key><false/><key>DisplayText</key><string>p</string><key>DisplayTextLocation</key><string>DisplayInside</string><key>DisplayTextPosition</key><string>Center</string><key>DisplayTheme</key><string>DisplayThemeDefault</string><key>FontSize</key><real>20</real><key>ID</key><string>Button.7B824E7E-9AB8-42E3-BA7B-B56924B45554</string><key>PanelObjectType</key><string>Button</string><key>Rect</key><string>{{0, 5}, {35, 35}}</string><key>Actions</key><array><dict><key>ActionParam</key><dict><key>CharString</key><string>p</string><key>isStickyKey</key><false/></dict><key>ActionRecordedOffset</key><real>0.0</real><key>ActionType</key><string>ActionPressKeyCharSequence</string><key>ID</key><string>Action.0AE7D5DD-C588-40FA-942E-89E25FD81EEA</string></dict></array></dict>

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.

Identifiable

$
0
0

What constitutes the identity of an object?

Philosophers have contemplated such matters throughout the ages. Whether it’s to do with reconstructed seafaring vessels from antiquity or spacefaring vessels from science fiction, questions of Ontology reveal our perception and judgment to be much less certain than we’d like to believe.

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:

protocolIdentifiable{associatedtypeID:Hashablevarid:ID{get}}

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:

importCoreLocationstructParcel:Identifiable{letid:Stringvarlocation:CLPlacemark?}

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:

extensionParcel:Equatable{}varspecialDelivery=Parcel(id:"123456789012")specialDelivery.location=CLPlacemark(location:CLLocation(latitude:37.3327,longitude:-122.0053),name:"Cupertino, CA")specialDelivery==Parcel(id:"123456789012")// falsespecialDelivery.id==Parcel(id:"123456789012").id// true

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.

vartrackedPackages:Set<Parcel>=...trackedPackages.contains(Parcel(id:"123456789012"))// false (?)

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:

structWidget:Identifiable{privatestaticvaridSequence=sequence(first:1,next:{$0+1})letid:Intinit?(){guardletid=Widget.idSequence.next()else{returnnil}self.id=id}}Widget()?.id// 1Widget()?.id// 2Widget()?.id// 3

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:

importFoundationstructGadget:Identifiable{letid=UUID()}Gadget().id// 584FB4BA-0C1D-4107-9EE5-C555501F2077Gadget().id// C9FECDCC-37B3-4AEE-A514-64F9F53E74BA

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.

importFoundationfileprivateextensionString{varnonEmpty:String?{isEmpty?nil:self}}structWhosit:Identifiable{letid:Stringinit?(id:String){guardletid=id.trimmingCharacters(in:CharacterSet.letters.inverted).lowercased().nonEmptyelse{returnnil}self.id=id}}Whosit(id:"Cow")?.id// cowWhosit(id:"--- cow ---")?.id// cowWhosit(id:"🐮")// nil

URL as ID

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.

URL(string:"https://nshipster.com/?a=1&b=2")!==URL(string:"http://www.NSHipster.com?b=2&a=1")!// falsetry!Data(contentsOf:URL(string:"https://nshipster.com?a=1&b=2")!)==Data(contentsOf:URL(string:"http://www.NSHipster.com?b=2&a=1")!)// true

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):

importFoundationfileprivateextensionURL{varnormalizedString:String{...}}structWhatsit:Identifiable{leturl:URLvarid:{url.normalizedString}}Whatsit(url:"https://example.com/123").id// example.com/123Whatsit(id:"http://Example.com/123/").id// example.com/123

Creating Custom Identifier ID Types

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:

structUPC:Hashable{vardigits:Stringimplementation details}structProduct:Identifiable{letid:UPCvarname:Stringvarprice:Decimal}

Three Forms of ID Requirements

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:

extensionIdentifiablewhereSelf:AnyObject{varid:ObjectIdentifier{returnObjectIdentifier(self)}}

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.

Dark Mode on iOS 13

$
0
0

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.

Adopting Dark Mode on iOS

Apple’s done a great job designing a flexible, convenient API and providing excellent documentation to go with it.

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:

$ find .-name'*.swift'\-execsed-i''-E's/#colorLiteral\(red: (.*), green: (.*), blue: (.*), alpha: (.*)\)/UIColor(red: \1, green: \2, blue: \3, alpha: \4)/ {} \;

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:

importSwiftySwiftColorSwiftletorange=UIColor(hex:"#FB8C00")// 👎

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:

NameAPILightDark
DefaultAccessibleDefaultAccessible
RedsystemRed
OrangesystemOrange
YellowsystemYellow
GreensystemGreen
TealsystemTeal
BluesystemBlue
IndigosystemIndigo
PurplesystemPurple
PinksystemPink
GraysystemGray
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:

importUIKitextensionUIColor{staticvarcustomAccent:UIColor{returnMaterialUI.red500}...}fileprivateenumMaterialUI{staticletorange600=UIColor(red:0xFB/0xFF,green:0x8C/0xFF,blue:0x00/0xFF,alpha:1)// #FB8C00...}

If your app uses a pattern like this, you can make it Dark Mode compatible using the newinit(dynamicProvider:)UIColor initializer in iOS 13 like so:

importUIKitextensionUIColorstaticvarcustomAccent:UIColor{if#available(iOS 13, *){returnUIColor{(traitCollection:UITraitCollection)->UIColoriniftraitCollection.userInterfaceStyle==.dark{returnMaterialUI.orange300}else{returnMaterialUI.orange600}}}else{returnMaterialUI.orange600}}

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.

Asset Catalog Color Asset

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.

Asset Catalog Color Asset Attributes Inspector

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:

extensionUIColor{@available(iOS 11, *)varcustomAccent:UIColor!{returnUIColor(named:"Accent")}}

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.

Interface Builder Named Color

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:

<?xml version="1.0" encoding="UTF-8"?><documenttype="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"<#...#>><device<#...#>/><dependencies><#...#><capabilityname="Named colors"minToolsVersion="9.0"/><!-- ❶ --></dependencies><scenes><!-- scenes.scene.objects.viewController.view --><#...#><viewkey="view"contentMode="scaleToFill"id="mai-nv-iew"><#...#><colorkey="backgroundColor"name="Accent"/><!-- ❷ --></view></scenes><resources><namedColorname="Accent"><!-- ❸ --><colorred="1"green="0.69019607843137254"blue="0.0"alpha="1"colorSpace="custom"customColorSpace="sRGB"/></namedColor></resources></document>
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:

$ find .-name'*.xib'-or-name'*.storyboard'\-exececho{}\;\-exec xmlstarlet sel -t\-m"//color[@colorSpace='custom']"-c.-n{}\;
        Main.storyboard<color red="1" green="0.69019607843137254" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

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:

$ find .-name'*.xib'-or-name'*.storyboard'\-exec xmlstarlet sel -t\-m"//color[@colorSpace='custom']"\-v"concat( @red,'  ',@green,'  ',@blue,'  ',@alpha)"-n{}\;\
        | sort-u
        1  0.69019607839999997  0.0  1

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:

colors=File.readlines('distinct_colors.txt').mapdo|line|components=line.strip.split(/\s+/).flat_map(&:to_f)red,green,blue=components[0..2].map{|c|(c*255).floor}alpha=(components.last*100).floor[red,green,blue,alpha]endcolors.uniq.eachdo|color|puts"#%02X%02X%02X (%d%%)"%colorend

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.

Apple Push Notification Device Tokens

$
0
0

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:

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:

// iOS 12(deviceTokenasNSData).description// "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"// iOS 13(deviceTokenasNSData).description// "{length = 32, bytes = 0x965b251c 6cb1926d e3cb366f dfb16ddd ... 5f857679 376eab7c }"

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:

letdeviceTokenString=deviceToken.map{String(format:"%02x",$0)}.joined()

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.


iOS 13

$
0
0

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:

importLinkPresentationletmetadataProvider=LPMetadataProvider()leturl=URL(string:"https://nshipster.com/ios-13/")!letmetadataProvider=LPMetadataProvider()metadataProvider.startFetchingMetadata(for:url){[weakself]metadata,erroringuardletmetadata=metadataelse{return}letlinkView=LPLinkView(metadata:metadata)self?.view.addSubview(linkView)}

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:

iOS 13 Link View

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:

letmetadata=LPLinkMetadata()metadata.url=urlmetadata.title="iOS 13"metadata.iconProvider=...letlinkView=LPLinkView(metadata:metadata)

Perform On-Device Speech Recognition

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

importSpeechguardSFSpeechRecognizer.authorizationStatus()==.authorizedletrecognizer=SFSpeechRecognizer()else{fatalError()}leturl:URL=...letrequest=SFSpeechURLRecognitionRequest(url:url)recognizer.recognitionTask(with:request){(result,error)inguardletresult=resultelse{return}forsegmentinresult.bestTranscription.segments{guardletvoiceAnalytics=segment.voiceAnalyticselse{continue}letpitch=voiceAnalytics.pitch.acousticFeatureValuePerFrameletvoicing=voiceAnalytics.voicing.acousticFeatureValuePerFrameletjitter=voiceAnalytics.jitter.acousticFeatureValuePerFrameletshimmer=voiceAnalytics.shimmer.acousticFeatureValuePerFrame}}

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.

letregion=MKCoordinateRegion(center:mapView.center,latitudinalMeters:1000,longitudinalMeters:1000)mapView.cameraBoundary=MKMapView.CameraBoundary(coordinateRegion:region)

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)}}

Keep Promises in JavaScript

If you enjoyed our article about JavaScriptCore, you’d be thrilled to know that JSValue objectsnow natively support promises.

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.

Another addition to JavaScriptCore in iOS 13 is support for symbols (no, not those symbols). For more information aboutinit(newSymbolFromDescription:in:),refer to the docsjust guess how to use it.

Respond to Objective-C Associated Objects (?)

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…

iOS 13 Share Actions

Shame that we don’t have the information we need to take advantage of this yet.

Format Lists and Relative Times

As discussed in a previous article, iOS 13 brings two new formatters to Foundation:ListFormatter andRelativeDateTimeFormatter.

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:

importUIKitfileprivateclassDownloadOperation:Operation{...}classViewController:UIViewController{privateletoperationQueue={letqueue=OperationQueue()queue.maxConcurrentOperationCount=1}()@IBOutletprivatevarprogressView:UIProgressView!@IBActionprivatefuncstartDownloading(_sender:Any){operationQueue.cancelAllOperations()progressView.observedProgress=operationQueue.progressforurlin[...]{letoperation=DownloadOperation(url:url)operationQueue.addOperation(operation)}}}

It’s also worth mentioning a few other APIs coming to in 13, like schedule(after:interval:tolerance:options:_:), which clues OperationQueue into the newCombine framework in a nice way, and addBarrierBlock(_:), which presumably works likeDispatch barrier blocks (though without documentation, it’s anyone’s guess).

Manage Background Tasks with Ease

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.

That solution came to iOS 13 by way of the newBackgroundTasks framework.

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:

importUIKitimportBackgroundTasksfileprivateletbackgroundTaskIdentifier="com.nshipster.example.task.refresh"@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?lazyvarbackgroundURLSession={letconfiguration=URLSessionConfiguration.background(withIdentifier:"com.nshipster.url-session.background")configuration.discretionary=trueconfiguration.timeoutIntervalForRequest=30returnURLSession(configuration:configuration,delegate:...,delegateQueue:...)}funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{...BGTaskScheduler.shared.register(forTaskWithIdentifier:backgroundTaskIdentifier,using:nil){taskinself.handleAppRefresh(task:taskas!BGAppRefreshTask)}returntrue}funcapplicationDidEnterBackground(_application:UIApplication){scheduleAppRefresh()}funcscheduleAppRefresh(){letrequest=BGAppRefreshTaskRequest(identifier:backgroundTaskIdentifier)request.earliestBeginDate=Date(timeIntervalSinceNow:60*10)do{tryBGTaskScheduler.shared.submit(request)}catch{print("Couldn't schedule app refresh: \(error)")}}funchandleAppRefresh(task:BGAppRefreshTask){scheduleAppRefresh()leturl:URL=...vardataTask=backgroundURLSession.dataTask(with:url){(data,response,error)in...letsuccess=(200..<300).contains(response?.statusCode)task.setTaskCompleted(success:success)}task.expirationHandler={dataTask.cancel()}dataTask.resume()}...}

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

@IBSegueActionfuncmakeProfileViewController(coder:NSCoder,sender:Any?,segueIdentifier:String?)->ProfileViewController?{ProfileViewController(coder:coder,name:self.selectedName,avatarImageURL:self.selectedAvatarImageURL)}

Second, the UIStoryboard class methodsinstantiateInitialViewController(creator:) and instantiateViewController(identifier:creator:) offer a convenient block-based customization point for instantiating a Storyboard’s view controllers.

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!

SwiftUI Previews on macOS Catalina and Xcode 11

$
0
0

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:

finalclassBorderedButton:UIButton{varcornerRadius:CGFloat{...}varborderWidth:CGFloat{...}varborderColor:UIColor?{...}}

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:

#if canImport(SwiftUI) && DEBUGimportSwiftUI@available(iOS 13.0, *)structBorderedButton_Preview:PreviewProvider{staticvarpreviews:someView{UIViewPreview{letbutton=BorderedButton(frame:.zero)button.setTitle("Follow",for:.normal)button.tintColor=.systemOrangebutton.setTitleColor(.systemOrange,for:.normal)returnbutton}.previewLayout(.sizeThatFits).padding(10)}}#endif

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:

Group{UIViewPreview{letbutton=FavoriteButton(frame:.zero)returnbutton}UIViewPreview{letbutton=FavoriteButton(frame:.zero)button.isFavorited=truereturnbutton}}.previewLayout(.sizeThatFits).padding(10)

Previewing Dark Mode

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:

ForEach(ColorScheme.allCases,id:\.self){colorSchemeinUIViewPreview{letbutton=BorderedButton(frame:.zero)button.setTitle("Subscribe",for:.normal)button.setImage(UIImage(systemName:"plus"),for:.normal)button.setTitleColor(.systemOrange,for:.normal)button.tintColor=.systemOrangereturnbutton}.environment(\.colorScheme,colorScheme).previewDisplayName("\(colorScheme)")}.previewLayout(.sizeThatFits).background(Color(.systemBackground)).padding(10)

Previewing Dynamic Type Size Categories

We can use the same approach to preview our views in variousDynamic Type Sizes:

ForEach(ContentSizeCategory.allCases,id:\.self){sizeCategoryinUIViewPreview{letbutton=BorderedButton(frame:.zero)button.setTitle("Subscribe",for:.normal)button.setImage(UIImage(systemName:"plus"),for:.normal)button.setTitleColor(.systemOrange,for:.normal)button.tintColor=.systemOrangereturnbutton}.environment(\.sizeCategory,sizeCategory).previewDisplayName("\(sizeCategory)")}.previewLayout(.sizeThatFits).padding(10)

Previewing Different Locales

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)

Previewing View Controllers on Different Devices

SwiftUI previews aren’t limited to views, you can also use them with view controllers. By creating a custom UIViewControllerPreview type and taking advantage of somenew UIStoryboard class methods in iOS 13, we can easily preview our view controller on various devices — one on top of another:

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

MetricKit

$
0
0

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.

MetricKit Diagram
Diagram from WWDC 2019 Session 417: "Improving Battery Life and Performance"

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

MetricKit Diagram

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:

importUIKitimportMetricKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{MXMetricManager.shared.add(self)returntrue}funcapplicationWillTerminate(_application:UIApplication){MXMetricManager.shared.remove(self)}}extensionAppDelegate:MXMetricManagerSubscriber{funcdidReceive(_payloads:[MXMetricPayload]){...}}

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:

letaudioLogHandle=MXMetricManager.makeLogHandle(category:"Audio")funcprocessAudioStream(){mxSignpost(.begin,log:audioLogHandle,name:"ProcessAudioStream")...mxSignpost(.end,log:audioLogHandle,name:"ProcessAudioStream")}

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:

Expand for JSON Representation:
{"locationActivityMetrics":{"cumulativeBestAccuracyForNavigationTime":"20 sec","cumulativeBestAccuracyTime":"30 sec","cumulativeHundredMetersAccuracyTime":"30 sec","cumulativeNearestTenMetersAccuracyTime":"30 sec","cumulativeKilometerAccuracyTime":"20 sec","cumulativeThreeKilometersAccuracyTime":"20 sec"},"cellularConditionMetrics":{"cellConditionTime":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":20,"bucketStart":"1 bars","bucketEnd":"1 bars"},"1":{"bucketCount":30,"bucketStart":"2 bars","bucketEnd":"2 bars"},"2":{"bucketCount":50,"bucketStart":"3 bars","bucketEnd":"3 bars"}}}},"metaData":{"appBuildVersion":"0","osVersion":"iPhone OS 13.1.3 (17A878)","regionFormat":"US","deviceType":"iPhone9,2"},"gpuMetrics":{"cumulativeGPUTime":"20 sec"},"memoryMetrics":{"peakMemoryUsage":"200,000 kB","averageSuspendedMemory":{"averageValue":"100,000 kB","standardDeviation":0,"sampleCount":500}},"signpostMetrics":[{"signpostIntervalData":{"histogrammedSignpostDurations":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":50,"bucketStart":"0 ms","bucketEnd":"100 ms"},"1":{"bucketCount":60,"bucketStart":"100 ms","bucketEnd":"400 ms"},"2":{"bucketCount":30,"bucketStart":"400 ms","bucketEnd":"700 ms"}}},"signpostCumulativeCPUTime":"30,000 ms","signpostAverageMemory":"100,000 kB","signpostCumulativeLogicalWrites":"600 kB"},"signpostCategory":"TestSignpostCategory1","signpostName":"TestSignpostName1","totalSignpostCount":30},{"signpostIntervalData":{"histogrammedSignpostDurations":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":60,"bucketStart":"0 ms","bucketEnd":"200 ms"},"1":{"bucketCount":70,"bucketStart":"201 ms","bucketEnd":"300 ms"},"2":{"bucketCount":80,"bucketStart":"301 ms","bucketEnd":"500 ms"}}},"signpostCumulativeCPUTime":"50,000 ms","signpostAverageMemory":"60,000 kB","signpostCumulativeLogicalWrites":"700 kB"},"signpostCategory":"TestSignpostCategory2","signpostName":"TestSignpostName2","totalSignpostCount":40}],"displayMetrics":{"averagePixelLuminance":{"averageValue":"50 apl","standardDeviation":0,"sampleCount":500}},"cpuMetrics":{"cumulativeCPUTime":"100 sec"},"networkTransferMetrics":{"cumulativeCellularDownload":"80,000 kB","cumulativeWifiDownload":"60,000 kB","cumulativeCellularUpload":"70,000 kB","cumulativeWifiUpload":"50,000 kB"},"diskIOMetrics":{"cumulativeLogicalWrites":"1,300 kB"},"applicationLaunchMetrics":{"histogrammedTimeToFirstDrawKey":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":50,"bucketStart":"1,000 ms","bucketEnd":"1,010 ms"},"1":{"bucketCount":60,"bucketStart":"2,000 ms","bucketEnd":"2,010 ms"},"2":{"bucketCount":30,"bucketStart":"3,000 ms","bucketEnd":"3,010 ms"}}},"histogrammedResumeTime":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":60,"bucketStart":"200 ms","bucketEnd":"210 ms"},"1":{"bucketCount":70,"bucketStart":"300 ms","bucketEnd":"310 ms"},"2":{"bucketCount":80,"bucketStart":"500 ms","bucketEnd":"510 ms"}}}},"applicationTimeMetrics":{"cumulativeForegroundTime":"700 sec","cumulativeBackgroundTime":"40 sec","cumulativeBackgroundAudioTime":"30 sec","cumulativeBackgroundLocationTime":"30 sec"},"timeStampEnd":"2019-10-22 06:59:00 +0000","applicationResponsivenessMetrics":{"histogrammedAppHangTime":{"histogramNumBuckets":3,"histogramValue":{"0":{"bucketCount":50,"bucketStart":"0 ms","bucketEnd":"100 ms"},"1":{"bucketCount":60,"bucketStart":"100 ms","bucketEnd":"400 ms"},"2":{"bucketCount":30,"bucketStart":"400 ms","bucketEnd":"700 ms"}}}},"appVersion":"1.0.0","timeStampBegin":"2019-10-21 07:00:00 +0000"}

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:

CREATETABLEIFNOTEXISTSmetrics(idBIGINTGENERATEDBYDEFAULTASIDENTITYPRIMARYKEY,payloadJSONBNOTNULL);

So easy!

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:

CREATEVIEWkey_performance_indicatorsASSELECTid,(payload->'appVersion')ASapp_version,(payload->'metaData'->>'deviceType')ASdevice_type,(payload->'metaData'->>'regionFormat')ASregion,(payload->'applicationTimeMetrics'->>'cumulativeForegroundTime')::INTERVALAScumulative_foreground_time,parse_byte_count(payload->'memoryMetrics'->>'peakMemoryUsage')ASpeak_memory_usage_bytesFROMmetrics;

With views, you can performaggregate queries over all of your metrics JSON payloads with the convenience of a schema-backed relational database:

SELECTavg(cumulative_foreground_time)FROMkey_performance_indicators;--         avg-- ══════════════════--  @ 9 mins 41 secsSELECTapp_version,percentile_disc(0.5)WITHINGROUP(ORDERBYpeak_memory_usage_bytes)ASmedianFROMkey_performance_indicatorsGROUPBYapp_version;--  app_version │  median-- ═════════════╪═══════════--  "1.0.1"     │ 192500000--  "1.0.0"     │ 204800000

Creating a Web Service

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):

require'sinatra/base'require'pg'require'sequel'classApp<Sinatra::BaseconfiguredoDB=Sequel.connect(ENV['DATABASE_URL'])endpost'/collect'doDB[:metrics].insert(payload: request.body.read)status204endend

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:

extensionAppDelegate:MXMetricManagerSubscriber{funcdidReceive(_payloads:[MXMetricPayload]){forpayloadinpayloads{leturl=URL(string:"https://example.com/collect")!varrequest=URLRequest(url:url)request.httpMethod="POST"request.httpBody=payload.jsonRepresentation()lettask=URLSession.shared.dataTask(with:request)task.priority=URLSessionTask.lowPrioritytask.resume()}}}

When you create something and put it out into the world, you lose your direct connection to it. That’s as true for apps as it is for college radio shows. Short of user research studies or invasive ad-tech, the truth is thatwe rarely have any clue about how people are using our software.

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.

Device Identifiers and Fingerprinting on iOS

$
0
0

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

Frankenstein’s monster was as much the creation of Mary Shelley as it was a spiritual collaboration withLuigi Galvani. And Bram Stoker’s fictionalized account of the mummy’s curse was more a response to theEgyptomania and European colonialism of the nineteenth century than any personal account of the Middle Kingdom.

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

importUIKitletidfv=UIDevice.current.identifierForVendor// BD43813E-CFC5-4EEB-ABE2-94562A6E76CA

Advertising Identifiers (IDFA)

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.

importAdSupportletidfa=ASIdentifierManager.shared().advertisingIdentifier

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:

{"device_token":"QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==","transaction_id":"D98BA630-E225-4A2F-AFEC-BE3A3D591708","timestamp":1572531720,"bit0":true,"bit1":false}

To retrieve those two bits at a later point in time, the server sends a payload without bit0 and bit1 fields:

{"device_token":"QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==","transaction_id":"D98BA630-E225-4A2F-AFEC-BE3A3D591708","timestamp":1572532500}

If everything worked, Apple’s servers would respond with a 200 status code and the following JSON payload:

{"bit0":true"bit1":false,"last_update_time":"2019-10"}

Fingerprinting in Today’s iOS

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.

importFoundationLocale.current.languageCodelog2(Double(Locale.isoLanguageCodes.count))// 9.217 bitsLocale.current.regionCodelog2(Double(Locale.isoRegionCodes.count))// 8 bitsLocale.current.calendar.identifier// ~2^4 (16) CalendarsTimeZone.current.identifierlog2(Double(TimeZone.knownTimeZoneIdentifiers.count))// 8.775 bitsUserDefaults.standard.object(forKey:"AppleKeyboards")// ~2^6 (64) iOS keyboards

Accessibility (~10 bits)

Accessibility preferences also provide a great deal of information, with each individual setting contributing a single potential bit:

UIAccessibility.isBoldTextEnabledUIAccessibility.isShakeToUndoEnabledUIAccessibility.isReduceMotionEnabledUIAccessibility.isDarkerSystemColorsEnabledUIAccessibility.isReduceTransparencyEnabledUIAccessibility.isAssistiveTouchRunning

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:

letapplication=UIApplication.sharedapplication.preferredContentSizeCategory

Hardware Information (~5 or ~6 bits)

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:

importUIKitletdevice=UIDevice.currentdevice.name// "iPhone 11 Pro"letfileManager=FileManager.defaultifletpath=fileManager.urls(for:.libraryDirectory,in:.systemDomainMask).last?.path,letsystemSize=try?fileManager.attributesOfFileSystem(forPath:path)[.systemSize]as?Int{Measurement<UnitInformationStorage>(value:Double(systemSize),unit:.bytes).converted(to:.gigabytes)// ~256GB}

With 14 supported iOS devices, most having 3 configurations each, let’s say that this contributes about 32 possibilities, or 5 bits.

You can go a few steps further on macOS, to further differentiate hardware by its processor count and amount of RAM:

processInfo.processorCount// 8Measurement<UnitInformationStorage>(value:Double(processInfo.physicalMemory),unit:.bytes).converted(to:.gigabytes)// 16GB

It’s hard to get a sense of how many different Mac models are in use, but a reasonable estimate would be on the order of 26 or 27.

Cellular Network (~2 bits)

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:

importCoreTelephonyletnetworkInfo=CTTelephonyNetworkInfo()letcarriers=networkInfo.serviceSubscriberCellularProviders?.valuescarriers?.map{($0.mobileNetworkCode,$0.mobileCountryCode)}

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.

importMessageUIMFMailComposeViewController.canSendMail()MFMessageComposeViewController.canSendText()

Additional Sources of Identifying Information

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.

Geolocation by source IP address is used extensively for things like region locking and localization. You could also combine this information withping-time measurements to hosts in known locations to get a more accurate pinpoint on location (Weinberg, Cho, Christin, Sekar, & Gill, 2018):

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)

vartimestampedBatteryLevels:[(Date,Float)]=[]ifUIDevice.current.isBatteryMonitoringEnabled{timestampedBatteryLevels.append((Date(),UIDevice.current.batteryLevel))}

And so on…

Everything from your heartbeat, to your gait, to yourbutt shape seem capable of leaking your identity. It can all be quite overwhelming.

I mean, if a motivated individual can find your home address bycross-referencing the reflection in your eyes against Google Street view, how can we even stand a chance out there?


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.

References
  1. de Montjoye, Y.-A., Hidalgo, C. A., Verleysen, M., & Blondel, V. D. (2013). Unique in the Crowd: The privacy bounds of human mobility. Scientific Reports, 3, 1376. https://doi.org/10.1038/srep01376
  2. Sweeney, L. (2000). Simple Demographics Often Identify People Uniquely. Carnegie Mellon University, Data Privacy. Working paper. Retrieved from http://dataprivacylab.org/projects/identifiability/
  3. Weinberg, Z., Cho, S., Christin, N., Sekar, V., & Gill, P. (2018). How to Catch when Proxies Lie: Verifying the Physical Locations of Network Proxies with Active Geolocation. In Proceedings of the Internet Measurement Conference 2018 (pp. 203–217). New York, NY, USA: ACM. https://doi.org/10.1145/3278532.3278551
  4. Olejnik, \L., Acar, G., Castelluccia, C., & Diaz, C. (2016). The Leaking Battery. In Revised Selected Papers of the 10th International Workshop on Data Privacy Management, and Security Assurance - Volume 9481 (pp. 254–263). New York, NY, USA: Springer-Verlag New York, Inc. https://doi.org/10.1007/978-3-319-29883-2_18
  5. Zhang, J., Beresford, A. R., & Sheret, I. (2019). SensorID: Sensor Calibration Fingerprinting for Smartphones. In Proceedings of the 40th IEEE Symposium on Security and Privacy (SP). IEEE.

Message-ID and Mail.app Deep Linking on iOS and macOS

$
0
0

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.

WhileiCloud Keychain,Shared Web Credentials,Password Autofill,Universal Links, andSign in with Apple have gone a long way to minimize friction for account creation and authentication, there are still a few use cases that aren’t entirely covered by these new features.

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:

"message:%3C1572873882024.NSHIPSTER%40mail.example.com%3E"

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:

importFoundationlettimestamp=String(Int(Date().timeIntervalSince1970*1000))letnonce=String(UInt64.random(in:0..<UInt64.max),radix:36,uppercase:true)letdomain="mail.example.com"letMessageID="<\(timestamp).\(nonce)@\(domain)>"//"<1572873882024.NSHIPSTER@mail.example.com>"

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:

importFoundationfuncmessageID<Value>(forvalue:Value,domain:String)->StringwhereValue:Identifiable,Value.ID==UUID{return"<\(value.id.uuidString)@\(domain)>"}

Mobile Deep Linking

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.

importFoundationvarcomponents=URLComponents()components.scheme="message"components.host=MessageIDcomponents.string!// "message://%3C1572873882024.NSHIPSTER%40mail.example.com%3E"

Opening a Mail Deep Link

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

Other systems might use Message-ID to streamline passwordless authentication for their native app or website by way ofUniversal Links, or incorporate it as part of a2FA strategy (since SMS is no longer considered to be secure for this purpose).


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.

Viewing all 71 articles
Browse latest View live