My app has a root viewcontroller, which at the start of the app displays
login viewController view if the user is not logged in
main viewController view if the user is logged in
AppDelegate code:
- (BOOL) application: (UIApplication*) application
didFinishLaunchingWithOptions: (NSDictionary*) launchOptions
{
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[RootViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
Here's the code used in RootViewController:
#implementation RootViewController
- (void) loadView
{
[super loadView];
// here mainViewController and loginNavigationController are initialized
}
...
- (UIView*) view
{
[super view]; // this invokes loadView
return self.isLoggedIn ? self.mainViewController.view :
self.loginNavigationController.view;
}
....
- (void) userDidLogin
{
[self.loginNavigationController presentViewController: self.mainViewController
animated: YES
completion: nil];
}
#end
If the user is not logged in and presses login button the main viewController is presented.
The problem is that after main viewController is presented, I'm not able to interact with any of the UI elements. For example, I have a tableView as a main viewController's subview and when I try to scroll it I get the following warning in debug panel:
<UITableView: 0x202a4000; frame = (0 0; 310 548); clipsToBounds = YES;
gestureRecognizers = <NSArray: 0x1fd9f570>; layer = <CALayer: 0x1fdccff0>;
contentOffset: {0, 0}>'s window
is not equal to <RootViewController: 0x1fd9f7d0>'s view's window!
Ok, so after looking at the updated code I see that you have a rootViewController and are dynamically giving the view you think should be presented. The thing is, the rootViewController is in charge of the root view while your other two view controllers manage their own views. You should not be passing a different view controller's view off.
So in the end it looks like you want to conditionally set your rootviewcontroller. So lets look at the app delegate. I think you should make your app delegate do something like this. Have it figure out at runtime which viewcontroller to present. Then make that the rootviewcontroller for the app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIViewController * resolvedRootViewController = [self someMethodThatCorrectlyGivesRootViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = resolvedRootViewController;
[self.window makeKeyAndVisible];
return YES;
}
Related
I want to know how to set up the app delegate in my Xcode project so that the generic view controller files (ViewController.h and .m) will be the files that control the root view controller I set in my app delegate.
My AppDelegate.h
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CGRect rect = [[UIScreen mainScreen] bounds];
self.window.frame = rect;
planet3dController *theController;
self.controller = theController;
self.window.rootViewController = self.controller;
GLView *glView = [[GLView alloc] initWithFrame:rect];
[self.window addSubview:glView];
theController.view=glView;
glView.controller = self.controller;
glView.animationInterval = 1.0 / kRenderingFrequency;
[glView startAnimation];
glView.layer.contentsScale = [UIScreen mainScreen].scale;
return YES;
}
self.window.rootViewController = self.controller;
It looks like you are already doing just that.
I don't know what your app is trying to do, but [self.window addSubview:glView]; is somewhat suspicious. You probably want to add glView as a subview of your rootViewController, not the window, right?
If you want the initial "generic" view controller files to be the rootViewController, then don't override it with your specialized planet3dController one like you are currently doing in the example, but set it to an instance of that controller.
I want to display a programmatically created ViewController in a storyboard ViewController. The ViewController of the Storyboard is of a different class as the 'programmatically' created ViewController.
I've got the following classes:
ViewController (linked to storyboard scene, and implementation happens here)
OnboardingVC (all elements are created over here)
I've tried the following:
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
self.onboardVC = [self generateFirstDemoVC]; // returns in an instance ofOnboardingViewController
self = (ViewController*)self.onboardVC;
}
return self;
}
This (obviously) crashes.
What I don't want is this:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:self.onboardVC];
[self.window makeKeyAndVisible];
Because this doesn't take the setup in the storyboard into account, and I don't want that, because the VC needs to managed by a NavigationController. How do I accomplish that?
It sounds like you have a UINavigationController as the entry point to your storyboard. You can manipulate this navigation controller when the application launches, to add, remove, or replace view controllers, etc.
For example:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
UIViewController* initialVc = [OnboardingViewController generateFirstDemoVC]; // or whatever
UINavigationController* nav = (id)_window.rootViewController;
nav.viewControllers = #[ initialVc ];
return YES;
}
I have an empty application and there is no storyboard or xib involved. I want to have a hidden status bar and support only landscape orientation. Again, I wan't to make those changes only within code and don't touch the Info.plist.
Problem
I create a UIWindow with a controller that says the only supported orientation is landscape. In that case my UIWindow is created in the dimension of portrait mode and doesn't rotate. The expected result would be a screen that is completely cyan.
This is my delegate:
#import "AppDelegate.h"
#import "AppViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor cyanColor];
self.window.rootViewController = [[AppViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
#end
This is my controller:
#import "AppViewController.h"
#implementation AppViewController
- (BOOL)shouldAutorotate {
return YES;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeLeft;
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
#end
What I've tried so far
If I set the rootViewController after calling makeKeyAndVisible everything seems to work at first.
self.window.backgroundColor = [UIColor cyanColor];
[self.window makeKeyAndVisible];
self.window.rootViewController = [[AppViewController alloc] init];
There are still some issues. First of all I don't like this since it seems to be very fragile. Second problem is that in a more complex application that sets a GLKViewController as the rootViewController I get the following result (expected would be no black area on the left):
It looks like the status bar is not hidden early enough. Several gesture recognizers are active and in the GLKViewController and clicking on the black area yields the following log message:
2014-09-25 13:20:42.170 StackOverflowExample[6971:107907] unexpected nil window in
_UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow: UIClassicWindow: 0x7fa20b805e00; frame = (0 0; 375 667);
userInteractionEnabled = NO; gestureRecognizers = NSArray:
0x7fa20b80a620; layer = UIWindowLayer: 0x7fa20b806890
I also performed various other changes, like attaching an empty UIViewController and adding my view as a sub-view. In that case my view looks correct but the window is still using the wrong dimensions.
Everything rotates correct if I do not override the supportedInterfaceOrientations methods in my view controller. But that is of course not what I want.
When you run landscape app from portrait mode UIScreen has portrait bounds in iOS 8 (only if you haven't this app in app switch panel, as iOS 8 makes some cache). Even displaying window with makeKeyAndVisible doesn't change it's frame. But it changes [UIScreen mainScreen].bounds according to AppViewController avaliable orientation.
#import "AppDelegate.h"
#import "AppViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Portrait bounds at this point
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor cyanColor];
self.window.rootViewController = [[AppViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
#end
So let's change window's frame after [self.window makeKeyAndVisible]
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [UIWindow new];
self.window.backgroundColor = [UIColor cyanColor];
self.window.rootViewController = [[AppViewController alloc] init];
[self.window makeKeyAndVisible];
// Here it is
self.window.frame = [UIScreen mainScreen].bounds;
return YES;
}
I think that it is iOS 8 bug.
I had a similar problem, for a portrait-only app.
I fixed the problem by setting status bar orientation BEFORE instantiate the UIWindow
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Init my stuff
// ...
// Prepare window
[application setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO]; // prevent start orientation bug
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window makeKeyAndVisible];
return YES;
}
In your case, you should use UIInterfaceOrienationLandscapeLeft (or Right) in the setStatusBarOrientation:animated: method.
Hope it helps you.
Personally, none of the solution presented above worked. I finally set "hidden" to YES for the window in my main xib, as first suggested here: unexpected nil window in _UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow
You can rotate UIWindow by adding single line only.
You can set the rootController for your UIWindow. e.g:
fileprivate(set) var bottonOverlayWindow = UIWindow()
self.bottonOverlayWindow.rootViewController = self;
// 'self' will the ViewController on which you had added UIWindow view. So whenever you ViewController change the orientation, your window view also change it's orientation.
Let me know if you face any issue.
The problem is solved when adding a Launch Screen, which you can only do by adding an extra property to the info.plist
had this problem myself, i'm not sure if you can add it through code though, i only managed to make it work with info.plist + Launch Screen xib file
<key>UILaunchStoryboardName</key>
<string>Launch Screen</string>
Actually i don't think you have to add a xib file, if just the key (with any value) is available in the plist it should work.
None of the solutions posted here or elsewhere worked for me.
However, I found that this issue apparently does not occur with Storyboards, so an alternative solution is to move away from xibs. (This fact sadly also makes it unlikely that Apple will take the problem seriously.)
I'm working on Chapter 7 of BNR's iOS Programming book and I've run into a problem. At the start of the chapter I setup a UIViewController (HypnosisViewController) with an UIView (HypnosisView) that responded to motion events in the previous chapter.
I create the UIViewController in the AppDelegate.m file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
HypnosisViewController *hvc = [[HypnosisViewController alloc] init];
[[self window] setRootViewController:hvc];
...
}
In the HypnosisViewController, I set HypnosisView to become first responder:
- (void)loadView
{
// Create a view
CGRect frame = [[UIScreen mainScreen] bounds];
HypnosisView *view = [[HypnosisView alloc] initWithFrame:frame];
[self setView:view];
[view becomeFirstResponder];
}
And in HypnosisView I make sure to return YES to canBecomeFirstResponder. Unfortunately, the HypnosisView did not respond to motion events like before. When I eventually moved on, I made an interesting discovery. If I move HypnosisViewController into a UITabBarController, HypnosisView starts responding to motion events. The code looks something like this:
HypnosisViewController *hvc = [[HypnosisViewController alloc] init];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
NSArray *viewControllers = [NSArray arrayWithObjects:hvc, <insert more objs here>, nil];
[tabBarController setViewControllers:viewControllers];
[[self window] setRootViewController:tabBarController];
Why didn't HypnosisView become first responder when HypnosisViewController was set as the RootViewController? Why did it start working once HypnosisViewController was placed inside another controller? What am I missing about RootViewController?
Thanks!
Your question is very apt. I'm also studying the same book and am on the same chapter. The thing is that before we used UITabBarController we would either use HypnosisViewController or TimeViewController. And we would then do [self.window setRootViewController:hvc] or [self.window setRootViewController:tvc] in the AppDelegate.m file. In that case setRootViewController method was calling loadView method internally. So if loadView should get called then becomeFirstResponder (which resides inside of it as a method call as per your code) also is supposed to get triggered. So internally canBecomeFirstResponder should get called
Now when we use UITabBarController, things tend to break. What happens is instead of loadView getting called via '[[self window] setRootViewController:tabBarController];' line of code, it gets called through '[tabBarController setViewControllers:viewControllers];'. So the bottomline is that rootViewController property (when set to tabBarController) does not call loadView method and hence 'becomeFirstResponder' is not called. You may argue that loadView does get called through '[tabBarController setViewControllers:viewControllers];' but setViewControllers is not used for setting root viewController.
When I faced this problem, I made an explicit call to becomeFirstResponder. Here's how:-
#implementation HypnoTimeAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //method of UIApplicationDelegate protocol
{
NSLog(#"lets begin");
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
HypnosisViewController *viewController= [[HypnosisViewController alloc] init];
TimeViewController *viewController2= [[TimeViewController alloc] init];
NSLog(#"view controllers are done initializing!");
UITabBarController *tabBarController= [[UITabBarController alloc] init];
NSArray *viewControllers= [NSArray arrayWithObjects:viewController,viewController2, nil];
[tabBarController setViewControllers:viewControllers];//loadView of HypnosisViewController gets called internally since the 'app view' isn't going to load from a XIB file but from 'HypnosisView.m'.loadView method of TimeViewController loads its own view from the XIB file.
[self.window setRootViewController:tabBarController];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
#implementation HypnosisViewController
-(void)loadView{
NSLog(#"HypnosisView loading...");
HypnosisView *myView= [[HypnosisView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view= myView;
[self configureFirstResponder];//configuring first responder
}
-(void) configureFirstResponder{
BOOL viewDidBecomeFirstResponder= [self.view becomeFirstResponder];
NSLog(#"Is First Responder set as HypnosisView? %i",viewDidBecomeFirstResponder);
}
I'm adding a UIView to a UIWindow that's not the keyWindow. I'd like the UIView to rotate when the device rotates. Any special properties on the window or the view I need to set? Currently the view is not rotating.
I'm aware of the fact that only the first subview of the application's keyWindow is told about device rotations. As a test I added my view to the first subview of the keyWindow. This causes the view to rotate. However the view being a subview of the keyWindow's first subview won't work for various aesthetic reasons.
An alternative approach is observing device orientation changes in the view and writing the rotation code myself. However I'd like to avoid writing this code if possible (having an additional window is cleaner in my opinion).
UIView doesn't handle rotations, UIViewController does.
So, all you need is to create a UIViewController, which implements shouldAutorotateToInterfaceOrientation and sets this controller as a rootViewController to your UIWindow
Something like that:
UIViewController * vc = [[[MyViewController alloc] init] autorelease];
vc.view.frame = [[UIScreen mainScreen] bounds];
vc.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
//you vc.view initialization here
UIWindow * window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.windowLevel = UIWindowLevelStatusBar;
window.backgroundColor = [UIColor clearColor];
[window setRootViewController:vc];
[window makeKeyAndVisible];
and I used this MyViewController, cause I want it to reflect changes of main application
#interface MyViewController : UIViewController
#end
#implementation MyViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
UIWindow *window = ((UIWindow *)[[UIApplication sharedApplication].windows objectAtIndex:0]);
UIViewController *controller = window.rootViewController;
if (!controller) {
NSLog(#"%#", #"I would like to get rootViewController of main window");
return YES;
}
return [controller shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
#end
but you can always just return YES for any orientation or write your logic, if you wish.
You can add below code to your project
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
and created the function to handle it.