├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── README.md ├── Sources └── SwiftUI Keyboard Observer │ ├── SoftwareKeyboard-EnvironmentKey.swift │ ├── SoftwareKeyboard-Extension.swift │ ├── SoftwareKeyboard.swift │ ├── SoftwareKeyboardObserver.swift │ └── SwiftUI_Keyboard_Observer.swift ├── Tests └── SwiftUI Keyboard ObserverTests │ └── SwiftUI_Keyboard_ObserverTests.swift └── sample.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftUI Keyboard Observer", 8 | platforms: [ 9 | .iOS(.v14) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "SwiftUI Keyboard Observer", 15 | targets: ["SwiftUI Keyboard Observer"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "SwiftUI Keyboard Observer", 26 | dependencies: []), 27 | .testTarget( 28 | name: "SwiftUI Keyboard ObserverTests", 29 | dependencies: ["SwiftUI Keyboard Observer"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Keyboard Observer 2 | 3 | This package adds a view extension which adds an iOS software keyboard observer to the environment. 4 | 5 | More documentation and updates to come. This is a very early draft. I appreciate any feedback or contributions! 6 | 7 | ```swift 8 | import SwiftUI 9 | import SwiftUI_Keyboard_Observer 10 | 11 | @main 12 | struct MyApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | .observeSoftwareKeyboard() 17 | } 18 | } 19 | } 20 | 21 | struct ContentView: View { 22 | @State private var text: String = "" 23 | @Environment(\.softwareKeyboard) var softwareKeyboard 24 | 25 | var body: some View { 26 | VStack { 27 | Form { 28 | TextField("Text Field:", text: $text) 29 | } 30 | 31 | if softwareKeyboard?.isVisible == true { 32 | Button("Dismiss Keyboard") { 33 | softwareKeyboard?.dismiss() 34 | } 35 | .padding() 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | ![](sample.gif) 43 | -------------------------------------------------------------------------------- /Sources/SwiftUI Keyboard Observer/SoftwareKeyboard-EnvironmentKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Gaafary on 4/9/21. 6 | // 7 | import SwiftUI 8 | 9 | public struct KeyboardIsVisible: EnvironmentKey { 10 | public static let defaultValue: SoftwareKeyboard? = nil 11 | } 12 | 13 | public extension EnvironmentValues { 14 | var softwareKeyboard: SoftwareKeyboard? { 15 | get { self[KeyboardIsVisible.self] } 16 | set { self[KeyboardIsVisible.self] = newValue } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftUI Keyboard Observer/SoftwareKeyboard-Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Gaafary on 4/9/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SoftwareKeyboardContainer: View { 11 | @ViewBuilder var content: () -> Content 12 | @StateObject var softwareKeyboardObserver = SoftwareKeyboardObserver() 13 | 14 | var body: some View { 15 | content() 16 | .environment(\.softwareKeyboard, softwareKeyboardObserver.softwareKeyboard) 17 | } 18 | } 19 | 20 | public extension View { 21 | func observeSoftwareKeyboard() -> some View { 22 | SoftwareKeyboardContainer { 23 | self 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftUI Keyboard Observer/SoftwareKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Gaafary on 4/9/21. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | public struct SoftwareKeyboard { 12 | public enum Status { 13 | case willShow, willHide, didShow, didHide 14 | } 15 | 16 | public let status: Status 17 | 18 | public var isVisible: Bool { 19 | switch status { 20 | case .willShow, .didShow: 21 | return true 22 | case .willHide, .didHide: 23 | return false 24 | } 25 | } 26 | 27 | public func dismiss() { 28 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Sources/SwiftUI Keyboard Observer/SoftwareKeyboardObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Gaafary on 4/9/21. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | public class SoftwareKeyboardObserver: ObservableObject { 12 | @Published private(set) var softwareKeyboard: SoftwareKeyboard? 13 | 14 | public init() { 15 | NotificationCenter.default 16 | .publisher(for: UIApplication.keyboardWillShowNotification) 17 | .map { _ in SoftwareKeyboard(status: .willShow) } 18 | .assign(to: &$softwareKeyboard) 19 | 20 | NotificationCenter.default 21 | .publisher(for: UIApplication.keyboardDidShowNotification) 22 | .map { _ in SoftwareKeyboard(status: .didShow) } 23 | .assign(to: &$softwareKeyboard) 24 | 25 | NotificationCenter.default 26 | .publisher(for: UIApplication.keyboardWillHideNotification) 27 | .map { _ in SoftwareKeyboard(status: .willHide) } 28 | .assign(to: &$softwareKeyboard) 29 | 30 | NotificationCenter.default 31 | .publisher(for: UIApplication.keyboardDidHideNotification) 32 | .map { _ in SoftwareKeyboard(status: .didHide) } 33 | .assign(to: &$softwareKeyboard) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SwiftUI Keyboard Observer/SwiftUI_Keyboard_Observer.swift: -------------------------------------------------------------------------------- 1 | struct SwiftUI_Keyboard_Observer { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Tests/SwiftUI Keyboard ObserverTests/SwiftUI_Keyboard_ObserverTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftUI_Keyboard_Observer 3 | 4 | final class SwiftUI_Keyboard_ObserverTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(SwiftUI_Keyboard_Observer().text, "Hello, World!") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgaaf/SwiftUI-Keyboard-Observer/bd45c696069a1ac770fcc2bc63c29cfafaa90233/sample.gif --------------------------------------------------------------------------------