UIPopoverPresentationController popover displays over the entire view - swift

I am trying to create a rightBarButtonItem that appears throughout my app. When this barItem is clicked I want to show a modal popup using UIPopoverPresentationController. I have been able to get the button to show up on the barItem on all the views. However when i click on the button the xib takes over the entire view (including nav bar, see image below). Please see the class below:
class MyAppsNavigationController: UINavigationController, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.navigationBar.barTintColor = Colors.Red01.color()
self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white]
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "Ellipsis"), style: .plain, target: self, action: #selector(displayMenu(sender:)))
}
func displayMenu(sender: UIBarButtonItem)
{
let filterVC = DropdownMenuController(nibName: "DropdownMenuController", bundle: nil)
let nav = UINavigationController(rootViewController: filterVC)
nav.modalPresentationStyle = UIModalPresentationStyle.popover
//nav.isNavigationBarHidden = true
nav.preferredContentSize = CGSize(width: 200, height: 300)
let popover = nav.popoverPresentationController! as UIPopoverPresentationController
popover.permittedArrowDirections = .up
popover.delegate = self
popover.barButtonItem = self.navigationItem.rightBarButtonItem
popover.sourceView = self.view;
var frame:CGRect = (sender.value(forKey: "view")! as AnyObject).frame
frame.origin.y = frame.origin.y+20
popover.sourceRect = frame
popover.delegate = self
self.present(nav, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Result when clicked on the button:
When clicked the popup takes over entire view:

Any chance you're not using the right delegate method? I think this looks better:
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Also, in this case sourceView and sourceRect is not needed: specifying a barButtonItem for the popover presentation controller is sufficient.
https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/1622314-barbuttonitem

Related

Why is the content of my UIScrollView scrolling behind my UIStackView?

I made a StackView on top of my ScrollView. The bottom constraint of my StackView is constrained to the top constraint of the ScrollView. I use the ScrollView as a container to display my content. The StackView consists of 6 buttons and serves as a navigation. Hierarchy looks like this.
To add a new controller into my ScrollView I use the following code:
#IBAction func planAction(_ sender: UIButton) {
let newPlan = sender.frame.origin.x
scrollView.subviews.forEach{$0.removeFromSuperview()}
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let nextController = storyBoard.instantiateViewController(withIdentifier: "PlansViewController2") as! PlansViewController2
addChildView(viewController: nextController, in: scrollView)
self.movingView.frame.origin.x = newPlan
}
public func addChildView(viewController: UIViewController, in containerView: UIView) {
viewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
viewController.view.frame = containerView.bounds
addChildViewController(viewController)
containerView.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
}
In my PlansViewController I add a UIBarButtonItem programmatically but it is not displayed. Look here.
public override func viewDidLoad() {
self.navigationController?.setNavigationBarHidden(false, animated: false)
if let viewControllers = self.navigationController?.viewControllers {
if !viewControllers.contains(where: {
return $0 is StructureNavigationListViewController }) {
let filterBtn = UIBarButtonItem(title: "Kategorienliste", style: .plain, target: self, action: #selector(handleCategoryListTap(sender:)))
navigationItem.rightBarButtonItems = [filterBtn]
}
}
Is my hierarchy wrong or what´s going on here?

Call a View from another ViewController Swift

I have a ViewController that has a UIView set on top of it and a button that opens a popover to another ViewController. I want a button on the popover view controller to set the UIView to disable. How do I reference the UIView from the first view controller from a button in the second view controller?
EDIT:
Below is code that I use to call the popover view controller. Notice how I call dimView.isHidden = false from this first viewcontroller. I want to run dimView.isHidden = true from the popover view controller.
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC")
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
dimView.isHidden = false
self.present(popover!, animated: false)
EDIT 2:
Below is my popover view controller. Since it is not called PopoverVC. I updated the answer to include let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC") as! PopOverViewController but still no luck.
import UIKit
var parentController:UIViewController?
class PopOverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func closeButton(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
}
}
EDIT 3:
class ViewController: FormViewController {
override func viewWillAppear(_ animated: Bool) {
dimView.isHidden = true
}
#IBOutlet weak var dimView: UIView!
You can pass a reference to your current view controller when presenting your PopoverVC and then you can access its view from PopoverVC. Just create a property in PopoverVC that can store the reference, like var parentController:UIViewController?
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC") as! PopoverViewController
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
popover?.dimView = self.dimView
dimView.isHidden = false
self.present(popover!, animated: false)
PopOverViewController:
class PopOverViewController: UIViewController {
var dimView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func closeButton(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
}
}

dimiss modal and return to presented childViewController in containerView

I am having a bit of an issue with dismissing a modal view presented from a childviewController in a container view. I have a UINavigationController as the rootViewController (MainNavigationController), and present a modal from one of the childViewControllers from the selectedSegmentIndex 1 (secondViewController). The modal is presented fine, but when I dismiss the modal to go back to the secondViewController(a subclass of HomeController) it returns me back to selectedIndex 0, so not the selectedIndex 1 childViewController it was presented from. I would like the modal to dismiss and return the user back to childViewController it was presented from (the secondViewController) and not return back to selectedIndex 0. Thanks in advance!
// NavigationConroller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// HomeController as parentViewController
class HomeController: UIViewController, FBSDKLoginButtonDelegate {
// child view controllers to put inside content view
var firstViewController: TravelersFeedVC?
var secondViewController: ProfileVC?
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(inactiveViewController: oldValue)
updateActiveViewController()
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMove(toParentViewController: nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = contentView.bounds
contentView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMove(toParentViewController: self)
}
}
// UI elements
lazy var contentView: UIView = {
let tv = UIView()
tv.backgroundColor = UIColor.purple
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.masksToBounds = true
return tv
}()
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
activeViewController = firstViewController
checkIfUserIsLoggedIn()
view.addSubview(contentView)
setupProfileScreen()
let items = ["Travelers", "Me"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
segmentedController.tintColor = UIColor.black
segmentedController.selectedSegmentIndex = 0
// Add function to handle Value Changed events
segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
// reference to collectionViewController
var travelersFeedVC: TravelersFeedVC!
func segmentedValueChanged(_ sender:UISegmentedControl!)
{
switch segmentedController.selectedSegmentIndex {
case 0:
activeViewController = firstViewController
case 1:
activeViewController = secondViewController
default: // Do nothing
break
}
}
// secondViewcontroller in containerView where modal is presented from
class ProfileVC: UIViewController {
// button to present modal
lazy var placesButton: UIButton = {
let customButton = UIButton(type: .system)
customButton.backgroundColor = UIColor.clear
// customButton.frame = CGRect(x: 150, y: 50, width: 120, height: self.view.frame.height)
customButton.setTitle("## of Places", for: .normal)
customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
customButton.setTitleColor(.white, for: .normal)
customButton.addTarget(self, action: #selector(handleShowPlacesVC), for: .touchUpInside)
return customButton
}()
// function to call to present modal
func handleShowPlacesVC() {
let placesVC = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: placesVC)
present(navigationController, animated: true, completion: nil)
}
// modal view to dismiss
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "back", style: .plain, target: self, action: #selector(handleCancel))
}
// dismiss modal view to return to secondViewController in childViewController containerView
func handleCancel() {
dismiss(animated: true, completion: nil)
}
When closing the modal dialog the viewDidAppear function in MainNavigationController is called. There you set a new homeController with it's childs. This will trigger a viewDidload in the HomeController with setting of firstViewController. Try to set a breakpoint there and you will see it.
I suggest to avoid content creation in viewDidAppear, use viewDidLoad instead.
Another hint: 'dismiss' is defined as: 'Dismisses the view controller that was presented modally by the view controller.' - If you open for instance an alert above your modal vc it closes the alert, not the modal view (self). A correct implementation has to call dismiss on the presenting controller (same controller that opened it): "presentingViewController?.dismiss()"
It works in your code because apple has implemented a fallback for the case that nothing is presented, but it's a trap that cause some headache sometime.
The chances are that although you're calling present from the child view controller, it isn't in fact handling the presentation. From the Apple docs:
The object on which you call this method may not always be the one that handles the presentation. Each presentation style has different rules governing its behavior. For example, a full-screen presentation must be made by a view controller that itself covers the entire screen. If the current view controller is unable to fulfill a request, it forwards the request up the view controller hierarchy to its nearest parent, which can then handle or forward the request.
Since you're keeping a reference of the active view controller, one solution may be to explicitly set the index upon dismissal.

how to present a popover in custom cell in swift

For presenting popover I am following the below code.
func showPopOver() {
let secondStoryboard = UIStoryboard(name: "Second", bundle: nil)
viewObj = secondStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
viewObj.modalPresentationStyle = UIModalPresentationStyle.Popover
viewObj.preferredContentSize = CGSizeMake(400,500)
let popoverPresentationController = viewObj.popoverPresentationController
popoverPresentationController?.delegate = self
popoverPresentationController?.sourceView = self.view //walletButton
popoverPresentationController?.sourceRect = CGRectMake(0, button.frame.origin.y+100, 0, 0)
presentViewController(viewObj, animated: true, completion: nil)
}
//MARK:- UIPopoverPresentationControllerDelegate methods... starts
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle{
return UIModalPresentationStyle.None
}
func prepareForPopoverPresentation(popoverPresentationController: UIPopoverPresentationController) {
print("prepare for presentation")
}
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
print("did dismiss")
}
func popoverPresentationControllerShouldDismissPopover(popoverPresentationController: UIPopoverPresentationController) -> Bool {
print("should dismiss")
return false
}
its working fine in normal view controller, but i need to show a popover view in custom tableview cell.
class flow like
|-UITableViewController
|-CustomCell:UITableViewCell
|-UIButton: button action-> here we need to show a popover with 3/4 frame value of the tableview frame.
whenever user tapped out of the popover view then dismiss the popover view.

set navigation backBarButtonItem to go to specific UIViewController in Swift

I need the navigation's back button always pops a specific UIViewController.
override func viewDidLoad() {
super.viewDidLoad()
//set title image
var logoImage:UIImage = UIImage(named: "barra")!
var logoImageView : UIImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRectMake(0, 0, 320, 44)
logoImageView.contentMode = .ScaleAspectFit
logoImageView.contentMode = UIViewContentMode.Center
logoImageView.clipsToBounds = true
self.navigationItem.titleView = logoImageView
//set back image
var backImage:UIImage = UIImage(named: "freccia")!
backImage = backImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"", style: .Plain,target: self, action: "goToServizi:")
self.navigationController!.navigationBar.backIndicatorImage = backImage
self.navigationController!.navigationBar.backIndicatorTransitionMaskImage = backImage
//set menu image
var menuImage:UIImage = UIImage(named: "menu")!
menuImage = menuImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.navigationItem.rightBarButtonItem?.image = menuImage
}
func goToServizi(sender: UIBarButtonItem)
{
self.navigationController?.popToViewController(ServiziVC(), animated: true)// here I add a breakpoint, but it is never executed.
}
ServiziVC is the UIViewController that I need to show every time I click on the Back button.
I can't understand why goToServizi func is not called. Please help.
Thank you.
Here is the example for you.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Customise your barButton Like this
var backImage:UIImage = UIImage(named: "freccia")!
backImage = backImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
var backButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.Bordered, target: self, action: "goToThird")
self.navigationItem.leftBarButtonItem = backButton
}
func goToThird(){
//Initiate newViewController this way
let ThirdView = self.storyboard?.instantiateViewControllerWithIdentifier("ThirdViewController") as ThirdViewController
self.navigationController?.pushViewController(ThirdView, animated: true)
}
}
EDIT
Click on the ViewController which you want to initiate with identifier and you can find StoryBoard ID in Identity Inspector like shown in below Image.
HERE is the example for you for more reference.
Modify code as per your need.may be this will help you.
self.navigationController?.popToViewController(ServiziVC(), animated: true)You can't customize the back button action like that. This works:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
var backImage:UIImage = UIImage(named: "freccia")!
backImage = backImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
var fakeBackButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.Bordered, target: self, action: "goToServizi:")
self.navigationItem.leftBarButtonItem = fakeBackButton;
}
func goToServizi(sender: UIBarButtonItem)
{
self.navigationController?.popToViewController(ServiziVC(), animated: true)
}
Alternatively, you could override the UINavigationController's delegate method as described here : UINavigationController and back button action