You’ll see that there are three view controllers. The first is a table view of the list of My Friends. The second is a view controller with a XIB to show the details of friend. The third is a table view that lets the user rate to his friend.
Look over the app and make sure you’re familiar with the structure. Once you’re done – let’s get to porting!
Upgrade Target for iPad
The initial step to port an iPhone to the iPad is almost misleadingly easy. Simply expand Targets and select “PortMe”, then click “Project\Upgrade Current Target for iPad”.
You have two options here – one universal application, or two device-specific applications. If you choose “One universal application”, that will allow you to make an app that customers can buy once, and have it work on both the iPhone or the iPad. If you want customers to purchase them separately, you may wish to choose “Two device-specific applications.”
In our case we’re going to make a Universal application, so click that and click OK.
Let’s take a look at what this did for us. The first thing it did was to create a new file called “MainWindow-iPad.xib” inside the “Resources-iPad” folder.
If you open it up and double click the Window, you’ll notice a big iPad sized Window. The XIB also contains the objects that were in the old XIB – the navigation controller with the PortMeFriendListController set as the root view controller.
Another thing this did was to set our project to link against the 3.2 SDK. You’ll notice that compiling for the 3.2 SDK isn’t even an option anymore (at least once you switch off of the old SDK for the first time):
We can see this in the Target Info as well; it changed the Base SDK to “iPhone Device 3.2″ and the Targeted Device Family to iPhone/iPad:
Autosizing
When I set up the sample project, I just dragged over several UI elements into the view, and paid no attention whatsoever to the autosizing attributes – as could be a common case when an iPhone app is made without rotation support.
But now that we want our app to support both the small and big screen as well as (eventually) be able to support rotation, autosizing becomes very important. With autosizing, we can tell each UI element how it should react when the size of its parent view changes.
The easiest way to see this working is by trying it out ourselves! Open up PortMeGameDetailsController.xib, and double click the view.
For the top label (friend name), we want the labels grow in width as the view grows in width. So select those two labels and go to the third tab in the inspector. Down in the Autosizing section, set click on the light red areas until you get it looking like the following:
Set the UIImageView like the following:
This means that the UIImageView should grow in both width AND height as the view expands, and stay anchored to the edges of the view.
Definitely an improvement!
However there’s one major problem: if you try to rotate the simulator with “Hardware\Rotate Left” – the iPad rotates but the app doesn’t! And since supporting all orientations is a requirement for iPad apps, that would mean instant rejection at this point.
Luckily, since we’ve set our autosizing attributes correctly for our view (and since UITableViewController already supports rotation), we can fix with just a couple lines of code.
Add the following code to the end of PortMeFriendListController.m, PortMeFriendDetailsController.m, and PortMeFriendRatingController.m:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
Compile and run the app, and now you should be able to rotate the phone to any direction and have the elements move correctly:
UISplitViewController Integration
UISplitViewControllers are designed so that you navigate to an item on the left hand side, and then see the details of the item on the right hand side.
This would be perfect for our app! We can put the list of friends on the left, and put the friend details on the right.
So let’s go ahead and integrate a UISplitViewController into our project. Open up MainWindow-iPad.xib, and drag a Split View Controller into the window, and delete the old Navigation Controller.
Expand the Split View Controller tree until you find the Table View Controller. In the Inspector, go to the fourth tab and set the Class to PortMeFriendListController.
Now we have to set up the right hand side. Let’s think about this a minute. When the user taps “Rate This”, we currently push another view controller onto the stack. This means that we need the right side view controller also to be a UINavigationController (at least for now until we change that).
So drag a Navigation Controller on top of the View Controller for the right hand side, dig down and set the root view controller to “PortMeFriendDetailsController.” When you’re done it should look like this:
Ok now we need to hook this up to the code. Currently, the Application Delegate is set up to add the navController property as a subview to the main window. However, for the iPad, we want it to add the split view controller to the window instead.
This means that we need an outlet for the split view controller. We might be tempted to just declare it as normal – however keep in mind this app needs to work on both the iPhone (running iPhone OS 3.0-3.1.3) and the iPad (running iPhone OS 3.2). iPhone OS 3.2 is iPad only btw.
Open up PortMeAppDelegate.h and add the following code:
// In the class interface
id _splitViewController;
// Afterwards
@property (nonatomic, retain) IBOutlet id splitViewController;
Then add the following to PortMeAppDelegate.m:
// In synthesize section
@synthesize splitViewController = _splitViewController;
// In didFinishLaunchingWithOptions, replace window addSubview line with:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIView *view = [_splitViewController view];
[window addSubview:view];
} else {
[window addSubview:_navController.view];
}
// In dealloc
self.splitViewController = nil;
The first line is the test you can use to see if your code is running on the iPad or not. If it is running on the iPad, we want to add the split view controller’s view as a subview of our window.
Since we have stored the split view as a generic object (rather than as the UISplitViewController class), we need to get the view by sending a message to it (rather than using dot notation).
That’s it for code! Last thing is to go back to MainWindow-iPad.xib and Control-drag from “Port Me App Delegate” to the “Split View Controller” and connect it to the “splitViewController” outlet, and Control-drag from “Port Me App Delegate” to the “Port Me Friend List Controller” and connect it to the “friendListController” outlet.
Make sure you save the XIB, then compile and run the app, and switch to landscape mode. The list of friends shows up OK on the left, but when you tap a friend it shows up in the same navigation controller instead of on the right hand side:
So let’s fix that next!
Linking up the Detail View
There are many good ways to hook the left and the right sides of a split view together, but one approach that works particularly well is delegation.
So we’ll follow the same approach we did in that tutorial and set up a protocol for “friend selected” that the detail view will implemenet to refresh the view.
Actually, this is a good point where you can practice doing this on your own and make sure you remember how to do it. If you successfully implement it on your own, just skip to the next section. Otherwise, you can keep following along!
If you choose to continue following along, go to File\New, choose Objective-C class, make sure “Subclass of” is “NSObject”, and click “Next”. Name the file “FriendSelectionDelegate” and click “Finish”.
Replace FriendSelectionDelegate.h with the following:
#import
@class Friends;
@protocol FriendSelectionDelegate
- (void)friendSelectionChanged:(Friends *)curSelection;
@end
Then delete FriendSelectionDelegate.m, since we don’t need it for a protocol.
Now, let’s modify the PortMeFriendListController to take a FriendSelectionDelegate. And add the following to PortMeFriendListController.h:
// At top, under #import
#import "FriendSelectionDelegate.h"
// Inside LeftViewController
id _delegate;
// Under @property
@property (nonatomic, assign) IBOutlet id delegate;
And add the following to PortMeFriendListController.h:
// Under @implementation
@synthesize delegate = _delegate;
// Inside didSelectRowAtIndexPath, replace navigation push line with:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if (_delegate != nil) {
[_delegate friendSelectionChanged:friend];
}
} else {
[self.navigationController pushViewController:_detailsController animated:YES];
}
// Inside dealloc
self.delegate = nil;
Then, we’ll modify PortMeFriendDetailsController to implement the delegate. Make the following changes to PortMeFriendDetailsController.h:
// At top, under #import
#import "FriendSelectionDelegate.h"
// Class declaration line
@interface PortMeFriendDetailsController : UIViewController {
Then add the following to PortMeFriendDetailsController.m:
// Replace viewWillAppear with the following:
- (void)refresh {
_nameLabel.text = _friend.name;
_descrView.text = _friend.descr;
_ratingLabel.text = [NSString
stringWithFormat:@"Your rating: %@", _friend.rating];
}
- (void) viewWillAppear:(BOOL)animated {
[self refresh];
}
- (void)friendSelectionChanged:(Friends *)curSelection {
self.friend = curSelection;
[self refresh];
}
And for the final step, we can actually connect the delegate using Interface Builder since we marked the delegate as an IBOutlet. Open up MainWindow-iPad.xib and Control-drag from “Port Me Friend List Controller” to “Port Me Friend Details Controller” and connect it to the delegate outlet.
That’s it! Compile and run the app, and you now should be able to select between the Friend like the following:
Adding a Popover List
It’s standard practice to have a way to bring up the left hand side when you’re in portrait mode by tapping a button in the toolbar.
There are a few changes due to this being a Universal app, and since the right side is a navigation controller rather than a view with a toolbar, so you may want to keep following along here.
Make the following changes to PortMeFriendDetailsController.h:
// Add UISplitViewControllerDelegate to the list of protocols
@interface PortMeFriendDetailsController : UIViewController
{
// Inside the class definition
id _popover;
// In the property section
@property (nonatomic, retain) id popover;
we declare the UIPopoverController as a generic object to avoid problems on the 3.0-3.1.3 OS.
Then add the following to PortMeFriendDetailsController.m:
// In synthesize section
@synthesize popover = _popover;
// In dealloc and viewDidUnload
self.popover = nil;
// In gameSelectionChanged
if (_popover != nil) {
[_popover dismissPopoverAnimated:YES];
}
// New functions
- (void)splitViewController: (UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController: (UIPopoverController*)pc {
barButtonItem.title = @"Sidebar";
UINavigationItem *navItem = [self navigationItem];
[navItem setLeftBarButtonItem:barButtonItem animated:YES];
self.popover = pc;
}
- (void)splitViewController: (UISplitViewController*)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
UINavigationItem *navItem = [self navigationItem];
[navItem setLeftBarButtonItem:nil animated:YES];
self.popover = nil;
}
Then add the following to PortMeFriendListController.m to make the popover be a bit smaller rather than the full height of the screen:
// In viewDidLoad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self setContentSizeForViewInPopover:CGSizeMake(320.0, 300.0)];
}
Note we have to be careful to only run this if we’re on the iPad (and use message passing rather than dot notation) since we may be running on the 3.0 OS.
One last thing – go back to MainWindow-iPad.xib and control-drag from “Split View Controller” to “Port Me Friend Details Controller” to set the right view controller as the delegate of the split view controller.
Compile and run the app, and if all goes well you should have an item on your nav controller bar that you can tap to bring up the list of Friends!
Using UIPopoverController
PopOverController is implemented on Rate button.
Add the following to PortMeFriendDetailsController.h:
// Inside class declaration
id _ratingPopover;
// In property section
@property (nonatomic, retain) id ratingPopover;
Again note the use of the generic objects here since this is a Universal app.
And the following to PortMeFriendDetailsController.m:
// In synthesize section
@synthesize ratingPopover = _ratingPopover;
// In dealloc AND viewDidUnload
self.ratingPopover = nil;
// In rateTapped, replace pushViewController with the following:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIButton *button = (UIButton *)sender;
if (_ratingPopover == nil) {
Class classPopoverController = NSClassFromString(@"UIPopoverController");
if (classPopoverController) {
self.ratingPopover = [[[classPopoverController alloc]
initWithContentViewController:_ratingController] autorelease];
}
}
[_ratingPopover presentPopoverFromRect:button.frame inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
} else {
[self.navigationController pushViewController:_ratingController animated:YES];
}
Here we again switch based on whether we’re running on the iPad or not, and either present a popover or push onto the navigation controller as usual.
Next switch over to PortMeFriendRatingController.m and add the following:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self setContentSizeForViewInPopover:CGSizeMake(320.0, 300.0)];
}
Compile and run the app, and if you select a friend and tap rate you should see the following:
Getting a Better Detail View
Better detail view could still use some work.
First, the image in the background was actually made for the iPhone, and is just being scaled to the larger screen. So it looks a bit grainy at the larger resolution.
Secondly, we could make better use of the increased screen real estate by making some of the text bigger, or moving around some of the labels to make better use of the space.
When you start to get a lot of changes you’d like to make to the view like this, you COULD do everything programatically by modifying the UI elements in code and switching on the UI_USER_INTERFACE_IDIOM(), but it’s often easier to just make a custom view XIB for the iPad.
So let’s give that a shot! Open up PortMeFriendDetailsController.xib, and click “File\Create iPad Version Using Autosizing Masks”. It will create an Untitled XIB – save the XIB in the project folder and name it “PortMeFriendDetailsController-iPad.xib”.
Then download a copy of a higher resolution background image and set the UIImageView’s image to “bg-iPad.jpg”. (Image credit: szajmon).
Save the XIB. The last step is to make sure that the iPad XIB is the one that is loaded. Open up MainWindow-iPad.xib, select the “Port Me Friend Details Controller”, and go to the first tab of the Inspector. Set the NIB name to “PortMeFriendDetailsController-iPad.xib”.
Save the XIB, compile and run the project. If all goes well you should see the new view like the following:
Cheers :))) Enjoy!
No comments:
Post a Comment