GLKit's -drawRect: is not called during screen rotation
Asked Answered
C

3

5

I have GLKit-based app with some simple object displayed.

All works fine, except screen rotation, during which GLKView is not updated (-drawRect: is not called). So during rotation projection matrix is not updated according to dynamically changing screen sizes and object looks badly (stretched).

Cumin answered 7/3, 2013 at 19:18 Comment(3)
Are you using shaders or GLKBaseEffect?Coth
Tried both: GLKBaseEffect and my own shaders - the same result. -drawRect: will be called after animation finished - so any GL rendering (that we normally place there) will be suspended to that moment.Cumin
Ok. I was thinking that maybe the projectionMatrix property on GLKBaseEffect was buggy. I'll have a think...Coth
F
1

That's simply not how UIKit animations work. I sort-of explain how half of it works in this answer, but I'll try to summarize the relevant bits:

  • Everything is a textured rectangle. (There are some exceptions, like perhaps CAShapeLayer, but this is true for the most part.)
  • UIView's properties are linked to CALayer's "model tree" properties. They change instantaneously.
  • Animations work by animating the "presentation tree" properties from the starting value to the current model value.
  • The starting value of the animation is, by default, the previous model value. Specifying UIViewAnimationOptionBeginFromCurrentState makes it use the current presentation value.

There are, of course, a few exceptions (CAShapeLayer seems to be more than a textured rect, UIScrollView does scrolling animations on a timer, UIView transitions are another thing entirely...).

How rotation is supposed to work is that you get a single -setFrame:, everything is laid out (and potentially rerendered), and then the animatable properties are animated. By default, this means things will rerender to the new dimensions but get stretched to fit the old dimensions, and then animate (and unstretch) as the rotation progresses.

That said, GLKView might work differently. If you're using GLKViewController, it might even suspend rendering during the rotation animation. You could try calling -setNeedsDisplay during the rotation, but it won't help much since the frame is already set to its final value.

Probably the easiest way to handle the rotation animation yourself is to force a non-animated relayout to the rotated frame and then do some fudging in the renderer (the black border and status bar animate separately though).

Alternatively, the traditional way is to make your VC portrait-only and handle the device orientation notifications yourself, but this is a big can of worms (you then have to worry about the status bar orientation which determines things like the touch offset and keyboard orientation, and it tends to interacts "interestingly" when transitioning to/from other VCs).

Frisbee answered 6/4, 2013 at 2:50 Comment(0)
C
6

This might be a shot in the dark since I don't have any experience with GLKit, however I do have experience with UIView and -drawRect:. Try changing the contentMode of the view in question to redraw:

view.contentMode = UIViewContentModeRedraw;

This will tell the view that it needs to redraw it's contents when it's boundaries change. Otherwise, UIView will simply attempt to scale it's contents (the default for contentMode is UIViewContentModeScaleToFill). The reason for this is that it's a lot easier to scale what's there than to redraw the contents and UIView is designed to be as efficient as possible.

Couloir answered 17/3, 2013 at 13:55 Comment(1)
Unfortunately, this doesn't seem to make any difference in practice on a GLKView.Babby
F
1

That's simply not how UIKit animations work. I sort-of explain how half of it works in this answer, but I'll try to summarize the relevant bits:

  • Everything is a textured rectangle. (There are some exceptions, like perhaps CAShapeLayer, but this is true for the most part.)
  • UIView's properties are linked to CALayer's "model tree" properties. They change instantaneously.
  • Animations work by animating the "presentation tree" properties from the starting value to the current model value.
  • The starting value of the animation is, by default, the previous model value. Specifying UIViewAnimationOptionBeginFromCurrentState makes it use the current presentation value.

There are, of course, a few exceptions (CAShapeLayer seems to be more than a textured rect, UIScrollView does scrolling animations on a timer, UIView transitions are another thing entirely...).

How rotation is supposed to work is that you get a single -setFrame:, everything is laid out (and potentially rerendered), and then the animatable properties are animated. By default, this means things will rerender to the new dimensions but get stretched to fit the old dimensions, and then animate (and unstretch) as the rotation progresses.

That said, GLKView might work differently. If you're using GLKViewController, it might even suspend rendering during the rotation animation. You could try calling -setNeedsDisplay during the rotation, but it won't help much since the frame is already set to its final value.

Probably the easiest way to handle the rotation animation yourself is to force a non-animated relayout to the rotated frame and then do some fudging in the renderer (the black border and status bar animate separately though).

Alternatively, the traditional way is to make your VC portrait-only and handle the device orientation notifications yourself, but this is a big can of worms (you then have to worry about the status bar orientation which determines things like the touch offset and keyboard orientation, and it tends to interacts "interestingly" when transitioning to/from other VCs).

Frisbee answered 6/4, 2013 at 2:50 Comment(0)
D
0

First of all, ensure that at some point early in your application lifecycle, enable device orientation changes.

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

Then when your view is instantiated register for a notification like so.

[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object: nil];

Then implement -deviceOrientationDidChange: in your view controller and call -setNeedsDisplay

Danais answered 11/3, 2013 at 3:44 Comment(3)
-...DidChange will be called after orientation change happened. And this time drawRect: will be called also (cause I update matrices by time & on each frame change). But I need to apply changes during this process.Cumin
I see now. I though you were saying that you were not getting any rotation information at all. Listen for and implement the view controller method - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration. The duration will allow you to calculate your animations during the transition.Danais
Yes, I know. And I can also reimplement view's -setFrame: - and will get view size changes each animation step. But I can't respond to this changes - I mean, I can update projection matrix, but updates won't be applied, because -drawRect: (and corresponding GL drawing) wan't be called until animation finished.Cumin

© 2022 - 2024 — McMap. All rights reserved.