scenee / FloatingPanel

A clean and easy-to-use floating panel UI component for iOS
MIT License
5.6k stars 509 forks source link

Dragging on content placed above the trackedView resets scroll position #526

Closed dudek-j closed 2 years ago

dudek-j commented 2 years ago

Description

My contentViewController consists of a UISearchBar placed above a UITableView. When dragging down the contentViewController while holding the search bar, upon release the contentOffset of the tracked tableView gets reset to the top.

If we instead use the grabber itself and carefully drag using that, the contentOffset persists correctly.

I was able to mitigate this issue somehow by increasing the surfaceView.grabberAreaOffset to match the height of the UISearchbar. This work if the user finger remains in the grabberArea. Unfortunately, if the users finger exists the grabberArea during the swipe (this happens if we swipe down quickly), the contentOffset is reset.

Expected behavior

I should be able to drag the floating panel using static child elements without affecting the scroll position of the tracked scrollView.

Actual behavior

If the contentViewController contains views other than the tracked scroll view, the offset of the scroll view is reset every time we drag using one of those views.

Steps to reproduce

Additional Example

Code example that reproduces the issue

import UIKit
import FloatingPanel

class ViewController: UIViewController {

    private lazy var floatingPanelController: FloatingPanelController = {
        let fpc = FloatingPanelController()
        fpc.set(contentViewController: tableViewController)
        fpc.track(scrollView: tableViewController.tableView)

        return fpc
    }()

    private lazy var tableViewController: TableViewController = {
        let vc = TableViewController()
        return vc
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .yellow

        view.addSubview(floatingPanelController.view)
        floatingPanelController.view.frame = view.bounds
        addChild(floatingPanelController)
        floatingPanelController.didMove(toParent: self)
        floatingPanelController.show()
    }

}

class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .red
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.dataSource = self
        tableView.delegate = self

        view.addSubview(tableView)

        NSLayoutConstraint.activate([ 
            tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }

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

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = "\(indexPath.row)"
        return cell
    }
} 

How do you display panel(s)?

How many panels do you displays?

Environment

Library version

Tested on 2.0.1 and 2.5.1 Issues occur on both

Installation method

iOS version(s) 15.2

Xcode version 13.2.1

dudek-j commented 2 years ago

After looking into this issue further it seems like setting the grabberAreaOffset is the intended solution to my issue. This should perhaps be added to the documentation and perhaps the maps example.

Unfortunately the second part of the bug is still there, if the pan interaction exits the grabber frame during the gesture the content offset is reset. This can fairly easily replicated in the simulator as shown in the video below.

https://user-images.githubusercontent.com/16480280/150013177-d1c883c5-5616-4472-a3bc-a589b3ce633b.mov

After poking around I noticed that the contentOffset reset occurs specifically in this code fragment.

image

It seems like using the current scrollView offset instead of the initial one solves the issue while still stopping the scrolling as intended

image

I am not sure if this has any potential side effects. I created a corresponding PR.

dudek-j commented 2 years ago

Resolved in 2.5.2 🎉