I have to check if my device has changed orientation in iOS 8+.
My approach is:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = UIScreen.main.bounds.width > UIScreen.main.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = UIScreen.main.bounds.width > UIScreen.main.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
it works fine in iPhone but in iPad isLand has already the new value which should be after the orientation completion, so:
Portrait > Landscape: true -> true
Landscape > Portrait: false -> false
According to the documentation the bounds should change with the orientation so it should have a before/after bounds, shouldn't it?
UIScreen main bounds:
This rectangle is specified in the current coordinate space, which
takes into account any interface rotations in effect for the device.
Therefore, the value of this property may change when the device
rotates between portrait and landscape orientations.
Whereas it works fine both iPhone and iPad if I use the bounds of the current root view controller like this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = UIApplication.shared.keyWindow!.rootViewController!.view.bounds.width > UIApplication.shared.keyWindow!.rootViewController!.view.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = UIApplication.shared.keyWindow!.rootViewController!.view.bounds.width > UIApplication.shared.keyWindow!.rootViewController!.view.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
Portrait > Landscape: false -> true
Landscape > Portrait: true -> false
You should try using the containerView of the coordinator context instead.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let isLand = coordinator.containerView.bounds.width > coordinator.containerView.bounds.height
coordinator.animate(alongsideTransition: nil) { _ in
let isLand2 = coordinator.containerView.bounds.width > coordinator.containerView.bounds.height
print("\(isLand) -> \(isLand2)")
}
}
If you want to get further information regarding the transition you can use the func view(forKey: UITransitionContextViewKey) and func viewController(forKey: UITransitionContextViewControllerKey) using the .from key.
Related
I've implemented the iOS 11 feature prefersLargeTitles and it works just fine. Portrait mode is working as expected:
I understand the large title will always stay collapsed (small) in landscape mode and that's fine to me. The problem is when I try to change to landscape and then again to portrait, the large title should be expanded (big) by default back in portrait mode, but it won't until I scroll down a bit:
My code looks quite simple:
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
I also tried using different values on tableView.contentInsetAdjustmentBehavior, nothing changed. I'm kind of solving it by now scrolling down the table programmatically after orientation changes, but I think that's just a (not very nice) workaround.
Is that supposed to be working as expected? Is it something left in my implementation? Is there a better workaround to this?
I faced the same issue. This worked for me.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
navigationItem.largeTitleDisplayMode = .always
coordinator.animate(alongsideTransition: { (_) in
self.coordinator?.navigationController.navigationBar.sizeToFit()
}, completion: nil)
}
One approach could be save the maximum navigation bar height, and set it during rotation.
Something like this:
var maximumHeight: CGFloat = 0
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let navigationController = navigationController else {
return
}
if maximumHeight < navigationController.navigationBar.frame.height {
maximumHeight = navigationController.navigationBar.frame.height
}
coordinator.animate(alongsideTransition: { (_) in
navigationController.navigationBar.frame.size.height = self.maximumHeight
}, completion: nil)
}
In landscape, the system knows that it must change its size, so you don't have to worry about it.
#rassar #twofish
iOS 16
First set the PrefersLargeTitles to true either on ViewDidLoad() or on the NavigationBar in the storyboard.
Then on the Navigation Controller's rootController add this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { (_) in
self.navigationController?.navigationBar.sizeToFit()
}
}
I'm getting some strange results when querying UIScreen.main.bounds.height after an orientation change. Maybe this isn't the correct way to do it.
I have an observer that listens for the NSNotification.Name.UIDeviceOrientationDidChange event:
NotificationCenter.default.addObserver(self, selector: #selector(self.orientationChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
This calls a function that sets a constraint to 75% of the new screen height. This works fine on iPhone but iPad returns the wrong screen height.
If the iPad is in landscape orientation UIScreen.main.bounds.height will return a value equal to the height in portrait orientation and vice versa.
func orientationChange() {
// This will print the correct value on iPhone and iPad.
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height
self.searchViewHeightConstraint.constant = screenHeight * 0.75
// Correct value on iPhone. Incorrect on iPad.
print("screenHeight: '\(screenHeight)'")
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.searchView.superview?.layoutIfNeeded()
}, completion: nil)
}
I've also come across the viewWilltransition method of monitoring orientation change but this behaves in the exact opposite way to the method above. ie. the height is correct on iPad but incorrect on iPhone:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height
self.searchViewHeightConstraint.constant = screenHeight * 0.75
// Correct value on iPad. Incorrect on iPhone.
print("screenHeight: '\(screenHeight)'")
}
What is the reason for this inconsistent behaviour between iPhone and iPad and is using NotificationCenter the correct approach to monitoring orientation change?
What you should be using is
viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator). This will give you the size and is WAY more reliable than using a notification.
Also, if you wanted to do animations, inside of the UIViewControllerTransitionCoordinator you can leverage the method animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool
You need to use :
dispatch_async(dispatch_get_main_queue()) {
println("h:\(view.bounds.size.height)")
println("w:\(view.frame.size.width)")
}
// All you need to write code under dispatch_async
This code will be executed after the rotation was completed (in the main queue) and the new sizes are available.
This works for me in Swift 2.3 for both iPad and iPhone.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
print(size.height)
if UIDevice.currentDevice().orientation.isLandscape {
print("Landscape")
print(size.height)
}
else {
print("Portrait")
print(size.height)
}
}
I have a navigation bar added programatically to the view with VFL (visual format language)
like this
navBar.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(navBar)
let views = ["bar": navBar]
var constraints = [NSLayoutConstraint]()
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[bar]|", options: [], metrics: nil, views:views)
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[bar(64)]", options: [], metrics: nil, views:views)
NSLayoutConstraint.activate(constraints)
the above code works fine but gives a constant height to the navigation bar as 64. but I would like to have it based on the mobile's orientation, portrait-64 and landscape-44
If you want to change height depending on orientation change you need to grab when orientation change
Below method will help you do this.So change your layout depending on orientation in this method too.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("Landscape")
//Change your code here for landscape
} else {
print("Portrait")
//Change your code here for portrait
}
}
Swift 3 version:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
}
I have a full-screen UIView. When I rotate the phone the view shrinks a little (and you see the black background) and then expands again to be full-screen. It basically animates as expected.
The other views on the screen also animate accordingly.
Can I prevent this from happening for one specific view?
I would like the full-screen view to just stay full-screen without animation revealing the black background, but maintain that the other views animate their rotation.
Kind of like how Apple does it in the camera app. The "viewfinder" does not animate its rotation, but the buttons do.
I have the following code in my ViewController. liveView is the the full-screen UIView mentioned.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
}, completion: nil)
}
AVCaptureVideoOrentation is an extension which basically translates UIInterfaceOrientation to AVCaptureVideoOrientation with a switch statement.
Thanks
- Joseph
Did you try self.liveView.layer.removeAllAnimations():
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.liveView, animation: { (context) in
if let connection = (self.liveView.layer as! AVCaptureVideoPreviewLayer).connection {
connection.videoOrientation = AVCaptureVideoOrientation(ui: UIApplication.shared.statusBarOrientation)
}
self.liveView.layer.removeAllAnimations()
}, completion: nil)
}
I adjust a constraint in a iOS8 rotation delegate method, but size is not the same what I can see on device, why?
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
println("size: \(size)")
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
widthConstraint.constant = (size.width / 8.0) - 4.0
}
size: (1024.0,768.0)
But real size is smaller then full iPad size.
Testing on real device, and viewController is an embedded container view controller.
It seems in somehow I get the parentViewController's view size.