stratigrafia

I bear messages which will make both your ears tingle.

Bram Stoker (Dracula)

DATA AND IOS

09/06/2014

In updating one of my apps (Coordinates) to using storyboards instead of .xib files, I took the opportunity to rethink how the app worked as a whole. Originally, the app was designed so that a user could enter a latitude and longitude in any format, and the app would convert those coordinates to the three most common formats: decimal degrees, degrees and decimal minutes, and degrees-minutes-seconds. Over time, I added features to the app,. Now it can also set the coordinates from a gps reading, show the location of those coordinates on a map, send a text message with those coordinates, and display a bearing and distance to a set of coordinates. The app grew to include an app controller, an initial view controller (which shows the converted coordinates), and three other view controllers that handled data entry, bearing and distance, and the map. The app also saves the coordinates and reloads them when you launch, so the user never loses their work.

I wanted to simplify how the data (the coordinates) was handled in the app, particularly to keep the coupling between the classes loose. It seemed obvious that the app controller would read and save the coordinates, but the question was how best to pass the coordinates among the app delegate and the four view controllers.

My firs step was to create a custom class to hold the coordinates. This class also returns the coordinates as strings formatted in the different coordinate styles, and it does things like calculate a great circle bearing and distance to a set of coordinates.

After this, three interrelated questions came to mind:

In my first approach, I made the data object in the app controller, and I accessed that data from all of the view controllers via the app delegate singleton. I also considered making the data object itself a singleton. Both approaches are flawed, though, for two reasons. First, they make the classes too dependent on each other; what I wanted was loose coupling of my classes. Second, it violated the principle of “Tell, don’t ask”. The idea of this principle is that every object should be initialized with the data it needs; it shouldn't have to go out and obtain the data itself.

The idea of setting up objects with the data they need is called dependency injection, and Apple uses it extensively in their classes. Dependency injection also makes testing of the classes much easier. Getting the data from the app controller to the initial view controller is straightforward. In the app delegate’s application:didFinishLaunchingWithOptions: method, I get the navigation controller from the window’s rootViewController, and I get the first view controller from the list of view controllers. Each of my view controllers has a property for the data object, and I set that property for the initial view controller.

UINavigationController *nav = (UINavigationController*) self.window.rootViewController;

initialVC = [[nav viewControllers] objectAtIndex:0];

initialVC.coordinates = coordinates;

For the second question, getting the data to the other view controllers works on the same principle. It is easy with storyboards by using the prepareForSegue:sender: method. If the segue to each view controller is given an identifier in Interface Builder, the proper data can be supplied to that view controller.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

if ([segue.identifier isEqualToString:@"Map"]) {

CoordinatesMapViewController *mapVC = (CoordinatesMapViewController *)segue.destinationViewController;

mapVC.currentCoordinates = currentCoordinates;

}... (and so on for each view controller)

The strength of this approach is that it solves the third question, of how to get the data back to the app controller so that it has the current coordinates when it is time to save, which I do when the app resigns its active status. The answer is ... nothing more needs to be done! Because the app controller uses its own coordinates property to initialize the coordinates property of the initial view controller, and because the initial view controller does the same when it initializes the other view controllers, all the view controllers and the app delegate are working with the exact same object. Because I call a method on the data class to update the data whenever a change is made, the app delegate and all of the view controllers always have the updated data. When the app resigns its active state, the app delegate already has the current data, and it saves it to a file.

The result of this is easy data sharing with a minimum of coupling. The app delegate needs to know about the initial view controller. The initial view controller needs to know about the other three view controllers. All of the view controllers and app delegate need to know about the data object. And that’s it - simple!

Home