Skip to content

Instantly share code, notes, and snippets.

@scottmatthewman
Last active April 19, 2024 12:56
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save scottmatthewman/722987c9ad40f852e2b6a185f390f88d to your computer and use it in GitHub Desktop.
Save scottmatthewman/722987c9ad40f852e2b6a185f390f88d to your computer and use it in GitHub Desktop.
An example of using Combine to automatically adapt a SwiftUI scrollable view to accommodate an iOS onscreen keyboard
import SwiftUI
import Combine
struct AdaptsToSoftwareKeyboard: ViewModifier {
@State var currentHeight: CGFloat = 0
func body(content: Content) -> some View {
content
.padding(.bottom, currentHeight)
.edgesIgnoringSafeArea(.bottom)
.onAppear(perform: subscribeToKeyboardEvents)
}
private func subscribeToKeyboardEvents() {
NotificationCenter.Publisher(
center: NotificationCenter.default,
name: UIResponder.keyboardWillShowNotification
).compactMap { notification in
notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect
}.map { rect in
rect.height
}.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
NotificationCenter.Publisher(
center: NotificationCenter.default,
name: UIResponder.keyboardWillHideNotification
).compactMap { notification in
CGFloat.zero
}.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
}
}
import SwiftUI
struct KeyboardAwareScrollableView : View {
@ObjectBinding var venue: SomeDataModel
var body: some View {
// Apply to any view with an instrinsic scroll view – ScrollView, Form, List, etc
Form {
Section {
TextField($venue.name, placeholder: Text("Venue Name"))
}
Section(header: Text("Address")) {
TextField($venue.street, placeholder: Text("Street"))
TextField($venue.city, placeholder: Text("City"))
TextField($venue.country, placeholder: Text("Country"))
TextField($venue.postalCode, placeholder: Text("Postcode/ZIP"))
}
// etc.
}
.navigationBarTitle(Text("New venue"))
.modifier(AdaptsToSoftwareKeyboard()) // <-- apply the modifier here
}
}
@damirstuhec
Copy link

Great! I've made two changes that some might take as improvements:

1. Extension on View for a cleaner API

extension View {
    var keyboardAware: some View {
        self.modifier(KeyboardAwareScrollableView())
    }
}

Usage

.keyboardAware

2. Edges

Most people probably don't want the edgesIgnoringSafeArea to always be set to .bottom. I think a better solution is to set it to .bottom only when the keyboard is shown.

Solution
Change .edgesIgnoringSafeArea(.bottom) to .edgesIgnoringSafeArea(currentHeight == 0 ? [] : .bottom).

@miotke
Copy link

miotke commented Jun 21, 2020

This seems to have resolved my issue. Thank you for putting this together 👍 .

@jaimeealmanza
Copy link

This works perfectly!

You could also add .animation(.default) after .padding(.bottom, currentHeight) to smooth out the transition since it's pretty fast and it's somewhat noticeable.

@damirstuhec
Copy link

@jaimeealmanza to go a step further, you can use the following animation to match the keyboard transition more closely:

extension Animation {
    static var keyboard: Animation {
        .interpolatingSpring(mass: 3, stiffness: 1000, damping: 500, initialVelocity: 0.0)
    }
}

@ntornado
Copy link

Did you notice that this code stopped working with Xcode 12 beta 3 on iOS 14 beta 3? At least for me, it works great on beta 2 and earlier. The notification process seems to work fine but the view doesn't seem to respect the padding. Can't quite figure out why that is. Thanks!

@ethanyuwang
Copy link

How do you make it scroll a bit higher? I tried this extra padding method but have a chunk of white space instead:

.padding(.bottom, currentHeight == 0 ? 0 : currentHeight + extraPadding)

Simulator Screen Shot - iPhone 11 Pro - 2020-08-31 at 22 31 28

@damirstuhec
Copy link

@ntornado
Copy link

ntornado commented Sep 4, 2020

@LukaszDziwosz
Copy link

LukaszDziwosz commented Nov 11, 2021

It doesn't work for me unfortunately. I only got ScrollView with 10 textfields. I think scrollview don't respect padding in Xcode 12.5.1

@santu1990
Copy link

Nice :)

@sskjames
Copy link

Will this work for TextEditor?

@kiddden
Copy link

kiddden commented Sep 11, 2022

@ethanyuwang did you find a solution for your issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment