Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable autorotate on a single subview in iOS8

I'm writing a drawing app and I don't want the drawing view to rotate. At the same time, I want other controls to rotate nicely depending on the orientation of the device. In iOS 7 I've solved this via:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    float rotation;

    if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
        rotation = 0;
    }
    else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
        rotation = M_PI/2;
    } else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
        rotation = -M_PI/2;
    }

    self.drawingView.transform = CGAffineTransformMakeRotation(rotation);
    self.drawingView.frame = self.view.frame;
}

But on iOS 8 even though the function is called and the transform is set correctly, it does not prevent the view from rotating.

I've tried creating a view controller which simply prevents the rotation of it's view and add it's view to the view hierarchy, but then it doesn't respond to user input.

Any ideas?

Thanks!

like image 414
Jan Avatar asked Feb 13 '15 17:02

Jan


4 Answers

Okay after some fighting with subviews and transitionCoordinators I've finally figured it out:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    CGAffineTransform targetRotation = [coordinator targetTransform];
    CGAffineTransform inverseRotation = CGAffineTransformInvert(targetRotation);

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        self.drawingView.transform = CGAffineTransformConcat(self.drawingView.transform, inverseRotation);

        self.drawingView.frame = self.view.bounds;
    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
    }];
}

What I do is, I calculate the inverse of the transform applied to the view and then use it to change the transform. furthermore I change the frame with the view bounds. This is due to it being full screen.

like image 132
Jan Avatar answered Nov 14 '22 17:11

Jan


Dimitri's answer worked perfectly for me. This is the swift version of the code in case someone needs it...

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
  let targetRotation = coordinator.targetTransform()
  let inverseRotation = CGAffineTransformInvert(targetRotation)

  coordinator.animateAlongsideTransition({ context in
    self.drawingView.transform = CGAffineTransformConcat(self.drawingView.transform, inverseRotation)
    self.drawingView.frame = self.view.bounds
    context.viewControllerForKey(UITransitionContextFromViewControllerKey)
  }, completion: nil)
}
like image 36
JoeGalind Avatar answered Nov 14 '22 15:11

JoeGalind


Swift 4 has a lot of updates, including the viewWillTransition function.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    let targetRotation = coordinator.targetTransform
    let inverseRotation = targetRotation.inverted()

    coordinator.animate(alongsideTransition: { context in
        self.drawingView.transform = self.drawingView.transform.concatenating(inverseRotation)
        self.drawingView.frame = self.view.bounds
        context.viewController(forKey: UITransitionContextViewControllerKey.from)
    }, completion: nil)
}
like image 2
Chewie The Chorkie Avatar answered Nov 14 '22 15:11

Chewie The Chorkie


I managed to obtain this, check:

rotation working as desired

Note: The green and the red views are subviews of the controller's view. The blue view is subview of the red view.

Idea According to https://developer.apple.com/library/archive/qa/qa1890/_index.html, we need to apply an inverse rotation to the view when it is transitioning to a new size. In addition to that, we need to adjust the constraints after rotation (landscape/portrait).

Implementation

class MyViewController: UIViewController {

    var viewThatShouldNotRotate = UIView()

    var view2 = UIView()

    var insiderView = UIView()

    var portraitConstraints: [NSLayoutConstraint]!

    var landscapeConstraints: [NSLayoutConstraint]!

    override func viewDidLoad() {
        super.viewDidLoad()

        viewThatShouldNotRotate.backgroundColor = .red

        view2.backgroundColor = .green

        insiderView.backgroundColor = .blue

        view.addSubview(viewThatShouldNotRotate)
        view.addSubview(view2)


        viewThatShouldNotRotate.addSubview(insiderView)
        portraitConstraints = createConstraintsForPortrait()
        landscapeConstraints = createConstraintsForLandscape()
    }

    func createConstraintsForLandscape() -> [NSLayoutConstraint] {
        return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
            viewThatShouldNotRotate.autoMatch(.height, to: .width, of: view)
            viewThatShouldNotRotate.autoMatch(.width, to: .height, of: view)
            viewThatShouldNotRotate.autoCenterInSuperview()

            view2.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(), excludingEdge: .top)
            view2.autoSetDimension(.height, toSize: 100)

            insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
            insiderView.autoSetDimension(.height, toSize: 100)
        }
    }

    func createConstraintsForPortrait() -> [NSLayoutConstraint] {
        return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
            viewThatShouldNotRotate.autoMatch(.height, to: .height, of: view)
            viewThatShouldNotRotate.autoMatch(.width, to: .width, of: view)
            viewThatShouldNotRotate.autoCenterInSuperview()
            view2.autoPinEdges(toSuperviewMarginsExcludingEdge: .top)
            view2.autoSetDimension(.height, toSize: 100)
            insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
            insiderView.autoSetDimension(.height, toSize: 100)
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if view.bounds.size.width > view.bounds.size.height {
            // landscape
            portraitConstraints.forEach {$0.autoRemove()}
            landscapeConstraints.forEach { $0.autoInstall() }
        } else {
            landscapeConstraints.forEach {$0.autoRemove()}
            portraitConstraints.forEach { $0.autoInstall() }
        }
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        let viewToRotate: UIView = viewThatShouldNotRotate

        coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
            let deltaTransform = coordinator.targetTransform
            let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))

            var currentRotation = (viewToRotate.layer.value(forKeyPath: "transform.rotation.z") as! NSNumber).floatValue

            // Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
            currentRotation = currentRotation + (-1 * Float(deltaAngle)) + 0.0001
            viewToRotate.layer.setValue(currentRotation, forKeyPath:"transform.rotation.z")

        }) { (UIViewControllerTransitionCoordinatorContext) -> Void in

            var currentTransform = viewToRotate.transform;
            currentTransform.a = round(currentTransform.a);
            currentTransform.b = round(currentTransform.b);
            currentTransform.c = round(currentTransform.c);
            currentTransform.d = round(currentTransform.d);
            viewToRotate.transform = currentTransform;
        }
    }
}
like image 1
Fran Depascuali Avatar answered Nov 14 '22 16:11

Fran Depascuali