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
Related
I have a View and a popover that appears on top of it, which alters data. I am trying to update the view/run a function on the main view (that is under the popover) once the popover is dismissed. I have tried numerous things including viewwillappear, but it isn't being registered as technically the view doesn't disappear since the popover is just above (And you can see part of the view from behind). If anyone can suggest how to call a function on the parent view when dismissing the popover (without crashing the app, as most of my attempts have), I would be very grateful! Thanks.
Update: I am attempting to do this with a modally presented vc now, and have attempted to use protocol callbacks but to no avail. Below is the code.
protocol MainVCDelegate: class {
func pushIt()
}
in the modally pushed view:
weak var delegate: MainVCDelegate?
#IBAction func changePartnerButton(_ sender: Any) {
delegate?.pushIt()
dismiss(animated: false)
}
in the main VC I implement the protocol and create the function to be run, but nothing happens.
In iOS 13 and iOS 14, you set yourself as the popover's presentation controller's delegate and implement presentationControllerDidDismiss. In iOS 12 and before, you set yourself as the popover's popover presentation controller's delegate and implement popoverPresentationControllerDidDismissPopover.
Strange things seem to happen when using the new iOS 11 navigationItem.searchController method on a detail view of a UISplitViewController.
The searchBar partly appears as a blank space on the first presentation, then appears in the wrong UITableViewController, but corrects itself after a few push and pops of UITableViewController.
I used to put the searchBar in the tableHeaderView, but I changed the code according to the WWDC recommendation:
if (#available(iOS 11.0, *)) {
self.navigationItem.searchController = self.searchController;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
} else {
self.tableView.tableHeaderView = self.searchController.searchBar;
}
This example is using standard sample code (default project for UISplitViewController and the Apple demo of UISearchController updated for iOS 11 (using a single UITableViewController)).
The initial view containing the searchController looks like this:
And clicking a UITableView item yields this:
However after clicking on a UITableView item and returning twice - it looks as it should:
and:
I was trying to determine why the Apple example for UISearchController worked and my code didn't. The main difference was it was embedded in UISplitViewController in the Detail View. Which means if shown in Compact mode has an extra UINavigationController in the stack. I found if my seque avoided the extra UINavigationController - it works correctly (but breaks device rotation). Similarly change the segue to modal allows it to work.
I note this is similar to this old question: UISplitViewController with new UISearchController issue with UISearchBar
I have created a sample project that demonstrates the problem (sample code: searchControllerDemo)
I'm stumped as to what is going on. So any help would be very much appreciated.
It's been a while since this erupted but thought to leave a note here for whoever will face the same issue...
On compact width devices, upon segueing from master to detail, the detail navigation controller is on top of the master view controller, unlike regular width where the two navigation controllers have their own separate root view controllers.
So, the UINavigationController of the detail view controller needs to be removed upon segue in combact width devices using UISplitViewControllerDelegate method: splitViewController(_:showDetail:sender:)
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if splitViewController.isCollapsed, let navController = vc as? UINavigationController {
if let detailVC = navController.topViewController {
splitViewController.showDetailViewController(detailVC, sender: sender)
return true
}
}
return false
}
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?.
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!