2

I have two UIBarButtonItems on my navigation controller:

    segmentControl = UISegmentedControl(items: ["Up", "Down"])
    infoItem = UIBarButtonItem(image: infoImage,
                               style: .plain, 
                               target: self,
                               action: #selector(infoAction))
    navigationItem.rightBarButtonItems = [infoItem, UIBarButtonItem(customView: segmentControl)]

When tapping infoItem I do:

@objc func infoAction()
{
    let popoverContentController = InfoViewController()

    popoverContentController.preferredContentSize = CGSize(width: 300, height: 300)
    popoverContentController.modalPresentationStyle = .popover
    popoverContentController.popoverPresentationController?.delegate = self
    popoverContentController.popoverPresentationController?.passthroughViews = nil

    self.present(popoverContentController, animated: true, completion: nil)
}

This then calls out to UIPopoverPresentationControllerDelegate functions:

func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController)
{
    popoverPresentationController.permittedArrowDirections = .any
    popoverPresentationController.barButtonItem = infoItem
    popoverPresentationController.passthroughViews = nil
}

func adaptivePresentationStyle(for controller: UIPresentationController,
                               traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
    return .none
}

Even though I set passthroughViews to nil twice, the UISegmentedControl is not decolorized and remains tappable while the popover is on screen.

If showing any other popover the UISegmentedControl behaves normally: decolorized and not tappable.

What am I missing here?

meaning-matters
  • 21,929
  • 10
  • 82
  • 142

2 Answers2

1

Looking at your code, everything seems to be fine. It seems there is a bug in OS.

I have found a quick fix for this, unless they check and fix it in next release of iOS.

  1. Define both barButtonItems and a variable to save the existing tint colour globally in your ViewController.

    var infoItem: UIBarButtonItem!
    var segmentItem: UIBarButtonItem!
    var savedTintColour: UIColor? = nil
    
  2. In your ViewDidLoad() Initialize them

    segmentedControl = UISegmentedControl(items: ["Up", "Down"])
    infoItem = UIBarButtonItem(image: UIImage(named: "setting_mobile"),
                               style: .plain,
                               target: self,
                               action: #selector(infoAction))
    segmentItem = UIBarButtonItem(customView: segmentedControl)
    navigationItem.rightBarButtonItems = [infoItem, segmentItem]
    
  3. The code for InfoAction will be remain the same.

    @objc func infoAction() {
        let popoverContentController = InfoViewController()
    
        popoverContentController.preferredContentSize = CGSize(width: 300, height: 300)
        popoverContentController.modalPresentationStyle = .popover
        popoverContentController.popoverPresentationController?.delegate = self
        popoverContentController.popoverPresentationController?.passthroughViews = nil
    
        self.present(popoverContentController, animated: true, completion: nil)
    }
    
  4. Implement the delegate method prepareForPopoverPresentation and set the tint colour to darkGray and save the previously available tintColour to a variable so that we can reuse that while enabling.

    func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
    popoverPresentationController.permittedArrowDirections = .any
    popoverPresentationController.barButtonItem = infoItem
    popoverPresentationController.passthroughViews = nil
    
    self.segmentItem.isEnabled = false
    if savedTintColour == nil {
        savedTintColour = self.segmentedControl.tintColor
    }
    self.segmentedControl.tintColor = .darkGray
    }
    
  5. Implement a delegate method popoverPresentationControllerDidDismissPopover, to reset the colour of your segmentControl and Enable the segmentedItem.

    func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
    self.segmentItem.isEnabled = true
    self.segmentedControl.tintColor = savedTintColour!
    }
    

Hope it helps.

Bhavin Kansagara
  • 2,866
  • 1
  • 16
  • 20
  • I know how to fake this behaviour, but that's now what I'm looking for now. – meaning-matters Oct 20 '18 at 22:43
  • Right, but there is no other options to overcome this problem. Either you can fake this behaviour or You can contact Apple to fix this issue. You can do one more thing by setting the segment control to your leftBarButtonItem, if UI modification is allowed in your case. :) – Bhavin Kansagara Oct 21 '18 at 06:53
  • As there are no other answers so far, [I mimicked iOS behavior](https://stackoverflow.com/a/52913773/1971013) as you suggest (and I had in mind if no better/real solution would appear). – meaning-matters Oct 21 '18 at 09:24
0

As Bhavin Kansagara suggests, mimicking iOS behaviour is a valid workaround. His answer was close, but missed a few details:

  • popoverPresentationControllerDidDismissPopover is called too late resulting in the segmented control turning blue again after all other UI elements. Need to use popoverPresentationControllerShouldDismissPopover instead.
  • The color changes need to be animated, like in iOS.
  • segmentedControl's isEnabled must also be saved.
  • Handle the lighter color in disabled state.

Here's what I've done, hoping for a better solution:

private var segmentedControlTintColor: UIColor?
private var segmentedControlIsEnabled: Bool = true

// Due to, what seems to be, an iOS issue, the segmented control is not decolorized when the info popover is
// on screen.  The two functions below mimick iOS behavior until a better solution is found.
func decolorizeSegmentedControl()
{
    segmentedControlIsEnabled = segmentedControl.isEnabled
    segmentedControl.isEnabled = false
    segmentedControlTintColor = segmentedControl.tintColor
    UIView.animate(withDuration: 0.333)
    {
        self.segmentedControl.tintColor = self.segmentedControlIsEnabled ? .darkGray : .lightGray
    }
}

func colorizeSegmentedControl()
{
    segmentedControl.isEnabled = segmentedControlIsEnabled
    UIView.animate(withDuration: 0.333)
    {
        self.segmentedControl.tintColor = self.segmentedControlTintColor
    }
}

func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController)
{
    popoverPresentationController.permittedArrowDirections = .any
    popoverPresentationController.barButtonItem = infoItem

    decolorizeSegmentedControl()
}

func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool
{
    colorizeSegmentedControl()

    return true
}
meaning-matters
  • 21,929
  • 10
  • 82
  • 142