Unless you were a Math Geek or an Ancient Greek,
Geometry probably wasn’t your favorite subject in school.
More likely, you were that kid in class
who dutifully programmed all of those necessary formulæ
into your TI-8X calculator to avoid rote memorization.
So for those of you who spent more time learning TI-BASIC than Euclid,
here’s the cheat-sheet for how geometry works in Quartz 2D,
the drawing system used by Apple platforms:
CoreGraphics Primitives (iOS)
A CGFloat
represents a scalar quantity.
A CGPoint
represents a location in a two-dimensional coordinate system
and is defined by x and y scalar components.
A CGSize
represents the extent of a figure in 2D space
and is defined by width and height scalar components.
A CGVector
represents a change in position in 2D space
and is defined by dx and dy scalar components.
A CGRect
represents a rectangle
and is defined by an origin point (CGPoint) and a size (CGSize).
On iOS, the origin is located at the top-left corner of a window,
so x and y values increase as they move down and to the right.
macOS, by default, orients (0, 0) at the bottom left corner of a window,
such that y values increase as they move up.
Every view in an iOS or macOS app
has a frame represented by a CGRect value,
so one would do well to learn the fundamentals
of these geometric primitives.
In this week’s article,
we’ll do a quick run through the APIs
with which every app developer should be familiar.
Introspection
“First, know thyself.”
So goes the philosophical aphorism.
And it remains practical guidance as we begin our survey of CoreGraphics API.
As structures,
you can access the member values of geometric types
directly through their stored properties:
point.x// 1.0point.y// 2.0size.width// 4.0size.height// 3.0rectangle.origin// {x 1 y 2}rectangle.size// {w 4 h 3}
You can mutate variables by reassignment
or by using mutating operators like *= and +=:
varmutableRectangle=rectangle// {x 1 y 2 w 4 h 3}mutableRectangle.origin.x=7.0mutableRectangle.size.width*=2.0mutableRectangle.size.height+=3.0mutableRectangle// {x 7 y 2 w 8 h 6}
For convenience,
rectangles also expose width and height
as top-level, computed properties;
(x and y coordinates must be accessed through the intermediary origin):
Although a rectangle can be fully described by
a location (CGPoint) and an extent (CGSize),
that’s just one side of the story.
For the other 3 sides,
use the built-in convenience properties
to get the minimum (min), median (mid), and maximum (max) values
in the x and y dimensions:
It’s often useful to compute the center point of a rectangle.
Although this isn’t provided by the framework SDK,
you can easily extend CGRect to implement it
using the midX and midY properties:
Things can get a bit strange when you use
non-integral or negative values in geometric calculations.
Fortunately,
CoreGraphics has just the APIs you need
to keep everything in order.
Standardizing Rectangles
We expect that a rectangle’s origin is situated at its top-left corner.
However,
if its size has a negative width or height,
the origin could become any of the other corners instead.
For example,
consider the following bizarro rectangle
that extends leftwards and upwards from its origin.
letǝןƃuɐʇɔǝɹ=CGRect(origin:point,size:CGSize(width:-4.0,height:-3.0))ǝןƃuɐʇɔǝɹ// {x 1 y 2 w -4 h -3}
We can use the standardized property
to get the equivalent rectangle
with non-negative width and height.
In the case of the previous example,
the standardized rectangle
has a width of 4 and height of 3
and is situated at the point (-3, -1):
ǝןƃuɐʇɔǝɹ.standardized// {x -3 y -1 w 4 h 3}
Integrating Rectangles
It’s generally a good idea for all CGRect values
to be rounded to the nearest whole point.
Fractional values can cause the frame to be drawn on a pixel boundary.
Because pixels are atomic units,
a fractional value causes drawing to be averaged over the neighboring pixels.
The result: blurry lines that don’t look great.
The integral property takes the floor each origin value
and the ceil each size value.
This ensures that your drawing code aligns on pixel boundaries crisply.
letblurry=CGRect(x:0.1,y:0.5,width:3.3,height:2.7)blurry// {x 0.1 y 0.5 w 3.3 h 2.7}blurry.integral// {x 0 y 0 w 4 h 4}
Transformations
While it’s possible to mutate a rectangle
by performing member-wise operations on its origin and size,
the CoreGraphics framework offers better solutions
by way of the APIs discussed below.
Translating Rectangles
Translation describes the geometric operation of
moving a shape from one location to another.
Use the offsetBy method (or CGRectOffset function in Objective-C)
to translate a rectangle’s origin by a specified x and y distance.
rectangle.offsetBy(dx:2.0,dy:2.0)// {x 3 y 4 w 4 h 3}
Consider using this method whenever you shift a rectangle’s position.
Not only does it save a line of code,
but it more semantically represents intended operation
than manipulating the origin values individually.
Contracting and Expanding Rectangles
Other common transformations for rectangles
include contraction and expansion around a center point.
The insetBy(dx:dy:) method can accomplish both.
When passed a positive value for either component,
this method returns a rectangle that
shrinks by the specified amount from each side
as computed from the center point.
For example,
when inset by 1.0 horizontally (dy = 0.0),
a rectangle originating at (1, 2)
with a width of 4 and height equal to 3,
produces a new rectangle originating at (2, 2)
with width equal to 2 and height equal to 3.
Which is to say:
the result of insetting a rectangle by 1 point horizontally
is a rectangle whose width is 2 points smaller than the original.
rectangle// {x 1 y 2 w 4 h 3}rectangle.insetBy(dx:1.0,dy:0.0)// {x 2 y 2 w 2 h 3}
When passed a negative value for either component,
the rectangle grows by that amount from each side.
When passed a non-integral value,
this method may produce a rectangle with non-integral components.
rectangle.insetBy(dx:-1.0,dy:0.0)// {x 0 y 2 w 6 h 3}rectangle.insetBy(dx:0.5,dy:0.0)// {x 1.5 y 2 w 3 h 3}
Identities and Special Values
Points, sizes, and rectangles each have a zero property,
which defines the identity value for each respective type:
CGPoint.zero// {x 0 y 0}CGSize.zero// {w 0 h 0}CGRect.zero// {x 0 y 0 w 0 h 0}
Swift shorthand syntax allows you to pass .zero directly
as an argument for methods and initializers,
such as CGRect.init(origin:size:):
CGRect has two additional special values: infinite and null:
CGRect.infinite// {x -∞ y -∞ w +∞ h +∞}CGRect.null// {x +∞ y +∞ w 0 h 0}
CGRect.null is conceptually similar to NSNotFound,
in that it represents the absence of an expected value,
and does so using the largest representable number to exclude all other values.
CGRect.infinite has even more interesting properties,
as it intersects with all points and rectangles,
contains all rectangles,
and its union with any rectangle is itself.
CGRect.infinite.contains(any point)// trueCGRect.infinite.intersects(any other rectangle)// trueCGRect.infinite.union(any other rectangle)// CGRect.infinite
Use isInfinite to determine whether a rectangle is, indeed, infinite.
CGRect.infinite.isInfinite// true
But to fully appreciate why these values exist and how they’re used,
let’s talk about geometric relationships:
Relationships
Up until this point,
we’ve been dealing with geometries in isolation.
To round out our discussion,
let’s consider what’s possible when evaluating two or more rectangles.
Intersection
Two rectangles intersect if they overlap.
Their intersection is the smallest rectangle
that encompasses all points contained by both rectangles.
CoreGraphics CGRect Intersection (iOS)
In Swift,
you can use the intersects(_:) and intersection(_:) methods
to efficiently compute the intersection of two CGRect values:
letsquare=CGRect(origin:.zero,size:CGSize(width:4.0,height:4.0))square// {x 0 y 0 w 4 h 4}rectangle.intersects(square)// truerectangle.intersection(square)// {x 1 y 2 w 3 h 2}
If two rectangles don’t intersect,
the intersection(_:) method produces CGRect.null:
The union of two rectangles
is the smallest rectangle that encompasses all of the points
contained by either rectangle.
CoreGraphics CGRect Union (iOS)
In Swift,
the aptly-named union(_:) method does just this for two CGRect values:
rectangle.union(square)// {x 0 y 0 w 5 h 5}
So what if you didn’t pay attention in Geometry class —
this is the real world.
And in the real world,
you have CGGeometry.h
and all of the types and functions it provides.
Know it well,
and you’ll be on your way to discovering great new user interfaces in your apps.
Do a good enough job with that,
and you may encounter the best arithmetic problem of all:
adding up all the money you’ve made with your awesome new app.
Mathematical!
Back in the early days of Swift 1, we didn’t have much in the way of error handling.
But we did have Optional,
and it felt awesome!
By making null checks explicit and enforced,
bombing out of a function by returning nil suddenly felt less like a code smell
and more like a language feature.
Here’s how we might write a little utility to grab Keychain data returning nil for any errors:
We set up a query,
pass an an empty inout reference to SecItemCopyMatching and then,
depending on the status code we get back,
either return the reference as data
or nil if there was an error.
At the call site,
we can tell if something has exploded by unwrapping the optional:
ifletmyData=keychainData(service:"My Service"){do something with myData...}else{fatalError("Something went wrong with... something?")}
Getting Results
There’s a certain binary elegance to the above,
but it conceals an achilles heel.
At its heart,
Optional is just an enum that holds either some wrapped value or nothing:
enumOptional<Wrapped>{case some(Wrapped)
case none
}
This works just fine for our utility when everything goes right — we just return our value.
But most operations that involve I/O can go wrong
(SecItemCopyMatching, in particular, can go wrong many, many ways),
and Optional ties our hands when it comes to signaling something’s gone sideways.
Our only option is to return nothing.
Meanwhile, at the call site, we’re wondering what the issue is and all we’ve got to work with is this empty .none.
It’s difficult to write robust software when every non-optimal condition is essentially reduced to ¯\_(ツ)_/¯.
How could we improve this situation?
One way would be to add some language-level features that let functions throw errors in addition to returning values.
And this is, in fact, exactly what Swift did in version 2 with its throws/throw and do/catch syntax.
But let’s stick with our Optional line of reasoning for just a moment. If the issue is that Optional can only hold a value or nil, and in the event of an error nil isn’t expressive enough, maybe we can address the issue simply by making a new Optional that holds either a value or an error?
enumResult<Success,Failure:Error>{case success(Success)
case failure(Failure)
}
Result holds either a successful value or an error.
And we can use it to improve our little keychain utility.
First,
let’s define a custom Error type with some more descriptive cases than a simple nil:
enumKeychainError:Error{case notData
case notFound(name: String)
case ioWentBad
...}
Next we change our keychainData definition to return Result<Data, Error> instead of Data?.
When everything goes right we return our data as the associated value of a .success.
What happens if any of SecItemCopyMatching’s many and varied disasters strike?
Rather than returning nil we return one of our specific errors wrapped in a .failure:
Now we have a lot more information to work with at the call site! We can, if we choose, switch over the result, handling both success and each error case individually:
switchkeychainData(service:"My Service"){case .success(let data):
do something with data...case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")case .failure(KeychainError.io):
print("Error reading from the keychain.")case .failure(KeychainError.notData):
print("Keychain is broken.")...}
All things considered, Result seems like a pretty useful upgrade to Optional. How on earth did it take it five years to be added to the standard library?
Three’s a Crowd
Alas, Result is also cursed with an achilles heel — we just haven’t noticed it yet because, up until now, we’ve only been working with a single call to a single function. But imagine we add two more error-prone operations to our list of Result-returning utilities:
funcmakeAvatar(fromuser:Data)->Result<UIImage,Error>{Return avatar made from user's initials...or return failure...}funcsave(image:UIImage)->Result<Void,Error>{Save image and return success...or returns failure...}
In our example,
the first function generates an avatar from user data,
and the second writes an image to disk.
The implementations don’t matter so much for our purposes,
just that they return a Result type.
Now, how would we write something that fetches user data from the keychain, uses it to create an avatar, saves that avatar to disk, and handles any errors that might occur along the way?
We might try something like:
switchkeychainData(service:"UserData"){case .success(let userData):
switchmakeAvatar(from:userData){case .success(let avatar):
switchsave(image:avatar){case .success:
break// continue on with our program...case .failure(FileSystemError.readOnly):
print("Can't write to disk.")...}case .failure(AvatarError.invalidUserFormat):
print("Unable to generate avatar from given user.")...}case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")...}
But whooo boy. Adding just two functions has led to an explosion of nesting, dislocated error handling, and woe.
Falling Flat
Thankfully, we can clean this up by taking advantage of the fact that,
like Optional,
Result implements flatMap.
Specifically, flatMap on a Result will,
in the case of .success,
apply the given transform to the associated value and return the newly produced Result.
In the case of a .failure, however,
flatMap simply passes the .failure and its associated error along without modification.
Because it passes errors through in this manner, we can use flatMap to combine our operations together without checking for .failure each step of the way. This lets us minimize nesting and keep our error handling and operations distinct:
letresult=keychainData(service:"UserData").flatMap(makeAvatar).flatMap(save)switchresult{case .success:
break// continue on with our program...case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")case .failure(AvatarError.invalidUserFormat):
print("Unable to generate avatar from given user.")case .failure(FileSystemError.readOnly):
print("Can't write to disk.")...}
This is, without a doubt, an improvement. But it requires us (and anyone reading our code) to be familiar enough with .flatMap to follow its somewhat unintuitive semantics.
Compare this to the do/catch syntax from all the way back in Swift 2 that we alluded to a little earlier:
do{letuserData=trykeychainData(service:"UserData")letavatar=trymakeAvatar(from:userData)trysave(image:avatar)}catchKeychainError.notFound(letname){print(""\(name)" not found in keychain.")}catchAvatarError.invalidUserFormat{print("Not enough memory to create avatar.")}catchFileSystemError.readOnly{print("Could not save avatar to read-only media.")}...
The first thing that might stand out is how similar these two pieces of code are. They both have a section up top for executing our operations. And both have a section down below for matching errors and handling them.
Whereas the Result version has us piping operations through chained calls to flatMap,
we write the do/catch code more or less exactly as we would if no error handling were involved.
While the Result version requires we understand the internals of its enumeration
and explicitly switch over it to match errors,
the do/catch version lets us focus on the part we actually care about:
the errors themselves.
By having language-level syntax for error handling, Swift effectively masks all the Result-related complexities it took us the first half of this post to digest: enumerations, associated values, generics, flatMap, monads… In some ways, Swift added error-handling syntax back in version 2 specifically so we wouldn’t have to deal with Result and its eccentricities.
Yet here we are, five years later, learning all about it. Why add it now?
Error’s Ups and Downs
Well, as it should happen, do/catch has this little thing we might call an achilles heel…
See, throw, like return, only works in one direction; up. We can throw an error “up” to the caller, but we can’t throw an error “down” as a parameter to another function we call.
This “up”-only behavior is typically what we want.
Our keychain utility,
rewritten once again with error handling,
is all returns and throws because its only job is passing either our data or an error
back up to the thing that called it:
But what if,
instead of fetching user data from the keychain,
we want to get it from a cloud service?
Even on a fast, reliable connection,
loading data over a network can take a long time compared to reading it from disk.
We don’t want to block the rest of our application while we wait, of course,
so we’ll make it asynchronous.
But that means we’re no longer returning anything“up”.
Instead we’re calling “down” into a closure on completion:
funcuserData(foruserID:String,completion:(Data)->Void){get data from the network// Then, sometime later:completion(myData)}
Now network operations can fail with all sorts of different errors,
but we can’t throw them “down” into completion.
So the next best option is to pass any errors along as a second (optional) parameter:
funcuserData(foruserID:String,completion:(Data?,Error?)->Void){Fetch data over the network...guardmyError==nilelse{completion(nil,myError)}completion(myData,nil)}
But now the caller, in an effort to make sense of this cartesian maze of possible parameters, has to account for many impossible scenarios in addition to the ones we actually care about:
userData(for:"jemmons"){maybeData,maybeErrorinswitch(maybeData,maybeError){case let (data?, nil):
do something with data...case (nil, URLError.timedOut?):
print("Connection timed out.")case (nil, nil):
fatalError("🤔Hmm. This should never happen.")case (_?, _?):
fatalError("😱What would this even mean?")...}}
It’d be really helpful if, instead of this mishmash of “data or nil and error or nil” we had some succinct way to express simply “data or error”.
Stop Me If You’ve Heard This One…
Wait, data or error?
That sounds familiar.
What if we used a Result?
funcuserData(foruserID:String,completion:(Result<Data,Error>)->Void){// Everything went well:completion(.success(myData))// Something went wrong:completion(.failure(myError))}
And at the call site:
userData(for:"jemmons"){resultinswitch(result){case (.success(let data)):
do something with data...case (.failure(URLError.timedOut)):
print("Connection timed out.")...}
Ah ha!
So we see that the Result type can serve as a concrete reification of Swift’s abstract idea of
“that thing that’s returned when a function is marked as throws.”
And as such, we can use it to deal with asynchronous operations that require concrete types for parameters passed to their completion handlers.
So, while the shape of Result has been implied by error handling since Swift 2
(and, indeed, quite a few developers have created their own versions of it in the intervening years),
it’s now officially added to the standard library in Swift 5 — primarily as a way to deal with asynchronous errors.
Which is undoubtedly better than passing the double-optional (Value?, Error?) mess we saw earlier.
But didn’t we just get finished making the case that Result tended to be overly verbose, nesty, and complex
when dealing with more than one error-capable call?
Yes we did.
And, in fact, this is even more of an issue in the async space
since flatMap expects its transform to return synchronously.
So we can’t use it to compose asynchronous operations:
userData(for:"jemmons"){userResultinswitchuserResult{case .success(let user):
fetchAvatar(for:user){avatarResultinswitchavatarResult{case .success(let avatar):
cloudSave(image:avatar){saveResultinswitchsaveResult{case .success:
// All done!case .failure(URLError.timedOut)
print("Operation timed out.")...}}case .failure(AvatarError.invalidUserFormat):
print("User not recognized.")...}}case .failure(URLError.notConnectedToInternet):
print("No internet detected.")...}
Awaiting the Future
In the near term, we just have to lump it.
It’s better than the other alternatives native to the language,
and chaining asynchronous calls isn’t as common as for synchronous calls.
But in the future, just as Swift used do/catch syntax to define away Result nesting problems in synchronous error handling, there are many proposals being considered to do the same for asynchronous errors (and asynchronous processing, generally).
do{letuser=tryawaituserData(for:"jemmons")letavatar=tryawaitfetchAvatar(for:user)tryawaitcloudSave(image:avatar)}catchAvatarError.invalidUserFormat{print("User not recognized.")}catchURLError.timedOut{print("Operation timed out.")}catchURLError.notConnectedToInternet{print("No internet detected.")}...
Which, holy moley! As much as I love Result, I, for one, cannot wait for it to be made completely irrelevant by our glorious async/await overlords.
Meanwhile? Let us rejoice!
For we finally have a concrete Result type in the standard library to light the way through these, the middle ages of async error handling in Swift.
Apple is nothing if not consistent. From Pentalobular screws to Sandboxing, customers are simply expected to relinquish a fair amount of control when they choose to buy a Mac or iPhone. Whether these design decisions are made to ensure a good user experience, or this control is exercised as an end in itself is debatable, but the reality is that in both hardware and software, Apple prefers an ivory tower to a bazaar.
No better example of this can be found with Xcode: the very software that software developers use to build software for the walled ecosystems of iOS & OS X software, is itself a closed ecosystem.
Indeed, significant progress has been made in recent years to break open the developer workflow, from alternative IDEs like AppCode to build tools like CocoaPods, xctool and nomad. However, the notion that Xcode itself could be customized and extended by mere mortals is extremely recent, and just now starting to pick up steam.
Xcode has had a plugin architecture going back to when Interface Builder was its own separate app. However, this system was relatively obscure, undocumented, and not widely used by third parties. Despite this, developers like Delisa Mason and Marin Usalj have done incredible work creating a stable and vibrant ecosystem of third-party Xcode extensions.
Simply install Alcatraz, and pull down all of the plugins (and color schemes and templates) that you desire.
This week on NSHipster: a roundup of some of the most useful and exciting plugins for Xcode—ready for you to try out yourself today!
And since these question come up every time there’s an article with pictures:
Just as New York became a melting pot of cultures from immigrants arriving at
Ellis Island, Xcode has welcomed the tired, poor, huddled masses of developers from every platform and language imaginable. Like those first wave Americans, who settled into their respective ethnic neighborhoods to re-establish their traditions in a new land, so too have new iOS developers brought over their preferred workflows and keybindings.
Perhaps you would appreciate a taste of home in the land of Cupertino.
Vim
Finding it too easy to quit Xcode? Try XVim, an experimental plugin that adds all of your favorite Vim keybindings.
SublimeText
Do you miss having a code minimap along the right gutter of your editor to put things into perspective? Install SCXcodeMiniMap and never again miss the tree nodes for the forest.
Atom
Looking to be more in tune with GitHub? Add the Show in GitHub / BitBucket plugin to open to the selected lines of a file online.
Fixing Xcode
Rather than waiting with crossed fingers and clenched teeth each June, as Apple engineers unveil the next version of Xcode, developers now have the ability to tailor the de facto editor to their particular needs (and most importantly, fix what’s broken).
Add Line Breaks to Issue Navigator
An annoyance going back to Xcode 4 has been the truncation of items in the Issues Navigator. Never again be frustrated by surprise ellipses when compiler warnings were just starting to get interesting, with BBUFullIssueNavigator.
Dismiss Debugging Console When Typing
Another annoyance going back to Xcode 4 is how the debugging console seems to always get in the way. No more, with BBUDebuggerTuckAway. As soon as you start typing in the editor, the debugging window will get out of your way.
Add ANSI Color Support to Debugging Console
ncurses enthusiasts will no doubt be excited by the XcodeColors plugin, which adds support for ANSI colors to appear in the debugging console.
Hide @property Methods in Source Navigator
Finding that @property synthesizers are creating a low signal-to-noise ratio in the Source Navigator? Let Xprop excise the cruft, and let the functions and methods shine through.
Blow Away DerivedData Folder
Xcode texting you again?rm -rf-ing the heck out of “Library/Developer/Xcode/DerivedData” does the trick every time, 90% of the time. Add a convenient button to your Xcode window to do this for you, with the DerivedData Exterminator.
Turbocharging Xcode
Not being the most verbose language in existence, Objective-C can use all the help it can get when it comes to autocompletion. Xcode does a lot of heavy lifting when it comes to class and method completion, but these plugins extend it even further:
Autocomplete switch Statements
Fact: switch statements and NS_ENUM go together like mango and sweet sticky rice. The only way it could be improved would be with SCXcodeSwitchExpander with automagically fills out a case statement for each value in the enumeration.
Autocomplete Documentation
Documentation adds a great deal of value to a code base, but it’s a tough habit to cultivate. The VVDocumenter-Xcode plugin does a great deal to reduce the amount of work necessary to add appledoc-compatible header documentation. Install it and wrap your code in a loving lexical embrace.
Formatting Xcode
“Code organization is a matter of hygiene”, so you owe it to yourself and your team to keep whitespace consistent in your code base. Make it easier on yourself by automating the process with these plugins.
Code Formatting with ClangFormat
ClangFormat-Xcode is a convenient wrapper around the ClangFormat tool, which automatically formats whitespace according to a specified set of style guidelines. Eliminate begrudging formatting commits forever with this plugin.
Statement Alignment
Fancy yourself a code designer, automated formatters be damned? XAlign automatically aligns assignments just so, to appease your most egregious OCD tendencies.
Extending Xcode
In a similar vein to what Bret Victor writes about Learnable Programming, these plugins push the boundaries of what we should expect from our editors, adding context and understanding to code without obscuring the meaning.
Inspect NSColor / UIColor Instances
Telling what a color is from its RGB values alone is a hard-won skill, so faced with an NSColor or UIColor value, we have little recourse to know what it’ll look like until the code is built and run. Enter ColorSense for Xcode
Quoth the README:
When you put the caret on one of your colors, it automatically shows the actual color as an overlay, and you can even adjust it on-the-fly with the standard OS X color picker.
Autocomplete Images from Project Bundle
Similar to the ColorSense plugin, KSImageNamed will preview and autocomplete images in [UIImage imageNamed:] declarations.
Semantics Highlighting
Any editor worth its salt is expected to have some form of syntax highlighting. But this recent post by Evan Brooks presents the idea of semantic highlighting in editors. The idea is that each variable within a scope would be assigned a particular color, which would be consistent across references. This way, one could easily tell the difference between two instance variables in the same method.
Polychromatic is a fascinating initial implementation of this for Xcode, and worth a look. The one downside is that this plugin requires the use of special desaturated color schemes—something that may be addressed in a future release, should this idea of semantic highlighting start to pick up mind share.
Localization
It’s no secret that NSHipster has a soft spot for localization. For this reason, this publication is emphatic in its recommendation of Lin, a clever Xcode plugin that brings the localization editor to your code.
Xcode’s plugin architecture is based on a number of private frameworks specific to Xcode, including DVTKit & IDEKit. A complete list can be derived by running class-dump on the Xcode app bundle.
Using private frameworks would be, of course, verboten on the AppStore, but since plugins aren’t distributed through these channels, developers are welcome to use whatever they want, however they want to.
To get started on your own plugin, download the Xcode5 Plugin Template, using the other available plugins and class-dump’d headers as a guide for what can be done, and how to do it.
Since time immemorial, iOS developers have been perplexed by a singular question:
“How do you resize an image?”
It’s a question of beguiling clarity,
spurred on by a mutual mistrust of developer and platform.
Myriad code samples litter Stack Overflow,
each claiming to be the One True Solution™ —
all others, mere pretenders.
In this week’s article,
we’ll look at 5 distinct techniques to image resizing on iOS
(and macOS, making the appropriate UIImage→ NSImage conversions).
But rather than prescribe a single approach for every situation,
we’ll weigh ergonomics against performance benchmarks
to better understand when to use one approach over another.
When and Why to Scale Images
Before we get too far ahead of ourselves,
let’s establish why you’d need to resize images in the first place.
After all,
UIImageView automatically scales and crops images
according to the behavior specified by its
contentMode property.
And in the vast majority of cases,
.scaleAspectFit, .scaleAspectFill, or .scaleToFill
provides exactly the behavior you need.
At its full resolution,
this image measures 12,000 px square
and weighs in at a whopping 20 MB of JPEG data.
20MB of memory is nothing on today’s hardware,
but that’s just its compressed size.
To display it,
the UIImageView needs to decode that JPEG into a bitmap.
Set that full-sized image on an image view as-is,
and your app’s memory usage will balloon to
hundreds of Megabytes of memory,
with no appreciable benefit to the user
(a screen can only display so many pixels, after all).
Not only that,
because that’s happening on the main thread,
it can cause your app to freeze for a couple seconds.
By simply resizing that image to the size of the image view
before setting its image property,
you can use an order-of-magnitude less RAM and CPU time:
Memory Usage (MB)
Without Downsampling
220.2
With Downsampling
23.7
This technique is known as downsampling,
and can significantly improve the performance of your app
in these kinds of situations.
If you’re interested in some more information about downsampling
and other image and graphics best practices,
please refer to
this excellent session from WWDC 2018.
Now,
few apps would ever try to load an image this large…
but it’s not too far off from some of the assets I’ve gotten back from design.
(Seriously, a 10MB PNG of a color gradient?)
So with that in mind,
let’s take a look at the various ways that you can go about
resizing and downsampling images.
Image Resizing Techniques
There are a number of different approaches to resizing an image,
each with different capabilities and performance characteristics.
And the examples we’re looking at in this article
span frameworks both low- and high-level,
from Core Graphics, vImage, and Image I/O
to Core Image and UIKit:
Here, size is a measure of point size,
rather than pixel size.
To calculate the equivalent pixel size for your resized image,
scale the size of your image view frame by the scale of your main UIScreen:
Technique #1: Drawing to a UIGraphicsImageRenderer
The highest-level APIs for image resizing are found in the UIKit framework.
Given a UIImage,
you can draw into a UIGraphicsImageRenderer context
to render a scaled-down version of that image:
UIGraphicsImageRenderer
is a relatively new API,
introduced in iOS 10 to replace the older,
UIGraphicsBeginImageContextWithOptions / UIGraphicsEndImageContext APIs.
You construct a UIGraphicsImageRenderer by specifying a point size.
The image method takes a closure argument
and returns a bitmap that results from executing the passed closure.
In this case,
the result is the original image scaled down to draw within the specified bounds.
Technique #2: Drawing to a Core Graphics Context
Core Graphics / Quartz 2D
offers a lower-level set of APIs
that allow for more advanced configuration.
Given a CGImage,
a temporary bitmap context is used to render the scaled image,
using the draw(_:in:) method:
This CGContext initializer takes several arguments to construct a context,
including the desired dimensions and
the amount of memory for each channel within a given color space.
In this example,
these parameters are fetched from the CGImage object.
Next, setting the interpolationQuality property to .high
instructs the context to interpolate pixels at a 👌 level of fidelity.
The draw(_:in:) method
draws the image at a given size and position, a
allowing for the image to be cropped on a particular edge
or to fit a set of image features, such as faces.
Finally,
the makeImage() method captures the information from the context
and renders it to a CGImage value
(which is then used to construct a UIImage object).
Technique #3: Creating a Thumbnail with Image I/O
Image I/O is a powerful (albeit lesser-known) framework for working with images.
Independent of Core Graphics,
it can read and write between many different formats,
access photo metadata,
and perform common image processing operations.
The framework offers the fastest image encoders and decoders on the platform,
with advanced caching mechanisms —
and even the ability to load images incrementally.
The important
CGImageSourceCreateThumbnailAtIndex offers a concise API with different options than found in equivalent Core Graphics calls:
Given a CGImageSource and set of options,
the CGImageSourceCreateThumbnailAtIndex(_:_:_:) function
creates a thumbnail of an image.
Resizing is accomplished by the kCGImageSourceThumbnailMaxPixelSize option,
which specifies the maximum dimension
used to scale the image at its original aspect ratio.
By setting either the
kCGImageSourceCreateThumbnailFromImageIfAbsent or
kCGImageSourceCreateThumbnailFromImageAlways option,
Image I/O automatically caches the scaled result for subsequent calls.
Technique #4: Lanczos Resampling with Core Image
Core Image provides built-in
Lanczos resampling functionality
by way of the eponymous CILanczosScaleTransform filter.
Although arguably a higher-level API than UIKit,
the pervasive use of key-value coding in Core Image makes it unwieldy.
That said, at least the pattern is consistent.
The process of
creating a transform filter,
configuring it, and
rendering an output image
is no different from any other Core Image workflow:
The Core Image filter named CILanczosScaleTransform
accepts an inputImage, an inputScale, and an inputAspectRatio parameter,
each of which are pretty self-explanatory.
More interestingly,
a CIContext is used here to create a UIImage
(by way of a CGImageRef intermediary representation),
since UIImage(CIImage:) doesn’t often work as expected.
Creating a CIContext is an expensive operation,
so a cached context is used for repeated resizing.
Technique #5: Image Scaling with vImage
Last up,
it’s the venerable Accelerate framework—
or more specifically,
the vImage image-processing sub-framework.
vImage comes with a
bevy of different functions
for scaling an image buffer.
These lower-level APIs promise high performance with low power consumption,
but at the cost of managing the buffers yourself
(not to mention, signficantly more code to write):
importUIKitimportAccelerate.vImage// Technique #5funcresizedImage(aturl:URL,forsize:CGSize)->UIImage?{// Decode the source imageguardletimageSource=CGImageSourceCreateWithURL(urlasNSURL,nil),letimage=CGImageSourceCreateImageAtIndex(imageSource,0,nil),letproperties=CGImageSourceCopyPropertiesAtIndex(imageSource,0,nil)as?[CFString:Any],letimageWidth=properties[kCGImagePropertyPixelWidth]as?vImagePixelCount,letimageHeight=properties[kCGImagePropertyPixelHeight]as?vImagePixelCountelse{returnnil}// Define the image formatvarformat=vImage_CGImageFormat(bitsPerComponent:8,bitsPerPixel:32,colorSpace:nil,bitmapInfo:CGBitmapInfo(rawValue:CGImageAlphaInfo.first.rawValue),version:0,decode:nil,renderingIntent:.defaultIntent)varerror:vImage_Error// Create and initialize the source buffervarsourceBuffer=vImage_Buffer()defer{sourceBuffer.data.deallocate()}error=vImageBuffer_InitWithCGImage(&sourceBuffer,&format,nil,image,vImage_Flags(kvImageNoFlags))guarderror==kvImageNoErrorelse{returnnil}// Create and initialize the destination buffervardestinationBuffer=vImage_Buffer()error=vImageBuffer_Init(&destinationBuffer,vImagePixelCount(size.height),vImagePixelCount(size.width),format.bitsPerPixel,vImage_Flags(kvImageNoFlags))guarderror==kvImageNoErrorelse{returnnil}// Scale the imageerror=vImageScale_ARGB8888(&sourceBuffer,&destinationBuffer,nil,vImage_Flags(kvImageHighQualityResampling))guarderror==kvImageNoErrorelse{returnnil}// Create a CGImage from the destination bufferguardletresizedImage=vImageCreateCGImageFromBuffer(&destinationBuffer,&format,nil,nil,vImage_Flags(kvImageNoAllocate),&error)?.takeRetainedValue(),error==kvImageNoErrorelse{returnnil}returnUIImage(cgImage:resizedImage)}
The Accelerate APIs used here clearly operate at a much lower-level
than any of the other resizing methods discussed so far.
But get past the unfriendly-looking type and function names,
and you’ll find that this approach is rather straightforward.
First, create a source buffer from your input image,
Then, create a destination buffer to hold the scaled image
Next, scale the image data in the source buffer to the destination buffer,
Finally, create an image from the resulting image data in the destination buffer.
Performance Benchmarks
So how do these various approaches stack up to one another?
The following numbers show the average runtime across multiple iterations
for loading, scaling, and displaying that
jumbo-sized picture of the earth
from before:
Time (seconds)
Technique #1: UIKit
0.1420
Technique #2: Core Graphics1
0.1722
Technique #3: Image I/O
0.1616
Technique #4: Core Image2
2.4983
Technique #5: vImage
2.3126
1
Results were consistent across different values of CGInterpolationQuality, with negligible differences in performance benchmarks.
2
Setting kCIContextUseSoftwareRenderer to true on the options passed on CIContext creation yielded results an order of magnitude slower than base results.
Conclusions
UIKit, Core Graphics, and Image I/O
all perform well for scaling operations on most images.
If you had to choose one (on iOS, at least),
UIGraphicsImageRenderer is typically your best bet.
Unless you’re already working with vImage,
the extra work necessary to use the low-level Accelerate APIs
probably isn’t justified in most circumstances.
Software development best practices
prescribe
strict separation of configuration from code.
Yet developers on Apple platforms
often struggle to square these guidelines with Xcode’s project-heavy workflow.
Understanding what each project setting does
and how they all interact with one another
is a skill that can take years to hone.
And the fact that much of this information
is buried deep within the GUIs os Xcode does us no favors.
Navigate to the “Build Settings” tab of the project editor,
and you’ll be greeted by hundreds of build settings
spread across layers of projects, targets, and configurations —
and that’s to say nothing of the other six tabs!
Fortunately,
there’s a better way to manage all of this configuration
that doesn’t involve clicking through a maze of tabs and disclosure arrows.
This week,
we’ll show you how you can use text-based xcconfig files
to externalize build settings from Xcode
to make your projects more compact, comprehensible, and powerful.
Xcode build configuration files,
more commonly known by their xcconfig file extension,
allow build settings for your app to be declared and managed without Xcode.
They’re plain text,
which means they’re much friendlier to source control systems
and can be modified with any editor.
Fundamentally,
each configuration file consists of a sequence of key-value assignments
with the following syntax:
BUILD_SETTING_NAME = value
For example,
to specify the Swift language version for a project,
you’d specify the SWIFT_VERSION build setting like so:
SWIFT_VERSION = 5.0
At first glance,
xcconfig files bear a striking resemblance to .env files,
with their simple, newline-delimited syntax.
But there’s more to Xcode build configuration files than meets the eye.
Behold!
Retaining Existing Values
To append rather than replace existing definitions,
use the $(inherited) variable like so:
BUILD_SETTING_NAME = $(inherited)additional value
You typically do this to build up lists of values,
such as the paths in which
the compiler searches for frameworks
to find included header files
(FRAMEWORK_SEARCH_PATHS):
You can conditionalize build settings according to their
SDK (sdk), architecture (arch), and / or configuration (config)
according to the following syntax:
BUILD_SETTING_NAME[sdk=sdk] = value for specified sdkBUILD_SETTING_NAME[arch=architecture] = value for specified architectureBUILD_SETTING_NAME[config=configuration] = value for specified configuration
Given a choice between multiple definitions of the same build setting,
the compiler resolves according to specificity.
BUILD_SETTING_NAME[sdk=sdk][arch=architecture] = value for specified sdk and architecturesBUILD_SETTING_NAME[sdk=*][arch=architecture] = value for all other sdks with specified architecture
For example,
you might specify the following build setting
to speed up local builds by only compiling for the active architecture:
A build configuration file can include settings from other configuration files
using the same #include syntax
as the equivalent C directive
on which this functionality is based:
#include "path/to/File.xcconfig"
As we’ll see later on in the article,
you can take advantage of this to build up cascading lists of build settings
in really powerful ways.
Creating Build Configuration Files
To create a build configuration file,
select the “File > New File…” menu item (⌘N),
scroll down to the section labeled “Other”,
and select the Configuration Settings File template.
Next, save it somewhere in your project directory,
making sure to add it to your desired targets
Once you’ve created an xcconfig file,
you can assign it to one or more build configurations
for its associated targets.
Now that we’ve covered the basics of using Xcode build configuration files
let’s look at a couple of examples of how you can use them
to manage development, stage, and production environments.
Customizing App Name and Icon for Internal Builds
Developing an iOS app usually involves
juggling various internal builds
on your simulators and test devices
(as well as the latest version from the App Store,
to use as a reference).
You can make things easier on yourself
with xcconfig files that assign each configuration
a distinct name and app icon.
If your backend developers comport themselves according to the aforementioned
12 Factor App philosophy,
then they’ll have separate endpoints for
development, stage, and production environments.
On iOS,
perhaps the most common approach to managing these environments
is to use conditional compilation statements
with build settings like DEBUG.
importFoundation#if DEBUG
let apiBaseURL = URL(string: "https://api.example.dev")!letapiKey="9e053b0285394378cf3259ed86cd7504"#else
let apiBaseURL = URL(string: "https://api.example.com")!letapiKey="4571047960318d233d64028363dfa771"#endif
This gets the job done,
but runs afoul of the canon of code / configuration separation.
An alternative approach takes these environment-specific values
and puts them where they belong —
into xcconfig files.
However,
to pull these values programmatically,
we’ll need to take one additional step:
Accessing Build Settings from Swift
Build settings defined by
the Xcode project file, xcconfig files, and environment variables,
are only available at build time.
When you run the compiled app,
none of that surrounding context is available.
(And thank goodness for that!)
But wait a sec —
don’t you remember seeing some of those build settings before
in one of those other tabs?
Info, was it?
As it so happens,
that info tab is actually just a fancy presentation of
the target’s Info.plist file.
At build time,
that Info.plist file is compiled
according to the build settings provided
and copied into the resulting app bundle.
Therefore,
by adding references to $(API_BASE_URL) and $(API_KEY),
you can access the values for those settings
through the infoDictionary property of Foundation’s Bundle API.
Neat!
Following this approach,
we might do something like the following:
importFoundationenumConfiguration{staticfuncvalue<T>(forkey:String)->T{guardletvalue=Bundle.main.infoDictionary?[key]as?Telse{fatalError("Invalid or missing Info.plist key: \(key)")}returnvalue}}enumAPI{staticvarbaseURL:URL{returnURL(string:"https://"+Configuration.value(for:"API_BASE_URL"))!}staticvarkey:String{returnConfiguration.value(for:"API_KEY")}}
When viewed from the call site,
we find that this approach harmonizes beautifully
with our best practices —
not a single hard-coded constant in sight!
Xcode projects are monolithic, fragile, and opaque.
They’re a source of friction for collaboration among team members
and generally a drag to work with.
Fortunately,
xcconfig files go a long way to address these pain points.
Moving configuration out of Xcode and into xcconfig files
confers a multitude of benefits
and offers a way to distance your project from the particulars of Xcode
without leaving a Cupertino-approved happy path.
I just left a hipster coffee shop.
It was packed with iOS devs,
whispering amongst each other about how
they can’t wait for Apple to release
an official style guide and formatter for Swift.
Lately,
the community has been buzzing about
the proposal from
Tony Allevato and
Dave Abrahams
to adopt an official style guide and formatting tool for the Swift language.
Hundreds of community members have weighed in on the
initial pitch
and proposal.
As with all matters of style,
opinions are strong, and everybody has one.
Fortunately,
the discourse from the community
has been generally constructive and insightful,
articulating a diversity of viewpoints, use cases, and concerns.
In spite of this,
Swift code formatting remains a topic of interest to many developers.
So this week on NSHipster,
we’re taking another look at the current field of
Swift formatters available today —
including the swift-format tool released as part of the proposal —
and see how they all stack up.
From there,
we’ll take a step back and try to put everything in perspective.
But first,
let’s start with a question:
What is Code Formatting?
For our purposes,
we’ll define code formatting
as any change made to code that makes it easier to understand
without changing its behavior.
Although this definition extends to differences in equivalent forms,
(e.g. [Int] vs. Array<Int>),
we’ll limit our discussion here to whitespace and punctuation.
Swift, like many other programming languages,
is quite liberal in its acceptance of newlines, tabs, and spaces.
Most whitespace is insignificant,
having no effect on the code around from the compiler’s point of view.
When we use whitespace to make code more comprehensible
without changing its behavior,
that’s an example of
secondary notation;
the primary notation, of course,
being the code itself.
Put enough semicolons in the right places,
and you can write pretty much anything in a single line of code.
But all things being equal,
why not use horizontal and vertical whitespace
to visually structure code in a way that’s easier for us to understand,
right?
Unfortunately,
the ambiguity created by the compiler’s accepting nature of whitespace
can often cause confusion and disagreement among programmers:
“Should I add a newline before a curly bracket?
How do I break up statements that extend beyond the width of the editor?”
Organizations often codify guidelines for how to deal with these issues,
but they’re often under-specified, under-enforced, and out-of-date.
The role of a code formatter
is to automatically enforce a set of conventions
so that programmers can set aside their differences
and get to work solving actual problems.
Formatter Tool Comparison
The Swift community has considered questions of style from the very beginning.
Style guides have existed from the very first days of Swift,
as have various open source tools to automate the process
of formatting code to match them.
To get a sense of the current state of Swift code formatters,
we’ll take a look at the following four tools:
To establish a basis of comparison,
we’ve contrived the following code sample to evaluate each tool
(using their default configuration):
structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
Although code formatting encompasses a wide range of possible
syntactic and semantic transformations,
we’ll focus on newlines and indentation,
which we believe to be baseline requirements for any code formatter.
SwiftFormat
First up is
SwiftFormat,
a tool as helpful as it is self-descriptive.
You can install it by running the following command:
$ brew install swiftformat
In addition,
SwiftFormat also provides an Xcode Source Editor Extension,
found in the EditorExtension,
which you can use to reformat code in Xcode.
Or, if you’re a user of VSCode,
you can invoke SwiftFormat with
this plugin.
Usage
The swiftformat command formats each Swift file
found in the specified file and directory paths.
$ swiftformat Example.swift
SwiftFormat has a variety of rules that can be configured
either individually via command-line options
or using a configuration file.
Example Output
Running the swiftformat command on our example
using the default set of rules produces the following result:
// swiftformat version 0.40.8structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
As you can see,
this is a clear improvement over the original.
Each line is indented according to its scope,
and each declaration has consistent spacing between punctuation.
Both the semicolon in the property declarations
and the newline in the initializer parameters are preserved;
however, the closing curly braces aren’t moved to separate lines
as might be expectedthis is fixed in 0.39.5.
Great work, Nick!
Performance
SwiftFormat is consistently the fastest of the tools tested in this article,
completing in a few milliseconds.
$time swiftformat Example.swift
0.03 real 0.01 user 0.01 sys
SwiftLint
Next up is,
SwiftLint,
a mainstay of the Swift open source community.
With over 100 built-in rules,
SwiftLint can perform a wide variety of checks on your code —
everything from preferring AnyObject over class for class-only protocols
to the so-called “Yoda condition rule”,
which prescribes variables to be placed on
the left-hand side of comparison operators
(that is, if n == 42 not if 42 == n).
As its name implies,
SwiftLint is not primarily a code formatter;
it’s really a diagnostic tool for identifying
convention violation and API misuse.
However, by virtue of its auto-correction faculties,
it’s frequently used to format code.
Installation
You can install SwiftLint using Homebrew
with the following command:
Running the previous command on our example
yields the following:
// swiftlint version 0.32.0structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
SwiftLint cleans up the worst of the indentation and inter-spacing issues
but leaves other, extraneous whitespace intact
(though it does strip the file’s leading newline, which is nice).
Again, it’s worth noting that formatting isn’t SwiftLint’s primary calling;
if anything, it’s merely incidental to providing actionable code diagnostics.
And taken from the perspective of “first, do no harm”,
it’s hard to complain about the results here.
Performance
For everything that SwiftLint checks for,
it’s remarkably snappy —
completing in a fraction of a second for our example.
$time swiftlint autocorrect --quiet--format--path Example.swift
0.09 real 0.04 user 0.01 sys
Prettier with Swift Plugin
Installation, Usage, Example Output, and Performance
If you’ve mostly shied away from JavaScript
(as discussed in last week’s article),
this may be the first you’ve heard of
Prettier.
On the other hand,
if you’re steeped in the world of ES6, React, and WebPack,
you’ve almost certainly come to rely on it.
Prettier is unique among code formatters in that it optimizes —
first and foremost —
for aesthetics,
wrapping lines of code onto newlines as if they were poetry.
To use Prettier and its plugin for Swift,
you’ll have to wade into the murky waters of the Node packaging ecosystem.
There are a few different approaches to get everything installed
(because of course there are),
but Yarn is our favorite 😻.
$ brew install yarn
$ yarn global add prettier prettier/plugin-swift
Usage
With the prettier command-line tool accessible from our $PATH,
run it with one or more file or directory paths.
$ prettier Example.swift
Example Output
Here’s the result of running the latest build of the Swift plugin with Prettier
on our example from before:
// Swift version 4.2// prettier version 1.17.1// prettier/plugin-swift version 0.0.0 (bdf8726)structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:StringvarpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
Prettier describes itself to be “An opinionated code formatter”.
In practice, this means that there isn’t much in the way of configuration;
there are only two options: “regular code” and “prettier code”.
Now, you may object to the increase in vertical whitespace,
but you’d be lying if you said this code didn’t look amazing.
The way that everything is evenly spaced…
the way that long lines are wrapped and indented…
it’s almost hard to believe that you achieve something like this automatically.
Of course, our caveat from before still applies:
This is still very much a work-in-progress
and isn’t suitable for production use yet.
Also, there’s the matter of performance…
Performance
To put it bluntly:
Prettier is an order of magnitude slower
than every other tool discussed in this article.
$time prettier Example.swift
0.60 real 0.40 user 0.18 sys
It’s unclear whether this is
a consequence of navigating a language barrier or
an opportunity for optimization,
but Prettier is slow enough to cause problems at scale.
For now,
we recommend using Prettier only for one-off formatting tasks,
such as writing code for articles and books.
swift-format
Having looked at the current landscape of available Swift formatters,
we now have a reasonable baseline for evaluating the swift-format tool
proposed by Tony Allevato and Dave Abrahams.
For your convenience,
we’re providing a Homebrew formula that builds from
our own fork of Google’s fork,
which you can install with the following command:
$ brew install nshipster/formulae/swift-format
Usage
Run the swift-format command,
passing one or more file and directory paths
to Swift files that you want to format.
$ swift-format Example.swift
The swift-format command also takes a --configuration option,
which takes a path to a JSON file.
For now,
the easiest way to customize swift-format behavior
is to dump the default configuration to a file
and go from there.
Using its default configuration,
here’s how swift-format formats our example:
// swift-format 0.0.1 (2019-05-15, 115870c)structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=regionself.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
Be still my heart!😍
We could do without the original semicolon,
but overall, this is pretty unobjectionable —
which is exactly what you’d want from an official code style tool.
Flexible Output
But in order to fully appreciate the elegance of swift-format’s output,
we must compare it across a multitude of different column widths.
Let’s see how it handles this new code sample,
replete with cumbersome UIApplicationDelegate methods
and URLSession construction:
40 Columns
importUIKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{leturl=URL(string:"https://nshipster.com/swift-format")!URLSession.shared.dataTask(with:url,completionHandler:{(data,response,error)inguarderror==nil,letdata=data,letresponse=responseas?HTTPURLResponse,(200..<300).contains(response.statusCode)else{fatalError(error?.localizedDescription??"Unknown error")}iflethtml=String(data:data,encoding:.utf8){print(html)}}).resume()// Override point for customization after application launch.returntrue}}
50 Columns
importUIKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{leturl=URL(string:"https://nshipster.com/swift-format")!URLSession.shared.dataTask(with:url,completionHandler:{(data,response,error)inguarderror==nil,letdata=data,letresponse=responseas?HTTPURLResponse,(200..<300).contains(response.statusCode)else{fatalError(error?.localizedDescription??"Unknown error")}iflethtml=String(data:data,encoding:.utf8){print(html)}}).resume()// Override point for customization after application launch.returntrue}}
60 Columns
importUIKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{leturl=URL(string:"https://nshipster.com/swift-format")!URLSession.shared.dataTask(with:url,completionHandler:{(data,response,error)inguarderror==nil,letdata=data,letresponse=responseas?HTTPURLResponse,(200..<300).contains(response.statusCode)else{fatalError(error?.localizedDescription??"Unknown error")}iflethtml=String(data:data,encoding:.utf8){print(html)}}).resume()// Override point for customization after application launch.returntrue}}
70 Columns
importUIKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{leturl=URL(string:"https://nshipster.com/swift-format")!URLSession.shared.dataTask(with:url,completionHandler:{(data,response,error)inguarderror==nil,letdata=data,letresponse=responseas?HTTPURLResponse,(200..<300).contains(response.statusCode)else{fatalError(error?.localizedDescription??"Unknown error")}iflethtml=String(data:data,encoding:.utf8){print(html)}}).resume()// Override point for customization after application launch.returntrue}}
90 Columns
importUIKit@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{leturl=URL(string:"https://nshipster.com/swift-format")!URLSession.shared.dataTask(with:url,completionHandler:{(data,response,error)inguarderror==nil,letdata=data,letresponse=responseas?HTTPURLResponse,(200..<300).contains(response.statusCode)else{fatalError(error?.localizedDescription??"Unknown error")}iflethtml=String(data:data,encoding:.utf8){print(html)}}).resume()// Override point for customization after application launch.returntrue}}
This kind of flexibility isn’t particularly helpful in engineering contexts,
where developers can and should make full use of their screen real estate.
But for those of us who do technical writing
and have to wrestle with things like mobile viewports and page margins,
this is a killer feature.
Performance
In terms of performance,
swift-format isn’t so fast as to feel instantaneous,
but not so slow as to be an issue.
$time swift-format Example.swift
0.24 real 0.16 user 0.14 sys
Conclusion: You Don’t Need To Wait to Start Using a Code Formatting Tool
Deciding which conventions we want to adopt as a community
is an important conversation to have,
worthy of the thoughtful and serious consideration we give
to any proposed change to the language.
However,
the question of whether there should be official style guidelines
or an authoritative code formatting tool
shouldn’t stop you from taking steps today for your own projects!
We’re strongly of the opinion that
most projects would be improved by the adoption of a code formatting tool,
provided that it meets the following criteria:
It’s stable
It’s fast (enough)
It produces reasonable output
And based on our survey of the tools currently available,
we can confidently say that
SwiftFormat
and
swift-format
both meet these criteria,
and are suitable for use in production.
(If we had to choose between the two,
we’d probably go with swift-format on aesthetic grounds.
But each developer has different preferences
and each team has different needs,
and you may prefer something else.)
While you’re evaluating tools to incorporate into your workflow,
you’d do well to try out
SwiftLint,
if you haven’t already.
In its linting capacity,
SwiftLint can go a long way to systematically improving code quality —
especially for projects that are
older and larger and have a large number of contributors.
The trouble with the debate about code style is that its large and subjective.
By adopting these tools in our day-to-day workflows today,
we not only benefit from better, more maintainable code today,
but we can help move the debate forward,
from vague feelings to precise observations about any gaps that still remain.
Apple’s press release
announcing the company’s annual developer conference
featured an illustration of a robot head (🤖)
bursting with code symbols, emoji, and Apple iconography.
All last week,
attendees saw this motif in slides and signage
throughout McEnery Convention Center,
with variations featuring heads of
monkeys (🐵), aliens (👽), skulls (💀),
and other Animoji characters.
Beyond presenting a compelling case for several 🤯-based additions to
Unicode’s recommended emoji ZWJ sequences,
these banners reinforced a central theme of the conference:
“Write code. Blow minds.”
Apple’s slogan was accurate but ambiguous;
it wasn’t us that was writing code, but Apple.
We were the ones getting our #mindsblown.
The problem with having your mind blown is that you can’t think straight.
Now that we’re safely removed from the Reality Distortion Field™
(some of us recovering from #dubdubflu🤒, perhaps),
let’s take a moment to process the announcements from last week as a whole,
and consider what they mean for
next week, next month, next year, and the years to come.
SwiftUI, Swift, and Open Source
SwiftUI is the most exciting announcement from Apple
since unveiling the Swift language itself five years prior.
That said,
for a language being developed in the open
with a rigorous process for vetting proposed changes,
many of us in the community thought
Swift was immune from surprise announcements at WWDC.
For better or for worse, we were wrong.
At the Platforms State of the Union,
few of us in the audience recognized the SwiftUI code.
It looked like the Swift we know and love,
but it had a bunch of new adornments
and seemed to play fast and loose with our understanding of syntax rules.
The SwiftUI announcement serves a reminder that Swift is Apple’s language.
Fortunately,
this arrangement has worked out pretty well so far —
thanks in large part to good faith efforts from Apple engineers
to communicate openly and
take their responsibility as stewards of the language seriously.
Case in point:
within hours of the Keynote,
Swift’s project leader, Ted Kremenek,
posted a message
outlining the situation,
and proposals for the new features
were submitted for review the same day
(though one might
wonder what that means in practice).
Also, to their credit,
the rest of the Core Team has done an outstanding job
communicating with attendees at labs and answering questions on social media.
What happened at WWDC this year underscores a central tension between
open community-driven contributions
and the closed product-driven development within Apple.
There’s already an active discussion
about how this dynamic can and should play out for SwiftUI and Combine.
Ultimately, it’s up to Apple to decide whether these technologies
will be incorporated into the Swift open source project.
However, I’m optimistic that they’ll make the right choice in due course.
SwiftUI, Catalyst, and the Long-Term Viability of Apple Platforms
I don’t think it’s an exaggeration to say that
Apple’s announcements of Catalyst and SwiftUI this year
saved macOS from becoming obsolete as a platform.
With AppKit’s increasing irrelevance in the era of UIKit,
few businesses have been able to justify investment into native macOS apps.
As a result,
the macOS ecosystem has languished,
becoming a shadow of its glory days of the
delicious generation.
At WWDC,
Apple laid out a clear path forward for developers:
If you’re working on an existing iOS app,
focus on making the most of the new features on iOS 13,
like Dark Mode support and Sign In with Apple ID.
If you have an iPad app,
consider adding support for macOS Catalina by way of Catalyst.
If you’re creating your n+1th app,
weigh the potential benefit of adopting SwiftUI
against the costs and risks associated with developing with new technologies
(this calculation will change over time).
If you’re looking to break into app development, or want to experiment,
start a new Xcode project, check the box next to SwiftUI, and have a blast!
There’s never been a better time to try everything out.
That’s essentially the decision tree Apple would have us follow.
As others have eloquently opined,
SwiftUI marks an important transition for Apple as a company
from the NeXT era to the Swift era.
By way of historical analogy:
However,
something’s missing from this narrative
of how we got from Macintosh OS to Mac OS X.
What about Java(Script)?
Everyone knows Java was deprecated in Mac OS X 10.4.
What this section presupposes is… maybe it didn’t?
The transition from C to Objective-C may seem inevitable today,
but at the time,
there was a real possibility that Objective-C
would be replaced by a more popular language —
namely, Java.
The situation in 2019 bears a striking resemblance to what happened in 1999.
With web technologies only becoming more capable and convenient over time,
can you really blame them for going with a “hybrid solution” —
especially one that promises to target every platform with a single code base?
We must remember that the purpose of software is to solve problems.
Consumers only care about which technologies we decide to use
insofar as it provides a better solution, more quickly, at a lower price.
Yes, there are plenty of examples of cross-platform or web-based technologies
that look and feel janky and out of place on the Mac or an iPhone.
But there are plenty of instances where the results are good
(or at least “good enough”).
Don’t let perfect be the enemy of good.
Or, put another way:
1GB of RAM for an Electron app isn’t great,
but it’s still better than the 0GB of RAM
for a native app that never ships.
Apple’s exciting new technology empowers Twitter to
easily bring our entire 1.5 million line code base from iOS to the Mac,
allowing ongoing full feature parity with our iPad app,
and enhanced with the macOS experience
that will make Twitter feel right at home on your Mac.
@TwitterSupport
Go ahead and try it for yourself:
open https://twitter.com/ in Chrome,
click the ⠇ icon on the far right side of your address bar
and select “Install Twitter”.
And lest you think this is just a Google thing,
Apple has also (quietly) improved its own support of PWAs over the years.
iOS 12.2, in particular, was a huge step forward,
addressing some of the most pressing shortcomings on the platform.
And the first iOS 13 beta promises to be even better for PWAs
(though you’d never know it based on this year’s conference sessions).
We’re not quite there yet,
but with forthcoming support for
Web App Manifest in WebKit,
we’re getting really close.
When the iPhone first debuted in 2007,
the intent was for 3rd-party developers to distribute software through web apps.
The strong distinction between native and web apps
is a product of history,
not an immutable truth about software.
When you think about the future of “apps”,
you have to wonder if this distinction will still make sense in 5 or 10 years.
Aside from ubiquitous high-speed wireless internet,
there’s nothing particularly futuristic about
downloading a 50MB payload from the App Store.
Apple, Politics, and the Global Economy
Apple’s transition from the NeXT era to the Swift era
goes beyond technical changes within the company.
The day after delivering the WWDC Keynote,
Tim Cook sat down for
an interview with CBS News,
in which he defended Apple against allegations of monopolistic behavior.
A month prior,
the U.S. Supreme Court
issued a decision in
Apple Inc. v. Pepper
affirming that consumers were “direct purchasers” of apps from the App Store,
and could sue Apple for antitrust practices.
All of this at a time when there’s growing political momentum in the U.S.
to break up big tech companies like Google, Amazon, Facebook, and Apple.
Meanwhile,
slowing iPhone sales in the U.S. and Europe,
difficulty entering emerging markets in China and India,
and the threat of a prolonged U.S. / China trade war
loom large over Apple’s future.
For the past decade,
the App Store has shielded many of us from the complexities of doing business
with the promise of being able to build an app and carve out a livelihood.
To the extent that this was ever true,
we may soon have to confront some deeply held assumptions about our industry.
This is all to say that there’s a big world outside the Apple ecosystem.
I don’t doubt that SwiftUI is the future of macOS and iOS development.
I’m thrilled that Apple is embracing a declarative UI paradigm on its platforms,
and couldn’t be more excited for functional reactive ideas
to spread to millions of new developers as a result.
I just worry that fixating on SwiftUI
will come at the expense of solving more important problems.
Mind the Enthusiasm Gap
At the time of writing,
SwiftUI has been public for what… like a week?
And yet,
if you haven’t spent every waking hour playing with SwiftUI,
you might have the impression that you’re already too late.
Before you drop everything to catch up on session videos
or start to feel bad for “falling behind,”
remember that we’ve been here before.
The Swift programming language
took a long time to become viable for app development.
Years.
Today, Swift 5 is barely recognizable from what we first saw in 2014.
All of those developers who rushed to learn it when it first came out
(myself included)
how many of us look back on all of that code churn fondly?
How many teams would have been better off
holding onto their Objective-C code bases for a few more years
instead of deciding to rewrite everything from scratch at the first chance?
By all means,
try out Xcode 11 on the latest macOS Catalina beta
to appreciate the full extent of its live canvas.
It’s insanely great!
But don’t feel beholden to it.
Don’t upend your work for the sake of staying ahead of the curve.
There are plenty of other things that you can focus on now
that will have a more immediate impact on your users and your own career.
Silver Bullets and Universal Truths
SwiftUI solves some of the most visible problems facing Apple platforms,
but they aren’t the most important or the most difficult ones —
not by a long shot.
Taking care of yourself —
sleeping enough, eating right, exercising regularly —
will do more to improve your productivity
than any language or framework out there.
Your ability to communicate and collaborate with others
will always be a better predictor of success
than your choice of technology stack.
Your relationships with others
are the most significant factors of success and happiness in life.
Apple’s annual release cycle
affords us all an opportunity each June to reflect on how far we’ve come
and where we want to go from here —
both individually and as a community.
Don’t be afraid to think different.
SwiftUI cast a large shadow over everything announced at WWDC.
So now that we’ve gotten this out of our system,
we look forward to digging into the
hundreds of new APIs and dozens of new frameworks coming later this year.
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 @-prefixed
attributes.
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 first
pitched 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 make open up functionality like lazy
so that library authors can implement similar functionality themselves.
The proposal does an excellent job outlining its design and implementation.
So rather than attempt to improve on this explanation,
we thought it’d be interesting to look at
some new patterns that property wrappers make possible —
and, in the process,
get a better handle on how we might use this feature in our projects.
So, for your consideration,
here are four potential use cases for the new @propertyWrapper attribute:
SE-0258 offers plenty of practical examples, including
@Lazy, @Atomic, @ThreadSpecific, and @Box.
But the one we were most excited about
was that of the @Constrained property wrapper.
Swift’s standard library offer correct,
performant floating-point number types,
and you can have it in any size you want —
so long as it’s
32 or
64(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 since
Swift 3.
However, doing so would require conformance to a
labyrinth of protocol requirements:
Pulling this off is no small feat,
and often far too much work to justify
for most use cases.
Fortunately,
property wrappers offer a way to parameterize
standard number types with significantly less effort.
Implementing a value clamping property wrapper
Consider the following Clamping structure.
As a property wrapper (denoted by the @propertyWrapper attribute),
it automatically “clamps” out-of-bound values
within the prescribed range.
A @Positive / @NonNegative property wrapper
that provides the unsigned guarantees to signed integer types.
A @NonZero property wrapper
that ensures that a number value is either greater than or less than 0.
@Validated or @Whitelisted / @Blacklisted property wrappers
that restrict which values can be assigned.
Transforming Values on Property Assignment
Accepting text input from users
is a perennial headache among app developers.
There are just so many things to keep track of,
from the innocent banalities of string encoding
to malicious attempts to inject code through a text field.
But among the most subtle and frustrating problems
that developers face when accepting user-generated content
is dealing with leading and trailing whitespace.
A single leading space can
invalidate URLs,
confound date parsers,
and sow chaos by way of off-by-one errors:
When it comes to user input,
clients most often plead ignorance
and just send everything as-is to the server.
¯\_(ツ)_/¯.
While I’m not advocating for client apps to take on more of this responsibility,
the situation presents another compelling use case for Swift property wrappers.
Foundation bridges the trimmingCharacters(in:) method to Swift strings,
which provides, among other things,
a convenient way to lop off whitespace
from both the front or back of a String value.
Calling this method each time you want to ensure data sanity is, however,
less convenient.
And if you’ve ever had to do this yourself to any appreciable extent,
you’ve certainly wondered if there might be a better approach.
In your search for a less ad-hoc approach,
you may have sought redemption through the willSet property callback…
only to be disappointed that you can’t use this
to change events already in motion.
structPost{vartitle:String{willSet{title=newValue.trimmingCharacters(in:.whitespacesAndNewlines)/* ⚠️ Attempting to store to property 'title' within its own willSet,
which is about to be overwritten by the new value */}}}
From there,
you may have realized the potential of didSet
as an avenue for greatness…
only to realize later that didSet isn’t called for
during initial property assignment.
structPost{vartitle:String{// 😓 Not called during initializationdidSet{self.title=title.trimmingCharacters(in:.whitespacesAndNewlines)}}}
Undeterred,
you may have tried any number of other approaches…
ultimately finding none to yield an acceptable combination of
ergonomics and performance characteristics.
If any of this rings true to your personal experience,
you can rejoice in the knowledge that your search is over:
property wrappers are the solution you’ve long been waiting for.
Implementing a Property Wrapper that Trims Whitespace from String Values
Consider the following Trimmed struct
that trims whitespaces and newlines from incoming string values.
By marking each String property in the Post structure below
with the @Trimmed annotation,
any string value assigned to title or body—
whether during initialization or via property access afterward —
automatically has its leading or trailing whitespace removed.
structPost{@Trimmedvartitle:String@Trimmedvarbody:String}letquine=Post(title:" Swift Property Wrappers ",body:"...")quine.title// "Swift Property Wrappers" (no leading or trailing spaces!)quine.title=" @propertyWrapper "quine.title// "@propertyWrapper" (still no leading or trailing spaces!)
Related Ideas
A @Transformed property wrapper that applies
ICU 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 API
caseInsensitiveCompare(_:):
Construct two string values that differ only by case,
and they’ll return false for a standard equality check,
but true when wrapped in a CaseInsensitive object.
So far, this approach is indistinguishable from
the custom “wrapper type” approach described above.
And this is normally where we’d start the long slog of
implementing conformance to ExpressibleByStringLiteral
and all of the other protocols
to make CaseInsensitive start to feel enough like String
to feel good about our approach.
Property wrappers allow us to forego all of this busywork entirely:
Here, Account objects are checked for equality
by a case-insensitive comparison on their name property value.
However, when we go to get or set the name property,
it’s a bona fideString value.
That’s neat, but what’s actually going on here?
Since Swift 4,
the compiler automatically synthesizes Equatable conformance
to types that adopt it in their declaration
and whose stored properties are all themselves Equatable.
Because of how compiler synthesis is implemented (at least currently),
wrapped properties are evaluated through their wrapper
rather than their underlying value:
// Synthesized by Swift CompilerextensionAccount:Equatable{staticfunc==(lhs:Account,rhs:Account)->Bool{lhs.$name==rhs.$name}}
Related Ideas
Defining @CompatibilityEquivalence
such that wrapped String properties with the values "①" and "1"
are considered equal.
A @Approximate property wrapper to refine
equality semantics for floating-point types
(See also SE-0259)
A @Ranked property wrapper that takes a function
that defines strict ordering for, say, enumerated values;
this could allow, for example,
the playing card rank .ace to be treated either low or high
in different contexts.
Auditing Property Access
Business requirements may stipulate certain controls
for who can access which records when
or prescribe some form of accounting for changes over time.
Once again,
this isn’t a task typically performed by, say, an iOS app;
most business logic is defined on the server,
and most client developers would like to keep it that way.
But this is yet another use case too compelling to ignore
as we start to look at the world through property-wrapped glasses.
Implementing a Property Value Versioning
The following Versioned structure functions as a property wrapper
that intercepts incoming values and creates a timestamped record
when each value is set.
A hypothetical ExpenseReport class could
wrap its state property with the @Versioned annotation
to keep a paper trail for each action during processing.
An @Audited property wrapper
that logs each time a property is read or written to.
A @Decaying property wrapper
that divides a set number value each time
each time the value is read.
However,
this particular example highlights a major limitation in
the current implementation of property wrappers
that stems from a longstanding deficiency of Swift generally:
Properties can’t be marked as throws.
Without the ability to participate in error handling,
property wrappers don’t provide a reasonable way to
enforce and communicate policies.
For example,
if we wanted to extend the @Versioned property wrapper from before
to prevent state from being set to .approved after previously being .denied,
our best option is fatalError(),
which isn’t really suitable for real applications:
This is just one of several limitations
that we’ve encountered so far with property wrappers.
In the interest of creating a balanced perspective on this new feature,
we’ll use the remainder of this article to enumerate them.
Limitations
Properties Can’t Participate in Error Handling
Properties, unlike functions,
can’t be marked as throws.
As it were,
this is one of the few remaining distinctions between
these two varieties of type members.
Because properties have both a getter and a setter,
it’s not entirely clear what the right design would be
if we were to add error handling —
especially when you consider how to play nice with syntax for other concerns
like access control, custom getters / setters, and callbacks.
As described in the previous section,
property wrappers have but two methods of recourse
to deal with invalid values:
Ignoring them (silently)
Crashing with fatalError()
Neither of these options is particularly great,
so we’d be very interested by any proposal that addresses this issue.
Wrapped Properties Can’t Be Aliased
Another limitation of the current proposal is that
you can’t create use instances of property wrappers as property wrappers.
Our UnitInterval example from before,
which constrains wrapped values between 0 and 1 (inclusive),
could be succinctly expressed as:
typealiasUnitInterval=Clamping(0...1)// ❌
However, this isn’t possible.
Nor can you use instances of property wrappers to wrap properties.
All this actually means in practice is more code replication than would be ideal.
But given that this problem arises out of a fundamental distinction
between types and values in the language,
we can forgive a little duplication if it means avoiding the wrong abstraction.
Property Wrappers Are Difficult To Compose
Composition of property wrappers is not a commutative operation;
the order in which you declare them
affects how they’ll behave.
Consider the interplay between an attribute that
performs string inflection
and other string transforms.
For example,
a composition of property wrappers
to automatically normalize the URL “slug” in a blog post
will yield different results if spaces are replaced with dashes
before or after whitespace is trimmed.
structPost{...@Dasherized@Trimmedvarslug:String}
But getting that to work in the first place is easier said than done!
Attempting to compose two property wrappers that act on String values fails,
because the outermost wrapper is acting on a value of the innermost wrapper type.
@propertyWrapperstructDasherized{private(set)varvalue:String=""varwrappedValue:String{get{value}set{value=newValue.replacingOccurrences(of:"",with:"-")}}init(initialValue:String){self.wrappedValue=initialValue}}structPost{...@Dasherized@Trimmedvarslug:String// ⚠️ An internal error occurred.}
There’s a way to get this to work,
but it’s not entirely obvious or pleasant.
Whether this is something that can be fixed in the implementation
or merely redressed by documentation remains to be seen.
A dependent type is a type defined by its value.
For instance,
“a pair of integers in which the latter is greater than the former” and
“an array with a prime number of elements”
are both dependent types
because their type definition is contingent on its value.
Swift’s lack of support for dependent types in its type system
means that any such guarantees must be enforced at run time.
The good news is that property wrappers
get closer than any other language feature proposed thus far
in filling this gap.
However,
they still aren’t a complete replacement for true value-dependent types.
You can’t use property wrappers to,
for example,
define a new type with a constraint on which values are possible.
These shortcomings are by no means deal-breakers;
property wrappers are extremely useful
and fill an important gap in the language.
It’ll be interesting to see whether the addition of property wrappers
will create a renewed interest in bringing dependent types to Swift,
or if they’ll be seen as “good enough”,
obviating the need to formalize the concept further.
Property Wrappers Are Difficult to Document
Pop Quiz:
Which property wrappers are made available by the SwiftUI framework?
In fairness, this failure isn’t unique to property wrappers.
If you were tasked with determining
which protocol was responsible for a particular API in the standard library
or which operators were supported for a pair of types
based only on what was documented on developer.apple.com,
you’re likely to start considering a mid-career pivot away from computers.
This lack of comprehensibility is made all the more dire
by Swift’s increasing complexity.
Property Wrappers Further Complicate Swift
Swift is a much, much more complex language than Objective-C.
That’s been true since Swift 1.0 and has only become more so over time.
The profusion of @-prefixed features in Swift —
whether it’s @dynamicMemberLookup
and
@dynamicCallable
from Swift 4,
or
@differentiable and @memberwise
from Swift for Tensorflow—
makes it increasingly difficult
to come away with a reasonable understanding of Swift APIs
based on documentation alone.
In this respect,
the introduction of @propertyWrapper will be a force multiplier.
How will we make sense of it all?
(That’s a genuine question, not a rhetorical one.)
Alright, let’s try to wrap this thing up —
Swift property wrappers allow library authors access to the kind of
higher-level behavior previously reserved for language features.
Their potential for improving safety and reducing complexity of code is immense,
and we’ve only yet to scratch the surface of what’s possible.
Yet, for all of their promise,
property wrappers and its cohort of language features debuted alongside SwiftUI
introduce tremendous upheaval to Swift.
When I was a student in Japan,
I worked part-time at a restaurant —
アルバイト as the locals call it —
where I was tasked with putting away dishes during downtime.
Every plate had to be stacked neatly,
ready to serve as the canvas for the next gastronomic creation.
Lucky for me,
the universal laws of physics came in quite handy —
all I had to do was pile things up, rather indiscriminately, and move on to the next task.
In contrast,
iOS developers often have to jump through several conceptual hoops when laying out user interfaces.
After all,
placing things in an upside-down 2D coordinate system is not intuitive for anyone but geometry geeks;
for the rest of us, it’s not that cut and dried.
But wait —
what if we could take physical world concepts like gravity and elasticity
and appropriate them for UI layouts?
As it turns out,
there has been no shortage of attempts to do so since the early years of
GUIs
and personal computing —
Motif’s XmPanedWindow and Swing’s BoxLayout are notable early specimens.
These widgets are often referred to as stack-based layouts,
and three decades later,
they are alive and well on all major platforms,
including Android’s LinearLayout and CSS flexbox,
as well as Apple’s own NSStackView, UIStackView, and —
new in SwiftUI —
HStack, VStack, and ZStack.
This week on NSHipster,
we invite you to enjoy a multi-course course
detailing the most delicious morsels
of this most versatile of layout APIs: UIStackView.
Hors-d’œuvres🍱 Conceptual Overview
Stacking layout widgets come in a wide variety of flavors.
Even so, they all share one common ingredient:
leaning on our intuition of the physical world to keep the configuration layer as thin as possible.
The result is a declarative API that doesn’t concern the developer with the minutiæ of view placement and sizing.
If stacking widgets were stoves,
they’d have two distinct sets of knobs:
Knobs that affect the items it contains
Knobs that affect the stack container itself
Together, these knobs describe how the available space is allotted;
whenever a new item is added,
the stack container recalculates the size and placement of all its contained items,
and then lets the rendering pipeline take care of the rest.
In short,
the raison d’être of any stack container is to ensure that all its child items get a slice of the two-dimensional,
rectangular pie.
Appetizer 🥗 UIStackView Essentials
Introduced in iOS 9,
UIStackView is the most recent addition to the UI control assortment in Cocoa Touch.
On the surface,
it looks similar to its older AppKit sibling, the NSStackView,
but upon closer inspection,
the differences between the two become clearer.
Managing Subviews
In iOS, the subviews managed by the stack view are referred to as the arranged subviews.
You can initialize a stack view with an array of arranged subviews,
or add them one by one after the fact. Let’s imagine that you have a set of magical plates, the kind that can change their size at will:
letsaladPlate=UIView(...)letappetizerPlate=UIView(...)letplateStack=UIStackView(arrangedSubviews:[saladPlate,appetizerPlate])// orletsidePlate=UIView(...)letbreadPlate=UIView(...)letanotherPlateStack=UIStackView(...)anotherPlateStack.addArrangedSubview(sidePlate)anotherPlateStack.addArrangedSubview(breadPlate)// Use the `arrangedSubviews` property to retrieve the platesanotherPlateStack.arrangedSubviews.count// 2
Adding an arranged view using any of the methods above also makes it a subview of the stack view.
To remove an arranged subview that you no longer want around,
you need to call removeFromSuperview() on it.
The stack view will automatically remove it from the arranged subview list.
In contrast,
calling removeArrangedSubview(_ view: UIView) on the stack view will only remove the view passed as a parameter from the arranged subview list,
without removing it from the subview hierarchy.
Keep this distinction in mind if you are modifying the stack view content during runtime.
One major benefit of using stack views over custom layouts is their built-in support for toggling subview visibility without causing layout ambiguity;
whenever the isHidden property is toggled for one of the arranged subviews,
the layout is recalculated,
with the possibility to animate the changes inside an animation block:
This feature is particularly useful when the stack view is part of a reusable view such as table and collection view cells;
not having to keep track of which constraints to toggle is a bliss.
Now, let’s resume our plating work, shall we?
With everything in place, let’s see what can do with our arranged plates.
Arranging Subviews Horizontally and Vertically
The first stack view property you will likely interact with is the axis property.
Through it you can specify the orientation of the main axis,
that is the axis along which the arranged subviews will be stacked.
Setting it to either horizontal or vertical will force all subviews to fit into a single row or a single column,
respectively.
This means that stack views in iOS do not allow overflowing subviews to wrap into a new row or column,
unlike other implementations such CSS flexbox and its flex-wrap property.
The orientation that is perpendicular to the main axis is often referred to as the cross axis.
Even though this distinction is not explicit in the official documentation,
it is one of the main ingredients in any stacking algorithm —
without it, any attempt at explaining how stack views work will be half-baked.
Stack view axes in horizontal and vertical orientations.
The default orientation of the main axis in iOS is horizontal;
not ideal for our dishware, so let’s fix that:
plateStack.axis=.vertical
Et voilà!
Entrée🍽 Configuring the Layout
When we layout views,
we’re accustomed to thinking in terms of origin and size.
Working with stack views, however, requires us to instead think in terms of main axis and cross axis.
Consider how a horizontally-oriented stack view works.
To determine the width and the x coordinate of the origin for each of its arranged subviews,
it refers to a set of properties that affect layout across the horizontal axis.
Likewise, to determine the height and the y coordinate,
it refers to another set of properties that affects the vertical axis.
The UIStackView class provides axis-specific properties to define the layout: distribution for the main axis, and alignment for the cross axis.
The Main Axis: Distribution
The position and size of arranged subviews along the main axis is affected in part by the value of the distribution property,
and in part by the sizing properties of the subviews themselves.
In practice, each distribution option will determine how space along the main axis is distributed between the subviews.
With all distributions,
save for fillEqually, the stack view attempts to find an optimal layout based on the intrinsic sizes of the arranged subviews.
When it can’t fill the available space, it stretches
the arranged subview with the the lowest content hugging priority.
When it can’t fit all the arranged subviews,
it shrinks the one with the lowest compression resistance priority.
If the arranged subviews share the same value for content hugging and compression resistance,
the algorithm will determine their priority based on their indices.
With that out of the way, let’s take a look at the possible outcomes,
staring the distributions that prioritize preserving the intrinsic content size of each arranged subview:
equalSpacing: The stack view gives every arranged subview its intrinsic size alongside the main axis, then introduces equally-sized paddings if there is extra space.
equalCentering: Similar to equalSpacing, but instead of spacing subviews equally, a variably sized padding is introduced in-between so as the center of each subview alongside the axis is equidistant from the two adjacent subview centers.
Examples of equalSpacing and equalCentering in both horizontal and vertical orientations. The dashed lines and values between parentheses represent the intrinsic sizes of each subview.
In contrast, the following distributions prioritize filling the stack container, regardless of the intrinsic content size of its subviews:
fill (default): The stack view ensures that the arranged subviews fill all the available space. The rules mentioned above apply.
fillProportionally: Similar to fill, but instead of resizing a single view to fill the remaining space, the stack view proportionally resizes all subviews based on their intrinsic content size.
fillEqually: The stack view ensures that the arranged views fill all the available space and are all the same size along the main axis.
Examples of fill distributions in both horizontal and vertical orientations.
The Cross Axis: Alignment
The third most important property of UIStackView is alignment.
Its value affects the positioning and sizing of arranged subviews along the cross axis.
That is, the Y axis for horizontal stacks,
and X axis for vertical stacks.
You can set it to one of the following values for both vertical and horizontal stacks:
fill (default): The stack view ensures that the arranged views fill all the available space on the cross axis.
leading/trailing: All subviews are aligned to the leading or trailing edge of the stack view along the cross axis. For vertical stacks, these correspond to the top edge and bottom edge respectively. For horizontal stacks, the language direction will affect the outcome: in left-to-right languages the leading edge will correspond to the left, while the trailing one will correspond to the right. The reverse is true for right-to-left languages.
center: The arranged subviews are centered along the cross axis.
For horizontal stacks, four additional options are available, two of which are redundant:
top: Behaves exactly like leading.
firstBaseline: Behaves like top, but uses the first baseline of the subviews instead of their top anchor.
bottom: Behaves exactly like trailing.
lastBaseline: Behaves like bottom, but uses the last baseline of the subviews instead of their bottom anchor.
Coming back to our plates,
let’s make sure that they fill the available vertical space, all while saving the unused horizontal space for other uses —
remember, these can shape-shift!
Another quirk of stack views in iOS is that they don’t directly support setting a background color. You have to go through their backing layer to do so.
Alright, we’ve come quite far,
but have a couple of things to go over before our dégustation is over.
Dessert 🍮 Spacing & Auto Layout
By default,
a stack view sets the spacing between its arranged subviews to zero.
The value of the spacing property is treated as an exact value for distributions that attempt to fill the available space
(fill, fillEqually, fillProportionally),
and as a minimum value otherwise (equalSpacing, equalCentering).
With fill distributions, negative spacing values cause the subviews to overlap and the last subview to stretch, filling the freed up space.
Negative spacing values have no effect on equal centering or spacing distributions.
plateStack.spacing=2// These plates can float too!
The spacing property applies equally between each pair of arranged subviews.
To set an explicit spacing between two particular subviews,
use the setCustomSpacing(:after:) method instead.
When a custom spacing is used alongside the equalSpacing distribution,
it will be applied on all views,
not just the one specified in the method call.
To retrieve the custom space later on, customSpacing(after:) gives that to you on a silver platter.
Sometimes you need more control over the sizing and placement of an arranged subview.
In those cases,
you may add custom constraints on top of the ones generated by the stack view.
Since the latter come with a priority of 1000,
make sure all of your custom constraints use a priority of 999 or less to avoid unsatisfiable layouts.
For vertical stack views,
the API lets you calculate distances from the subviews’ baselines,
in addition to their top and bottom edges.
This comes in handy when trying to maintain a vertical rhythm in text-heavy UIs.
plateStack.isBaselineRelativeArrangement=true// Spacing will be measuerd from the plates' lips, not their wells.
L’addition s’il vous plaît!
The automatic layout calculation that stack views do for us come with a performance cost.
In most cases,
it is negligible.
But when stack views are nested more than two layers deep,
the hit could become noticeable.
To be on the safe side,
avoid using deeply nested stack views,
especially in reusable views such as table and collection view cells.
After Dinner Mint 🍬 SwiftUI Stacks
With the introduction of SwiftUI during last month’s WWDC,
Apple gave us a sneak peek at how we will be laying out views in the months and years to come:
HStack, VStack, and ZStack.
In broad strokes,
these views are specialized stacking views where the main axis is pre-defined for each subtype and the alignment configuration is restricted to the corresponding cross axis.
This is a welcome change that alleviates the UIStackView API shortcomings highlighted towards the end of cross axis section above.
There are more interesting tidbits to go over, but we will leave that for another banquet.
Stack views are a lot more versatile than they get credit for.
Their API on iOS isn’t always the most self-explanatory,
nor is it the most coherent,
but once you overcome these hurdles,
you can bend them to your will to achieve non-trivial feats —
nothing short of a Michelin star chef boasting their plating prowess.
Etymologically, confetti comes from the Italian word
for the sugar-coated almond sweet thrown at celebrations,
which, in turn, get their name from the Latin conficio:
con- (“with, together”) +
facio (“do, make”);
in another sense, “to celebrate”.
Confetti gets tossed around a lot these days,
but not nearly as in the 20th century
with its iconic ticker-tape parades
down the streets of New York City,
like the one welcoming home the Apollo 11 astronauts
50 years ago.
Alas, the rise of digital technology made obsolete the stock tickers
whose waste paper tape comprised the substrate of those spectacles.
And as a result, the tradition has become much less commonplace today.
This week mark’s NSHipster’s 7th anniversary!
And what better way to celebrate the occasion
than to implement a fun and flexible confetti view on iOS?
Let’s dive right in with a quick refresher on
the difference between views and layers:
Views and Layers
On iOS,
each view is backed by a layer
…or perhaps it’s more accurate to say that layers are fronted by view.
Because despite their reputation as the workhorse of UIKit,
UIView delegates the vast majority of its functionality to CALayer.
Sure, views handle touch events and autoresizing,
but beyond that,
nearly everything else between your code and the pixels on screen
is the responsibility of layers.
Among the available CALayer subclasses
in the Quartz Core / Core Animation framework
there are APIs for displaying large amounts of content
by scrolling
and tiling,
there are APIs for doing
advancedtransformations,
and there are APIs that let you get at the bare
metal.
But our focus today is a special class called
CAEmitterLayer.
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 circle
case triangle
case square
case 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:
The next step is to implement the emitter layer itself.
The primary responsibility of CAEmitterLayer
is to configure its cells.
Confetti rains down from above
with just enough variation in its size, speed, and spin to make it interesting.
We use the passed array of Content values
to set the contents of the cell (a CGImage)
and a fill color (a CGColor).
We’ll call this configure(with:) method from our confetti view,
which will be our next and final step.
Implementing ConfettiView
We want our confetti view to emit confetti for a certain amount of time
and then stop.
However, accomplishing this is surprisingly difficult,
as evidenced by the questions floating around on
Stack Overflow.
The central problem is that
Core Animation operates on its own timeline,
which doesn’t always comport with our own understanding of time.
For instance,
if you neglect to initialize the beginTime of the emitter layer
with CACurrentMediaTime() right before it’s displayed,
it’ll render with the wrong time space.
As far as stopping goes:
you can tell the layer to stop emitting particles
by setting its birthRate property to 0.
But if you start it again up
by resetting that property to 1,
you get a flurry of particles filling the screen
instead of the nice initial burst on the first launch.
Suffice to say that there are myriad different approaches
to making this behave as expected.
But here’s the best solution we’ve found
for handling starting and stopping,
as well as having more than one emitter at the same time:
Going back to our original explanation for a moment,
each instance of UIView
(or one of its subclasses)
is backed by a single, corresponding instance of CALayer
(or one of its subclasses).
A view may also host one or more additional layers,
either as siblings or children to the backing layer.
Taking advantage of this fact,
we can create a new emitter layer each time we fire our confetti cannon.
We can add that layer as a hosted sublayer for our view,
and let the view handle animation and disposal of each layer
in a nice, self-contained way.
There’s a lot of code to unpack here,
so let’s focus on its distinct parts:
❶
First, we create an instance of our custom CAEmitterLayer subclass
(creatively named Layer here, because it’s a private, nested type).
It’s set up with our configure(with:) method from before
and added as a sublayer.
The needsDisplayOnBoundsChange property defaults to false
for whatever reason;
setting it to true here allows us to better handle device trait changes
(like rotation or moving to a new window).
❷
Next, we create a keyframe animation
to taper the birthRate property down to 0 over the specified duration.
❸
Then we add that animation in a transaction
and use the completion block to set up a fade-out transition.
(We set the view as the transition’s animation delegate,
as described in the next session)
❹
Finally, we use key-value coding
to create a reference between the emitter layer and the transition
so that it can be referenced and cleaned up later on.
The animationDidStop(_:) delegate method is called
when our CATransition finishes.
We then get the reference to the calling layer
in order to remove all animations and remove it from its superlayer.
The end result:
it’s as if NSHipster.com were parading down the streets of New York City
(or rather, Brooklyn, if you really wanted to lean into the hipster thing)
I’ll post the full sample code later this week,
once I have a chance to spruce it up,
get proper tests and documentation,
and run it through Instruments a few more times.
…which doesn’t make for a particularly snazzy conclusion.
So instead,
we’ll end with a bonus round detailing seven other ways
that you could implement confetti instead:
✨Bonus ✨ 7 Alternative Approaches to Confetti
SpriteKit Particle System
SpriteKit is the cooler, younger cousin to UIKit,
providing nodes to games rather than views to apps.
On their surface,
they couldn’t look more different from one another.
And yet both share a common, deep reliance on layers,
which makes for familiar lower-level APIs.
The comparison between these two frameworks goes even deeper,
as you’ll find if you open
File > New, scroll down to “Resource”
and create a new SpriteKit Particle System file.
Open it up, and Xcode provides a specialized editor
reminiscent of Interface Builder.
Call up your designed SKEmitterNode by name
or reimplement in code
(if you’re the type to hand-roll all of your UIViews)
for a bespoke confetti experience.
SceneKit Particle System
Again with the metaphors,
SceneKit is to 3D what SpriteKit is to 2D.
In Xcode 11,
open File > New,
select SceneKit Scene File under the “Resource” heading,
and you’ll find an entire 3D scene editor —
right there in your Xcode window.
Add in a dynamic physics body and a turbulence effect,
and you can whip up an astonishingly capable simulation in no time at all
(though if you’re like me,
you may find yourself spending hours just playing around with everything)
UIKit Dynamics
At the same time that SpriteKit entered the scene,
you might imagine that UIKit started to get self-conscious about the
“business only” vibe it was putting out.
So in iOS 7,
in a desperate attempt to prove itself cool to its “fellow kids”
UIKit added UIDynamicAnimator as part of a set of APIs known as
“UIKit Dynamics”.
Good news!
Later this year,
AVFoundation adds support for alpha channels in HEVC video!
So if, say, you already had a snazzy After Effects composition of confetti,
you could export that with a transparent background
and composite it directly to your app or on the web.
Of course,
this time next year, we’ll still be sending animated GIFs around to each other,
despite all of their shortcomings.
Animated GIFs are especially awful for transparency.
Without a proper alpha channel,
GIFs are limited to a single, transparent matte color,
which causes unsightly artifacts around edges.
We all know to use PNGs on the web for transparent images,
but only a fraction of us are even aware that
APNG is even a thing.
Well even fewer of us know that iOS 13 adds native support for APNG
(as well as animated GIFs — finally!).
Not only were there no announcements at WWDC this year,
but the only clue of this new feature’s existence is
an API diff between Xcode 11 Beta 2 and 3.
Here’s the documentation stub for the function,
CGAnimateImageAtURLWithBlock
And if you think that’s (unfortunately) par for the course these days,
it gets worse.
Because for whatever reason,
the relevant header file (ImageIO/CGImageAnimation.h)
is inaccessible in Swift!
Now, we don’t really know how this is supposed to work,
but here’s our best guess:
But really,
we could do away with all of this confetti
and express ourselves much more simply:
letmood="🥳"
It’s hard to believe that it’s been seven years since I started this site.
We’ve been through a lot together, dear reader,
so know that your ongoing support means the world to me.
Thanks for learning with me over the years.
Until next week:
May your code continue to compile and inspire.
Conversion is a tireless errand in software development.
Most programs boil down to some variation of
transforming data into something more useful.
In the case of user-facing software,
making data human-readable is an essential task —
and a complex one at that.
A user’s preferred language, calendar, and currency
can all factor into how information should be displayed,
as can other constraints, such as a label’s dimensions.
All of this is to say:
calling description on an object just doesn’t cut it
under most circumstances.
Indeed,
the real tool for this job is Formatter:
an ancient, abstract class deep in the heart of the Foundation framework
that’s responsible for transforming data into textual representations.
Formatter’s origins trace back to NSCell,
which is used to display information and accept user input in
tables, form fields, and other views in AppKit.
Much of the API design of (NS)Formatter reflects this.
Back then,
formatters came in two flavors: dates and numbers.
But these days,
there are formatters for everything from
physical quantities and time intervals to personal names and postal addresses.
And as if that weren’t enough to keep straight,
a good portion of these have been
soft-deprecated,
or otherwise superseded by more capable APIs (that are also formatters).
To make sense of everything,
this week’s article groups each of the built-in formatters
into one of four categories:
NumberFormatter covers every aspect of number formatting imaginable.
For better or for worse
(mostly for better),
this all-in-one API handles
ordinals and cardinals,
mathematical and scientific notation,
percentages,
and monetary amounts in various flavors.
It can even write out numbers in a few different languages!
So whenever you reach for NumberFormatter,
the first order of business is to establish
what kind of number you’re working with
and set the numberStyle property accordingly.
Number Styles
Number Style
Example Output
none
123
decimal
123.456
percent
12%
scientific
1.23456789E4
spellOut
one hundred twenty-three
ordinal
3rd
currency
$1234.57
currencyAccounting
($1234.57)
currencyISOCode
USD1,234.57
currencyPlural
1,234.57 US dollars
Rounding & Significant Digits
To prevent numbers from getting annoyingly pedantic
(“thirty-two point three three — repeating, of course…”),
you’ll want to get a handle on NumberFormatter’s rounding behavior.
Here, you have two options:
Set usesSignificantDigits to true
to format according to the rules of
significant figures
Set usesSignificantDigits to false(or keep as-is, since that’s the default)
to format according to specific limits on
how many decimal and fraction digits to show
(the number of digits leading or trailing the decimal point, respectively).
If you need specific rounding behavior,
such as “round to the nearest integer” or “round towards zero”,
check out the
roundingMode,
roundingIncrement, and
roundingBehavior properties.
Locale Awareness
Nearly everything about the formatter can be customized,
including the
grouping separator,
decimal separator,
negative symbol,
percent symbol,
infinity symbol,
and
how to represent zero values.
Although these settings can be overridden on an individual basis,
it’s typically best to defer to the defaults provided by the user’s locale.
MeasurementFormatter
MeasurementFormatter was introduced in iOS 10 and macOS 10.12
as part of the full complement of APIs for performing
type-safe dimensional calculations:
Unit subclasses represent units of measure,
such as count and ratio
Dimension subclasses represent dimensional units of measure,
such as mass and length,
(which is the case for the overwhelming majority of
the concrete subclasses provided,
on account of them being dimensional in nature)
A Measurement is a quantity of a particular Unit
A UnitConverter converts quantities of one unit to
a different, compatible unit
For the curious, here's the complete list of units supported by MeasurementFormatter:
MeasurementFormatter and its associated APIs are a intuitive —
just a delight to work with, honestly.
The only potential snag for newcomers to Swift
(or Objective-C old-timers, perhaps)
are the use of generics to constrain Measurement values
to a particular Unit type.
importFoundation// "The swift (Apus apus) can power itself to a speed of 111.6km/h"letspeed=Measurement<UnitSpeed>(value:111.6,unit:.kilometersPerHour)letformatter=MeasurementFormatter()formatter.string(from:speed)// 69.345 mph
Configuring the Underlying Number Formatter
By delegating much of its formatting responsibility to
an underlying NumberFormatter property,
MeasurementFormatter maintains a high degree of configurability
while keeping a small API footprint.
Readers with an engineering background may have noticed that
the localized speed in the previous example
gained an extra significant figure along the way.
As discussed previously,
we can enable usesSignificantDigits and set maximumSignificantDigits
to prevent incidental changes in precision.
A MeasurementFormatter,
by default,
will use the preferred unit for the user’s current locale (if one exists)
instead of the one provided by a Measurement value.
Readers with a non-American background certainly noticed that
the localized speed in the original example
converted to a bizarre, archaic unit of measure known as “miles per hour”.
You can override this default unit localization behavior
by passing the providedUnit option.
DateFormatter is the OG class
for representing dates and times.
And it remains your best, first choice
for the majority of date formatting tasks.
For a while,
there was a concern that it would become overburdened with responsibilities
like its sibling NumberFormatter.
But fortunately,
recent SDK releases spawned new formatters for new functionality.
We’ll talk about those in a little bit.
Date and Time Styles
The most important properties for a DateFormatter object are its
dateStyle and timeStyle.
As with NumberFormatter and its numberStyle,
these date and time styles provide preset configurations
for common formats.
Style
Example Output
Date
Time
none
“”
“”
short
“11/16/37”
“3:30 PM”
medium
“Nov 16, 1937”
“3:30:32 PM”
long
“November 16, 1937”
“3:30:32 PM”
full
“Tuesday, November 16, 1937 AD
“3:30:42 PM EST”
letdate=Date()letformatter=DateFormatter()formatter.dateStyle=.longformatter.timeStyle=.longformatter.string(from:date)// July 15, 2019 at 9:41:00 AM PSTformatter.dateStyle=.shortformatter.timeStyle=.shortformatter.string(from:date)// "7/16/19, 9:41:00 AM"
NSDateFormatter*formatter=[[NSDateFormatteralloc]init];[formattersetDateStyle:NSDateFormatterLongStyle];[formattersetTimeStyle:NSDateFormatterLongStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// July 15, 2019 at 9:41:00 AM PST[formattersetDateStyle:NSDateFormatterShortStyle];[formattersetTimeStyle:NSDateFormatterShortStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// 7/16/19, 9:41:00 AM
dateStyle and timeStyle are set independently.
So,
to display just the time for a particular date,
for example,
you set dateStyle to none:
letformatter=DateFormatter()formatter.dateStyle=.noneformatter.timeStyle=.mediumletstring=formatter.string(from:Date())// 9:41:00 AM
NSDateFormatter*formatter=[[NSDateFormatteralloc]init];[formattersetDateStyle:NSDateFormatterNoStyle];[formattersetTimeStyle:NSDateFormatterMediumStyle];NSLog(@"%@",[formatterstringFromDate:[NSDatedate]]);// 9:41:00 AM
As you might expect, each aspect of the date format can alternatively be configured individually, a la carte. For any aspiring time wizards NSDateFormatter has a bevy of different knobs and switches to play with.
ISO8601DateFormatter
When we wrote our first article about NSFormatter back in 2013,
we made a point to include discussion of
Peter Hosey’s ISO8601DateFormatter’s
as the essential open-source library
for parsing timestamps from external data sources.
Fortunately,
we no longer need to proffer a third-party solution,
because, as of iOS 10.0 and macOS 10.12,
ISO8601DateFormatter is now built-in to Foundation.
letformatter=ISO8601DateFormatter()formatter.date(from:"2019-07-15T09:41:00-07:00")// Jul 15, 2019 at 9:41 AM
DateIntervalFormatter
DateIntervalFormatter is like DateFormatter,
but can handle two dates at once —
specifically, a start and end date.
“6:03:28 PM Pacific Standard Time - 7:50:08 PM Pacific Standard Time”
DateComponentsFormatter
As the name implies,
DateComponentsFormatter works with DateComponents values
(previously),
which contain a combination of discrete calendar quantities,
such as “1 day and 2 hours”.
DateComponentsFormatter provides localized representations of date components
in several different, pre-set formats:
Some years ago,
formatters introduced the concept of formatting context,
to handle situations where
the capitalization and punctuation of a localized string may depend on whether
it appears at the beginning or middle of a sentence.
A context property is available for DateComponentsFormatter,
as well as DateFormatter, NumberFormatter, and others.
Formatting Context
Output
standalone
“About 2 hours”
listItem
“About 2 hours”
beginningOfSentence
“About 2 hours”
middleOfSentence
“about 2 hours”
dynamic
Depends*
*
A Dynamic context changes capitalization automatically
depending on where it appears in the text
for locales that may position strings differently
depending on the content.
RelativeDateTimeFormatter
RelativeDateTimeFormatter is a newcomer in iOS 13 —
and at the time of writing, still undocumented,
so consider this an NSHipster exclusive scoop!
Longtime readers may recall that
DateFormatter actually gave this a try circa iOS 4
by way of the doesRelativeDateFormatting property.
But that hardly ever worked,
and most of us forgot about it, probably.
Fortunately,
RelativeDateTimeFormatter succeeds
where doesRelativeDateFormatting fell short,
and offers some great new functionality to make your app
more personable and accessible.
(As far as we can tell,)
RelativeDatetimeFormatter takes the most significant date component
and displays it in terms of past or future tense
(“1 day ago” / “in 1 day”).
letformatter=RelativeDateTimeFormatter()formatter.localizedString(from:DateComponents(day:1,hour:1))// "in 1 day"formatter.localizedString(from:DateComponents(day:-1))// "1 day ago"formatter.localizedString(from:DateComponents(hour:3))// "in 3 hours"formatter.localizedString(from:DateComponents(minute:60))// "in 60 minutes"
For the most part,
this seems to work really well.
However, its handling of nil, zero, and net-zero values
leaves something to be desired…
formatter.localizedString(from:DateComponents(hour:0))// "in 0 hours"formatter.localizedString(from:DateComponents(day:1,hour:-24))// "in 1 day"formatter.localizedString(from:DateComponents())// ""
Styles
Style
Example
abbreviated
“1 mo. ago” *
short
“1 mo. ago”
full
“1 month ago”
spellOut
“one month ago”
*May produce output distinct from short for non-English locales.
Using Named Relative Date Times
By default,
RelativeDateTimeFormatter adopts the formulaic convention
we’ve seen so far.
But you can set the dateTimeStyle property to .named
to prefer localized deictic expressions—
“tomorrow”, “yesterday”, “next week” —
whenever one exists.
importFoundationletformatter=RelativeDateTimeFormatter()formatter.localizedString(from:DateComponents(day:-1))// "1 day ago"formatter.dateTimeStyle=.namedformatter.localizedString(from:DateComponents(day:-1))// "yesterday"
This just goes to show that
beyond calendrical and temporal relativity,
RelativeDateTimeFormatter is a real whiz at linguistic relativity, too!
For example,
English doesn’t have a word to describe the day before yesterday,
whereas other languages, like German, do.
formatter.localizedString(from:DateComponents(day:-2))// "2 days ago"formatter.locale=Locale(identifier:"de_DE")formatter.localizedString(from:DateComponents(day:-2))// "vorgestern"
Hervorragend!
Formatting People and Places
Class
Example Output
Availability
PersonNameComponentsFormatter
“J. Appleseed”
iOS 9.0+ macOS 10.11+
CNContactFormatter
“Applessed, Johnny”
iOS 13.0+ macOS 10.15+
CNPostalAddressFormatter
“1 Infinite Loop\n Cupertino CA 95014”
iOS 13.0+ macOS 10.15+
PersonNameComponentsFormatter
PersonNameComponentsFormatter is a sort of high water mark for Foundation.
It encapsulates one of the hardest,
most personal problems in computer
in such a way to make it accessible to anyone
without requiring a degree in Ethnography.
The documentation
does a wonderful job illustrating the complexities of personal names
(if I might say so myself),
but if you had any doubt of the utility of such an API,
consider the following example:
CNPostalAddressFormatter provides a convenient Formatter-based API
to functionality dating back to the original AddressBook framework.
The following example formats a constructed CNMutablePostalAddress,
but you’ll most likely use existing CNPostalAddress values
retrieved from the user’s address book.
letaddress=CNMutablePostalAddress()address.street="One Apple Park Way"address.city="Cupertino"address.state="CA"address.postalCode="95014"letaddressFormatter=CNPostalAddressFormatter()addressFormatter.string(from:address)/* "One Apple Park Way
Cupertino CA 95014" */
Styling Formatted Attributed Strings
When formatting compound values,
it can be hard to figure out where each component went
in the final, resulting string.
This can be a problem when you want to, for example,
call out certain parts in the UI.
Rather than hacking together an ad-hoc,
regex-based solution,
CNPostalAddressFormatter provides a method that vends an
NSAttributedString that lets you identify
the ranges of each component
(PersonNameComponentsFormatter does this too).
The NSAttributedString API is…
to put it politely,
bad.
It feels bad to use.
So for the sake of anyone hoping to take advantage of this functionality,
please copy-paste and appropriate the following code sample
to your heart’s content:
Rounding out our survey of formatters in the Apple SDK,
it’s another new addition in iOS 13:
ListFormatter.
To be completely honest,
we didn’t know where to put this in the article,
so we just kind of stuck it on the end here.
(Though in hindsight,
this is perhaps appropriate given the subject matter).
NSListFormatter provides locale-correct formatting of a list of items
using the appropriate separator and conjunction.
Note that the list formatter is unaware of
the context where the joined string will be used,
e.g., in the beginning of the sentence
or used as a standalone string in the UI,
so it will not provide any sort of capitalization customization on the given items,
but merely join them as-is.
The string joined this way may not be grammatically correct when placed in a sentence,
and it should only be used in a standalone manner.
tl;dr:
This is joined(by:) with locale-aware serial and penultimate delimiters.
For simple lists of strings,
you don’t even need to bother with instantiating ListFormatter—
just call the localizedString(byJoining:) class method.
importFoundationletoperatingSystems=["macOS","iOS","iPadOS","watchOS","tvOS"]ListFormatter.localizedString(byJoining:operatingSystems)// "macOS, iOS, iPadOS, watchOS, and tvOS"
ListFormatter works as you’d expect
for lists comprising zero, one, or two items.
ListFormatter.localizedString(byJoining:[])// ""ListFormatter.localizedString(byJoining:["Apple"])// "Apple"ListFormatter.localizedString(byJoining:["Jobs","Woz"])// "Jobs and Woz"
Lists of Formatted Values
ListFormatter exposes an underlying itemFormatter property,
which effectively adds a map(_:) before calling joined(by:).
You use itemFormatter whenever you’d formatting a list of non-String elements.
For example,
you can set a NumberFormatter as the itemFormatter for a ListFormatter
to turn an array of cardinals (Int values)
into a localized list of ordinals.
letnumberFormatter=NumberFormatter()numberFormatter.numberStyle=.ordinalletlistFormatter=ListFormatter()listFormatter.itemFormatter=numberFormatterlistFormatter.string(from:[1,2,3])// "1st, 2nd, and 3rd"
As some of the oldest members of the Foundation framework,
NSNumberFormatter and NSDateFormatter
are astonishingly well-suited to their respective domains,
in that way only decade-old software can.
This tradition of excellence is carried by the most recent incarnations as well.
If your app deals in numbers or dates
(or time intervals or names or lists measurements of any kind),
then NSFormatter is indispensable.
And if your app doesn’t…
then the question is,
what does it do, exactly?
Invest in learning all of the secrets of Foundation formatters
to get everything exactly how you want them.
And if you find yourself with formatting logic scattered across your app,
consider creating your own Formatter subclass
to consolidate all of that business logic in one place.
Product design is about empathy.
Knowing what a user wants,
what they like,
what they dislike,
what causes them frustration,
and learning to understand and embody those motivations —
this is what it takes to make something insanely great.
There is, however,
one critical factor that app developers often miss:
network condition,
or more specifically,
the latency and bandwidth of an Internet connection.
For something so essential to user experience,
it’s unfortunate that most developers take an ad-hoc approach
to field-testing their apps under different conditions
(if at all).
This week on NSHipster,
we’ll be talking about the
Network Link Conditioner,
a utility that allows macOS and iOS devices
to accurately and consistently simulate adverse networking environments.
Installation
Network Link Conditioner can be found
in the “Additional Tools for Xcode” package.
You can download this from the
Downloads for Apple Developers
page.
Search for “Additional Tools”
and select the appropriate release of the package.
Once the download has finished,
open the DMG,
navigate to the “Hardware” directory,
and double-click “Network Link Condition.prefPane”.
Click on the Network Link Conditioner preference pane
at the bottom of System Preferences.
Controlling Bandwidth, Latency, and Packet Loss
Enabling the Network Link Conditioner
changes the network environment system-wide
according to the selected configuration,
limiting uplink or download
bandwidth,
latency, and rate of
packet loss.
You can choose from one of the following presets:
100% Loss
3G
DSL
EDGE
High Latency DNS
LTE
Very Bad Network
WiFi
WiFi 802.11ac
…or create your own according to your particular requirements.
Now try running your app with the Network Link Conditioner enabled:
How does network latency affect your app startup?
What effect does bandwidth have on table view scroll performance?
Does your app work at all with 100% packet loss?
Enabling Network Link Conditioner on iOS Devices
Although the preference pane works well for developing on the simulator,
it’s also important to test on a real device.
Fortunately,
the Network Link Conditioner is available for iOS as well.
To use the Network Link Conditioner on iOS,
set up your device for development:
Connect your iOS device to your Mac
In Xcode, navigate to Window > Organizer
Select your device in the sidebar
Click “Use for Development”
Now you’ll have access to the Developer section of the Settings app.
You can enable and configure the Network Link Conditioner
on your iOS device under Settings > Developer > Networking.
(Just remember to turn it off after you’re done testing!).
For a while now,
the distinction between “desktop” and “mobile”
has become increasingly tenuous.
As the computers in our pockets grow ever more capable,
they more closely resemble the computers typically situated on our desks and laps.
This trend was especially pronounced in this year’s WWDC,
with the announcement of
Catalyst and
iPadOS.
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 that
makes a computer a “desktop” computer in the traditional sense;
when you purchase an external keyboard for your iPad,
you do so to make it “desktop”-like.
But for many others —
including those of us with a physical disability —
a typewriter-like keyboard is but one of many input methods
available to desktop users.
This week on NSHipster,
we’re taking a look at the macOS Accessibility Keyboard.
Beyond its immediate usefulness as an assistive technology,
the Accessibility Keyboard challenges us to think differently
about the nature of input methods
and any remaining distinction between mobile and desktop computers.
Introduced in macOS High Sierra,
the Accessibility Keyboard
lets you type and interact with your Mac
without the use of a physical keyboard.
To turn it on,
open System Preferences,
click the Accessibility preference pane,
select “Keyboard” under the “Interactions” section in the sidebar.
(Alternatively, you can search for “Accessibility Keyboard”
and navigate to the first result).
Click the checkbox labeled “Enable Accessibility Keyboard”
to present the accessibility keyboard over the windows of the frontmost app.
The software keyboard reproduces the layout of your hardware keyboard.
The modifier keys outlined in red (⌘, ⇧, ⌥)
are “sticky keys”
and remain active until a non-“sticky” key is activated,
allowing for capital letters and keyboard shortcuts.
Along the top row are iOS-style suggestions
that update automatically as you type.
However, the most interesting feature of the Accessibility Keyboard
is tucked behind the ⚙︎ button on the top right corner —
the ability to customize and create your own keyboards!
Customizing and Creating Your Own Accessibility Keyboards
Panel Editor is a built-in app
that lets you edit Accessibility Keyboard panels.
For something so obscure,
the Panel Editor app is remarkably well made.
Adding, moving, and editing buttons on a panel is a cinch.
You can even click and drag to select and move multiple buttons at once,
and group buttons together at your convenience.
Each button has a name
as well as options for font size, color, and position.
By default, the name appears in the button itself,
but you can specify an image to display instead.
You can configure a button to perform any one of the following actions
when clicked:
None
Go Back (navigate between panels in Accessibility Keyboard)
Open Panel
Show / Hide Toolbar
Dwell (relevant to head- or eye-tracking technology, and other hardware switches)
AppleScript
Enter Text
Press Keys
Open App
System Event
Typing Suggestions
Of these,
“Enter Text” is the most common.
We’ll use that in our next example
as a way to solve the problem of
creating input methods for scripts without a keyboard.
Creating an IPA Keyboard
Standard Latin script is insufficient for expressing phonetics,
how a word sounds when spoken.
As English speakers, we know this all too well.
That’s why linguists invented their own script,
the International Phonetic Alphabet
(IPA).
Whereas typical letters may have different pronunciations
across dialects (/tə.ˈme͡ɪ.do͡ʊ/, /tə.ˈmɑ.to͡ʊ/) —
or even within the same word (like the letter “a” in “application”) —
IPA symbols represent a single sound, or phoneme;
the mid-central vowel, “ə” (a.k.a “schwa”)
sounds the same whether its part of
an English word or a Japanese word or nonsensical babbling.
Working with IPA on computers has pretty much always been a PITA,
for three reasons:
Incomplete Text Encoding
Until Unicode version 6.1,
some IPA symbols didn’t have a specified code point,
forcing linguists to either use a similar-looking character
or define ad hoc encodings within a
Private 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 the
IPA 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 hosts
an 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 the
official 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.
The PanelObjects key is associated with an array of dictionaries,
each representing a single button.
Fortunately, he majority of the key names are self-explanatory:
The takeaway from looking at the file format is that
it’d be very easy to generate Accessibility Keyboard panels in code,
rather than using the Panel Editor app.
(In fact, we used find-and-replace to
bulk resize the buttons in the IPA keyboard,
a task that would have otherwise taken 100⨉ longer).
Additional Use Cases
There are dozens of scripts comprising hundreds of characters
that lack a dedicated keyboard layout.
And the macOS Accessibility Keyboard offers a wonderful, built-in solution
for producing these characters.
But what else could you do with this technology,
now that you know it exists?
Here are a few ideas for you to consider:
Templating Frequent Communications
Do you find yourself writing the same things over and over again
in emails or GitHub Issues?
Create a custom, virtual keyboard to summon boilerplate
with the click of your mouse or the tap of your trackpad.
Generative Text
The Accessibility Keyboard isn’t limited to canned responses.
Thanks to its AppleScript integration,
you can populate text dynamically from virtually any source.
For example,
you could create a Fortune button
that inserts a (pseudo)random entry from the
fortune 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 a
drive-time radio DJlive streamer?
Use the Accessibility Keyboard to trigger funny sound effects at will
to delight your throng of fans.
do shell script"afplay /System/Sounds/Sosumi.aiff"
World Domination
AppleScript gives you the closest thing to complete,
programmatic access to the entire system.
Set a button to kick off a build of your app,
or send a message on Slack,
or turn on the lights in your house,
or play your theme song!
The Accessibility Keyboard serves as a powerful, built-in, and omnipresent
interface to whatever functionality you desire —
without going through all the trouble of building an app.
Because, if you think about it,
is there any real difference between
the j key on your keyboard
and a hypothetical Party button on a virtual keyboard?
The strong connection between
the word “computer” and typewriter keyboards
is merely a historical one.
The rise of smartphones and smartwatches help illustrate this.
Any distinction between
the computers in your hand, on your wrist, or on your desk
is ultimately insignificant.
All computers are the same;
they’re all force multipliers.
Once you separate “desktop” computers from the shape of their primary interface,
you can start to fully appreciate the full capabilities
of what’s at our fingertips.
Our humble publication has frequented this topic with some regularity,
whether it was attempting to make sense of
equality in Objective-C
or appreciating the much clearer semantics of Swift
vis-à-vis the Equatable protocol.
Swift 5.1 gives us yet another occasion to ponder this old chestnut
by virtue of the new Identifiable protocol.
We’ll discuss the noumenon of this phenomenal addition to the standard library,
and help you identify opportunities to
realize its potential in your own projects.
But let’s dispense with the navel gazing and
jump right into some substance:
Swift 5.1 adds the Identifiable protocol to the standard library,
declared as follows:
Values of types adopting the Identifiable protocol
provide a stable identifier for the entities they represent.
For example,
a Parcel object may use the id property requirement
to track the package en route to its final destination.
No matter where the package goes,
it can always be looked up by its id:
The Swift Evolution proposal for Identifiable,
SE-0261,
was kept small and focused in order to be incorporated quickly.
So, if you were to ask,
“What do you actually get by conforming to Identifiable?”,
the answer right now is “Not much.”
As mentioned in the future directions,
conformance to Identifiable has the potential to unlock
simpler and/or more optimized versions of other functionality,
such as the new ordered collection diffing APIs.
But the question remains:
“Why bother conforming to Identifiable?”
The functionality you get from adopting Identifiable is primarily semantic,
and require some more explanation.
It’s sort of like asking,
“Why bother conforming to Equatable?”
And actually, that’s not a bad place to start.
Let’s talk first about Equatable and its relation to Identifiable:
Identifiable vs. Equatable
Identifiable distinguishes the identity of an entity from its state.
A parcel from our previous example
will change locations frequently as it travels to its recipient.
Yet a normal equality check (==)
would fail the moment it leaves its sender:
While this is an expected outcome from a small, contrived example,
the very same behavior can lead to confusing results further down the stack,
where you’re not as clear about how different parts work with one another.
If Parcel were to adopt Hashable
and not provide its own implementation of hash(into:),
the result of calling trackedPackages.contains(_:) would be false,
because the synthesized implementation factors both id and location
into the hash value.
If we instead provide our own implementation
that only considers id,
then trackedPackages.contains(_:) would return true,
However,
taking this additional step
flouts a convention for hash values
to be state-dependent.
So long as Equatable semantics hold,
everything should still work as expected,
albeit somewhat slower from dictionary access no longer being
a constant-time (O(1)) operation.
Identifiable and Hashable have similar and related semantics,
but they have some important distinctions:
Unlike identifiers,
hash values are typically state-dependent,
changing when an object is mutated.
Identifiers are stable across launches,
whereas hash values are calculated by randomly generated hash seeds,
making them unstable between launches.
Identifiers are unique,
whereas hash values may collide,
requiring additional equality checks when fetched from a collection.
Identifiers can be meaningful,
whereas hash values are chaotic
by virtue of their hashing functions.
In short,
hash values are similar to
but no replacement for identifiers.
So what makes for a good identifier, anyway?
Choosing ID Types
Aside from conforming to Hashable,
Identifiable doesn’t make any other demands of
its associated ID type requirement.
So what are some good candidates?
If you’re limited to only what’s available in the Swift standard library,
your best options are Int and String.
Include Foundation,
and you expand your options with UUID and URL.
Each has its own strengths and weaknesses as identifiers,
and can be more or less suited to a particular situation:
Int as ID
The great thing about using integers as identifiers
is that (at least on 64-bit systems),
you’re unlikely to run out of them anytime soon.
Most systems that use integers to identify records
assign them in an auto-incrementing manner,
such that each new ID is 1 more than the last one.
Here’s a simple example of how you can do this in Swift:
If you wanted to guarantee uniqueness across launches,
you might instead initialize the sequence with a value
read from a persistent store like UserDefaults.
And if you found yourself using this pattern extensively,
you might consider factoring everything into a self-contained
property wrapper.
Monotonically increasing sequences have a lot of benefits:
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 across
every device that’s running your app, then
congratulations —you’ve hit
a fundamental problem in computer science.
But before you start in on
vector clocks and
consensus 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 the
UUID type.
Thus making adoption to Identifiable with UUIDs trivial:
Beyond minor ergonomic and cosmetic issues,
UUID serves as an excellent alternative to Int
for generated identifiers.
However,
your model may already be uniquely identified by a value,
thereby obviating the need to generate a new one.
Under such circumstances,
that value is likely to be a String.
String as ID
We use strings as identifiers all the time,
whether it takes the form of a username or a checksum or a translation key
or something else entirely.
The main drawback to this approach is that,
thanks to The Unicode® Standard,
strings encode thousands of years of written human communication.
So you’ll need a strategy for handling identifiers like
“⽜”, “𐂌”, “”, and “🐮”
…and that’s to say nothing of the more pedestrian concerns,
like leading and trailing whitespace and case-sensitivity!
Normalization is the key to successfully using strings as identifiers.
The easiest place to do this is in the initializer,
but, again, if you find yourself repeating this code over and over,
property wrappers can help you here, too.
URLs (or URIs if you want to be pedantic)
are arguably the most ubiquitous kind of identifier
among all of the ones described in this article.
Every day, billions of people around the world use URLs
as a way to point to a particular part of the internet.
So URLs a natural choice for an id value
if your models already include them.
URLs look like strings,
but they use syntax
to encode multiple components,
like scheme, authority, path, query, and fragment.
Although these formatting rules dispense with much of the invalid input
you might otherwise have to consider for strings,
they still share many of their complexities —
with a few new ones, just for fun.
The essential problem is that
equivalent URLs may not be equal.
Intrinsic, syntactic details like
case sensitivity,
the presence or absence of a trailing slash (/),
and the order of query components
all affect equality comparison.
As do extrinsic, semantic concerns like
a server’s policy to upgrade http to https,
redirect from www to the apex domain,
or replace an IP address with a
which might cause different URLs to resolve to the same webpage.
If your model gets identifier URLs for records from a trusted source,
then you may take URL equality as an article of faith;
if you regard the server as the ultimate source of truth,
it’s often best to follow their lead.
But if you’re working with URLs in any other capacity,
you’ll want to employ some combination of
URL normalizations
before using them as an identifier.
Unfortunately, the Foundation framework doesn’t provide
a single, suitable API for URL canonicalization,
but URL and URLComponents provide enough on their own
to let you roll your own
(though we’ll leave that as an exercise for the reader):
UUID and URL both look like strings,
but they use syntax rules to encode information in a structured way.
And depending on your app’s particular domain,
you may find other structured data types that
would make for a suitable identifier.
Thanks to the flexible design of the Identifiable protocol,
there’s nothing to stop you from implementing your own ID type.
For example,
if you’re working in a retail space,
you might create or repurpose an existing
UPC type
to serve as an identifier:
As Identifiable makes its way into codebases,
you’re likely to see it used in one of three different ways:
The newer the code,
the more likely it will be for id to be a stored property —
most often this will be declared as a constant (that is, with let):
importFoundation// Style 3: id requirement fulfilled by stored propertyextensionProduct: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 3: id requirement fulfilled by computed propertyextensionProduct:Identifiable{varid{uuid}}
If by coincidence the existing class or structure already has an id property,
it can add conformance by simply declaring it in an extension
(assuming that the property type conforms to Hashable).
importFoundationstructProduct{varid:UUID}// Style 3: id requirement fulfilled by existing propertyextensionProduct:Identifiable{}
No matter which way you choose,
you should find adopting Identifiable in a new or existing codebase
to be straightforward and noninvasive.
As we’ve said time and again,
often it’s the smallest additions to the language and standard library
that have the biggest impact on how we write code.
(This speaks to the thoughtful,
protocol-oriented
design of Swift’s standard library.)
Because what Identifiable does is kind of amazing:
it extends reference semantics to value types.
When you think about it,
reference types and value types differ not in what information they encode,
but rather how we treat them.
For reference types,
the stable identifier is the address in memory
in which the object resides.
This fact can be plainly observed
by the default protocol implementation of id for AnyObject types:
Ever since Swift first came onto the scene,
the popular fashion has been to eschew all reference types for value types.
And this neophilic tendency has only intensified
with the announcement of SwiftUI.
But taking such a hard-line approach makes a value judgment
of something better understood to be a difference in outlook.
It’s no coincidence that much of the terminology of programming
is shared by mathematics and philosophy.
As developers, our work is to construct logical universes, after all.
And in doing so,
we’re regularly tasked with reconciling our own mental models
against that of every other abstraction we encounter down the stack —
down to the very way that we understand electricity and magnetism to work.
Today is Labor Day in the United States
(and Labour Day in Canada),
a day to celebrate the achievement of the workers
who organized to bring about fair and safe conditions for employees —
protections that serve as the foundation for the modern workplace.
Labor Day is also the unofficial end of summer;
the long weekend acting as a buffer between
the lemonades, sunburns, and trashy romance novels of June and
the pumpkin spice lattés, 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 palette.
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.13 Mojave,
and its contemporaneous launch with Safari 12
rippled throughout the World-Wide Web,
gaining steady adoption among websites
(like yours truly)
and
other 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 with
Display Accommodations
to limit our light pollution when browsing Reddit late at night.
Of course, the challenge with Apple technologies
is that short of telling you that “you’re holding it wrong”,
they’ll rarely acknowledge the existence of prior art or alternatives,
let alone provide a transition document that in any way resembles
how everyone was doing things before we got an officially-sanctioned API.
(Then again, can you really blame them?)
If you were following 100% of Apple’s guidance to the letter,
you’d barely have to change a line or code
to get your app ready for
next week’s special event.
But most apps are built on solutions we built for ourselves
to bridge the gaps in the SDK,
and it may not be clear how to get on the new
happy path
from there.
Apple’s guidance for adopting Dark Mode is fantastic for new projects
but doesn’t quite hit all of the points you should be aware of
when preparing your existing app for iOS 13.
So without further ado,
here’s 6 action items for how to get your app ready for Dark Mode.
#Cancel Color Literals
In Xcode,
a color literal
is code with the prefix #colorLiteral
that is rendered as a color swatch in the editor.
For example,
the code #colorLiteral(red: 1, green: 0.67, blue: 0, alpha: 1)
is rendered in Xcode as .
Color literals can be drag-and-dropped from
the Media Browser in Xcode 10,
which has been consolidated with Snippets into the new Library panel in Xcode 11.
Both color literals and their cousin, image literals,
were introduced in support of Xcode Playgrounds.
But don’t let their appearance fool you:
neither have a place in your app’s codebase.
Dark Mode or not,
you can replace all usage of color literals throughout your codebase
from the command line:
But before you do,
you might as well do one better
and replace it with something that will stand the test of time.
Nix UIColor Hexadecimal Initializers
Among the most common extensions you’ll find in a
Swiss Army Knife-style CocoaPod
is a category on UIColor that initializes from a hexadecimal string.
Something along the lines of this:
Setting aside whatever qualms we have with
how they’re typically employed
or any misgivings you might have about
their underlying implementations,
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 is illegible when that background becomes black
when Dark Mode is enabled.
To make your app ready for Dark Mode on iOS 13,
you’ll most likely want to replace any instance of the following
UIColor class properties:
red
orange
yellow
brown
green
cyan
blue
purple
magenta
white
lightGray
gray
darkGray
black
Hopefully you aren’t using the built-in
ROYGBIVUIColor constants for much other than occasional layout debugging,
but chances you’ll probably find a few instances of .black or .white
peppered throughout your codebase somewhere.
In any case,
the easiest change to support Dark Mode would be to
replace any of the aforementioned fixed color properties with
the corresponding system-prefixed adaptable color below:
Name
API
Light
Dark
Default
Accessible
Default
Accessible
Red
systemRed
Orange
systemOrange
Yellow
systemYellow
Green
systemGreen
Teal
systemTeal
Blue
systemBlue
Indigo
systemIndigo
Purple
systemPurple
Pink
systemPink
Gray
systemGray
Gray (2)
systemGray2
Gray (3)
systemGray3
Gray (4)
systemGray4
Gray (5)
systemGray5
Gray (6)
systemGray6
You may notice that this table doesn’t provide direct correspondences for
black or white (or brown, but disregard that for now).
Black and white don’t have adaptive colors
because their names would cease to be descriptive in Dark Mode;
if .systemBlack existed, it’d pretty much have to be .white
to be visible in a dark color pallet.
Which gets to a deeper point about color management in an era of Dark Mode…
Use Semantic Colors
The best way to ensure consistent rendering of your UI
on any device in any appearance mode
is to use semantic colors,
named according to their function rather than appearance.
Similar to how Dynamic Type
uses semantic tags like “Headline” and “Body” to
automatically set the most suitable font for the user’s display preferences,
semantic colors —
or what Apple’s calling
UI Element Colors—
provide future-proof behavior for your views and controls.
When styling your component,
set the color to the closest UIColor class property below:
Label Colors
label
secondaryLabel
tertiaryLabel
quaternaryLabel
Text Colors
placeholderText
Link Colors
link
Separator Colors
separator
opaqueSeparator
Fill Colors
systemFill
secondarySystemFill
tertiarySystemFill
quaternarySystemFill
Background Colors
systemBackground
secondarySystemBackground
tertiarySystemBackground
Grouped Background Colors
systemGroupedBackground
secondarySystemGroupedBackground
tertiarySystemGroupedBackground
Upgrade Homegrown Semantic Color Palettes
If you’ve given any thought to color management in your app
you’ll have likely landed on some form of the following strategy,
whereby you define semantic colors according to fixed colors
within a namespace or in an extension to UIColor.
For example,
the following example shows how an app might define
UIColor constants from the
Material UI color system
and reference them from semantic UIColor class properties:
Nothing about the fixed Material UI color constants has to change
with this approach.
Instead, the semantic color property customAccent provides a dynamic color
that uses the color most appropriate for the current rendering context.
When Dark Mode is enabled,
a lighter orange is used to contrast against the darker color palette;
otherwise, the behavior is unchanged from the original implementation.
The extra #available check creates some bloat in the implementation,
but it’s a small price to pay for the flexibility this approach provides.
Unfortunately,
there’s one crucial shortcoming to using color properties in this way:
they can’t be referenced from Interface Builder.
If your app uses either Storyboards or XIBs,
the best approach is to use color assets.
Manage Colors with an Asset Catalog
Color assets let you manage colors in an Xcode Asset Catalog
in the same way that you do for
images, data, or other resources.
To create a color set,
open an Asset Catalog in your Xcode project,
click the + button on the bottom left-hand corner,
and select “New Color Set”.
In the Attributes inspector,
select “Any, Dark” appearance.
Colors are frequently expressed in the form (“#RRGGBB”);
you can enter colors in this form by
selecting “8-bit Hexadecimal” from the “Input Method” drop-down.
Here,
we’ve done the same as before,
except instead of defining fixed UIColor constants like orange300 in code,
we set those in the color set itself.
Now when it comes time to reference the color asset
by the existing semantic class property,
we can use the UIColor named initializer:
Your opinion about color assets will largely be informed by
your preference or dispreference towards specialized Xcode document editors.
Some folks like to have everything spelled out in code,
while others appreciate the affordances provided by a bespoke UI
like the one provided by Xcode for color sets.
In fact, your opinion of color assets
is probably concomitant with your feelings about Storyboards —
which is convenient,
because the killer feature of color assets is that
they can be referenced from within Interface Builder.
(If you’re not on team IB, then you can safely skip this whole discussion.)
Replace Instances of “Custom Color” in XIBs and Storyboards
The “Custom Color” option in Interface Builder
that brings up the macOS system-native color picker
suffers the same problem as the color literals and fixed colors
we talked about earlier.
If you want your XIB-powered views to look good on iOS 13,
you’ll need to migrate to named colors.
This can be done easily:
select any component in your scene,
and you can set its color attribute using the same, named color
defined in your Asset Catalog.
For a small project,
this can be done by hand for all your screens in under an hour.
However,
for a larger app,
this is a process you’ll want to automate.
XIB Anatomy
Under the hood,
XIB and Storyboard files are merely XML files like any other:
<?xml version="1.0" encoding="UTF-8"?><documenttype="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"<#...#>>
<deviceid="retina6_1"orientation="portrait"><adaptationid="fullscreen"/></device><dependencies><deploymentidentifier="iOS"/><plugInidentifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin"version="14490.49"/><capabilityname="Safe area layout guides"minToolsVersion="9.0"/><capabilityname="documents saved in the Xcode 8 format"minToolsVersion="8.0"/></dependencies><scenes><!--View Controller--><scenesceneID="nsh-ips-ter"><objects><viewControllerid="dar-kmo-de1"customClass="ViewController"customModule="NSHipster"customModuleProvider="target"sceneMemberID="viewController"><viewkey="view"contentMode="scaleToFill"id="mai-nv-iew"><rectkey="frame"x="0.0"y="0.0"width="414"height="896"/><colorkey="backgroundColor"red="1"green="0.69019607843137254"blue="0.0"alpha="1"colorSpace="custom"customColorSpace="sRGB"/></view></viewController><placeholderplaceholderIdentifier="IBFirstResponder"id="dkx-z0-nzr"sceneMemberID="firstResponder"/></objects></scene></scenes></document>
We wouldn’t want to write this from scratch by hand,
but there’s nothing too mysterious going on here.
So consider what happens when you use Xcode
to switch the custom background color of the main view to a named color:
A new "Named colors" capability is added as a dependency
for opening the document
(Xcode uses this to determine whether it can edit files
created by newer versions).
❷
The red, green, blue, and alpha components on the color element
were replaced by a single name attribute.
❸
A corresponding namedColor element was added to the top-level resources element.
Based on this understanding,
we should know enough to make this change en masse
with our own tooling!
Finding All Custom Colors
The first order of business when migrating your Storyboards and XIBs for Dark Mode
is to find all of the instances of custom colors.
You could go through each by hand and click each of the visual elements…
or you could run the following command:
This command prints the name of each file
followed by each custom, unnamed color element it found
(denoted by the colorSpace="custom" attribute).
The resulting list serves as a battle plan for the next phase of attack:
Finding Each Distinct Custom Color
Apps tend to reuse a small set of colors
across their views — as they should!
By running the following command,
you can generate a sorted, unique’d list of
every custom color in every XIB or Storyboard:
From here,
the final step is to map each set of RGBA 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_color_components(red,green,blue,alpha)# Return named color matching components# e.g. 1 0.69 0 1 => "Accent"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("//color[@colorSpace='custom']").eachdo|color|components=color.attributes.values_at("red","green","blue","alpha").map(&:value).map(&:to_f)nextunlessname=name_for_color_components(*components)named_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.matches?("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.matches?("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 annual
NMOSGM.
But if it’s any consolation,
know that Apple engineers rarely get to observe
Memorial Day—
the unofficial start of summer in America —
in the run-up to WWDC.
In law,
the latin phrase
stare 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 of
United States antitrust law,
the connection between the two was inescapable.
Adherence to precedence confers inertia in the decision-making process.
It promotes stability throughout the legal system
and the institutions that rely on a consistent application of laws.
However,
like inertia,
precedence can also be overcome with sufficiently compelling reasons;
we are bound by the past only insofar as to give it due consideration.
Bearing all of that in mind,
let’s smash cut
to our subject for this week’s brief:
Apple Push Notification Device Tokens—
and in particular,
a single change in iOS 13 that may incidentally break push notifications
for thousands of apps.
A Push Notifications Primer
Push notifications allow apps to communicate with users
in response to remote events when they aren’t currently in use.
Unlike SMS or email,
which allows a sender to communicate with a recipient directly
using a unique identifier (a phone number and email address, respectively),
communication between the app’s remote server and the user’s local device
are facilitated by the Apple Push Notification service
(APNs).
Here’s how that works:
After launching an app,
the app calls the method
registerForRemoteNotifications(),
prompting the user to grant the app permission to send push notifications.
The deviceToken parameter in the app delegate method
is an opaque Data value —
kind of like a really long unique phone number or email address —
that the app’s push notification provider uses
to route notifications through APNs to reach
this particular installation of the app.
In principle,
representing this parameter as a Data value makes a lot of sense —
the value itself is meaningless.
However, in practice,
this API design decision has been the source of untold amounts of heartache.
The Enduring Challenges of Sending Device Tokens Back to the Server
When the app delegate receives its deviceToken,
that’s not the end of the story.
In order for its to be used to send push notifications,
it needs to be sent from the client to the server.
The question is, “How”?
Before you jump to a particular answer,
consider the historical context of iOS 3 (circa 2009),
when push notifications were first introduced:
“Back in My Day…“
You could create an NSURLRequest object,
set its httpBody property to the deviceToken,
and send it using NSURLConnection,
but you’d probably also want to include some additional information —
like a username or email address —
to associate it with an account in the app.
That meant that the data you set as a request’s HTTP body
couldn’t just be the device token.
Sending an HTTP POST body withapplication/x-www-form-urlencoded
(e.g. username=jappleseed&deviceToken=____)
is one possibility for encoding multiple fields into a single payload,
but then the question becomes,
“How do you encode binary data into text?”
Base64
is a great binary-to-text encoding strategy,
but NSData -base64EncodedStringWithOptions:
wouldn’t be available until iOS 7,
four years after push notifications were first introduced in iOS 3.
Without CocoaPods or a strong open-source ecosystem
to fill in the gaps,
you were left to follow
blog 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 in
application(_:didRegisterForRemoteNotificationsWithDeviceToken:):
// Swift 2: deviceToken is NSDatadeviceToken.description// "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"// Swift 3: deviceToken is DatadeviceToken.description// "32 bytes"
However,
many developers remained undeterred by what was seen as a minor annoyance,
and worked around the issue by recasting to NSData and its former behavior:
// ⚠️ Warning: Don't do thislettokenData=deviceTokenasNSDatalettoken=tokenData.descriptionlettoken="\(deviceToken)".replacingOccurrences(of:"",with:"").replacingOccurrences(of:"<",with:"").replacingOccurrences(of:">",with:"")
Once again,
doing things the wrong way
managed to keep things working for another couple years.
But that’s all coming to an end with iOS 13.
Overturned in iOS 13
iOS 13 changes the format of descriptions
for Foundation objects,
including NSData:
Whereas previously,
you could coerce NSData to spill its entire contents
by converting it into a String,
it now reports its length and a truncated summary of its internal bytes.
So from now on,
if you need to convert your push notification registration deviceToken
into a Base16-encoded / hexadecimal string representation,
you should do the following:
For clarity, let’s break this down and explain each part:
The map method operates on each element of a sequence.
Because Data is a sequence of bytes in Swift,
the passed closure is evaluated for each byte in deviceToken.
The String(format:) initializer
evaluates each byte in the data
(represented by the anonymous parameter $0)
using the %02x format specifier,
to produce a zero-padded, 2-digit hexadecimal representation of
the byte / 8-bit integer.
After collecting each byte representation created by the map method,
joined() concatenates each element into a single string.
Was Apple irresponsible in making this particular change?
No, not really —
developers shouldn’t have relied on a specific format for
an object’s description.
Dumping an entire Data value’s contents becomes untenable at a certain point,
and this change to a more succinct summary
makes debugging larger data blobs significantly easier.
Like we said about laws at the start of this article,
precedence is a form of inertia,
not an immutable truth.
Stare decisis plays an important role
throughout software engineering.
Examples like the “Referer” [sic] header”—
even the conventions we have about
the direction of electricity flow—
demonstrate the value of sticking to decisions,
unless an opportunity arises to compel change.
Apple announced a lot at WWDC this year.
During the conference and the days that followed,
a lot of us walked around in a daze,
attempting to recover from have our minds “hashtag-mindblown’d” (#🤯).
But now a few months later,
after everything announced has launched
(well, almost everything)
those very same keynote announcements now elicit far different reactions:
Although the lion’s share of attention
has been showered on the aforementioned features,
not nearly enough coverage has been given to the rest of iOS 13 —
and that’s a shame,
because this release is among the most exciting in terms of new functionality
and satisfying in terms of improving existing functionality.
So to mark last week’s release of iOS 13,
we’re taking a look at some obscure (largely undocumented) APIs
that you can now use in your apps.
We’ve scavenged the best bits out of the
iOS 13 Release NotesAPI diffs,
and now present them to you.
Here are some of our favorite things you can do starting in iOS 13:
Generate Rich Representations of URLs
New in iOS 13,
the
LinkPresentation framework
provides a convenient, built-in way to replicate the
rich previews of URLs you see in Messages.
If your app has any kind of chat or messaging functionality,
you’ll definitely want to check this out.
Rich previews of URLs have a rich history
going at least as far back as the early ’00s,
with the spread of
Microformats
by semantic web pioneers,
and early precursors to Digg and Reddit
using khtml2png
to generate thumbnail images of web pages.
Fast forward to 2010,
with the rise of social media and user-generated content,
when Facebook created the OpenGraph protocol
to allow web publishers to customize how their pages looked
when posted on the Newsfeed.
These days,
most websites reliably have OpenGraph <meta> tags on their site
that provide a summary of their content for
social networks, search engines, and anywhere else that links are trafficked.
For example,
here’s what you would see if you did “View Source” for this very webpage:
<metaproperty="og:site_name"content="NSHipster"/><metaproperty="og:image"content="https://nshipster.com/logo.png"/><metaproperty="og:type"content="article"/><metaproperty="og:title"content="iOS 13"/><metaproperty="og:url"content="https://nshipster.com/ios-13/"/><metaproperty="og:description"content="To mark last week's release of iOS 13, we're taking a look at some obscure (largely undocumented) APIs that you can now use in your apps."/>
If you wanted to consume this information in your app,
you can now use the LinkPresentation framework’s LPMetadataProvider class
to fetch the metadata and optionally construct a representation:
After setting appropriate constraints
(and perhaps a call to sizeToFit()),
you’ll get the following,
which the user can tap to preview the linked webpage:
Alternatively,
if you already have the metadata in-app,
or otherwise can’t or don’t want to fetch remotely,
you can construct an LPLinkMetadata directly:
SFSpeechRecognizer
gets a major upgrade in iOS 13 —
most notably for its added support for on-device speech recognition.
Previously,
transcription required an internet connection
and was restricted to a maximum of 1-minute duration
with daily limits for requests.
But now,
you can do speech recognition completely on-device and offline,
with no limitations.
The only caveats are that
offline transcription isn’t as good as what you’d get with a server connection,
and is only available for certain languages.
To determine whether offline transcription is available for the user’s locale,
check the SFSpeechRecognizer property
supportsOnDeviceRecognition.
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 like
jitter (variations in pitch) and
shimmer (variations in amplitude).
Information about pitch and voicing and other features
could be used by your app
(perhaps in coordination with CoreML)
to differentiate between speakers
or determine subtext from a speaker’s inflection.
Send and Receive Web Socket Messages
Speaking of the Foundation URL Loading System,
we now have native support for
something that’s been at the top of our wish list for many years:
web sockets.
Thanks to the new
URLSessionWebSocketTask 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()// Configure how messages are receivedwebSocketTask.receive{resultinguardlet.success(message)=resultelse{return}...}// Send a messageletmessage:URLSessionWebSocketTask.Message=.string("Hello, world!")webSocketTask.send(message){errorin...}// 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 new
MKMapView.CameraBoundary API
in iOS 13
makes it much easier to constrain a map’s viewport to a particular region
without locking it down completely.
And with the new
MKPointOfInterestFilter API,
you can now customize the appearance of map views
to show only certain kinds of points of interest
(whereas previously it was an all-or-nothing proposition).
letfilter=MKPointOfInterestFilter(including:[.cafe])mapView.pointOfInterestFilter=filter// only show cafés
Finally,
with MKGeoJSONDecoder,
we now have a built-in way to pull in GeoJSON shapes
from web services and other data sources.
letdecoder=MKGeoJSONDecoder()ifleturl=URL(string:"..."),letdata=try?Data(contentsOfURL:url),letgeoJSONObjects=try?decoder.decode(data){forcase let overlay as MKOverlay in geoJSONObjects {
mapView.addOverlay(overlay)}}
For the uninitiated:
in JavaScript, a Promise
is an object that represents the eventual completion (or rejection)
of an asynchronous operation
and its resulting value.
Promises are a mainstay of modern JS development —
perhaps most notably within the fetch API.
On a lark,
we decided to see if there was anything new in Objective-C this year
and were surprised to find out about
objc_setHook_setAssociatedObject.
Again, we don’t have much to go on except the declaration,
but it looks like you can now configure a block to execute when
an associated object is set.
For anyone still deep in the guts of the Objective-C runtime,
this sounds like it could be handy.
Tame Activity Items (?)
On the subject of missing docs:
UIActivityItemsConfigurationseems like a compelling option for managing
actions in the new iOS 13 share sheets,
but we don’t really know where to start…
Shame that we don’t have the information we need to take advantage of this yet.
Not to harp on about this,
but both of them are still undocumented,
so if you want to learn more,
we’d recommend checking out that article from July.
Or, if you’re in a rush
here’s a quick example demonstrating how to use both of them together:
importFoundationletrelativeDateTimeFormatter=RelativeDateTimeFormatter()relativeDateTimeFormatter.dateTimeStyle=.namedletlistFormatter=ListFormatter()listFormatter.string(from:[relativeDateTimeFormatter.localizedString(from:DateComponents(day:-1)),relativeDateTimeFormatter.localizedString(from:DateComponents(day:0)),relativeDateTimeFormatter.localizedString(from:DateComponents(day:1))])// "yesterday, today, and tomorrow"
Track the Progress of Enqueued Operations
Starting in iOS 13,
OperationQueue now has a
progress property.
Granted,
(NS)Progress
objects aren’t the most straightforward or convenient things to work with
(we’ve been meaning to write an article about them at some point),
but they have a complete and well-considered API,
and even have some convenient slots in app frameworks.
For example,
check out how easy it is to wire up a UIProgressView
to display the live-updating progress of an operation queue
by way of its observedProgress property:
One of the things that often differentiates category-defining apps from their competitors
is their use of background tasks
to make sure the app is fully synced and updated for the next time
it enters the foreground.
iOS 7 was the first release to provide
an official API for scheduling background tasks(though developers had employed various creative approaches prior to this).
But in the intervening years,
multiple factors —
from an increase in iOS app capabilities and complexity
to growing emphasis in performance, efficiency, and privacy for apps —
have created a need for a more comprehensive solution.
As described in this year’s WWDC session
“Advances in App Background Execution”,
the framework distinguishes between two broad classes of background tasks:
app refresh tasks:
short-lived tasks that keep an app up-to-date throughout the day
background processing tasks:
long-lived tasks for performing deferrable maintenance tasks
The WWDC session and the accompanying sample code project
do a great job of explaining how to incorporate both of these
into your app.
But if you want the quick gist of it,
here’s a small example of an app that schedules periodic refreshes
from a web server:
Annotate Text Content Types for Better Accessibility
You know how frustrating it is to hear some people read out URLs?
(“eɪʧ ti ti pi ˈkoʊlən slæʃ slæʃ ˈdʌbəlju ˈdʌbəlju ˈdʌbəlju dɑt”…)
That’s what it can be like when
VoiceOver
tries to read something without knowing more about what it’s reading.
iOS 13 promises to improve the situation considerably
with the new accessibilityTextualContext property
and UIAccessibilityTextAttributeContextNSAttributedString attribute key.
Whenever possible,
be sure to annotate views and attributed strings with
the constant that best describes the kind of text being displayed:
UIAccessibilityTextualContextConsole
UIAccessibilityTextualContextFileSystem
UIAccessibilityTextualContextMessaging
UIAccessibilityTextualContextNarrative
UIAccessibilityTextualContextSourceCode
UIAccessibilityTextualContextSpreadsheet
UIAccessibilityTextualContextWordProcessing
Remove Implicitly Unwrapped Optionals from View Controllers Initialized from Storyboards
SwiftUI may have signaled the eventual end of Storyboards,
but that doesn’t mean that things aren’t and won’t continue to get better
until if / when that day comes.
One of the most irritating anti-patterns for Swift purists
when working on iOS projects with Storyboards
has been view controller initialization.
Due to an impedance mismatch between
Interface Builder’s “prepare for segues” approach and
Swift’s object initialization rules,
we frequently had to choose between
making all of our properties non-private, variable, and (implicitly unwrapped) optionals,
or foregoing Storyboards entirely.
Xcode 11 and iOS 13 allow these paradigms to reconcile their differences
by way of the new @IBSegueAction attribute
and some new UIStoryboard class methods:
First,
the @IBSegueAction attribute
can be applied view controller method declarations
to designate itself as the API responsible for
creating a segue’s destination view controller
(i.e. the destinationViewController property of the segue parameter
in the prepare(for:sender:) method).
importUIKitstructPerson{...}classProfileViewController:UIViewController{letname:StringletavatarImageURL:URL?init?(coder:NSCoder,name:String,avatarImageURL:URL?){self.name=nameself.avatarImageURL=avatarImageURLsuper.init(coder:coder)}requiredinit?(coder:NSCoder){fatalError("init(coder:) has not been implemented")}}letstoryboard=UIStoryboard(name:"ProfileViewController",bundle:nil)storyboard.instantiateInitialViewController(creator:{decoderinProfileViewController(coder:decoder,name:"Johnny Appleseed",avatarImageURL:nil)})
Together with the new UIKit Scene APIs,
iOS 13 gives us a lot to work with
as we wait for SwiftUI to mature and stabilize.
That does it for our round-up of iOS 13 features
that you may have missed.
But rest assured —
we’re planning to cover many more new APIs in future NSHipster articles.
If there’s anything we missed that you’d like for us to cover,
please @ us on Twitter!
Working on a large iOS codebase often involves a lot of waiting:
Waiting for Xcode to index your files,
waiting for Swift and Objective-C code to compile,
waiting for the Simulator to boot and your app to launch…
And after all of that,
you spend even more time getting your app
into a particular state and onto a particular screen,
just to see whether the Auto Layout constraint you just added
fixes that regression you found.
It didn’t, of course,
so you jump back into Xcode,
tweak the Content Hugging Priority,
hit ⌘R,
and start the whole process again.
We might relate our sorry predicament to
that one xkcd comic,
but for those of us who don’t so much relish in
the stop-and-go nature of app development,
there’s an old Yiddish joke about Shlemiel the painter
(provided below with a few -specific modifications;
for the uninitiated,
please refer to Joel Spolsky’s
original telling):
Shlemiel gets a job as a software developer,
implementing a new iOS app.
On the first sprint he opens Xcode
and implements 10 new screens of the app.
“That’s pretty good!” says his manager,
“you’re a fast worker!” and pays him a Bitcoin.
The next sprint Shlemiel only gets 5 screens done.
“Well, that’s not nearly as good as yesterday,
but you’re still a fast worker. 5 screens is respectable,”
and pays him a Bitcoin.
The next sprint Shlemiel implements 1 screen.
“Only 1!” shouts his manager.
“That’s unacceptable!
On the first day you did ten times that much work!
What’s going on?”
“I can’t help it,” says Shlemiel.
“Each sprint I get further and further away from
application(_:didFinishLaunchingWithOptions:)!”
Over the years,
there have been some developments that’ve helped things slightly,
including
@IBInspectable and @IBDesignable
and Xcode Playgrounds.
But with Xcode 11,
our wait is finally over —
and it’s all thanks to SwiftUI.
Although many of us have taken a “wait and see” approach to SwiftUI,
we can start using its capabilities today
to radically speed up and improve our development process —
without changing a line of code in our UIKit apps.
Consider a subclass of UIButton
that draws a border around itself:
Normally,
if we wanted to test how our UI element performs,
we’d have to add it to a view in our app,
build and run,
and navigate to that screen.
But with Xcode 11,
we can now see a preview side-by-side with the code editor
by adding the following under the original declaration of BorderedButton:
Using a new feature called dynamic replacement,
Xcode can update this preview without recompiling —
within moments of your making a code change.
This lets you rapidly prototype changes like never before.
Want to see how your button handles long titles?
Bang away on your keyboard within the call to setTitle(_:for:)
in your preview,
and test out potential fixes in your underlying implementation
without so much as leaving your current file!
Previewing Multiple States
Let’s say our app had a FavoriteButton—
a distant cousin (perhaps by composition) to BorderedButton.
In its default state,
it shows has the title “Favorite”
and displays a ♡ icon.
When its isFavorited property is set to true,
the title is set to “Unfavorite”
and displays a ♡̸ icon.
We can preview both at once
by wrapping two UIViewPreview instances within a single SwiftUI Group:
With Dark Mode in iOS 13,
it’s always a good idea to double-check that your custom views
are configured with dynamic colors
or accommodate both light and dark appearance in some other way.
An easy way to do this
would be to use a ForEach element
to render a preview for each case in the ColorScheme enumeration:
Xcode Previews are especially time-saving when it comes to
localizing an app into multiple languages.
Compared to the hassle of configuring Simulator
back and forth between different languages and regions,
this new approach makes a world of difference.
Let’s say that, in addition to English,
your app supported various right-to-left languages.
You could verify that your
RTL logic worked as expected like so:
letsupportedLocales:[Locale]=["en-US",// English (United States)"ar-QA",// Arabid (Qatar)"he-IL",// Hebrew (Israel)"ur-IN"// Urdu (India)].map(Locale.init(identifier:))funclocalizedString(_key:String,forlocale:Locale)->String?{...}returnForEach(supportedLocales,id:\.identifier){localeinUIViewPreview{letbutton=BorderedButton(frame:.zero)button.setTitle(localizedString("Subscribe",for:locale),for:.normal)button.setImage(UIImage(systemName:"plus"),for:.normal)button.setTitleColor(.systemOrange,for:.normal)button.tintColor=.systemOrangereturnbutton}.environment(\.locale,locale).previewDisplayName(Locale.current.localizedString(forIdentifier:locale.identifier))}.previewLayout(.sizeThatFits).padding(10)
#if canImport(SwiftUI) && DEBUGimportSwiftUIletdeviceNames:[String]=["iPhone SE","iPad 11 Pro Max","iPad Pro (11-inch)"]@available(iOS 13.0, *)structViewController_Preview:PreviewProvider{staticvarpreviews:someView{ForEach(deviceNames,id:\.self){deviceNameinUIViewControllerPreview{UIStoryboard(name:"Main",bundle:nil).instantiateInitialViewController{coderinViewController(coder:coder)}!}.previewDevice(PreviewDevice(rawValue:deviceName)).previewDisplayName(deviceName)}}}#endif
Although most of us are still some years away from shipping SwiftUI in our apps
(whether by choice or necessity),
we can all immediately benefit from the order-of-magnitude improvement
it enables with Xcode 11 on macOS Catalina.
By eliminating so much time spent waiting for things to happen,
we not only get (literally) hours more time each week,
but we unlock the possibility of maintaining an unbroken flow state during that time.
Not only that,
but the convenience of integrated tests
fundamentally changes the calculus for testing:
instead of being a rare “nice to have,”
they’re the new default.
Plus:
these inline previews serve as living documentation
that can help teams both large and small
finally get a handle on their design system.
It’s hard to overstate how much of a game-changer Xcode Previews are for iOS development,
and we couldn’t be happier to incorporate them into our workflow.
As an undergraduate student,
I had a radio show called
“Goodbye, Blue Monday”
(I was really into Vonnegut at the time).
It was nothing glamorous —
just a weekly, 2-hour slot at the end of the night
before the station switched into automation.
If you happened to be driving through the hills of Pittsburgh, Pennsylvania
late at night with your radio tuned to
WRCT 88.3,
you’d have heard an eclectic mix of
Contemporary Classical,
Acid Jazz,
Italian Disco, and
Bebop.
That, and the stilting, dulcet baritone of
a college kid doing his best impersonation of
Tony Mowod.
Sitting there in the booth,
waiting for tracks to play out before launching into an
FCC-mandated
PSA
or on-the-hour
station identification,
I’d wonder:
Is anyone out there listening?And if they were, did they like it?
I could’ve been broadcasting static the whole time and been none the wiser.
The same thoughts come to mind whenever I submit a build to App Store Connect…
but then I’ll remember that, unlike radio,
you can actually know these things!
And the latest improvements in Xcode 11 make it easier than ever
to get an idea of how your apps are performing in the field.
We’ll cover everything you need to know in this week’s NSHipster article.
So as they say on the radio:
“Don’t touch that dial (it’s got jam on it)”.
MetricKit is a new framework in iOS 13
for collecting and processing battery and performance metrics.
It was announced at WWDC this year
along with XCTest Metrics and the Xcode Metrics Organizer
as part of a coordinated effort to bring new insights to developers
about how their apps are performing in the field.
Apple automatically collects metrics from apps installed on the App Store.
You can view them in Xcode 11
by opening the Organizer (⌥⌘⇧O)
and selecting the new Metrics tab.
MetricKit complement Xcode Organizer Metrics by providing a programmatic way to
receive daily information about how your app is performing in the field.
With this information,
you can collect, aggregate, and analyze on your own in greater detail
than you can through Xcode.
Understanding App Metrics
Metrics can help uncover issues you might not have seen while testing locally,
and allow you to track and changes across different versions of your app.
For this initial release,
Apple has focused on the two metrics that matter most to users:
battery usage and performance.
Battery Usage
Battery life depends on a lot of different factors.
Physical aspects like the age of the device and
the number of charge cycles are determinative,
but the way your phone is used matters, too.
Things like CPU usage,
the brightness of the display and the colors on the screen,
and how often radios are used to fetch data or get your current location —
all of these can have a big impact.
But the main thing to keep in mind is that
users care a lot about battery life.
Aside from how good the camera is,
the amount of time between charges
is the deciding factor when someone buys a new phone these days.
So when their new, expensive phone doesn’t make it through the day,
they’re going to be pretty unhappy.
Until recently,
Apple’s taken most of the heat on battery issues.
But since iOS 12 and its new
Battery Usage screen in Settings,
users now have a way to tell when their favorite app is to blame.
Fortunately,
with iOS 13 you now have everything you need to make sure
your app doesn’t run afoul of reasonable energy usage.
Performance
Performance is another key factor in the overall user experience.
Normally, we might look to stats like
processor clock speed or frame rate
as a measure of performance.
But instead,
Apple’s focusing on less abstract and more actionable metrics:
Hang Rate
How often is the main / UI thread blocked,
such that the app is unresponsive to user input?
Launch Time
How long does an app take to become usable after the user taps its icon?
Peak Memory & Memory at Suspension
How much memory does the app use at its peak
and just before entering the background?
Disk Writes
How often does the app write to disk,
which — if you didn’t already know — is a
comparatively slow operation(even with the flash storage on an iPhone!)
Using MetricKit
From the perspective of an API consumer,
it’s hard to imagine how MetricKit could be easier to incorporate.
All you need is for some part of your app to serve as
a metric subscriber
(an obvious choice is your AppDelegate),
and for it to be added to the shared MXMetricManager:
iOS automatically collects samples while your app is being used,
and once per day (every 24 hours),
it’ll send an aggregated report with those metrics.
To verify that your MXMetricManagerSubscriber
is having its delegate method called as expected,
select Simulate MetricKit Payloads from the Debug menu
while Xcode is running your app.
Annotating Critical Code Sections with Signposts
In addition to the baseline statistics collected for you,
you can use the
mxSignpost function
to collect metrics around the most important parts of your code.
This signpost-backed API
captures CPU time, memory, and writes to disk.
For example,
if part of your app did post-processing on audio streams,
you might annotate those regions with metric signposts
to determine the energy and performance impact of that work:
Creating a Self-Hosted Web Service for Collecting App Metrics
Now that you have this information,
what do you do with it?
How do we fill that ... placeholder in our implementation of didReceive(_:)?
You could pass that along to some paid analytics or crash reporting service,
but where’s the fun in that?
Let’s build our own web service to collect these for further analysis:
Storing and Querying Metrics with PostgreSQL
The MXMetricPayload objects received by metrics manager subscribers
have a convenient
jsonRepresentation() method
that generates something like this:
As you can see,
there’s a lot baked into this representation.
Defining a schema for all of this information would be a lot of work,
and there’s no guarantee that this won’t change in the future.
So instead,
let’s embrace the NoSQL paradigm
(albeit responsibly, using Postgres)
by storing payloads in a JSONB column:
We can extract individual fields from payloads
using JSON operators
like so:
SELECT(payload->'applicationTimeMetrics'->>'cumulativeForegroundTime')::INTERVALFROMmetrics;-- interval-- ═══════════════════-- @ 11 mins 40 secs-- (1 row)
Advanced: Creating Views
JSON operators in PostgreSQL can be cumbersome to work with —
especially for more complex queries.
One way to help with that is to create a view
(materialized or otherwise)
to project the most important information to you
in the most convenient representation:
In this example,
most of the heavy lifting is delegated to Postgres,
making the server-side implementation rather boring.
For completeness,
here are some reference implementations in
Ruby (Sinatra) and JavaScript (Express):
importexpressfrom'express';import{Pool}from'pg';constdb=newPool(connectionString:process.env.DATABASE_URL,ssl:process.env.NODE_ENV==='production');constapp=express();app.post('/collect',(request,response)=>{db.query('INSERT INTO metrics (payload) VALUES ($1)',[request.body],(error,results)=>{if(error){throwerror;}response.status(204);})});app.listen(process.env.PORT||5000)
Sending Metrics as JSON
Now that we have everything set up,
the final step is to implement
the required MXMetricManagerSubscriber delegate method didReceive(_:)
to pass that information along to our web service:
Metrics offer a convenient way to at least make sure that
things aren’t too slow or too draining.
And though they provide but a glimpse in the aggregate
of how our apps are being enjoyed,
it’s just enough to help us honor both our creation and our audience
with a great user experience.
For every era,
there’s a monster that embodies the anxieties of the age.
At the dawn of the Holocene,
our ancestors traced the contours of shadows cast by the campfire
as they kept watch over the darkness.
Once we learned to read the night sky for navigation,
sailors swapped stories of sea creatures like
Leviathan and
Siren
to describe the dangers of open ocean
(and the perils to be found on unfamiliar shores).
More recently,
the “monster ruins a beach party”
trope of the 1960s
arose from concerns of teenager morality.
While the
Martians
who invaded those same drive-in double features
served as a proxy for Cold War fears at the height of the
Space Race.
All of which begs the question:
“What monster best exemplifies our present age?”
Consider the unnamed monster from the film
It Follows:
a formless, supernatural being that relentlessly pursues its victims
anywhere on the planet.
Sounds a bit like the state of
ad 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, and
analyze
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 the
identifierForVendor property on UIDevice
to generate a unique identifier that’s shared across apps and extensions
created by the same vendor
(IDFV).
Along with identifierForVendor came the introduction of a new
AdSupport framework,
which Apple created to help distinguish
identification necessary for app functionality
from anything in the service of advertising.
The resulting
advertisingidentifier property
(affectionately referred to as
IDFA by its associates)
differs from identifierForVendor
by returning the same value for everyone.
The value can change, for example,
if the user resets their Advertising Identifier
or erases their device.
If advertising tracking is limited,
the property returns a zeroed-out UUID instead.
idfa.uuidString=="00000000-0000-0000-0000-000000000000"// true if the user has limited ad tracking
DeviceCheck
identifierForVendor and advertisingIdentifier
provide all the same functionality as the uniqueIdentifier property
they replaced in iOS 6,
save for one:
the ability to persist across device resets and app uninstalls.
In iOS 11,
Apple quietly introduced the
DeviceCheck framework,
which allows developers to assign two bits of information
that are persisted by Apple
until the developer manually removes them.
Interacting with the DeviceCheck framework should be familiar to
anyone familiar with APNS:
after setting things up on App Store Connect and your servers,
the client generates tokens on the device,
which are sent to your servers to set or query two bits of information:
importDeviceCheckletdevice=DCDevice.currentifdevice.isSupported{device.generateToken{data,erroriniflettoken=data?.base64EncodedString(){send token to your server}}}
Based on the device token and other information sent by the client,
the server tells Apple to set each bit value
by sending a JSON payload like this:
Despite these affordances by Apple,
advertisers have continued to work to circumvent user privacy protections
and use any and all information at their disposal
to identify users by other means.
Over the years,
Apple’s restricted access to information about
device hardware,
installed apps,
nearby WiFi networks.
They’ve required apps to request permission to
get your current location,
access your camera and microphone,
flip through your contacts, and
find and connect to Bluetooth accessories.
They’ve taken bold steps to prevent user tracking in Safari.
For lack of this information,
companies have had to get creative,
looking to forge unique identities from the scraps of what’s still available.
This process of identification by a combination of external factors
is known as fingerprinting.
The unfortunate reality is that we can be uniquely identified
by vanishingly small amounts of information.
For example,
individuals within a population can be singled out by as few as
four timestamped coordinates
(de Montjoye, Hidalgo, Verleysen, & Blondel, 2013)
or little more than a birthday and a ZIP code
(Sweeney, 2000).
Every WWDC since 2012 has featured a session about Privacy,
but the only mention of fingerprinting specifically was
a brief discussion in 2014
about how to avoid doing it.
By our count,
a determined party could use conventional, unrestricted APIs
to generate a few dozen bits of randomness:
Locale Information (~36 bits)
Locale information is the greatest source of identifying information
on Apple platforms.
The combination of your
preferred languages, region, calendar, time zone,
and which keyboards you have installed
say a lot about who you are —
especially if you have less conventional preferences.
Of the approximately ~25% of users who take advantage of
Dynamic Type
by configuring a preferred font size,
that selection may also be used to fingerprint you:
Although most of the juiciest bits have been locked down
in OS updates over the years,
there’s just enough to contribute a few more bits for purposes of identification.
On iOS,
you can get the current model and amount of storage of a user’s device:
Knowing whether someone’s phone is on Verizon or Vodaphone
can also be factored into a fingerprint.
You can use the CTTelephonyNetworkInfo class from the
CoreTelephony framework
to lookup the providers for devices with cellular service:
The number of providers varies per country,
but using the 4 major carriers in United States
as a guideline,
we can say carrier information would contribute about 2 bits
(or more if you have multiple SIM cards installed).
Communication Preferences (2 bits)
More generally,
even knowing whether someone can send texts or email at all
can be factored into a fingerprint.
This information can be gathered without permissions via
the MessageUI framework.
If the use of digital fingerprinting seems outlandish,
that’s just scratching the surface of how companies and researchers
have figured out how to circumvent your privacy.
GeoIP and Relative Network Speeds
Although access to geolocation through conventional APIs
requires explicit authorization,
third parties may be able to get a general sense of where you are in the world
based on how you access the Internet.
ping -c 5 99.24.18.13 # San Francisco, USA
--- 99.24.18.13 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 11.900/12.184/12.895/0.380 ms
ping -c 5 203.47.10.37 # Adelaide, Australia
--- 203.47.10.37 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 202.122/202.433/203.436/0.504 ms
Battery Health
It’s unclear whether this is a concern in iOS,
but depending on how precise the results of UIDevice battery APIs are,
you may be able to use them to identify a device by its battery health.
(Olejnik, Acar, Castelluccia, & Diaz, 2016)
Much as we may bemoan the current duopoly of mobile operating systems,
we might take some solace in the fact that
at least one of the players actually cares about user privacy.
Though it’s unclear whether that’s a fight that can ever be won.
At times,
our fate of being tracked and advertised to
may seem as inevitable as the victims in It Follows.
But let’s not forget that,
as technologists, as people with a voice,
we’re in a position to fight back.