UIViewController autorotation manual layout - ios

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?

Related

Programatically show the slide over master view in a UISplitViewController

When using a UISplitViewController in portrait mode on an iPad I am given a fullscreen view of my detail view controller which is intended. I can slide from the left and reveal the master view slide over, how can I trigger this slide over programmatically?
There's no easy way to do this it seems, but I have found that using the following code has the intended behaviour:
UIView.animate(withDuration: 0.2, animations: {
self.splitViewController?.preferredDisplayMode = .primaryOverlay
})
Make sure you set the display mode back to automatic on landscape rotation so that it will always show the master and detail like default:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
self.splitViewController?.preferredDisplayMode = .automatic
}
}, completion: nil)
}

Swift: Hidden navigation bar appears when tap occurs in view

Introduction
I'm creating a simple app in with the RootViewController is embedded in a UINavigationController. I have a UIView subclass "landscapeView" with a UICollectionView in it that fills the view. "landscapeView" is hidden in portrait and displayed in landscape device orientation.
Issue
When the device is rotated to a landscape orientation I hide the navigationBar and the portrait table view "rootTableView", while showing the "landscapeView". However, the navigationBar appears when I tap the screen in landscape orientation. I can't figure out how to disable this tap to show thing. (I have `navigationController?.hidesBarsOnTap = false, its setup to default in storyboard).
Clarification: hiding the navigation bar works perfectly depending on device orientation.
Question
How can I prevent the navigationBar from appearing when the screen is tapped in landscape orientation?
Code
viewWillTransition() in the "RootViewController"
private let landscapeView = LandscapeView(frame: .zero)
private let rootTableView = UITableView(frame: .zero, style: .grouped)
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
var isLandscape = true
switch UIDevice.current.orientation {
case .landscapeLeft:
navigationController?.navigationItem.searchController?.accessibilityElementsHidden = true
isLandscape = true
case .landscapeRight:
navigationController?.navigationItem.searchController?.accessibilityElementsHidden = true
isLandscape = true
case .portrait, .portraitUpsideDown, .faceUp, .faceDown, .unknown:
isLandscape = false
navigationController?.navigationItem.searchController?.accessibilityElementsHidden = false
default:
break
}
if isLandscape {
navigationController?.setNavigationBarHidden(true, animated: false)
self.landscapeView.isHidden = false
// This simply tells the "landscapeView" to layoutSubviews() and reloadData() for the collectionView within.
landscapeViewDelegate?.landscapeViewWillAppear(inDarkMode: inDarkMode)
}
UIView.animate(withDuration: 0.6, delay: 0, options: .layoutSubviews, animations: {
// This is a tableView displayed in portrait mode.
self.rootTableView.alpha = isLandscape ? 0 : 1
self.landscapeView.alpha = isLandscape ? 1 : 0
}) { (success) in
if isLandscape == false {
self.landscapeView.isHidden = true
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
return
}
}
Thanks for reading.
The issue is that you checked Hide Bars When Vertically Compact for your navigation controller in the storyboard. This property brings that tap behaviour with it:
When the value of this property is true, the navigation controller
hides its navigation bar and toolbar when it transitions to a
vertically compact environment. Upon returning to a vertically regular
environment, the navigation controller automatically shows both bars
again. In addition, unhandled taps in the content area cause the
navigation controller to show both bars again. The default value of
this property is false.
You can uncheck that property since you take care of showing and hiding the navigation bar in the viewWillTransition method yourself.

UIViewController selective Autorotation with Size classes

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.

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)
}

UISplitViewController iPad behaviour

I'm making my first universal app, so far so good, but I have a problem with UISplitViewController on iPad.
how can i make the UISplitViewController act same as on iPhone when it is in portrait mode?
like in portrait mode show only master screen when i click on it, it navigate to the details screen, and when in landscape mode show both of them beside each other.
what happens now , is that it shows the details screen only in portrait and show both of them in landscape mode.
for iPhone i used this code in master view to solve this issue
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
but this didn't work with iPad, i found another code on here but didn't work too.
func splitViewController(svc: UISplitViewController, willHideViewController aViewController: UIViewController, withBarButtonItem barButtonItem: UIBarButtonItem, forPopoverController pc: UIPopoverController) {
self.navigationItem.leftBarButtonItem?.target?.performSelector((self.navigationItem.leftBarButtonItem?.action)!, withObject: self.navigationItem)
}
other code maybe you need to know, i added those in viewDidLoad in master view controller
self.splitViewController?.delegate = self
self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryOverlay
self.splitViewController!.maximumPrimaryColumnWidth = splitViewController!.view.bounds.size.width;
self.splitViewController!.preferredPrimaryColumnWidthFraction = 0.3
so please if anyone can help me find solution for this issue, I will be very thankful
UISplitViewController use size classes to determine how to display his master and detail view controller.
When your UISplitViewController has horizontalSizeClass and verticalSizeClass Regular it will display both the Master and Detail view controllers on the same screen.
You'll need to embed your split view controller into a container view controller to override the default size class as I explained here.
You also have to check the device orientation to fork between Compact (when Portrait) or Regular (when Landscape) horizontal size class:
class ContainerVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
performOverrideTraitCollection()
}
private func performOverrideTraitCollection() {
let orientation = UIDevice.currentDevice().orientation
var isPortrait = false
switch orientation {
case .Portrait, .PortraitUpsideDown:
isPortrait = true
default:
isPortrait = false
}
for childVC in self.childViewControllers {
self.traitCollection.userInterfaceIdiom
setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: isPortrait ? .Compact : .Regular), forChildViewController: childVC)
}
}
}

Resources