I'm having some trouble with the handleOpenURL method in my app delegate. I have a rootviewcontroller that should be shown normally, but when a file is opened in my app, I need the handleOpenURL method to present a new viewcontroller and send the url info. The problem is I can't present a modal view controller from my app delegate. Also, when I try to call a method in my rootviewcontroller to present the modalviewcontroller I get
Warning: Attempt to present ... whose view is not in the window hierarchy!
So, I guess I'm just looking for a solution that will present a new modal view controller and pass the url information to that class. This is the app delegate method.
- (void)handleOpenURL:(NSURL *)url {
}
Thanks for your help
In your RootViewController,Create an instance of appDelegate just like
YourAppDelegate *appDelegate = (YourAppDelegate *)[[UIApplication sharedApplication]delegate];
appDelegate.rootViewControllerInstance = self;
then subject
[appDelegate handleOpenURL:yourURL];
also create a callBackFunction eg:
-(void)callBack:(NSUrl *)url;
Now in appDelegate class create a property of your RootViewController
and in appDelegate your function as specified this way..
- (void)handleOpenURL:(NSURL *)url {
[rootViewControllerInstance callBackUrl:url];
}
In storyboard, add the view controller you want to present by adding a new view controller and setting its class to the one you are presenting.
Control-drag from the root view controller to the new controller, which will create a segue.
Set a name for the identifier of the segue, customize it to be shown modally.
In root view controller, call [self performSegueWithIdentifier:#"MySegueIdentifier"];.
In prepareForSegue of the root view controller set any attributes, including e.g. a URL.
I am trying to create a UITableView using storyboard but I came to something that at the end may be easy but I have no idea how to solve it.
First of all let me point out that I know that one of the limitations of storyboards is that you will have to dig through the storyboard to find information about a view you have and link it to the app delegate.
I have create my mutable array and the information that I will use in the table in the app delegate and now I want to reference that UITableView to the app delegate. The hierarchy goes like that
First I have the root view that once you click on a button it will redirect you to the second view
Inside the second view there is another button that once you press it it will redirect you to the UINavigationController
The UINavigationController contains the UITableView.
Therefore as you can see there are two views before the navigation control and the UITableView.
Here is the code I am trying to use but it does not work
UIViewController *viewController = (UIViewController *)self.window.rootviewController;
// The next line refers to the second view but does not work at all
UIViewController *secondView = [[UIViewController viewController] objectAtIndex:1];
//Then the following line is to redirect from the second view to the navigation controller
UINavigationController *navigationController =[[secondView viewController] objectAtIndex:0];
//Then is the table view
BuildingsViewController *buildingsViewController = [[navigationController viewControllers] objectAtIndex:0];
The above code does not work. Can anyone please help me?
Thanks a lot
If this code is in the app delegate there are a variety of reasons why it will probably not work. Firstly you appear to be mixing up View's, ViewControllers and Navigation controllers with what you are trying to do. Secondly there is no guarantee at the time you are trying to do this that all of the views/viewcontrollers have yet been created yet or are joined in the way they will be when the final building view controller is rendered.
What you could try instead is in your BuildingsViewController (which you say is your table view controller) you can get a handle to the App Delegate by using
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
Once you have a handle to the delegate you can simply reference your mutable array structure etc. that you created on it from within your BuildingsViewController.
e.g. in the 'numberOfRowsInSection' method:
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray *myBuildings = myAppDelegate.buildingArray;
return [myBuildings count];
Or in the cellForRowAtIndexPath method:
// something like this but using your names for app delegate, building array and the accessor for the building name
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray *myBuildings = myAppDelegate.buildingArray;
cell.textLabel.text = [myBuildings objectAtIndex indexPath.row].theBuildingName;
I'm authoring an iPad app. One of the screens in the app is perfectly suited to using a UISplitViewController. However, the top level of the app is a main menu, which I don't want to use a UISplitViewController for. This presents a problem, because Apple state that:
UISplitViewController should be the top level view controller in the app, i.e. its view should be added as the subview of UIWindow
if used, UISplitViewController should be there for the lifetime of the app -- i.e. don't remove its view from UIWindow and put another in place, or vice versa
Having read around and experimented, it seems to only viable option to satisfy Apple's requirements and our own is to use modal dialogs. So our app has a UISplitViewController at the root level (i.e. its view added as the subview of UIWindow), and to show our main menu, we push it as a full-screen modal dialog onto the UISplitViewController. Then by dismissing the main menu view controller modal dialog, we can actually show our split view.
This strategy seems to work fine. But it begs the questions:
1) Is there any better way of structuring this, without modals, that also meets all the requirements mentioned? It seems a bit odd having the main UI appear by virtue of being pushed as a modal dialog. (Modals are supposed to be for focused user tasks.)
2) Am I at risk of app store rejection because of my approach? This modal strategy is probably 'misusing' modal dialogs, as per Apple's human interface guidelines. But what other choice have they given me? Would they know that I'm doing this, anyway?
I seriously didn't believe that this concept of having some UIViewController to show before UISplitViewController (login form for example) turns out to be so complicated, until I had to create that kind of view hiearchy.
My example is based on iOS 8 and XCode 6.0 (Swift), so I'm not sure if this problem existed before in a same way, or it's due to some new bugs introduced with iOS 8, but from all of the similar questions I found, I didn't see complete 'not very hacky' solution to this problem.
I'll guide you through some of the things I have tried before I ended up with a solution (at the end of this post). Each example is based on creating new project from Master-Detail template without CoreData enabled.
First try (modal segue to UISplitViewController):
create new UIViewController subclass (LoginViewController for example)
add new view controller in storyboard, set it as initial view controller (instead of UISplitViewController) and connect it to LoginViewController
add UIButton to LoginViewController and create modal segue from that button to UISplitViewController
move boilerplate setup code for UISplitViewController from AppDelegate's didFinishLaunchingWithOptions to LoginViewController's prepareForSegue
This almost worked. I say almost, because after the app is started with LoginViewController and you tap button and segue to UISplitViewController, there is a strange bug going on: showing and hiding master view controller on orientation change is no longer animated.
After some time struggling with this problem and without real solution, I thought that it's somehow connected with that weird rule that UISplitViewController must be rootViewController (and in this case it isn't, LoginViewController is) so I gave up from this not so perfect solution.
Second try (modal segue from UISplitViewController):
create new UIViewController subclass (LoginViewController for example)
add new view controller in storyboard, and connect it to LoginViewController (but this time leave UISplitViewController to be initial view controller)
create modal segue from UISplitViewController to LoginViewController
add UIButton to LoginViewController and create unwind segue from that button
Finally, add this code to AppDelegate's didFinishLaunchingWithOptions after boilerplate code for setting up UISplitViewController:
window?.makeKeyAndVisible()
splitViewController.performSegueWithIdentifier("segueToLogin", sender: self)
return true
or try with this code instead:
window?.makeKeyAndVisible()
let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
splitViewController.presentViewController(loginViewController, animated: false, completion: nil)
return true
Both of these examples produce same several bad things:
console outputs: Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
UISplitViewController must be shown first before LoginViewController is segued modally (I would rather present only the login form so the user doesn't see UISplitViewController before logged in)
Unwind segue doesn't get called (this is totally other bug, and I'm not going into that story now)
Solution (update rootViewController)
The only way I found which works properly is if you change window's rootViewController on the fly:
Define Storyboard ID for LoginViewController and UISplitViewController,
and add some kind of loggedIn property to AppDelegate.
Based on this property, instantiate appropriate view controller and after that set it as rootViewController.
Do it without animation in didFinishLaunchingWithOptions but animated when called from the UI.
Here is sample code from AppDelegate:
var loggedIn = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
setupRootViewController(false)
return true
}
func setupRootViewController(animated: Bool) {
if let window = self.window {
var newRootViewController: UIViewController? = nil
var transition: UIViewAnimationOptions
// create and setup appropriate rootViewController
if !loggedIn {
let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
newRootViewController = loginViewController
transition = .TransitionFlipFromLeft
} else {
let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
let controller = masterNavigationController.topViewController as MasterViewController
newRootViewController = splitViewController
transition = .TransitionFlipFromRight
}
// update app's rootViewController
if let rootVC = newRootViewController {
if animated {
UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in
window.rootViewController = rootVC
}, completion: nil)
} else {
window.rootViewController = rootVC
}
}
}
}
And this is sample code from LoginViewController:
#IBAction func login(sender: UIButton) {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
delegate.loggedIn = true
delegate.setupRootViewController(true)
}
I would also like to hear if there is some better/cleaner way for this to work properly in iOS 8.
Touche! Ran in to the same issue and solved it the same way using modals. In my case it was a login view and then the main menu as well to be shown before the splitview. I used the same strategy as thought out by you. I (as well as several other knowledgeable iOS folks I spoke to) could not find a better way out. Works fine for me. User never notices the modal anyway. Present them so. And yes I can also tell you that there are quite a few apps doing the same under the hood tricks on the App store. :) On another note, do let me know if you figure a better way out somehow someway sometime :)
And who said you can have only one window ? :)
See if my answer on this similar question can help.
This approach is working very well for me. As long as you don't have to worry about multiple displays or state restoration, this linked code should be enough to do what you need: you don't have to make your logic look backwards or rewrite existing code, and can still take advantage of the UISplitView in a deeper level within your application - without (AFAIK) breaking Apple guidelines.
For future iOS developers running into the same issue: here's another answer and explanations. You HAVE to make it root view controller. If it is not, overlay a modal.
UISplitviewcontroller not as a rootview controller
Just ran into this problem on a project and thought I'd share my solution. In our case (for iPad), we wanted to start with a UISplitViewController with both view controllers visible (using preferredDisplayMode = .allVisible). At some point in the detail (right) hierarchy (we had a navigation controller for this side, too) we wanted to push a new view controller over the entire split view controller (not use a modal transition).
On iPhone, this behavior comes for freeāas only one view controller is visible at any time. But on iPad we had to figure something else out. We ended up going with a root container view controller that adds the split view controller to it as a child view controller. This root view controller is embedded in a navigation controller. When the detail view controller in the split view controller wants to push a new controller over the entire split view controller, the root view controller pushes this new view controller with its navigation controller.
I'd like to contribute my approach to presenting a UISplitViewController, as you might like to via -presentViewController:animated:completion: (we all know that won't work though).
I created a UISplitViewController subclass which responds to:
-presentAsRootViewController
-returnToPreviousViewController
The class, which like other successful approaches, sets the UISplitViewController as the window's rootViewController but does so with an animation similar to what you get (by default) with -presentViewController:animated:completion:
PresentableSplitViewController.h
#import <UIKit/UIKit.h>
#interface PresentableSplitViewController : UISplitViewController
- (void) presentAsRootViewController;
#end
PresentableSplitViewController.m
#import "PresentableSplitViewController.h"
#interface PresentableSplitViewController ()
#property (nonatomic, strong) UIViewController *previousViewController;
#end
#implementation PresentableSplitViewController
- (void) presentAsRootViewController {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
_previousViewController=window.rootViewController;
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = self;
[window insertSubview:windowSnapShot atIndex:0];
CGRect dstFrame=self.view.frame;
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=self.view.frame.size.width;
offset.height*=self.view.frame.size.height;
self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.view.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
}];
}
- (void) returnToPreviousViewController {
if(_previousViewController) {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = _previousViewController;
[window addSubview:windowSnapShot];
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=windowSnapShot.frame.size.width;
offset.height*=windowSnapShot.frame.size.height;
CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
windowSnapShot.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
_previousViewController=nil;
}];
}
}
#end
I did a UISplitView as initial view, than it goes modally to a fullscreen UIView and back to UISplitView. If you need to go back to the SplitView you have to use a custom segue.
Read this link (translate it from japanese)
UIViewController to UISplitViewController
Adding to the answer of #tadija I am in a similar situation:
My app was for phones only, and I am adding a tablet UI. I decided doing it in Swift in the same app - and eventually migrate all the app to use the same storyboard (when I feel the IPad version is stable, using it for phones should be trivial with the new classes from XCode6).
No segues were defined in my scene yet and it still works.
My the code in my app delegate is in ObjectiveC, and is slightly different - but uses the same idea.
Note that I am using the default view controller from the scene, unlike previous examples. I feel this will also work on IOS7/IPhone in which the runtime will generate a regular UINavigationController instead of a UISplitViewController. I might even add new code which will push the login view controller on IPhones, instead of changing the rootVC.
- (void) setupRootViewController:(BOOL) animated {
UIViewController *newController = nil;
UIStoryboard *board = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve;
if (!loggedIn) {
newController = [board instantiateViewControllerWithIdentifier:#"LoginViewController"];
} else {
newController = [board instantiateInitialViewController];
}
if (animated) {
[UIView transitionWithView: self.window duration:0.5 options:transition animations:^{
self.window.rootViewController = newController;
NSLog(#"setup root view controller animated");
} completion:^(BOOL finished) {
NSLog(#"setup root view controller finished");
}];
} else {
self.window.rootViewController = newController;
}
}
Another option: In the details view controller I display a modal view controller:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if (!appDelegate.loggedIn) {
// display the login form
let storyboard = UIStoryboard(name: "Storyboard", bundle: nil)
let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
self.presentViewController(login, animated: false, completion: { () -> Void in
// user logged in and is valid now
self.updateDisplay()
})
} else {
updateDisplay()
}
Don't dismiss the login controller without setting the login flag. Note that in IPhones the master view controller will come first, so a very similar code will need to be on the master view controller.
I have only one window and I tried
UIWindow* mWindow = [[UIApplication sharedApplication] keyWindow];
but this returned nil.
I also tried:
UIWindow* mWindow = (UIWindow*)[[UIApplication sharedApplication].windows objectAtIndex:0];
But this raised an exception and the app closed, when I tried to print out
[[UIApplication sharedApplication].windows count]
It printed 0
Note: I am putting this in my only view controller's viewDidLoad method and this is completely a new iPad View Based Application so I changed nothing, just trying to get the window
Please help me to get this object
If your main window is an outlet of your AppDelegate (which should be the case), you may simply use
MyAppDelegate* myDelegate = (((MyAppDelegate*) [UIApplication sharedApplication].delegate));
[myDelegate.window ...]
Easiest way is to get the window from the app delegate instead:
UIWindow *keyWindow = [[[UIApplication sharedApplication] delegate] window];
// Do something with the window now
Your application's key window isn't set until [window makeKeyAndVisible] gets called in your app delegate. Your UIViewController is probably being loaded from a NIB before this call. This explains why keyWindow is returning nil.
Luckily, your view controller doesn't need to go through UIApplication to get the window. You can just do:
UIWindow *mWindow = self.view.window;
[[[UIApplication sharedApplication] windows] objectAtIndex:0]; // You can also check the count of this to make sure, because if there are no windows it will crash.
With iOS 13+ you should use:
if let currentScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = currentScene.keyWindow {
// now window contains the active window
}
You can achieve this in multiple ways. I like to use one of the following.
Use this when you need to access it just once.
if let window = UIApplication.shared.windows.first as UIWindow? {
// Action
}
If you need to access the UIWindow multiple times, I would suggest extending a class. I like to extend the UIViewController.
extension UIViewController {
/// Returns the UIWindow if available.
public var window: UIWindow? {
UIApplication.shared.windows.first as UIWindow?
}
}
Creating a new app based on the SplitViewController template and it works fine in Split View.
My main screen is to be a non-splitview 'menu'. I'm trying to figure out the best practice for
adding this 'mainMenu' modally above the splitViewController. (Then either push other non-split views above the mainMenu or
remove it to reveal and use the UISplitViewController.)
I have tried:
[self.navigationController presentModalViewController:mainMenu animated:NO];
And
[self presentModalViewController:mainMenu animated:NO];
In the viewWillAppear and viewWillLoad methods for rootViewController & detailViewController. In both cases, the code executes without error, but the mainMenu doesn't appear, the regular detailViewController and rootViewControllers appear.
(I did create an outlet from the navigationController in the main.xib file to the detailView navigationController, but that didn't change anything.)
I was able to make this work by using, which works, but seems like it is incorrect.
iPad_Prototype_SplitAppDelegate *delegate = (iPad_Prototype_SplitAppDelegate *) [ [UIApplication sharedApplication] delegate];
[delegate.splitViewController.view addSubview:mainMenu.view];
[delegate.splitViewController.view bringSubviewToFront:mainMenu.view];
I've seen many responses saying to present such a covering view modally, but I can't seem to find the right place or configuration in the splitViewController setup. Thanks for any help or insight.
Finally, is this approach wrong, should I just be swapping out the detailViewController and having it take full screen in portrait mode and not add the menu item for the root controller?
Is your splitViewController in the AppDelegate like the example and will this help?
//AppDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyController *myCont = [[MyController alloc] initMainMenu];
// mess around with myCont.view.modalPresentationStyle;
[myCont setModalDelegate:self];
// Create a delegate (<ModalControllerDelegate>) to dismiss view when done
[self.splitViewController presentModalViewController:myCont animated:NO];
[myCont release];
}
// for completion sake
-(void)modalViewDismiss:(MyController *)modalView {
[self.splitViewController dismissModalViewController:YES];
}