Getting into iOS development, I’m running across all kinds of quirks, workarounds, and best practices. This blog seems like as good a place as any to put them. Here’s a big first batch of them.

Reading .plist files

let path = Bundle.main.bundlePath + "/My.plist"
let pListData = NSDictionary(contentsOfFile:path)

Test Fixture Files

Fixture files are useful whenever you need complex data in a test that can be stored in a file of some kind. First, find the bundle identifier of your test target:

  • Select your project in the left-hand sidebar, then select the appropriate test target
  • Select the Build Settings tab, then scroll or search to Packaging > Product Bundle Identifier.

Then:

let testBundle = Bundle(identifier: "your.test.bundle.identifier")!
let path = testBundle.path(forResource: "myfile", ofType: "txt")!
let contentString = try String(contentsOfFile: path)

Generating Swift NSManagedObject Subclasses

If Xcode is generating NSManagedObject subclasses in Objective-C instead of Swift, select your Core Data Model file, choose the file inspector, and look for a Code Generation > Language setting. Mine keeps getting switched from Swift back to Objective-C, so you may need to check it each time you need to generate an NSManagedObject subclass.

Setting the model file language

Saving Arrays in Core Data

I wanted to have an array field on a Core Data model. I set it up as a Transformable in the Core Data Model file, generated the NSManagedObject subclass, then in the subclass I changed the field type to NSMutableArray. The problem I ran into was that the saving of the array was super unreliable. I found a good Stack Overflow question about this, and the answer that worked for me was actually a comment on another answer: changing the field from a NSMutableArray to an NSArray. When I need to make changes to it, I make a new mutable array with the current array’s contents, change it, then make a new immutable array and set that on the Core Data model.

Detecting Popovers

If you have a “Present as Popover” segue, it will present the view controller as a popover on iPad, but as a modal on iPhone. When I do this, on iPhone I tend to have a UIBarButtonItem to dismiss the modal, but on iPad I want to hide it. To detect whether a view controller is presented in a popover, you can check to see whether it has popover arrows:

// returns true if a modal
return parent!.popoverPresentationController?.arrowDirection == .unknown

Note that this may not work in viewDidLoad; it’s better to call it from viewWillAppear.

Executing Code After Table Animation Completes

I had some code that I wanted to run after a table animation completed. This is actually pretty tricky to do, but CATransaction ended up doing the trick:

CATransaction.begin()
CATransaction.setCompletionBlock {
    // what to do upon completion
}
    
self.tableView.beginUpdates()
// perform table updates
self.tableView.endUpdates()
    
CATransaction.commit()