Adoption Curve Dot Net

Testing NSUserDefaults

| Comments

NSUserDefaults is where you can store persistent app settings, as an alternative to storing them in some kind of in-app database. You shouldn’t try to store actual application data in NSUserDefaults, but they are useful for capturing “small” settings like environment URLs and so on.

If you’re using NSUserDefaults, you should be testing NSUserDefaults. Here’s how.

One of the most common testing scenarios is that you want to store some value in the defaults store so that it can be read out later. This is the process for testing that your code actually does so. Assume that we’ve got a class called myClass which has an IBAction method called didTapUpdateSettingsButton which will write out a specific value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it(@"should write the configured environment to the user defaults"), ^{

  NSString *apiUrl = @"http://foo.com/bar";
  NSString *theKey = @"defaultApiUrl";

  id mockStandardDefaults = [NSUserDefaults nullMock];
  
  [NSUserDefaults stub:@selector(standardUserDefaults) andReturn:mockStandardDefaults];
  
  [[mockStandardDefaults should] receive:@selector(setObject:forKey:) withArguments:theObject, theKey];
  
  [[mockStandardDefaults should] receive:@selector(sychronize)];
  
  [myClass didTapUpdateSettingsButton:nil];

}

Walking through this, we first create variables for the key and object that we are expecting the didTapUpdateSettings method to attempt to write to the defaults store.

Next, we create a mock object which will stand in for the instance of the NSUserDefaults class that would normally be returned by the [NSUserDefaults standardUserDefaults] method. We’re going to use this mock to catch and test the messages that our code under test sends. It’s created as a null mock here so that it won’t complain if it gets messages other than the ones that we send it.

Then we need to stub the standardUserDefaults method of the real, live NSUserDefaults class, and return our mock instance instead of the real thing.

Once we’ve stubbed out NSUserDefaults, we can now set some expectations about what our mock standardUserDefaults will receive. There are two things we’re interested in – firstly, that it receives the setObject:forKey message with the values that we expect. If that message isn’t received, or the values differ from what we expect, then there’s something wrong with the code that we’re writing.

Secondly, we’re also setting an expectation that the mock receives the synchronize message. This saves the values that we’ve updated – not sending a synchronize message to NSUserDefaults is a common mistake, and hard to diagnost – with a specific test, we’ll catch any situations where we forget that final step.

Finally, we invoke the code under test by “tapping” the button (or in this case, calling the IBAction method that the button is wired up to).

If you’re taking a test-first approach to writing the code, obviously this will initially fail. But by fixing each failing assertion in turn, you’ll end up with a method that both writes and saves the correct values.

Where Has the iOS8 Simulator Gone?!?

| Comments

With Xcode 6 and the new iOS8 Simulators, Apple thought it would be a good idea to move the location of the Simulator files. That’s not usually a problem, because there’s only a few specific circumstances when you need to access the files underlying the Simulator’s functions.

If you do need to access those files, the chances are that you can’t find them anymore.

Instead of looking in

~/Library/Application Support/iPhone Simulator/

you’ll find them instead in

~/Library/Developer/CoreSimulator/Devices/*

Which at least is easier to embed into scripts thanks to the lack of spaces in the path.

Working With Size Classes in Interface Builder

| Comments

Android used to be notorious amongst iOS developers for its practically infinite permutations of interface size. Viewed from the iOS world, this used to look like a problem, because iOS didn’t really provide much in the way of support for building interfaces of different sizes.

If you were building a universal app that supported both iPhone and iPad, there was a tendency to end up with a lot of if deviceType == kIpad-style code.

AutoLayout was the first part of fixing that problem, and the job’s been completed with iOS 8 and size classes. This is probably the least-sexy feature introduced in 8, but it’s definitely one of the more important.

Some quick background on size classes

There are currently two size classes – horizontal and vertical, and each one comes in two sizes – regular and compact. The current orientation of the device can be described as a combination of the sizes:

  • Horizontal regular, vertical regular: iPad in either orientation
  • Horizontal compact, vertical regular: iPhone portrait
  • Horizontal regular, vertical compact: no current device
  • Horizontal compact, vertical compact: iPhone landscape

Storyboards and nib files now support these size classes – so you can think of them as having up to four different layouts contained within the same file. At the bottom of the Interface Builder window, there’s now a control that allows you to switch between each combination:

Every control or AutoLayout constraint can exist in one, several or all of the size classes. This means that you can build interfaces that change depending on device type and/or orientation without any code. Controls can appear or disappear, change size, or change arrangements – all based on the layout that you create in Interface Builder.

The Many Forms of Swift Functions

| Comments

There are a somewhat bewildering variety of forms that a Swift function can take, depending on the permutations of parameters and return values that you want to use. Here’s a cheat summary:

Here’s a downloadable PDF version.

A Minimum Viable tableView in Swift

| Comments

This GitHub repo is a minimum viable implementation of a UITableView in Swift. Here’s a swift (badum, tish) tutorial on creating a UITableView using the new language.

This code has been updated for Swift 1.0 and Xcode 6.0.1

The project consists consists of a single storyboard with a table view control, and a view controller written in Swift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let cellIdentifier = "cellIdentifier"
    var tableData = [String]()
    
    @IBOutlet var tableView: UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Register the UITableViewCell class with the tableView
        
        
        self.tableView?.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.cellIdentifier)
        
        // Setup table data
        for index in 0...100 {
            self.tableData.append("Item \(index)")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // UITableViewDataSource methods
    
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier) as UITableViewCell
        
        cell.textLabel?.text = self.tableData[indexPath.row]
        
        return cell
    }
    
    // UITableViewDelegate methods
    
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {

        let alert = UIAlertController(title: "Item selected", message: "You selected item \(indexPath.row)", preferredStyle: UIAlertControllerStyle.Alert)
    
        alert.addAction(UIAlertAction(title: "OK",
                                      style: UIAlertActionStyle.Default,
                                    handler: {
                                        (alert: UIAlertAction!) in println("An alert of type \(alert.style.hashValue) was tapped!")
                                    }))
        
        self.presentViewController(alert, animated: true, completion: nil)
        
    }

}