Type-safe Table Views with Swift

I recently started writing an app in Swift, and one of the first things I needed to do was develop some infrastructure for implementing table view data sources. My goals were to decouple the data source implementation from the table view controller, reduce the boilerplate required to implement a typical data source, and to maximize type safety (since Swift is all about that type safety).

The main challenge was building something that would bridge to Objective-C so that it could be used with UITableView. Since code that uses Swift-specific features like generics doesn’t bridge to Objective-C, achieving my goal of maximizing type safety turned out to be a non-trivial task. Read on to learn about my solution to this problem.


Since table views use data that is organized in sections, the first thing to do is to implement a Section:

Section is implemented as a struct because it is a value type that contains no mutable state. Read Andy Matuschak’s article on structs and value types for more information on this topic.

Note that Section is a generic type, for the same reason why Array and Dictionary in Swift are generic types — to make them type safe by restricting their elements to a single type that is known at compile-time.

Next up is implementing the data source itself. From the methods defined in the UITableViewDataSource protocol, we understand that the table view data source has two primary responsibilites:

  1. Tell the table view how much data it will display, in terms of sections and rows
  2. Create and configure instances of UITableViewCell to display data

Since I wanted to allow for the possibility of also implementing a collection view data source at some point, I first defined a general data source for data organized in sections:

This struct isn’t very useful at the moment, but the idea is that in the future, I may want to implement additional functionality that is common to anything that uses sections.

Now, TableViewDataSource is implemented as follows:

  • Since the data source needs to know the reuse identifier of the cell to be able to create an instance of it or dequeue it from the reuse pool, the ReusableCell protocol is defined so that a cell class conforming to it can define its own reuse identifier.
  • TableViewDataSource is parametrized over T, the type of element that each section contains, and U, the type of a UITableViewCell subclass that conforms to ReusableCell.
  • CellConfigurator is the type of a closure that configures a cell of type U using a section item of type T.

The next step is to implement the UITableViewDataSource methods. Easy, right? Not exactly. TableViewDataSource is a struct and also a generic type, which means that it does not bridge to Objective-C. This means that any methods declared inside it — like implementations of UITableViewDataSource methods — also do not bridge to Objective-C. This is obviously a problem, since this needs to bridge in order to be able to use it as the dataSource of a UITableView.

The solution to this problem is to strip away the bits that make it incompatible. In other words, the object implementing UITableViewDataSource needs to be, well… an object, not a struct, and it can’t be a generic type. In order to do this without losing type safety, we need to implement a way to transform instances of Swift-only types like Section and TableViewDataSource into instances of types that can be bridged to Objective-C.

First up is Section:

  • SectionObjC is a class that is very similar to the generic Section type, except that it gets rid of the need for a type argument by using [AnyObject], which can safely be bridged to an NSArray. The @objc attribute is used tell the compiler that this class should be bridged to Objective-C.
  • The type parameter T on Section has the new constraint that it must conform to AnyObject, since Objective-C doesn’t support structs and enums.
  • The toObjC() method is defined on Section to transform it into an instance of SectionObjC.

TableViewDataSource needs a similar treatment:

The changes here are essentially the same as the changes made to Section. A new class is defined in order to facilitate bridging by removing type parameters (using AnyObject instead), and a toObjC() function is defined on TableViewDataSource to transform it into an instance of the Objective-C compatible TableViewDataSourceObjC.

You might ask, doesn’t removing the type parameters from the *ObjC classes also kill the type safety? In this case, the answer is no, because SectionObjC and TableViewDataSourceObjC are implementation details. TableViewDataSourceObjC is only exposed as “some object that conforms to UITableViewDataSource" so that it can be assigned as the dataSource of UITableView. In order to create an instance of TableViewDataSourceObjC, the API consumer must first create an instance of TableViewDataSource, which is completely type-safe.

The UITableViewDataSource methods can now be trivially implemented on TableViewDataSourceObjC:

And we’re done! Here’s what basic usage looks like:

This may not be a perfect solution, but it ended up satisfying all of my goals, so I’m pretty happy with it. This also isn’t intended to cover all of the possible use cases for a table view — think of it as a case study on how common tasks in iOS development can be made much simpler and less error-prone by reimagining them using all of the power that Swift and static typing give us.

The full Xcode project is available on GitHub.

WWDC 2014 Session 226: What’s New in Table and Collection Views

Adopting Dynamic Type

Self-sizing Table View Cells

  • Two options for self-sizing table cells:
    1. Autolayout constraints — add constraints to cell.contentView.
    2. Manual sizing — override -sizeThatFits:.
  • Set UITableView's estimatedRowHeight instead of rowHeight and set rowHeight to UITableViewAutomaticDimension on iOS 8 for self-sizing table cells.
    • In the current seed, table views unarchived from a NIB have their rowHeight property set to a constant height by default. This will change to UITableViewAutomaticDimension in a future seed.

Self-sizing Collection View Cells

Invalidation Contexts

WWDC 2014 Session 205: Creating Extensions for iOS and OS X, Part 1

  • Extensions have a separate code signature, entitlements, and container from their parent app.
  • They are not a form of app to app IPC and are accessed solely by Apple frameworks (not even Apple’s apps access them directly)

Notification Center Widgets

  • Widgets are view controllers — all of the same lifecycle and containment concepts apply.
    • Widget should be ready to display after -viewWillAppear:.
  • Notification Center sets the view frame.
    • Height can be set using Autolayout constraints or by setting UIViewController's preferredContentSize.
    • Content should animate alongside the height change.
      • On iOS, implement -viewWillTransitionToSize:withTransitionCoordinator: and call -animateAlongsideTransition:completion: from the UIViewControllerTransitionCoordinator protocol.
      • On OS X, implement -viewWillTransitionToSize:.
  • Implement -widgetPerformUpdateWithCompletionHandler: from the NCWidgetProviding protocol to be notified when to update the widget.
  • New “Today Extension” Xcode template on both iOS and OS X for adding a widget target.
  • Use UIViewController's extensionContext (see NSExtensionContext) to call back to the main app from the widget.

Share Extensions


Complete documentation can be found in the App Extension Programming Guide.

WWDC 2014 Session 221: Creating Custom iOS User Interfaces

Spring Animations

Vibrancy and Blur

  • Continue using -[UIView drawViewHierarchyInRect:afterScreenUpdates:] for static blurs.
  • UIVisualEffectView is a new API for creating live blurs and vibrancy effects.
    • Vibrancy is used to make content visible when the background is blurred.
    • Blurs can be tinted by setting the backgroundColor of the contentView
    • Changing the alpha will result in the blur being dropped.
    • Can not be placed in a view hierarchy that contains masks.
  • UIBlurEffect
    • Has 3 styles: dark, light, extra light
    • Not just a simple Gaussian blur. Three steps:
      1. Downsample
      2. Modify colors
      3. Compute the blur
    • Rendered offscreen.
  • UIVibrancyEffect
    • Used as a subview of or layered on top of a UIVisualEffectView that uses a UIBlurEffect to make content (e.g. text) more vivid and legible.
    • Also rendered offscreen, takes even more time than blur.

Shape Layers

  • CAShapeLayer is used to create animatable bezier paths.
    • Rasterizes shape layer on CPU and sends the rasterized layer to the render server
    • Expensive in CPU time, be cautious about frequent changes.

Dynamic Core Animation Behaviours

  • Actions can be used to implement dynamic animation behaviour based on the context.
    • -actionForLayer:forKey: is called on the CALayerDelegate when an animatable property is set.
      • Return an object that conforms to the CAAction protocol to run custom animations.

WWDC 2014 Session 713: What’s New in iOS Notifications

User Notifications

  • Local notifications now require user approval as well.
  • Apps must register notification settings (see UIUserNotificationSettings) by calling -[UIApplication registerUserNotificationSettings:].

Notification Actions

  • Two new actions when swiping to the left on the lock screen or notification center, or when swiping down on a notification banner.
  • Create actions using the UIMutableUserNotificationAction class.
  • Use UIMutableUserNotificationCategory to group actions for particular contexts (default and minimal).
    • Minimal is used in lock screen, notification center, and banners.
    • Default is used in notification alerts.

Remote Notifications

  • Need to include category identifier in push payload.
  • Previous size limit of 256 bytes for a payload has been increased to 2 KB.
  • Handle them using UIApplicationDelegate's -application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
  • Silent notifications require remote-notification in the UIBackgroundModes array in Info.plist.
  • Enabled by default, can be disabled in Settings, user is not prompted for approval until a notification is posted.
  • -[UIApplication registerForRemoteNotifications] is the new method for registering for remote notifications.

Local Notifications

  • Set category identifier using category property on UILocalNotification.
  • Handle them using UIApplicationDelegate's -application:handleActionWithIdentifier:forLocalNotification:completionHandler:

Location Notifications

  • Need to register with Core Location using -[CLLocationManager requestWhenInUseAuthorization], which requests permission for tracking user location when the app is running in the foreground.
    • Define NSLocationWhenInUseUsageDescription key in Info.plist with a string value to set the usage text that appears in the authorization alert.
  • Handle CLLocationManagerDelegate's -locationManager:didChangeAuthorizationStatus: callback to know when to schedule location based notifications.
  • UILocalNotification has new properties region and regionTriggersOnce for location based notifications, schedule them like any other local notification.

WWDC 2014 Session 413: Debugging with Xcode 6

Queue Debugging

  • Xcode 6 can show backtraces for enqueued blocks across GCD queues and threads in the Debug navigator.
  • In the Queues view, it also shows the pending blocks for a queue in addition to the blocks that are already executing.
    • Debug menu > Debug Workflow > Always Show Pending Blocks in Queue
  • Shortcut: hold down Option key and click the disclosure triangle to the left of the process info to expand all the thread backtraces.

View Debugging

  • View debugger button is on the Debug toolbar (bottom of the window) to the left of the “Simulate location” button.
  • Third view mode in the Debug navigator (in addition to thread and queue views) for showing the UI hierarchy.
    • You can filter views using the search field!
  • Attributes inspector shows view attributes but they are read-only.
  • Size inspector shows a list of all the layout constraints.

Integrating with Quick Look

  • Xcode 6 supports Quick Look for UIView and NSView.
  • Override -debugQuickLookObject in your own objects and return one of the supported types to add support for Quick Look.
  • More documentation here.

WWDC 2014 Session 206: Introducing the Modern WebKit API

Architecture

  • API is identical on OS X and iOS.
  • Uses the fast Nitro JS engine on iOS (unlike UIWebView), including fourth tier LLVM JIT.
  • WKWebView has its own process(es), separate from the main app process.
  • Each page gets its own process up to an unspecified limit, at which point pages start sharing processes.

WKWebView

  • WKWebViewConfiguration class encapsulates all configuration so that it can be shared among multiple instances of WKWebView.
  • Properties (title, URL, loading, estimatedProgress) are KVO-compliant.

Customizing Page Loading

Gestures

  • allowsBackForwardNavigationGestures property enables left/right swipe gestures for navigation.
  • Set allowsMagnification property on NSScrollView for double tap and pinch to zoom. UIScrollView does this automatically with no additional configuration.

Customizing Webpage Content

  • Use WKUserContentController (userContentController property on WKWebViewConfiguration) to inject user scripts into web pages and register handlers for script messages.
  • User scripts (see WKUserScript) are scripts written in JS and are generally used to modify the DOM.
    • Debugged using the Safari web inspector.
  • Script messages (see WKScriptMessage) are sent as JSON from the web page to your app and are automatically converted into Objective-C types.

The modern WebKit API, including WKWebView and all associated classes are part of the open source WebKit project. The source code for these classes can be found here.