简体   繁体   中英

iOS - position view set distance from bottom of screen or keyboard (if it is showing) without subscribing to keyboard notifications

In Android you can use a RelativeLayout and pin a view (such as a button) to the bottom of it using layout_alignParentBottom . When the keyboard appears, the view will be right above it.

Is there a way to do this in iOS without subscribing to keyboard events and adjusting the view's constraint depending on the keyboard height?

I ask because all the examples I see subscribe to keyboard events. But this is rather buggy from my experience. You have to not only subscribe to keyboard events but app wide events too.

EDIT: For clarification I am asking about doing exactly what I asked. Not adding an inputAccessoryView . I would like a view (whether it is a button, image, or anything else) to always stay a set distance from the bottom of the screen. When the keyboard appears, I want that view to then be that same set distance from the top of the keyboard. If the keyboard disappears, the view will move back down to its original position.

There is currently no other way. Showing a keyboard is an app-wide event and is so by design. You can get notifications about frame changes and then layout your views accordingly when needed. There are tools that may help you such as mentioned IQKeyboard but note this tool may and did get buggy on multiple iOS versions and/or devices so you need to keep updating the library. It is also very hackish but it does work without you needing much work.

From keyboard notifications you can make higher level tools to make things much easier. What I used some time ago was:

import UIKit

protocol KeyboardManagerDidChangeVisibleDelegate: class {
    func keyboardManagerChangedKeyboardVisible(sender: KeyboardManager, visible: Bool)
}
protocol KeyboardManagerWillChangeFrameDelegate: class {
    func keyboardManagerWillChangeKeyboardFrame(sender: KeyboardManager, from startFrame: CGRect, to endFrame: CGRect)
}
protocol KeyboardManagerDidChangeFrameDelegate: class {
    func keyboardManagerDidChangeKeyboardFrame(sender: KeyboardManager, from startFrame: CGRect, to endFrame: CGRect)
}

class KeyboardManager {

    var keyboardVisible: Bool = false
    var keyboardFrame: CGRect = CGRect.zero

    var visibilityDelegate: KeyboardManagerDidChangeVisibleDelegate?
    var willChangeFrameDelegate: KeyboardManagerWillChangeFrameDelegate?
    var didChangeFrameDelegate: KeyboardManagerDidChangeFrameDelegate?

    static var sharedInstance: KeyboardManager = {
        let manager = KeyboardManager(isShared: true)
        return manager
    }()

    deinit {
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillChangeFrame, object: nil)
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
    }

    convenience init() {
        self.init(isShared: false)

    }

    private init(isShared: Bool) {
        attachNotifications()

        if isShared == false {
            keyboardVisible = KeyboardManager.sharedInstance.keyboardVisible
            keyboardFrame = KeyboardManager.sharedInstance.keyboardFrame
        }
    }

    private func attachNotifications() {

        NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardChange), name: .UIKeyboardWillChangeFrame, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillShow), name: .UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillHide), name: .UIKeyboardWillHide, object: nil)

    }

    @objc private func onKeyboardChange(notification: NSNotification) {
        guard let info = notification.userInfo else {
            return
        }
        guard let value: NSValue = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
            return
        }
        guard let oldValue: NSValue = info[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
            return
        }

        let newFrame = value.cgRectValue
        self.keyboardFrame = newFrame

        let oldFrame = oldValue.cgRectValue

        if let durationNumber = info[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, let keyboardCurveNumber = info[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber {
            let duration = durationNumber.doubleValue
            let keyboardCurve = keyboardCurveNumber.intValue
            let curve: UIViewAnimationCurve = UIViewAnimationCurve(rawValue: keyboardCurve) ?? .linear
            let options = UIViewAnimationOptions(rawValue: UInt(curve.rawValue << 16))

            UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
                self.willChangeFrameDelegate?.keyboardManagerWillChangeKeyboardFrame(sender: self, from: oldFrame, to: newFrame)
            }, completion: { _ in
                self.didChangeFrameDelegate?.keyboardManagerDidChangeKeyboardFrame(sender: self, from: oldFrame, to: newFrame)
            })
        } else {
            self.willChangeFrameDelegate?.keyboardManagerWillChangeKeyboardFrame(sender: self, from: oldFrame, to: newFrame)
            self.didChangeFrameDelegate?.keyboardManagerDidChangeKeyboardFrame(sender: self, from: oldFrame, to: newFrame)
        }
    }
    @objc private func onKeyboardWillShow(notification: NSNotification) {
        self.keyboardVisible = true
        self.visibilityDelegate?.keyboardManagerChangedKeyboardVisible(sender: self, visible: self.keyboardVisible)
    }
    @objc private func onKeyboardWillHide(notification: NSNotification) {
        self.keyboardVisible = false
        self.visibilityDelegate?.keyboardManagerChangedKeyboardVisible(sender: self, visible: self.keyboardVisible)
    }

}

What I do here is subscribe to will appear delegate KeyboardManager.sharedInstance.willChangeFrameDelegate = self in view will appear method of view controller. Then the implementation is simply:

extension MyViewController: KeyboardManagerWillChangeFrameDelegate {

    func keyboardManagerWillChangeKeyboardFrame(sender: KeyboardManager, from startFrame: CGRect, to endFrame: CGRect) {
        panelBottomConstraint?.constant = view.bounds.height-max(0, view.convert(endFrame, from: nil).origin.y)
    }

}

So still using constraints but since manager is imported only once the amount of code when using it is relatively small. This should already animate your view as well alongside keyboard which is nice.

As for being buggy this procedure has never failed me but in general working with keyboard in iOS is always a pain and is very easy to produce bugs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM