Search code examples
iosswiftuisegmentedcontrol

UISegmentedControl deselect / reset after view disappears


I'm trying to fix a small bug. I have an UISegmentedControl that stays showing user interaction if I navigate back while pressing a segment (without releasing the finger which is selecting the segment from the screen): enter image description here

I tried to deselect the segment on viewWillDisappear but I doesn't make a difference. Any ideas on how to reset the state of the UISegmentedControl?

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    fixedPositionSegmentControl.selectedSegmentIndex = UISegmentedControl.noSegment
    fixedPositionSegmentControl.selectedSegmentIndex = 0
}

Solution

  • The problem is that in this specific case (leaving the screen while touching the control) the segmented control's touchesEnded / touchesCancelled functions do not get called. So you could cancel the touch programmatically:

    override func viewDidDisappear(_ animated: Bool) {
        segmentedControl.touchesCancelled(Set<UITouch>(), with: nil)
        super.viewDidDisappear(animated)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        segmentedControl.selectedSegmentIndex = 0
    }
    

    Subclassing UISegmentedControl might even be the cleaner (but maybe oversized) approach:

    class SegmentedControl: UISegmentedControl {
    
        // property to store the latest touches
        private var touches: Set<UITouch>?
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesBegan(touches, with: event)
            self.touches = touches
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesMoved(touches, with: event)
            self.touches = touches
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesEnded(touches, with: event)
            self.touches = nil
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesCancelled(touches, with: event)
            self.touches = nil
        }
    
        override func didMoveToWindow() {
            // cancel pending touches when the view is removed from the window
            if window == nil, let touches = touches {
                touchesCancelled(touches, with: nil)
            }
        }
    
    }
    

    With that approach you can simply reset the index in viewWillAppear:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        segmentedControl.selectedSegmentIndex = 0
    }