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.
Related
I'm trying to get my NSScrollView (and thus a contained NSOutlineView) to use a blurred NSVisualEffectView with behind-window blending effect.
I've successfully made NSVisualEffectView the container view and placed my scroll view as a subview. This seems to work fine (as long as I make all my table cells, table, nsscrollview etc transparent).
However I've now turned 'Reduce transparency' ON under Accessibility options and all of a sudden I have a black background behind my NSScrollView. I tried subclassing the visual effect view in order to override the drawRect method so that I can draw my own background, but I've just learned this isn't possible or recommended.
How do I detect that Reduce Transparency is ON and how do I make my scrollview opaque dynamically?
Took me a while to find it, but there are a couple of new methods on NSWorkspace that you can use to find out about the preferences for OS X Yosemite’s new accessibility features. -[NSWorkspace accessibilityDisplayShouldReduceTransparency] is the one you want.
By Listening for NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification you can find out when that preference changes. Note that you’ll have to register for that notification on the correct NSNotificationCenter, that is [[NSWorkspace sharedWorkspace] notificationCenter].
It seems as for now I've ended up overriding a parent NSView that contains everything and set a background color for that. This way, when Reduce Transparency is ON, the NSVisualEffectView becomes transparent and the color I end up getting is the one visible below it. This seems to work fine for now.
Despite we have SwiftUI nowadays, in classic Cocoa you can still subclass a custom NSScrollView and use ...
-(NSColor *)backgroundColor {
return NSColor.clearColor;
}
-(BOOL)drawsBackground {
return NO;
}
or set properties of your NSScrollView accordingly if you don't want to subclass like...
yourscrollview.drawsBackground = NO;
yourscrollview.backgroundColor = NSColor.clearColor;
this forces your view to show what is below, and with it also the blurEffect or opaque color of your View or Window that is enclosing your NSScrollView.
This solution has the benefit that you do not have to observe the Workspace for some Notification or VibrancyEffect.
I need to know if there is a way to tell a iOS7 device to set a views orientation without the device being rotated. Some way in code to trigger the device to calling the code that tells it which way to display the view.
If the device is in landscape and remains held in landscape orientation while a certain change happens I want to force a change to show the view in portrait orientation, at which point the user would need to turn the device to look at it properly. I'll explain why below
Looking at my app might make my description clearer - it is free to download
I have a number of view controllers (embedded in navigationControllers) and only one of them needs to be rotated into landscape and then only under certain conditions.
Solutions here on StackOverflow seem to be to make a category on UINavigationController giving it shouldAutorotate and supportedInterfaceOrientations methods and then use those methods in the individual viewControllers to block or allow rotations.
This has worked for me .... however
On the one view controller I wish to rotate , I don't want it to rotate all the time.
This view controller is the diveSiteDetailsController, (if you have downloaded the app you need to select dive sites on the first page then click the '+' to see it). It has a UISegmentedController and 4 subviews (3 tableviews and 1 other UIView). The current version on the App Store works fine now i've solved this - but looking at it may help you understand my issue better).
On diveSiteDetailViewController the UISegmentedController is used to switch between the 4 subviews.
All the subviews are used to enter data about the same dive site but as there is a lot of potential data, I have broken it into logical chucks each of which is a subview - location, data (depths,currents, visibility), type of environment and notes.
The .hidden property of each subview is used to make them appear and disappear.
I only want the second subview to rotate (the data view - it has some sliders on it that are easier to work with if in landscape).
restricting this rotation is easy - iI achieved it like this
- (NSUInteger)supportedInterfaceOrientations{
if (self.dsDataRangeSlidingTV.hidden) {
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAllButUpsideDown;
}
Now the view will only rotate to landscape when the data table view is displayed.
However, once in landscape, if I chose a different subview with the UISegmentedController then they are, obviously, shown in landscape also as the iOS device hasn't done a rotation. This is the situation I am trying to avoid.
Rotating the iOS device will return those views to portrait as expected but i need to trigger the device to to reevaluate its display when I use the UISegmentedController to switch from the data subview to another subview and its that triggering that I don't know how to do.
any suggestions greatly received.
Heres a workaround that is working for me
I've added the following few lines to the end of my method that responds to the UISegmentedControl being tapped.
UIViewController *aDummyController = [[UIViewController alloc]init];
[self presentViewController:aDummyController animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];
adding a new viewController and popping it off triggers the rotation . This is a kludgey way of achieving what I wanted.
I found the solution in this post
Is there a documented way to set the iPhone orientation?
all credit to Josh who although not the accepted answer is the one that 99 people currently have up voted.
I still have a bug in that, if I were holding the device in landscape (although the display is portrait view) whilst on the screen that segues into the diveSiteDetailsController then the initial view the diveSiteDetailsController display will be in landscape.
To get around this I created a Bool property called notThisTime on the diveSiteDetailsController and set it to true in the prepareFor Segue on the viewController that called it.
i then did changed supportedInterfaceOrientation to
- (NSUInteger)supportedInterfaceOrientations
{// DLog(#"Running %# '%#'", self.class, NSStringFromSelector(_cmd));
if (self.notThisTime){
return UIInterfaceOrientationMaskPortrait;
}
if (!self.dsDataRangeSlidingTV.hidden) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
then at the end of the ViewDidLoad method I added
self.notThisTime = NO;
I would still love to hear from anyone with a suggestion how better to handle this. pushing and popping a dummy view to get the iPhone to do an orientation check seems like a work around for something that should just be available as a standard method call.
One final Note - the iOS simulator does not like this - you need to check on the device - it sometimes tries to draw the iPhone container in landscape while the screen is drawn vertically - however it does work fine on the iPhone
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'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.
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.