I'm using this.
I've put this in where the images are added:
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
I've put these overrides in SlideShowViewController:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)InterfaceOrientation duration:(NSTimeInterval)duration {
}
The first is called, but not the second.
I'm not using a nib; it uses a custom init which returns a self.view with the 3 images on it.
Am I missing something? What would be the best way to modify this script to get the landscape photos stretch to full width when the iPhone is rotated by the user?
Edit: Just tried adding willAnimate... to the Root View Controller of my NavigationController, but it's not invoked. Perhaps the NavigationController is the problem? In that case, where do I put the willAnimate...? Alternatively, how do I receive and pass on messages sent to it towards the visible viewController?
Edit 2: Cause of the error in topic was that the TabBarController was missing a connection in IB (which I was sure was there). So now my problem is the same as for a few other similar questions - willAnimate... is called in root VC but not in the pushed VCs. The code I've used fades in the gallery VC rather than slide it in, ie. it's not pushed but currently added as a subview to the window - to fill the entire display.
Suggestions for how to message that subview's controller to set the frames would be appreciated, or alternate solutions!
This is not an answer to why your "willAnimateRotation.."-method doesn't trigger but instead an alternative to how the image gallery can adapt to rotations itself.
In initWithImages add:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
Add this method to receive the notification. In didRotate you can handle the resizing an repositioning of the images.
- (void) didRotate:(NSNotification *)notification
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft)
{
NSLog(#"Landscape Left!");
}
}
Don't forget to unregister the notification in dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
May I also suggest that you use a UIScrollView instead of handling scrolling and gestures yourself?
Is SlideShowViewController pushed directly onto the Navigation Controller stack, or is it part of some other view? The UIView delegate methods get kind of messed up when working with subviews on views on the Navigation Controller stack.
If SlideShowViewController is just a subview of some other view that's been pushed onto the Navigation Controller stack, I would try to listen for this method in the view controller that is actually on the Navigation Controller stack, and then calling the appropriate method in SlideShowViewController from there.
I had a similar problem in getting viewWillAppear and similar method called within a navigation controller.
I solved by calling them manually when the root method is called.
I don't know if it's the same issue with rotation, but you may try putting in your root view controller
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)InterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.navigatioController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
This question was a bit tricky to answer I guess, since it was about my specific non-rudimentary app setup. Sorry for letting the bounty time run out, it was basically too late the same day I asked ;)
I thought I'd post this 'not buried in comments' for anyone who gets the same symptoms.
The first was that I noticed an IBOutlet that I'm sure I connected at one point was no longer connected (Tab Bar Controller -> tabBarController object). Since I was racking my brains and moved stuff around to make this work, this probably was due to me trying something and then restoring a backup.
Second part of the solution was to addSubView the gallery view to the tabBarController's view instead of the navigated-to view. Then the root view controller's shouldAuto... and willAuto methods were invoked.
(To hide the Tab Bar and Navigation Bar while rotating I had to set their .alpha to 0 while the gallery is displayed, otherwise the navigated-to view was partly hidden under the navigation Bar when returning to it. I found no way of hiding the status bar while showing the gallery.)
After this there was a request to have the gallery in a detail-view navigated to from another tab, and I got the tricky error that only while the OTHER (working) detail-view was shown under the other tab would autorotate work with the new gallery. Will get back to the solution to this, quittin time.
Related
I have been working on iOS5 and have been struggling with storyboards, segues and adjusting the views for different orientations. After going through some Apple's help for the past couple of days; I have not found one place for my answers, I have come to the following conclusions that I will articulate very carefully and I hope someone can tell me if I am correct:
1- Using a storyboard does not mean you have to use segues (yes this is confusion for a beginner).
2- You can only use segues between VCs that belong to a Navigation controller.
3- On the other hand, having a Navigation controller does not necessarily imply that you have to use a segue from one VC to another VC (yep!). So if you are trying to go from one VC to another VC (regardless of whether the first VC belongs to a Nav Controller or not),you can do the following:
Assume you have a WhiteVC class and a YellowVC class and you placed a button on the WhiteVC view (in the storyboard and changed the class of the generic VC to WhiteVC) and linked it to an IBAction in the WhiteVC class. This would be the code you would implement, in WhiteVC.m, to be able to click that button and have the YellowVC view appear:
-(IBAction)GoToYellow
{
YellowViewController *YellowVC=[self.storyboard instantiateViewControllerWithIdentifier:#"myYellowVC"];
[self presentViewController:YellowVC animated:YES completion:nil];
}
To return from the YellowVC to the WhiteVC, you would then create another button on the YellowVC view (follow same process as above) and use the following code:
-(IBAction)ReturnToWhiteVC
{
[self dismissViewControllerAnimated:YES completion:nil];
}
This will dismiss the YellowVC and return to the White VC.
4- It is not recommended to have one VC own (instantiate) multiple views - for different orientations for example- it is better to have a separate VC for each view. It is ok though to have VCs instantiate other VCs as part of the MVC.
5- Now all of this came about because I was building an app that has a Nav Controller but its root VC will be different (different VCs with different Views) depending on whether it is landscape or portrait. So I could either instantiate the VCs programmatically (like #3 above) which worked well. Or, if I wanted to do the transition using segues, the apple help docs provides the following code:
BOOL isShowingLandscapeView;
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
[self performSegueWithIdentifier:#"AlternateView" sender:self];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];// breakpoint shows that this is triggered
isShowingLandscapeView = NO;
}
}
The above code obviously only applies with a Nav Controller (because of the segues) and it worked when the orientation changed from portrait to Landscape but when I rotate back to portrait, I end up with the landscape VC rotated back to portrait. What am I doing here wrong?
I hope someone can validate/confirm these points and answer my question.
Thanks
KB.
First of all : segues don't apply only with a UINavigationController, any UIViewController is able to perform a segue.
The Apple documentation also say that you'll have to restrict supported orientation of each UIViewController you'll use to handle orientation i.e. you'll have to restrict portrait to portrait only and landscape to landscape only). This could be done following the doc :
For iOS 6 and later : override supportedInterfaceOrientation
For iOS 5 and earlier : override shouldAutoRotateToInterfaceOrientation
Then, in order to use Apple design to manage orientation, you can follow this blog post by Alex Moffat.
It basically describe how to design your view hierarchy using two view controllers displaying the same content (one for Landscape and one for Portrait), embedded in a Navigations Controller. Both view controller should be able to push a hird view controller able to autorotate.
The main idea is to push/pop the Landscape view on/from the navigation stack, and to handle the transition with the third view accordingly.
You should be able to adapt it's code to your own view hierarchy.
Hi I am trying to provide screen orientation in my app and I have read many docs online but when I try that It just is a total disaster (I tried emulator and iphone). On one partwhen the screen is rotated to landscape half of the views are not visible any more. Their position is on the left side and right side is empty, plus if I open a next view controller and then rotate again I can see through the second view parts of the first view it just is terrible I don't know how to fix it (Excluding blocking the possibility to lock orientation in portrait).
I know in android you can provide different xml layouts for portrait and landscape. Is there a way to fo that in iphone? If so how?
each of my views are composed of a controller and .xib
There is a "main" controller and the other controllers are called like that:
ayuda=[[AyudaView alloc]
initWithNibName:#"AyudaView"
bundle:nil];
[self.view addSubview:ayuda.view];
also I have already added these metods to the maincontroller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if(UIDeviceOrientationIsValidInterfaceOrientation(orientation)) {
[self handleInterfaceRotationForOrientation:orientation];
}
}
#pragma mark -
#pragma mark Screen Orientation Handling
- (void)handleInterfaceRotationForOrientation:(UIInterfaceOrientation)interfaceOrientation {
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self handleInterfaceRotationForOrientation:interfaceOrientation];
return YES;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self handleInterfaceRotationForOrientation:toInterfaceOrientation];
}
as indicated here
and have tried to play with the autosizing param but it only gets worse
Thank you very much
EDIT: ok I have been reading some more:
http://www.geckogeek.fr/iphone-forcer-le-mode-landscape-ou-portrait-en-cours-dexecution.html
http://www.cocoaosx.com/2011/11/10/rotatingviewcontroller-display-a-different-uiviewcontroller-depending-on-the-rotation-of-the-device/
and basically what I have found is that you have to create 2 controllers but this solution is problematic for me because of the nib files. I mean I would also have to create 2 nibs so double the amount of code (or use the library above). What if I have values in my textfields , do I just have to send them around?
So, apart from the comments suggesting you don't have any idea about what's going on in your code (which may or may not be true -- who am I to judge?), I see a few problems.
The first issue is that I don't think you understand MVC and how iOS handles the view hierarchy. You're correct in having each of your views controlled by a controller and designed by a .xib (there are cases where a view doesn't need its own controller, but for simplicity, we'll assume yours do). However, you are incorrect in the way you are presenting those views on screen. A view's controller is responsible for presenting or removing a view from the screen. The view only knows what it displays, such as labels or buttons. The controller is responsible for telling the view what to display on those labels and buttons. Instead of adding the new VC's view as a subview of your main view, push it onto a navigation controller's stack or present the view modally. If you're in your main VC, you can say
ayuda = [[AyudaView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:ayuda animated:YES];
I'm thinking that getting the view hierarchy correct might just be enough to solve your landscape/portrait swapping chaos. But while we're on that topic...why are you creating this handleInterfaceRotation method? View Controllers already have a method for this called
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
Use this method to handle rotation.
I wrote simple code to test UIImagePickerController:
#implementation ProfileEditViewController
- (void)viewDidLoad {
[super viewDidLoad];
photoTaker_ = [[UIImagePickerController alloc] init];
photoTaker_.delegate = self;
photoTaker_.sourceType = UIImagePickerControllerSourceTypeCamera;
photoTaker_.showsCameraControls = NO;
}
- (void)viewDidAppear: (BOOL)animated {
[self presentModalViewController: photoTaker_ animated: NO];
}
#end
And I'm getting strange warnings like the following:
2010-05-20 17:53:13.838 TestProj[2814:307] Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
2010-05-20 17:53:13.849 TestProj[2814:307] Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
Got any idea what this is about? Thanks a lot in advance!
This message will appear if you are presenting the UIImagePickerController within another UIViewController. Because it isn't pushed like a UINavigationController stack, there is confusion at the UIWindow level. I don't know if the warning is a problem, but to eliminate the warning you can do the following:
// self = a UIViewController
//
- (void) showCamera
{
cameraView = [[UIImagePickerController alloc] init];
[[[UIApplication sharedApplication] keyWindow] setRootViewController:cameraView];
[self presentModalViewController:cameraView animated:NO];
}
- (void) removeCamera
{
[[[UIApplication sharedApplication] keyWindow] setRootViewController:self];
[self dismissModalViewControllerAnimated:NO];
[cameraView release];
}
Perhaps you are adding the root UIViewController's view as a subview of the window instead of assigning the view controller to the window's rootController property?
IT ALL FALLS BACK ON THE UI
This warning can be implemented for several different objects: Pickers, keyboard, etc.
I have found that it is related to the UI taking two steps to complete a transition or other animation. Or for any instance where the UI is trying to finish one thing and is being asked to execute another before it has finished. (therefore it covers a wide range of possible triggers)
I have seen the warning appear on 4.0 & 4.2. In my case I was working with rotating the device and catching whether the keyboard was still up-(i.e. text field was still first responder). If so, the keyboard needed to stay up for between the views, but this presented other complications with other views.
Therefore, I implemented a BOOL tracker to keep track if keybaordIsPresent, and if so I was {textfield resignFirstResponder]; when detecting the orientation change and the reset the textfield to becomeFristResponder after the transition that was wrapped in an Animation Block. My BOOL tracker worked better, I still use the NSNotifications for the Keyboard, but there were overlaps of notifications during rotations because the keyboard was being dismissed without requesting such. The BOOL is set to NO on Load, and when the [textfield resignFirstResponder]; is implemented. *not when "-(void)keyboardWillhide is trigger by the NSNotifications, which gives me both working triggers that never conflict. The BOOL is set to YES, only when the user touches textfield which automatically triggers becomeFirstResponder.
I removed the warning by taking the [textfild resignFirstResponder]; out of the
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}
}
and placing it back at the top of the code for the:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (keyboardIsPresent == YES) {
[self.entryTextField resignFirstResponder];
}
//Determine Which Orientation is Present:
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) || (fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
//LANDSCAPE VIEW:
[self configureLandscapeView];
}else {
//PORTRAIT VIEW:
[self configurePortraitView];
}
}
**Even though I had no code inside the -(void)willAnimatFirstHalfOfRotationToInterface:, the warning was still popping up. I think the warning was still popping up because the compiler still has to attempt the method while it is trying to execute the first animation and therefore gets the double animation call or overlap of resources. It doesn't know that there is no executable code with the method until after it runs through it. And by that time it already set aside resource in preparation for handling possible actions within the method.
**To ellimiate the warning I had to remove or nil out the code for the willAnimateFirstHalfOfRotation, so that the compiler does not have to even check to see if there is a possible 2nd animation or action that may need to be executed at the same time.
/*-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}}*/
After the transition is completed, within the original animation block I check to see if the "keyboardIsPresent" was YES prior to the rotation, and if so, I resign the First Responder once again. I use setAnimationDuration:0.3which comes out pretty clean and not jumpy.
Well, you are presenting UIImagePickerController modally inside viewDidAppear of ProfileEditViewController.
Think about this. That means when ProfileEditViewController view appears, the UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and.... you get the idea.
That warning is rather cryptic though, not sure if that is what it's trying to tell you. I would suggest making a button somewhere on the ProfileEditViewController that calls presentModalViewController and also make sure you have a way to dismiss the UIImagePickerController (I've never used it not sure if it has one automatically).
You may be trying to present two modal view controllers at the same time, and they are fighting for animation resources.
1) There is rarely any UI reason to do this. You could instead just go directly to the second view controller (the image picker); and, after dismissing it, then present the first view or view controller.
2) If you do want two stacked view controllers or a view controller on top of a view, then set a timer in viewDidAppear to present the second view controller after the first one has finished it's animation. (You could display a dummy png image of a blank picker in the first one to prevent too much display flashing until the second view controller goes live.)
EDIT - Added a random code example:
I might try substituting this as an experiment:
- (void)foo {
[self presentModalViewController: photoTaker_ animated: NO];
}
- (void)viewDidAppear: (BOOL)animated {
NSTimer *bar = [ NSTimer scheduledTimerWithTimeInterval: (2.0f)
target: self
selector: #selector(foo)
userInfo: nil
repeats:NO ];
}
A shorter time delay may work as well.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,
I think the warning here is about Core Animation performance. As a test, I loaded the image picker without any action sheet or other animations going on and the warnings are still there. I think these are warnings coming from the image picker class itself and not from any misuse of the API.
I'm trying to detect a shake on the iPhone device, so for what i've read I need to set my view controller as first responder on the viewDidAppear method. This method is supposed to be called automatically, but for some reason this method is never called. Only viewDidLoad is called.
this is my code (I put only the relevant parts):
BuildHouseViewController.h:
#interface BuildHouseViewController : UIViewController {
BuildHouseViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view becomeFirstResponder];}
-(void)viewDidAppear:(BOOL)animated{
[self becomeFirstResponder];
[super viewDidAppear:animated];}
-(BOOL)canBecomeFirstResponder {
return YES;}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(#"shake");
if ( event.subtype == UIEventSubtypeMotionShake )
{ }
if ( [super respondsToSelector:#selector(motionEnded:withEvent:)] )
[super motionEnded:motion withEvent:event];
}
I added breakpoints on the viewDidAppear method and it is never called. Shakes are never detected, I suppose it is because as this methods are never called, so the view controller can never become first responder. I don't understand why this is happening.
Any help will be appreciated.
Edit:
I call the view from another view using:
[self.view addSubview:nextScreen.view];
The view is displayed on screen
Thanks for the quick answers.
I've found something interesting. I tried loading the same view I'm having problems with in different ways and I'm getting different results
-As I said before if I call it from another view using:
[self.view addSubview:nextScreen.view];
viewDidLoad is never called and I cannot detect shakes.
-Now if I call it from the AppDelegate using:
[window addSubview:nextScreen.view];
viewDidLoad is called!! and I am able to detect shakes, however this solution is not possible, I should be able to call it from another view
-If I call it from another view using:
[self presentModalViewController:nextScreen animated:YES];
viewDidLoad is called!! However I don't want to use a modal view controller, but it appears to be the only solution to my problem, shakes are detected.
It is strange that the first method doesn't load the view correctly, is it a bug??
The [self becomeFirstResponder] and the like don't actually make that become the first responder. The method gets called when the view is going to become the first responder. So that's not doing what you think it is.
Secondly, the viewDidAppear will only be called when the view, well, did appear. Is it showing up on the screen? Where are you telling it to be displayed? You need to either add the view controller's view as a subview of another view, or pushed onto a navigation controller stack, or pushed as a modal view.
viewDidAppear:(BOOL)animated only gets called when the view is shown by UINavigationController or UITabBarController. If you add a view controller's view to a subview (such as a scrollview or what have you), it won't get called. You would think it would but you'd be wrong. Just got bit by this myself.
Further to #Genericrich's comments, you can manually call viewDidAppear after you put the subview in yourself.
[self.view addSubview:theViewController.view];
[theViewController viewDidAppear:FALSE];
This worked for me. Hope it helps someone else.
I'm returning YES in my view controller's shouldAutorotateToInterfaceOrientation function, and I can see using breakpoints that YES is being returned, however the willRotateToInterfaceOrientation method isn't being called, and nor is any other rotating method. It seems like after returning YES nothing happens!
Any ideas?
Mike
Is this views viewController a subview of some other root view controller thats not a navigation controller? if so then the call does not propagate to the subviews controller, so that might be why your view isnt rotating.
I have a similar problem and saw Daniel's answer however I can't find any confirmation of this in the developer documentation. Not that I don't believe the answer but I don't really understand why the orientation call does not propagate.
Someone gave me a trick that works using something like this:
[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(detectOrientation) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
One thing to look for is I found if I had UIPopoverController called in [UINavigationController viewDidAppear], then willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are not called. It looks like UIPopoverController being modal blocks the rotation method calls.
Yes me too. Ok, it won't get called in a sub-viewcontroller - have to pass it down. Can deal with that. And the notification idea works well except that you only get "did..." not "will..." (afaik) and anyway it's a messy solution to a problem which shouldn't be there.
My mistake was to call [super loadView] in my loadView. Not supposed to do that. When I removed [super loadView] and alloc'd the view myself willRotateToInterfaceOrientation started working.
What's really weird is that the [super loadView] was in the sub-viewcontroller and the event wasn't even reaching the top one...
If you are not receiving callbacks on willAutoRotateToInterfaceOrientation in any view controller, add the view controller as your root view controller's child view controller.
For Eg; say self.viewController is your root view controller and childViewController is the view controller in which you want to get auto-rotation callbacks, add the following line of code;
[self.viewController addChildViewController:childViewController];
Actually, adding as the child view controller to any view controller which gets rotation call backs will work too.
Hope it helps.
Apple documentation
At launch time, apps should always set up their interface in a portrait orientation. After the application:didFinishLaunchingWithOptions: method returns, the app uses the view controller rotation mechanism described above to rotate the views to the appropriate orientation prior to showing the window.
So if you are using a TabBarViewController be carefull to set up the selected view in the application:didFinishLaunchingWithOptions: method.