Steblo
Steblo

Reputation: 625

detecting orientation changes using viewWillTransitionToSize

I have read posts which say didRotate(from fromInterfaceOrientation is deprecated and advising using viewWillTransitionToSize.

But whenever I try to use this function e.g.

 override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.currentDevice().orientation.isLandscape.boolValue {
        print("Landscape")

    } else {

        imageView.image = UIImage(named: const)
    }

I get an error which says Method does not override any method of its superclass. I have tried to use the function in both view controller and its view but to no avail.

Upvotes: 0

Views: 2966

Answers (1)

user7014451
user7014451

Reputation:

If you are using Swift 3, the signature is (for UIViewController):

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

See if that helps.

An alternative that should give you the dimensions after rotation is willWillLayoutSubviews() and viewDidLayoutSubviews().(I use the former.) BEWARE: Both may be called several times in a single rotation. Here's my code....

View Controller:

var initialOrientation = true
var isInPortrait = false

override func viewWillLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if initialOrientation {
        initialOrientation = false
        if view.frame.width > view.frame.height {
            isInPortrait = false
        } else {
            isInPortrait = true
        }
        // call initial layout, isInPortrait is set
    } else {
        if view.orientationHasChanged(&isInPortrait) {
            // orientation has changed, isInPortrait is set
        }
    }
}

UIView extension:

extension UIView {
    public func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
        if self.frame.width > self.frame.height {
            if isInPortrait {
                isInPortrait = false
                return true
            }
        } else {
            if !isInPortrait {
                isInPortrait = true
                return true
            }
        }
        return false
    }
}

EDIT:

16 months later I was asked if I included a typo - should it be viewWillLayoutSubviews or viewDidLayoutSubviews. The full answer is "it depends".

  • The baseline for this question is about the weakness of using viewWillTransitionToSize, namely, that if you are targeting iPad devices the size class will not change. (Also, note the Will piece of the override - it's triggered before the orientation change.

  • In my experience, there is no "guarantee" the either viewWillLayoutSubviews or viewDidLayoutSubviews will be called only once. But my testing over the years results in viewDidLayoutSubviews is more likely to be called only once.

  • More, in the view controller lifecycle, viewDidLoad will trigger *at least two calls to viewWillLayoutSubviews with the initial call having a frame of CGRect.zero.

So if you need to know the orientation change for autolayout constraint activation/deactivation, viewWillLayoutSubviews will work fine. However, if you wish to manipulate subview frames, viewDidLayoutSubviews is the proper place for things.

My latest project involves a UIImageView that uses auto layout to be as large as possible in both orientations. The contentMode is scaleAspectFit, so the image itself is automatically resized on an orientation change. The user can use subviews to 'build" a mask (both move and resize), so auto layout won't work here. More, I need to have this mask be "constrained" to the actual image.

So I need to "manually" resize/repostion the mask frame by using both the "will" and "did" pieces of an orientation change. Here's the code I now use. The UIView extension is still the same, the changes are only in UIViewController.

var initialOrientation = true
var isInPortrait = false
var orientationDidChange = false

override func viewWillLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if initialOrientation {
        initialOrientation = false
        if view.frame.width > view.frame.height {
            isInPortrait = false
        } else {
            isInPortrait = true
        }
        orientationWillChange()
    } else {
        if view.orientationHasChanged(&isInPortrait) {
            orientationWillChange()
        }
    }
}
func orientationWillChange() {
    // capture the old frame values here, storing in class variables
    orientationDidChange = true
}
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if orientationDidChange {
        // change frame for mask and reposition
        orientationDidChange = false
   }

}

Upvotes: 1

Related Questions