7

I just discovered that if I do the following:

  1. Click the button that animates a UIPickerView into my view
  2. Quickly start the wheel rolling towards, then past, the last item
  3. Dismiss the view with a button

Then it has not yet selected the last item yet.

I tried this by simply outputting to the console whenever the didSelectRow method was fired, and it fires when the wheel stabilizes on the last item.

Can I detect that the wheel is still rolling, so that I can delay checking it for a selected value until it stabilizes?

If it matters, I'm programming in MonoTouch, but I can read Objective-C code well enough to reimplement it, if you have a code example that is.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825

7 Answers7

8

As animation keys don't work, I wrote this simple function that works for detecting if a UIPickerView is currently moving.

-(bool) anySubViewScrolling:(UIView*)view
{
    if( [ view isKindOfClass:[ UIScrollView class ] ] )
    {
        UIScrollView* scroll_view = (UIScrollView*) view;
        if( scroll_view.dragging || scroll_view.decelerating )
        {
            return true;
        }
    }

    for( UIView *sub_view in [ view subviews ] )
    {
        if( [ self anySubViewScrolling:sub_view ] )
        {
            return true;
        }
    }

    return false;
}

It ends up returning true five levels deep.

Drainboy
  • 101
  • 1
  • 2
  • Great, do you think that would be achievable with a listener pattern. I mean listening when the Picker starts dragging or decelerating and when it ends? – Żabojad Feb 26 '19 at 13:03
6

Swift 4 (updated) version with extension of @DrainBoy answers

extension UIView  {
 func isScrolling () -> Bool {

    if let scrollView = self as? UIScrollView {
        if  (scrollView.isDragging || scrollView.isDecelerating) {
            return true
        }
    }

    for subview in self.subviews {
        if ( subview.isScrolling() ) {
            return true
        }
    }
    return false
 }
}
iluvatar_GR
  • 1,017
  • 13
  • 19
3

Since animationKeys seems to not work anymore, I have another solution. If you check the subviews of UIPickerView, you'll see that there is a UIPickerTableView for each component.

This UIPickerTableView is indeed a subclass of UITableView and of course of UIScrollView. Therefore, you can check its contentOffset value to detect a difference.

Besides, its scrollViewDelegate is nil by default, so I assume you can safely set an object of yours to detect scrollViewWillBeginDragging, scrollViewDidEndDecelerating, etc.

By keeping a reference to each UIPickerTableView, you should be able to implement an efficient isWheelRolling method.

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
Romano
  • 320
  • 3
  • 5
  • well, "scrollViewDelegate is nil by default" doesn't seem to be the case at least for UIDatePicker on iOS 13. Resetting it leads to disabling it's main purpose – glassomoss Aug 20 '20 at 19:43
3

Expanded @iluvatar_GR answer

extension UIView { 
func isScrolling () -> Bool {

    if let scrollView = self as? UIScrollView {
        if (scrollView.isDragging || scrollView.isDecelerating) {
            return true
        }
    }

    for subview in self.subviews {
        if ( subview.isScrolling() ) {
            return true
        }
    }
    return false
}
func waitTillDoneScrolling (completion: @escaping () -> Void) {
    var isMoving = true
    DispatchQueue.global(qos: .background).async {
    while isMoving == true {
        isMoving = self.isScrolling()
    }
        DispatchQueue.main.async {
            completion()}

    }
}
}
  • Updating `isMoving` should probably be enclosed in `DispatchQueue.main.sync { ... }`, otherwise there will be runtime warnings about accessing GUI components from a background thread. – Lauri Nurmi Jul 01 '21 at 11:56
1

Expanded @iluvatar_GR, @Robert_at_Nextgensystems answer

Used Gesture, UIScrollView isDragging or isDecelerating.

// Call it every time when Guesture action.
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
    // Changes the button name to scrolling at the start of scrolling.
    DispatchQueue.main.async {
       self._button.setTitle("Scrolling...", for: .normal)
       self._button.isEnabled = false
       self._button.backgroundColor = Utils.hexStringToUIColor(hex: "FF8FAE")
    }

    // Indication according to scrolling status
    _datePicker.waitTillDoneScrolling(completion: {
        print("completion")
        DispatchQueue.main.async {
           self._button.setTitle("Completion", for: .normal)
           self._button.isEnabled = true
           self._button.backgroundColor = Utils.hexStringToUIColor(hex: "7CB0FF")
        }
    })
}

[SWIFT4] Share Example Source link!

enter Sample Source link

Dev.Faith
  • 49
  • 4
-1

You can use a SwipeGestureRecognizer on the picker. I assume this is not a perfect solution at all.

- (void)viewDidLoad
{
    [super viewDidLoad];

    _pickerSwipeGestureRecognizer.delegate = self;
    [_pickerSwipeGestureRecognizer setDirection:(UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp)];
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if([gestureRecognizer isEqual:_pickerSwipeGestureRecognizer]){
        NSLog(@"start");
    }
}

- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    NSLog(@"end");
}
miskin
  • 1
-1

I think you can just check if the UIPickerView is in the middle of animating and wait for it to stop. This was answered here link

Community
  • 1
  • 1
Joel Kravets
  • 2,473
  • 19
  • 16
  • 1
    And how would you "check" the animation keys? Can you perhaps provide a more complete answer, one that would elaborate on such a methodology please. I came across your answer hoping to answer one question but I'm leaving with more questions now. – Pavan Sep 06 '14 at 08:10
  • AnimationKeys doesn't seem to work in 2015. DrainBoy's answer below worked for me, after I converted his code to Swift. – ObjectiveTC Jul 31 '15 at 05:10