52inc / Pulley

A library to imitate the iOS 10 Maps UI.
https://cocoapods.org/pods/Pulley
MIT License
2.02k stars 265 forks source link

Tapping inside the UISearchBar's UITextField will cause the Drawer Height to increase #318

Open PatrickPichu opened 5 years ago

PatrickPichu commented 5 years ago

I posted this on Dec. 19 2018, under a related Issue #257. After waiting over two months, with no response from the developer, I am starting a new issue. Hopefully the developer will respond to this post..

Repeatedly tapping inside the UISearchBar's text field (after it has become FirstResponder), will cause the Drawer Height to increase. With each subsequent tap in the textfield, the bottom of the drawer will move further and further toward the bottom of the iPad screen, eventually completely moving off the bottom of the screen. At which point, the drawer can no longer be closed, because the gripper bar has moved off the screen. This is easily reproducible in your PulleyDemo test project.

Below is a description of the issue, as documented in my personal project.

PulleyVC’s UIScrollViewDelegate method, scrollViewDidScroll, is being called after the tap. This method calls syncDrawerContentViewSizeToMatchScrollPositionForSideDisplayMode. Within syncDrawerContentViewSizeToMatchScrollPositionForSideDisplayMode, the value of drawerScrollView.contentOffset.y can be seen to have increased by 92 units, instead of its normal value of 0 units. This value is used to calculate the frame for the drawerContentContainer(which is a UIView). Thus increasing the height of the drawer by 92 units. This happens each time the textfield is tapped.

A similar thing happens in the PulleyDemo project. In your demo project, drawerScrollView.contentOffset.y starts at 0, and increases to 810, after the first tap. After each subsequent tap, the value can be observed to continually increase, after each tap in the textfield.

Why is the drawerScrollView.contentOffset.y increasing with every tap in the textfield, after it has already been made FirstResponder? Is there a way to completely prevent the Drawer Height from increasing, when the textfield is tapped? In your test project, the inital tap (setting the textfield as First responder), causes the drawer to change to .open case. Is there a way to prevent any change to the Drawer Height, when tapping first time, or subsequent times in the textfield?

amyleecodes commented 5 years ago

@ulmentflam Can you take a look at this when you get a few minutes?

ulmentflam commented 5 years ago

@PatrickPichu to give you an update on the status of this issue, I have been looking into it and have discovered this issue is not related to the position of the text field changing to the .open case. In the demo project, I placed a guard against setting the drawer position in searchBarTextDidBeginEditing if the current position is .open, however, that is not the reason the drawerScrollView.contentOffset.y is increasing after it has been made the FirstResponder. I am now looking to see if the issue is related to automatic adjustments that the scrollView can make when the keyboard opens. I will let you know when I figure out more. For reference, the position is being set to the .open here in the demo project.

PatrickPichu commented 5 years ago

@ulmentflam Thanks for your response.

I have implemented this work around for another TextField related problem (On an iPhone, when typing in the textfield, the drawer would drop down behind the virtual keyboard. So the keyboard would obscure the textfield and drawer). I had to add the following code to PulleyViewController. Note, this is just a work around, it is not really a solution to this particular textfield problem.

    /// added property for disabling code in viewDidLayoutSubviews when keyboard is on screen
    fileprivate var isAllowedToLayoutSubviews = true

    override open func viewDidLoad() {
        super.viewDidLoad()
        /// added observing for disabling/endabling code in viewDidLayoutSubviews when keyboard is on/off screen
        NotificationCenter.default.addObserver(self, selector: #selector(toggleIsAllowedToLayoutSubviews(notification: )), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(toggleIsAllowedToLayoutSubviews(notification: )), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    /// added selector to toggle isAllowedToLayoutSubviews based on which notification was received
    @objc func toggleIsAllowedToLayoutSubviews(notification: Notification) {
        if notification.name == UIResponder.keyboardWillShowNotification {
            isAllowedToLayoutSubviews = false
        }
        else if notification.name == UIResponder.keyboardWillHideNotification {
           isAllowedToLayoutSubviews = true
        }
    }

    override open func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        // added check to determine if layout subview modifications should be made
        guard isAllowedToLayoutSubviews else { return }
mvalentiner commented 5 years ago

@PatrickPichu regarding the keyboard, I had a similar issue in my project. I implemented my own custom DrawerContentViewController with included a UISearchBar. When the text field would become FirstResponder (tap in the text field), the keyboard would popup obscuring the drawer's drag control. I added the following code to handle this and set the drawer position behaviors I wanted in my app.

// MARK: PulleyViewController extension - add functionality to respond to hide/show events
extension PulleyViewController {
    override open func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillAppear), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillDisappear), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    @objc func handleKeyboardWillAppear(notification: NSNotification) {
        guard let userInfo = notification.userInfo, let rect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
            return
        }
        // Resize the view to accommodate the height of the keyboard popping up.
        let height = rect.size.height
        let frame = view.frame
        let newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: frame.size.height - height)
        view.frame = newFrame

        // If the drawer is not, at least, partially open, open it.
        guard drawerPosition != .open &&  drawerPosition != .partiallyRevealed else {
            return
        }
        setDrawerPosition(position: .partiallyRevealed, animated: true)
    }

    @objc func handleKeyboardWillDisappear(notification: NSNotification) {
        guard let userInfo = notification.userInfo, let rect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
            return
        }
        // Resize the view to accommodate the height of the keyboard going away.
        let height = rect.size.height
        let frame = view.frame
        let newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: frame.size.height + height)
        view.frame = newFrame

        setDrawerPosition(position: .collapsed, animated: true)
    }
}

@ulmentflam @brendan09 I would submit this in a PR if I knew where to put it. The current demo App doesn't need it because it doesn't have the keyboard issue, but other apps that don't use the Demo App storyboard might.