DarkLeafyGreen
DarkLeafyGreen

Reputation: 70476

UITableView with UIPanGestureRecognizer causes "jumping" touch

The case: https://github.com/ArtworkAD/ios_jumping_y.git

enter image description here

The Container

class ContainerController: UIViewController { 
    @IBOutlet weak var container: UIView!   
    @IBOutlet weak var foregroundTopSpace: NSLayoutConstraint!
    @IBOutlet weak var foregroundBottomSpace: NSLayoutConstraint!
}

The container is connected to the UITableViewController to its right.

What I want to do

The user scrolls the table and when he reaches the top I want to move the table down with the same movement. So I simply change the top/bottom space of the container view. However this causes the table to jump.

class MainController: UITableViewController, UIGestureRecognizerDelegate {

    var panRecognizer: UIPanGestureRecognizer!

    var gotStart: Bool = true

    var startTouch: CGPoint!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.bounces = false

        self.panRecognizer = self.tableView.panGestureRecognizer
        self.panRecognizer.addTarget(self, action: "pan:")
        self.tableView.addGestureRecognizer(self.panRecognizer)
    }

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

    func pan(rec:UIPanGestureRecognizer){

        if let parent = self.parentViewController as? ContainerController {

            var y = self.tableView.contentOffset.y
            var touch = rec.locationInView(self.view)

            // JUMPING y
            println(touch.y)

            if y == -64 {

                if self.gotStart  || y < -64 || y > -64 {

                    self.startTouch = touch
                    self.gotStart = false

                } else {

                    if rec.state == .Changed {

                        var changedVerticalHeight = (self.startTouch.y - touch.y)*(-1)

                        if changedVerticalHeight <= parent.view.frame.size.height && touch.y >= changedVerticalHeight {

                            parent.foregroundTopSpace.constant = changedVerticalHeight
                            parent.foregroundBottomSpace.constant = -changedVerticalHeight
                        }

                    } else if rec.state == .Ended {

                        parent.foregroundTopSpace.constant = 0
                        parent.foregroundBottomSpace.constant = 0

                        self.gotStart = true
                    }
                }
            }
        }
    }
}

Result:

enter image description here

Any ideas whats wrong? There has to be some problem with the container view because when I move the table view directly there is no jumping.

Upvotes: 0

Views: 2284

Answers (3)

Victor Sigler
Victor Sigler

Reputation: 23459

I going to clarify some things in my answer before I put the complete code

  1. You can use the function scrollViewWillEndDragging to know where exactly the contentOffset is going to, see the below code :

    override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
       println(targetContentOffset.memory.y)        
    }
    
  2. You can use the function scrollViewDidEndDecelerating to know when the scroll has finished it.

Then you can merge the two above tips to accomplish your goal in the following way:

class MainController: UITableViewController {

   var position : CGFloat!

   override func viewDidLoad() {
      super.viewDidLoad()       
   }

   override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {  
      // Save where the contentOffset go        
      position = targetContentOffset.memory.y
   }   

   override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {

       // If reach the top or more than the top when the user move the scroll
       if (position < 0) {               
           var to = scrollView.contentOffset.y + 213
           scrollView.setContentOffset(CGPoint(x: 0, y: to), animated: true)
       }        
   }    

   // Default table stuff, not related to problem  

   /* Here comes the rest of your code of TableView */    
}

I remove some code for your project relative to the panGestureRecognizer and bounces to test.

In the line var to = scrollView.contentOffset.y + 213 you can add the position variable or the value you want, I put 213 only to test the down movement.

I hope this help you.

Upvotes: 4

Taras Chernyshenko
Taras Chernyshenko

Reputation: 2829

I believe that exactly what you want:

import UIKit

class MainController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        let beginingContentOffset: CGFloat = -64
        if scrollView.contentOffset.y < beginingContentOffset {
            scrollView.bounces = false
            UIView.animateWithDuration(0.05, animations: { () -> Void in
                scrollView.contentOffset = CGPointMake(0, 0)
                }, completion: { (finished: Bool) -> Void in
                scrollView.bounces = true
            })
        }
    }

    // Default table stuff, not related to problem

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 20
    }

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell_ : UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("CELL_ID") as? UITableViewCell
        if(cell_ == nil){
            cell_ = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CELL_ID")
        }

        cell_!.textLabel!.text = "Row " + String(indexPath.row)

        return cell_!
    }
}

P.S. That "-64" value. It's margin from storyboard. You can uncheck margins for constraint and it will be "0". It will be much clearest

Upvotes: 0

torcelly
torcelly

Reputation: 516

You're printing the location from self.view system coordinates. The UITableViewController use a table view like self.view. So the origin (x:0 y:0) start to move when the user scrolling.

You have to user a static system coordinates. Use the UIViewController and UITableView panGestureRecognizer property for print position correctly and get the scrolling:

  1. Create a new UIViewController.
  2. Add UITableview and setup AutoLayout.
  3. Connect the IBOutlet with the UITableView

Add the code to the UIViewController class:

var panRecognizer: UIPanGestureRecognizer!
@IBOutlet weak var tableView: UITableView?

override func viewDidLoad() {
    super.viewDidLoad()

    self.panRecognizer = tableView?.panGestureRecognizer;
    self.panRecognizer .addTarget(self, action: "pan:")
    tableView?.addGestureRecognizer(self.panRecognizer)

}

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

func pan(rec:UIPanGestureRecognizer){
    println(rec.locationInView(self.view).y)
}

You can download the project example here.

If you just need control the scrolling, remember that UIScrollView delegate method:

optional func scrollViewDidScroll(_ scrollView: UIScrollView)

can help you. It's a cleaner a simpler solution:

Good luck.

Upvotes: 1

Related Questions