iOS 8 UISplitViewController delegate not responding - objective-c

I'm transitioning my app to iOS 8, and I've decided to use a SplitViewController because its new functionality finally allows me to do what I want. I present the SVC modally on iPad, and the SVC is the root view controller of the full-screen cover vertical transition. From the presenting view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
plotSplitViewController = segue.destinationViewController;
plotViewController = (PlotViewController *)[[[segue.destinationViewController viewControllers] objectAtIndex:1] topViewController];
plotViewController.inventory = _inventory;
if ([plotViewController view]) [plotViewController setPlot:selectedPlot];
}
Then I manually make connections in the PlotSplitViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
// set up controllers
layoutNavigationController = [self.viewControllers objectAtIndex:1];
plotViewController = (PlotViewController *)[layoutNavigationController topViewController];
plotViewController.delegate = self;
// configure split view
[self showInfoPane:NO withTable:infoTableViewController];
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
}
So both the master and the detail get view controllers inside UINavigationControllers (to take advantage of the free toolbar resizing, plus the master pushes a table view hierarchy).
Everything seems fine; the views load as they're supposed to, the delegate method from PlotViewController functions correctly, etc. But as you can see, I assign the Split View Controller to be its own delegate...but it won't respond to any of its own methods, so I can't customize its behavior. I checked to make sure it's set correctly:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"split view %# did appear, delegate: %#", self, self.delegate);
}
And it returns the same object (itself) for both values. Is this just a no-no? I read that you can assign a SplitViewController as its own delegate, and I think an object can be a delegate for more than one other object, right? It can certainly implement protocols for more than one. So why is my SplitViewController not able to receive delegate methods for itself? I have NSLogs in all of them, and none are ever called.

Turns out it works if you make your custom view controller a subclass of UIViewController, and add a UISplitViewController programmatically, as a child view controller. Make your VC its delegate, and you can make it behave however you want.

Related

I lose access to my IBOutlets when making view controller new detail view controller

I have an iPad master-detail where the main detail controller is a navigation controller and depending on the table row (in the master view controller) selected, I may or may not replace the view controller managed by the detail navigation controller.
This seems to work fine, except that I lose access to the new detail view controller's IBOutlets when this move is made.
Here's me switching the navigation controller's view controller:
CustomerDetailViewController *subview = [[CustomerDetailViewController alloc] initWithNibName:#"CustomerDetailViewController" bundle:nil];
[subview setTitle:#"Testing"];
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
app.splitViewController.delegate = subview;
[app.detailNavigationController setViewControllers:[NSArray arrayWithObjects:subview, nil]];
[subview setData:[custData objectForKey:#"name"]];
custData is an NSDictionary containing my view information. Here is the setData command:
- (void)setData:(NSDictionary *)cust {
NSLog(#"%#\n", [cust valueForKey:#"name"]);
self.nameLabel.text = [cust valueForKey:#"name"];
NSLog(#"%#\n", self.nameLabel.text);
}
So what happens is, subview becomes the new view controller but the label does not get changed - however, those two log commands are executed. The label is synthesized and wired up using IB and works if I push subview as a new view controller instead of replace it.
I'd say, the view is not yet initialized. Outlets are first connected in the viewDidLoad method. Try putting a log statement in the viewDidLoad to find out, which one gets called first.
If the viewDidLoad is called after your setData method, you can only set a local variable of the CustomerDetailViewController which is then read by viewDidLoad which sets the label accordingly.

prepareForSegue is not called after performSegue:withIdentifier: with popover style

I have a universal app, where I am sharing the same controller for a IPad and IPhone storyboard.
I have put a UILongPressGestureRecognizer on a UITableView, that when a cell is pressed on iPhone it calls an action that perform a segue:
-(IBAction)showDetail:(id)sender {
UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer*)sender;
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gesture locationInView:self.theTableView];
NSIndexPath *indexPath = [self.theTableView indexPathForRowAtPoint:p];
if (indexPath != nil) {
[self performSegueWithIdentifier:SEGUE_DETAIL sender:indexPath];
}
}
}
the segue is a detail view performed as a 'push'. The first thing you should notice is that the sender is an NSIndexPath, is the only way I found for passing the selected cell. Maybe there's a better solution.
Everything works fine, in a sense that the segue is performed, and before the prepareForSegue is called too.
However it happens that on iPad, I have changed the segue identifier to Popover.
Now things are working in part, the segue is performed, but prepareForSegue is not called and therefore the destination view controller is not set up as it should be.
What am I doing wrong ?
What I have discovered so far, is that with any segue identifier that is not popover these are the invocations made by iOS:
prepareForSegue (on source controller)
viewDidLoad (on destination controller)
while in popover segue the invocation order is:
viewDidLoad (on destination controller)
prepareForSegue (on source controller)
just because I put all my logic in viewDidLoad, the controller was not properly initialized, and a crash happened. So this is not exactly true that prepareForSegue is not called, the truth is that I was getting an exception, and I wrongly mistaken as prepareForSegue not getting called.
I couldn't put everything in viewWillAppear because a call to CoreData had to be made and I didn't want to check if entities were ok each time the view display.
How did I solve this ? I created another method in destination controller
-(void)prepareViewController {
// initialization logic...
}
and changing the prepareForSegue method in source controller itself:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
MyViewController *mvc = (MyViewController*)[segue destinationViewController];
// passing variable
// with segue style other than popover this called first than viewDidLoad
mvc.myProp1=#"prop1";
mvc.myProp2=#"prop2";
// viewWillAppear is not yet called
// so by sending message to controller
// the view is initialized
[mvc prepareViewController];
}
don't know if this is expected behavior with popover, anyway now things are working.
I've noticed that the boiler plate code for Xcode's Master-Detail template (iPhone) uses the following pattern for configuring the detail VC's view:
the detail VC's setters (for properties) are overwritten in order to invoke the configureView method (configureView would update all your controls in the view, e.g., labels, etc.)
the detail VC's viewDidLoad method also invokes the configureView method
I did not follow this pattern the other day when I was trying to re-use a detail VC in my movie app, and this gave me trouble.
I don't have much experience with popovers; however, if the pattern above is used with a detail VC that is displayed inside a popover, then wouldn't the detail VC's view get configured when you set the detail VC's properties from within the prepareForSegue method?

How to perform a segue with a programmatically loaded storyboard?

I need to perform a segue from my storyboard. The method to call in that case is -[UIViewController performSegueWithIdentifier:sender:]. This method relies on the storyboard property of UIViewController to find the storyboard (and therefore the segue).
However, the storyboard property is not set when the UIViewController was not created from the storyboard. And since it's read-only, I can't set it programmatically where I load my storyboard.
So the question is: how to perform a segue from a storyboard that has been loaded programmatically?
If it's not possible, it's perhaps because my architecture is incorrect. Here is the use case:
The app is a legacy tabbar application where each of the 8 tabs has its own NIB file. Many of the 8 tabs are rather complex, and can benefit a lot from storyboards, especially prototype table cells and static tables. So I want to evolve the app to use storyboards.
However, one humongous storyboard doesn't seem a good idea: it would prevent incremental changes to the app, it would be unwieldy, it would make it difficult for the team members to work on their tab independently.
The right level of modularity seems to let the UITabBarController have its own specific storyboard. This makes it possible for each tab to evolve at its own pace and makes it easier for each developers to work on their tab with few source control conflicts.
My approach so far is the main nib file to contain the UITabBarController and each of its main daughter view controllers. The daughter view controllers load their storyboard from their viewDidLoad method. And from there, they can't perform their segue.
The alternative would be for the daughter view controllers to be created from their storyboards, but then how can I hook them up to the UITabBarController? And where do I programmatically load the storyboards?
Thanks for any suggestion.
make a reference on the segue and save the pointer value in a singleton class. Then using the pointer reference saved in the singleton class, access the segue wherever you like and load it. Here is a sample code in which i am loading a segue view from a single storyboard class but each view has separate view controllers .h and .m files that have appropiate connections. (i used formal protocol, hence the line shareVC.delegate = self; is there .
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue isKindOfClass:[UIStoryboardPopoverSegue class]]) {
if (self.currentPopover != nil) {
[_currentPopover dismissPopoverAnimated:YES];
self.currentPopover = nil;
}
UIStoryboardPopoverSegue *popSegue = (UIStoryboardPopoverSegue *)segue;
self.currentPopover = popSegue.popoverController;
}
if ([segue.identifier compare:#"ShareModal"] == NSOrderedSame) {
//the share controller is being presented modally, probably iphone
UINavigationController *shareNavController = segue.destinationViewController;
myViewController *shareVC = (myViewController *)[shareNavController topViewController];
shareVC.delegate = self;
} else if ([segue.identifier compare:#"SharePopover"] == NSOrderedSame) {
FollowersViewController *followerVC = segue.destinationViewController;
followerVC.delegate = self;
} else if ([segue.identifier compare:#"StartScreenSegue"] == NSOrderedSame) {
UINavigationController *startNavController = segue.destinationViewController;
StartViewController *startVC = (StartViewController *)startNavController.topViewController;
startVC.delegate = self;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationOpenUrlNotification object:nil];
}
}
Hope this helps:)

UIViewController and UIImagePickerController: Unable to create and managing views as expected

I have a UIViewController subclass that contains an instance of UIImagePickerController. Let's call this controller CameraController. Among other things, the CameraController manages the UIImagePickerController instance's overlayView, and other views, buttons, labels etc. that are displayed when the UIImagePickerController, let's call this instance photoPicker, is displayed as the modal controller.
The photoPicker's camera overlay and the elemets that are part of the CameraController view hierarchy display and function as expected. The problem I'm having is that I cannot use UIViewController's default initializer to create the CameraController's view heirarchy.
I am initializing CameraController from within another UIViewController. Let's call this controller the WebViewController. When the user clicks on a button in a view managed by WebViewController, the launchCamera method is called. It currently looks like this:
- (void) launchCamera{
if (!cameraController) {
cameraController = [[CameraController alloc] init];
// cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
// bundle:[NSBundle mainBundle]];
cameraController.delegate = self;
}
[self presentModalViewController:cameraController.photoPicker animated:NO];
}
I want to be able to create CameraController by calling initWithNibName:bundle: but it's not working
as I'll explain.
CameraController's init method looks like this:
- (id) init {
if (self == [super init]) {
// Create and configure the image picker here...
// Load the UI elements for the camera overlay.
nibContents = [[NSBundle mainBundle] loadNibNamed:#"CameraController" owner:self options:nil];
[nibContents retain];
photoPicker.cameraOverlayView = overlay;
// More initialization code here...
}
return self;
}
The only way I can get the elements to load from the CameraController.xib file is to call loadNibNamed:owner:options:. Otherwise the camera takes over but no overlay nor other view components are displayed. It appears that a side-effect of this problem is that none of the view management methods on CameraController are ever called, like viewDidLoad, viewDidAppear etc.
However, all outlets defined in the nib seem to be working. For example, when the camera loads a view is displayed with some instructions for the user. On this view is a button to dismiss it. The button is declared in CameraController along with the method that is called that dismisses this instructions view. It is all wired together through the nib and works great. Furthermore, the button to take a picture is on the view that servers as photoPicker's overlay. This button and the method that is called when it's pressed is managed by CameraController and all wired up in the nib. It works fine too.
So what am I missing? Why can't I use UIViewController's default initializer to create the CameraController instance. And, why are none of CameraController's view mangement methods ever called.
Thanks.
Your problem is easy but need some steps.
Well... First, if overlay is an IBOutlet, it can not be loaded at init time. So move picker and co in viewDidLoad. Place also here all other items that your say that they are not loaded. They should be loaded there (viewDIDLoad). Check that outlets are connected.
Second, call
cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
bundle:nil];
and ensure that CameraController contains (just) a view, and CameraController inherits UIViewController. Check also file's owner.
And at some time, you may consider that calling :
[self presentModalViewController:cameraController.photoPicker animated:NO];
does not make the CameraController control your picker. Does that make sense to you ?
What does that do regarding your problem ?
It seems you are confusing some things. I try to explain in another way :
The one that controls the picker is the one that is its delegate. Your may consider creating in a MAIN view.
The controller of the overlay (added as subview) is the one that own its view in File's Owner. That may be created from the MAIN view, adding its view as subview of the controller. Basically, it is loaded just to get the overlay, but viewDidLoad, ... won't be called.
That's all and I belive those steps are not ok in your code.
That should give something like :
MainController
Loadcamera {
self.picker = [UIImagePicker alloc] init.....];
self.picker.delegate = self;
SecondController* scnd = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.picker addOverlay:scnd.view];
[self presentModalViewController:self.picker animated:NO];
}
/// And here manage your picker delegate methods
SecondController
// Here manage your IBActions and whatever you want for the overlay

Why is this iPhone program not calling -loadView?

I am trying to work my way through basic iPhone programming and I have a good basic understanding of how Interface Builder works, so I decided to try my hand at doing the views programmatically. I have gone through the ViewController Apple guide and searched everywhere and I cannot seem to find a solution to my problem. This leads me to believe it is a very simple solution, but I am just really banging my head against the wall. Basically all I am trying to do is create a view that gets main window as a subview. I know that if self.view is not defined then the loadView method is supposed to be called, and everything is supposed to be set up there. Here is the current state of my code:
The delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
StartMenuViewController *aViewController = [[StartMenuViewController alloc] init];
self.myViewController = aViewController;
[aViewController release];
UIView *controllersView = [myViewController view];
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setBackgroundColor:[UIColor redColor]];
[window addSubview:controllersView];
[window makeKeyAndVisible];
}
The view controller:
- (id)init {
if (self = [super init]) {
self.title = #"Start Menu";
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *startView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[startView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[startView setBackgroundColor:[UIColor greenColor]];
self.view = startView;
[startView release];
}
Thanks for the help in advance!
Are you sure that you're inheriting from UIViewController and not overriding the implementation of - (UIView*)view?
EDIT: More info:
UIViewController has a special implementation of the "-(UIView*) view" message so that when it's called, the loadView method is called if the view member variable is not set. So, if you provide an implementation of "- (id)view" in your subclass, (or a property named view) it will break the auto-calling of "- loadView".
Just to document a "loadView is not called" case:
I wrote a 2 UITableViewController(s) to handle detail data for a master ViewController. Since the devil was in #2, I made a simple UITableViewController for #1, and referenced it in the XIB for the "master" ViewController.
When I was done with #2, I could simply copy the code to #1, remove the complicated code, and go on with life.
But to my dismay and several days work, no matter what I did, viewLoad was not being called for my simple #1 UITableViewController.
Today I finally realised that I was referencing the UITableViewController in the XIB to the master ViewController program. - and of course, loadView was never being called.
Just to help some other dork that makes the same mistake....
Best Regards,
Charles
viewDidLoad only if the view is unarchived from a nib, method is invoked after view is set.
loadView only invoked when the view proberty is nil. use when creating views programmatically. default: create a UIView object with no subviews.
(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen
mainScreen].applicationFrame];
[view setBackgroundColor:_color];
self.view = view;
[view release];
}
By implementing the loadView method, you hook into the default memory management behavior. If memory is low, a view controller may receive the didReceiveMemoryWarning message. The default implementation checks to see if the view is in use. If its view is not in the view hierarchy and the view controller implements the loadView method, its view is released. Later when the view is needed, the loadView method is invoked
again to create the view.
I would strongly recommend you use interface builder for at least your initial Window/View.
If you create a new project in XCode you should be able to select from one of many pre-defined iPhone templates that come with everything setup.
Unless I am reading this wrong, you did not associate any view with the the controller's view property like this
myViewController.view = controllersView;
So as far as Cocoa is concerned the view you are setting in the window has no controller to call loadView on. loadView is a View controller, not view, method. The view you assign to the window is not associated with any view controller. So your view controller loadView method is never called. Get it? The view you are trying to display, has no view controller associated with it.
When you use interface builder to create views you can link the UIView object you created in IB to the view property in the controller in IB which the framework automatically
But if not done in IB you have to set it