Welcome to the last week of the iOS tutorials. Here we will be tackling some of the more intermediate and advanced topics in iOS development. If you're going to be writing iOS apps, you've already learned more than enough to get started. Everything we cover from here on out deals with specific frameworks, functionality, or features such as GPS, social sharing, and the local database– you can get by just fine without learning any of this material, but learning it now will help you greatly if you are later confronted with it. Obviously the repetition inherent in iOS development will continue to teach you the basics, too.
DriveSense is an app produced by the CS department. Its purpose is to track and grade drivers' performance through a grading system that considers GPS, accelerator, and compass data. Abrupt turns, braking, or speeding will decrease the driver's score, while consistent good driving will return a high score. Trips are logged automatically when power is turned in and the app detects acceleration only possible in a vehicle or manually. Users can view all trips and scores on the device or online through a web interface.
For the purpose of this tutorial a lot of the repetitive content has been omitted in favor of new topics. These three tutorials will cover the following topics:
and more.
This lecture will revolve around the map.
Same old, same old, with the exception of supporting iPads as well as iPhones this time around.
Note that supporting both tablets and phones creates two .storyboard files. iOS knows which to load at runtime based on the active device. We will begin by working on just the iPhone version.
Hopefully you steamed through those steps.
Lets try something new. You're can almost recreate any of the iOS interfaces you've ever seen: they are all just increasingly complex organizations of all the basic UIViews. The last two pieces of the puzzles are images and animations. Even the most intricate interfaces are just layered images.
With Xcode 5 and iOS 7, the system for managing images has been simplified greatly. The complexity here arises from a simple issue: how do we handle images for different sized screens with different resolutions? Apple's answer is the .xcassets folder, which allows you to drag and drop in images for each size needed. To save time we will only use one resolution of image.
Repeat the following steps 4 times, one for each image. Download the images here. Name the images:
These images will go in a nested view on top of the MapRoot controller. Remember that UINavigationController automatically adds a NavBar to the top of each view controller maintained in its heirarchy. This bar displays the title of the controller and the back button, if applicable. We're going to roll our own.
Note that the screenshot has the map set up already, we'll cover that in the next section. Do not add it now, it will crash your app.
Why do we need an IBAction and IBOutlet? The IBAction alerts the controller of a button press, but we want to dynamically toggle the image set as the button's background from Play to Pause when the button is pressed.
[BUTTONNAME setBackgroundImage:[UIImage imageNamed:@"NAME"] forState:UIControlStateNormal];
Test the button changes its image when pressed.
All maps shown in controllers are subclasses of MKMapView, which leans on the MKMapKit framework. In this section we'll add a map to the controller, import the framework, and customize its functionality a little.
You have to be a little careful when adding the map. If you just drag it onto the screen it will cover up the view you just made. To correct this, you'll change the ordering of the views in the view hierarchy, placing the map behind the top bar.
Test your app, and gasp at imminent crash. Though not immediately obvious, you have to link against the Map framework to use any map functionality. See frameworks, follow instructions on adding a MapKit.
NOTE: saw some gimmicky maps when testing. If your map doesn't load, change the simulator type to iPhone 4-inch 64 bit and try again. This is accomplished by clicking the simulator type next to the Run and Stop buttons.
Test! You should see the map load as well as a blue icon that represents the user's current location. If you don't see the user's location, proceed to the next step.
Note that the simulator obviously cannot rely on actual GPS signals to determine its position. Instead, all of the data is faked and fed into the machine; you can edit the type of signals being faked through the Debug menu of the simulator.
[MAPNAME setUserTrackingMode:MKUserTrackingModeFollow]; -Turn on location debugging by selecting the simulator, going Debug > Location > Freeway Drive on the top bar
Although we have a map, everything is not quite finished on the GPS-tracking front. Eventually the user will be able to inspect all available trips, see settings, etc, all which will rely on additional controllers. Since we want to be able to keep recording GPS data even when the MapRoot controller has left the screen, something else must do the actual recording.
The simplest way to do this is to make a “recorder” object. Its job will be to run in the background constantly, recording data when needed, regardless of the currently active view controller. We could declare and instantiate this object when the app starts and pass it along from controller to controller, but this is somewhat unwieldy since the controllers don't really need to have access to the object.
A possible solution to this issue is known as a Singleton. Read up on Singletons here, and follow instructions to create a new singleton called TripRecorder. You'll have to make a new object first, ensure it subclasses NSObject.
TripRecorder will have two public methods, startRecording and stopRecording. It will rely on the other location framework to stay updated as to the user's location, CoreLocation.
Location updates come in through a delegate method called locationManager:didUpdateLocation. This method is called whenever an update comes through, but you must alert the locationManager you want to be alerted of updates first.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { CLLocation *location = [locations objectAtIndex:0]; }
Write an NSLog statement that prints the latitude and longitude of the device in the method. Access latitude and longitude on the property called “coordinate” of the CLLocation object (location.coordinate….)
The last step here is to turn on and off the recorder from the MapRoot controller. You'll call the singleton accessor method first, then call “stopRecording” and “startRecording” on the resulting object.
[[TripRecorder recorder] METHODNAME];
Use the log console to check that the recorder is starting and stopping appropriately.
Consider this section extra credit. Although there will certainly be content here that is on the exam, do not rush to complete this section if you didn't get to it in the first tutorial class.
Here we will look at the last part needed to make dynamic, fluid interfaces. The actual code is easy, the tough part is conceptualizing what needs to be done as far as coordinates, frames, and views.
A few methods exist for animating views, the simplest of which is a class method of UIView called “animateWithDuration:animations:”. It takes a block and a length of time and executes any of the changes in view frames within the block over the time passed in.
[UIView animateWithDuration:0.25 animations:^{ //change frames here }];
You have to do a bit of preparation first to make your life easier. When the controller loads, the view will begin off-screen. When the user hits the record button, the view will animate in, showing information about the current recording session such as duration. When paused, the view will retreat offscreen.
Since the view begins in its final resting position but must immediately leave the screen on controller launches, we need to save the starting frame to animate it onscreen. To animate it offscreen, you'll set the y-origin to the height of the screen.
Bored? Finished early? Figure out how to update the label in the slide-in view to show a time counter. Youll need the following:
Don't forget to clear the timer between trip recordings.
Next section will deal with CoreData, the internal database system used by iOS, the heavyweight as far as data persistence goes.