How to Add a Button When the View Controller is Replaced in Your iOS App - dummies

How to Add a Button When the View Controller is Replaced in Your iOS App

By Jesse Feiler

Because you may be replacing the view controller in your iOS app with a new one — albeit one from the same base class — the new view controller has no access to the button it needs to display or the popover it needs to dismiss. Fortunately, the folks at Apple provide a perfect place to do that.

When a segue is triggered — which you do when you tap a Table entry — but before the new controller slides its view into place, the storyboard runtime calls the current view controller’s method so that it can pass data to the view controller that’s about to be displayed.

That means that you can pass the button information on to the new view controller before it even gets launched. Then, prepareForSegue:sender:, will both assign the button to be used in viewDidLoad and dismiss the view controller as well.

prepareForSegue:sender: is a view controller method that notifies a view controller that a segue is about to be performed. segue is the UIStoryboadSegue object, and it contains information about the view controllers involved in the segue.

Although default implementation of this method does nothing, UIViewController subclasses can override it and pass data to the view controller that’s about to be displayed. The segue object has properties that point to the source view controller as well as the destination view controller. The segue is the only object that simultaneously knows about both the source and the destination view controllers.

sender is the object that caused the segue, but you won’t need to use it here.

You add the code to the prepareForSegue:sender: method to make the DestinationController the UISplitViewController delegate and assign the popOverButton and masterPopoverController properties.

You’ll also dismiss the popover when it’s present so the user doesn’t have to touch in the view to get rid of it.

To do all that, add this code to MasterViewController.m.

- (void)prepareForSegue:
(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad)
{
DetailViewController *currentDetailViewController;
if ([[self.splitViewController.viewControllers
lastObject]
isKindOfClass:[UINavigationController class]])
currentDetailViewController =
(DetailViewController *) ((UINavigationController
*)
[self.splitViewController.viewControllers
lastObject]).topViewController;
else
currentDetailViewController =
[self.splitViewController.viewControllers
lastObject];
if(currentDetailViewController.masterPopover
Controller
!= nil)
[currentDetailViewController.masterPopover
Controller
dismissPopoverAnimated:YES];
DetailViewController
*destinationDetailViewController;
if ([segue.destinationViewController
isKindOfClass:[UINavigationController class]])
destinationDetailViewController =
(DetailViewController *)
((UINavigationController *)
segue.destinationViewController).topViewController;
else
destinationDetailViewController =
segue.destinationViewController;
self.splitViewController.delegate =
destinationDetailViewCon troller;
destinationDetailViewController.popOverButton = currentDetailViewController.popOverButton;
destinationDetailViewController.
masterPopoverController
=
currentDetailViewController.masterPopoverController;
}
}

You start by finding the current Detail View controller because it has the button and popover properties the new view controller needs.

This code appears more complicated than it is. You first check to see whether you’re running on an iPad. If you are, you get the current Detail View controller by accessing the list of view controllers in the splitViewController’s viewController array. Fortunately, the UIViewController class has a splitViewController property to make that easy for you.

You then check to see whether a Navigation controller is in the viewControllers array and, if one is there, you get the Navigation controller’s topViewController (the current view controller); if not, you simply use the controller in the array. (You’ll notice a whole lot of casting going on here)

if ([[self.splitViewController.viewControllers 
lastObject]    
isKindOfClass:[UINavigationController 
class]])
currentDetailViewController = (RTDetailViewController *)
(UINavigationController *)
[self.splitViewController.viewControllers
lastObject]).topViewController; else
currentDetailViewController = 
[self.splitViewController.viewControllers
lastObject];

Next, if you see a popover, you want to dismiss it. You check to see whether a popover controller (that’s why you had to make the property accessible by moving it into the header file.

if (currentDetailViewController.masterPopoverController             
!= nil)
[currentDetailViewController.masterPopoverController
dismissPopoverAnimated:YES];

Next, you find the new destination controller (the one being transitioned to) using logic similar to the logic you used to find the current Detail View controller.

if ([segue.destinationViewController 

isKindOfClass:[UINavigationController
class]])destinationDetailViewController =
(DetailViewController *)((UINavigationController *)
segue.destinationViewController).topViewController; else
destinationDetailViewController =
segue.destinationViewController;

Then you simply set the Split View Controller delegate to the new view controller, so it will get the splitViewController:willHideViewController:withBarButtonItem:forPopoverController : and splitViewController:willShowViewController:invalidatingBarButtonItem: messages.

self.splitViewController.delegate =    destinationDetailViewController;

Finally, you assign the popOverButton and masterPopOverController properties in the new view controller.

destinationDetailViewController.popOverButton = 
currentDetailViewController.popOverButton;
destinationDetailViewController.masterPopoverController =
currentDetailViewController.masterPopoverController;

Admittedly, this just dismisses the popover and assigns the properties, but doesn’t do anything to display the button. That actually gets done in viewDidLoad.

You also can specify the size of the popover window by assigning a value to the prefferedContentSize property. You should be aware that the actual size may be reduced so that the popover fits on the screen and that the popover does not overlap a keyboard when a keyboard is presented. You can see the code that does that in the MasterViewController ’s awakeFromNib method.

- (void)awakeFromNib{ if ([[UIDevice currentDevice] 
userInterfaceIdiom] == UIUserInterfaceIdiomPad)

{ self.clearsSelectionOnViewWillAppear = NO;
self.preferredContentSize = CGSizeMake
(320.0, 600.0); } [super awakeFromNib];}

The awakeFromNib message is sent to an object that has been instantiated from the storyboard after all the objects have been loaded and initialized. When the message is sent, all its outlet and action connections have been set.

If you decide to ignore the advice here and don’t dismiss the popover controller, taps outside the popover window will cause the popover to be dismissed. You can, however, allow the user to interact with the specified views and not dismiss the popover, using the passthroughViews property (although you won’t be doing that here). You’d then have to dismiss the popover yourself.