I'm recreating some kind of UIAlertView specific to my app, so I'm subclassing UIWindow to do it. The window gets added to [UIApplication sharedApplication].windows, but is never actually shown. I trimmed it down to this small piece of code:
UIWindow *testWindow = [[UIWindow alloc] initWithFrame:self.view.bounds];
testWindow.backgroundColor = [UIColor blueColor];
[testWindow makeKeyAndVisible];
When I log [UIApplication sharedApplication].windows, I see:
"<UIWindow: 0x83774f0; frame = (0 0; 320 480); opaque = NO; autoresize = RM+BM; layer = <UIWindowLayer: 0x8377660>>",
"<UIWindow: 0x8382630; frame = (0 0; 300 400); layer = <UIWindowLayer: 0xf573e60>>"
And yet that second window with a blue background color is nowehere to be seen.
UPDATE: this seems to be an issue only when ARC is enabled. I created 2 new "single view" projects, one with ARC enabled and the other with ARC disabled. Both are identical and I add the UIWindow code to viewDidAppear: of the main view controller. When I run the apps in the simulator, the blue window only shows up in the ARC-disabled project. It looks like ARC gets rid of my UIWindow too quickly and so it doesn't even have time to show up. Making it __strong didn't help. Still clueless...
It looks like ARC gets rid of my UIWindow too quickly and so it doesn't even have time to show up. Making it __strong didn't help.
Making what __strong? The variable you showed in your question appears to be a local variable, which only exists until the method returns. When the method returns, the variable goes away, so nothing owns the window, so it will then get deallocated.
Assign the window's pointer to a __strong instance variable or a strong property. Then you'll have a longer-lasting ownership keeping the window alive. Set the ivar or property to nil after you dismiss the window.
As a side note, are you sure you want this to be a subclass of UIWindow and not UIView? Even UIAlertView is a view and not a window. If it creates its own window, you may want to do that—have the view create its own window as an implementation detail.
The docs mention that you should use UIScreen to determine the bounds: [[UIScreen mainScreen] bounds]
I'm not sure it's required, but you're not adding a UIView to your window.
I noticed the opaque = NO - try [window setOpaque:NO]; or something similar.
I tested your code and got:
Application windows are expected to have a root view controller at the end of application launch
So basically, use [window setRootViewController:...];. Don't ask me why, this really doesn't seem needed to me.
Related
My app has been running fine for a couple years, but there have been several hiccups with the release of iOS 6 and one of them is a problem with StatusBar orientation.
My app works in either Landscape orientation, but touches were vertically off by the width of the status bar in one of the two orientations. Intuitively, it seemed that even though the status bar image was being relocated properly during rotation, the LOGICAL position of the status bar was not changing -- and thus consuming touches on the wrong side of the screen.
In my Info.plist file "UIStatusBarHidden" was set to FALSE. In my App Delegate's "didFinishLaunchingWithOptions" function I created my own custom ViewController and set it as the RootViewController for the Window. Even though this worked prior to iOS 6, I believe it is at this point that the StatusBar did not "transfer" to the new RootViewController.
I theorized that prior to iOS 6 my code worked because orientation changes got propagated to the OLD RootViewController and caused orientation to work as expected. Since iOS 6 no longer propagates orientation beyond the NEW RootViewController, the OLD controller (which "logically" contained the StatusBar) never got updated.
Note: my app does not have a .XIB file and does not explicitly create a RootViewController until this point. But I think a default one was created for me behind the scenes anyhow.
My "solution" was to set "UIStatusBarHidden" to TRUE in the Info.plist file and manually set it to FALSE in code at a point AFTER I've set my custom RootViewController. This seems to work, but I may have addressed the symptom instead of the actual problem.
I'm also worried about side-effects because other things besides StatusBar could still be logically tied to that old/implicit RootViewController and they aren't getting "transferred" to the new one either.
Does anyone have similar experience with StatusBar and/or RootViewController? Is this approach ok or should I leave the default/implicit RootViewController in place and somehow add my ViewController to it as a child? That didn't seem to work when I first wrote the code two years ago, which lead to my current implementation.
Below are the highlights of the code in questions. Thanks in advance for the advice.
-BT
// ******** Info.plist value *********
<key>UIStatusBarHidden</key>
<true/>
// ******** App Delegate **********
- (BOOL)application : (UIApplication *)application didFinishLaunchingWithOptions : (NSDictionary *)launchOptions
{
CGRect rect = [[UIScreen mainScreen] applicationFrame];
rect.origin.x = 0; rect.origin.y = 0;
self.window = [[[UIWindow alloc] initWithFrame:rect] autorelease];
self.gameViewController = [[[GameViewController alloc]init] autorelease];
self.gameView = [[[GameView alloc] initWithFrame:rect] autorelease];
[self.gameViewController.view addSubview : self.gameView];
[self.window setRootViewController:self.gameViewController];
// Setting it now attaches it to our actual RootViewController instead of the
// "phantom" controller created under the hood?
[UIApplication sharedApplication].statusBarHidden = NO;
// ...
}
I'm in the process of making some adjustments to an app, including changing to a navigation-based interface. As part of that change I've written a view controller that contains a UINavigationController. The problem is, for some strange reason the UINavigationBar and UIToolbar managed by the UINavigationController are displaced 20px down from where they should be. I've managed to produce the following example that demonstrates the issue:
// MyAppDelegate.m
#implementation MyAppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.frame = [[UIScreen mainScreen] applicationFrame];
[self.window makeKeyAndVisible];
TestController* tc = [TestController new];
[self.window addSubview:tc.view];
return YES;
}
#end
// TestController.m
#implementation TestController
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
UINavigationController* navController = [UINavigationController new];
navController.view.backgroundColor = [UIColor blueColor];
[navController setToolbarHidden:NO animated:NO];
[self.view addSubview:navController.view];
}
#end
This produces the following result on my machine:
As you can see, the controls are 20px down from where I'd expect them to be. I've tried just about everything I can think of (various combinations of wantsFullScreenLayout, autoresizesSubviews, etc) with no positive effect. This also has nothing to do with programatically messing with the statusbar (as seems to be the case in most other examples of this I have come across), since I do not at any point mess with the statusbar. This occurs with or without a root view controller in the navigation controller - if there is one, it's contents are shifted 20px down too (so they actually are in the right place relative to the navigation bar and toolbar).
Any help much appreciated!
EDIT: After a bit of investigation, it seems that removing the line self.window.frame = [[UIScreen mainScreen] applicationFrame]; seems to correct the positioning of the navigation bar and toolbar and content. That said, now some other views in the application are in the wrong place (up underneath the statusbar). My understanding is that line is generally recommended to ensure that the window is the correct size?
As mentioned in my edit, removing the line self.window.frame = [[UIScreen mainScreen] applicationFrame]; seems to have corrected 95% of my problems. I've managed to fudge an approach to fix the other 5% by using the same background colour for my window and the remaining views having issues, but I can't say I'm thrilled with this solution - I shouldn't have to do that.
I'll keep experimenting, and if I find a better result will certainly post an edit here.
UINavigationController does not play nicely with being used as a subview; as you've noticed, it will often leave room for the status bar even when it is not actually under the status bar. If you're not trying to write your own container view controller, you should rework your code to not be adding a view controller's view as a subview at all.
That said, I've had luck fixing it by setting wantsFullScreenLayout to NO on the UINavigationController, which will make it not leave space for the status bar. You would, of course, want to do this just after allocating it, before loadView gets triggered.
I've got three ViewControllers set up to handle three views. The problem that I'm having is that in the simulator the orientation is LandscapeRight (which is what I want), and the first view shows up correctly in that landscape view, but when I move onto the second and third views, they show up rotated 90 degrees counter-clockwise with the upper-left corner of the view in the lower left corner of the phone's screen. I've been trying to debug this for a few days and the closest that I've gotten to a clue is tracing it the following way:
The following is in my app delegate's applicationDidFinishLaunching:
NSLog(#"1");
[window addSubview:welcomeController.view];
NSLog(#"2");
[window addSubview:goalController.view];
NSLog(#"3");
[window addSubview:planningController.view];
NSLog(#"4");
[window bringSubviewToFront:welcomeController.view];
NSLog(#"5");
Each of my ViewControllers implement something similar to the following (the only change being the controller's name switched out in the string passed to NSLog):
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
NSLog(#"called for WelcomeController");
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
With that, I get the following output on the Console:
a
called for WelcomeController
called for WelcomeController
called for WelcomeController
called for WelcomeController
2
called for GoalController
3
called for PlanningController
4
5
I find it interesting that shouldAutorotateToInterfaceOrientation is called 4 times for the first view that's added, while the other two only get called once. I expect that this is probably because it's got to do some setup at first (and I believe that the simulator starts off in portrait mode, so it's might be calling it while doing the rotation), but I find the correlation a bit suspicious.
I've switched the order around so that the addSubview is called for the goalController first and the welcomeController second. In this case, it's the goalController which displays in the correct landscape orientation (it's normally the welcome controller). This would seem to eliminate my XIB files and the ViewControllers themselves. I'm not sure why the first view where addSubview is called is special. I also tried using insertSubview at index 0 with the same results.
Ran into the same problem, and apparently adding subviews to a UIWindow doesn't work the way I expected it to. I managed to solve the problem after adding a "dummy" UIViewController that is the ONLY subview in the UIWindow. After adding that one, it works perfectly to add multiple subviews to the dummy-controller, all with the correct orientation.
So the only code in the "dummy" controller class is the "shouldAutorotateToInterfaceOrientation" function. This should also match the same function in all the other subviews.
Hope it helps.
I had a similar issue. Not sure why either. But the workaround was to call this on every view after the first one:
[planningController.view setFrame:CGRectMake(0, 0, 480, 300)];
and before -addView. I'm curious if this helps you out. If I am not the only one with this problem and this workaround, then maybe there's a reason.
This is far far far from ideal. But you can hack the second views transform so it is rotated correctly. This works for me because my app is only ever in landscape mode. It may not be ideal if you want to change orientation.
[window addSubview:firstController.view];
[window addSubview:secondController.view];
CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI/2.0);
[backgroundViewController.view setTransform:rotate];
CGRect contentRect = CGRectMake(0, 0, 1024, 768);
backgroundViewController.view.bounds = contentRect;
[backgroundViewController.view setCenter:CGPointMake(768/2, 1024/2)];
I think I have a solution for this that appears to work. Add a view, and then immediate remove it, repeat for each view, then add all three. Like this:
[window addSubview:welcomeController.view];
[welcomeController.view removeFromSuperview];
[window addSubview:goalController.view];
[goalController.view removeFromSuperview];
[window addSubview:planningController.view];
[planningController.view removeFromSuperview];
[window addSubview:welcomeController.view];
[window addSubview:goalController.view];
[window addSubview:planningController.view];
It seems to work, at least in the simulator.
Using this method to hide the status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
When setting "hidden" back to NO, the tap-to-scroll-to-top (in UIWebView, UITableView, whatever) doesn't work any more, and requires a restart of the app to get the functionality back.
Is this a bug (I filed a rdar anyhow) or have I missed a step? Should I perhaps expect this behavior since the statusBar "loses touch" somehow with the respective view?
You could try setting the ScrollsToTop property to true again after re-showing it:
[currentView setScrollsToTop:YES];
If that's not working, are you definitely only showing one view? If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored...
In iOS 5.0 you can access the scrollview property of the UIWebView
webView.scrollView.scrollsToTop = YES;
The following fix by Alex worked for me. Thanks!
((UIScrollView *)[[webView subviews] objectAtIndex:0]).scrollsToTop = NO;
Being in a hurry this fix worked great, however given more time I might've subclassed the UIWebView and accessed the protected UIScrollView member directly.
The worry I have with Alex' method is that it assumes that UIScrollView is at index zero of the subviews (encapsulation allows private members to change). Which suggests another solution still:
for (UIView* v in [webView subviews])
{
if ([v isKindOfClass:[UIScrollView class]])
{
(UIScrollView *)v.scrollsToTop = NO;
}
}
I was having a similar problem where the scroll-to-top functionality was lost. Turns out this will only work when you have only one active view at a time (within the same scroll view). In my case I had a table view and another view which would fade in/out. Adding a removeFromSuperview at the end of the animation did the trick.
The answer was in the UIScrollView.h file comments:
/*
this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
flag set or the delegeat method returns NO, the view isn't scrolled
*/
#property(nonatomic) BOOL scrollsToTop; // default is YES. if set, special gesture will scroll to top of view after consulting delegate
You can use the following code to have the UIWebView ignore scrollToTop without the extra UIScrollView:
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = NO;
I had a similar problem after playing a Youtube video within my app. scrollsToTop was still set to YES but tapping the status bar had no effect.
I finally realised that my app window was no longer the key window. After adding the following line to a UIWindow subclass (which I already had for other reasons) everything worked as it should again:
if (![self isKeyWindow]) [self makeKeyWindow];
I just ran across a similar behavior in the app I'm currently working on. In its case, if you load a YouTube video from within a UIWebView, scroll to top stops working for the rest of the application's life cycle. I kind of assume this might happen after loading the movie player as well, but haven't confirmed. That functionality has been around a lot longer and probably has fewer bugs.
When there are multiple scrollview, you can also set scrollUpToTop to NO for the others scrollview. cf:
setScrollsToTop with multiple UIScrollView classes and/or subclasses(UITableView)
I want to add my case, I add an UIWebView on an UIScrollView, as h4xxr had answered on the top:
If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored
So, I get a simply way to make it work on webView: just set the scrollView·s scrollsToTop property false.
And when tap the status bar, it won`t got intercepted by the scrollView, and the webView scrolls to the top!
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.scrollsToTop = false; //igore scrollView`s scrollsToTop
[self.view addSubview:scrollView];
UIWebView *webView = [[UIWebView alloc] init];
webView.frame = scrollView.bounds;
[scrollView addSubview:webView];
I'm learning how Objective-C on the iPhone layers views onto the main window.
I've tried remaking a basic program to draw two different colored rectangular views onto the main UIView for an app, but it does not seem to work.
My current output is a white screen. However, when I close the application, it briefly shows the two rectangles as they're closing, making me believe they're drawn but not displayed for some reason.
Assuming the psuedo-code below (shortened from the original documentation for the sake of StackOverflow, but copied directly into my project) is fine, what else might I have to do to my project to get these rectangles to display?
Code from Apple in my TestAppDelegate.m file:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the window object and assign it to the
// window instance variable of the application delegate.
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.backgroundColor = [UIColor whiteColor];
// Create and initialize a simple red square
// Create and initializesimple blue square
// Add the square views to the window
[window addSubview:redView];
[window addSubview:blueView];
// Once added to the window, release the views to avoid the
// extra retain count on each of them.
[redView release];
[blueView release];
// Show the window.
[window makeKeyAndVisible];
}
Link to Original in Apple Docs: http://tinyurl.com/y8alvgt
You may need to call setNeedsDisplay on the window
[window setNeedsDisplay];
this has the effect of telling CocoaTouch that your window contents have changed and need to be redrawn.
Here's a link to the documentation.