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

CoreGraphics Geometry Primitives and Functions

$
0
0

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:

  • 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).
importCoreGraphicsletfloat:CGFloat=1.0letpoint=CGPoint(x:1.0,y:2.0)letsize=CGSize(width:4.0,height:3.0)letvector=CGVector(dx:4.0,dy:3.0)varrectangle=CGRect(origin:point,size:size)

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

rectangle.origin.xrectangle.origin.yrectangle.widthrectangle.height

Accessing Minimum, Median, and Maximum Values

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:

rectangle.minX// 1.0rectangle.midX// 3.0rectangle.maxX// 5.0rectangle.minY// 2.0rectangle.midY// 3.5rectangle.maxY// 5.0

Computing the Center of a Rectangle

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:

extensionCGRect{varcenter:CGPoint{returnCGPoint(x:midX,y:midY)}}

Normalization

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

letsquare=CGRect(origin:.zero,size:CGSize(width:4.0,height:4.0))

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.

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:

rectangle.intersects(.zero)// falserectangle.intersection(.zero)// CGRect.null

Union

The union of two rectangles is the smallest rectangle that encompasses all of the points contained by either rectangle.

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!


Optional, throws, Result, and async/await

$
0
0

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:

funckeychainData(service:String)->Data?{letquery:NSDictionary=[kSecClass:kSecClassGenericPassword,kSecAttrService:service,kSecReturnData:true]varref:CFTypeRef?=nilswitchSecItemCopyMatching(query,&ref){case errSecSuccess:
        returnrefas?Datadefault:returnnil}}

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?

Well, congratulations are in order: change a few names, and we see we just invented the new Result type, now available in the Swift 5 standard library!

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:

funckeychainData(service:String)->Result<Data,Error>{letquery:NSDictionary=[...]varref:CFTypeRef?=nilswitchSecItemCopyMatching(query,&ref){case errSecSuccess:
        guardletdata=refas?Dataelse{return.failure(KeychainError.notData)}return.success(data)case errSecItemNotFound:
        return.failure(KeychainError.notFound(name:service))case errSecIO:
        return.failure(KeychainError.ioWentBad)...}}

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:

funckeychainData(service:String)throws->Data{letquery:NSDictionary=[...]varref:CFTypeRef?=nilswitchSecItemCopyMatching(query,&ref){case errSecSuccess:
        guardletdata=refas?Dataelse{throwKeychainError.notData}returndatacase errSecItemNotFound:
        throwKeychainError.notFound(name:service)case errSecIO:
        throwKeychainError.ioWentBad...}}

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

The async/await proposal is one such animal. If adopted it would reduce the above to:

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.

Xcode Plugins

$
0
0

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:

  1. The color scheme is Tomorrow Night
  2. The app used to make animated GIFs is LICEcap

Making Xcode More Like X

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

SCXcodeMiniMap

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

Show in GitHub

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

BBUFullIssueNavigator

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

BBUDebuggerTuckAway

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

XcodeColors

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

SCXcodeSwitchExpander

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

VVDocumenter

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

XAlign

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

ColorSense

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

KSImageNamed

Similar to the ColorSense plugin, KSImageNamed will preview and autocomplete images in [UIImage imageNamed:] declarations.

Semantics Highlighting

Polychromatic

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

Lin

Lin

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.

Image Resizing Techniques

$
0
0

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

imageView.contentMode=.scaleAspectFitimageView.image=image

So when does it make sense to resize an image?
When it’s significantly larger than the image view that’s displaying it.


Consider this stunning image of the Earth, from NASA’s Visible Earth image catalog:

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 Downsampling220.2
With Downsampling23.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:

  1. Drawing to a UIGraphicsImageRenderer
  2. Drawing to a Core Graphics Context
  3. Creating a Thumbnail with Image I/O
  4. Lanczos Resampling with Core Image
  5. Image Scaling with vImage

For consistency, each of the following techniques share a common interface:

funcresizedImage(aturl:URL,forsize:CGSize)->UIImage?{...}imageView.image=resizedImage(at:url,for:size)

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:

letscaleFactor=UIScreen.main.scaleletscale=CGAffineTransform(scaleX:scaleFactor,y:scaleFactor)letsize=imageView.bounds.size.applying(scale)

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:

importUIKit// Technique #1funcresizedImage(aturl:URL,forsize:CGSize)->UIImage?{guardletimage=UIImage(contentsOfFile:url.path)else{returnnil}letrenderer=UIGraphicsImageRenderer(size:size)returnrenderer.image{(context)inimage.draw(in:CGRect(origin:.zero,size:size))}}

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:

importUIKitimportCoreGraphics// Technique #2funcresizedImage(aturl:URL,forsize:CGSize)->UIImage?{guardletimageSource=CGImageSourceCreateWithURL(urlasNSURL,nil),letimage=CGImageSourceCreateImageAtIndex(imageSource,0,nil)else{returnnil}letcontext=CGContext(data:nil,width:Int(size.width),height:Int(size.height),bitsPerComponent:image.bitsPerComponent,bytesPerRow:image.bytesPerRow,space:image.colorSpace??CGColorSpace(name:CGColorSpace.sRGB)!,bitmapInfo:image.bitmapInfo.rawValue)context?.interpolationQuality=.highcontext?.draw(image,in:CGRect(origin:.zero,size:size))guardletscaledImage=context?.makeImage()else{returnnil}returnUIImage(cgImage:scaledImage)}

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:

importImageIO// Technique #3funcresizedImage(aturl:URL,forsize:CGSize)->UIImage?{letoptions:[CFString:Any]=[kCGImageSourceCreateThumbnailFromImageIfAbsent:true,kCGImageSourceCreateThumbnailWithTransform:true,kCGImageSourceShouldCacheImmediately:true,kCGImageSourceThumbnailMaxPixelSize:max(size.width,size.height)]guardletimageSource=CGImageSourceCreateWithURL(urlasNSURL,nil),letimage=CGImageSourceCreateThumbnailAtIndex(imageSource,0,optionsasCFDictionary)else{returnnil}returnUIImage(cgImage:image)}

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:

importUIKitimportCoreImageletsharedContext=CIContext(options:[.useSoftwareRenderer:false])// Technique #4funcresizedImage(aturl:URL,scale:CGFloat,aspectRatio:CGFloat)->UIImage?{guardletimage=CIImage(contentsOf:url)else{returnnil}letfilter=CIFilter(name:"CILanczosScaleTransform")filter?.setValue(image,forKey:kCIInputImageKey)filter?.setValue(scale,forKey:kCIInputScaleKey)filter?.setValue(aspectRatio,forKey:kCIInputAspectRatioKey)guardletoutputCIImage=filter?.outputImage,letoutputCGImage=sharedContext.createCGImage(outputCIImage,from:outputCIImage.extent)else{returnnil}returnUIImage(cgImage:outputCGImage)}

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?

Here are the results of some performance benchmarks performed on an iPhone 7 running iOS 12.2, in this project.

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: UIKit0.1420
Technique #2: Core Graphics10.1722
Technique #3: Image I/O0.1616
Technique #4: Core Image22.4983
Technique #5: vImage2.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.
  • Core Image is outperformed for image scaling operations. In fact, according to Apple’s Performance Best Practices section of the Core Image Programming Guide, you should use Core Graphics or Image I/O functions to crop and downsampling images instead of Core IMage.
  • 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.

Xcode Build Configuration Files

$
0
0

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!

Xcode build settings

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

FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)
        

Xcode assigns inherited values in the following order (from lowest to highest precedence):

  • Platform Defaults
  • Xcode Project File Build Settings
  • xcconfig File for the Xcode Project
  • Active Target Build Settings
  • xcconfig File for the Active Target

Referencing Values

You can substitute values from other settings by their declaration name with the following syntax:

BUILD_SETTING_NAME = $(ANOTHER_BUILD_SETTING_NAME)
        

Substitutions can be used to define new variables according to existing values, or inline to build up new values dynamically.

OBJROOT = $(SYMROOT)
        CONFIGURATION_BUILD_DIR = $(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)
        

Conditionalizing

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:

ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES
        

Including Settings from Other Configuration Files

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

Xcode new configuration file

Once you’ve created an xcconfig file, you can assign it to one or more build configurations for its associated targets.

Xcode project configuration

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.

// Development.xcconfig
        PRODUCT_NAME = $(inherited) α
        ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Alpha
        ---
        // Staging.xcconfig
        PRODUCT_NAME = $(inherited) β
        ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Beta
        

Managing Constants Across Different Environments

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.

# Development.xcconfig
        API_BASE_URL = api.example.dev
        API_KEY = 9e053b0285394378cf3259ed86cd7504
        ---
        # Production.xcconfig
        API_BASE_URL = api.example.com
        API_KEY = 4571047960318d233d64028363dfa771
        

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!

leturl=URL(string:path,relativeTo:API.baseURL)!varrequest=URLRequest(url:url)request.httpMethod=methodrequest.addValue(API.key,forHTTPHeaderField:"X-API-KEY")


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.

Swift Code Formatters

$
0
0

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.

Since our article was first published back in March, the proposal, “SE-0250: Swift Code Style Guidelines and Formatter” started formal review; that process was later suspended, to be reconsidered sometime in the future.

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:

ProjectRepository URL
SwiftFormathttps://github.com/nicklockwood/SwiftFormat
SwiftLinthttps://github.com/realm/SwiftLint
Prettier with Swift Plugin *https://github.com/prettier/prettier
swift-formathttps://github.com/google/swift/tree/format

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.

Installation

SwiftFormat is distributed via Homebrew as well as Mint and CocoaPods.

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:

$ brew install swiftlint
        

Alternatively, you can install SwiftLint with CocoaPods, Mint, or as a standalone installer package (.pkg).

Usage

To use SwiftLint as a code formatter, run the autocorrect subcommand passing the --format option and the files or directories to correct.

$ swiftlint autocorrect --format--path Example.swift
        

Example Output

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.

Thanks to its (in-development) plugin architecture, the same line-breaking behavior can be applied to other languages, including Swift.

Installation

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.

Installation

The code for swift-format is currently hosted on the format branch of Google’s fork of the Swift project. You can check it out and build it from source by running the following commands:

$ git clone https://github.com/google/swift.git swift-format
        $cd swift-format
        $ git submodule update --init$ swift build
        

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.

$ swift-format -m dump-configuration .swift-format.json
        

Running the command above populates the specified file with the following JSON:

{"blankLineBetweenMembers":{"ignoreSingleLineProperties":true},"indentation":{"spaces":2},"lineBreakBeforeControlFlowKeywords":false,"lineBreakBeforeEachArgument":true,"lineLength":100,"maximumBlankLines":1,"respectsExistingLineBreaks":true,"rules":{"AllPublicDeclarationsHaveDocumentation":true,"AlwaysUseLowerCamelCase":true,"AmbiguousTrailingClosureOverload":true,"AvoidInitializersForLiterals":true,"BeginDocumentationCommentWithOneLineSummary":true,"BlankLineBetweenMembers":true,"CaseIndentLevelEqualsSwitch":true,"DoNotUseSemicolons":true,"DontRepeatTypeInStaticProperties":true,"FullyIndirectEnum":true,"GroupNumericLiterals":true,"IdentifiersMustBeASCII":true,"MultiLineTrailingCommas":true,"NeverForceUnwrap":true,"NeverUseForceTry":true,"NeverUseImplicitlyUnwrappedOptionals":true,"NoAccessLevelOnExtensionDeclaration":true,"NoBlockComments":true,"NoCasesWithOnlyFallthrough":true,"NoEmptyAssociatedValues":true,"NoEmptyTrailingClosureParentheses":true,"NoLabelsInCasePatterns":true,"NoLeadingUnderscores":true,"NoParensAroundConditions":true,"NoVoidReturnOnFunctionSignature":true,"OneCasePerLine":true,"OneVariableDeclarationPerLine":true,"OnlyOneTrailingClosureArgument":true,"OrderedImports":true,"ReturnVoidInsteadOfEmptyTuple":true,"UseEnumForNamespacing":true,"UseLetInEveryBoundCaseVariable":true,"UseOnlyUTF8":true,"UseShorthandTypeNames":true,"UseSingleLinePropertyGetter":true,"UseSpecialEscapeSequences":true,"UseSynthesizedInitializer":true,"UseTripleSlashForDocumentationComments":true,"ValidateDocumentationComments":true},"tabWidth":8,"version":1}

After fiddling with the configuration — such as setting lineLength to the correct value of 80 (don’t @ me)— you can apply it thusly:

$ swift-format Example.swift --configuration .swift-format.json
        

Example Output

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.

WWDC 2019

$
0
0

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.

WWDC 2019 Banner

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.

importSwiftUIstructContent:View{@Statevarmodel=...varbody:someView{...}}

Even if you ardently followed the Swift Forums and knew about the proposals for property delegates, implicit returns for single-line expressions, and opaque result types… you still might struggle to make sense of everything you saw.

Apple’s language

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.

Thinking outside the App Store

On the heels of Catalyst’s official debut, Twitter announced plans to port its iPad app to the Mac.

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

In fact, you don’t need to wait for macOS Catalina — you can experience Twitter as a native desktop app today! The latest version of Chrome lets you install progressive web apps (PWAs), like Twitter, as native desktop apps.

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

Twitter App

Android has a similar feature that generates and installs a native app from a web app manifest— all without ever leaving the browser. Meanwhile, the Google Play Store has started to become more browser-like itself; with instant apps and games, that can be launched immediately, without installing the full payload.

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.

See you all next week!

Swift Property Wrappers

$
0
0

Years ago, we remarked that the “at sign” (@) — along with square brackets and ridiculously-long method names — was as characteristic of Objective-C as parentheses are to Lisp or punctuation is to Perl.

Then came Swift, and with it an end to these curious little 🥨-shaped glyphs.
Or so we thought.

At first, the function of @ was limited to Objective-C interoperability: @IBAction, @NSCopying, @UIApplicationMain, and so on. But in time, Swift has continued to incorporate an ever-increasing number of @-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:


Constraining Values

SE-0258 offers plenty of practical examples, including @Lazy, @Atomic, @ThreadSpecific, and @Box. But the one we were most excited about was that of the @Constrained property wrapper.

Swift’s standard library offer correct, performant floating-point number types, and you can have it in any size you want — so long as it’s 32 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:

ExpressibleByIntegerLiteralFloatExpressibleByFloatLiteralBinaryFloatingPointFloatingPointFloat80DoubleSignedNumericSignedIntegerIntAdditiveArithmeticNumericFixedWidthIntegerBinaryIntegerComparableEquatableUnsignedIntegerUInt
Credit: Flight School Guide to Swift Numbers

Pulling this off is no small feat, and often far too much work to justify for most use cases.

Fortunately, property wrappers offer a way to parameterize standard number types with significantly less effort.

Implementing a value clamping property wrapper

Consider the following Clamping structure. As a property wrapper (denoted by the @propertyWrapper attribute), it automatically “clamps” out-of-bound values within the prescribed range.

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

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

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

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

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

Related Ideas

  • A @Positive / @NonNegative property wrapper that provides the unsigned guarantees to signed integer types.
  • A @NonZero property wrapper that ensures that a number value is either greater than or less than 0.
  • @Validated or @Whitelisted / @Blacklisted property wrappers that restrict which values can be assigned.

Transforming Values on Property Assignment

Accepting text input from users is a perennial headache among app developers. There are just so many things to keep track of, from the innocent banalities of string encoding to malicious attempts to inject code through a text field. But among the most subtle and frustrating problems that developers face when accepting user-generated content is dealing with leading and trailing whitespace.

A single leading space can invalidate URLs, confound date parsers, and sow chaos by way of off-by-one errors:

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

When it comes to user input, clients most often plead ignorance and just send everything as-is to the server. ¯\_(ツ)_/¯.

While I’m not advocating for client apps to take on more of this responsibility, the situation presents another compelling use case for Swift property wrappers.


Foundation bridges the trimmingCharacters(in:) method to Swift strings, which provides, among other things, a convenient way to lop off whitespace from both the front or back of a String value. Calling this method each time you want to ensure data sanity is, however, less convenient. And if you’ve ever had to do this yourself to any appreciable extent, you’ve certainly wondered if there might be a better approach.

In your search for a less ad-hoc approach, you may have sought redemption through the willSet property callback… only to be disappointed that you can’t use this to change events already in motion.

structPost{vartitle:String{willSet{title=newValue.trimmingCharacters(in:.whitespacesAndNewlines)/* ⚠️ Attempting to store to property 'title' within its own willSet,
        which is about to be overwritten by the new value              */}}}

From there, you may have realized the potential of didSet as an avenue for greatness… only to realize later that didSet isn’t called 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.

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

By marking each String property in the Post structure below with the @Trimmed annotation, any string value assigned to title or body— whether during initialization or via property access afterward — automatically has its leading or trailing whitespace removed.

structPost{@Trimmedvartitle:String@Trimmedvarbody:String}letquine=Post(title:"  Swift Property Wrappers  ",body:"...")quine.title// "Swift Property Wrappers" (no leading or trailing spaces!)quine.title="      @propertyWrapper     "quine.title// "@propertyWrapper" (still no leading or trailing spaces!)

Related Ideas

  • A @Transformed property wrapper that 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(_:):

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

Construct two string values that differ only by case, and they’ll return false for a standard equality check, but true when wrapped in a CaseInsensitive object.

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

So far, this approach is indistinguishable from the custom “wrapper type” approach described above. And this is normally where we’d start the long slog of implementing conformance to ExpressibleByStringLiteral and all of the other protocols to make CaseInsensitive start to feel enough like String to feel good about our approach.

Property wrappers allow us to forego all of this busywork entirely:

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

Here, Account objects are checked for equality by a case-insensitive comparison on their name property value. However, when we go to get or set the name property, it’s a bona fideString value.

That’s neat, but what’s actually going on here?

Since Swift 4, the compiler automatically synthesizes Equatable conformance to types that adopt it in their declaration and whose stored properties are all themselves Equatable. Because of how compiler synthesis is implemented (at least currently), wrapped properties are evaluated through their wrapper rather than their underlying value:

// Synthesized by Swift CompilerextensionAccount:Equatable{staticfunc==(lhs:Account,rhs:Account)->Bool{lhs.$name==rhs.$name}}

Related Ideas

  • Defining @CompatibilityEquivalence such that wrapped String properties with the values "①" and "1" are considered equal.
  • A @Approximate property wrapper to refine equality semantics for floating-point types (See also SE-0259)
  • A @Ranked property wrapper that takes a function that defines strict ordering for, say, enumerated values; this could allow, for example, the playing card rank .ace to be treated either low or high in different contexts.

Auditing Property Access

Business requirements may stipulate certain controls for who can access which records when or prescribe some form of accounting for changes over time.

Once again, this isn’t a task typically performed by, say, an iOS app; most business logic is defined on the server, and most client developers would like to keep it that way. But this is yet another use case too compelling to ignore as we start to look at the world through property-wrapped glasses.

Implementing a Property Value Versioning

The following Versioned structure functions as a property wrapper that intercepts incoming values and creates a timestamped record when each value is set.

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

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

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

Related Ideas

  • An @Audited property wrapper that logs each time a property is read or written to.
  • A @Decaying property wrapper that divides a set number value each time each time the value is read.

However, this particular example highlights a major limitation in the current implementation of property wrappers that stems from a longstanding deficiency of Swift generally: Properties can’t be marked as throws.

Without the ability to participate in error handling, property wrappers don’t provide a reasonable way to enforce and communicate policies. For example, if we wanted to extend the @Versioned property wrapper from before to prevent state from being set to .approved after previously being .denied, our best option is fatalError(), which isn’t really suitable for real applications:

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

This is just one of several limitations that we’ve encountered so far with property wrappers. In the interest of creating a balanced perspective on this new feature, we’ll use the remainder of this article to enumerate them.

Limitations

Properties Can’t Participate in Error Handling

Properties, unlike functions, can’t be marked as throws.

As it were, this is one of the few remaining distinctions between these two varieties of type members. Because properties have both a getter and a setter, it’s not entirely clear what the right design would be if we were to add error handling — especially when you consider how to play nice with syntax for other concerns like access control, custom getters / setters, and callbacks.

As described in the previous section, property wrappers have but two methods of recourse to deal with invalid values:

  1. Ignoring them (silently)
  2. Crashing with fatalError()

Neither of these options is particularly great, so we’d be very interested by any proposal that addresses this issue.

Wrapped Properties Can’t Be Aliased

Another limitation of the current proposal is that you can’t 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.

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

All this actually means in practice is more code replication than would be ideal. But given that this problem arises out of a fundamental distinction between types and values in the language, we can forgive a little duplication if it means avoiding the wrong abstraction.

Property Wrappers Are Difficult To Compose

Composition of property wrappers is not a commutative operation; the order in which you declare them affects how they’ll behave.

Consider the interplay between an attribute that performs string inflection and other string transforms. For example, a composition of property wrappers to automatically normalize the URL “slug” in a blog post will yield different results if spaces are replaced with dashes before or after whitespace is trimmed.

structPost{...@Dasherized@Trimmedvarslug:String}

But getting that to work in the first place is easier said than done! Attempting to compose two property wrappers that act on String values fails, because the outermost wrapper is acting on a value of the innermost wrapper type.

@propertyWrapperstructDasherized{private(set)varvalue:String=""varwrappedValue:String{get{value}set{value=newValue.replacingOccurrences(of:"",with:"-")}}init(initialValue:String){self.wrappedValue=initialValue}}structPost{...@Dasherized@Trimmedvarslug:String// ⚠️ An internal error occurred.}

There’s a way to get this to work, but it’s not entirely obvious or pleasant. Whether this is something that can be fixed in the implementation or merely redressed by documentation remains to be seen.

Property Wrappers Aren’t First-Class Dependent Types

A dependent type is a type defined by its value. For instance, “a pair of integers in which the latter is greater than the former” and “an array with a prime number of elements” are both dependent types because their type definition is contingent on its value.

Swift’s lack of support for dependent types in its type system means that any such guarantees must be enforced at run time.

The good news is that property wrappers get closer than any other language feature proposed thus far in filling this gap. However, they still aren’t a complete replacement for true value-dependent types.

You can’t use property wrappers to, for example, define a new type with a constraint on which values are possible.

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

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

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

These shortcomings are by no means deal-breakers; property wrappers are extremely useful and fill an important gap in the language.

It’ll be interesting to see whether the addition of property wrappers will create a renewed interest in bringing dependent types to Swift, or if they’ll be seen as “good enough”, obviating the need to formalize the concept further.

Property Wrappers Are Difficult to Document

Pop Quiz: Which property wrappers are made available by the SwiftUI framework?

Go ahead and visit the official SwiftUI docs and try to answer.

😬

In fairness, this failure isn’t unique to property wrappers.

If you were tasked with determining which protocol was responsible for a particular API in the standard library or which operators were supported for a pair of types based only on what was documented on developer.apple.com, you’re likely to start considering a mid-career pivot away from computers.

This lack of comprehensibility is made all the more dire by Swift’s increasing complexity.

Property Wrappers Further Complicate Swift

Swift is a much, much more complex language than Objective-C. That’s been true since Swift 1.0 and has only become more so over time.

The profusion of @-prefixed features in Swift — whether it’s
@dynamicMemberLookup and @dynamicCallable from Swift 4, or @differentiable and @memberwise from Swift for Tensorflow— makes it increasingly difficult to come away with a reasonable understanding of Swift APIs based on documentation alone. In this respect, the introduction of @propertyWrapper will be a force multiplier.

How will we make sense of it all? (That’s a genuine question, not a rhetorical one.)


Alright, let’s try to wrap this thing up —

Swift property wrappers allow library authors access to the kind of higher-level behavior previously reserved for language features. Their potential for improving safety and reducing complexity of code is immense, and we’ve only 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.

Or, as Nataliya Patsovska put it in a tweet:

iOS API design, short history:

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

@nataliya_bg

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


UIStackView

$
0
0

When I was a student in Japan, I worked part-time at a restaurant — アルバイト(arubaito) as the locals call it — where I was tasked with putting away dishes during downtime. Every plate had to be stacked neatly, ready to serve as the canvas for the next gastronomic creation. Lucky for me, the universal laws of physics came in quite handy — all I had to do was pile things up, rather indiscriminately, and move on to the next task.

In contrast, iOS developers often have to jump through several conceptual hoops when laying out user interfaces. After all, placing things in an upside-down 2D coordinate system is not intuitive for anyone but geometry geeks; for the rest of us, it’s not that cut and dried.

But wait — what if we could take physical world concepts like gravity and elasticity and appropriate them for UI layouts?

As it turns out, there has been no shortage of attempts to do so since the early years 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

You can also insert subviews at a specific index:

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

Adding an arranged view using any of the methods above also makes it a subview of the stack view. To remove an arranged subview that you no longer want around, you need to call removeFromSuperview() on it. The stack view will automatically remove it from the arranged subview list. In contrast, calling removeArrangedSubview(_ view: UIView) on the stack view will only remove the view passed as a parameter from the arranged subview list, without removing it from the subview hierarchy. Keep this distinction in mind if you are modifying the stack view content during runtime.

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

Toggling Subview Visibility

One major benefit of using stack views over custom layouts is their built-in support for toggling subview visibility without causing layout ambiguity; whenever the isHidden property is toggled for one of the arranged subviews, the layout is recalculated, with the possibility to animate the changes inside an animation block:

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

This feature is particularly useful when the stack view is part of a reusable view such as table and collection view cells; not having to keep track of which constraints to toggle is a bliss.

Now, let’s resume our plating work, shall we? With everything in place, let’s see what can do with our arranged plates.

Arranging Subviews Horizontally and Vertically

The first stack view property you will likely interact with is the axis property. Through it you can specify the orientation of the main axis, that is the axis along which the arranged subviews will be stacked. Setting it to either horizontal or vertical will force all subviews to fit into a single row or a single column, respectively. This means that stack views in iOS do not allow overflowing subviews to wrap into a new row or column, unlike other implementations such CSS flexbox and its flex-wrap property.

The orientation that is perpendicular to the main axis is often referred to as the cross axis. Even though this distinction is not explicit in the official documentation, it is one of the main ingredients in any stacking algorithm — without it, any attempt at explaining how stack views work will be half-baked.

Main AxisCross AxisCross AxisMain AxisHorizontal StackVertical Stack

The default orientation of the main axis in iOS is horizontal; not ideal for our dishware, so let’s fix that:

plateStack.axis=.vertical

Et voilà!


Entrée🍽 Configuring the Layout

When we layout views, we’re accustomed to thinking in terms of origin and size. Working with stack views, however, requires us to instead think in terms of main axis and cross axis.

Consider how a horizontally-oriented stack view works. To determine the width and the x coordinate of the origin for each of its arranged subviews, it refers to a set of properties that affect layout across the horizontal axis. Likewise, to determine the height and the y coordinate, it refers to another set of properties that affects the vertical axis.

The UIStackView class provides axis-specific properties to define the layout: distribution for the main axis, and alignment for the cross axis.

The Main Axis: Distribution

The position and size of arranged subviews along the main axis is affected in part by the value of the distribution property, and in part by the sizing properties of the subviews themselves.

In practice, each distribution option will determine how space along the main axis is distributed between the subviews. With all distributions, save for fillEqually, the stack view attempts to find an optimal layout based on the intrinsic sizes of the arranged subviews. When it can’t fill the available space, it stretches the arranged subview with the the lowest content hugging priority. When it can’t fit all the arranged subviews, it shrinks the one with the lowest compression resistance priority. If the arranged subviews share the same value for content hugging and compression resistance, the algorithm will determine their priority based on their indices.

With that out of the way, let’s take a look at the possible outcomes, staring the distributions that prioritize preserving the intrinsic content size of each arranged subview:

  • equalSpacing: The stack view gives every arranged subview its intrinsic size alongside the main axis, then introduces equally-sized paddings if there is extra space.
  • equalCentering: Similar to equalSpacing, but instead of spacing subviews equally, a variably sized padding is introduced in-between so as the center of each subview alongside the axis is equidistant from the two adjacent subview centers.
(60 × 110)60 × 110(60 × 110)60 × 110(110 × 60)110 × 60(110 × 60)110 × 60(102 × 128)102 × 128(102 × 128)102 × 128(128 × 102)128 × 102(128 × 102)128 × 102(68 × 190)68 × 190(68 × 190)68 × 190(190 × 68)190 × 68(190 × 68)190 × 68Equal SpacingEqual CenteringEqual SpacingEqual CenteringHorizontal StackVertical Stack

In contrast, the following distributions prioritize filling the stack container, regardless of the intrinsic content size of its subviews:

  • fill (default): The stack view ensures that the arranged subviews fill all the available space. The rules mentioned above apply.
  • fillProportionally: Similar to fill, but instead of resizing a single view to fill the remaining space, the stack view proportionally resizes all subviews based on their intrinsic content size.
  • fillEqually: The stack view ensures that the arranged views fill all the available space and are all the same size along the main axis.
(60 × 110)60 × 254(60 × 110)66.5 × 254(110 × 60)254 × 60(110 × 60)254 × 66.5(102 × 128)126 × 254(102 × 128)112.5 × 254(102 × 128)84.5 × 254(128 × 102)254 × 126(128 × 102)254 × 112.5(110 × 60)254 × 84.5(128 × 102)254 × 84.5(68 × 190)68 × 254(68 × 190)75 × 254(68 × 190)84.5 × 254(190 × 68)254 × 68(190 × 68)254 × 75(190 × 68)254 × 84.5FillFillFill ProportionallyFill EquallyHorizontal StackVertical StackFill ProportionallyFill Equally(60 × 110)84.5 × 254

The Cross Axis: Alignment

The third most important property of UIStackView is alignment. Its value affects the positioning and sizing of arranged subviews along the cross axis. That is, the Y axis for horizontal stacks, and X axis for vertical stacks. You can set it to one of the following values for both vertical and horizontal stacks:

  • fill (default): The stack view ensures that the arranged views fill all the available space on the cross axis.
  • leading/trailing: All subviews are aligned to the leading or trailing edge of the stack view along the cross axis. For 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!

plateStack.distribution=.fillplateStack.alignment=.leading

Palate Cleanser 🍧 Background Color

Another quirk of stack views in iOS is that they don’t directly support setting a background color. You have to go through their backing layer to do so.

plateStack.layer.backgroundColor=UIColor.white.cgColor

Alright, we’ve come quite far, but have a couple of things to go over before our dégustation is over.


Dessert 🍮 Spacing & Auto Layout

By default, a stack view sets the spacing between its arranged subviews to zero. The value of the spacing property is treated as an exact value for distributions that attempt to fill the available space (fill, fillEqually, fillProportionally), and as a minimum value otherwise (equalSpacing, equalCentering). With fill distributions, negative spacing values cause the subviews to overlap and the last subview to stretch, filling the freed up space. Negative spacing values have no effect on equal centering or spacing distributions.

plateStack.spacing=2// These plates can float too!

The spacing property applies equally between each pair of arranged subviews. To set an explicit spacing between two particular subviews, use the setCustomSpacing(:after:) method instead. When a custom spacing is used alongside the equalSpacing distribution, it will be applied on all views, not just the one specified in the method call.

To retrieve the custom space later on, customSpacing(after:) gives that to you on a silver platter.

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

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

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

Sometimes you need more control over the sizing and placement of an arranged subview. In those cases, you may add custom constraints on top of the ones generated by the stack view. Since the latter come with a priority of 1000, make sure all of your custom constraints use a priority of 999 or less to avoid unsatisfiable layouts.

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

For vertical stack views, the API lets you calculate distances from the subviews’ baselines, in addition to their top and bottom edges. This comes in handy when trying to maintain a vertical rhythm in text-heavy UIs.

plateStack.isBaselineRelativeArrangement=true// Spacing will be 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.

CAEmitterLayer

$
0
0

Etymologically, confetti comes from the Italian word for the sugar-coated almond sweet thrown at celebrations, which, in turn, get their name from the Latin conficio: con- (“with, together”) + facio (“do, make”); in another sense, “to celebrate”.

Confetti gets tossed around a lot these days, but not nearly as in the 20th century with its iconic ticker-tape parades down the streets of New York City, like the one welcoming home the Apollo 11 astronauts 50 years ago. Alas, the rise of digital technology made obsolete the stock tickers whose waste paper tape comprised the substrate of those spectacles. And as a result, the tradition has become much less commonplace today.

This week mark’s NSHipster’s 7th anniversary! And what better way to celebrate the occasion than to implement a fun and flexible confetti view on iOS?


Let’s dive right in with a quick refresher on the difference between views and layers:

Views and Layers

On iOS, each view is backed by a layer …or perhaps it’s more accurate to say that layers are fronted by view.

Because despite their reputation as the workhorse of UIKit, UIView delegates the vast majority of its functionality to CALayer. Sure, views handle touch events and autoresizing, but beyond that, nearly everything else between your code and the pixels on screen is the responsibility of layers.

Among the available CALayer subclasses in the Quartz Core / Core Animation framework there are APIs for displaying large amounts of content by scrolling and tiling, there are APIs for 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:

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

Creating a CAEmitterLayer Subclass

The next step is to implement the emitter layer itself.

The primary responsibility of CAEmitterLayer is to configure its cells. Confetti rains down from above with just enough variation in its size, speed, and spin to make it interesting. We use the passed array of Content values to set the contents of the cell (a CGImage) and a fill color (a CGColor).

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

We’ll call this configure(with:) method from our confetti view, which will be our next and final step.

Implementing ConfettiView

We want our confetti view to emit confetti for a certain amount of time and then stop. However, accomplishing this is surprisingly difficult, as evidenced by the questions floating around 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.

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

There’s a lot of code to unpack here, so let’s focus on its distinct parts:

First, we create an instance of our custom CAEmitterLayer subclass (creatively named Layer here, because it’s a private, nested type). It’s set up with our configure(with:) method from before and added as a sublayer. The needsDisplayOnBoundsChange property defaults to false for whatever reason; setting it to true here allows us to better handle device trait changes (like rotation or moving to a new window).
Next, we create a keyframe animation to taper the birthRate property down to 0 over the specified duration.
Then we add that animation in a transaction and use the completion block to set up a fade-out transition. (We set the view as the transition’s animation delegate, as described in the next session)
Finally, we use key-value coding to create a reference between the emitter layer and the transition so that it can be referenced and cleaned up later on.

Adopting the CAAnimationDelegate Protocol

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

The animationDidStop(_:) delegate method is called when our CATransition finishes. We then get the reference to the calling layer in order to remove all animations and remove it from its superlayer.

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

The end result: it’s as if NSHipster.com were parading down the streets of New York City (or rather, Brooklyn, if you really wanted to lean into the hipster thing)


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.

Xcode SpriteKit Particle Editor

Call up your designed SKEmitterNode by name or reimplement in code (if you’re the type to hand-roll all of your UIViews) for a bespoke confetti experience.

SceneKit Particle System

Again with the metaphors, SceneKit is to 3D what SpriteKit is to 2D.

In Xcode 11, open File > New, select SceneKit Scene File under the “Resource” heading, and you’ll find an entire 3D scene editor — right there in your Xcode window.

Xcode SceneKit Scene Editor

Add in a dynamic physics body and a turbulence effect, and you can whip up an astonishingly capable simulation in no time at all (though if you’re like me, you may find yourself spending hours just playing around with everything)

UIKit Dynamics

At the same time that SpriteKit entered the scene, you might imagine that UIKit started to get self-conscious about the “business only” vibe it was putting out.

So in iOS 7, in a desperate attempt to prove itself cool to its “fellow kids” UIKit added UIDynamicAnimator as part of a set of APIs known as “UIKit Dynamics”.

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

HEVC Video with Alpha Channel

Good news! Later this year, AVFoundation adds support for alpha channels in HEVC video! So if, say, you already had a snazzy After Effects composition of confetti, you could export that with a transparent background and composite it directly to your app or on the web.

For more details, check out WWDC 2019 Session 506.

Animated PNGs

Of course, this time next year, we’ll still be sending animated GIFs around to each other, despite all of their shortcomings.

Animated GIFs are especially awful for transparency. Without a proper alpha channel, GIFs are limited to a single, transparent matte color, which causes unsightly artifacts around edges.

We all know to use PNGs on the web for transparent images, but only a fraction of us are even aware 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:

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

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

<imgsrc="animated.png"/>

WebGL

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

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

Emoji

But really, we could do away with all of this confetti and express ourselves much more simply:

letmood="🥳"

It’s hard to believe that it’s been seven years since I started this site. We’ve been through a lot together, dear reader, so know that your ongoing support means the world to me. Thanks for learning with me over the years.

Until next week: May your code continue to compile and inspire.

Formatter

$
0
0

Conversion is a tireless errand in software development. Most programs boil down to some variation of transforming data into something more useful.

In the case of user-facing software, making data human-readable is an essential task — and a complex one at that. A user’s preferred language, calendar, and currency can all factor into how information should be displayed, as can other constraints, such as a label’s dimensions.

All of this is to say: calling description on an object just doesn’t cut it under most circumstances. Indeed, the real tool for this job is Formatter: an ancient, abstract class deep in the heart of the Foundation framework that’s responsible for transforming data into textual representations.


Formatter’s origins trace back to NSCell, which is used to display information and accept user input in tables, form fields, and other views in AppKit. Much of the API design of (NS)Formatter reflects this.

Back then, formatters came in two flavors: dates and numbers. But these days, there are formatters for everything from physical quantities and time intervals to personal names and postal addresses. And as if that weren’t enough to keep straight, a good portion of these have been soft-deprecated, or otherwise superseded by more capable APIs (that are also formatters).

To make sense of everything, this week’s article groups each of the built-in formatters into one of four categories:

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

Formatting Numbers and Quantities

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

NumberFormatter

NumberFormatter covers every aspect of number formatting imaginable. For better or for worse (mostly for better), this all-in-one API handles ordinals and cardinals, mathematical and scientific notation, percentages, and monetary amounts in various flavors. It can even write out numbers in a few different languages!

So whenever you reach for NumberFormatter, the first order of business is to establish what kind of number you’re working with and set the numberStyle property accordingly.

Number Styles

Number StyleExample Output
none123
decimal123.456
percent12%
scientific1.23456789E4
spellOutone hundred twenty-three
ordinal3rd
currency$1234.57
currencyAccounting($1234.57)
currencyISOCodeUSD1,234.57
currencyPlural1,234.57 US dollars

Rounding & Significant Digits

To prevent numbers from getting annoyingly pedantic (“thirty-two point three three — repeating, of course…”), you’ll want to get a handle on NumberFormatter’s rounding behavior. Here, you have two options:

varformatter=NumberFormatter()formatter.usesSignificantDigits=trueformatter.minimumSignificantDigits=1// defaultformatter.maximumSignificantDigits=6// defaultformatter.string(from:1234567)// 1234570formatter.string(from:1234.567)// 1234.57formatter.string(from:100.234567)// 100.235formatter.string(from:1.23000)// 1.23formatter.string(from:0.0000123)// 0.0000123
  • Set usesSignificantDigits to false(or keep as-is, since that’s the default) to format according to specific limits on how many decimal and fraction digits to show (the number of digits leading or trailing the decimal point, respectively).
varformatter=NumberFormatter()formatter.usesSignificantDigits=falseformatter.minimumIntegerDigits=0// defaultformatter.maximumIntegerDigits=42// default (seriously)formatter.minimumFractionDigits=0// defaultformatter.maximumFractionDigits=0// defaultformatter.string(from:1234567)// 1234567formatter.string(from:1234.567)// 1235formatter.string(from:100.234567)// 100formatter.string(from:1.23000)// 1formatter.string(from:0.0000123)// 0

If you need specific rounding behavior, such as “round to the nearest integer” or “round towards zero”, check out the roundingMode, roundingIncrement, 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:
MeasureUnit SubclassBase Unit
AccelerationUnitAccelerationmeters per second squared (m/s²)
Planar angle and rotationUnitAngledegrees (°)
AreaUnitAreasquare meters (m²)
Concentration of massUnitConcentrationMassmilligrams per deciliter (mg/dL)
DispersionUnitDispersionparts per million (ppm)
DurationUnitDurationseconds (sec)
Electric chargeUnitElectricChargecoulombs (C)
Electric currentUnitElectricCurrentamperes (A)
Electric potential differenceUnitElectricPotentialDifferencevolts (V)
Electric resistanceUnitElectricResistanceohms (Ω)
EnergyUnitEnergyjoules (J)
FrequencyUnitFrequencyhertz (Hz)
Fuel consumptionUnitFuelEfficiencyliters per 100 kilometers (L/100km)
IlluminanceUnitIlluminancelux (lx)
Information StorageUnitInformationStorageByte* (byte)
LengthUnitLengthmeters (m)
MassUnitMasskilograms (kg)
PowerUnitPowerwatts (W)
PressureUnitPressurenewtons per square meter (N/m²)
SpeedUnitSpeedmeters per second (m/s)
TemperatureUnitTemperaturekelvin (K)
VolumeUnitVolumeliters (L)

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


MeasurementFormatter and its associated APIs are a intuitive — just a delight to work with, honestly. The only potential snag for newcomers to Swift (or Objective-C old-timers, perhaps) are the use of generics to constrain Measurement values to a particular Unit type.

importFoundation// "The swift (Apus apus) can power itself to a speed of 111.6km/h"letspeed=Measurement<UnitSpeed>(value:111.6,unit:.kilometersPerHour)letformatter=MeasurementFormatter()formatter.string(from:speed)// 69.345 mph

Configuring the Underlying Number Formatter

By delegating much of its formatting responsibility to an underlying NumberFormatter property, MeasurementFormatter maintains a high degree of configurability while keeping a small API footprint.

Readers with an engineering background may have noticed that the localized speed in the previous example gained an extra significant figure along the way. As discussed previously, we can enable usesSignificantDigits and set maximumSignificantDigits to prevent incidental changes in precision.

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

Changing Which Unit is Displayed

A MeasurementFormatter, by default, will use the preferred unit for the user’s current locale (if one exists) instead of the one provided by a Measurement value.

Readers with a non-American background certainly noticed that the localized speed in the original example converted to a bizarre, archaic unit of measure known as “miles per hour”. You can override this default unit localization behavior by passing the providedUnit option.

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

Formatting Dates, Times, and Durations

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

DateFormatter

DateFormatter is the OG class for representing dates and times. And it remains your best, first choice for the majority of date formatting tasks.

For a while, there was a concern that it would become overburdened with responsibilities like its sibling NumberFormatter. But fortunately, recent SDK releases spawned new formatters for new functionality. We’ll talk about those in a little bit.

Date and Time Styles

The most important properties for a DateFormatter object are its dateStyle and timeStyle. As with NumberFormatter and its numberStyle, these date and time styles provide preset configurations for common formats.

StyleExample Output
DateTime
none“”“”
short“11/16/37”“3:30 PM”
medium“Nov 16, 1937”“3:30:32 PM”
long“November 16, 1937”“3:30:32 PM”
full“Tuesday, November 16, 1937 AD“3:30:42 PM EST”
letdate=Date()letformatter=DateFormatter()formatter.dateStyle=.longformatter.timeStyle=.longformatter.string(from:date)// July 15, 2019 at 9:41:00 AM PSTformatter.dateStyle=.shortformatter.timeStyle=.shortformatter.string(from:date)// "7/16/19, 9:41:00 AM"

dateStyle and timeStyle are set independently. So, to display just the time for a particular date, for example, you set dateStyle to none:

letformatter=DateFormatter()formatter.dateStyle=.noneformatter.timeStyle=.mediumletstring=formatter.string(from:Date())// 9:41:00 AM

As you might expect, each aspect of the date format can alternatively be configured individually, a la carte. For any aspiring time wizards NSDateFormatter has a bevy of different knobs and switches to play with.

ISO8601DateFormatter

When we wrote our first article about NSFormatter back in 2013, we made a point to include discussion 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.

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

Date Interval Styles

StyleExample Output
DateTime
none“”“”
short“6/30/14 - 7/11/14”“5:51 AM - 7:37 PM”
medium“Jun 30, 2014 - Jul 11, 2014”“5:51:49 AM - 7:38:29 PM”
long“June 30, 2014 - July 11, 2014”“6:02:54 AM GMT-8 - 7:49:34 PM GMT-8”
full“Monday, June 30, 2014 - Friday, July 11, 2014“6:03:28 PM Pacific Standard Time - 7:50:08 PM Pacific Standard Time”

DateComponentsFormatter

As the name implies, DateComponentsFormatter works with DateComponents values (previously), which contain a combination of discrete calendar quantities, such as “1 day and 2 hours”.

DateComponentsFormatter provides localized representations of date components in several different, pre-set formats:

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

Date Components Unit Styles

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

Formatting Context

Some years ago, formatters introduced the concept of formatting context, to handle situations where the capitalization and punctuation of a localized string may depend on whether it appears at the beginning or middle of a sentence. A context property is available for DateComponentsFormatter, as well as DateFormatter, NumberFormatter, and others.

Formatting ContextOutput
standalone“About 2 hours”
listItem“About 2 hours”
beginningOfSentence“About 2 hours”
middleOfSentence“about 2 hours”
dynamicDepends*

* A Dynamic context changes capitalization automatically depending on where it appears in the text for locales that may position strings differently depending on the content.

RelativeDateTimeFormatter

RelativeDateTimeFormatter is a newcomer in iOS 13 — and at the time of writing, still undocumented, so consider this an NSHipster exclusive scoop!

Longtime readers may recall 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

StyleExample
abbreviated“1 mo. ago” *
short“1 mo. ago”
full“1 month ago”
spellOut“one month ago”

*May produce output distinct from short for non-English locales.

Using Named Relative Date Times

By default, RelativeDateTimeFormatter adopts the formulaic convention we’ve seen so far. But you can set the dateTimeStyle property to .named to prefer localized deictic expressions— “tomorrow”, “yesterday”, “next week” — whenever one exists.

importFoundationletformatter=RelativeDateTimeFormatter()formatter.localizedString(from:DateComponents(day:-1))// "1 day ago"formatter.dateTimeStyle=.namedformatter.localizedString(from:DateComponents(day:-1))// "yesterday"

This just goes to show that beyond calendrical and temporal relativity, RelativeDateTimeFormatter is a real whiz at linguistic relativity, too! For example, English doesn’t have a word to describe the day before yesterday, whereas other languages, like German, do.

formatter.localizedString(from:DateComponents(day:-2))// "2 days ago"formatter.locale=Locale(identifier:"de_DE")formatter.localizedString(from:DateComponents(day:-2))// "vorgestern"

Hervorragend!


Formatting People and Places

ClassExample OutputAvailability
PersonNameComponentsFormatter“J. Appleseed”iOS 9.0+
macOS 10.11+
CNContactFormatter“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:

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

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

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

‘nuf said.

CNPostalAddressFormatter

CNPostalAddressFormatter provides a convenient Formatter-based API to functionality dating back to the original AddressBook framework.

The following example formats a constructed CNMutablePostalAddress, but you’ll most likely use existing CNPostalAddress values retrieved from the user’s address book.

letaddress=CNMutablePostalAddress()address.street="One Apple Park Way"address.city="Cupertino"address.state="CA"address.postalCode="95014"letaddressFormatter=CNPostalAddressFormatter()addressFormatter.string(from:address)/* "One Apple Park Way
        Cupertino CA 95014" */

Styling Formatted Attributed Strings

When formatting compound values, it can be hard to figure out where each component went in the final, resulting string. This can be a problem when you want to, for example, call out certain parts in the UI.

Rather than hacking together an ad-hoc, regex-based solution, CNPostalAddressFormatter provides a method that vends 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:

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

One Apple Park Way
CupertinoCA95014


Formatting Lists and Items

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

ListFormatter

Rounding out our survey of formatters in the Apple SDK, it’s another new addition in iOS 13: ListFormatter. To be completely honest, we didn’t know where to put this in the article, so we just kind of stuck it on the end here. (Though in hindsight, this is perhaps appropriate given the subject matter).

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

NSListFormatter provides locale-correct formatting of a list of items using the appropriate separator and conjunction. Note that the list formatter is unaware of the context where the joined string will be used, e.g., in the beginning of the sentence or used as a standalone string in the UI, so it will not provide any sort of capitalization customization on the given items, but merely join them as-is.

The string joined this way may not be grammatically correct when placed in a sentence, and it should only be used in a standalone manner.

tl;dr: This is joined(by:) with locale-aware serial and penultimate delimiters.

For simple lists of strings, you don’t even need to bother with instantiating ListFormatter— just call the localizedString(byJoining:) class method.

importFoundationletoperatingSystems=["macOS","iOS","iPadOS","watchOS","tvOS"]ListFormatter.localizedString(byJoining:operatingSystems)// "macOS, iOS, iPadOS, watchOS, and tvOS"

ListFormatter works as you’d expect for lists comprising zero, one, or two items.

ListFormatter.localizedString(byJoining:[])// ""ListFormatter.localizedString(byJoining:["Apple"])// "Apple"ListFormatter.localizedString(byJoining:["Jobs","Woz"])// "Jobs and Woz"

Lists of Formatted Values

ListFormatter exposes an underlying itemFormatter property, which effectively adds a map(_:) before calling joined(by:). You use itemFormatter whenever you’d formatting a list of non-String elements. For example, you can set a NumberFormatter as the itemFormatter for a ListFormatter to turn an array of cardinals (Int values) into a localized list of ordinals.

letnumberFormatter=NumberFormatter()numberFormatter.numberStyle=.ordinalletlistFormatter=ListFormatter()listFormatter.itemFormatter=numberFormatterlistFormatter.string(from:[1,2,3])// "1st, 2nd, and 3rd"

As some of the oldest members of the Foundation framework, NSNumberFormatter and NSDateFormatter are astonishingly well-suited to their respective domains, in that way only decade-old software can. This tradition of excellence is carried by the most recent incarnations as well.

If your app deals in numbers or dates (or time intervals or names or lists measurements of any kind), then NSFormatter is indispensable.

And if your app doesn’t… then the question is, what does it do, exactly?

Invest in learning all of the secrets of Foundation formatters to get everything exactly how you want them. And if you find yourself with formatting logic scattered across your app, consider creating your own Formatter subclass to consolidate all of that business logic in one place.

Network Link Conditioner

$
0
0

Product design is about empathy. Knowing what a user wants, what they like, what they dislike, what causes them frustration, and learning to understand and embody those motivations — this is what it takes to make something insanely great.

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

There is, however, one critical factor that app developers often miss: network condition, or more specifically, the latency and bandwidth of an Internet connection.

For something so essential to user experience, it’s unfortunate that most developers take an ad-hoc approach to field-testing their apps under different conditions (if at all).

This week on NSHipster, we’ll be talking about the Network Link Conditioner, a utility that allows macOS and iOS devices to accurately and consistently simulate adverse networking environments.

Installation

Network Link Conditioner can be found in the “Additional Tools for Xcode” package. You can download this from the Downloads for Apple Developers page.

Search for “Additional Tools” and select the appropriate release of the package.

Additional Tools - Hardware

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

Install Network Link Conditioner

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

Network Link Conditioner

Controlling Bandwidth, Latency, and Packet Loss

Enabling the Network Link Conditioner changes the network environment system-wide according to the selected configuration, limiting uplink or 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.

Preset


Now try running your app with the Network Link Conditioner enabled:

How does network latency affect your app startup?
What effect does bandwidth have on table view scroll performance?
Does your app work at all with 100% packet loss?

Enabling Network Link Conditioner on iOS Devices

Although the preference pane works well for developing on the simulator, it’s also important to test on a real device. Fortunately, the Network Link Conditioner is available for iOS as well.

To use the Network Link Conditioner on iOS, set up your device for development:

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

iOS Devices

Now you’ll have access to the Developer section of the Settings app. You can enable and configure the Network Link Conditioner on your iOS device under Settings > Developer > Networking. (Just remember to turn it off after you’re done testing!).

macOS Accessibility Keyboard

$
0
0

For a while now, the distinction between “desktop” and “mobile” has become increasingly tenuous. As the computers in our pockets grow ever more capable, they more closely resemble the computers typically situated on our desks and laps. This trend was especially pronounced in this year’s WWDC, with the announcement of Catalyst 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).

Accessibility Keyboard Panel Editor

Click the checkbox labeled “Enable Accessibility Keyboard” to present the accessibility keyboard over the windows of the frontmost app.

The software keyboard reproduces the layout of your hardware keyboard. The modifier keys outlined in red (, , ) are “sticky keys” and remain active until a non-“sticky” key is activated, allowing for capital letters and keyboard shortcuts. Along the top row are iOS-style suggestions that update automatically as you type.

However, the most interesting feature of the Accessibility Keyboard is tucked behind the ⚙︎ button on the top right corner — the ability to customize and create your own keyboards!

Customizing and Creating Your Own Accessibility Keyboards

Panel Editor is a built-in app that lets you edit Accessibility Keyboard panels.

Accessibility Keyboard Panel Editor window and icon

For something so obscure, the Panel Editor app is remarkably well made. Adding, moving, and editing buttons on a panel is a cinch. You can even click and drag to select and move multiple buttons at once, and group buttons together at your convenience.

Each button has a name as well as options for font size, color, and position. By default, the name appears in the button itself, but you can specify an image to display instead.

You can configure a button to perform any one of the following actions when clicked:

  • None
  • Go Back (navigate between panels in Accessibility Keyboard)
  • Open Panel
  • Show / Hide Toolbar
  • Dwell (relevant to head- or eye-tracking technology, and other hardware switches)
  • AppleScript
  • Enter Text
  • Press Keys
  • Open App
  • System Event
  • Typing Suggestions

Of these, “Enter Text” is the most common. We’ll use that in our next example as a way to solve the problem of creating input methods for scripts without a keyboard.

Creating an IPA Keyboard

Standard Latin script is insufficient for expressing phonetics, how a word sounds when spoken. As English speakers, we know this all too well. That’s why linguists invented their own script, the International Phonetic Alphabet (IPA). Whereas typical letters may have different pronunciations across dialects (/tə.ˈme͡ɪ.do͡ʊ/, /tə.ˈmɑ.to͡ʊ/) — or even within the same word (like the letter “a” in “application”) — IPA symbols represent a single sound, or phoneme; the mid-central vowel, “ə” (a.k.a “schwa”) sounds the same whether its part of an English word or a Japanese word or nonsensical babbling.

Working with IPA on computers has pretty much always been a PITA, for three reasons:

Incomplete Text Encoding
Until Unicode version 6.1, some IPA symbols didn’t have a specified code point, forcing linguists to either use a similar-looking character or define ad hoc encodings within 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.

$ tree ~/Library/Application Support/com.apple.AssistiveControl/dwellControlUserPanels1.ascconfig/
        Contents
        ├── Info.plist
        └── Resources
        ├── AssetIndex.plist
        └── PanelDefinitions.plist
        

Opening up PanelDefinitions.plist reveals the inner structure of our custom virtual keyboard layout:

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

The PanelObjects key is associated with an array of dictionaries, each representing a single button. Fortunately, he majority of the key names are self-explanatory:

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

The takeaway from looking at the file format is that it’d be very easy to generate Accessibility Keyboard panels in code, rather than using the Panel Editor app. (In fact, we used find-and-replace to bulk resize the buttons in the IPA keyboard, a task that would have otherwise taken 100⨉ longer).

Additional Use Cases

There are dozens of scripts comprising hundreds of characters that lack a dedicated keyboard layout. And the macOS Accessibility Keyboard offers a wonderful, built-in solution for producing these characters.

But what else could you do with this technology, now that you know it exists?

Here are a few ideas for you to consider:

Templating Frequent Communications

Do you find yourself writing the same things over and over again in emails or GitHub Issues? Create a custom, virtual keyboard to summon boilerplate with the click of your mouse or the tap of your trackpad.

Generative Text

The Accessibility Keyboard isn’t limited to canned responses. Thanks to its AppleScript integration, you can populate text dynamically from virtually any source.

For example, you could create a Fortune button that inserts a (pseudo)random entry from 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.

Identifiable

$
0
0

What constitutes the identity of an object?

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

Our humble publication has frequented this topic with some regularity, whether it was attempting to make sense 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:

protocolIdentifiable{associatedtypeID:Hashablevarid:ID{get}}

Values of types adopting the Identifiable protocol provide a stable identifier for the entities they represent.

For example, a Parcel object may use the id property requirement to track the package en route to its final destination. No matter where the package goes, it can always be looked up by its id:

importCoreLocationstructParcel:Identifiable{letid:Stringvarlocation:CLPlacemark?}

The Swift Evolution proposal for Identifiable, SE-0261, was kept small and focused in order to be incorporated quickly. So, if you were to ask, “What do you actually get by conforming to Identifiable?”, the answer right now is “Not much.” As mentioned in the future directions, conformance to Identifiable has the potential to unlock simpler and/or more optimized versions of other functionality, such as the new ordered collection diffing APIs.

But the question remains: “Why bother conforming to Identifiable?”

The functionality you get from adopting Identifiable is primarily semantic, and require some more explanation. It’s sort of like asking, “Why bother conforming to Equatable?”

And actually, that’s not a bad place to start. Let’s talk first about Equatable and its relation to Identifiable:

Identifiable vs. Equatable

Identifiable distinguishes the identity of an entity from its state.

A parcel from our previous example will change locations frequently as it travels to its recipient. Yet a normal equality check (==) would fail the moment it leaves its sender:

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

While this is an expected outcome from a small, contrived example, the very same behavior can lead to confusing results further down the stack, where you’re not as clear about how different parts work with one another.

vartrackedPackages:[Parcel]=...trackedPackages.contains(Parcel(id:"123456789012"))// false (?)

But wait, would that contains call fail if trackedPackages were a Dictionary instead of an Array?

The answer is… it depends!

To find out why, we need to talk briefly about the Hashable protocol.

Identifiable vs. Hashable

Recall our previous article on the subject, where we noted that the Swift compile can automatically synthesize conformance to Hashable.

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,

extensionParcel:Hashable{funchash(intohasher:inoutHasher){hasher.combine(id)}}

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:

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

If you wanted to guarantee uniqueness across launches, you might instead initialize the sequence with a value read from a persistent store like UserDefaults. And if you found yourself using this pattern extensively, you might consider factoring everything into a self-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:

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

Beyond minor ergonomic and cosmetic issues, UUID serves as an excellent alternative to Int for generated identifiers.

However, your model may already be uniquely identified by a value, thereby obviating the need to generate a new one. Under such circumstances, that value is likely to be a String.

String as ID

We use strings as identifiers all the time, whether it takes the form of a username or a checksum or a translation key or something else entirely.

The main drawback to this approach is that, thanks to The Unicode® Standard, strings encode thousands of years of written human communication. So you’ll need a strategy for handling identifiers like “⽜”, “𐂌”, “”, and “🐮” …and that’s to say nothing of the more pedestrian concerns, like leading and trailing whitespace and case-sensitivity!

Normalization is the key to successfully using strings as identifiers. The easiest place to do this is in the initializer, but, again, if you find yourself repeating this code over and over, property wrappers can help you here, too.

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

URL as ID

URLs (or URIs if you want to be pedantic) are arguably the most ubiquitous kind of identifier among all of the ones described in this article. Every day, billions of people around the world use URLs as a way to point to a particular part of the internet. So URLs a natural choice for an id value if your models already include them.

URLs look like strings, but they use syntax to encode multiple components, like scheme, authority, path, query, and fragment. Although these formatting rules dispense with much of the invalid input you might otherwise have to consider for strings, they still share many of their complexities — with a few new ones, just for fun.

The essential problem is that equivalent URLs may not be equal. Intrinsic, syntactic details like case sensitivity, the presence or absence of a trailing slash (/), and the order of query components all affect equality comparison. 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.

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

If your model gets identifier URLs for records from a trusted source, then you may take URL equality as an article of faith; if you regard the server as the ultimate source of truth, it’s often best to follow their lead.

But if you’re working with URLs in any other capacity, you’ll want to employ some combination of URL normalizations before using them as an identifier.

Unfortunately, the Foundation framework doesn’t provide a single, suitable API for URL canonicalization, but URL and URLComponents provide enough on their own to let you roll your own (though we’ll leave that as an exercise for the reader):

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

Creating Custom Identifier ID Types

UUID and URL both look like strings, but they use syntax rules to encode information in a structured way. And depending on your app’s particular domain, you may find other structured data types that would make for a suitable identifier.

Thanks to the flexible design of the Identifiable protocol, there’s nothing to stop you from implementing your own ID type.

For example, if you’re working in a retail space, you might create or repurpose an existing UPC type to serve as an identifier:

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

Three Forms of ID Requirements

As Identifiable makes its way into codebases, you’re likely to see it used in one of three different ways:

The newer the code, the more likely it will be for id to be a stored property — most often this will be declared as a constant (that is, with let):

importFoundation// Style 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:

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

Ever since Swift first came onto the scene, the popular fashion has been to eschew all reference types for value types. And this neophilic tendency has only intensified with the announcement of SwiftUI. But taking such a hard-line approach makes a value judgment of something better understood to be a difference in outlook.

It’s no coincidence that much of the terminology of programming is shared by mathematics and philosophy. As developers, our work is to construct logical universes, after all. And in doing so, we’re regularly tasked with reconciling our own mental models against that of every other abstraction we encounter down the stack — down to the very way that we understand electricity and magnetism to work.

Dark Mode on iOS 13

$
0
0

Today is Labor Day in the United States (and Labour Day in Canada), a day to celebrate the achievement of the workers who organized to bring about fair and safe conditions for employees — protections that serve as the foundation for the modern workplace.

Labor Day is also the unofficial end of summer; the long weekend acting as a buffer between the lemonades, sunburns, and trashy romance novels of June and the pumpkin spice 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.

Adopting Dark Mode on iOS

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

Of course, the challenge with Apple technologies is that short of telling you that “you’re holding it wrong”, they’ll rarely acknowledge the existence of prior art or alternatives, let alone provide a transition document that in any way resembles how everyone was doing things before we got an officially-sanctioned API. (Then again, can you really blame them?)

If you were following 100% of Apple’s guidance to the letter, you’d barely have to change a line or code to get your app ready for next week’s special event. But most apps are built on solutions we built for ourselves to bridge the gaps in the SDK, and it may not be clear how to get on the 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:

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

But before you do, you might as well do one better and replace it with something that will stand the test of time.

Nix UIColor Hexadecimal Initializers

Among the most common extensions you’ll find in a Swiss Army Knife-style CocoaPod is a category on UIColor that initializes from a hexadecimal string. Something along the lines of this:

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

Setting aside 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:

NameAPILightDark
DefaultAccessibleDefaultAccessible
RedsystemRed
OrangesystemOrange
YellowsystemYellow
GreensystemGreen
TealsystemTeal
BluesystemBlue
IndigosystemIndigo
PurplesystemPurple
PinksystemPink
GraysystemGray
Gray (2)systemGray2
Gray (3)systemGray3
Gray (4)systemGray4
Gray (5)systemGray5
Gray (6)systemGray6

You may notice that this table doesn’t provide direct correspondences for black or white (or brown, but disregard that for now).

Black and white don’t have adaptive colors because their names would cease to be descriptive in Dark Mode; if .systemBlack existed, it’d pretty much have to be .white to be visible in a dark color pallet.

Which gets to a deeper point about color management in an era of Dark Mode…

Use Semantic Colors

The best way to ensure consistent rendering of your UI on any device in any appearance mode is to use semantic colors, named according to their function rather than appearance.

Similar to how Dynamic Type uses semantic tags like “Headline” and “Body” to automatically set the most suitable font for the user’s display preferences, semantic colors — or what Apple’s calling UI Element Colors— provide future-proof behavior for your views and controls.

When styling your component, set the color to the closest UIColor class property below:

Label Colors
label
secondaryLabel
tertiaryLabel
quaternaryLabel
Text Colors
placeholderText
Link Colors
link
Separator Colors
separator
opaqueSeparator
Fill Colors
systemFill
secondarySystemFill
tertiarySystemFill
quaternarySystemFill
Background Colors
systemBackground
secondarySystemBackground
tertiarySystemBackground
Grouped Background Colors
systemGroupedBackground
secondarySystemGroupedBackground
tertiarySystemGroupedBackground

Upgrade Homegrown Semantic Color Palettes

If you’ve given any thought to color management in your app you’ll have likely landed on some form of the following strategy, whereby you define semantic colors according to fixed colors within a namespace or in an extension to UIColor.

For example, the following example shows how an app might define UIColor constants from the Material UI color system and reference them from semantic UIColor class properties:

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

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

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

Nothing about the fixed Material UI color constants has to change with this approach. Instead, the semantic color property customAccent provides a dynamic color that uses the color most appropriate for the current rendering context. When Dark Mode is enabled, a lighter orange is used to contrast against the darker color palette; otherwise, the behavior is unchanged from the original implementation.

The extra #available check creates some bloat in the implementation, but it’s a small price to pay for the flexibility this approach provides.

Unfortunately, there’s one crucial shortcoming to using color properties in this way: they can’t be referenced from Interface Builder.

If your app uses either Storyboards or XIBs, the best approach is to use color assets.

Manage Colors with an Asset Catalog

Color assets let you manage colors in an Xcode Asset Catalog in the same way that you do for images, data, or other resources.

Asset Catalog Color Asset

To create a color set, open an Asset Catalog in your Xcode project, click the + button on the bottom left-hand corner, and select “New Color Set”. In the Attributes inspector, select “Any, Dark” appearance. Colors are frequently expressed in the form (“#RRGGBB”); you can enter colors in this form by selecting “8-bit Hexadecimal” from the “Input Method” drop-down.

Asset Catalog Color Asset Attributes Inspector

Here, we’ve done the same as before, except instead of defining fixed UIColor constants like orange300 in code, we set those in the color set itself. Now when it comes time to reference the color asset by the existing semantic class property, we can use the UIColor named initializer:

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

Your opinion about color assets will largely be informed by your preference or dispreference towards specialized Xcode document editors. Some folks like to have everything spelled out in code, while others appreciate the affordances provided by a bespoke UI like the one provided by Xcode for color sets.

In fact, your opinion of color assets is probably concomitant with your feelings about Storyboards — which is convenient, because the killer feature of color assets is that they can be referenced from within Interface Builder. (If you’re not on team IB, then you can safely skip this whole discussion.)

Replace Instances of “Custom Color” in XIBs and Storyboards

The “Custom Color” option in Interface Builder that brings up the macOS system-native color picker suffers the same problem as the color literals and fixed colors we talked about earlier. If you want your XIB-powered views to look good on iOS 13, you’ll need to migrate to named colors.

This can be done easily: select any component in your scene, and you can set its color attribute using the same, named color defined in your Asset Catalog.

Interface Builder Named Color

For a small project, this can be done by hand for all your screens in under an hour. However, for a larger app, this is a process you’ll want to automate.

XIB Anatomy

Under the hood, XIB and Storyboard files are merely XML files like any other:

<?xml version="1.0" encoding="UTF-8"?><documenttype="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"<#...#>>
        <deviceid="retina6_1"orientation="portrait"><adaptationid="fullscreen"/></device><dependencies><deploymentidentifier="iOS"/><plugInidentifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin"version="14490.49"/><capabilityname="Safe area layout guides"minToolsVersion="9.0"/><capabilityname="documents saved in the Xcode 8 format"minToolsVersion="8.0"/></dependencies><scenes><!--View Controller--><scenesceneID="nsh-ips-ter"><objects><viewControllerid="dar-kmo-de1"customClass="ViewController"customModule="NSHipster"customModuleProvider="target"sceneMemberID="viewController"><viewkey="view"contentMode="scaleToFill"id="mai-nv-iew"><rectkey="frame"x="0.0"y="0.0"width="414"height="896"/><colorkey="backgroundColor"red="1"green="0.69019607843137254"blue="0.0"alpha="1"colorSpace="custom"customColorSpace="sRGB"/></view></viewController><placeholderplaceholderIdentifier="IBFirstResponder"id="dkx-z0-nzr"sceneMemberID="firstResponder"/></objects></scene></scenes></document>

We wouldn’t want to write this from scratch by hand, but there’s nothing too mysterious going on here.

So consider what happens when you use Xcode to switch the custom background color of the main view to a named color:

<?xml version="1.0" encoding="UTF-8"?><documenttype="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"<#...#>>
        <device<#...#>/>
        <dependencies><#...#>
        <capabilityname="Named colors"minToolsVersion="9.0"/><!-- ❶ --></dependencies><scenes><!-- scenes.scene.objects.viewController.view --><#...#>
        <viewkey="view"contentMode="scaleToFill"id="mai-nv-iew"><#...#>
        <colorkey="backgroundColor"name="Accent"/><!-- ❷ --></view></scenes><resources><namedColorname="Accent"><!-- ❸ --><colorred="1"green="0.69019607843137254"blue="0.0"alpha="1"colorSpace="custom"customColorSpace="sRGB"/></namedColor></resources></document>
A new "Named colors" capability is added as a dependency for opening the document (Xcode uses this to determine whether it can edit files created by newer versions).
The red, green, blue, and alpha components on the color element were replaced by a single name attribute.
A corresponding namedColor element was added to the top-level resources element.

Based on this understanding, we should know enough to make this change en masse with our own tooling!

Finding All Custom Colors

The first order of business when migrating your Storyboards and XIBs for Dark Mode is to find all of the instances of custom colors. You could go through each by hand and click each of the visual elements… or you could run the following command:

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

This command prints the name of each file followed by each custom, unnamed color element it found (denoted by the colorSpace="custom" attribute).

The resulting list serves as a battle plan for the next phase of attack:

Finding Each Distinct Custom Color

Apps tend to reuse a small set of colors across their views — as they should! By running the following command, you can generate a sorted, unique’d list of every custom color in every XIB or Storyboard:

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

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.


Apple Push Notification Device Tokens

$
0
0

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:

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:

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

Whereas previously, you could coerce NSData to spill its entire contents by converting it into a String, it now reports its length and a truncated summary of its internal bytes.

So from now on, if you need to convert your push notification registration deviceToken into a Base16-encoded / hexadecimal string representation, you should do the following:

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

For clarity, let’s break this down and explain each part:

  • The map method operates on each element of a sequence. Because Data is a sequence of bytes in Swift, the passed closure is evaluated for each byte in deviceToken.
  • The String(format:) initializer evaluates each byte in the data (represented by the anonymous parameter $0) using the %02x format specifier, to produce a zero-padded, 2-digit hexadecimal representation of the byte / 8-bit integer.
  • After collecting each byte representation created by the map method, joined() concatenates each element into a single string.

Was Apple irresponsible in making this particular change?

No, not really — developers shouldn’t have relied on a specific format for an object’s description.

Dumping an entire Data value’s contents becomes untenable at a certain point, and this change to a more succinct summary makes debugging larger data blobs significantly easier.

Like we said about laws at the start of this article, precedence is a form of inertia, not an immutable truth.

Stare decisis plays an important role throughout software engineering. Examples like the “Referer” [sic] header”— even the conventions we have about the direction of electricity flow— demonstrate the value of sticking to decisions, unless an opportunity arises to compel change.

iOS 13

$
0
0

Apple announced a lot at WWDC this year. During the conference and the days that followed, a lot of us walked around in a daze, attempting to recover from have our minds “hashtag-mindblown’d” (#🤯). But now a few months later, after everything announced has launched (well, almost everything) those very same keynote announcements now elicit far different reactions:

Although the lion’s share of attention has been showered on the aforementioned features, not nearly enough coverage has been given to the rest of iOS 13 — and that’s a shame, because this release is among the most exciting in terms of new functionality and satisfying in terms of improving existing functionality.

So to mark last week’s release of iOS 13, we’re taking a look at some obscure (largely undocumented) APIs that you can now use in your apps. We’ve scavenged the best bits out of the iOS 13 Release NotesAPI diffs, and now present them to you.

Here are some of our favorite things you can do starting in iOS 13:


Generate Rich Representations of URLs

New in iOS 13, 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:

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

After setting appropriate constraints (and perhaps a call to sizeToFit()), you’ll get the following, which the user can tap to preview the linked webpage:

iOS 13 Link View

Alternatively, if you already have the metadata in-app, or otherwise can’t or don’t want to fetch remotely, you can construct an LPLinkMetadata directly:

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

Perform On-Device Speech Recognition

SFSpeechRecognizer gets a major upgrade in iOS 13 — most notably for its added support for on-device speech recognition.

Previously, transcription required an internet connection and was restricted to a maximum of 1-minute duration with daily limits for requests. But now, you can do speech recognition completely on-device and offline, with no limitations. The only caveats are that offline transcription isn’t as good as what you’d get with a server connection, and is only available for certain languages.

To determine whether offline transcription is available for the user’s locale, check the SFSpeechRecognizer 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).

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

Information about pitch and voicing and other features could be used by your app (perhaps in coordination with CoreML) to differentiate between speakers or determine subtext from a speaker’s inflection.

Send and Receive Web Socket Messages

Speaking of the Foundation URL Loading System, we now have native support for something that’s been at the top of our wish list for many years: web sockets.

Thanks to the 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.

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

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

Keep Promises in JavaScript

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

For the uninitiated: in JavaScript, a Promise is an object that represents the eventual completion (or rejection) of an asynchronous operation and its resulting value. Promises are a mainstay of modern JS development — perhaps most notably within the fetch API.

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

Respond to Objective-C Associated Objects (?)

On a lark, we decided to see if there was anything new in Objective-C this year and were surprised to find out 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…

iOS 13 Share Actions

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

Format Lists and Relative Times

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

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:

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

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

Manage Background Tasks with Ease

One of the things that often differentiates category-defining apps from their competitors is their use of background tasks to make sure the app is fully synced and updated for the next time it enters the foreground.

iOS 7 was the first release to 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.

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

As described in this year’s WWDC session “Advances in App Background Execution”, the framework distinguishes between two broad classes of background tasks:

  • app refresh tasks: short-lived tasks that keep an app up-to-date throughout the day
  • background processing tasks: long-lived tasks for performing deferrable maintenance tasks

The WWDC session and the accompanying sample code project do a great job of explaining how to incorporate both of these into your app. But if you want the quick gist of it, here’s a small example of an app that schedules periodic refreshes from a web server:

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

Annotate Text Content Types for Better Accessibility

You know how frustrating it is to hear some people read out URLs? (“eɪʧ ti ti pi ˈkoʊlən slæʃ slæʃ ˈdʌbəlju ˈdʌbəlju ˈdʌbəlju dɑt”…) That’s what it can be like when VoiceOver tries to read something without knowing more about what it’s reading.

iOS 13 promises to improve the situation considerably with the new accessibilityTextualContext property and UIAccessibilityTextAttributeContextNSAttributedString attribute key. Whenever possible, be sure to annotate views and attributed strings with the constant that best describes the kind of text being displayed:

  • UIAccessibilityTextualContextConsole
  • UIAccessibilityTextualContextFileSystem
  • UIAccessibilityTextualContextMessaging
  • UIAccessibilityTextualContextNarrative
  • UIAccessibilityTextualContextSourceCode
  • UIAccessibilityTextualContextSpreadsheet
  • UIAccessibilityTextualContextWordProcessing

Remove Implicitly Unwrapped Optionals from View Controllers Initialized from Storyboards

SwiftUI may have signaled the eventual end of Storyboards, but that doesn’t mean that things aren’t and won’t continue to get better until if / when that day comes.

One of the most irritating anti-patterns for Swift purists when working on iOS projects with Storyboards has been view controller initialization. Due to an impedance mismatch between Interface Builder’s “prepare for segues” approach and Swift’s object initialization rules, we frequently had to choose between making all of our properties non-private, variable, and (implicitly unwrapped) optionals, or foregoing Storyboards entirely.

Xcode 11 and iOS 13 allow these paradigms to reconcile their differences by way of the new @IBSegueAction attribute and some new UIStoryboard class methods:

First, the @IBSegueAction attribute can be applied view controller method declarations to designate itself as the API responsible for creating a segue’s destination view controller (i.e. the destinationViewController property of the segue parameter in the prepare(for:sender:) method).

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

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

importUIKitstructPerson{...}classProfileViewController:UIViewController{letname:StringletavatarImageURL:URL?init?(coder:NSCoder,name:String,avatarImageURL:URL?){self.name=nameself.avatarImageURL=avatarImageURLsuper.init(coder:coder)}requiredinit?(coder:NSCoder){fatalError("init(coder:) has not been implemented")}}letstoryboard=UIStoryboard(name:"ProfileViewController",bundle:nil)storyboard.instantiateInitialViewController(creator:{decoderinProfileViewController(coder:decoder,name:"Johnny Appleseed",avatarImageURL:nil)})

Together with the new UIKit Scene APIs, iOS 13 gives us a lot to work with as we wait for SwiftUI to mature and stabilize.


That does it for our round-up of iOS 13 features that you may have missed. But rest assured — we’re planning to cover many more new APIs in future NSHipster articles.

If there’s anything we missed that you’d like for us to cover, please @ us on Twitter!

SwiftUI Previews on macOS Catalina and Xcode 11

$
0
0

Working on a large iOS codebase often involves a lot of waiting: Waiting for Xcode to index your files, waiting for Swift and Objective-C code to compile, waiting for the Simulator to boot and your app to launch…

And after all of that, you spend even more time getting your app into a particular state and onto a particular screen, just to see whether the Auto Layout constraint you just added fixes that regression you found. It didn’t, of course, so you jump back into Xcode, tweak the Content Hugging Priority, hit R, and start the whole process again.

We might relate our sorry predicament 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:

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

Normally, if we wanted to test how our UI element performs, we’d have to add it to a view in our app, build and run, and navigate to that screen. But with Xcode 11, we can now see a preview side-by-side with the code editor by adding the following under the original declaration of BorderedButton:

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

Using a new feature called dynamic replacement, Xcode can update this preview without recompiling — within moments of your making a code change. This lets you rapidly prototype changes like never before.

Want to see how your button handles long titles? Bang away on your keyboard within the call to setTitle(_:for:) in your preview, and test out potential fixes in your underlying implementation without so much as leaving your current file!

Previewing Multiple States

Let’s say our app had a FavoriteButton— a distant cousin (perhaps by composition) to BorderedButton. In its default state, it shows has the title “Favorite” and displays a icon. When its isFavorited property is set to true, the title is set to “Unfavorite” and displays a ♡̸ icon.

We can preview both at once by wrapping two UIViewPreview instances within a single SwiftUI Group:

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

Previewing Dark Mode

With Dark Mode in iOS 13, it’s always a good idea to double-check that your custom views are configured with dynamic colors or accommodate both light and dark appearance in some other way.

An easy way to do this would be to use a ForEach element to render a preview for each case in the ColorScheme enumeration:

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

Previewing Dynamic Type Size Categories

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

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

Previewing Different Locales

Xcode Previews are especially time-saving when it comes to localizing an app into multiple languages. Compared to the hassle of configuring Simulator back and forth between different languages and regions, this new approach makes a world of difference.

Let’s say that, in addition to English, your app supported various right-to-left languages. You could verify that 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)

Previewing View Controllers on Different Devices

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

#if canImport(SwiftUI) && DEBUGimportSwiftUIletdeviceNames:[String]=["iPhone SE","iPad 11 Pro Max","iPad Pro (11-inch)"]@available(iOS 13.0, *)structViewController_Preview:PreviewProvider{staticvarpreviews:someView{ForEach(deviceNames,id:\.self){deviceNameinUIViewControllerPreview{UIStoryboard(name:"Main",bundle:nil).instantiateInitialViewController{coderinViewController(coder:coder)}!}.previewDevice(PreviewDevice(rawValue:deviceName)).previewDisplayName(deviceName)}}}#endif

Although most of us are still some years away from shipping SwiftUI in our apps (whether by choice or necessity), we can all immediately benefit from the order-of-magnitude improvement it enables with Xcode 11 on macOS Catalina.

By eliminating so much time spent waiting for things to happen, we not only get (literally) hours more time each week, but we unlock the possibility of maintaining an unbroken flow state during that time. Not only that, but the convenience of integrated tests fundamentally changes the calculus for testing: instead of being a rare “nice to have,” they’re the new default. Plus: these inline previews serve as living documentation that can help teams both large and small finally get a handle on their design system.

It’s hard to overstate how much of a game-changer Xcode Previews are for iOS development, and we couldn’t be happier to incorporate them into our workflow.

MetricKit

$
0
0

As an undergraduate student, I had a radio show called “Goodbye, Blue Monday” (I was really into Vonnegut at the time). It was nothing glamorous — just a weekly, 2-hour slot at the end of the night before the station switched into automation.

If you happened to be driving through the hills of Pittsburgh, Pennsylvania late at night with your radio tuned 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.

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

Apple automatically collects metrics from apps installed on the App Store. You can view them in Xcode 11 by opening the Organizer (O) and selecting the new Metrics tab.

MetricKit complement Xcode Organizer Metrics by providing a programmatic way to receive daily information about how your app is performing in the field. With this information, you can collect, aggregate, and analyze on your own in greater detail than you can through Xcode.

Understanding App Metrics

Metrics can help uncover issues you might not have seen while testing locally, and allow you to track 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

MetricKit Diagram

Battery life depends on a lot of different factors. Physical aspects like the age of the device and the number of charge cycles are determinative, but the way your phone is used matters, too. Things like CPU usage, the brightness of the display and the colors on the screen, and how often radios are used to fetch data or get your current location — all of these can have a big impact. But the main thing to keep in mind is that users care a lot about battery life.

Aside from how good the camera is, the amount of time between charges is the deciding factor when someone buys a new phone these days. So when their new, expensive phone doesn’t make it through the day, they’re going to be pretty unhappy.

Until recently, Apple’s taken most of the heat on battery issues. But since iOS 12 and its new Battery Usage screen in Settings, users now have a way to tell when their favorite app is to blame. Fortunately, with iOS 13 you now have everything you need to make sure your app doesn’t run afoul of reasonable energy usage.

Performance

Performance is another key factor in the overall user experience. Normally, we might look to stats like processor clock speed or frame rate as a measure of performance. But instead, Apple’s focusing on less abstract and more actionable metrics:

Hang Rate
How often is the main / UI thread blocked, such that the app is unresponsive to user input?
Launch Time
How long does an app take to become usable after the user taps its icon?
Peak Memory & Memory at Suspension
How much memory does the app use at its peak and just before entering the background?
Disk Writes
How often does the app write to disk, which — if you didn’t already know — is a comparatively slow operation(even with the flash storage on an iPhone!)

Using MetricKit

From the perspective of an API consumer, it’s hard to imagine how MetricKit could be easier to incorporate. All you need is for some part of your app to serve as a metric subscriber (an obvious choice is your AppDelegate), and for it to be added to the shared MXMetricManager:

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

iOS automatically collects samples while your app is being used, and once per day (every 24 hours), it’ll send an aggregated report with those metrics.

To verify that your MXMetricManagerSubscriber is having its delegate method called as expected, select Simulate MetricKit Payloads from the Debug menu while Xcode is running your app.

Annotating Critical Code Sections with Signposts

In addition to the baseline statistics collected for you, you can use 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:

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

Creating a Self-Hosted Web Service for Collecting App Metrics

Now that you have this information, what do you do with it? How do we fill that ... placeholder in our implementation of didReceive(_:)?

You could pass that along to some paid analytics or crash reporting service, but where’s the fun in that? Let’s build our own web service to collect these for further analysis:

Storing and Querying Metrics with PostgreSQL

The MXMetricPayload objects received by metrics manager subscribers have a convenient jsonRepresentation() method that generates something like this:

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

As you can see, there’s a lot baked into this representation. Defining a schema for all of this information would be a lot of work, and there’s no guarantee that this won’t change in the future. So instead, let’s embrace the NoSQL paradigm (albeit responsibly, using Postgres) by storing payloads in a JSONB column:

CREATETABLEIFNOTEXISTSmetrics(idBIGINTGENERATEDBYDEFAULTASIDENTITYPRIMARYKEY,payloadJSONBNOTNULL);

So easy!

We can extract individual fields from payloads using JSON operators like so:

SELECT(payload->'applicationTimeMetrics'->>'cumulativeForegroundTime')::INTERVALFROMmetrics;--      interval-- ═══════════════════--  @ 11 mins 40 secs-- (1 row)

Advanced: Creating Views

JSON operators in PostgreSQL can be cumbersome to work with — especially for more complex queries. One way to help with that is to create a view (materialized or otherwise) to project the most important information to you in the most convenient representation:

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

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

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

Creating a Web Service

In this example, most of the heavy lifting is delegated to Postgres, making the server-side implementation rather boring. For completeness, here are some reference implementations in Ruby (Sinatra) and JavaScript (Express):

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

Sending Metrics as JSON

Now that we have everything set up, the final step is to implement the required MXMetricManagerSubscriber delegate method didReceive(_:) to pass that information along to our web service:

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

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

Metrics offer a convenient way to at least make sure that things aren’t too slow or too draining. And though they provide but a glimpse in the aggregate of how our apps are being enjoyed, it’s just enough to help us honor both our creation and our audience with a great user experience.

Device Identifiers and Fingerprinting on iOS

$
0
0

For every era, there’s a monster that embodies the anxieties of the age.

At the dawn of the Holocene, our ancestors traced the contours of shadows cast by the campfire as they kept watch over the darkness. Once we learned to read the night sky for navigation, sailors swapped stories of sea creatures like Leviathan and Siren to describe the dangers of open ocean (and the perils to be found on unfamiliar shores).

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

More recently, the “monster ruins a beach party” trope of the 1960s arose from concerns of teenager morality. While 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).

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

Advertising Identifiers (IDFA)

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.

importAdSupportletidfa=ASIdentifierManager.shared().advertisingIdentifier

If advertising tracking is limited, the property returns a zeroed-out UUID instead.

idfa.uuidString=="00000000-0000-0000-0000-000000000000"// true if the user has limited ad tracking

DeviceCheck

identifierForVendor and advertisingIdentifier provide all the same functionality as the uniqueIdentifier property they replaced in iOS 6, save for one: the ability to persist across device resets and app uninstalls.

In iOS 11, Apple quietly introduced 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:

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

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

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

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

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

Fingerprinting in Today’s iOS

Despite these affordances by Apple, advertisers have continued to work to circumvent user privacy protections and use any and all information at their disposal to identify users by other means.

Over the years, Apple’s restricted access to information about device hardware, installed apps, nearby WiFi networks. They’ve required apps to request permission to get your current location, access your camera and microphone, flip through your contacts, and find and connect to Bluetooth accessories. They’ve taken bold steps to prevent user tracking in Safari.

For lack of this information, companies have had to get creative, looking to forge unique identities from the scraps of what’s still available. This process of identification by a combination of external factors is known as fingerprinting.

The unfortunate reality is that we can be uniquely identified by vanishingly small amounts of information. For example, individuals within a population can be singled out by as few as four timestamped coordinates (de Montjoye, Hidalgo, Verleysen, & Blondel, 2013) or little more than a birthday and a ZIP code (Sweeney, 2000).

Every WWDC since 2012 has featured a session about Privacy, but the only mention of fingerprinting specifically 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.

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

Accessibility (~10 bits)

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

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

Of the approximately ~25% of users who take advantage of Dynamic Type by configuring a preferred font size, that selection may also be used to fingerprint you:

letapplication=UIApplication.sharedapplication.preferredContentSizeCategory

Hardware Information (~5 or ~6 bits)

Although most of the juiciest bits have been locked down in OS updates over the years, there’s just enough to contribute a few more bits for purposes of identification.

On iOS, you can get the current model and amount of storage of a user’s device:

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

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

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

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

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

Cellular Network (~2 bits)

Knowing whether someone’s phone is on Verizon or Vodaphone can also be factored into a fingerprint. You can use the CTTelephonyNetworkInfo class from the CoreTelephony framework to lookup the providers for devices with cellular service:

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

The number of providers varies per country, but using the 4 major carriers in United States as a guideline, we can say carrier information would contribute about 2 bits (or more if you have multiple SIM cards installed).

Communication Preferences (2 bits)

More generally, even knowing whether someone can send texts or email at all can be factored into a fingerprint. This information can be gathered without permissions via the MessageUI framework.

importMessageUIMFMailComposeViewController.canSendMail()MFMessageComposeViewController.canSendText()

Additional Sources of Identifying Information

If the use of digital fingerprinting seems outlandish, that’s just scratching the surface of how companies and researchers have figured out how to circumvent your privacy.

GeoIP and Relative Network Speeds

Although access to geolocation through conventional APIs requires explicit authorization, third parties may be able to get a general sense of where you are in the world based on how you access the Internet.

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

ping -c 5 99.24.18.13 # San Francisco, USA
        
        --- 99.24.18.13 ping statistics ---
        5 packets transmitted, 5 packets received, 0.0% packet loss
        round-trip min/avg/max/stddev = 11.900/12.184/12.895/0.380 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)

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

And so on…

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

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


Much as we may bemoan the current duopoly of mobile operating systems, we might take some solace in the fact that at least one of the players actually cares about user privacy. Though it’s unclear whether that’s a fight that can ever be won.

At times, our fate of being tracked and advertised to may seem as inevitable as the victims in It Follows.

But let’s not forget that, as technologists, as people with a voice, we’re in a position to fight back.

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