2

Using UIPanGesture on a UITableView (Swift 4.0) to drag the tableview position (i.e. origin). UIPanGestureRecognizer detecting touch events and consuming them. At some point I want to delegate the events to table so it will scroll its items. How can I do so?

What have I tried:

@objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {

        if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {

            let translation = gestureRecognizer.translation(in: self.view)

// if origin moved to map origin it should not scroll above but cell should scroll normally. But returning does not help scrolling cells
            if self.tableView.frame.origin == self.mapContainer.frame.origin && translation.y < 0 {

                return
            }

            var frame = self.tableView.frame
            let nHeight = (frame.origin.y + translation.y)

            if (nHeight < self.view.frame.size.height) {

                frame.origin.y = nHeight
                self.tableView.frame = frame
                gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
            }
        }
}

Gesture added to tableView

let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.tableViewDragged(gestureRecognizer:)))
self.tableView.addGestureRecognizer(gesture)
self.tableView.isUserInteractionEnabled = true
gesture.delegate = self

Problem Summary:

For the same pan gesture at any point of tableview (i.e. on any cell), I need to scroll items when tableviews origin got fixed in top left corner of the container. Otherwise I have to move the tableview until its got attached to top left point. Moreover If If nothing to scroll down I need to again move the table below for pan down gesture.

*Problem Usecases: *

  1. initial condition - tableview origin is at middle of the screen, i.e. (0, Height/2)
  2. pan up gesture - table moved up vertically, but no item scrolls, until origin got stuck to (0,0)
  3. pan up gesture - cell items moved up but not table itself
  4. pan up gesture - cell items moved up until end of the cells
  5. pan down gesture - cell items moved down until index 0, lets say came to 0 index
  6. pan down gesture - tableview moved to down vertically until its origin reached to middle of the screen (0, Height/2)
Sazzad Hissain Khan
  • 37,929
  • 33
  • 189
  • 256

3 Answers3

3

Best way to do this using tableView's own panGestureRecognizer (without adding new gestureRecognizer), like:

tableView.panGestureRecognizer.addTarget(self, action: #selector(self.tableViewDragged(gestureRecognizer:))

Action

@objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {
    guard tableView.contentOffset.y < 0 else { return }
    if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {

    let translation = gestureRecognizer.translation(in: self.view)

    // if origin moved to map origin it should not scroll above but cell should scroll normally. But returning does not help scrolling cells
    if self.tableView.frame.origin == self.mapContainer.frame.origin && translation.y < 0 {

        return
    }

    var frame = self.tableView.frame
    let nHeight = (frame.origin.y + translation.y)

    if (nHeight < self.view.frame.size.height) {

        frame.origin.y = nHeight
        self.tableView.frame = frame
        gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
    }
}
}
SPatel
  • 4,768
  • 4
  • 32
  • 51
  • `tableView.panGestureRecognizer.delegate = self` crashing the app – Sazzad Hissain Khan May 31 '18 at 11:35
  • why you assign delegate. remove this line Just add may code in view did load – SPatel May 31 '18 at 11:47
  • To controll when to scroll and when to not. please refer to my actual problem. There are two cases (pan gesture should be applied on to tableview to move its position, pan gesture should be applied to scroll its items). How to controll this behavior? – Sazzad Hissain Khan May 31 '18 at 11:50
  • @SazzadHissainKhan for that you need to check tableview's contentOffset in your "tableViewDragged" method. How to control? refer this answer -> https://stackoverflow.com/a/48661043/6630644 – SPatel May 31 '18 at 11:53
  • It does not solve the problem. For the same pan gesture at any point of tableview (i.e. on any cell), I need to scroll items when tableviews origin got fixed in top left corner of the container. Otherwise I have to move the tableview until its got attached to top left point. Moreover If If nothing to scroll down I need to again move the table below for pan down gesture. – Sazzad Hissain Khan May 31 '18 at 12:00
1

I don't know a better solution than subclassing your tableView and blocks its contentOffset in the layoutSubviews of it. Something like :

class MyTableView: UITableView {
    var blocksOffsetAtZero = false
    override func layoutSubviews() {
        super.layoutSubviews()
        guard blocksOffsetAtZero else { return }
        contentOffset = .zero
    }
}

I think this is the solution used in the Maps app. When the user drags the tableView on the home screen, it does not scroll but lifts until it reaches the top of the screen and then scrolls again.

To do so, you add a gesture to your tableview which recognizes simultaneously with the recognizer of the tableView and blocks the contentOffset of the tableView each time the recognizer moves the frame of the tableView.

UPDATE https://github.com/gaetanzanella/mapLike

Did you implement the delegate correctly ?

// MARK: - UIGestureRecognizerDelegate

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                       shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

Try something like :

@objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {

    if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {

        let translation = gestureRecognizer.translation(in: self.view)

        var frame = self.tableView.frame
        let nHeight = (frame.origin.y + translation.y)

        if (nHeight < self.view.frame.size.height) {
            frame.origin.y = nHeight
            self.tableView.frame = frame
            gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
            tableView.blocksOffsetAtZero = true
        } else {
            tableView.blocksOffsetAtZero = false
        }
    }
}

The main idea : when you are changing the frame, block the scroll.

You could also use the delegate technique presented by kamaldeep singh bhatia but you will not be able to move first tableView then scroll in a single gesture.

See a example here

GaétanZ
  • 4,870
  • 1
  • 23
  • 30
  • It will block the gesture event but will it delegate the event to scroll the items? – Sazzad Hissain Khan May 31 '18 at 08:47
  • It will block the scroll, not the gesture. It will overwrite the behavior of the internal gesture recognizer which updates the contentOffset of the tableView and calls `setNeedsLayout()`. Maybe you could add code or more explanations about what you are trying to do. – GaétanZ May 31 '18 at 08:51
  • I am required not to make any subclass of tableview due to legacy complicacy. Is it possible to achieve similar behavior with UITableView? – Sazzad Hissain Khan May 31 '18 at 11:45
1

If I am understanding it correctly so you want to cancel PanGesture when you want to Scroll.

Try this :

func isItemAvailabe(gesture: UISwipeGestureRecognizer) -> Bool {
    if gesture.direction == .down {
        // check if we have some values in down if yes return true else false
    } else if gesture.direction == .up {
        // check if we have some values in up if yes return true else false
    }
    return false
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                       shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    if gestureRecognizer == self.panGesture && otherGestureRecognizer == self.scrollGesture && isItemAvailabe(gesture: otherGestureRecognizer) {
        return true
    }
    return false
}

Let me know if this solve your problem or I am not getting it correctly.

Also remember to add this too :

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

You can check more here

  • I dont have two gestures, I need to use tableViews scroll gesture and another pan gesture attached on same tableview. How can i get tableviews scrollGestureRecognizer? – Sazzad Hissain Khan May 31 '18 at 11:42
  • @SazzadHissainKhan check this ans, It will help you to get gestures of your tableView https://stackoverflow.com/a/9538266/5622566 – Kamaldeep singh Bhatia May 31 '18 at 11:57
  • here you'll need to check if it's a swipe (left/right) gesture then you can do your remaining stuffs. I think after init of tableView you can get your required gestures and play with that :) – Kamaldeep singh Bhatia May 31 '18 at 11:59
  • It does not solve the problem. For the same pan gesture at any point of tableview (i.e. on any cell), I need to scroll items when tableviews origin got fixed in top left corner of the container. Otherwise I have to move the tableview until its got attached to top left point. Moreover If If nothing to scroll down I need to again move the table below for pan down gesture. – Sazzad Hissain Khan May 31 '18 at 12:01
  • for that you can try a hack like, Check first if in your scrollDirection more items are remaining so say Pan to wait (return true) otherwise false – Kamaldeep singh Bhatia May 31 '18 at 12:10
  • It would be helpful if you put some code. even pseudocode. thanks for your time. – Sazzad Hissain Khan May 31 '18 at 12:12
  • where did you find self.scrollGesture? – Sazzad Hissain Khan Jun 01 '18 at 02:55
  • There is nothing like scroll gesture its just swipe gesture which I was saying to get from list of tableview gestures. See 2nd Comment. You need to takeout you tableview gesture and with the help of that you would be able to override it according to your choice. (I have used scroll for understanding. It'll be your swipe gesture of tableview) – Kamaldeep singh Bhatia Jun 01 '18 at 04:55