-
Why I Still Don't Like The Interface Builder
Even with the improvements of the Interface Builder in the last years I still prefer doing iOS interfaces in code. Here is why:
Context shift
I'm a coder. What I do best is coding. When ever I switch from code to Interface Builder I have to switch context. Interface Builder is visual. There are many places where I have to change something to get the desired look. The mouse pointer has to move long distances on screen.Less conflicts
I still encounter merge conflicts for storyboards when working with others on the same project.Options all over the place
The elements in iOS become more and more complex. This means there are many options for interface elements. In Interface Builder you have to click through all the inspectors to find what you are searching for. When writing code most of the times I already know the first characters for what I'm trying to do and autocompletion does the rest.Easier get help
It's easier to post code than screenshots in case you need help on Stackoverflow ;). If you find code on github you can see without downloading how the interface is build.No betas for you
I had a few times that a project could not be opened by Xcode when I opened it in a beta of Xcode because the Interface Builder file structure had been changed.No refactoring
Code is easier to refactor than storyboards.No inheritance
It is impossible to inherit from a storyboard scene. In code you can easily make subclasses to reuse code. Using a storyboard you have to make two scenes even if they share 90% of their UI elements.Harder to DRY
At work I'm working on an app that has 8 targets right now (more to come). Each target has several storyboards. Differences in the interface for the different targets are managed in the different storyboards. It turned out that this isn't a good idea. We had to fix one layout bug in 16 storyboard scenes (8 universal targets). And this happens again and again. We are right now moving most of the more complex user interfaces to code to prevent repeating ourselves.Layout constraints
For me it's easier to see all the constrains of a view in code. In Interface Builder I have to open the size inspector to see the constraints. And then I see the constraints of one view. And each row is one single constraint. In code when using visual format language there are several constraints in one string. For example the visual format language sting "|-[nameLabel]-[button(30)]-|" represents 4 constraints on three interface elements. In combination with layout format options the layout becomes much easier to understand. At least for me.Performance Performance Performance
I use a MacBook Air (Early 2015) for development. The performance of Interface Builder on that machine is far from acceptable. Here are two examples (using Xcode 6.3.2) from a real project I'm working on right now:One could argue that a MBA isn't the right machine for a developer. But in my opinion a slow machine should slow down compilation and not editing code or user interface.
I would love to hear/read what you think.
Read more...
-
Another Approach To Stubbing NSURLSession With Dependency Injection
Recently I watched a video by Jon Reid. Jon Reid explains how he uses unit tests to test networking code. This video was an eye opener for me. If you haven't watched it yet, do it now.
Here is how his approach looks like in Swift using NSURLSession:
First we need a property to inject the mock url session in the test:
public class Foo { public var session: URLSession = NSURLSession.sharedSession() }Note the type of session . It is of type URLSession. URLSession is a protocol which declares one method:
public protocol URLSession { func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask }To make the compiler happy with the above definition we need to tell it that NSURLSession implements the URLSession protocol:
extension NSURLSession: URLSession { }The networking code in this example is quite easy. It just fetches a user profile from App.net:
let urlString = "https://api.app.net/users/@(username)" let task = session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: { (data, response, error) -> Void in let rawDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as! [String:AnyObject] self.user = self.userFromDictionay(rawDictionary) }) task.resume()The method userFromDictionay(_:) extracts and unwraps the elements which are needed to create a user and returns a user or nil if not all elements could be extracted:
func userFromDictionay(dictionary: [String:AnyObject]) -> User? { if let rawUser = dictionary["data"] as? [String:AnyObject], username = rawUser["username"] as? String, name = rawUser["name"] as? String, counts = rawUser["counts"] as? [String:Int], followers = counts["followers"], following = counts["following"], posts = counts["posts"] { return User(username: username, name: name, numberOfPosts: posts, followers: followers, following: following) } return nil }To store the user, we also need a user property:
public var user: User?
The user looks like this:
import Foundation public struct User { public let username: String public let name: String public let numberOfPosts: Int public let followers: Int public let following: Int public init(username: String, name: String, numberOfPosts: Int, followers: Int, following: Int) { self.username = username self.name = name self.numberOfPosts = numberOfPosts self.followers = followers self.following = following } }The initializer is needed because we need to initialize a user in the tests and it seems that the generated initializer is declared as
internal.That's all for the networking code.
Let's switch to the test code. The mock url session looks like this:
class MockURLSession: URLSession { typealias Handler = (NSData!, NSURLResponse!, NSError!) -> Void var completionHandler: Handler? var url: NSURL? func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask { self.url = url self.completionHandler = completionHandler return NSURLSessionDataTask() } }Note that the mock url session stores the completion handler in a property.
The test looks like this:
func testFetchingOfUserSetUserProperty() { // Arrange let mockURLSession = MockURLSession() secondViewController.session = mockURLSession // Act secondViewController.fetchUserWithUsername("dasdom") let userDictionary = ["data": ["username": "dasdom", "name": "Dominik", "counts": ["followers": 11, "following": 22, "posts": 33]]] as NSDictionary let data = NSJSONSerialization.dataWithJSONObject(userDictionary, options: nil, error: nil) mockURLSession.completionHandler!(data!, nil, nil) // Assert let testUser = User(username: "dasdom", name: "Dominik", numberOfPosts: 33, followers: 11, following: 22) XCTAssertTrue(secondViewController.user! == testUser, "should be equal") }First we inject the mock url session. Then we fetch a user and call the caught completionHandler with a test JSON. Finally we assert that the fetched user is as we expected.
To make this work we also need to override the == operator:
public func ==(left: User, right: User) -> Bool { if left.username != right.username { return false } if left.name != right.name { return false } if left.numberOfPosts != right.numberOfPosts { return false } if left.followers != right.followers { return false } if left.following != right.following { return false } return true }Keep in mind that the response of a NSURLDataTask is delivered on a background thread. So you can't use this approach to check if a label got updated as a result of the response. But in this case you would test two things at the same time. You shouldn't do this anyway.
If you have any comments or questions about this, ping be on App.net or Twitter.
Read more...
-
Stubbing NSURLSession With Dependency Injection
I think I found a nice way to stub NSURLSession using dependency injection.
Let's say you have an app that should fetch the info of a user from App.net and create a user object/struct from that info. To be able to unit test that feature you want to stub the HTTP request in the test.
You could use Mockingjay or you could write your own NSURLProtocol. But with Swift 1.2 there is an even simpler approach.
In the test you can inject the mock class to be used instead of NSURLSession.
Here is the view controller with the method to fetch the user data:
import UIKit public class SwiftViewController: UIViewController { public var MySession = NSURLSession.self public var user: User? public func loadUser() { let url = NSURL(string: "https://api.app.net/users/@dasdom") let session = MySession.sharedSession() let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in let dataDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as! [String:AnyObject] let userDict = dataDict["data"] as! [String:AnyObject] self.user = User(username: userDict["username"] as! String, name: userDict["name"] as! String, id: userDict["id"] as! String) }) task.resume() } }This code is straight forward. The only piece that is noteworthy it the first property:
public var MySession = NSURLSession.self
MySession is a NSURLSession type.
Let's write a mock for that class. You will have to mock NSURLSession and NSURLSessionDataTask. Here is the smallest implementation that can stub dataTaskWithURL(_:,completionHandler:):
class MockSession: NSURLSession { var completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)? static var mockResponse: (data: NSData?, urlResponse: NSURLResponse?, error: NSError?) = (data: nil, urlResponse: nil, error: nil) override class func sharedSession() -> NSURLSession { return MockSession() } override func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask { self.completionHandler = completionHandler return MockTask(response: MockSession.mockResponse, completionHandler: completionHandler) } class MockTask: NSURLSessionDataTask { typealias Response = (data: NSData?, urlResponse: NSURLResponse?, error: NSError?) var mockResponse: Response let completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)? init(response: Response, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) { self.mockResponse = response self.completionHandler = completionHandler } override func resume() { completionHandler!(mockResponse.data, mockResponse.urlResponse, mockResponse.error) } } }The mock session class has a static property (which will be used as a class property here) to store the mock response and a property to keep a reference of the passed in completion handler when dataTaskWithURL... is called. It overrides the class method sharedSession() and dataTaskWithURL(_:,completionHandler).
The mock task class is defined within the mock session class. It has two properties, dataTaskWithURL(_:,completionHandler) and completionHandler and it overrides resume() to call the completion handler with the test response instead.
The test then looks like:
func testExample() { let jsonData = NSJSONSerialization.dataWithJSONObject(["meta": ["code": 200], "data": ["username": "dasdom", "name": "Dpminik Hauser", "id": "1472"]], options: nil, error: nil) let urlResponse = NSHTTPURLResponse(URL: NSURL(string: "https://api.app.net/posts/stream/global")!, statusCode: 200, HTTPVersion: nil, headerFields: nil) MockSession.mockResponse = (jsonData, urlResponse: urlResponse, error: nil) viewController.MySession = MockSession.self XCTAssertTrue(viewController.user == nil, "") viewController.loadUser() XCTAssertEqual(viewController.user!.username, "dasdom", "") }Note the dependency injection on the line:
viewController.MySession = MockSession.self
That's it. Light and easy to read. HTTP stubbing done within minutes.
I would love to hear your feedback to this stubbing method.
Read more...
-
Quick Tip: Adding Empty Spaces In The Dock
I use empty spaces in the dock to group applications in the dock. My dock looks like this:

To add an empty space, open the Terminal and type in
defaults write com.apple.dock persistent-apps -array-add '{"tile-type"="spacer-tile";}' killall DockYou can drag and drop an empty space like application icons. To remove it, drag it from the dock.
Read more...
-
Testing If A View Controller Got Pushed
I want to write a unit test that tests if a view controller is pushed onto the navigation stack. Here is the solution I came up with:
// func testViewControllerGotPushedStoryboardVersion() { class MockNavigationController: UINavigationController { var pushedViewController: UIViewController? private override func pushViewController(viewController: UIViewController, animated: Bool) { pushedViewController = viewController super.pushViewController(viewController, animated: animated) } } let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MasterViewController") as MasterViewController let mockNavigationController = MockNavigationController(rootViewController: viewController) let expectation = expectationWithDescription("should push") viewController.performSegueWithIdentifier("showDetail", sender: nil) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC*0)), dispatch_get_main_queue()) { () -> Void in XCTAssertTrue(mockNavigationController.pushedViewController is DetailViewController, "") expectation.fulfill() } waitForExpectationsWithTimeout(1, handler: nil) }What do you think? Is there a better way to test this?
Read more...
-
Quick Tipp: Installing An App On The Simulator From Command Line
Recently I'm doing a lot UIAutomation at my day job. If you haven't played with it, I highly recommend you have look.
To make the testing scriptable I needed a way to install an app on the simulator from the command line (I already have a script which runs my UIAutomation tests).
I searched the internet for a solution and the best match I found was a repository from phonegap. While this looks promising I was more searching for a simple solution using build in features of the Xcode command line tools.
Then I remembered that xcodebuilds installs the app on the simulator when one runs unit tests with it.
Here is how to prepare the app and how to run xcodebuild to install an app on the simulator:
Fist create a demo project. It doesn't matter what it does or how it looks. If you are brave you can use a project you are working on right now. Go to Product/Scheme/New Scheme, call the scheme Install and click OK. Go to Product/Scheme/Edit Scheme, select the Test phase and add your test target. Expand the test target until you see all the tests. Deselect all tests. This scheme is not meant to be testing something, it just should install the app on the simulator.
Navigate to the project in the Terminal and run this command:
xcodebuild test -scheme Install -destination 'platform=iOS Simulator,name=iPhone 4s'
xcodebuild should now install the App on the simulator. If you have more than one SDK installed you can select the OS to run with
0S=8.1within the destination string.Any comments? Ping me on Twitter or App.net.
Read more...
-
Phy Update Diary: TDD for the rescue
In the Phy Update Diary I'm writing about the process of updating the first App I have ever put into the App Store.
As I wrote in an earlier post I'm back using XCTest for the unit tests of Phy.
Phy has a calculator. The calculator in the current version in the App Store has quite annoying bugs. For example you can input numbers like
12.34.56.78. As I started the next version from scratch I have the opportunity to fix the known bugs right from the beginning.The calculator in Phy is different than most calculators. You put in the whole string you want to calculate. For example it can calculate a string like
1+2+sin(0.3)×2×(3.33-4). The current input position is shown by an underscore that can be moved to allow input at the beginning or the middle of the calculation string. For example if the input field shows1+2.3_-15and you touch the 4 you get1+2.34_-15.This means to make sure the user can't put in numbers like
12.34.56.78, the app has to check if the cursor is within a number that already has a decimal delimiter every time the user adds something to the calculation string. This is a great use case for unit tests. Before I started the implementation of the feature I wrote this test:func testDecimalDelimiterCanNotBeAddedIfThereIsAlreadyOneInTheNumberAtCursorPosition() { viewModel.addElement("1") viewModel.addElement("+") viewModel.addElement("1") viewModel.addElement("2") viewModel.addElement(".") viewModel.addElement("1") var (attributedResultString, result) = viewModel.addElement(".") XCTAssertEqual(attributedResultString.string, "1+12.1_") viewModel.attributedStringByMovingCursorLeft() (attributedResultString, result) = viewModel.addElement(".") XCTAssertEqual(attributedResultString.string, "1+12._1") viewModel.attributedStringByMovingCursorLeft() viewModel.attributedStringByMovingCursorLeft() viewModel.attributedStringByMovingCursorLeft() viewModel.attributedStringByMovingCursorLeft() (attributedResultString, result) = viewModel.addElement(".") XCTAssertEqual(attributedResultString.string, "1._+12.1") viewModel.attributedStringByMovingCursorLeft() viewModel.attributedStringByMovingCursorLeft() (attributedResultString, result) = viewModel.addElement("1") XCTAssertEqual(attributedResultString.string, "1_1.+12.1") (attributedResultString, result) = viewModel.addElement(".") XCTAssertEqual(attributedResultString.string, "1_1.+12.1") }(I know, one should not have several tests within a test method. But in this case they all test the same thing: Test if the addition of a decimal delimiter follows the rules. Maybe I should refactor the test into two tests.)
The calculator has a view model that is responsible for handling the calculation string. To add something to the calculation string the method addElement(element: String) is called. The method attributedStringByMovingCursorLeft() moves the cursor left and returns the current attributed calculation string. This is an attributed string because it's color coded to show the user matching parentheses.
The check whether a decimal delimiter can be added to the string has to be done in addElement(element: String):
public func addElement(element: String) -> (attributedCalcString: NSAttributedString, result: Double) { var localElement = element if localElement == DecimalDelimiterString { if !canAddDecimalDelimiterAtCursorPosition() { localElement = "" } } ... }Here is how canAddDecimalDelimiterAtCursorPosition() is implemented:
private func canAddDecimalDelimiterAtCursorPosition() -> Bool { let string = attributedString.string let range = string.rangeOfString(kCursorString, options: nil, range: nil)! var canAddDecimalDelimiter = true var isDigit = true var position = range.startIndex do { if position == string.startIndex { isDigit = false } else { position = position.predecessor() isDigit = string.hasDigitAtIndex(position) if !isDigit { canAddDecimalDelimiter = !string.hasDecimalDelimiterAtIndex(position) } } } while isDigit if canAddDecimalDelimiter { isDigit = true position = range.startIndex do { if position == string.endIndex { isDigit = false } else { position = position.successor() isDigit = string.hasDigitAtIndex(position) if !isDigit { canAddDecimalDelimiter = !string.hasDecimalDelimiterAtIndex(position) } } } while isDigit } return canAddDecimalDelimiter }This method propagates from the position of the cursor left and right as long as there are digits. If it finds a decimal delimiter it returns false.
Without unit tests the implementation would have taken a lot longer because I did not have to navigate to the calculator and put in the test string by tapping the numbers and callsigns on the calculator user interface.
What do you think about the test? And do you have a better (maybe swifter) implementation of canAddDecimalDelimiterAtCursorPosition()?
Read more...
-
Phy Update Diary: Installing Realm using Carthage
> In the Phy Update Diary I'm writing about the process of updating the first App I have ever put into the App Store.
As I wrote in [the previous post](http://swiftandpainless.com/2015/02/phy-update-diary-a-fresh-start/) I decided to start over with XCTests instead of behavior tests using a third party framework.
In the first attempt I installed Realm manually following the steps described on [realm.io](http://realm.io/docs/cocoa/0.90.5/). Starting with a fresh project gave me the opportunity to learn something new: using Carthage.
I'm not a big fan of CocoaPods. Mainly because I have no idea how it works. It somehow creates a workspace with all the pods and everything is kind of magic. And if it fails I'm done. I can't do anything about it because I do not understand it. Several times I had to follow blindly the instructions on the CocoaPods website. And sometimes the only thing that worked was to delete all Pods and start over. This resulted sometimes it merge problems in git.
I'm not saying that CocoaPods is bad. It's not. It's great! It changed the way iOS developer work! This is about me. I would have to learn how it works. I would have to learn Ruby and stuff. And still there would be the workspace I'm not really comfortable with.
A few month ago [Carthage](https://github.com/Carthage/Carthage) appeared in my filter bubble and it sounded more like something I would like to use. Carthage is different. It doesn't change the project at all. It doesn't create a workspace. Instead it just builds dynamic frameworks I have to put into my project myself. So all the time I have the control over my project.
So what do I have to do to get frameworks via Carthage into my project? The first step is to install Carthage. This is done by simply [downloading](https://github.com/Carthage/Carthage#installing-carthage) a `pkg` and opening it. Restart the terminal session and create a Cartfile. In the case of realm this looks like this:
github "realm/realm-cocoa"
Then just follow [the instructions](https://github.com/Carthage/Carthage#getting-started). At the time of writing there are only four steps to get the realm framework into my project.
As I wanted to use Realm from Swift I also had to add the file `RLMSupport.swift` to my project.
Done!
Read more...
-
Phy Update Diary: A fresh start
In the Phy Update Diary I'm writing about the process of updating the first App I have ever put into the App Store.
In the [first post](http://swiftandpainless.com/2015/01/phy-update-diary-setup/) I wrote about using Quick and Nimble for behavior tests. After a few hours with behavior tests and problems when I updated to Xcode 6.3 I decided to go back to XCTest (it's not the fault of Quick and/or Nimble; they have been updated a few hours after the Swift 1.2 release).
I could remove them from the project but it feels better to start over and add the classes I have already written to the new project. And this is exactly what I will do.
Read more...
-
Quick Tip: Open Device Log in Xcode 6
Sometimes there are bug which only occur on release builds. Or you experiencing an app from the App Store which crashes and you want to see if there is something in the device log. Here is how you get the there:
I Xcode 6 it's quite hidden. Connect you device to you Mac and open Xcode. Go to Window/Devices. In the left menu select you device and click the rectangle with the up error at the bottom of the window:
It opens the log window which looks in my case like this:

Can you figure out what I did during the log was taken? :)
Read more...
subscribe via RSS

