So I thought i’d write a regular series of blog posts on where iOS development is at, every 6 months say. Kind of like how the americans have their ‘state of the union’ address every now and again. Plus, I can’t sleep tonight - the stress of contracting is getting to me again. So here goes the first one, as of early 2013. Please keep in mind that this is entirely subjective and my opinions so take it all with a grain of salt!
What follows is a rough description of how I create and lay out views in my view controllers these days, a method which I’ve seen a lot of senior devs use as well. It helps me handle the giraffe(5) vs stubby(4) iPhone sizes easily, and handle screen rotations on the iPad easily too. You’ll notice that xib’s aren’t featured here: I haven’t met many people who use or like them any more.
The first case is where your view controller only has one view: usually a UITableView or a UICollectionView. In this case, in your VC’s loadView, you do something like this:
- (void)loadView {
self.view = [[UISomethingView alloc] init];
}
Note that you’re not specifying the frame size of the view - since this view is now managed by the VC, it is the container VC’s responsibility to size it for you to fill the appropriate space. So you don’t need any layout code in the VC at all. Also, careful not to call [super loadView] here, or it’ll create a default empty view as the VC’s main view.
A common situation is to have a view controller with a table or collection view, a loading view, and maybe a search box. In this case, none of those three views can be assigned as the VC’s main view, they all have to be added as subviews. But because there are only a few of them, it’s not worth creating a custom UIView subclass to manage them. So here’s what I do:
- (void)viewDidLoad {
[super viewDidLoad];
_myTableView = [[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain]];
_someOtherView = [[UIView alloc] init];
.. make your loading / search box views here ..
[self.view addSubView:_myTableView]
}
Notice that i’m not specifying the view frames yet. Where possible, i just call the ‘init’ without a frame for brevity, and since UITableView needs a frame for its designated initialiser, i just pass CGRectZero. The reason for this is that these views are to be laid out below:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
int width = self.view.bounds.size.width;
int height = self.view.bounds.size.height;
_myTableView.frame = self.view.bounds; // Fill the whole area.
_myViewAtTheBottom.frame = CGRectMake(0, height-20, width, 20);
_myLoadingView.frame = .. something using the height and width ..
}
This way, you can lay out your frames using maths as complicated as you like. Just please don’t hardcode 320 or 480 anywhere, just make it all dependent on the size of the VC’s view. Also notice you don’t need to use autoresizing masks at all if you use this method. If you simply base everything off your width and height, your VC will automatically just work on a tall or short iPhone with no further code.
Say you’ve got a VC that has a dozen subviews, you’ll want to make the effort to make a custom UIView subclass to contain all those. In your VC, it’ll be a simple matter of creating one of these to be the VC’s main view:
- (void)loadView {
self.view = [[MyCustomView alloc] init];
}
You then won’t need any more layout code in your VC - which makes for really nice proper separation of concerns. In your custom view, you create all your views in your init with zero frames, and lay them out in your layoutSubviews:
- (id)init {
if (self = [super init]) {
_someSubview = [[UILabel alloc] init];
_anotherSubview = [[UIView alloc] init];
.. lots of subviews ..
[self addSubView:_someSubview];
[self addSubView:_anotherSubview];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
int width = self.bounds.size.width;
int height = self.bounds.size.height;
_myTableView.frame = self.bounds; // Fill the whole area.
_myViewAtTheBottom.frame = CGRectMake(0, height-20, width, 20);
_myLoadingView.frame = .. something using the height and width ..
}
Again, if you keep it simple like this, your VC will remain simple and clean and devoid of creation and layout of views - let your custom UIView subclass handle all that. And the only place where layout is handled is in the layoutSubviews.
As for passing information down from the VC to the labels etc in your custom view, you have two options: you can either expose the labels as properties and let the VC set the text property itself, or you can expose a setter in the custom view that takes a model object, and takes the fields from that model object and applies them to the custom labels. I’d go the latter route if there’s more than a few labels involved.
OCMock isn’t popular any more - it’s been stale for a while now, and has been missing lots of conveniences that other testing frameworks give us (such as the ability to specify a wildcard on value types in expectations). Which is a pity, I miss its bulletproof reliability - it never did anything weird, it just worked reliably.
Everyone seems to be using Kiwi for their testing now. The syntax is much nicer than OCMock, it handles all the important things for you (such as launching the simulator etc), it is much more expressive (eg you can set expectations on a non-mock object, which I think is just some kind of black magic), handles async testing reasonably well, and seems quite well rounded as a one-stop-shop testing toolbox.
That’s not to say Kiwi is perfect: the number of times i’ve had kiwi crash on me because a test failed, with no hint to be found in the stack trace of which failing test actually caused the crash, let’s just say it’s a large number. Also, i’ve often had async tests fail expectations later on during the running of another unrelated test, which makes it really difficult to figure out which test is actually failing. So it seems to be a little buggy. But when it works, it’s quite nice and slick - writing tests is much less of a chore. I think that there may be room for a more bulletproof testing framework in future, however.
As for UI/Integration testing (testing frameworks that simulate a user tapping their way through the app), KIF seems to be losing ground to Frank. However I’m of the opinion that UI testing as a whole is falling by the wayside: nobody really has the time to maintain fragile UI tests, and the benefit they give over a good set of Kiwi tests is debatable. I’ve yet to see a good set of UI tests - you may be better off sticking to Kiwi / unit tests, unless you’ve got the budget for a full-time test engineer to write these tests and keep them running.
Well, iOS5 is now old hat, and everyone seems to be targeting it now for their apps. Which is great - we can all now embrace ARC finally. Seriously, if you’re doing manual memory management still, you’re really missing out.
One key thing iOS5 gave us was over-the-air updates, which means that new versions get upgraded much more quickly than before. Which leads me to iOS6 - it hasn’t been out long, yet has a pretty large share of the market already, and you could justify targeting it for a lot of your iPhone apps these days.
iOS6 gives you much better view/layer memory management, which means we don’t need to worry about unloading views anymore - your VC just got a whole lot simpler! Unfortunately, until Google releases their maps for the iPad, i don’t think iPad developers will be able to target iOS6 - i suspect that a lot of users are holding back upgrading until they can get their beloved google maps on their ipad. I don’t blame them. But it’s a shame, because the best thing iOS6 gave iPad developers is the UICollectionView…
The collection view is the ipad’s version of the table view, in a nutshell. Expandable in every which way to take advantage of the extra screen real estate, so you can now make grid views, TV guides, horizontal or vertical scrolling tables with gaps between the cells, custom layouts, and all that good stuff with automatic cell recycling. Very handy - you need to learn it, although it does limit you to iOS6.
I’ve yet to meet anyone who has made serious money from the App Store as an indie developer making apps in their spare time. Most devs i know make a living from contracting or working full-time for a company. I think iOS6’s new app store layout has cemented that even further, now that you can only see one app at a time when browsing - it has brought more attention to the popular apps at the expense of the long tail. So don’t expect to make a living from apps any more, it’s become more of a ‘hits’ ecosystem, with no support for the long tail. I’d love to be proven wrong, of course, because i’ve got a dozen or more apps of my own in the app store, languishing in the ‘long tail’ where users no longer venture.
Thanks for reading! Now i’m off to bed…
Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.
(Comp Sci, Hons - UTS)
Software Developer (Freelancer / Contractor) in Australia.
I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!
Get in touch:
[email protected]
github.com/chrishulbert
linkedin