|
1 | | -# ARKeyboardControl |
| 1 | +# ARKeyboardControl |
| 2 | + |
| 3 | +Lightweight Swift library that adds keyboard awareness and interactive dismissal to any `UIView`. Inspired by [DAKeyboardControl](https://github.com/danielamitay/DAKeyboardControl), rewritten from scratch in pure Swift without method swizzling. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Interactive dismiss** — drag down to dismiss keyboard, like in iMessage |
| 8 | +- **Non-panning mode** — track keyboard appearance/disappearance without gestures |
| 9 | +- **Two callback modes** — frame-based (inside animation block) and constraint-based (before animation) |
| 10 | +- **iOS 15+ optimization** — uses `keyboardLayoutGuide` for non-panning mode when available |
| 11 | +- **Minimal footprint** — single associated object per view, clean teardown |
| 12 | +- **No swizzling** — no `+load`, no method exchange, no global side effects |
| 13 | + |
| 14 | +## Requirements |
| 15 | + |
| 16 | +- iOS 13.0+ |
| 17 | +- Swift 5.7+ |
| 18 | + |
| 19 | +## Installation |
| 20 | + |
| 21 | +### Swift Package Manager |
| 22 | + |
| 23 | +Add the package dependency: |
| 24 | + |
| 25 | +```swift |
| 26 | +dependencies: [ |
| 27 | + .package(url: "https://github.com/AppleArealidea/ARKeyboardControl.git", from: "1.0.0") |
| 28 | +] |
| 29 | +``` |
| 30 | + |
| 31 | +Or add it as a local package in Xcode via File > Add Package Dependencies. |
| 32 | + |
| 33 | +## Usage |
| 34 | + |
| 35 | +### Interactive keyboard dismiss (chat screens) |
| 36 | + |
| 37 | +```swift |
| 38 | +import KeyboardControl |
| 39 | + |
| 40 | +override func viewDidAppear(_ animated: Bool) { |
| 41 | + super.viewDidAppear(animated) |
| 42 | + |
| 43 | + view.addKeyboardPanning { beginFrame, endFrame, opening, closing in |
| 44 | + // Called inside UIView.animate — update frames here |
| 45 | + if opening { |
| 46 | + tableView.contentOffset.y += beginFrame.minY - endFrame.minY |
| 47 | + } |
| 48 | + } constraintBasedActionHandler: { _, endFrame, _, _ in |
| 49 | + // Called before animation — update constraints here |
| 50 | + let offset = view.frame.height - endFrame.minY - view.safeAreaInsets.bottom |
| 51 | + bottomConstraint.constant = offset > 0 ? -offset : 0 |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +override func viewWillDisappear(_ animated: Bool) { |
| 56 | + super.viewWillDisappear(animated) |
| 57 | + view.removeKeyboardControl() |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +### Keyboard awareness only (no gestures) |
| 62 | + |
| 63 | +```swift |
| 64 | +view.addKeyboardNonpanning { _, endFrame, opening, closing in |
| 65 | + let offset = view.frame.height - endFrame.origin.y - view.safeAreaInsets.bottom |
| 66 | + bottomConstraint.constant = offset > 0 ? -offset : 0 |
| 67 | + view.layoutIfNeeded() |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +On iOS 15+ this automatically uses `keyboardLayoutGuide` under the hood. |
| 72 | + |
| 73 | +### Gesture coordination |
| 74 | + |
| 75 | +Access the pan gesture recognizer to coordinate with other gestures: |
| 76 | + |
| 77 | +```swift |
| 78 | +if let keyboardPan = view.keyboardPanRecognizer { |
| 79 | + myGesture.require(toFail: keyboardPan) |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +### Utilities |
| 84 | + |
| 85 | +```swift |
| 86 | +view.hideKeyboard() // Programmatically dismiss |
| 87 | +view.isKeyboardOpened // Current state |
| 88 | +view.keyboardFrameInView // Keyboard frame in view's coordinate space |
| 89 | +``` |
| 90 | + |
| 91 | +### Cleanup |
| 92 | + |
| 93 | +Always call `removeKeyboardControl()` when the view is going away: |
| 94 | + |
| 95 | +```swift |
| 96 | +// In viewWillDisappear or deinit |
| 97 | +view.removeKeyboardControl() |
| 98 | +``` |
| 99 | + |
| 100 | +## API |
| 101 | + |
| 102 | +```swift |
| 103 | +public extension UIView { |
| 104 | + func addKeyboardPanning( |
| 105 | + frameBasedActionHandler: @escaping KeyboardDidMoveBlock, |
| 106 | + constraintBasedActionHandler: @escaping KeyboardDidMoveBlock |
| 107 | + ) |
| 108 | + func addKeyboardNonpanning(actionHandler: @escaping KeyboardDidMoveBlock) |
| 109 | + func removeKeyboardControl() |
| 110 | + func hideKeyboard() |
| 111 | + |
| 112 | + var keyboardPanRecognizer: UIPanGestureRecognizer? { get } |
| 113 | + var keyboardFrameInView: CGRect { get } |
| 114 | + var isKeyboardOpened: Bool { get } |
| 115 | +} |
| 116 | + |
| 117 | +public typealias KeyboardDidMoveBlock = ( |
| 118 | + _ keyboardBeginFrame: CGRect, |
| 119 | + _ keyboardEndFrame: CGRect, |
| 120 | + _ opening: Bool, |
| 121 | + _ closing: Bool |
| 122 | +) -> Void |
| 123 | +``` |
| 124 | + |
| 125 | +## Callback parameters |
| 126 | + |
| 127 | +| Parameter | Description | |
| 128 | +|-----------|-------------| |
| 129 | +| `keyboardBeginFrame` | Keyboard frame before the transition (in view's coordinates) | |
| 130 | +| `keyboardEndFrame` | Keyboard frame after the transition (in view's coordinates) | |
| 131 | +| `opening` | `true` when keyboard is appearing | |
| 132 | +| `closing` | `true` when keyboard is disappearing | |
| 133 | + |
| 134 | +## License |
| 135 | + |
| 136 | +MIT. See [LICENSE](LICENSE) for details. |
0 commit comments