This is the final part of the GGT tutorial.
In this section you will finally graph some grants using another imported class. All of the graphing is done Google's CorePlot graphing library. After importing the app and setting up the third-party frameworks, we'll cover rotation triggers, programmatic views, and programmatic view controllers.
You may not finish this tutorial in class, but some of the content here will be on the midterm, so I encourage you to finish it in your own time.
The code that actually does the graphing is built around CorePlot, Google's graphing library. Thankfully, you won't have to write any of the code that actually makes the magic happen. Download the files here.
Along with the actual controller, you'll need to add the static library files and headers. The first is a precompiled file that contains the CorePlot implementation, the second is the set of header files that allows us to access the library within our code.
Theres a bit of a glitch here, however. In order to load the library, you'll have to set a linking flag within Xcode's project files. You should only have to do this for projects rarely, and we won't discuss the project files in detail in this tutorial.
So what do they flags you just set do? They force the linker to load all statically referenced libraries before compile time so the compiler has access to the symbols stored within. Don't spend too much time thinking on it.
Our new graphing controller looks a lot better in landscape view. Although we could lay it out in InterfaceBuilder as we've done with all the previous controllers, its faster to create it programatically.
Read up on UINavigationControllers in the Building Blocks if you've forgotten their function. To this point we've only really interacted with the controller from IB and let it act behind the scenes, save for one example in the previous week.
All view controllers organized into a nav stack (refers to the navigation controller's active stack) have access to the navigation controller in memory through self.navigationController. This allows them to push or pop view controllers in code without having to establish a segue in IB. The two methods used are:
pushViewController:animated popViewControllerAnimated:
where …animated: takes a boolean (YES or NO) to toggle a transitional animation. You will use both of these to present and remove the landscape controller.
The iOS device can be in one of four orientations: landscape left and right, portrait up and down. Thankfully, all view controllers are notified of changes in orientation through an optional delegate method called willRotateToInterfaceOrientation. By implementing this method, your controllers can react to changes appropriately.
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { //double check the destination orientation if(UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { } }
Note that UIInterfaceOrientationIsLandscape is orange when pasted into Xcode. The only other orange text you've seen so far is #pragma mark. These are both preprocessor directives. They are not Objective-C and are not compiled as code, instead they are replaced before the compiler runs. In the case of UIInterfaceOrientationIsLandscape, the ability to check a passed orientation comes from a clever macro. Note that toInterfaceOrientation is passed into the delegate method, and is how we check what the resulting orientation will be.
Now that we can recognize an orientation transition, its time to push the new view controller. Within the conditional above, you must instantiate a new Landscape controller, call init on it with the grants array, and finally call pushViewController on self.navigationController, passing the Landscape controller.
Test the app and ensure that the Landscape controller appears when the device is rotated (tip: command + arrowKeys rotate the simulator). You'll undoubtedly be quick to note that although Landscape appears, Root never reappears on rotation shifts. Implement the rotation callback in Landscape, popping self, to return to Root.
Since all transitions are orientation-based we don't really need that top white bar on Landscape, it in fact occludes the graph. This is called a UINavigationBar and is autogenerated for a controller that belongs to a nav stack. Lets get rid of it.
This section could alternatively be titled “why we like InterfaceBuilder.”
Here you'll add control buttons that allow the user to change the grants displayed on the graph. As the number of grants that could be passed into Landscape is variable, you'll need a variable number of buttons. Though this is certainly possible in InterfaceBuilder (and is how the issue was originally solved) its much cleaner to add the views to the screen programatically.
In this section you will instantiate UIButtons in Landscape and add them as subviews to Landscape's view. The tricky part will be understanding how iOS handles placement of views.
Tidbits on Views:
Lets get started.
button.frame = CGRectMake(100, 100, widthVariable, heightVariable); [self.view addSubview:button]; [self.view bringSubviewToFront:button]; [button setTitle:@"Test" forState:UIControlStateNormal]; [buttonReferences addObject:button];
Test to make sure the button appears.
Instantiating a button doesn't do much, you have to explicitly add it as a subview using addSubview. Note how we refer to the controller's view property using self.view. Note also the usage of bringSubviewToFront; without this line the button would be stuck behind the graph.
CGRectMake is a C convenience function, as obvious by the parenthesis. It produces a CGRect struct, which we assign to the button's frame property, thereby changing its location and size.
Aright, here comes the fun part. You'll next need to make a loop that iterates over each grant stored in “grantObjects,” creating a button for each one with the appropriate title.
[button setTitle:[[grant getMetadata] objectForKey:@"title"] forState:UIControlStateNormal];
Test to make sure you see the buttons on the screen.
Obviously we'd like the buttons to not overlap. The solution here is to dynamically set each frame property so they sit hugging the right edge of the screen, one on top of each other. To do this you'll need the following:
The x coordinate should be easy, use the loop variable to set the y coordinates of each button to a variable amount. Pad the y-value with some constant value so the buttons dont creep to the top and get occluded by the status bar (the battery, reception, etc indicators).
The buttons are all in the right place, but they're fairly ugly. Lets make them pretty.
//Create an array of color objects that are removed in order for each grant, ensuring unique colors NSArray *plotColors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], [UIColor cyanColor], [UIColor yellowColor],[UIColor magentaColor],[UIColor orangeColor],[UIColor purpleColor], [UIColor grayColor], [UIColor whiteColor], nil];
//coloring UIColor *temp = [plotColors objectAtIndex:i]; const CGFloat* components = CGColorGetComponents(temp.CGColor); button.backgroundColor = [UIColor colorWithRed:components[0] green:components[1] blue:components[2] alpha:.4]; [[button layer] setCornerRadius:8.0f]; [[button layer] setMasksToBounds:YES]; [[button layer] setBorderWidth:1.0f]; [button.titleLabel setFont:[UIFont systemFontOfSize:8]];
Ok, buttons in place, pretty, everything is going well, with the notable exception that touching a button does… nothing.
If we were laying these onto the controller's view using InterfaceBuilder, the next step would be connecting an IBAction. This would have the effect of creating the callback method within the controller and alerting the button it should call that method when touched.
The method required to realize this in code is called addTarget:withSelector:forControlEvents:.
The method buttonPress has already been created in Landscape. Check it out if you'd like.
Test to make sure the connections work. When you press a button, the graph of the matching color should disappear from the screen, the screen should resize itself, and the SUM graph should change.
—-
Hey, you survived the intermediate material! If you made it here, then you're plenty comfortable with Xcode and Objective-C at this point (or exhausted and frustrated from hours of brute-forcing solutions). Congratulations!
Next week we will cover a smattering of advanced iOS topics. Don't feel like everything we've covered so far is just the tip of the iceberg- once we finish up next week you'll have seen most of what iOS has to throw at you, confidence comes with repetition.