(Xcode6-beta3, Swift, iPad, iOS8)
How can I create a splash page for an iPad app using a split view controller?
I've tried the straight-forward approach of drag n' dropping the little arrow to a new view controller, and setting up a button to segue to the split view controller on a touch up inside. This throws a memory error
I've also tried simply commenting out the following code from the application function in the AppDelegate, but I'm getting a
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: [identifier length] > 0'
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
// let splitViewController = self.window!.rootViewController as UISplitViewController
// let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
// splitViewController.delegate = navigationController.topViewController as DetailViewController
return true
}
I've even disconnect the Master-Detail view in Storyboard, so that all that should be loaded is the splash page alone, but it still crashes.
I am so stuck! Thanks for your help.
The problem you were having is related to the code in application:didFinishLaunchingWithOptions:
In that code the template access the "first" view controller defined in the Storyboard to get to the split view controller and set its delegate property. If you change the "little arrow" you are changing UIWindow's rootViewController property, and being of a different view controller, it crashes.
To solve that, the best way is:
create the storyboard as described (normal ViewController with a segue to the original Split VC)
comment out code in application:didFinishLaunchingWithOptions
create a UIView controller subclass for your newly added Scene
in that class, before the segue is done, insert this modified version of the code to set the Split View Controller's delegate property:
let splitViewController = segue.destinationViewController as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
splitViewController.delegate = navigationController.topViewController as DetailViewController
Working project here
Related
I use in my project only xib files. Now i need a static tableview for a settings view. I want to combinied xib and one storyboard(for the tableview).
I add a storyboard with one viewcontroller in my projekt. After than i add a identifier(SettingsView) for this viewcontroller. the following code is executed when the button was pressed:
SettingsView *CustomViewController = [[UIStoryboard storyboardWithName:#"Storyboard" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:#"SettingsView"];
[self presentModalViewController:CustomViewController animated:YES];
My Application crashed when i push the setting button:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Storyboard () doesn't contain a view controller with identifier 'SettingsView''
Please make sure that you added the StoryBoard ID as follows in the picture attached.
The storyboard and individual view controller that you are attempting to instantiate, names have match exactly. Otherwise, the rest of what you are doing is correct from what I can see.
My app has a map that tracks the user's location. This map will only appear under certain circumstances, and will dominate the user's attention until a particular task is complete, which is why the map isn't part of a navigation or tab bar UI.
If my map VC is set as the initial view controller in storyboard, it works fine. But if I try to load the map VC from elsewhere like this;
MapViewController *mapVC = [[MapViewController alloc] init];
[self presentModalViewController:mapVC animated:YES];
I just get a black screen.
I can confirm with NSLog that the VC is calling viewDidLoad and viewDidAppear, but the 'map' property of the VC is (null). I don't understand why (or how) I need to create the map property manually when using this technique, but it gets done for me when it is the initial VC.
The MapViewController instance in your storyboard is configured with a view hierarchy, including an MKMapView, and whatever else you did to configure that particular instance in the storyboard.
Now in this code which you show here, you are creating a completely new instance of MapViewController. It has no relationship to the instance in the storyboard other than they happen to be of the same class. So the one you create here with [[MapViewController alloc] init] has no view hierarchy (which is why you see a black screen), and none of the outlets or other configuration you may have made to the other MapViewController in your storyboard.
So what you want is to load that MapViewController that you've already set up from the storyboard. Assuming you are doing this from within a method in another view controller loaded from the same storyboard already, you can just do this:
// within some method on another vc from a scene in the same storyboard:
// given an identifier for the map view controller we want to load:
static NSString *mapVCIdentifier = #"SomeAppropriateIdentifier";
NSLog(#"Storyboard: %#",self.storyboard); // make sure this vc(self) was loaded from a storyboard
MapViewController *mapVC = [self.storyboard instantiateViewControllerWithIdentifier:mapVCIdentifier];
[self presentModalViewController:mapVC animated:YES];
And then back in the storyboard, just make sure you set the identifier for this map view controller to "SomeAppropriateIdentifier".
Hope that helps.
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?
I setup a storyboard based on the Master-Detail Application, embed the detail view in a navigation controller, and add a new table view controller object which I will use as a second detail view controller.
I then push the new detail view controller with the following code (instead of a segue because I am pushing both a root view and a detail view controller at the same time. Only the detail view code is shown).
// Push the detailView view controller:
NewClass *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"test"];
newViewController.navigationItem.hidesBackButton = YES;
self.splitViewController.delegate = newViewController;
[self.detailViewController pushViewController:newViewController animated:YES];
This works perfectly, EXCEPT that the splitView delegate methods are never called before or after the push. If I do this while in portrait mode, after it pushes the detailViewController, the button to drop down the masterView popover does not show up UNTIL I rotate to landscape mode and then back to portrait mode.
How can I cause the willHideViewController/willShowViewController split view controller delegate methods to be called or manually cause them to be called?
So from what I found, it doesn't call the method because the orientation hasn't changed.
What you have to do is to pass the button from the presenting view controller since it's already tied to the popover like this:
if(self.navigationItem.leftBarButtonItem != nil) {
newViewController.navigationItem.leftBarButtonItem = self.navigationItem.leftBarButtonItem;
}
// Push the newViewController
I have a view controller:
#interface DetailViewController : UIViewController
It displays a map and pins are dropped onto this map. When the user tap's the accessory button one of the annotation views I want another view to be pushed in front of the user.
For some or other reason the navigation controller is always null when I run the following code.
hotelDetailViewController = [[HotelDetailViewController alloc] initWithNibName:#"HotelDetailViewController"
bundle:nil];
if (![self navigationController])
{
NSLog(#"navigation controller null");
}
[self.navigationController pushViewController:hotelDetailViewController animated:YES];
What am I doing wrong? At what point to do I need to alloc and init the navigation controller because it seems to be read only?
At what point to do I need to alloc and init the navigation controller because it seems to be read only?
Well, you don't usually set the navigationController property yourself, you would typically have a navigation controller set up from the start and then pass your DetailViewController to the navigation controller, and that's when the property is set.
The section in the View Controllers programming guide about Navigation Controllers explains how you should set up your navigation controller, either with a nib file or programmatically.