Created
August 16, 2019 18:43
-
-
Save nickffox/3c3dbd6ea5c58318ea91e70ef6a23349 to your computer and use it in GitHub Desktop.
Adjusting a SwiftUI View for the Keyboard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
final class Keyboard: ObservableObject { | |
// MARK: - Published Properties | |
@Published var state: Keyboard.State = .default | |
// MARK: - Private Properties | |
private var cancellables: Set<AnyCancellable> = [] | |
private var notificationCenter: NotificationCenter | |
// MARK: - Initializers | |
init(notificationCenter: NotificationCenter = .default) { | |
self.notificationCenter = notificationCenter | |
// Observe keyboard notifications and transform them into state updates | |
notificationCenter.publisher(for: UIResponder.keyboardWillChangeFrameNotification) | |
.merge(with: notificationCenter.publisher(for: UIResponder.keyboardWillHideNotification)) | |
.compactMap(Keyboard.State.from(notification:)) | |
.assign(to: \.state, on: self) | |
.store(in: &cancellables) | |
} | |
deinit { | |
cancellables.forEach { $0.cancel() } | |
} | |
} | |
// MARK: - Nested State Type | |
extension Keyboard { | |
struct State { | |
// MARK: - Properties | |
let animationDuration: TimeInterval | |
let height: CGFloat | |
// MARK: - Initializers | |
init(animationDuration: TimeInterval, height: CGFloat) { | |
self.animationDuration = animationDuration | |
self.height = height | |
} | |
// MARK: - Static Properties | |
fileprivate static let `default` = Keyboard.State(animationDuration: 0.25, height: 0) | |
// MARK: - Static Methods | |
static func from(notification: Notification) -> Keyboard.State? { | |
return from( | |
notification: notification, | |
safeAreaInsets: UIApplication.shared.windows.first?.safeAreaInsets, | |
screen: .main | |
) | |
} | |
// NOTE: A testable version of the transform that injects the dependencies. | |
static func from( | |
notification: Notification, | |
safeAreaInsets: UIEdgeInsets?, | |
screen: UIScreen | |
) -> Keyboard.State? { | |
guard let userInfo = notification.userInfo else { return nil } | |
// NOTE: We could eventually get the aniamtion curve here too. | |
// Get the duration of the keyboard animation | |
let animationDuration = | |
(userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue | |
?? 0.25 | |
// Get keyboard height | |
var height: CGFloat = 0 | |
if let keyboardFrameValue: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { | |
let keyboardFrame = keyboardFrameValue.cgRectValue | |
// If the rectangle is at the bottom of the screen, set the height to 0. | |
if keyboardFrame.origin.y == screen.bounds.height { | |
height = 0 | |
} else { | |
height = keyboardFrame.height - (safeAreaInsets?.bottom ?? 0) | |
} | |
} | |
return Keyboard.State( | |
animationDuration: animationDuration, | |
height: height | |
) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct KeyboardObserving<Content: View>: View { | |
@EnvironmentObject var keyboard: Keyboard | |
let content: Content | |
init(@ViewBuilder builder: () -> Content) { | |
self.content = builder() | |
} | |
var body: some View { | |
VStack { | |
content | |
Spacer() | |
.frame(height: keyboard.state.height) | |
} | |
.animation(.easeInOut(duration: keyboard.state.animationDuration)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment