When clicking on a rightBarButton, a UIPopoverController will present.
The problem is: when clicking on the NavigationBar, this UIPopoverController does not dismiss.
Why? And is there a way to resolve it?
I have tried to search, but can't find anything about this.
Thanks in advance.
UIPopoverController seems to add the navigation bar to its passthroughViews array when it is presented. I was able to solve the problem by re-setting passthroughViews to an empty array immediately after presenting the popover.
When launching from a bar button you can simply do this
[popoverController presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[popoverController setPassthroughViews:nil];
Items on your navigation bar will be automatically added to popoverViewController's passthroughViews. It happens after the popover shows up. So you need to clear passthroughViews after that.
And for iOS 8, we can get popoverController from UIViewController.popoverPresentationController, before that, we can get popoverController from UIStoryboardPopoverSegue.
In your view controller presents a view controller as popover.
var popoverController: UIPopoverController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Before IOS8, we need to get reference of popOverController from UIStoryboardPopoverSegue
if (!self.respondsToSelector(Selector("popoverPresentationController"))) {
if let popoverController = (segue as? UIStoryboardPopoverSegue)?.popoverController {
let menuViewController = segue.destinationViewController as AIMSMenuTableViewController
menuViewController.popoverController = popoverController
}
}
}
In your view controller that is presented as popover.
var popoverController: UIPopoverController?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Set passthroughViews to nil make tapping other navigation bar button
// dismiss presenting popoverController
if (self.respondsToSelector(Selector("popoverPresentationController"))) {
self.popoverPresentationController?.passthroughViews = nil
} else {
// For iOS8-pre version, we need to pass popoverController reference from segue
self.popoverController?.passthroughViews = nil
}
}
The documentation for UIPopoverController states:
When displayed, taps outside of the
popover window cause the popover to be
dismissed automatically. To allow the
user to interact with the specified
views and not dismiss the popover, you
can assign one or more views to the
passthroughViews property. Taps inside
the popover window do not
automatically cause the popover to be
dismissed. Your view and view
controller code must handle actions
and events inside the popover
explicitly and call the
dismissPopoverAnimated: method as
needed.
The navigation bar is added as one of the passthroughViews when the popover is presented from a bar button item.
Perhaps try settings an empty array as the passthroughViews property on your popover controller.
You put this cod on any other action or After completing the selection or provide some close button in the popover and accomplish uy yhing,
[popOverControllerObj dismissPopoverAnimated:YES];
This is expected behavior as far as I can tell. The popover on the bookshelf in iBooks behaves like this. Retain a reference to the popover when you present it, and then dismiss it if any of the buttons in the navigation bar are tapped.
Related
In my project you can create a post from a modal view.
When the modal view is dismissed (user presses on save post) I want to switch the tab bar controller to the second tab (post feed screen).
This topic is similar to my problem. The only difference being this is presented from a modal view. I can't figure out how to implement it in my code (tab bar is nil)
Switch tab bar programmatically in Swift
I have added 3 images to make this issue clearer
code screenshot
console message
#objc func saveAction(sender: UIButton) {
print ("> save pressed")
print(presentingViewController?.tabBarController)
print(presentingViewController)
presentingViewController?.tabBarController?.selectedIndex = 1
dismiss(animated: true)
}
edit: sorry stack overflow doesn't allow me to add images yet
You can do this using delegate pattern. But if you prefer not to add a delegate for this, you can do as shown below;
You can switch the tabbar by changing the selectedIndex property of tabBarController
if let presenter = presentingViewController as? LibraryViewController {
presenter.tabBarController?.selectedIndex = 1
}
dismiss(animated: true)
If you are presenting the modal on navigation controller in tabbar, use:
if let tabBar = presentingViewController as? UITabBarController {
tabBar.selectedIndex = 1
}
dismiss(animated: true)
It's easier to show you a drawing and then explain.
Dashboard Storyboard
I have 2 separate UIViewControllers (i've included just one in the drawing, the other is irrelevant) embedded in container view called ContainerViewController.
Post Storyboard
NewPostViewController shows a UIButton that presents TextPostViewController. As you can see, all of them are embedded in UINavigationControllers. Now, once the completion block of the new post is being called, I have to present the ContainerViewController and it needs to handle it's own logic. The problem is that it's embedded in UINavigationController and once I present it, the UITaBbar is hidden.
I tried to do this:
self.performSegue(withIdentifier: "TextPostToNavContainerVC", sender: nil)
The transition is successful but I'm losing the UITabBar, even though in the DashboardViewController and the ContainerViewController I called:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tabBarController?.tabBar.isHidden = false
}
What am I doing wrong or is there are better way to do that?
You should instantiate the tab bar controller. not the view controller.
Imagine you're putting a initial view controller ahead of your tab bar controller. Making your tab bar not being pushed
If I undestand it correctly.
You are doing this
Segue connect to a view controller
But you should actually do this Segue connected to a tab bar controller
You can try to add it as a child to control it's frame like this
let textPost = self.storyboard?.instantiateViewController(withIdentifier: "containerID") as! TextPostToNavContainerVC
textPost.view.frame = CGRect(x:20,y:0,width:self.view.frame.width,height:self.view.frame.height-50)
self.view.addSubview(nvc.view)
self.addChildViewController(textPost)
textPost.didMove(toParentViewController: self)
i'm fairly new to swift and having some difficulty with unwind segues in a master detail application.
the unwind seems to work fine dismissing my popovers on the iPhone, however when i try the same thing on the iPad, the popover remains.
If i add a dismissViewControllerAnimated to the presenting viewController's unwind handling action, then the iPad version works fine and dismisses the popover, however the iPhone version dismisses the popover and then dismisses the view that presented the popover. i.e. dismisses two views.
I have worked out that the problem is that popover's are automatically dismissed with an unwind when presented modally such as on an iPhone. However when presented as true popovers they don't dismiss with an unwind segue. Could somebody help me figure out how to manage both cases so that only the popover will be dismissed. Thank you very much in advance.
Okay. after a long time working on this i managed to come up with a solution. i used a popoverpresentationcontroller and declared the presenting controller as the delegate. by then adding an additional function forcing the iPhone to use the popover in lieu of the modal presentation, the presentation and dismissal is consistent for the iPhone and iPad. the code is below. I just used a generic UIViewController in the if let vc statement so that I could use this with a popover that's imbedded in a navigation controller also.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "My Segue Identifier" :
if let vc = segue.destinationViewController as? UIViewController {
if let ppc = vc.popoverPresentationController {
ppc.delegate = self
}
}
default: break
}
}
}
additionally you need to add the following function to prevent the modal presentation on the iPhone:
func adaptivePresentationStyleForPresentationController (controller:UIPresentationController)-> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
I've create a Popover segue in my project and inside a button to when it's touched open another ViewController modally. Then inside the another ViewController I've a button to dismiss the actual ViewController and the Popover too with the following code :
var tmpController :UIViewController! = self.presentingViewController;
self.dismissViewControllerAnimated(true, completion: {()->Void in
println("done");
tmpController.dismissViewControllerAnimated(true, completion: nil);
});
My problem is that when the actual ViewController is dismissed the Popover is like in FullScreen, then it resize to its original size and is dismissed after, and the resize process is animated.
What is causing this behaviour ?
How can I avoid this?
I would suggest you to dismiss the popover view controller from the parent view controller using delegation/notification.
How can i implement this popup menu in iphone app like a popover in ipad?
EDIT: This is the best at moment: https://github.com/runway20/PopoverView
iOS 8 and later
Beginning with iOS 8, you can use UIPopoverPresentationController for iPhones in addition to iPads.
Setup
Add a UIBarButtonItem to your main View Controller.
Add another View Controller to the storyboard. Change it to the size that you want the popover to be and add any content that you want it to have. For my example I just added a UILabel. If you want a whole menu, then just add a table view or list of buttons.
Add a segue from the bar button item to the view controller that you will use as the popover. Rather than show, choose Present as Popover.
Select the segue in the storyboard and set the identifier to popoverSegue (or whatever string you called it in the code).
In the Attributes inspector for the popover view controller, check Use Preferred Explicit Size and confirm that it is the size you want it to be.
Code
This is the code for the main view controller that has the bar button item in it.
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "popoverSegue" {
let popoverViewController = segue.destinationViewController
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
popoverViewController.popoverPresentationController!.delegate = self
}
}
// MARK: - UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// Force popover style
return UIModalPresentationStyle.None
}
}
Popover at an arbitrary anchor point
If you want to set the popover to appear somewhere besides a bar button item (on a UIButton for example) then you need to set the sourceView and sourceRect. See this answer for details.
Further reading
The above example comes mostly from the first link.
iPad Style Popovers on the iPhone with Swift
iOS 8 Popover Presentations
UIPopoverPresentationController on iOS 8 iPhone
General overview of popup options in iOS
Have a look at the iPhone UIPopoverController implementation: WEPopover
On iPhone you would generally use a UIActionSheet for a stack of buttons like that. It slides up from the bottom, rather than popping up next to the button, but that's the standard behavior on iPhone.
There is one that is even better than WEPopover. Developed by a company called 50pixels, it is called FPPopover.
You can download FPPopover at https://github.com/50pixels/FPPopover
You would have to manually instantiate a UIView using a custom background image or drawing with transparency, add some UIButtons (or other type of custom view) on top, and also somehow handle all touches outside that view.
Note that is is non-standard UI. An actionsheet would be more HIG compliant.
To get a popover from a right side bar button item on a navigation controller that is part of a tableview controller, the following worked for me for Swift 4 and Xcode 9.
Follow the steps in Suragch answer above (as edited by the Community.)
Do not implement the Segue as shown in the answer above. For some reason, the segue causes the popover to go full screen despite setting the explicit size.
Give your popover view controller a title in Attributes Inspector
Add the following code in the TableView controller where the popup will show.
Modify the string identifier (the one here is referencing a Constant.swift file)
Modify "as! FilterVC" to use the title of the your popover view controller.
/// Shows a filter popover view
#IBAction func filterBtnPressed(_ sender: UIBarButtonItem) {
let popover = storyboard?.instantiateViewController(withIdentifier: FILTER_VC) as! FilterVC
popover.modalPresentationStyle = UIModalPresentationStyle.popover
popover.popoverPresentationController?.backgroundColor = UIColor.green
popover.popoverPresentationController?.delegate = self
popover.popoverPresentationController?.backgroundColor = ColorPalette.Blue.Medium
popover.popoverPresentationController?.sourceView = self.view
popover.popoverPresentationController?.sourceRect = CGRect(x: self.view!.bounds.width, y: 0, width: 0, height: 0)
popover.popoverPresentationController?.permittedArrowDirections = .up
self.present(popover, animated: true)
} }
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
You can check WYPopoverController: https://github.com/sammcewan/WYPopoverController
The screenshot above is not a UIActionSheet. It looks like a simple UIView subclass with custom UIButtons on top of it. So go ahead and create the subclass according to your needs and then add it as a subview to your view every time you need it.