I’m working on an early prototype of an app with Chris, and the first stage is testing out the location finding properties of the iPhone and learning to use CLLocationManager and MapKit.
The UI is pretty simple – a bunch of saved locations in a list view or map view (switched via a tab bar), a modal view to save a new location, and a detail view that shows a single saved location (accessed via a navigation stack from either the map or list view). So – a TabBarController, two NavigationController stacks, a TableView – easy right?
In my first design I thought that each navigation stack would needed a parent controller to “own” it – leading to a pretty complex structure, which (somehow) made sense in my head… here is a simplified diagram.
Well, you get the idea. It did work – mostly – all the functionality was there, the tabs would switch views, the CLLocationManager was finding the user location correctly, the annotations were being updated in the map view when I added an item in the list view, you could drill down to the right places in the application.
But my viewWillLoad/viewWillAppear methods on my navigation controllers weren’t firing… I had to call them manually for some reason. The list wouldn’t resize properly… it got stuck scrolling behind the tab bar. My detail view had to be manually positioned to take the tab bar height in to account. To update the model I was passing messages (via delegates) through what seemed like way too many middle-men. Something about it just felt clunky.
So I pulled the whole thing apart, and thought about it again, knowing that it should be easier than this. I re-architected in an attempt to a) use as much framework functionality as possible and b) simplify the messaging so I wasn’t using so many “middle man” controller classes. Instead of parent “container” controllers, I just let the UINavigationControllers act as the containers (which is what they are anyway), and added them to the TabBarController. I moved the table and map views from their child view controllers so that they mapped directly on to the main list and map view controllers: one view –> one controller. Here is the new structure:
My instance messaging overhead dropped considerably, I had fewer delegates to manage, and it was suddenly a lot easier to get a handle on what was going on. I ran the app and all the scrolling and viewWillAppear issues had completely disappeared.
That’s when I remembered one of the best pieces of design advice from Apple:
“Don’t fight the framework.”
UIKit is designed to make implementing the navigation structure and basic UI behaviour as simple as possible so that you can focus on the core functionality of your app.
The reason my list view wasn’t resizing correctly was because I was attempting to re-implement functionality that already existed in UINavigationController. The reason viewWillAppear wasn’t firing was because I’d overridden the navigationController in my UIViewController subclass and it couldn’t send the message properly.
It was a round-about way of doing things (I could have just used an example template after all), and it took me a long time, but I’m considering this an important lesson learned. Next time I try to do something that I’ve seen before (especially in Apple’s own apps) and I’m finding it strangely difficult, then I’ll just check the design again… because I’m probably doing it wrong.