Изменить сенсорный приемник при прокрутке прокрутки

Такие приложения, как приложение карт Apple или карты Google, используют прокручиваемые наложения нижнего листа для представления дополнительного контента. Хотя это поведение не так уж сложно перестроить, я изо всех сил пытаюсь реализовать одну важную функцию:

Когда внутри нижнего листа встроено представление прокрутки, пользователь может прокручивать его вверх, но затем — вместо того, чтобы отскакивать вверх — нижний лист начинает прокручиваться вниз вместо табличного представления.

Вот пример видео того, что я имею в виду:

Пример видео:

Пример нижнего листа

Это приятный пользовательский опыт, поскольку прокрутка не прерывается, и это то, что я ожидаю как пользователь: это как если бы представление прокрутки контента достигло своей вершины, приемник жестов автоматически передает представление суперпрокрутки.


Чтобы добиться такого поведения, я вижу три разных подхода:

  1. Я отслеживаю представление прокрутки содержимого contentOffset в методе делегата представления прокрутки scrollViewDidScroll(_:). Тогда я делаю

    if contentScrollView.contentOffset.y < 0 {
        contentScrollView.contentOffset.y = 0
    }
    

    чтобы прокрутка содержимого не прокручивалась над верхней частью его содержимого. Вместо этого я передаю расстояние по оси y, которое было бы прокручено до режима суперпрокрутки, который прокручивает весь нижний лист.

    1. Я нахожу способ изменить получателя распознавателя жестов прокрутки (панорамирования) с представления прокрутки содержимого на представление суперпрокрутки, как только представление прокрутки содержимого прокрутится до его вершины.

    2. Я обрабатываю все внутри представления супер прокрутки. Он запрашивает свой контроллер представления контента через протокол делегата, хочет ли он обрабатывать касания, и только если он этого не делает (поскольку его представление прокрутки содержимого достигло вершины), представление суперпрокрутки прокручивается само по себе.


Хотя мне удалось реализовать первый вариант (это то, что вы видите в видео), я бы предпочел использовать подход 2 или 3. Это гораздо более чистый способ, чтобы контроллер представления, который управляет нижним листом, управлял всей прокруткой. логика, не раскрывая ее внутренностей.

К сожалению, я не нашел способа как-то разделить жест панорамирования на два компонента (один, который управляет видом прокрутки приемника, и другой, который управляет другим видом прокрутки)

Любые идеи о том, как добиться такого поведения?


person Mischa    schedule 20.05.2018    source источник
comment
touchesBegan touchesMoved и touchesEnded будут вызываться, чтобы вы могли отслеживать начальную точку первого касания, и если contentOffset попал в цель, начните движение на расстояние currentTouch от точки, которая maxContentOffset. Затем, если двигаться в другую сторону, вы можете проверить, сколько переводов произошло, и пойти в другую сторону, прежде чем разрешать прокрутку. Я действительно думаю, что прокрутка никогда не теряет прикосновение, потому что индикатор прокрутки присутствует все время. Я также хотел бы знать это, и правильно ли то, что я говорю. Если это то, как смещение контента заблокировано так хорошо?   -  person agibson007    schedule 20.05.2018


Ответы (1)


Меня очень интересует этот вопрос, и я надеюсь, предоставив, как я буду его реализовывать, он не задушит ответ, который может показать, как действительно передать ответчику. Уловка, которую я описал в комментариях, заключается в отслеживании прикосновений. Я забыл о том, как прокрутка проглатывает их, но вы можете использовать UIPanGesture. Посмотрите, близко ли это к тому, что вы ищете. Единственный случай, с которым я столкнулся, который может потребовать больше размышлений, — это использование прокрутки для закрытия вида снизу. Большая часть этого кода настроена для получения рабочего прокрутки в представлении. Я думаю, что анимация свойств может быть лучше, чтобы сделать ее прерываемой, или даже мои любимые анимации Facebook Pop. Для простоты я просто использовал анимацию UIView. Дайте мне знать, если это решит то, что вы ищете. Код приведен ниже, а вот результат sogif. Scrollview остается прокручиваемым и активным. Я анимирую кадры, но обновление ограничений также может работать.

import UIKit

    class ViewController: UIViewController{
        //setup
        var items : [Int] = []
        lazy var tableView : UITableView = {
            let tv = UITableView(frame: CGRect(x: 0, y: topViewHeight, width: self.view.frame.width, height: self.view.frame.height))
            tv.autoresizingMask = [.flexibleWidth,.flexibleHeight]
            tv.delegate = self
            tv.dataSource = self
            tv.layer.cornerRadius = 4
            return tv
        }()

        lazy var topView : UIView = {
            let v = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: topViewHeight))
            v.backgroundColor = .green
            v.autoresizingMask = [.flexibleWidth,.flexibleHeight]
            return v
        }()

        let cellIdentifier = "ourCell"



        //for animation
        var isAnimating = false
        var lastOffset : CGPoint = .zero
        var startingTouch : CGPoint?
        let topViewHeight : CGFloat = 500
        var isShowing : Bool = false
        let maxCollapse : CGFloat = 50




        override func viewDidLoad() {
            super.viewDidLoad()

            for x in 0...100{
                items.append(x)
            }
            // Do any additional setup after loading the view, typically from a nib.
            self.view.addSubview(topView)
            self.view.addSubview(tableView)
            self.tableView.reloadData()

            let pan = UIPanGestureRecognizer(target: self, action: #selector(moveFunction(pan:)))
            pan.delegate = self
            self.view.addGestureRecognizer(pan)
        }

        @objc func moveFunction(pan:UIPanGestureRecognizer) {
            let point:CGPoint = pan.location(in: self.view)
            switch pan.state {
            case .began:
                startingTouch = point
                break
            case .changed:
                  processMove(touchPoint:point.y)
                break
            default:
                processEnding(currentPointY: point.y)
                break
            }
        }

    }

    extension ViewController : UITableViewDelegate,UITableViewDataSource {

        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return items.count
        }

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            var cell : UITableViewCell!
            cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
            if cell == nil {
                cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
            }
            cell.textLabel?.text = "\(items[indexPath.row])"
            return cell
        }

        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return 30
        }
    }

    extension ViewController : UIScrollViewDelegate{
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if isAnimating == true{
                scrollView.contentOffset = lastOffset
                return
            }
            lastOffset = scrollView.contentOffset
        }
    }

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

    extension ViewController{
        func processMove(touchPoint:CGFloat){
            if let start = startingTouch{
                if touchPoint <= topViewHeight && start.y > topViewHeight{
                    isAnimating = true
                    tableView.frame = CGRect(x: 0, y:touchPoint, width: self.view.frame.width, height: self.view.frame.height)
                    return
                }else if touchPoint >= self.maxCollapse && isShowing == true && start.y < self.maxCollapse{
                    isAnimating = true
                    tableView.frame = CGRect(x: 0, y:touchPoint, width: self.view.frame.width, height: self.view.frame.height)
                    return
                }else if isShowing == true && self.tableView.contentOffset.y <= 0{
                    //this is the only one i am slightly unsure about
                    isAnimating = true
                    tableView.frame = CGRect(x: 0, y:touchPoint, width: self.view.frame.width, height: self.view.frame.height)
                    return
                }
            }
            self.isAnimating = false
        }

        func processEnding(currentPointY:CGFloat){
            startingTouch = nil

            if isAnimating{
                if currentPointY < topViewHeight/2{
                    UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.0, options: .curveEaseInOut, animations: {
                        self.tableView.frame = CGRect(x: 0, y:self.maxCollapse, width: self.view.frame.width, height: self.view.frame.height)
                    }) { (finished) in
                        self.isShowing = true
                    }
                }else{
                    UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.0, options: .curveEaseInOut, animations: {
                        self.tableView.frame = CGRect(x: 0, y:self.topViewHeight, width: self.view.frame.width, height: self.view.frame.height)
                    }) { (finished) in
                        self.isShowing = false

                    }
                }
            }
            self.isAnimating = false
        }
    }
person agibson007    schedule 23.05.2018