From e8ff583c43e2029010cb63561cc7af070ef33547 Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Fri, 2 Feb 2024 10:45:23 -0500 Subject: [PATCH 1/4] Added keyboardHeightEnvironmentValue view modifier. --- .../KeyboardHeightEnvironmentValue.swift | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Sources/FrameUp/FrameAdjustment/Readers/KeyboardHeightEnvironmentValue.swift diff --git a/Sources/FrameUp/FrameAdjustment/Readers/KeyboardHeightEnvironmentValue.swift b/Sources/FrameUp/FrameAdjustment/Readers/KeyboardHeightEnvironmentValue.swift new file mode 100644 index 0000000..6567d51 --- /dev/null +++ b/Sources/FrameUp/FrameAdjustment/Readers/KeyboardHeightEnvironmentValue.swift @@ -0,0 +1,79 @@ +// +// KeyboardHeightEnvironmentValue.swift +// KeyboardAvoidance +// +// Created by Ryan Lintott on 2024-02-01. +// + +import SwiftUI + + +private struct KeyboardHeightEnvironmentKey: EnvironmentKey { + static let defaultValue: CGFloat = 0 +} + +public extension EnvironmentValues { + /// Height of software keyboard when visible + var keyboardHeight: CGFloat { + get { self[KeyboardHeightEnvironmentKey.self] } + set { self[KeyboardHeightEnvironmentKey.self] = newValue } + } +} + +public extension Animation { + /// An approximation of Apple's keyboard animation + /// + /// source: https://forums.developer.apple.com/forums/thread/48088 + static var keyboard: Self { + .interpolatingSpring(mass: 3, stiffness: 1000, damping: 500, initialVelocity: 0) + } +} + +#if os(iOS) +struct KeyboardHeightEnvironmentValue: ViewModifier { + @State private var keyboardHeight: CGFloat = 0 + + func body(content: Content) -> some View { + content + .environment(\.keyboardHeight, keyboardHeight) + .animation(.keyboard, value: keyboardHeight) + .background( + GeometryReader { keyboardProxy in + GeometryReader { proxy in + Color.clear + .onChange(of: keyboardProxy.safeAreaInsets.bottom - proxy.safeAreaInsets.bottom) { newValue in + DispatchQueue.main.async { + if keyboardHeight != newValue { + keyboardHeight = newValue + } + } + } + } + .ignoresSafeArea(.keyboard) + } + ) + } +} +#endif + +public extension View { + /// Adds an environment value for software keyboard height when visible + /// + /// Must be applied on a view taller than the keyboard that touches the bottom edge of the safe area. + /// Access keyboard height in any child view with + /// @Environment(\.keyboardHeight) var keyboardHeight + func keyboardHeightEnvironmentValue() -> some View { + #if os(iOS) + modifier(KeyboardHeightEnvironmentValue()) + #else + environment(\.keyboardHeight, 0) + #endif + } +} + +#Preview { + VStack { + TextField("Example", text: .constant("")) + } + .keyboardHeightEnvironmentValue() +} From 7f1a01e79e15c6a48f2a142454cbe6d20fb8a075 Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Fri, 2 Feb 2024 15:00:49 -0500 Subject: [PATCH 2/4] Adjustments to TwoSidedVisionOSView preview. --- Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift b/Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift index 6b456c5..f09b4ad 100644 --- a/Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift +++ b/Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift @@ -86,6 +86,8 @@ struct TwoSidedVisionOSView_Previews: PreviewProvider { .fill(.red) .overlay(Text("Down")) } + .offset(z: 100) + .frame(maxWidth: 200, maxHeight: 200) .padding() @@ -98,7 +100,7 @@ struct TwoSidedVisionOSView_Previews: PreviewProvider { Text("Change Rotation") HStack { - ForEach([-360,-180,-120,-45,45,120,180,360], id: \.self) { i in + ForEach([-360,-180,-90,-45,45,90,180,360], id: \.self) { i in Button("\(i > 0 ? "+" : "")\(i)") { withAnimation(.spring().speed(0.4)) { angle += .degrees(Double(i)) From e5b5f51c87cff45e4919d7e29ea0a8e327ecb2af Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Fri, 2 Feb 2024 16:00:28 -0500 Subject: [PATCH 3/4] Update readme --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d49194..31971f4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A collection of SwiftUI framing views and tools to help with layout. - [`AutoRotatingView`](#autorotatingview) to set allowable orientations for a view. -- [Frame Adjustment](#frame-adjustment) tools like [`WidthReader`](#widthreader), [`HeightReader`](#heightreader), [`onSizeChange(perform:)`](#onsizechangeperform), [`.relativePadding`](#relativepaddingedges-lengthfactor), [`ScaledView`](#scaledview) and [`OverlappingImage`](#overlappingimage). +- [Frame Adjustment](#frame-adjustment) tools like [`WidthReader`](#widthreader), [`HeightReader`](#heightreader), [`onSizeChange(perform:)`](#onsizechangeperform), [`keyboardHeight`](#keyboardHeight), [`.relativePadding`](#relativepaddingedges-lengthfactor), [`ScaledView`](#scaledview) and [`OverlappingImage`](#overlappingimage). - [`FULayout`](#fulayout) for building custom layouts (similar to SwiftUI `Layout`). - Included FULayouts: [`HFlow`](#hflow), [`VFlow`](#vflow), [`HMasonry`](#hmasonry), and [`VMasonry`](#vmasonry). - [`AnyFULayout`](#anyfulayout) to wrap multiple layouts and switch between with animation. @@ -138,6 +138,39 @@ struct OnSizeChangeExample: View { } ``` +### keyboardHeight +An environment variable that will update with animation as the iOS keyboard appears and disappears. + +`Animation.keyboard` is added as an approximation of the keyboard animation curve and is used by keyboardHeight. + +In order to use keyboardHeight you first need to add it somewhere at the top of your view heirachry so it can see the entire frame. It will use a GeometryReader on a background layer to measure the keyboard so ensure the view is using the entire available height. + +```swift +struct ContentView: View { + var body: some View { + MyView() + .frame(maxHeight: .infinity) + .keyboardHeightEnvironmentValue() + } +} +``` + +When you want to access the keyboardHeight use an environment variable. If you're using it to adjust the position of a view that should avoid the keyboard use the keyboardHeight directly and make sure the view ignores the keyboard safe area. + +```swift +struct MyView: View { + @Environment(\.keyboardHeight) var keyboardHeight + @State private var text = "" + + var body: some View { + TextField("Moves with keyboard", text: $text) + .keyboardHeightEnvironmentValue() + .padding(.bottom, keyboardHeight == 0 ? 100 : keyboardHeight) + .ignoresSafeArea(.keyboard) + } +} +``` + ### .relativePadding(edges:, lengthFactor:) Adds a padding amount to specified edges of a view relative to the size of the view. Width is used for .leading/.trailing and height is used for .top/.bottom From a51bb74f054283a08f31f9f30aec400a36c222f5 Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Fri, 2 Feb 2024 16:02:37 -0500 Subject: [PATCH 4/4] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31971f4..23e4a48 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ struct OnSizeChangeExample: View { ``` ### keyboardHeight -An environment variable that will update with animation as the iOS keyboard appears and disappears. +An environment variable that will update with animation as the iOS keyboard appears and disappears. It will always be zero for non-iOS platforms. `Animation.keyboard` is added as an approximation of the keyboard animation curve and is used by keyboardHeight.