Use Your Loaf

[[brain engage] write]

Detecting Layout Problems With a Pseudolanguage

A quick tip if you are using Xcode 6 for previewing your interface layout with different localizations.

Double-Length Pseudolanguage

It is possible to preview a storyboard directly in Interface Builder using the assistant editor view. This is useful if you are working with auto layout to get a quick check that the interface responds as expected to different device sizes and orientations. The preview mode is not the automatic choice of the assistant editor so you may need to manually select it:

What has been missing up to now is a way to preview localizations. Xcode 6 adds the ability to preview not only the device and orientation but also the language localization. This is great if you already have a full set of localizations to preview but what if you are just starting work and only have the base localization?

Luckily the Xcode 6 team has introduced a pseudo language which does nothing but duplicate all user strings. You can find the Double-Length Pseudolanguage in the language menu in the bottom right-hand of the preview window:

This is not the same as checking every localization your project will use but it does give you a quick and dirty way of checking your layout constraints:

Creating a CocoaPod

I have been meaning to look at CocoaPods as a way of managing dependencies on third party libraries for some time. There is a good Introduction to CocoaPods over on NSHipster. You should read that together with Mattt Thompson’s article on Stewardship on Open Source communities. I will quote just one sentence:

It’s not enough to dump a pile of source code somewhere and declare it “open source”.

So partly because I want to be a better community member but also to gain some practical experience in creating a CocoaPod I decided to revisit my old iOS Keychain wrapper project. This post collects my notes on converting and releasing that project as a CocoaPod which I hope will be useful to others. If you are a CocoaPod guru and I missed a trick let me know.

Installation

CocoaPods is a Ruby gem which can be a cause for alarm if you are not familiar with Ruby. Luckily OS X ships with a version of Ruby pre-installed which will run CocoaPods fine. Installation is as simple as typing the command:

$ sudo gem install cocoapods

Note: I am using the Ruby Version Manager rvm to manage Ruby and gem dependencies which removes the need for the sudo:

$ gem install cocoapods

For further installation details check the CocoaPods Getting Started Guide.

Preparation

Before getting into creating a CocoaPod I will clean up the project a little and adopt some good practises.

Project Organisation

Life will be easier if the library files are all under a single directory structure. The latest versions of Xcode separate the Xcode files, project files and test files into separate directories. I have reorganised the project files into three main directories to create a similar setup:

  • PasswordManager - contains the class files for the library
  • PasswordManagerTests - contains the unit test files
  • ExampleApp - contains the example app code for the library

Tagging a Release

CocoaPods rely on semantic versioning which in simple terms means using a version number like this:

MAJOR.MINOR.PATCH
Increment MAJOR version for incompatible API changes.
Increment MINOR version for backwards-compatible changes.
Increment PATCH version for backwards-compatible bug fixes.

It is important that the version in the Podspec file (which I will explain shortly) also exists as a tag in the git repository. I have not been good at tagging the repository so will fix that by adding an initial tag for version 0.0.1 and pushing that to the repository:

$ git tag -a 0.0.1
$ git push —tags

Once I have everything working I will update the tag and the Podspec file to 1.0.0.

Adding a License

Before you can add a library to the CocoaPod repository it must have a valid license file. There are a lot of useful sites if you want help on choosing an open source library. My project already contains a BSD-style license file in the project root directory so I am good to go.

Documenting a Library

It seems obvious but if you want other people to use your library it needs to be well documented. This means you should have a README file in the project root and for an Objective-C library create appledoc style documentation.

My library already has a pretty good README which helped when adding the missing appledoc comments to the library header file. Once again NSHipster has a good guide to get you started on appledoc.

Tests

The final preparation step is to have a good test suite for the library. CocoaPods does not dictate which test framework you use. My library already has a set of OCUnit based tests that still work for iOS 6 so I will stick with them.

Creating the CocoaPod

Creating the Podspec File

The Podspec file describes the version of a library and includes basic metadata such as the name, version, description and the location of the source repository. The Podspec file lives in the root project directory. You can create it from scratch but the easiest way is to use the pod spec create command with the path to the repository:

$ pod spec create https://github.com/kharrison/UYLPasswordManager
Specification created at UYLPasswordManager.podspec

If you have not yet tagged the repository you will get a warning and the Podspec will use version 0.0.1. Either way you will get a well commented initial version of a spec pre-populated with some basic data from the repository. Don’t forget to remove the comments. The final version is below:

Pod::Spec.new do |s|
  s.name         = "UYLPasswordManager"
  s.version      = "1.0.0"
  s.summary      = "Simple iOS Keychain Access"
  s.description  = <<-DESC
    The UYLPasswordManager class provides a simple wrapper around Apple Keychain
    Services on iOS devices. The class is designed to make it quick and easy to
    create, read, update and delete keychain items. Keychain groups are also
    supported as is the ability to set the data migration and protection attributes
    of keychain items.
    DESC
  s.homepage         = "https://github.com/kharrison/UYLPasswordManager"
  s.license          = { :type => "BSD", :file => "LICENSE" }
  s.authors          = { "Keith Harrison" => "[email protected]" }
  s.social_media_url = 'https://twitter.com/kharrison'
  s.platform     = :ios, "6.0"
  s.source       = { :git => "https://github.com/kharrison/UYLPasswordManager.git", :tag => "1.0.0" }
  s.source_files  = "PasswordManager"
  s.requires_arc = true
end

Spec Metadata

The initial metadata section of the Podspec file is straightforward and mostly populated from the repository. I only needed to add the description text and change the homepage URL to point to the github project page.

  s.name         = "UYLPasswordManager"
  s.version      = "1.0.0"
  s.summary      = "Simple iOS Keychain Access"
  s.description  = <<-DESC
    The UYLPasswordManager class provides a simple wrapper around Apple Keychain
    Services on iOS devices. The class is designed to make it quick and easy to
    create, read, update and delete keychain items. Keychain groups are also
    supported as is the ability to set the data migration and protection attributes
    of keychain items.
    DESC
  s.homepage         = "https://github.com/kharrison/UYLPasswordManager"

Spec License

I am using a BSD license stored in a file named LICENSE in the project root so we can specify that in the Podspec file:

s.license      = { :type => "BSD", :file => "LICENSE" }

Author Metadata

The author or maintainer of the library. If you prefer you can omit the email address.

s.authors             = { "Keith Harrison" => "[email protected]" }

You can also add a social media URL. If you specify a Twitter account it will receive notifications from the @CocoaPodsFeed account.

s.social_media_url = 'https://twitter.com/kharrison'

Platform Specifics

The supported platform and deployment target. This is an iOS only library that supports iOS 6.0 and later:

s.platform     = :ios, "6.0"

Source Location

The location of the source repository. Typically the Git repository and release tag:

s.source       = { :git => "https://github.com/kharrison/UYLPasswordManager.git", :tag => “1.0.0” }

Source Code

With the library files cleanly contained in a single directory the source file location is easy:

  s.source_files  = "PasswordManager"

Build Settings

Not yet a default so we need to say if we expect ARC:

s.requires_arc = true

Checking the Podspec File

Before committing the Podspec file to the repository we can test from the project directory.

$ pod lib lint
 -> UYLPasswordManager (1.0.0)
UYLPasswordManager passed validation.

Assuming everything passes we can make the final commit of the changes to the repository, create the tag and push it to the repository. I first pushed a beta version (1.0.0-beta) to allow me to check the AppleDocs. When I was sure the CocoaPod was good I updated to 1.0.0:

$ git tag ‘1.0.0’
$ git push —tags

To test against the files in the repository:

$ pod spec lint UYLPasswordManager.podspec
 -> UYLPasswordManager (1.0.0)
Analyzed 1 podspec.
UYLPasswordManager.podspec passed validation.

Testing

Before releasing the CocoaPod to the world it is a good idea to test it in a real project.

Creating a Test Project

Create a new Xcode project to exercise the library and add the Podfile to the project root directory

platform :ios, '7.0'
pod 'UYLPasswordManager', :git => 'https://github.com/kharrison/UYLPasswordManager.git'

The git reference will retrieve the CocoaPod directly from the Git repository without referring to the central CocoaPods directory. To install the pod, from the project directory:

$ pod install

This will create an Xcode workspace which you can use to check the library installed without problems. Take a look at the Using CocoaPods guide for more details on adding pods to an Xcode project.

Publishing the CocoaPod

The CocoaPods Trunk Service is now the preferred way to publish a CocoaPod.

Register

Before you can publish a CocoaPod you need to register with the CocoaPods Trunk service. Make sure you use the email address that you use for committing and verify by clicking on the link in the email you receive.

$ pod trunk register [email protected] “Keith Harrison”

Publish to Trunk

The final step is to push the Podspec to the trunk service:

$ pod trunk push UYLPasswordManager.podspec

If you included a Twitter account in the Podspec you should see a notification within a few minutes announcing the new pod to the world.

Self Sizing Table View Cells

Dynamic Type Everywhere

I covered the basics of supporting dynamic type some time ago. Introduced in iOS 7, dynamic type allows the user to specify a preferred reading size for text. In the upcoming release of iOS 8 the Apple internal apps will adopt dynamic type and it seems they are making a big push to encourage all apps to do the same.

Supporting dynamic type is not without cost. You need to consider the impact when a user changes their text size preference. For example you may need to adjust the row height of a table view as the text size changes. I covered one approach to create Table View Cells With Varying Row Heights a while ago.

That example targeted iOS 7 and used auto layout for the cell content view. It needed a prototype cell to layout and size a cell from within the table view delegate method tableView:heightForRowAtIndexPath:. You can go back and read that earlier post or browse the example code if you want to learn more. In this post we will look at how to simplify that example code with some changes that Apple is introducing in iOS 8.

I am using iOS 8 beta 5 at the time I write this. There are still some rough edges which could change before the final release.

Self Sizing Cells

The WWDC 2014 Session 226 What’s New in Table and Collection Views covers a way in iOS 8 to create self-sizing cells in both collection and table views. This removes the need to use tricks such as a prototype cell to get cell heights. I will walk through a working example but if you want the one line summary:

Use estimatedRowHeight not rowHeight in iOS 8

Sample Code

I am going to use the original example code as the starting point so go back and read that post if you want the step by step instructions. As we will see the only code that will change is the table view controller and the storyboard. In brief, I am using the first fifteen chapters of Huckleberry Finn as a source of text with varying line length. A table view shows each line of text together with a label showing the line number:

Both text fields are using a dynamic type font style so the table needs to adjust as the text size changes.

Universal Storyboard

The original sample code was an iPhone only project with an iPhone only storyboard. In another move to make it easier to support different sizes of device iOS 8 introduces a universal storyboard. If you look carefully at the screenshot of the new storyboard below you will notice that the views are no longer iPhone or iPad size.

Instead of creating device specific layouts iOS 8 allows you to create a more generic default layout that uses auto layout to adapt to the actual device dimensions. To customise for specific device sizes iOS 8 defines two size classes: regular and compact. The default layout constraints can be overridden in the storyboard for any combination of the size class both horizontally and vertically. I will not go into detail here as the default layout is all we need and gives us a universal app without any extra code.

Setting the Estimated Row Height

Before iOS 8 if you had a table view that had cells with varying row heights you needed to use the table view delegate method tableView:heightForRowAtIndexPath: to return the height of each row. Loading the table view calls this method for every row in the table which for large tables can be slow. iOS 7 introduced tableView:estimatedHeightForRowAtIndexPath: to give an estimate of the row height which improved table load speed but you still need the height of each cell.

In iOS 8 we no longer need either of these methods as we can directly set the estimatedRowHeight in the table view controller and rely on auto layout to do the rest. In the WWDC session they use viewDidLoad for this:

- (void)viewDidLoad
{
   ...
   ...
   self.tableView.estimatedRowHeight = 100.0;
}

Since we no longer need to waste time with tableView:heightForRowAtIndexPath: we can also remove the dummy prototype cell from the table view controller.

Loading a Table View From a Storyboard

Unfortunately if you build and run at this point it does not work. The cell height is incorrect causing a truncated text label.

The root cause of the problem is that when the storyboard loads the table view it sets the rowHeight property to the value from interface builder. To make use of self-sizing cells we need to set estimatedRowHeight and leave rowHeight to what should be the default in iOS 8 - UITableViewAutomaticDimension.

The WWDC session mentions this problem and even that the next seed fixes it. Since that does not seem to be the case yet (in iOS 8 beta 5) we need to fix it in viewDidLoad:

- (void)viewDidLoad
{
   ...
   ...
   self.tableView.estimatedRowHeight = 100.0;
   self.tableView.rowHeight = UITableViewAutomaticDimension;
}

One More Problem

If you build and run now it almost works. Unfortunately the cells displayed on the initial screen are still incorrect. If you scroll the table view you will see that the height is fine for new cells as they appear on screen. I suspect the problem is that the initial cells load before we have a valid row height. The workaround is to force a table reload when the view appears:

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

Update 18 August 2014: I have logged rdar://17799811 with Apple to see if this is really a bug or something I am doing wrong. There are some further suggestions for workarounds in the comments. I will post an update if this changes in future iOS 8 releases.

Wrapping Up

That was a lot of words and not much code so I hope you made it this far. This is one of those cases where SDK improvements saves us code. The combination of universal storyboards, auto layout and self-sizing cells makes it easy to support different device dimensions with almost no code.

The new and improved auto-sizing table view code is in my GitHub repository. The name of the sample project is SelfSize.