iOS Development Reference

Kip Landergren

(Updated: )

My cheat sheet for iOS development covering common scenarios and helpful resources.

Contents

Common Scenarios

The following are not definitive or exclusive solutions.

Symbolic Breakpoints

Prerequisite understanding:

An example symbolic breakpoint that logs the class name on every call to viewWillAppear:

Symbol: -[UIViewController viewWillAppear:]
Action: Log Message
viewWillAppear         - @(char *)object_getClassName($arg1)@

results in:

viewWillAppear         - "UITabBarController"

Note: this pattern of breakpoint can be expanded to any method you wish to observe.

Custom Colors

Custom user interfaces should:

Tint color is the base color iOS will use to apply visual effects, like blur and vibrancy changes, to. By convention, tint color is typically used for interactive elements. The value is the first non-default color specified. “barTintColor” is the navigation bar’s background color (but not the backgrounColor property). I think mainly because the navigation bar contains interactive elements, and therefore has some degree of interactivity. Tint is also used in combination with different rendering modes to color a UIImage.

Navigation Bars have translucency

Blur

Vibrancy

Is there a difference between alpha and translucency? Yes. translucent can be thought of as “semi-opaque”. It allows light to pass through but not detailed shapes. Opacity refers to the value of the alpha channel. Full opacity, or opaque, at a value of 1.0 means that no light goes through. Zero opacity, at a value of 0.0, means fully transparent. What is difference between a UIView’s alpha and a UIView’s layer’s opacity?

masks

shadow

images

template images

How to Tell If a View Controller’s View is Visible?

Make use of viewWillAppear and viewWillDisappear to set a Bool.

How to Get the User’s Locale?

Locale.current

How to Log?

import os.log
os_log("hello world!")

Updating Table Views

The term reload, and the associated methods prefixed with reload, is for when it is significant that the user to know that data has changed. Otherwise just manipulate the cell directly.

Dealing with View Controller Editing State

Somehow, it appears that setting property isEditing triggers a call to setEditing(:), which in turn calls loadView(). This can result in unexpected behavior, especially with UITableViewController instances.

Generally, do not rely on external manipulation of isEditing, and only use setEditing(:) when you know the view is loaded and visible.

Single-Object View Controllers and Core Data

List View Controllers and Core Data

Understand Difference Between Testing Targets

Unit tests are run in a host application, UI tests have a target application.

How to Create a Unit Test Suite

Create new class that subclasses XCTestCase.

How to Create a Unit Test Suite

Create new class that subclasses XCTestCase.

How to Create a Test

Prefix function name with “test”.

How to Test a View Controller in Isolation?

The broad strokes of this approach are to create a new application target with separate @UIApplicationMain / UIApplicationDelegate that switches the root view controller based on launch arguments. Then, within the UITests target for this new application target, construct a UI test suite passing a launch argument to initialize the

Some problems:

Broad initial setup steps:

And then steps for each view controller:

Example code for the UIApplicationDelegate:

if ProcessInfo.processInfo.arguments.contains("AlphaViewController") {
  window?.rootViewController = AlphaViewController()
} else {
  window?.rootViewController = ViewController()
}

Example code for the UI test:

let app = XCUIApplication()
app.launchArguments.append("AlphaViewController")
app.launch()
XCTAssertTrue(app.staticTexts["Alpha"].exists)

Alternatives considered include:

How To Load a Single View Controller For Isolated Interaction?

The broad approach is to write a UI test that loads your view controller and then call wait(for:timeout:):

let app = XCUIApplication()
app.launchArguments.append("AlphaViewController")
app.launch()
app.wait(for: .unknown, timeout: 10000000)

How To Create an Archive

How To View Archives

How To Automatically Bump / Increment App Version or Build Number

Up front, Apple’s terminology is:

version number CFBundleShortVersionString; string using semantic versioning; user visible
build string CFBundleVersion; string using semantic versioning

The internet seems to fall into two camps:

agvtool will modify project.pbxproj, which may cause issues with distributed teams not using a conflict mitigation strategy.

Note: under the hood fastlane just uses agvtool.

If you configure a Run ScriptBuild Phase to run something like:

xcrun agvtool bump -all

you will have to consider the following:

Using agvtool With Apple Generic Versioning System

Note: this may not fit with the semantic versioning system you expect. An alternative is to set the Versioning System to None and manually set the version via:

xcrun agvtool new-version -all x.y.z

To use the Apple Generic system with agvtool:

And then use agvtool from the command line:

$ xcrun agvtool what-version

and:

$ xcrun agvtool what-marketing-version

How To Upload Archive

How To Add Compliance

When you upload an app, Apple checks to see if you have handled compliance. In Info.plist you can set key ITSAppUsesNonExemptEncryption appropriate to your situation, and ITSEncryptionExportComplianceCode if you have completed that process.

How To Configure Beta Testing

How To Register For All Notifications

// hack
let center = NotificationCenter.default
center.addObserver(forName: .NSManagedObjectContextDidSave, object: nil, queue: nil) { notification in
  print("\(notification.name): \(notification.userInfo ?? [:])")
}
center.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: nil, queue: nil) { notification in
  print("\(notification.name): \(notification.userInfo ?? [:])")
}

How To Deal with UITableViewCell Labels Being Compressed

If your .value1 type cell is losing its detailTextLabel, consider changing the type to .subtitle.

How to create custom section titles using NSFetchedResultsController

Sample code:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  guard let sectionInfo = _fetchedResultsController.sections?[section] else {
    return nil
  }
  return sectionInfo.name
}

Logging

Viewing streaming log message using log, adapted from this stackoverflow post. Note that a predicate must be used as the --subsystem flag does not apply:

xcrun simctl spawn booted log stream --level debug --predicate '(subsystem BEGINSWITH "com.example.app")' --source

creating a log:

import Foundation
import os.log
struct Log {
  private static var _subsystem = "land.kip.AppName"
  static let coreData = OSLog(subsystem: _subsystem, category: "core-data")
  static let pointsOfInterest = OSLog(subsystem: _subsystem, category: .pointsOfInterest)
  static let userInterface = OSLog(subsystem: _subsystem, category: "user-interface")
}

creating a default level log message:

os_log("default msg 1")

creating a debug level log message:

os_log(.debug, log: Log.userInterface, "debug msg 1")

creating an info level log message:

os_log(.info, log: Log.userInterface, "info msg 1")

creating a signpost:

os_signpost(.event, log: Log.pointsOfInterest, name: "View Controller Tapped")

creating a signpost with a message (format string and arguments are supported):

os_signpost(.event, log: Log.pointsOfInterest, name: "View Controller Tapped", "my message!")

important:

Resources

Documentation

Questions

FAQ

How to print out an NSString containing the class name via a symbolic breakpoint?

po [[NSString alloc] initWithFormat:@"viewDidAppear - %@", @((char *)object_getClassName($arg1))]