Just when i thought I had everything figured out .. i got this problem.
the scenario.
I got a simple tableView. and with a search bar in navigation item's titleView. The SearchBar is added to navItems titleView via a uibarbuttonitem in view controllers toolbar.
NOW, normally
After initiating the searchbar with [beginResponder] the keyboard shows up. And It sends out a notification "KeyboardDidShow" which is where i calculate the UIKeyboard's height and set the tableView's height accordingly (Shorten it).
ON Rotation - to and fro landscape/portrait, everything works fine.
-(void)didRotateInferfaceOrientation is called and everythings kool.
Heres the problem.
When the keyboard is active, it has a Google "search" button, this pushes to a new view - webviewcontroller.
the problem is, this
When, [PORTRAIT]ViewController [SearchBar with keyboard active] --> taps Search --> [Portrait]WebViewController --> Change Device Orientation to [Landscape] --> [Landscape]WebViewController changes to landscape ---> HERES THE PROBLEM, user taps back to uiViewController[Landscape]
the method -didRotatefromInterfaceOrientation isnt called. and somehow the tableView height is messed up. Though the view is rotating perfectly.
Is there something im missing here..
would appreciate any help. .thanks
When user taps back, -didRotatefromInterfaceOrientation will not be called. You need to check orientation in viewWillAppear (or call viewDidLoad, prior to returning from tap on back), and then call the proper layout for the chosen orientation.
In all of your (BOOL)shouldRotate... methods, you should be call a separate method to ensure your layout is correct for the device orientation.
I got a similar problem in one of my applications recently, not exactly our problem but don't bother, you should see what I'm heading for: I wanted to simply rotate an viewController displayed using presentModalViewController...Unfortunatly it didn't really worked put, especially on old iPhone with OS prior to iOS 4...So I needed to rotate programatically! Just get your screen size, use CGAffineTransform or something like that and change the sizes and then you should be done...
If your interested I could post a bunch of code, so let me know!
EDIT:
UIScreen *screen = [UIScreen mainScreen];
myController.view.bounds = CGRectMake(0, 0, screen.bounds.size.height, screen.bounds.size.width - 20);
if(currentOrientation == UIDeviceOrientationLandscapeRight){
myController.view.transform = CGAffineTransformConcat(myController.view.transform, CGAffineTransformMakeRotation(degreesToRadian(-90)));
}else{
myController.view.transform = CGAffineTransformConcat(myController.view.transform, CGAffineTransformMakeRotation(degreesToRadian(90)));
}
myController.view.center = window.center;
[[UIApplication sharedApplication] setStatusBarOrientation:currentOrientation];
[self.window addSubview:self.tabBarController.view];
[self.window bringSubviewToFront:self.tabBarController.view];
[self.window addSubview:myController.view];
[self.window bringSubviewToFront:myController.view];
[self.tabBarController.view removeFromSuperview];`
This also includes removing a TabBar when rotating to landscape to get some more space...enjoy :)
You could call didRotateFromInterfaceOrientation manually on viewWillAppear and just pass an orientation yourself (i.e. [UIApplication sharedApplication].statusBarOrientation).
Related
I got an app will do auto login for users. When logging in to the server, there will be a 50% transparent view with "logging in..." message showing and it blocked user from doing anything else(Please don't argue with me whether I should do that by HIG, we gotta let the user login in order to do anything). So what i did was create a xib LoadingMessageViewController in IB, and in the main view controller's - (void)viewDidLoad(), I initialize the view controller and do [self.view addSubview:LoadingMessageViewController.view];
It will work fine in portrait mode, but not in landscape. I saw tons of posts about this problem and nothing really worked for me.
I set autoresizingMask to true, set autosizing in IB, I also tried to use [[[window subviews] objectAtIndex:0] addSubview:LoadingMessageViewController.view] instead of [self.view addSubview:LoadingMessageViewController.view]. The problem with using [[window subviews] objectAtIndex:0] is it happen in -(void)ViewDidLoad, and at that point, the view has not really finished loading to the screen yet, and subviews at that point is nil, and call objectAtIndex will cause a pointer out of range exception.
Please point me to the right direction.
Thanks for reading my post.
This is how it looks like:
Hi I have an app and I have two *.pngs for default splash screen:
Default-Landscape.png
Default-Portrait.png
What I want is to animate this default splash screen away when my app is loaded and ready to go.
To achieve this I would normally present an UIImageView with either default-landscape or default-portrait (depending on the device orientation), keep it on screen for a certain time and then animate it away.
My problem is that if I call [[UIDevice currentDevice] orientation] in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
The answer is always that the device is in portrait orientation even if I clearly have it in landscape. I tried this in simulator and on the device as well and the behaviour is the same.
Does anyone know a fix for this or maybe some other approach?
Thanks!
I had troubles with this and I solved it by making one image 1024x1024 and setting the contentMode of the UIImageView to UIViewContentModeTop, then using left and right margin autoresizing. So long as your portrait and landscape default images are the same layout then this will work fine.
Just to clarify here's what I used:
bgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:SplashImage]];
bgView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
bgView.contentMode = UIViewContentModeTop;
To get around this problem I installed the splash image view inside of a view controller that allowed both orientations. Even though the device reported the wrong orientation at startup, the view controller seems to get the right one.
You can use UIApplication statusBarOrientation as follows:
if ( UIDeviceOrientationIsLandscape( [[UIApplication sharedApplication] statusBarOrientation] ))
{
// landscape code
}
else
{
// portrait code
}
Maybe you could show a blank view with black background at start time and place [[UIDevice currentDevice] orientation] into this view's viewDidAppear and start your splash screen from there?
Another solution would be to read the accelerometer data and determine the orientation yourself.
To know at start what is the orientation (UIDevice orientation don't work until user have rotate the device) intercept shouldAutorotateToInterfaceOrientation of your View Controller, it is called at start, and you know your device orientation.
There are certainly times when you want to transition from the loading image to something else before the user gets control of your app. Unless your app is really simple, going from loading image to landing page probably won't be sufficient without making the app experience really suck. If you ever develop a large app, you'll definitely want to do that to show progress during setup, loading xibs, etc. If an app takes several seconds to prepare with no feedback, users will hate it. IMO, there's nothing wrong with a nice transition effect either. Almost nobody uses loading screens the way Apple suggests to. I don't know which apps you looked at that showed the "empty UI" type loading screens they suggest. Heck, even Apple doesn't do that except in their sample code and none of my clients would find that acceptable. It's a lame design.
Only the first view added to the window is rotated by the OS. So if you want your splash screen to automatically rotate AND your main view is rotatable then just add it as a child of that view.
Here is my code for fading out the appropriate splash screen image:
// Determine which launch image file
NSString * launchImageName = #"Default.png";
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
launchImageName = #"Default-Portrait.png";
} else {
launchImageName = #"Default-Landscape.png";
}
}
// Create a fade out effect
UIImageView* whiteoutView = [[[UIImageView alloc] initWithFrame:self.window.frame] autorelease];
whiteoutView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
whiteoutView.autoresizesSubviews = YES;
whiteoutView.image = [UIImage imageNamed:launchImageName];
[[[self.window subviews] objectAtIndex:0] addSubview:whiteoutView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
whiteoutView.alpha = 0.0;
[UIView commitAnimations];
Note: You'll have to update it to support hi-res screens.
It sounds like you're not using the launch image the way Apple recommends in the iOS HIG. It specifically calls out that you should not use it as a splash screen, but rather as a skeleton version of your actual UI. The net effect is that your app appears to be ready just that much faster.
The suggestions that you could draw a splash screen yourself after the app has launching in viewDidAppear or similar also are missing the basic purpose of a launch image. It's not a splash screen. If your app is ready, let the user interact with it, don't waste their time drawing a splash screen.
From my five minute survey of Apple apps and third-party apps, everyone showed a portrait launch image, loaded the portrait version of the UI, and then rotated to landscape. It's been a while since programming on iOS, but I think this mirrors the order of the method calls -- first your app gets launched, then it is told to rotate to a particular orientation.
It might be a nice enhancement request to file with Apple though :)
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];
How can I make my view resize in response to the in-call status bar from my nib?
I figured it would just be setting the resize properties, but they're not enabled for the root UIView.
(I think my main problem here is I don't know what any of this is called; I can't find any reference to the in-call status bar in any of the documentation except where it talks about the simulator menu command.)
iOS will invoke your viewController's viewWillLayoutSubviews method whenever there is a change in status bar. You can override that and adjust your subviews according to the new bounds.
- (void)viewWillLayoutSubviews {
// Your adjustments accd to
// viewController.bounds
[super viewWillLayoutSubviews];
}
You're looking for -[UIApplication statusBarFrame] and, in your UIApplicationDelegate, you should implement this delegate method to be notified of when the status bar's frame changes:
- (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame
A view may not resize automatically with status bar size changes if you do not have a root view controller set. I originally had this in my app delegate, and my app worked properly in all regards except that it would not resize correctly during phone calls.
[self.window addSubview:rootController.view];
I changed the above line to this, and now my app resizes automatically during calls.
[self.window setRootViewController:rootController];
I discovered this fix after seeing this in the log and investigating the cause.
Application windows are expected to have a root view controller at the end of application launch
this works for me perfectly when my application is running in background and I press command + T. In my scenario , the root controller is my tab bar controller and I am readjusting my nav controller inside each tab.
- (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame{
[UIView animateWithDuration:0.35 animations:^{
CGRect windowFrame = ((UINavigationController *)((UITabBarController *)self.window.rootViewController).viewControllers[0]).view.frame;
if (newStatusBarFrame.size.height > 20) {
windowFrame.origin.y = newStatusBarFrame.size.height - 20 ;// old status bar frame is 20
}
else{
windowFrame.origin.y = 0.0;
}
((UINavigationController *)((UITabBarController *)self.window.rootViewController).viewControllers[0]).view.frame = windowFrame;
}];
}
What do you mean when you say that 'the resize properties aren't enabled for the root UIView'?
The in-call status bar doesn't have any particular special designation, and I don't think there are any APIs or notifications around it. Instead, your views should simply be set up to autoresize correctly.
Try creating a new navigation-based app in Xcode and study the autoresize settings on the table view in RootViewController.xib. Hopefully you'll see a delta between what Apple's set and what you've set in your project.
For views like UITableView you'll typically need to change the table cell height, and there's no other way to do it except to implement application:didChangeStatusBarFrame:. But it's no biggie, and you can set the row height to non-integer values if you need to.
The notification for status bar changing frame is
UIApplicationWillChangeStatusBarFrameNotification
Register as an observer:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(statusBarFrameWillChange:)name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
Then respond to change in your handler:
- (void)statusBarFrameWillChange:(NSNotification*)notification
{
// respond to changes
}
Even with autolayout setup correctly, you may need to respond to changes. For example, a table view that calculates its cell height based on the given space in the screen would may need to reloadData after the status bar changed.
Documentation
I was having the same problem. What I did was this.
Open the xib file in IB
All interface elements are by default attached to move along with with top and shown in the 'Autosizing' property in the 'Size Inspector'. So, for the UI elements at the bottom of the screen, remove the link from top and instead make the link from bottom. Leave all the others as is.
Problem Solved!!!
I hope I was clear.
You will have to update the views manually if you are setting your view frames programatically
-(void) viewDidAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(statusBarFrameWillChange:)name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
}
- (void)statusBarFrameWillChange:(NSNotification*)notification
{
// respond to changes
NSLog(#"STATUS BAR UPDATED");
NSDictionary *statusBarDetail = [notification userInfo];
NSValue *animationCurve = statusBarDetail[UIApplicationStatusBarFrameUserInfoKey];
CGRect statusBarFrameBeginRect = [animationCurve CGRectValue];
int statusBarHeight = (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication]statusBarOrientation])) ? statusBarFrameBeginRect.size.height : statusBarFrameBeginRect.size.width;
NSLog(#"status bar height %d", statusBarHeight);
}
-(void) viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationBackgroundRefreshStatusDidChangeNotification object:nil];
}
This gives you the new height of the status bar and using this you can update your frames accordingly.
The solution is to ensure you have made your window key:
[window makeKeyAndVisible];
If you don't do that the subview does not get resized automatically, but there are no other symptoms as far as I know.
None of the solutions posted before worked for me. What did work for me was that I didn't have my constraints set up properly for my views. In my situation, I had two different container views within my view.
The bottom (in the list) container view was a UITableView, which was not scrolling correctly to the bottom of the table. The reason was that the offset of the in-call status bar was causing the origin (top-left corner) of the UITableView to change, but the size would remain this same. This meant that the bottom of the table was off screen!
The solution was to correctly set the Autoresizing height constraint properly. In the screenshot below, it is the vertical arrows in the middle of the Autoresizing box.