UIViewController selective Autorotation with Size classes - ios

The new size classes based Autorotation is a pain if you want custom behavior. Here are my requirements and flow:
View Controller 1(VC1) modally presents View Controller 2 (VC2)
VC1 only supports landscape, portrait, or both depending on user settings (achieved via supportedInterfaceOrientations). For this example, we assume it is locked to landscape,
VC 2 supports both landscape & portrait
I use Size classes and in View Controller 1, I check for statusBarOrientation in viewWillTransition(to size...) to configure interface elements positioning & other customizations.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.shared.statusBarOrientation
NSLog("View will transition to \(size), \(orientation.rawValue)")
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [unowned self] (_) in
....
}
}
This works, except when VC 2 is presented and the device is rotated. VC 1 is all messed up when VC 2 is dismissed. To be sure, I would like to refresh layout of View Controller 1 when it appears. How do I do that? I tried UIViewController.attemptRotationToDeviceOrientation() but it doesn't force the autorotation methods to be called again.
EDIT: As can be seen, in VC1 I check for statusBarOrientation to determine interface orientation. That poses a problem, because statusBarOrientation gets changed to portrait when VC2 rotates to portrait mode. And viewWillTransition to size gets invoked on VC1 at the same time where I force layout to portrait mode.

Related

UIViewController autorotation manual layout

I have a view Controller VC1 which can autorotate to all orientations. I set the interface manually by activating and deactivating appropriate autolayout constraints. On autorotation, I do:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.shared.statusBarOrientation
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [unowned self] (_) in
let orient = UIApplication.shared.statusBarOrientation
self.layoutInterfaceForOrientation(orient)
}, completion: { [unowned self] (UIViewControllerTransitionCoordinatorContext) -> Void in
let orient = UIApplication.shared.statusBarOrientation
NSLog("rotation completed to \(orient.rawValue)")
self.layoutInterfaceForOrientation(orient)
})
}
Now comes the problem. I have VC2 which is invoked via a segue from VC1 by a touch of a button. VC2 supports only landscape mode. So if my VC1 is in portrait mode and the segue gets triggers, VC1 screen is still in portrait mode but starts laying out elements as if the screen is in landscape mode and all my UI looks messed up & garbled while segue is in transition. This is because UIApplication.shared.statusBarOrientation returns landscape mode as the segue is in transition but iPhone is still held vertical.
I tried many things, such as having a flag for segue in progress and disabling autorotation when the flag is set, but no effect. Any ideas?

Getting black overlay when rotating the device from portrait to landscape during navigation

The application i am developing which support both portrait and landscape for iPad using autolayout. Issue is when device in Portrait mode i am tapping on button to navigate next screen from current screen.
While navigation action is on immediately i am rotating the device to landscape mode from portrait mode, After navigated to next screen getting black overlay in the right corner like in the reference screen attached.
and this black overlay not getting hide in this screen until i rotating the device to portrait.
Note:
1.This issue is happening in both orientation when i am doing the above mentioned action.
2.This issue is happening in some other screens also.
I am using viewWillTransition to set the view orientation
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
popOverLay.isHidden = true
popOverView.isHidden = true
super.viewWillTransition(to: size, with: coordinator)
self.view.layoutIfNeeded()
self.openMapsActionSheet?.dismiss(animated: true, completion: nil)
}

viewWillTransitionToSize Calls Wrong ViewController in TabBarController

I have tabBarController application with 4 viewcontrollers. This application is landscape orientation enabled so I have viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator in each viewcontroller.m file to control the orientation changes.
The problem I'm having, is when I change the device orientation while in the 3rd viewcontroller, the viewWillTransitionToSize in the 2nd viewcontroller is called so the wrong code is ran.
How is it possible that the 2nd viewcontroller's viewWillTransitionToSize is even called? Especially, when it hasn't even been loaded yet. I know it hasn't been loaded because I NSLog it's viewDidLoad and it shows when I change orientation from the 3rd viewcontroller.
Additional Info: There is no code in the 3rd viewcontroller's viewWillTransitionToSize, viewWillAppear, viewWillDisappear, etc. that would reference the 2nd viewcontroller.
I'm using Xcode 8.2.1 and Objective-C code. Please help, thanks.
Test to see which UIViewController is the selected UIViewController before handling the transition.
In Swift:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
guard self == tabBarController?.selectedViewController else { return }
// handle transition here
}
In my situation, the UIViewController was embedded in a UINavigationController so I had to handle it slightly differently:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
guard self.navigationController == tabBarController?.selectedViewController else { return }
// handle transition here
}
I replaced each instance of
viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator
with
willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
to avoid the aforementioned issue with viewWillTransitionToSize...

UIViewController doesn't update view's constraints when the device orientation is changing

I'm making an app where the iPad can be used in both landscape and portrait mode.
The configuration of my UIViewControllers is the following :
I have a UIViewController with a UICollectionView which has the following constraints : top, leading, trailing, and bottom set to 0 with its superview (the superview is the UIView of my UIViewController).
When I tap a cell, I push another UIViewController in the UINavigationController which is a detail viewController of the cell. Simple.
When I change the device orientation in the first view controller, the UICollectionView bounds are set properly.
Here come my problem :
If I select a cell in the UICollectionView, the second detail viewController is displayed. Then if in this second ViewController I change the device orientation and then I go back into the first viewController, the view of my UIViewController hasn't been rotated. I don't know why, I tried this without success :
self.view.layoutIfNeeded() in viewDidAppear() of my firstViewController to update constraints according the current orientation, it didn't work. This didn't work because I noticed one thing :
The frame view of my first viewController is still the same as the previous orientation, it didn't change... But if I rotate again the device, the UIViewController detect the orientation changes, and the view bounds are set properly.
So my problem is : when I'm in the second view controller, and that I'm rotating the device, and then that I go back in the previous view controller, the previous controller hasn't detected the orientation change I just made in the second view controller.
GIF Demo :
After that I made a rotation from portrait to landscape mode, the view is not changed as the debug view hierarchy tool shows below :
The UIView is still in portrait, so the UICollectionView constraint's can't be updated with the new orientation...
Any ideas?
Resolved by resetting the frame of the view in my first viewController with the screen bounds in the viewWillAppear method :
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.view.frame = UIScreen.main.bounds
self.view.layoutIfNeeded()
}
Like that the main UIView of my viewController have the right frame, and the constraints can be updated automatically when I call layoutIfNeeded() on my UIView.
Found need to reset the frame size of collectionview. add self.collectionview.frame.size = size
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if let layout = self.collectionview.collectionViewLayout as? UICollectionViewFlowLayout {
let width = (size.width - 30) / 3
layout.itemSize = CGSize(width:width, height:width)
self.collectionview.frame.size = size
layout.invalidateLayout()
}
}

Force Landscape orientation, Swift

I have a view controller that is designed in portrait view. I have another view controller designed in Landscape view (in storyboards). On my first view controller (portrait VC) i have a button that takes you to the landscape VC. When you click on the button the Landscape view controller shows up in portrait view initially and doesn't change until rotated. I would like to have this landscape view controller automatically show up in landscape without having to rotate it.
Ive been coding this in swift, and have had no luck. In my plist menus i have all orientations enabled.
Any help would be great.
You should add this in the second viewcontroller (designed to be in landscape) in the viewDidLoad
It is in swift3 :
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
Hope it will help you :)
I believe the best and most robust way to achieve this is by overriding two attributes - and avoid force orientation changes of the windows (as suggested by others).
This is how I got one of my view controllers to always show up in landscape mode, and stay there, without affecting the entire navigation stack. Add both of these overrides to the landscape view controller:
override var shouldAutorotate: Bool {
false
}
This will prevent the view controller from auto-rotating when device orientation changes. Now that the view controller no longer rotates by itself, all you need to do is request the orientation you want:
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
UIInterfaceOrientation.landscapeLeft
}
And voila! Should do what you want.
I'm working on a similar scenario I want to load and keep my first ViewController in Landscape orientation while allowing subviews to switch as needed
I think placing
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
Swift 4
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
should load your view in landscape mode...But if holding phone in portrait orientation it will soon rotate...and that's the part I'm at...How to prevent the rotation.

Resources