diff --git a/KeyboardAvoidanceSwiftUI/KeyboardAdaptive.swift b/KeyboardAvoidanceSwiftUI/KeyboardAdaptive.swift index 1a22a99..e80659e 100644 --- a/KeyboardAvoidanceSwiftUI/KeyboardAdaptive.swift +++ b/KeyboardAvoidanceSwiftUI/KeyboardAdaptive.swift @@ -12,24 +12,38 @@ import Combine /// Note that the `KeyboardAdaptive` modifier wraps your view in a `GeometryReader`, /// which attempts to fill all the available space, potentially increasing content view size. struct KeyboardAdaptive: ViewModifier { + private let rerender: AnyPublisher @State private var bottomPadding: CGFloat = 0 - + + init(rerender: AnyPublisher? = nil) { + var publishers = [Publishers.keyboardHeight.map { arg -> Any in arg }.eraseToAnyPublisher()] + if let rerender = rerender { + publishers.append(rerender) + } + self.rerender = Publishers.MergeMany(publishers).eraseToAnyPublisher() + } + + private func bottomPadding(forGeometry geometry: GeometryProxy) -> CGFloat { + let keyboardTop = geometry.frame(in: .global).height - Publishers.keyboardHeight.value + var focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0 + focusedTextInputBottom += bottomPadding / 2 + return 2 * max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom) + } + func body(content: Content) -> some View { GeometryReader { geometry in content .padding(.bottom, self.bottomPadding) - .onReceive(Publishers.keyboardHeight) { keyboardHeight in - let keyboardTop = geometry.frame(in: .global).height - keyboardHeight - let focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0 - self.bottomPadding = max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom) - } - .animation(.easeOut(duration: 0.16)) + .onReceive(self.rerender, perform: { _ in + self.bottomPadding = self.bottomPadding(forGeometry: geometry) + }) + .animation(.easeOut(duration: 0.16)) } } } extension View { - func keyboardAdaptive() -> some View { - ModifiedContent(content: self, modifier: KeyboardAdaptive()) + func keyboardAdaptive(rerender: AnyPublisher? = nil) -> some View { + ModifiedContent(content: self, modifier: KeyboardAdaptive(rerender: rerender)) } } diff --git a/KeyboardAvoidanceSwiftUI/KeyboardHeightPublisher.swift b/KeyboardAvoidanceSwiftUI/KeyboardHeightPublisher.swift index e01a5b3..df276e9 100644 --- a/KeyboardAvoidanceSwiftUI/KeyboardHeightPublisher.swift +++ b/KeyboardAvoidanceSwiftUI/KeyboardHeightPublisher.swift @@ -9,16 +9,22 @@ import Combine import UIKit +fileprivate var keyboardUpdates: AnyCancellable? +fileprivate let keyboardHeightPublisher: CurrentValueSubject = { + let subject = CurrentValueSubject(0) + + let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification) + .map { $0.keyboardHeight } + let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification) + .map { _ in CGFloat(0) } + keyboardUpdates = Publishers.MergeMany(willShow, willHide).assign(to: \.value, on: subject) + + return subject +}() + extension Publishers { - static var keyboardHeight: AnyPublisher { - let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification) - .map { $0.keyboardHeight } - - let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification) - .map { _ in CGFloat(0) } - - return MergeMany(willShow, willHide) - .eraseToAnyPublisher() + static var keyboardHeight: CurrentValueSubject { + keyboardHeightPublisher } }