swift - Popover is not displayed correctly in landscape mode - ios

Popover takes the complete screen when displayed in landscape mode, it works correctly in portrait mode though. Also, it is does not disappear when i click outside the popover in landscape mode.
I connected the popover through the storyboard. Inside the popoverviewcontroller I placed a view which contains the buttons. The code for the viewdidload() of the popoverviewcontroller is:
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = popoverView.frame.size
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Portrait:
landscape:

The answer from #Jake2Finn works for Swift 4.0.
The trait parameter specifically is required to fix the landscape problem:
traitCollection: UITraitCollection
Without it the function adaptive... only works for portrait.

You have to add UIPopoverPresentationControllerDelegateto your class like this:
swift 3
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
...
As a second step, add the following function:
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
Explanation: By returning UIModalPresentationStyle as none, the original presentation style is kept and your popover is not streched to the bottom of your screen in landscape orientation.

Related

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

UINavigationController doesn't rotate when presenting child view controller

Here is my sample project
I have two view controllers embedded in a UINavigationController. On the first one, there is just a button performing a segue on the 2nd view controller. On the latter, a button dismiss it back to the 1rst view controller.
The 1rst view controller is not allowed to rotate and stays in Portrait while the 2nd is allowed to rotate in Landscape.
To do so, I added this code in the 1rst view controller:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
and added an extension to UINavigationController:
extension UINavigationController {
override public func shouldAutorotate() -> Bool {
if let topViewController = topViewController {
return topViewController.shouldAutorotate()
}
return false
}
override public func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if let topViewController = self.topViewController {
return topViewController.supportedInterfaceOrientations()
}
return .Portrait
}
}
On the 2nd view controller, I add programmatically a label with some autolayout constraints. The label's title show the UIDevice.currentDevice().orientation.
My problem is the following:
When I put the device on landscape when I'm on the 1rst view controller, it's fine, the layout is laid for Portrait but when I tapp the button to present the 2nd view controller, this one stays on Portrait instead of switching to Landscape.
And bigger problem for me as in my real project I set some constraints depending on the device orientation, the UIDevice.currentDevice().orientation return the Landscape position.
What's wrong? Is it a normal behaviour? How can I fix it?
In your sample project, you're not updating the label when the device is rotated. You should override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) Also, Apple recommends that you not use UIDevice orientation but rather just look at the bounds of your view controller's view. For example:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if size.height<size.width {
label.text = "Landscape"
} else {
label.text = "Portrait"
}
}
Calling UIViewController.attemptRotationToDeviceOrientation() in the viewWillAppear of the 2nd view controller did the trick.

collapseSecondaryViewController only called once

I tried to implement UISplitViewController by following steps in 《iOS 8 by tutorial》。
The ducoment said if I return yes in splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: method, the split view controller will shows only the content from its primary view controller.
But in my project, the split view controller shows both primary and secondary view controller in collapsed interface no matter I return true of false in this method. And the most wired thing is that this method is only called once when the app begins running.
Here is my custom SplitViewController which subclasses to UISplitViewController:
import UIKit
class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK:- UISplitViewControllerDelegate
func splitViewController(splitController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
// We don't want anything to happen. Say we've dealt with it
return true
}
}
I found I needed to add "self.preferredDisplayMode=.primaryOverlay" to my ViewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
self.preferredDisplayMode = .primaryOverlay
}
The preferredDisplayMode has some other options to customize the initial behavior you can toy with to get your preferred look and feel.
Note this is for iPhone, Compact Width. Test also on an iPad as it behaves differently (landscape vs. portrait).

UIModalPresentationPopover for iPhone 6 Plus in landscape doesn't display popover

I want to always present a ViewController in a popover on all devices and all orientations. I tried to accomplish this by adopting the UIPopoverPresentationControllerDelegate and setting the sourceView and sourceRect.
This works very well for all devices and orientations, except the iPhone 6 Plus in landscape. In that case the view controller slides up from the bottom of the screen in a form sheet. How can I prevent that so that it will always appear in a popover?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let popoverPresentationController = segue.destinationViewController.popoverPresentationController
popoverPresentationController?.delegate = self
popoverPresentationController?.sourceView = self.titleLabel!.superview
popoverPresentationController?.sourceRect = self.titleLabel!.frame }
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None }
All device are under iOS 8.2 or higher
Implement the new adaptivePresentationStyleForPresentationController:traitCollection: method of UIAdaptivePresentationControllerDelegate:
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
// This method is called in iOS 8.3 or later regardless of trait collection, in which case use the original presentation style (UIModalPresentationNone signals no adaptation)
return UIModalPresentationNone;
}
UIModalPresentationNone tells the presentation controller to use the original presentation style which in your case will display a popover.
In Swift 3, if you implemented the original adaptivePresentationStyle method, simply adding this code works:
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return adaptivePresentationStyle(for: controller)
}
Apple designed the iPhone 6 Plus presentation to behave that way, based on its size class.
To prevent the modal presentation on the iPhone 6 Plus, you'll have to override the trait collection (horizontal size).
You should be able to set the overrideTraitCollection property for the presentation controller:
presentedVC.presentationController.overrideTraitCollection = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
(Sorry for the Objective C! I haven't learned Swift yet.)
A note to people having issues with this:
This
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *) controller traitCollection:(UITraitCollection *)traitCollection {
return UIModalPresentationNone;
}
Is not the same as this
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
return UIModalPresentationNone;
}
The later will not get called / work the same as the former.

Setting UIModalPresentationStyle for iPhone 6 Plus in landscape?

I want to always present a view controller in a popover on all devices and all orientations. I tried to accomplish this by adopting the UIPopoverPresentationControllerDelegate and setting the sourceView and sourceRect. The segue in the storyboard is configured as a Present As Popover segue. This works very well for all devices and orientations, except the iPhone 6 Plus in landscape. In that case the view controller slides up from the bottom of the screen in a form sheet. How can I prevent that so that it will always appear in a popover?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let popoverPresentationController = segue.destinationViewController.popoverPresentationController
popoverPresentationController?.delegate = self
popoverPresentationController?.sourceView = self.titleLabel!.superview
popoverPresentationController?.sourceRect = self.titleLabel!.frame
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
Implement the new (as of iOS 8.3) adaptivePresentationStyleForPresentationController:traitCollection: method of UIAdaptivePresentationControllerDelegate:
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
// This method is called in iOS 8.3 or later regardless of trait collection, in which case use the original presentation style (UIModalPresentationNone signals no adaptation)
return UIModalPresentationNone;
}
UIModalPresentationNone tells the presentation controller to use the original presentation style which in your case will display a popover.

Resources