4

I have a UIViewController that only rotates some of it subviews when the device is rotated. This works fine under iOS7 but breaks under iOS8. It appears that the UIView's bounds are adjusted by the transform under iOS8. This was unexpected.

Here's some code:

@interface VVViewController ()
@property (weak, nonatomic) IBOutlet UIView *pinnedControls;
@property (nonatomic, strong) NSMutableArray *pinnedViews;

@end

@implementation VVViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.pinnedViews = [NSMutableArray array];
    [self.pinnedViews addObject:self.pinnedControls];
}

-(void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    [UIViewController rotatePinnedViews:self.pinnedViews forOrientation:self.interfaceOrientation];
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];

    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation) && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))  {
        [UIViewController rotatePinnedViews:self.pinnedViews forOrientation:toInterfaceOrientation];
    }
}

@end

We've made a category on UIViewController to handle this behavior. Here's the pertinent code:

@implementation UIViewController (VVSupport)

+ (void)rotatePinnedViews:(NSArray *)views forOrientation:(UIInterfaceOrientation)orientation {
    const CGAffineTransform t1 = [UIViewController pinnedViewTansformForOrientation:orientation counter:YES];
    const CGAffineTransform t2 = [UIViewController pinnedViewTansformForOrientation:orientation counter:NO];
    [views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
        // Rotate the view controller
        view.transform = t1;
        [view.subviews enumerateObjectsUsingBlock:^(UIView *counterView, NSUInteger idx, BOOL *stop) {
            // Counter-rotate the controlsUIin the view controller
            counterView.transform = t2;
        }];
    }];
}

+ (CGAffineTransform)pinnedViewTansformForOrientation:(UIInterfaceOrientation)orientation counter:(BOOL)counter {
    CGAffineTransform t;
    switch ( orientation ) {
        case UIInterfaceOrientationPortrait:
        case UIInterfaceOrientationPortraitUpsideDown:
            t = CGAffineTransformIdentity;
            break;

        case UIInterfaceOrientationLandscapeLeft:
            t = CGAffineTransformMakeRotation(counter ? M_PI_2 : -M_PI_2);
            break;

        case UIInterfaceOrientationLandscapeRight:
            t = CGAffineTransformMakeRotation(counter ? -M_PI_2 : M_PI_2);
            break;
    }

    return t;
}

@end

Here's what the nib looks like:

enter image description here

The UIView named pinned in the nib is the IBOutlet of pinnedControls:

When I run this in portrait mode under iOS7 or iOS8 I get this:

enter image description here

And I see the desired outcome under iOS7 in landscape mode:

enter image description here

But under iOS8 (GM) I do not get this behavior. This is what I see instead:

enter image description here

Notice that the center of the UILabel with the text "Pinned Label" is maintaining its distance from the bottom of the pinned UIView, which has not changed size to accommodate the rotation. That UIView has all its edges pinned to the top, left, bottom and right sides of the super view.

It looks to me that the transform property interacts with Auto Layout differently under iOS8. I'm a bit baffled here. I know I can't rely on the frame. I may just start manually setting bounds but that just seems like the wrong thing to do, essentially do an end run around Auto Layout.

Paul Cezanne
  • 8,629
  • 7
  • 59
  • 90

3 Answers3

1

So this was driving me crazy for the past couple days and I was able to fix by changing the timing of the setTransform call in my animations block

When going to landscape, I'm setting the transform AFTER setting up the new frame. When going portrait, I'm setting the transform BEFORE setting up the new frame. All this was going inside the animations block on the "animateWithDuration..." method

I'm not sure if it will help you directly with your code, but it might spark some inspiration to solve it since we are definitely having a similar issue

Lucas Chwe
  • 2,578
  • 27
  • 17
0

This is more of a work around than a fix so it may not help everybody in similar situations. My problem was that the outer "pinned" view was being resized again after the transform was applied.

My solution was to change the constraints on the pinned view to be center vertically, center horizontally, and width and height equal a constant.

Then, in viewDidLoad, I set the height and width of the pinned view's frame to be the height of the main screen. This makes the view square so I don't care if it gets an extra rotate.

Paul Cezanne
  • 8,629
  • 7
  • 59
  • 90
0

in ios8, uiviewcontrollers need to be resized with uitraitcollections depending on the device orientation. Otherwise, you get a uiview in portrait mode, while the phone oriented in landscape, when you try to rotate it. So the correct steps are to rotate AND override uitraitcollections

EDIT: I override my uitraitcollection with the following code UITraitCollection *horTrait = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact]; UITraitCollection *verTrait = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact]; UITraitCollection *finalTrait = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horTrait,verTrait]]; [self.parentViewController setOverrideTraitCollection:finalTrait forChildViewController:self];

unfortunately, it doesnt work if the uiviewcontroller im trying to modify does NOT have a parentviewcontroller :'(

Lucas Chwe
  • 2,578
  • 27
  • 17
  • Interesting. Our app has several iOS8 visual anomalies, they've not made it to the top of my To Do list yet but I'll certainly remember this when they do. BTW, what do you override UITraitCollections to do? – Paul Cezanne Oct 22 '14 at 13:53
  • 1
    I override the uitraitcollections, so the view is reshaped to landscape, otherwise stays in portrait shape when you try to manually rotate the view – Lucas Chwe Oct 23 '14 at 13:50