From bc80b172ef4530da05f138690022a97061129a67 Mon Sep 17 00:00:00 2001 From: martinpucik Date: Mon, 16 May 2022 15:30:26 +0200 Subject: [PATCH 1/2] feat: `additionalInputViewBottomConstraintConstant` in KeyboardManager --- Example/Example.xcodeproj/project.pbxproj | 24 +++- ...onalBottomSpaceExampleViewController.swift | 71 ++++++++++++ .../CommonTableViewController.swift | 0 .../InputAccessoryExampleViewController.swift | 0 .../READMEPreviewViewController.swift | 0 .../SubviewExampleViewController.swift | 9 +- .../SwiftUIExample.swift | 0 .../InputBarStyleSelectionController.swift | 75 ++++++------ Sources/KeyboardManager/KeyboardManager.swift | 107 +++++++++--------- 9 files changed, 185 insertions(+), 101 deletions(-) create mode 100644 Example/Sources/Example ViewControllers/AdditionalBottomSpaceExampleViewController.swift rename Example/Sources/{ => Example ViewControllers}/CommonTableViewController.swift (100%) rename Example/Sources/{ => Example ViewControllers}/InputAccessoryExampleViewController.swift (100%) rename Example/Sources/{ => Example ViewControllers}/READMEPreviewViewController.swift (100%) rename Example/Sources/{ => Example ViewControllers}/SubviewExampleViewController.swift (83%) rename Example/Sources/{ => Example ViewControllers}/SwiftUIExample.swift (100%) diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 0f475979..be9034bc 100755 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 131A72ED28325D2E001F73BE /* AdditionalBottomSpaceExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A72EC28325D2E001F73BE /* AdditionalBottomSpaceExampleViewController.swift */; }; 3821ADE120F534DA00DE0D1D /* SubviewExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3821ADDF20F534DA00DE0D1D /* SubviewExampleViewController.swift */; }; 3821ADE220F534DA00DE0D1D /* InputAccessoryExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3821ADE020F534DA00DE0D1D /* InputAccessoryExampleViewController.swift */; }; 3821ADE420F5359800DE0D1D /* CommonTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3821ADE320F5359800DE0D1D /* CommonTableViewController.swift */; }; @@ -45,6 +46,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 131A72EC28325D2E001F73BE /* AdditionalBottomSpaceExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalBottomSpaceExampleViewController.swift; sourceTree = ""; }; 3821ADDF20F534DA00DE0D1D /* SubviewExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubviewExampleViewController.swift; sourceTree = ""; }; 3821ADE020F534DA00DE0D1D /* InputAccessoryExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputAccessoryExampleViewController.swift; sourceTree = ""; }; 3821ADE320F5359800DE0D1D /* CommonTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonTableViewController.swift; sourceTree = ""; }; @@ -83,6 +85,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 131A72EB28325A05001F73BE /* Example ViewControllers */ = { + isa = PBXGroup; + children = ( + 3821ADE320F5359800DE0D1D /* CommonTableViewController.swift */, + 38FCF9D5219F797600A47350 /* READMEPreviewViewController.swift */, + 3821ADE020F534DA00DE0D1D /* InputAccessoryExampleViewController.swift */, + 3821ADDF20F534DA00DE0D1D /* SubviewExampleViewController.swift */, + 131A72EC28325D2E001F73BE /* AdditionalBottomSpaceExampleViewController.swift */, + 46E4094D252996D900D238A8 /* SwiftUIExample.swift */, + ); + path = "Example ViewControllers"; + sourceTree = ""; + }; 383B83901F47897800027965 = { isa = PBXGroup; children = ( @@ -106,14 +121,10 @@ isa = PBXGroup; children = ( 383B839C1F47897800027965 /* AppDelegate.swift */, - 38FCF9D5219F797600A47350 /* READMEPreviewViewController.swift */, 388714E8202AC9620064C8BB /* InputBarStyleSelectionController.swift */, - 3821ADE320F5359800DE0D1D /* CommonTableViewController.swift */, - 3821ADE020F534DA00DE0D1D /* InputAccessoryExampleViewController.swift */, - 3821ADDF20F534DA00DE0D1D /* SubviewExampleViewController.swift */, - 46E4094D252996D900D238A8 /* SwiftUIExample.swift */, - 468A8726251C8AAB0018D007 /* Community Examples */, + 131A72EB28325A05001F73BE /* Example ViewControllers */, 38F0C1FC20C89C8700FF8DD3 /* InputBar Examples */, + 468A8726251C8AAB0018D007 /* Community Examples */, 38F0C20920C89D2D00FF8DD3 /* Cells */, 38F0C20A20C89D4100FF8DD3 /* Sample Data */, ); @@ -266,6 +277,7 @@ 383989BD1F564F5C003D30DD /* Random.swift in Sources */, 388714EB202AC9A50064C8BB /* SampleData.swift in Sources */, 38F0C20020C89CA200FF8DD3 /* SlackInputBar.swift in Sources */, + 131A72ED28325D2E001F73BE /* AdditionalBottomSpaceExampleViewController.swift in Sources */, 388714E9202AC9620064C8BB /* InputBarStyleSelectionController.swift in Sources */, 383989BA1F55C600003D30DD /* Lorem.swift in Sources */, 3821ADE120F534DA00DE0D1D /* SubviewExampleViewController.swift in Sources */, diff --git a/Example/Sources/Example ViewControllers/AdditionalBottomSpaceExampleViewController.swift b/Example/Sources/Example ViewControllers/AdditionalBottomSpaceExampleViewController.swift new file mode 100644 index 00000000..726eb719 --- /dev/null +++ b/Example/Sources/Example ViewControllers/AdditionalBottomSpaceExampleViewController.swift @@ -0,0 +1,71 @@ +// +// AdditionalBottomSpaceExampleViewController.swift +// Example +// +// Created by Martin Púčik on 16.05.2022. +// Copyright © 2022 Nathan Tannar. All rights reserved. +// + +import Foundation +import UIKit +import InputBarAccessoryView + +final class AdditionalBottomSpaceExampleViewController: CommonTableViewController { + + private lazy var keyboardManager = KeyboardManager() + + private lazy var additionalBottomBar: UIView = { + let view = UIView() + view.backgroundColor = .systemBlue + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(inputBar) + view.addSubview(additionalBottomBar) + + NSLayoutConstraint.activate([ + additionalBottomBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + additionalBottomBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + additionalBottomBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + additionalBottomBar.heightAnchor.constraint(equalToConstant: 50) + ]) + + view.layoutIfNeeded() + + keyboardManager.additionalInputViewBottomConstraintConstant = { + var safeBottomInset: CGFloat = self.view.safeAreaInsets.bottom + if let windowBottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom, + safeBottomInset != windowBottomInset { + safeBottomInset = windowBottomInset + } + return -(self.additionalBottomBar.frame.height + safeBottomInset) + } + + // Binding the inputBar will set the needed callback actions to position the inputBar on top of the keyboard + keyboardManager.bind(inputAccessoryView: inputBar) + + // Binding to the tableView will enabled interactive dismissal + keyboardManager.bind(to: tableView) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + /// This replicates instagram's behavior when commenting in a post. As of 2020-09, it appears like they have one of the best product experiences of this handling the keyboard when dismissing the UIViewController + self.inputBar.inputTextView.resignFirstResponder() + /// This is set because otherwise, if only partially dragging the left edge of the screen, and then cancelling the dismissal, on viewDidAppear UIKit appears to set the first responder back to the inputTextView (https://stackoverflow.com/a/41847448) + self.inputBar.inputTextView.canBecomeFirstResponder = false + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + /// The opposite of `viewWillDisappear(_:)` + self.inputBar.inputTextView.canBecomeFirstResponder = true + self.inputBar.inputTextView.becomeFirstResponder() + } +} diff --git a/Example/Sources/CommonTableViewController.swift b/Example/Sources/Example ViewControllers/CommonTableViewController.swift similarity index 100% rename from Example/Sources/CommonTableViewController.swift rename to Example/Sources/Example ViewControllers/CommonTableViewController.swift diff --git a/Example/Sources/InputAccessoryExampleViewController.swift b/Example/Sources/Example ViewControllers/InputAccessoryExampleViewController.swift similarity index 100% rename from Example/Sources/InputAccessoryExampleViewController.swift rename to Example/Sources/Example ViewControllers/InputAccessoryExampleViewController.swift diff --git a/Example/Sources/READMEPreviewViewController.swift b/Example/Sources/Example ViewControllers/READMEPreviewViewController.swift similarity index 100% rename from Example/Sources/READMEPreviewViewController.swift rename to Example/Sources/Example ViewControllers/READMEPreviewViewController.swift diff --git a/Example/Sources/SubviewExampleViewController.swift b/Example/Sources/Example ViewControllers/SubviewExampleViewController.swift similarity index 83% rename from Example/Sources/SubviewExampleViewController.swift rename to Example/Sources/Example ViewControllers/SubviewExampleViewController.swift index a01c7d12..82769e34 100644 --- a/Example/Sources/SubviewExampleViewController.swift +++ b/Example/Sources/Example ViewControllers/SubviewExampleViewController.swift @@ -21,9 +21,14 @@ final class SubviewExampleViewController: CommonTableViewController { super.viewDidLoad() view.addSubview(inputBar) - + + keyboardManager.shouldApplyAdditionBottomSpaceToInteractiveDismissal = true // Binding the inputBar will set the needed callback actions to position the inputBar on top of the keyboard - keyboardManager.bind(inputAccessoryView: inputBar) + keyboardManager.bind(inputAccessoryView: inputBar, withAdditionalBottomSpace: { + return 0 + return -self.view.safeAreaInsets.bottom + return -(self.inputBar.frame.height + self.view.safeAreaInsets.bottom) + }) // Binding to the tableView will enabled interactive dismissal keyboardManager.bind(to: tableView) diff --git a/Example/Sources/SwiftUIExample.swift b/Example/Sources/Example ViewControllers/SwiftUIExample.swift similarity index 100% rename from Example/Sources/SwiftUIExample.swift rename to Example/Sources/Example ViewControllers/SwiftUIExample.swift diff --git a/Example/Sources/InputBarStyleSelectionController.swift b/Example/Sources/InputBarStyleSelectionController.swift index 4cc0d770..ecec2eb7 100755 --- a/Example/Sources/InputBarStyleSelectionController.swift +++ b/Example/Sources/InputBarStyleSelectionController.swift @@ -22,14 +22,6 @@ class InputBarStyleSelectionController: UITableViewController { tableView.tableFooterView = UIView() title = "InputBarAccessoryView" navigationItem.backBarButtonItem = UIBarButtonItem(title: "Styles", style: .plain, target: nil, action: nil) - if #available(iOS 13, *) { - navigationController?.navigationBar.tintColor = .systemBackground - navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.systemBackground] - } else { - navigationController?.navigationBar.tintColor = .white - navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] - } - navigationController?.navigationBar.barTintColor = .systemBlue } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { @@ -49,7 +41,7 @@ class InputBarStyleSelectionController: UITableViewController { switch section { case 0: return 1 case 1...2: return styles.count - case 3: return 3 + case 3: return 4 default: fatalError("unknown section \(section)") } } @@ -60,8 +52,9 @@ class InputBarStyleSelectionController: UITableViewController { case (0, _): cell.textLabel?.text = "README Preview" case (1...2, _): cell.textLabel?.text = styles[indexPath.row].rawValue case (3, 0): cell.textLabel?.text = "Tab bar example (Slack style)" - case (3, 1): cell.textLabel?.text = "Send button animations" - case (3, 2): cell.textLabel?.text = "SwiftUI example" + case (3, 1): cell.textLabel?.text = "Addition bottom space example (Slack)" + case (3, 2): cell.textLabel?.text = "Send button animations" + case (3, 3): cell.textLabel?.text = "SwiftUI example" default: assertionFailure("unrecognized \(indexPath). Are you trying to add an additional example?") } @@ -70,38 +63,38 @@ class InputBarStyleSelectionController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - if indexPath.section == 0 { + let convo = SampleData.shared.getConversations(count: 1)[0] + switch indexPath.section { + case 0: navigationController?.pushViewController(READMEPreviewViewController(), animated: true) - } else { - let convo = SampleData.shared.getConversations(count: 1)[0] - if indexPath.section == 1 { - navigationController?.pushViewController( - InputAccessoryExampleViewController(style: styles[indexPath.row], - conversation: convo), - animated: true) - } else if indexPath.section == 2 { - navigationController?.pushViewController( - SubviewExampleViewController(style: styles[indexPath.row], - conversation: convo), - animated: true) - } else if indexPath.section == 3 { - switch indexPath.row { - case 0: - let tabBarController = UITabBarController() - let contained = SubviewExampleViewController(style: InputBarStyle.slack, conversation: convo) - tabBarController.viewControllers = [contained] - navigationController?.pushViewController(tabBarController, animated: true) - case 1: - let example = ButtonAnimationExample(style: .imessage, conversation: convo) - navigationController?.pushViewController(example, animated: true) - case 2: - let example = UIHostingController(rootView: SwiftUIExample.make(style: .imessage, conversation: convo)) - navigationController?.pushViewController(example, animated: true) - default: - fatalError("Unknown row \(indexPath.row) in Community Examples section. Are you trying to add a new example?") - } + case 1: + let controller = InputAccessoryExampleViewController(style: styles[indexPath.row], conversation: convo) + navigationController?.pushViewController(controller, animated: true) + case 2: + let controller = SubviewExampleViewController(style: styles[indexPath.row], conversation: convo) + navigationController?.pushViewController(controller, animated: true) + case 3: + switch indexPath.row { + case 0: + let tabBarController = UITabBarController() + let contained = SubviewExampleViewController(style: InputBarStyle.slack, conversation: convo) + tabBarController.viewControllers = [contained] + contained.tabBarItem = UITabBarItem(title: "Slack", image: UIImage(systemName: "number"), tag: 0) + navigationController?.pushViewController(tabBarController, animated: true) + case 1: + let example = AdditionalBottomSpaceExampleViewController(style: .slack, conversation: convo) + navigationController?.pushViewController(example, animated: true) + case 2: + let example = ButtonAnimationExample(style: .imessage, conversation: convo) + navigationController?.pushViewController(example, animated: true) + case 3: + let example = UIHostingController(rootView: SwiftUIExample.make(style: .imessage, conversation: convo)) + navigationController?.pushViewController(example, animated: true) + default: + fatalError("Unknown row \(indexPath.row) in Community Examples section. Are you trying to add a new example?") } + default: + fatalError("Unknown Section \(indexPath.section). Are you trying to add a new example?") } } } diff --git a/Sources/KeyboardManager/KeyboardManager.swift b/Sources/KeyboardManager/KeyboardManager.swift index 46744742..b347b59d 100644 --- a/Sources/KeyboardManager/KeyboardManager.swift +++ b/Sources/KeyboardManager/KeyboardManager.swift @@ -30,47 +30,50 @@ import UIKit /// An object that observes keyboard notifications such that event callbacks can be set for each notification @available(iOSApplicationExtension, unavailable) open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { - + /// A callback that passes a `KeyboardNotification` as an input public typealias EventCallback = (KeyboardNotification)->Void - + // MARK: - Properties [Public] - + /// A weak reference to a view bounded to the top of the keyboard to act as an `InputAccessoryView` /// but kept within the bounds of the `UIViewController`s view open weak var inputAccessoryView: UIView? - + /// A flag that indicates if a portion of the keyboard is visible on the screen private(set) public var isKeyboardHidden: Bool = true - + /// A flag that indicates if the additional bottom space should be applied to /// the interactive dismissal of the keyboard public var shouldApplyAdditionBottomSpaceToInteractiveDismissal: Bool = false + /// Closure for providing an additional bottom constraint constant for `InputAccessoryView` + public var additionalInputViewBottomConstraintConstant: () -> CGFloat = { 0 } + // MARK: - Properties [Private] - + /// The additional bottom space specified for laying out the input accessory view /// when binding to it private var additionalBottomSpace: (() -> CGFloat)? /// The `NSLayoutConstraintSet` that holds the `inputAccessoryView` to the bottom if its superview private var constraints: NSLayoutConstraintSet? - + /// A weak reference to a `UIScrollView` that has been attached for interactive keyboard dismissal private weak var scrollView: UIScrollView? - + /// The `EventCallback` actions for each `KeyboardEvent`. Default value is EMPTY private var callbacks: [KeyboardEvent: EventCallback] = [:] - + /// The pan gesture that handles dragging on the `scrollView` private var panGesture: UIPanGestureRecognizer? /// A cached notification used as a starting point when a user dragging the `scrollView` down /// to interactively dismiss the keyboard private var cachedNotification: KeyboardNotification? - + // MARK: - Initialization - + /// Creates a `KeyboardManager` object an binds the view as fake `InputAccessoryView` /// /// - Parameter inputAccessoryView: The view to bind to the top of the keyboard but within its superview @@ -78,25 +81,23 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { self.init() self.bind(inputAccessoryView: inputAccessoryView) } - + /// Creates a `KeyboardManager` object that observes the state of the keyboard public override init() { super.init() addObservers() } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - + + public required init?(coder: NSCoder) { nil } + // MARK: - De-Initialization - + deinit { NotificationCenter.default.removeObserver(self) } - + // MARK: - Keyboard Observer - + /// Add an observer for each keyboard notification private func addObservers() { NotificationCenter.default.addObserver(self, @@ -124,9 +125,9 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { name: UIResponder.keyboardDidChangeFrameNotification, object: nil) } - + // MARK: - Mutate Callback Dictionary - + /// Sets the `EventCallback` for a `KeyboardEvent` /// /// - Parameters: @@ -138,7 +139,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[event] = callback return self } - + /// Constrains the `inputAccessoryView` to the bottom of its superview and sets the /// `.willChangeFrame` and `.willHide` event callbacks such that it mimics an `InputAccessoryView` /// that is bound to the top of the keyboard @@ -147,7 +148,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { /// - Returns: Self @discardableResult open func bind(inputAccessoryView: UIView, withAdditionalBottomSpace additionalBottomSpace: (() -> CGFloat)? = .none) -> Self { - + guard let superview = inputAccessoryView.superview else { fatalError("`inputAccessoryView` must have a superview") } @@ -155,17 +156,19 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { self.additionalBottomSpace = additionalBottomSpace inputAccessoryView.translatesAutoresizingMaskIntoConstraints = false constraints = NSLayoutConstraintSet( - bottom: inputAccessoryView.bottomAnchor.constraint(equalTo: superview.bottomAnchor), + bottom: inputAccessoryView.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: additionalInputViewBottomConstraintConstant()), left: inputAccessoryView.leftAnchor.constraint(equalTo: superview.leftAnchor), right: inputAccessoryView.rightAnchor.constraint(equalTo: superview.rightAnchor) ).activate() callbacks[.willShow] = { [weak self] (notification) in - let keyboardHeight = notification.endFrame.height guard self?.isKeyboardHidden == false, - self?.constraints?.bottom?.constant == 0, - notification.isForCurrentApp else { return } + self?.constraints?.bottom?.constant == self?.additionalInputViewBottomConstraintConstant(), + notification.isForCurrentApp + else { return } + + let keyboardHeight = notification.endFrame.height self?.animateAlongside(notification) { self?.constraints?.bottom?.constant = -keyboardHeight - (additionalBottomSpace?() ?? 0) self?.inputAccessoryView?.superview?.layoutIfNeeded() @@ -175,7 +178,9 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { let keyboardHeight = notification.endFrame.height guard self?.isKeyboardHidden == false, - notification.isForCurrentApp else { return } + notification.isForCurrentApp + else { return } + self?.animateAlongside(notification) { self?.constraints?.bottom?.constant = -keyboardHeight - (additionalBottomSpace?() ?? 0) self?.inputAccessoryView?.superview?.layoutIfNeeded() @@ -184,13 +189,13 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[.willHide] = { [weak self] (notification) in guard notification.isForCurrentApp else { return } self?.animateAlongside(notification) { [weak self] in - self?.constraints?.bottom?.constant = 0 + self?.constraints?.bottom?.constant = self?.additionalInputViewBottomConstraintConstant() ?? 0 self?.inputAccessoryView?.superview?.layoutIfNeeded() } } return self } - + /// Adds a `UIPanGestureRecognizer` to the `scrollView` to enable interactive dismissal` /// /// - Parameter scrollView: UIScrollView @@ -205,9 +210,9 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { self.scrollView?.addGestureRecognizer(recognizer) return self } - + // MARK: - Keyboard Notifications - + /// An observer method called last in the lifecycle of a keyboard becoming visible /// /// - Parameter notification: NSNotification @@ -216,7 +221,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { guard let keyboardNotification = KeyboardNotification(from: notification) else { return } callbacks[.didShow]?(keyboardNotification) } - + /// An observer method called last in the lifecycle of a keyboard becoming hidden /// /// - Parameter notification: NSNotification @@ -227,7 +232,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[.didHide]?(keyboardNotification) cachedNotification = nil } - + /// An observer method called third in the lifecycle of a keyboard becoming visible/hidden /// /// - Parameter notification: NSNotification @@ -237,7 +242,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[.didChangeFrame]?(keyboardNotification) cachedNotification = keyboardNotification } - + /// An observer method called first in the lifecycle of a keyboard becoming visible/hidden /// /// - Parameter notification: NSNotification @@ -247,7 +252,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[.willChangeFrame]?(keyboardNotification) cachedNotification = keyboardNotification } - + /// An observer method called second in the lifecycle of a keyboard becoming visible /// /// - Parameter notification: NSNotification @@ -257,7 +262,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { guard let keyboardNotification = KeyboardNotification(from: notification) else { return } callbacks[.willShow]?(keyboardNotification) } - + /// An observer method called second in the lifecycle of a keyboard becoming hidden /// /// - Parameter notification: NSNotification @@ -267,15 +272,15 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { callbacks[.willHide]?(keyboardNotification) cachedNotification = nil } - + // MARK: - Helper Methods - + private func animateAlongside(_ notification: KeyboardNotification, animations: @escaping ()->Void) { UIView.animate(withDuration: notification.timeInterval, delay: 0, options: [notification.animationOptions, .allowAnimatedContent, .beginFromCurrentState], animations: animations, completion: nil) } - + // MARK: - UIGestureRecognizerDelegate - + /// Starts with the cached `KeyboardNotification` and calculates a new `endFrame` based /// on the `UIPanGestureRecognizer` then calls the `.willChangeFrame` `EventCallback` action /// @@ -287,14 +292,15 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { case .changed = recognizer.state, let view = recognizer.view, let window = UIApplication.shared.windows.first - else { return } + else { return } guard - // if there's no difference in frames for the `cachedNotification`, no adjustment is necessary. This is true when the keyboard is completely dismissed, or our pan doesn't intersect below the keyboard + // if there's no difference in frames for the `cachedNotification`, no adjustment is necessary. + // This is true when the keyboard is completely dismissed, or our pan doesn't intersect below the keyboard keyboardNotification.startFrame != keyboardNotification.endFrame, // when the width of the keyboard from endFrame is smaller than the width of scrollView manager is tracking // with panGesture, we can assume the keyboard is floatig ahd updating inputAccessoryView is not necessary - keyboardNotification.endFrame.width >= view.frame.width + keyboardNotification.endFrame.width >= view.frame.width else { return } @@ -307,26 +313,23 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { keyboardNotification.endFrame = frame var yCoordinateDirectlyAboveKeyboard = -frame.height - - if shouldApplyAdditionBottomSpaceToInteractiveDismissal, - let additionalBottomSpace = additionalBottomSpace { + if shouldApplyAdditionBottomSpaceToInteractiveDismissal, let additionalBottomSpace = additionalBottomSpace { yCoordinateDirectlyAboveKeyboard -= additionalBottomSpace() } /// If a tab bar is shown, letting this number becoming > 0 makes it so the accessoryview disappears below the tab bar. setting the max value to 0 prevents that - let aboveKeyboardAndAboveTabBar = min(0, yCoordinateDirectlyAboveKeyboard) + let aboveKeyboardAndAboveTabBar = min(additionalInputViewBottomConstraintConstant(), yCoordinateDirectlyAboveKeyboard) self.constraints?.bottom?.constant = aboveKeyboardAndAboveTabBar self.inputAccessoryView?.superview?.layoutIfNeeded() } - + /// Only receive a `UITouch` event when the `scrollView`'s keyboard dismiss mode is interactive open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return scrollView?.keyboardDismissMode == .interactive } - + /// Only recognice simultaneous gestures when its the `panGesture` open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return gestureRecognizer === panGesture } - } From b1b6f6ce152d98c2e03be4a0f9169f6439572ee9 Mon Sep 17 00:00:00 2001 From: martinpucik Date: Mon, 16 May 2022 16:28:42 +0200 Subject: [PATCH 2/2] feat: `additionalInputViewBottomConstraintConstant` in KeyboardManager Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 145b6a3d..d87465dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog - Master: - - + - [#230](https://github.com/nathantannar4/InputBarAccessoryView/pull/230) Added `additionalInputViewBottomConstraintConstant` to `KeyboardManager` as a way for providing additional bottom constraint constant offset for `inputAccessoryView`. Example implementation can be found in `AdditionalBottomSpaceExampleViewController` - 6.0.0 - **Breaking change**: Drop iOS 12 support - **Breaking change**: Drop CocoaPods support & RxSwift extension (it was available only via CocoaPods)