UIScrollView dragging affected by UIWindow transform rotation - ios

I'm adding a video out function to my iPad app and have run into a problem with my UIScrollView. To get the proper view orientation on the external monitor, I've rotated the UIWindow based on the current interface orientation (e.g. - mirroredScreenWindow.transform = CGAffineTransformMakeRotation(- M_PI * 0.5);).
The problem I've run into is that the ScrollView dragging seems to be affected by the UIWindow transform. If the UIWindow is rotated 90 degrees, horizontal drags scroll the view vertically and vice versa. Is there any way to correct this?

I got a response from Apple Dev Support that said essentially, "Doing a transform on UIWindow will confuse the internal objects and should never be done."
Looks like I'll just have to create a modified ViewController that lays out all of my UI elements specifically for the format of the external screen, rather than just transforming the view controller that already works correctly on the iPad screen.

Scroll views seem to maintain their own hidden transform. You can try examining it, and see if there's any difference between when instantiating and adding the scroll view before or after modifying the window transform.

Related

Finer grained UIView rotation in a UIViewController

I have a UIViewController that overlays controls on a view presenting what the camera sees. I have a couple of scenarios I would like to allow.
For the iPad, I want to keep the controls on the right most edge of the device, by your right thumb, no matter what the device's rotation. The controls should rotate their content so that their top is always upwards (away from the ground). I don't want the camera view to rotate at all, because that would just be silly – its position & size should stay the same and its contents shouldn't rotate either.
For the iPhones, I want to keep the controls at the bottom of the device's screen, by to the home button, wherever the home button actually is. The controls should rotate their content so that up is always pointing upwards. Again, I don't want the camera view's frame or content to take part in any view rotation animation at all.
I'm using auto-layout.
I'm wondering if there is any way to describe some or all of this in a storyboard. In particular, it'd be great to be able to describe that some view positions need to autorotate (ie, the controls, on iPad), but that other views don't (the camera view).
A question from 2011 indicates this wasn't possible at the time, but perhaps things have moved on since then? If it's not directly supported, can you suggest an approach and are there some sensible places to be hooking in to autorotation to achieve this?
Ok, this isn't quite a complete answer, but I tried a few things which look promising.
First, you can create a separate set of constraints for portrait vs. landscape using the size specifiers: landscape is w Regular, h Any; portrait is w Any, h Regular (I think -- double-check these) This is accessible via the pop-up control in the bottom-center of the storyboard view. By installing different constraints for portrait and landscape, it should be possible to scale the width and height of your controls' container view so it appears to be in a constant position w.r.t. device orientation; in other words, the container doesn't actually counter-rotate -- it scales so it effectively looks like it has counter-rotated.
I got this close to working. It looks like it's doing the correct thing in the storyboard view, but when I actually run it, I get debug messages about conflicting constraints. Not sure how to fix this, but maybe play with the constraint priorities? That sometimes helps.
A second thing I (partially) tried was creating a custom container view class which counter-rotates itself to the correct position based on the device orientation (in the UIDevice class). You implement this by overriding layoutSubviews. For each orientation, you define a transform which puts it in the correct position, and set the view's transform property.
Another possible solution is to override updateConstraints in your view controller and add/remove constraints to position/scale your container to the correct place for each orientation.
For all of these, the idea is that you "force" the container to be in the correct place, but leave the subviews (the actual controls) alone. The controls should do the right thing if their constraints are independent of the specific orientation of the container view.
So, those are some ideas anyway... if they lead you to an actual solution, could you post it? I anticipate having a need for this myself.

GLKViewController orientation animation

I have a layout, where the screen is divided into 2 parts: UIView at the top and GLKViewController at the bottom. The problem is that when the screen orientation changes the
GLKViewController part is rotated and gradually stretched out until the animation finishes, at which point a new unstretched frame will be drawn.
Is there a way to disable the automatic rotation animation for the GLKViewController, so I could animate it manually by manipulating the modelview-projection matrix?
If you're just looking to deal with the stretching effect, you don't need to replace the whole orientation/rotation system. Depending on how much you're making use of device orientation in you're app's UI, you're likely to cause yourself more maintenance headaches by reimplementing orientation and rotation. (If you need to do more than just work around the stretching effect, the other answers are still good.)
Your view is drawing itself during the rotation animation, so all you need to do is ensure that it's drawing itself in a way that matches its intermediate size during the animation. The rotation animation is handled by Core Animation, so you use its presentationLayer method to access the transitory state during the animation. For example:
CALayer *presentationLayer = [self.view.layer presentationLayer];
CGSize layerSize = presentationLayer.bounds.size;
float aspect = fabsf(layerSize.width / layerSize.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);
Run this snippet as part of your update/draw loop — note this might mean moving calculation of your projection matrix into the update/draw loop if it isn't there already. (You might also want to make sure this snippet only runs every frame if an orientation change is in progress.)
To see the animation in progress and make sure it's working right, use the "Toggle Slow Animations" command in the iOS Simulator.
(Credit where due: code is from this answer.)
GLKViewController is a subclass of UIViewController. UIViewController has a method willAnimateRotationToInterfaceOrientation:duration: You can override it and configure the animation of view.
You can find more details in the documentation: UIViewController Class Reference under the Responding to View Rotation Events section.
Depending on what you want to achieve, you would do different things to disable automatic rotation.
If you do not need rotation at all, disable it at project level. In Xcode, use the navigator to select the project (top item of the file list) and visit the "General" section of the target app. For device orientations, untick everything but "Portrait":
If you need to disable it for some "screens" and not for others, you need to disable rotation at the UIViewController level. UIViewController was initially designed to take up the whole screen area, so if you are using a GLKViewController that manages GLKView that covers the screen just partially, it is most probably inside another view controller. You need to subclass the parent view controller and add these methods:
// From iOS 6
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
// iOS 5
- (BOOL)shouldRotateToOrientation:(UIInterfaceOrientation)orientation
{
return NO;
}
These will prevent the parent view controller from being rotated.
Finally, if you need to allow rotation of some elements on the screen, but not others, then you will have to determine which method to use:
Allow the screen to rotate while rotating back those elements you want to remain static. I would not recommend it.
Block the screen from rotating as mentioned above, and manually rotating those elements that you want rotated by setting their transform property. This is what I would go for.

Problems rotating in the middle of a pan with UIViewPageController-based slide show

I have an image slide-show viewer that uses a UIPageViewController to present the images.
The image-view viewer ViewController is pretty simple -- a top-level view containing a UIScrollView containing a UIImageView. On initial image presentation or when the device is rotated the image is aspect-fitted to the view dimensions and centered. It works fine except for a problem which happens on both iOS 7 and iOS 6.
If I am in the middle of panning to change images and two images are on the screen and I then rotate the device it sometimes (maybe always?) messes up the display of one or more images. The previously-centered image appears in the wrong place on the screen and this persists when rotated and zoomed.
The only thing I can find wrong when this happens is that the center property of the UIImageView is not, in fact, the center of the frame. If I change the center property of the UIImageView in viewDidAppear to be the center of the possibly-scaled UIImageView then the images seem to display correctly in all cases.
Does this sound like the effect of some familiar mistake that I'm making?
Edit to respond to the question:
I don't do anything in the willRotate or didRotate methods.
In viewDidLayoutSubviews I aspect-fit the image into the current UIScrollView bounds (which is the full top-level view which is the full screen) and center it. This takes care of both the initial presentation and rotations. I don't see any problems in "normal" rotation situations. The problem occurs only when I rotate with two of the pageViewController's children views both partly onscreen at once. I can "fix" the problem in all cases but surely I'm doing something wrong.
Edit 2
I've discovered another anomaly that occurs less frequently when rotating the device while panning with 2 images onscreen. Best explained by an example:
The page view controller is being used to display one image in a sequence of images from left to right of A B C. Image B is onscreen. During a pan to the left you see part of both images A and B. If the pan to image A is nearly complete, i.e. B is nearly offscreen, and you rotate the device then image B normally ends up onscreen (it never rotates still showing the partial pan), but sometimes image C ends up being displayed -- as though you had panned in the other direction. Anybody else see this kind of behavior?
What are you doing in your UIViewController to prepare for the rotation?
– willRotateToInterfaceOrientation:duration:
And then how do you recover once the rotation has finished?
- didRotateFromInterfaceOrientation
These calls are provided specifically so you have a chance to get ready for a rotation, and then to put everything back to normal once it completes.

Main view that should not rotate but subviews that should, including UIPopoverController

I would appreciate some help before spending any more time on trial and error.
Imagine the following: I'm just starting to create something for the iPad that will look something like a dashboard with a number on dials on it. When rotating the iPad (portrait, landscapeLeft etc) the background should not rotate, the dials position should remain but the inside of the dials should rotate to correct position. So, main view should not rotate, but the subviews (inside of the dials) should. I have done this on the iPhone before by telling the viewController to only be in portrait and then checking UIDeviceOrientation, so I thought this was gonna be easy. But my headache starts when displaying a UIPopoverController. Since I'm not changing the UIInterfaceOrientation, the UIPopoverController will always be in portrait.
Ideal solution would be to have the main view (self.view from the viewController) not observe changes in rotation, but allowing the subviews to do it, but from what I understand that is not possible. Only other solution I could think of is to not animate the change in rotation, and jut move the subviews (dials) into their new position. Animating them (subviews) make the dance all over the place. But I have not found any good solution on how to do that.
Any thoughts anyone?
You are correct in thinking that if the main view does not rotate, having the subviews automatically rotate is not possible.
A workaround that springs to mind is this: What is animated when you rotate the view is a change in the view's transform. I am pretty sure that you could register to receive device orientation changes and when you get the change, animate a transform change to a container view that contains all the subviews you want to rotate.
Edit: just read about you having a popover controller.
As far as the popover is concerned, the way the API manages autorotation is to hide the popover and then show it again at the end of the rotation. It shouldn't be too hard to implement similar behavior.
Another thing that occurs to me is this: Is what you want to not rotate just a background? Would it work to just have two backgrounds, one for portrait and one for landscape that you could switch between? It might not be the most pretty looking, but it would probably be easier than recreating autorotation behavior yourself.

UIView coordinate transforms on rotation during keyboard appearance

iPad app; I'm trying to resize my view when the keyboard appears. It amounts to calling this code at appropriate times:
CGRect adjustedFrame = self.frame;
adjustedFrame.size.height -= keyboardFrame.size.height;
[self setFrame:adjustedFrame];
Using this technique for a view contained in a uisplitview-based app works in all 4 orientations, but I've since discovered that a vanilla uiview-based app does not work.
What happens is that apparently the uisplitview is smart enough to convert the coordinates of its subviews (their frame) such that the origin is in the "viewer's top left" regardless of the orientation. However, a uiview is not able to correctly report these coordinates. Though the origin is reported as (0,0) in all orientations, the view's effective origin is always as if the ipad were upright.
What is weird about this is that the view correctly rotates and draws, but it always originates in the literal device top left. How can I get the view to correctly make its origin the "top left" to the viewer, not the device's fixed top left? What am I missing? Please, for something so trivial I've spent about 6 hours on this already with every brute force technique and research angle I could think of.
This is the original source which doesn't work in this case:
move up UIToolbar
OK, I don't know what the ACTUAL answer is to the original question, but I can say with certainty that one way to resolve the issue is to always ensure that you don't manipulate a viewController's view directly. Always wrap your view inside a container view inside the main "view", then have that container view adjust its position etc as needed. Works exactly as the splitview does, probably because in both cases now the view in question is a subview of the main "view". What a relief!

Resources