Skip to content

Commit

Permalink
Merge pull request #10 from ryanlintott/dev
Browse files Browse the repository at this point in the history
KeyboardHeight
  • Loading branch information
ryanlintott authored Feb 6, 2024
2 parents 83f15b9 + a51bb74 commit 75e58e6
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 2 deletions.
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -138,6 +138,39 @@ struct OnSizeChangeExample: View {
}
```

### keyboardHeight
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.

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

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
4 changes: 3 additions & 1 deletion Sources/FrameUp/TwoSidedView/TwoSidedVisionOSView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ struct TwoSidedVisionOSView_Previews: PreviewProvider {
.fill(.red)
.overlay(Text("Down"))
}
.offset(z: 100)
.frame(maxWidth: 200, maxHeight: 200)
.padding()


Expand All @@ -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))
Expand Down

0 comments on commit 75e58e6

Please sign in to comment.