Is there something similar to NSPopover for iOS apps? It appears in the Object library for Mac but not for iPhone nor iPad, although I have downloaded apps using this (or at least some very similar) feature.
So my question: Is there a legit way to implement this?
UIPopoverController is what you're looking for.
There is UIPopOverController, but its use is restricted to iPads:
Popover controllers are for use exclusively on iPad devices. Attempting to create one on other devices results in an exception.
(source)
For iPhone/iPod touch, you could use an external framework, like WEPopOver.
There is they are called UIPopovers,
Heres a tutorial: http://www.youtube.com/watch?v=1iykxemuxbk
For the sake of those Googling this, UIPopoverController is now available for both iPhone and iPad. You can make popovers that look the same on both (as of iOS 9, I believe).
Step 1: Include UIAdaptivePresentationControllerDelegate and UIPopoverPresentationControllerDelegate on your class definition
Step 2: Override the presentation somewhere in your class like this:
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Step 3: When you present a view controller, you tell it to use popover style. Something like this:
//I pull my popover from a separate storyboard, but any modal view will do
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
let modal = storyboard.instantiateViewController(withIdentifier: "AnyPickerModal") as! AnyPickerVC
modal.modalPresentationStyle = UIModalPresentationStyle.popover
let pc = modal.popoverPresentationController
pc?.permittedArrowDirections = .any
pc?.sourceView = <your button or view you tap to show the popover>
pc?.sourceRect = <your button or view>.bounds
//How big the popover should be
modal.preferredContentSize = CGSize(width: 300, height: 180)
pc?.delegate = self
self.present(modal, animated: true, completion: nil)
Your presented modal will show up as a popover on both iPhone and iPad. Attached is a screenshot from my iPhone app.
Happy coding!
Related
First of all, I don't want to use storyboards at all. I am able to "present" the targeted view controller, but I can't get it to show with the standard iOS back button at the top. My understanding is I can get this to work by pushing the controller instead of presenting it. I don't get any errors, but nothing happens.
On a button click this code is run. The commented code is what successfully presented the ForgotPasswordPage :
// Changes to Forgot Password Page
func forgotPasswordSwitch(sender: UIButton!) {
//let ForgotPassword:ForgotPasswordPage = ForgotPasswordPage()
//self.presentViewController(ForgotPassword, animated: true, completion: nil)
let ForgotPassword = ForgotPasswordPage()
self.navigationController?.pushViewController(ForgotPassword, animated: true)
}
You have to manually create a UINavigationcontrollerto get the back bar. To do this you can use the code from this answer, which achieves this by using this code:
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var nav1 = UINavigationController()
var mainView = ViewController() //ViewController = Name of your controller
nav1.viewControllers = [mainView]
self.window!.rootViewController = nav1
self.window?.makeKeyAndVisible()
Here just add all the ViewControllers you want to be under the Navigation controller into the array and then push between them.
Hope that helps, Julian
I know that sometimes it's much better develop apps programmatically, but there are many time that Storyboard can save you a lot of time. You just simply use it for this situations...
1) In you Storyboard, localize the view controller you want to enable for pushing
2) Select it and find the "Editor" in your top bar
3) Select Editor->Embed In->Navigation Controller
And now your view controller it's ready for pushing
Before presenting a view controller I set the modalPresentationStyle property to UIModalPresentationPopover. This will present the view controller as a popover when running on devices with a regular horizontal size class (iPad and iPhone 6+ in landscape) and as modal/fullscreen on other devices. It's also possible to override this behaviour by overriding adaptivePresentationStyleForPresentationController so that the view controller is presented as a popover on all devices.
I wonder if it's possible, after a view controller is presented, to know if it's presented as a popover or not? Just looking at the size class won't do it as it's possible that the view controller overrides adaptivePresentationStyleForPresentationController.
The obvious answer would be that I, as the programmer, should know if I override adaptivePresentationStyleForPresentationController or not but I want to write a function that can determine this in runtime for any view controller by passing in the view controller or maybe the UIPopoverPresentationController (or any other object needed) as an argument.
Here's some code to present the view controller:
navigationController = (UINavigationController *)[MVSStore sharedInstance].addViewController;
navigationController.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:navigationController animated:YES completion:^{}];
UIPopoverPresentationController *popoverController = navigationController.popoverPresentationController;
popoverController.sourceView = self.view;
popoverController.sourceRect = CGRectMake(20, 20, 20, 20); // Just a dummy
popoverController.permittedArrowDirections = UIPopoverArrowDirectionAny;
Here's the current code to detect if the view controller is presented as a popover or not. But as mentioned above it just looks at the size class which doesn't work for all cases.
+ (BOOL)willPresentTruePopover:(id<UITraitEnvironment>)vc {
return ([vc traitCollection].horizontalSizeClass == UIUserInterfaceSizeClassRegular);
}
I cannot find any property or function in UIViewController or UIPopoverPresentationController (or anywhere else) that gives me this information right away but maybe I'm missing something?
You said you were trying to do this to remove the cancel/done button. Instead, only add the button in the case when it is needed.
The official way to implement this is first remove the Done button from your view controller and second, when adapting to compact embed your view controller in a navigation controller, now add the done button as a navigation item:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.FullScreen
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
return navigationController
}
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
Full Tutorial
Use the UIAdaptivePresentationControllerDelegate method presentationController:willPresentWithAdaptiveStyle:transitionCoordinator:. To query the presentation style at other times, ask the presentation controller for its adaptivePresentationStyleForTraitCollection:, passing the current traits. These methods were added in iOSĀ 8.3, and are not documented yet.
I'm attacking it in the same way you were, in that I set up the Done button with its target-action in Interface Builder. In order to remove it, I was testing if the popoverPresentationController != nil. On my testing device (iPhone 5 running iOS 10), this test successfully ignored the iPhone while executing on an iPad Pro running iOS 11. I ran into problems while testing it on an iPhone 8 running iOS 11. It appears in iOS 11 that a popoverPresentationController is now instantiated even when presenting the view modally. As a result, I am just testing the horizontal size class of the presenting view controller. Not sure if that's the correct way, but it works for me, since I can't find any way for the popoverPresentationController to tell me it is actually presenting modally.
weak var ppcDelegate: UIPopoverPresentationControllerDelegate?
...
if popoverPresentationController != nil &&
popoverPresentationController!.presentingViewController.traitCollection.horizontalSizeClass == .regular {
navigationItem.rightBarButtonItem = nil
popoverPresentationController?.delegate = ppcDelegate
}
My iPad app has several data gathering popovers, and I want to be able to disable the dismissal of the popover by touching outside of it, I then will use a button to quit the popover at the users discretion.
The app looks great, the popovers work fine, and I have a button inside them that quits nicely. Only I can't find a way of disabling dismissal in Swift, lots of posts on obj-c but nothing in Swift.
Does this mean that the functionality is no longer available?
I would greatly appreciate any help to my frustration.
Simply set the view controller's modalInPopover to true and the popover's passthroughViews to nil. But you must do the latter using delayed performance or it won't work. A small delay is all that's needed. Example:
let vc = UIViewController()
vc.modalPresentationStyle = .Popover
self.presentViewController(vc, animated: true, completion: nil)
if let pop = vc.popoverPresentationController {
vc.modalInPopover = true
delay(0.1) {
pop.passthroughViews = nil
}
}
For the delay function, see dispatch_after - GCD in swift?.
I am using following code to present a view controller modally. I have changed presentation style to "Over current Context". It works fine on iOS 8 but screen gets black on os < 8. I know Over Current Context is available only in iOS 8. My question is how can I achieve this in iOS 7.
let vc = self.storyboard.instantiateViewControllerWithIdentifier("markerView") as! MarkerViewController
self.presentViewController(vc, animated: false, completion: nil)
You have to use Current Context for iOS 7.
To check the iOS-Version you can use NSFoundationVersionNumber.
let iOS7 = floor(NSFoundationVersionNumber) <= floor(NSFoundationVersionNumber_iOS_7_1)
let iOS8 = floor(NSFoundationVersionNumber) > floor(NSFoundationVersionNumber_iOS_7_1)
Then you can check which version is running and use OverCurrentContext or CurrentContext.
if iOS8 {
self.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
} else {
self.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
}
For some reason it seems that the following code doesn't work for popovers in iOS 7 when using Swift:
self.dismissViewControllerAnimated(true, completion: nil)
There are no errors but the popover doesn't dismiss. It works fine in iOS 8. Do I need to do something different in iOS 7?
Yes, popovers were extensively reworked in iOS8 to be fully fledged view controllers.
To dismiss in iOS7 you need to call dismissPopoverAnimated: on the UIPopoverController instance. You can do this from the content controller by passing a reference to the owning UIPopoverController , to the content view controller.
like...
class MyViewController:UIViewController {
var parentPopover:UIPopoverController?
func dismissPopover() {
parentPopover?.dismissPopoverAnimated(true)
}
and then when setting up...
func popoverThatThing() {
let mvc = MyViewController()
let popover = UIPopoverViewController(contentViewController:mvc)
mvc.parentPopover = popover
popover.presentFromWhatever...
}
or even easier, don't support iOS7