Sazzad Hissain Khan
Sazzad Hissain Khan

Reputation: 40265

UIPanGestureRecognizer on tableView does not scroll table items

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)

Upvotes: 2

Views: 4767

Answers (3)

Ga&#233;tanZ
Ga&#233;tanZ

Reputation: 4930

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

Upvotes: 1

Kamaldeep singh Bhatia
Kamaldeep singh Bhatia

Reputation: 732

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

Upvotes: 1

SPatel
SPatel

Reputation: 4956

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)
    }
}
}

Upvotes: 3

Related Questions