User Tools

Site Tools


ios-labs-s14:class-07

This is an old revision of the document!


DriveSense Intro

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:

  1. GPS and Map
  2. CoreData
  3. Animations
  4. iPhone and iPad versions
  5. Images
  6. Social Sharing (Facebook, Twitter)
  7. Singletons

and more.

This lecture will revolve around the map.


Setting up Project

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.

Create a New Project

  1. Create a new project. When naming your project, select “Universal” under “Devices”
  2. Delete stock controller, add a navigation controller, delete its root controller, add a new view controller, set it as the nav's root, and ensure the nav as the root view controller
  3. Create a new controller subclass called “MapRootViewController” and change the view controller in storyboard to this new subclass

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:

  1. Play
  2. Pause
  3. Trips
  4. Settings

Adding Images

  1. Click “Images.xcassets” in project navigator
  2. Add a new image using the plus sign on the bottom left of the center pane
  3. Doubleclick the image name (see screenshot), set the new name

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.

Making a Pretty Top Bar

  1. Turn off the navigation controller's top bar by selecting it in IB and changing “Top Bar” from “Inferred” to “None” in the Attributes Inspector on the right pane (fourth tab from the left)
  2. Drag a new UIView to the top of the MapRoot controller. Size it like the screenshot below
  3. Add a label and three buttons. Set the label's text to “DriveSense”
  4. Select each button and delete the text by using the right pane. Type the name of the appropriate image in the “Background Image” field, or click the arrow and select from the list of added images.
  5. Connect an IBAction and IBOutlet from the Play/Pause button to the header file of MapRoot using the assistant editor

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.

Toggle Button Image

  1. Create a private instance variable in MapRoot of type boolean. Name it “recording”
  2. Initialize it in viewDidLoad to false.
  3. Create an if/else statement in the button's IBAction
  4. Copy the following code into each block of the statement above, changing the image name as appropriate
[BUTTONNAME setBackgroundImage:[UIImage imageNamed:@"NAME"] forState:UIControlStateNormal];

Test the button changes its image when pressed.


Maps

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.

Adding a Map

  1. Drag an MKMapView onto MapRoot. Make it the size of the whole controller
  2. Click “Show Document Outline” button on the bottom left of InterfaceBuilder. Ensure the map is below the view you previously added by dragging it up above the entry marked “View” directly above it
  3. Connect the map as a private IBOutlet
  4. Right-click the map in IB, set the delegate to MapRootby dragging from the circle to the yellow square below the controller
  5. Select the map, ensure attributes inspector is selected in the right pane, click “Shows User Location” checkbox

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.

Having the Map Follow the User

  1. Create viewDidAppear method in MapRoot
  2. Add the following method call:
[MAPNAME setUserTrackingMode:MKUserTrackingModeFollow];

-Turn on location debugging by selecting the simulator, going Debug > Location > Freeway Drive on the top bar


Singleton

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.

Setting up CoreLocation

  1. Link project against CoreLocation framework, import framework headers into TripRecorder.h
  2. Create private instance variable of type CLLocationManager, initialize it in TripRecorder's init method
  3. After initializing, set the following properties of the object: delegate, desiredAccuracy, and distanceFilter. Set self as delegate, desiredAccuracy as “kCLLocationAccuracyBest”, and distanceFilter as 1

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.

Recording Location

  1. Add two public methods to TripRecorder (header and implementation files) called “startRecording” and “stopRecording”
  2. Call “startUpdatingLocation” and “stopUpdatingLocation” on your CLLocationManager instance variable in the appropriate method implementations
  3. Copy the following method into TripRecorder.m:
- (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.

Recording and Pausing

  1. Import TripRecorder into MapRoot
  2. Use following code to call methods on singleton:
[[TripRecorder recorder] METHODNAME];

Use the log console to check that the recorder is starting and stopping appropriately.


Animating UIViews

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.

Adding Slide-In Pane

  1. Add a new view to the bottom of the controller. Make it the same height as the top bar view
  2. Add a few dummy labels
  3. Connect the view and labels as private IBOutlets to MapRoot

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.

Animating

  1. Create instance variable of type CGRect
  2. In viewDidAppear, set the instance variable equal to YOURVIEW.frame
  3. Create two methods “animateIn” and “animateOut”
  4. In animateIn set the views frame equal to the instance variable
  5. In animateOut, copy the instance variable, set the y-coordinate to the height of the screen, and assign it to the view within the block
  6. Call animate out in viewDidAppear, call both methods in appropriate parts of the play buttons' IBAction

Extra Extra Credit

Bored? Finished early? Figure out how to update the label in the slide-in view to show a time counter. Youll need the following:

  1. NSTimer's scheduledTimerWithInterval method
  2. An update method that changes the contents of the label
  3. NSString building methods
  4. An instance variable counter

Don't forget to clear the timer between trip recordings.


Conclusion

Next section will deal with CoreData, the internal database system used by iOS, the heavyweight as far as data persistence goes.

ios-labs-s14/class-07.1393271046.txt.gz · Last modified: 2014/02/24 13:44 by mbarboi