Skip to content

Commit 624a5de

Browse files
committed
Re-pad the view on re-render
If the view re-renders, and the position of the first responder changes, while the keyboard is showing.
1 parent 5479f20 commit 624a5de

File tree

2 files changed

+38
-18
lines changed

2 files changed

+38
-18
lines changed

Diff for: KeyboardAvoidanceSwiftUI/KeyboardAdaptive.swift

+23-9
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,38 @@ import Combine
1212
/// Note that the `KeyboardAdaptive` modifier wraps your view in a `GeometryReader`,
1313
/// which attempts to fill all the available space, potentially increasing content view size.
1414
struct KeyboardAdaptive: ViewModifier {
15+
private let rerender: AnyPublisher<Any, Never>
1516
@State private var bottomPadding: CGFloat = 0
16-
17+
18+
init(rerender: AnyPublisher<Any, Never>? = nil) {
19+
var publishers = [Publishers.keyboardHeight.map { arg -> Any in arg }.eraseToAnyPublisher()]
20+
if let rerender = rerender {
21+
publishers.append(rerender)
22+
}
23+
self.rerender = Publishers.MergeMany(publishers).eraseToAnyPublisher()
24+
}
25+
26+
private func bottomPadding(forGeometry geometry: GeometryProxy) -> CGFloat {
27+
let keyboardTop = geometry.frame(in: .global).height - Publishers.keyboardHeight.value
28+
var focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
29+
focusedTextInputBottom += bottomPadding / 2
30+
return 2 * max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom)
31+
}
32+
1733
func body(content: Content) -> some View {
1834
GeometryReader { geometry in
1935
content
2036
.padding(.bottom, self.bottomPadding)
21-
.onReceive(Publishers.keyboardHeight) { keyboardHeight in
22-
let keyboardTop = geometry.frame(in: .global).height - keyboardHeight
23-
let focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
24-
self.bottomPadding = max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom)
25-
}
26-
.animation(.easeOut(duration: 0.16))
37+
.onReceive(self.rerender, perform: { _ in
38+
self.bottomPadding = self.bottomPadding(forGeometry: geometry)
39+
})
40+
.animation(.easeOut(duration: 0.16))
2741
}
2842
}
2943
}
3044

3145
extension View {
32-
func keyboardAdaptive() -> some View {
33-
ModifiedContent(content: self, modifier: KeyboardAdaptive())
46+
func keyboardAdaptive(rerender: AnyPublisher<Any, Never>? = nil) -> some View {
47+
ModifiedContent(content: self, modifier: KeyboardAdaptive(rerender: rerender))
3448
}
3549
}

Diff for: KeyboardAvoidanceSwiftUI/KeyboardHeightPublisher.swift

+15-9
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@
99
import Combine
1010
import UIKit
1111

12+
fileprivate var keyboardUpdates: AnyCancellable?
13+
fileprivate let keyboardHeightPublisher: CurrentValueSubject<CGFloat, Never> = {
14+
let subject = CurrentValueSubject<CGFloat, Never>(0)
15+
16+
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
17+
.map { $0.keyboardHeight }
18+
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
19+
.map { _ in CGFloat(0) }
20+
keyboardUpdates = Publishers.MergeMany(willShow, willHide).assign(to: \.value, on: subject)
21+
22+
return subject
23+
}()
24+
1225
extension Publishers {
13-
static var keyboardHeight: AnyPublisher<CGFloat, Never> {
14-
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
15-
.map { $0.keyboardHeight }
16-
17-
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
18-
.map { _ in CGFloat(0) }
19-
20-
return MergeMany(willShow, willHide)
21-
.eraseToAnyPublisher()
26+
static var keyboardHeight: CurrentValueSubject<CGFloat, Never> {
27+
keyboardHeightPublisher
2228
}
2329
}
2430

0 commit comments

Comments
 (0)