I'm doing an app where I have a GLES view which I don't want to be auto-rotated, and UIKit views on top of that, which do need to be auto-rotated.
Apparently this is not something you're supposed to be doing, as I can't find any good documentation on it. However I'm totally sure this is exactly what I want.
I've found a solution, but it feels hacky:
Create window.
Create some auto-rotated UIViewControllers.
Add their views to the window.
Create an OpenGL ES view controller that's not auto-rotated.
Add its view to the window.
Call bringSubviewToFront: for the auto-rotated views.
Give yourself a high-five, while trying to ignore the nagging feeling that this is actually a nasty hack.
Anyone know of a better solution? NB: I'm absolutely positive that I want to auto-rotate the UIKit views and not the GL view.
Thanks for your time, much appreciated. =)
I've come up with a different strategy for a similar situation. In my case I allow all the views to rotate, but then use a transform to bring the view I want to keep steady back to its original orientation. This works because the "non-rotating" view is square, but with a bit more math it could work for other shapes as well. Here's what I put in my willRotateToInterfaceOrientation:duration:
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortrait:
steadyView.transform = CGAffineTransformIdentity;
break;
case UIInterfaceOrientationLandscapeLeft:
steadyView.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
case UIInterfaceOrientationPortraitUpsideDown:
steadyView.transform = CGAffineTransformMakeRotation(M_PI);
break;
case UIInterfaceOrientationLandscapeRight:
steadyView.transform = CGAffineTransformMakeRotation(3*M_PI_2);
break;
}
The steadyView is s subview of the controller. This seems to do the trick.
There isn't a good way to do this any better than what you proposed.
Are you sure you don't just want to rotate the OpenGL views too? If your application is limited to the ES 2.0 devices and later on iOS 4.2, there is no longer any performance penalty to using UIViewController rotation for OpenGL views.
Wouldn't it be simpler to just draw your OpenGL content sideways when rotated? Just apply a rotation in your renderer to undo the rotation of OpenGL view.
You may see an unacceptable jump back to the original rotation, but you could address that easily. You could animate the return rotation, once the view is finished rotating. Or you could override willAnimateFirstHalfOfRotationToInterfaceOrientation:duration: and willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration: to animate the content staying in place during the interface rotation.
So I found out how it works behind the scenes; Frogblast is right, there isn't any substantially better way to do it.
What happens is that when a view gets added to a window that has no subviews, that view's controller is automatically the "rotation authority"; If that view controller returns NO from shouldAutorotateToInterfaceOrientation, no other views will rotate either. However, subsequent views (with controllers) added to the window are still allowed not to rotate, even if the "rotation authority" does rotate.
Unclear to me why they've made it like this, but there you have it.
Related
I've found a problem in my OSX app that I think is a Mavericks bug.
I have the following hierarchy:
NSView
->NSScrollView
->NSClipView
->NSTableView
When the scroll view doesn't contain any records I'm creating an overlay view as a subview of the NSView, and positioning it above the other subviews. I do this using:
[containerView addSubview:overlay
positioned:NSWindowAbove
relativeTo:nil];
FYI, the overlay view is a custom NSView subclass with a drawRect to draw the overlay itself.
On Mountain Lion this works fine, but on Mavericks the overlay does not appear. Googling around I think this is because the overlay is not being positioned above the other sibling views.
I found these links for reference:
Display Order Messed Up
Maverick Issue When Adding Subview on NSView (Stack Overflow)
The second link suggests the following code to fix the issue, which it appears to do, but as I don't have any layer-backed views in my app this feels a bit off:
[overlay setWantsLayer:YES];
Can anyone suggest a workaround for this problem other than the one suggested?
EDIT: I've found that if I put an Xcode breakpoint in the original code (without the workaround) at the point after the subview is added the subviews get added correctly, and my overlay view is displayed. If I remove the breakpoint, the overlay view is no longer displayed. Does this behaviour indicate anything?
Thanks
Darren.
It looks like you've been bitten by overlapping views:
Note: For performance reasons, Cocoa does not enforce clipping among sibling views or guarantee correct invalidation and drawing behavior when sibling views overlap. If you want a view to be drawn in front of another view, you should make the front view a subview (or descendant) of the rear view.
from Apple's Working With A View Heirarchy.
As you note, the common recommendation to fix this is to is to set a layer. However since 10.4 you can hide a view using setHidden:, see Hiding Views in the above reference. This may solve your particular problem as your overlap appears to be total (you don't want to see parts of both views, only one of them).
HTH
I'm trying to make a large UIScrollView with various UI elements on it which is larger (wider) than the iPhone's screen.
I'd like to be able to lay those elements out in a UIView, and then load that UIView into the UIScrollView. This way, I can clearly see in Interface Builder what I'm doing with that large view.
How can I do this, with Storyboards active? I'm trying to do something similar to the #2 example here, but using Storyboards:
http://agilewarrior.wordpress.com/2012/05/18/uiscrollview-examples/
See if this tutorial from Ray Wenderlich helps, which goes into pretty good detail about UIScrollView :
http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content
If you're doing layout programmatically, please take note of this important comment:
You might ask why we don’t do all of the above in viewDidLoad, and you’d be right to ask. The reason you can’t is that the view size isn’t definitely known until viewWillAppear:, and since you use the size of scrollView when calculating the minimum zoom, things might go wrong if we do it in viewDidLoad.
Good luck!
I'm having difficulty figuring out how to deal with different device orientations for one screen of my iPad app. Here's my situation:
~ All of this screen is rotating perfectly using springs and struts except for one label. The problem with this label is that I want it to move in an unorthodox manner (diagonally), thus springs and struts (or resizing masks will not work).
~ The way that I'm considering doing this is as such:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)newInterfaceOrientation duration:(NSTimeInterval)duration
{
if(UIInterfaceOrientationIsLandscape(newInterfaceOrientation))
{
self.myScreenLabel.frame = CGRectMake(600,0,400,100);
}
else {
self.myScreenLabel.frame = CGRectMake(...//something); }
}
I would also put a check in viewDidLoad with similar logic. If in portrait mode, put label at... else put label at....
I think that this will work; however, I'm kinda wondering if there's a better designed way to do this. The method above, has hard-coded numbers everywhere; thus, is there a better way to do this? Also, this method does not take advantage of the fact that I have my label positioned perfectly in the storyboard for portrait mode and it's just landscape that I need to change.
Any suggestions on better design?
First question, I've implemented behavior on orientation change with your approach with no bad results, if you are okay that the method is triggered right before rotation happens. Alternatively, you can use NSNotifications to add a trigger on orientation change:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:)name:UIDeviceOrientationDidChangeNotification object:nil];
Then add a method like:
- (void) didRotate:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft) {
//your code here
}
}
Regarding your second question, positioning frames like that is scary, but I think you realize that. Instead, position the element related to something else, just like a strut (which would say, always position the widget relative to a distance between it is strutted against). So use the window's frame, the view's frame, or some other UIView subclass in the view to position the object against, rather than absolute numbers.
A couple of thoughts:
If iOS 5+, you might want to use viewWillLayoutSubviews, probably even more important given the comments in the iOS 6 release notes re modal views and the screen reorientation methods. This also has the advantage that your code is in one spot. Since I still support pre 5 (though I won't for much longer), I actually have dynamic checking of iOS version and invoke my viewWillLayoutSubviews from the other methods if pre 5.0, otherwise I let viewWillLayoutSubviews just do the heavy lifting.
If your landscape orientation is radically different, you probably want to pursue Creating an Alternate Landscape Interface. I've never done this, but it seems like it's up your alley. There are also postings on SO about using different NIBs for different orientations. Not sure this makes sense in a storyboard environment, though.
For these controls that we occasionally have to move around or resize based upon screen dimensions, I think most of us do it with viewWillLayoutSubviews. That's the entire purpose of that method (though I generally use it for labels whose height changes based upon the data contents and the screen width). I had never stopped to think that there might be another way. If you only have one control that you're moving around as you change your orientation, maybe you could create two additional, hidden controls, one for where you want your visible control in portrait, the other hidden control for where you want it in landscape (and in IB, you can toggle your view orientation to facilitate the layout of controls). Then in your viewWillLayoutSubviews set your frame of your actual visible control to be the frame of one of those two hidden controls (depending upon orientation, of course). That gets you out of the business of hardcoding frame coordinates in your code, and take advantage of the benefits of IB. This whole suggestion might be too cute by half, but it's an alternative approach if you don't want to go through the effort of my second point.
So I'm working on an issue I have when trying to do some simple animation using CAKeyframeAnimation, and I believe my problem is more related to not fully understanding how NSWindow, NSView, and CALayer work together.
I have two main objects in question. MyContainerWindow (NSWindow subclass) and MyMovableView (NSView subclass). My goal is to be able to animate MyMovableView back and forth across the screen, while maintaining the ability to click on anything through MyContainerWindow unless you are clicking on wherever MyMovableView is. I am able to accomplish the first part fine, by calling -addAnimation:forKeyPath: on myMovableView.layer, and everything is great except I can't click through MyContainerWindow. I could make the window smaller, but then the animation would clip by the bounds of the window.
Important points:
1) MyContainerWindow is initWithFrame to [[NSScreen mainScreen] frame], NSBorderlessWindowMask, defer no, buffered
2) I setWantsLayer:TRUE to MyMovableView
3) MyContainerWindow is clear, and I want it to be as if there wasnt a window at all, but need it so I have a larger canvas to animate on.
Is there something obvious I'm missing to be able to click through an NSWindow?
Thanks in advance!
My solution in this scenario was actually to use:
[self ignoresMouseEvents:YES];
I originally was hoping to be able to retain the mouse events on the specific CALayer that I'm animating, but upon some further research I understand this comes with the cost of custom drawing everything from scratch, which is not ideal for this particular project.
I have made a very simple web browser app using a web view. Now I need to get the app so that when the iPhone is rotated, the text of the page is rotated as well.
How do I do this?
I am very confused by the auto-resize dialog, so it is possible I have done something wrong there.
Any help would be appreciated!
I think you sholud rotate UIWebView widget, not its contents. Contents should rotate as well. To support rotating add following code to your view controller:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Rotated widget might look different then expected. Adjust struts and springs in Interface Builder.
I think you need to give us some sample code in order to determine what goes wrong. It is as Jacek says, the only think you should need to do is to support auto rotation on the UIWebView itself. The content should be rotated automatically.
I think you are confused by device orientation and view frame.
In most cases UIViews do change with respect to the orientation change. But to clarify - it is not because of the orientation change, but the layout change.
Only UIViewControllers need to consider device orientation - UIViews do NOT. When the device orientation changes, the UIViewController captures the event from its instance methods:
– willRotateToInterfaceOrientation:duration:
– willAnimateRotationToInterfaceOrientation:duration:
– didRotateFromInterfaceOrientation:
The UIViewController then re-layout the views - leading to reframing of the UIViews. In many cases, iOS can helps you in simplifying the relayout process by setting the UIViewAutoresizeMask. For example:
myWebview.autoresizeMask = UIVIewAutoresizeMaskFlexibleHeight | UIVIewAutoresizeMaskFlexibleHeight;
implies that when webview's superview changed its bounds, the webview will change accordingly.
As a summary, UIView only takes care of its frame / bounds etc.