Skip to content

Commit

Permalink
Update bottom sheet to include option to listen to keyboard notificat…
Browse files Browse the repository at this point in the history
…ions and adjust height accordingly (#272)

* Update bottom sheet to include option to listen to keyboard notifications and adjust height accordingly

* Bump version in podspec to 3.5.1

Co-authored-by: Ryan Cole <ryan_cole@intuit.com>
  • Loading branch information
rcole34 and Ryan Cole authored Apr 28, 2021
1 parent 75c301d commit 5eebfc2
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CardParts.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'CardParts'
s.version = '3.5.0'
s.version = '3.5.1'
s.platform = :ios
s.summary = 'iOS Card UI framework.'

Expand Down
63 changes: 63 additions & 0 deletions CardParts/src/Classes/CardPartsBottomSheetViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ public class CardPartsBottomSheetViewController: UIViewController {
/// Gesture recognizers that should block the vertical dragging of bottom sheet. Will automatically find and use all gesture recognizers if nil, otherwise will use recognizers in the array. Default is empty array.
public var preferredGestureRecognizers: [UIGestureRecognizer]? = []

/// If there is a text field in the bottom sheet we may want to automatically have the bottom sheet adjust for the keyboard. Default is false
public var shouldListenToKeyboardNotifications: Bool = false {
didSet {
if shouldListenToKeyboardNotifications {
listenToKeyboardShowHideNotifications()
} else {
stopListeningToKeyboardShowHideNotifications()
}
}
}

// MARK: Private variables
private var bottomSheetContainerVC: UIViewController = UIViewController()
private var bottomSheetHeight: CGFloat = 0
Expand All @@ -119,6 +130,8 @@ public class CardPartsBottomSheetViewController: UIViewController {
private var bottomSheetTopConstraint: NSLayoutConstraint!
private var viewTopConstraint: NSLayoutConstraint!
private var _contentHeight: CGFloat = 0
private var keyboardHeight: CGFloat? = nil
private var animateKeyboardDismiss: Bool = true // set to false when dismissing the whole bottom sheet otherwise animations conflict
private var isShowingBottomSheet: Bool = false

// MARK: Public functions
Expand Down Expand Up @@ -196,6 +209,7 @@ public class CardPartsBottomSheetViewController: UIViewController {
self.bottomSheetTopConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: { finished in
self.animateKeyboardDismiss = true
self.teardown()
self.didDismiss?(dismissalType)
self.isShowingBottomSheet = false
Expand Down Expand Up @@ -236,6 +250,11 @@ public class CardPartsBottomSheetViewController: UIViewController {
let window = UIApplication.shared.keyWindow
bottomSheetHeight += window?.safeAreaInsets.bottom ?? 0
}

// check for keyboard
if self.shouldListenToKeyboardNotifications, let keyboardHeight = self.keyboardHeight {
bottomSheetHeight += keyboardHeight
}
}

/// Sets up dark overlay.
Expand Down Expand Up @@ -326,6 +345,41 @@ public class CardPartsBottomSheetViewController: UIViewController {
}
}

// MARK: keyboard notifications
extension CardPartsBottomSheetViewController {
private func listenToKeyboardShowHideNotifications() {
// remove and re-add to avoid setting up duplicate listeners
stopListeningToKeyboardShowHideNotifications()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}

private func stopListeningToKeyboardShowHideNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
notificationCenter.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc func keyboardWillShow(notification: Notification) {
guard var keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }

keyboardRect = view.convert(keyboardRect, from: nil)
keyboardHeight = keyboardRect.height
DispatchQueue.main.async { [weak self] in
self?.updateHeight()
}
}

@objc open func keyboardWillHide(notification: Notification) {
keyboardHeight = nil
guard animateKeyboardDismiss else { return }
DispatchQueue.main.async { [weak self] in
self?.updateHeight()
}
}
}

// MARK: Gesture Recognizers
extension CardPartsBottomSheetViewController: UIGestureRecognizerDelegate {
/// Sets up gesture recognizers for drags and overlay tap.
Expand Down Expand Up @@ -363,6 +417,10 @@ extension CardPartsBottomSheetViewController: UIGestureRecognizerDelegate {
dismissBottomSheet(.programmatic(info: ["error": "closing due to nil constraints"]))
return
}
// dismiss keyboard on drag if we're listening to that
if shouldListenToKeyboardNotifications {
UIResponder.first?.resignFirstResponder()
}
let touchHeight = self.view.bounds.height - recognizer.location(in: self.view).y
let heightPercentage: CGFloat = -self.bottomSheetTopConstraint.constant / self.bottomSheetHeight
switch recognizer.state {
Expand Down Expand Up @@ -401,6 +459,11 @@ extension CardPartsBottomSheetViewController: UIGestureRecognizerDelegate {

/// Gesture recognizer for a tap in the overlay.
@objc private func tapInOverlay(recognizer: UITapGestureRecognizer) {
// dismiss keyboard if we're listening to that but don't update bottom sheet height to avoid conflicting animations with dismissal
if shouldListenToKeyboardNotifications {
animateKeyboardDismiss = false
UIResponder.first?.resignFirstResponder()
}
dismissBottomSheet(.tapInOverlay)
}

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,7 @@ There are also over two dozen other properties that you can set to further custo
- `var didDismiss: ((_ dismissalType: BottomSheetDismissalType) -> Void)?`: Callback function to be called when bottom sheet is done dismissing itself. Parameter `dismissalType`: information about how the bottom sheet was dismissed - `.tapInOverlay`, `.swipeDown`, `.programmatic(info)`.
- `var didChangeHeight: ((_ newHeight: CGFloat) -> Void)?`: Callback function to be called when bottom sheet height changes from dragging or a call to `updateHeight`.
- `var preferredGestureRecognizers: [UIGestureRecognizer]?`: Gesture recognizers that should block the vertical dragging of bottom sheet. Will automatically find and use all gesture recognizers if nil, otherwise will use recognizers in the array. Default is empty array.
- ` var shouldListenToKeyboardNotifications: Bool`: If there is a text field in the bottom sheet we may want to automatically have the bottom sheet adjust for the keyboard. Default is false.

If you change the `contentVC` or `contentHeight` properties, the bottom sheet will automatically update its height. You can also call `updateHeight()` to trigger an update of the height (this is mainly for if the content of the `contentVC` has changed and you want the bottom sheet to update to match the new content size).

Expand Down

0 comments on commit 5eebfc2

Please sign in to comment.