viewDidLayoutSubviews: and UIInterfaceOrientationIsPortrait - ios

I have to ask this question because Im utterly puzzled by the mechanics of this. I am trying to check for portrait orientation, and while it works fine in viewdidload for my case, because I only need to make the call once, I still wondered what to do if I needed to check for portrait every time the device was turned back to portrait.
I have the following code
-(void)viewDidLayoutSubviews{
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
self.adviceLabel.preferredMaxLayoutWidth = [[UIScreen mainScreen]bounds].size.width-25;
[self.adviceLabel setFrame:CGRectMake(0, 0,[[UIScreen mainScreen]bounds].size.width-25 , 103)];
NSLog(#"portrait");
}
First of all "portrait" gets loved 3 times, which I suppose is because I have 3 views inside my VC, duh. But then when I start to turn the screen around, it actually starts to log "portrait" consistently when I "turn to landscape" meaning it logs after I hit landscape mode. Is there any meaning behind this? I hate leaving a subject without understand. Thanks in advance!

Use following code for your goal:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// do something before rotation
}
or
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
// do something after rotation
}
-viewDidLayoutSubviews method is called every time when view and it's subviews change their frames. This is not good practice to check orientation every time view changes it's frame.

Related

UIButton doesn't work after rotate to landscape

The following is a snippet of the code being used to set up the subview layout on device rotate (I am using the same code for initial setup of the views):
(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if(screenBounds.size.height >= 1024) {
[myButton setFrame:CGRectMake((screenBounds.size.width/3), (screenBounds.size.height/100)*81, (screenBounds.size.width/3), (screenBounds.size.height/100)*16)];
}
else {
[myButton setFrame:CGRectMake((screenBounds.size.width/3), (screenBounds.size.height/100)*81, (screenBounds.size.width/3), (screenBounds.size.height/100)*16)];
}
}
What is happening is myButton is being redrawn properly, but trying to interact with the button does nothing. If I go back to portrait, the button is able to be pressed after redraw. If I start in landscape, the button works; but if I later rotate away and then back to landscape, button no longer works. This has me stumped.
So, it's very difficult to tell what's going on in your view without being able to see your storyboard, view hierarchy, etc. However, Xcode has made troubleshooting these types of issues much easier with Live View Debugging.
The first thing you can try is turning on view frames. This will allow you to see the frames and bounds for each control. Most likely what you will see is that your button's frame is in one place, but the bounds is someplace else. You can turn on view frames via Debug -> View Debugging -> Show View Frames.
If you need a more comprehensive overview of your hierarchy -- for example, maybe another view is covering the button -- Xcode now has a really cool feature to capture the view hierarchy. You can manipulate the hierarchy in 3D, filter for certain controls and more. This will allow you to visually inspect the hierarchy at run-time and see what might be going wrong with your button. Debug -> View Debugging -> Capture View Hierarchy.

iOS8 touch position is limited in Landscape, as if window is Portrait on one side?

I'm having a bizarre problem here with iOS8. I've been googling and bashing my head against a wall for a couple of days with no luck. I'm not exactly sure how to even explain it, but here goes...
Apps have been running fine under iOS7, but now compiling using xCode 6/iOS8 I'm having a few strange orientation problems. I'm not using a xib file, but instead creating a window and view programatically. I'm running OpenGL in the view, and then handling everything else inside OpenGL. So, I'm collecting touches and passing them to my GL routines. All has been fine forever, til now.
Now with iOS8 it seems as if the "touch window" is rotating itself so that touches in Landscape are limited to one side of the screen, as if the display window is Landscape, but the touches are all within a Portrait window that is set to on one side of the screen.
What seems bizarre is that touches come through to the view all over the screen as normal, but the Y value stops at 320 and goes no further. The X acts normally. If I turn the device to Portrait (the view controller does not auto rotate), it all works, but in Landscape it switches to this strange behaviour.
I've tried all I can think of without any changes, and am not sure where to begin to work out why this could be happening. Any advice would be greatly appreciated.
On iOS8, UIScreen is now interface oriented. In some (or should I say all of) libraries before iOS8, some checks were done to adjust that lack and swap width and height when the application was in landscape.
You should look at some code that makes some rotation and remove it.
-[UIScreen bounds],
-[UIScreen applicationFrame],
Status bar frame and
Keyboard frame are now interface-oriented.
(cf. WWDC2014 video - view controller advancements in iOS8 # 50:41)
In our case.
Force to rotate with view's transform caused that problem.
We removed that code, rotate with
+ (void)attemptRotationToDeviceOrientation
and problem solved.
We called that method at our forceRotate method.
-(void)forceRotateLandscape {
...
UIViewController *vc = [UIViewController new];
[topVc presentViewController:vc animated:NO completion:nil];
[topVc dismissViewControllerAnimated:NO completion:nil];
supportedOrientations = UIInterfaceOrientationMaskLandscapeRight;
[UIViewController attemptRotationToDeviceOrientation];
...
}
My guess is that whatever view owns the gesture recognizer isn't getting resized during the rotation for one (or more) of the following reasons:
It uses auto layout and isn't pinned to its superview
It uses springs and struts and doesn't have the correct UIViewAutoresizing mask
It lives in a subview that isn't getting resized
If you're using auto layout, try this in your view controller:
- (void)viewDidLayoutSubviews
{
// For each subview with its own auto layout:
[self.mySubview layoutSubviews];
// ...
[super viewDidLayoutSubviews];
}
Ok, I've found the issue on my side (I'm the bounty giver).
We were probably using some kind of hack in our App to manually handle the rotation of our views, because we were returning 0 to the - (NSUInteger)supportedInterfaceOrientations in our rootViewController.
- (NSUInteger)supportedInterfaceOrientations {
return 0; // BOOOOOOO, even forbidden by documentation
}
Then we manually managed our internal views by listening to the UIDeviceRotationChanged notification, rotating them views, and asking the statusBar to rotate as well.
Returning something not 0 to this supportedInterfaceOrientations made the "touch" layer rotate along with the rest.
But now we have to make the code evolve to use iOS rotation instead of our hacky layer.
Also, after investigating more, especially on iPad, you have to use a Storyboard-based UIWindow.
Using a XIB-based window will result in its dimensions being wrong when the app is launched while in landscape. This leads to a mountain of buggy frames and messes up badly with our layout.
I had the same problem. In iOS7 all worked fine, in iOS8 it was like the "touch-view" was still in portrait.
I just replaced the main xib with a storyboard, no other change (i've tried a lot playing with screen bound and frame without any results).
Just try it out, let me know if it work also for you. Good luck.
I have the same issue with second window in landscape. It refuses to display itself on the full screen and also to receives touch events on the cut part of the screen. The only solution that I found so far is to enable support of the iPhone6/6+ native resolutions by adding required launch screens. If you do not support these resolutions in your app that might be the same problem.

Tilemap changing size when landscape

I'm working on a little App, where I load a tilemap ( JSTilemap ) into sprite kit.
This all works fine except that the tile map looks normal and fine in portrait but when I flip to landscape it seems to blow up. I have not added any code just imported JSTilemap header and this code initwithsize.
self.map = [JSTileMap mapNamed:#"tilemap.tmx"];
[self addChild:self.map];
I have also downloaded a simple project with a tile map and when I rotate the size stays the same, only that if i try to mimic the code my tilemap still seems to blow up when I rotate it.
Is there some setting to restrict this that I am missing?
You should use -(id)initWithSize:(CGSize)size instead of -(void)didMoveToView:(SKView *)view
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
JSTileMap* tiledMap = [JSTileMap mapNamed:#"level1.tmx"];
if (tiledMap)
[self addChild:tiledMap];
self.userInteractionEnabled = YES;
}
return self;
}
Also in GameViewController (if you are using default template classes) you should change scene initializing with sceneWithSize
/*
// Create and configure the scene.
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
*/
// Create and configure the scene.
SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
I assume by "blow up" you mean stretch and/or zoom inappropriately? Try changing your scaleMode on your SKScene to be SKSceneScaleModeAspectFill.
Just so you know, when running your application the iPhone always begins in Portrait mode. This has nothing to do with Sprite Kit. It's about general iOS lifecycle & view issues. Skewing content or loosing content entirely happens because of this. When making a game usually Portrait or Landscape is used. Using them together is pretty rare. To understand why your content is being "stretched" write the following code into your SKScene's initWithSize method:
NSLog(#"In initWithSize, at %0.f wide and %0.f high", size.width, size.height);
Fire the game up & look at the NSLog message. Even though we think we started in Landscape mode (that is if you tilted your phone & waited for the game to load or did that a bit later), the NSLog message displays "In initWithSize, at 320 wide and 568 high" (I'm using an iPhone 5S). That means the scene is being rendered in Portrait mode even if we selected Landscape mode in the Targets/Deployment Info section. We want the width to be more then the height (568 wide and 320 high)! But why is all of this happening? The issue is internally. The iOS app life cycle begins EVERY application in Portrait and ONLY switches to Landscape during the loading process. So the Storyboard loads, it loads that initial View Controller and it still thinks it's in Portrait orientation. This view controller will automatically load the View object (for Sprite Kit that's the SKView). We're still in Portrait orientation. The View object loaded and that will cause viewDidLoad to be called back in the View Controller. We're still in Portrait orientation. But in the viewDidLoad method that is where we create our scene (SKScene) passing in the current height & width. Still Portrait orientation. All of this is taking a fraction of a second, but it's only after this that the application realizes it's supposed to be in Landscape and the View Controller switches to Landscape taking the View with it. Depending on your settings, what the Scene is going to do is trying to react to this by scaling up to fill the available space and we're going to loose content because internally it's still in Portrait orientation...just scaled up. How do we fix this? Our scene is being created in viewDidLoad, but when this is happening the application DOES NOT yet know we're supposed to be in Landscape mode. So we need to create our scene, but we just need to do it a bit later in the process - after the iOS application life cycle has realized we're in Landscape mode. There's a couple of different methods to do this. The one I like is viewWillLayoutSubviews. It is another built-in View Controller method. It gets called automatically after viewDidLoad and after the shift to Landscape mode. So if we post-pone our scene creation by a few milliseconds until we're in viewWillLayoutSubviews method then we can create it with the right orientation. So copy & paste all the code in viewDidLoad (apart from the call to [super viewDidLoad];) and put it into viewWillLayoutSubviews. There is only 1 more issue now. It is possible viewWillLayoutSubviews will be called a couple of times or at least more then once during the lifecycle. That's ok. We just don't want to create the scene several times. So once we have grabbed hold of the view we're going to create an if statement to check if there's a Scene object in that view. If not, we'll create it and return it. Otherwise, we don't need to do that. Here's how all of this code looks inside the View Controller that creates our Scene.
//Method can be deleted (It's called behind the scenes regardless).
-(void)viewDidLoad
{
[super viewDidLoad];
}
//Called AFTER viewDidLoad & after the view shifts to landscape (which is what we want).
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews]; //Don't really need to call this method (but a good habit just in case Apple ever changes that).
//Configure the view.
_skView = (SKView *)self.view;
_skView.showsFPS = YES;
_skView.showsNodeCount = YES;
//_skView.showsPhysics = YES;
//viewWillLayoutSubviews might get called several times & that's ok. But, we don't want several GameScenes to be created. Therefore...
//Is there a scene object in that view? If there ISN'T, we'll create it.
if (!_skView.scene)
{
//Create & configure the scene.
SKScene *sceneGame = [MainMenuScene sceneWithSize:_skView.bounds.size];
sceneGame.scaleMode = SKSceneScaleModeAspectFill;
//Present the scene.
[_skView presentScene:sceneGame transition:[SKTransition fadeWithDuration:0.5]];
}
//If there is, we don't need to do that.
}
Now go ahead and run this. Everything should look good now. The NSLog message should now say "In initWithSize, at 568 wide and 320 high". Hurray!

UIView switching animation for landscape and portrait scene in storyboard?

I'm trying to design different layouts for my view controller. After googling, I've got this answer from SO. Basically, the answer instructs me to setup 2 scenes, one for landscape and one for portrait, when the device rotates, hide one and show the other one.
And my question is what if I want to add a sweet animation for the rotation process? For example, since I can setup the same view on different scenes, can I set the motion path for these views and disappear? Or, if it is easier, can I add some rotation and fade out animation?
I'm quite green with Core Animation, so a detailed explanation is very useful for me. Big big thanks in advance!
There are a few options here. The main methods you are looking for to do any of this are:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration;
and
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
If you want to move the elements around the screen while the rotation is happening you will be best off changing the frames of the elements rather than creating a separate view file. Using any motion animation would require some mapping between the two and I have never heard of anything like this before. Again, if you just change the frames though, you can preform basically any screen movements you would like (for views and subviews).
To do the fading you could just throw an animation block in the first method:
[UIView animateWithDuration:duration
animations:^{
// set alphas for old and new views here.
}];

Changing iPad orientation, rotating statusbar, toolbars, but not the main view

I’m trying to set up an iPad test application, window-based, where I have a single view controller and a single view. When I rotate the iPad, I want the orientation of the toolbar to change, but not that of the view itself. For example, a sort of background view that you work in is fixed to the device, but the status bar and toolbars rotate around it. This would enable the user to work the view from all angles, but always with a correctly-oriented toolset.
A beautiful implementation of what I want can be found in the Brushes for iPad app, where the painting’s orientation is locked to the device, and the toolbars rotate around it. I think other painting apps do the same thing.
I’ve been trying to figure out how to do this, but after exhausting many many other questions here concerning orientation, I’m still at a loss.
Could anyone point me in the right direction towards a neat solution? A particular combination of autoresizes for the autoresizeMask? Countering the rotation animation with another one in the opposite direction? Using multiple concurrent view controllers, one for the rotating views and one for the non-rotating ones?
I’d very much appreciate it,
(Edit: Attempted to clarify the question, after Olie’s comment.)
To prevent rotation, you'd put this in your view controller's .m:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
But you say you still want the view frame to resize in response to the rotation. I haven't had a need to do this myself, so I'm not sure if it's sufficient to just set the autoresizingMask to have flexible width and height; you may also have to implement didRotateFromInterfaceOrientation: and use setNeedsLayout and/or resize the view manually.
I had a bug that did this a while back -- I'm pretty sure that what you're asking will get you a HIG-violation rejection from Apple. However, I'll give a shot at remembering what the problem was. I'm pretty sure it was something like this:
I had a tabbarViewController that said "I orient to any orientation."
One of the tabs was a regular-old UIViewController that said "I only do LandscapeLeft & L-Right"
When you rotated, the inside (UIVC) stayed put, but the outside (TabVC) rotated around things.
I might have some of the details backwards or otherwise convoluted, but the general ideas is: stacked VCs, not all one VC.
Good luck!
To the extent I have worked with I cannot see any simple answer to your question. What about rotating everything (tabbar, nav and status bar, your view controller) and then redrawing the content of your view controller in "old coordinates" so for the user it will look like it's not rotated?

Resources