Post

SwiftUI: Add Custom Padding Above Keyboard

Add custom spacing between TextField and keyboard using Combine publishers and ViewModifier.

SwiftUI: Add Custom Padding Above Keyboard

Since iOS 14, SwiftUI automatically moves views up when the keyboard appears. But what if you need extra padding between your TextField and the keyboard?

Custom Keyboard Padding Modifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import SwiftUI
import Combine

struct KeyboardAvoiding: ViewModifier {
    let inset: CGFloat
    @State private var keyboardPadding: CGFloat = .zero
    
    private var keyboardPublisher: AnyPublisher<Bool, Never> {
        Publishers.Merge(
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .map { _ in true },
            
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .map { _ in false }
        )
        .eraseToAnyPublisher()
    }
    
    func body(content: Content) -> some View {
        content
            .safeAreaInset(edge: .bottom) {
                EmptyView()
                    .frame(height: keyboardPadding)
            }
            .onReceive(keyboardPublisher) { isPresented in
                keyboardPadding = isPresented ? inset : .zero
            }
    }
}

extension View {
    func keyboardAvoiding(_ inset: CGFloat) -> some View {
        modifier(KeyboardAvoiding(inset: inset))
    }
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
struct ContentView: View {
    @State private var text = ""
    
    var body: some View {
        VStack {
            Spacer()
            TextField("Enter text", text: $text)
                .textFieldStyle(.roundedBorder)
        }
        .keyboardAvoiding(50) // 50 points padding above keyboard
    }
}

Advanced: Dynamic Padding with Publishers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct KeyboardPadding: ViewModifier {
    @State private var keyboardHeight: CGFloat = 0
    let padding: CGFloat
    
    private var keyboardPublisher: AnyPublisher<CGFloat, Never> {
        NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillShowNotification)
            .compactMap { notification in
                (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height
            }
            .merge(with: 
                NotificationCenter.default
                    .publisher(for: UIResponder.keyboardWillHideNotification)
                    .map { _ in CGFloat.zero }
            )
            .eraseToAnyPublisher()
    }
    
    func body(content: Content) -> some View {
        content
            .padding(.bottom, keyboardHeight > 0 ? padding : 0)
            .onReceive(keyboardPublisher) { height in
                withAnimation(.easeOut(duration: 0.16)) {
                    keyboardHeight = height
                }
            }
    }
}

Conditional Padding Based on Field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct AdaptiveKeyboardPadding: ViewModifier {
    let padding: CGFloat
    @FocusState private var focusedField: Field?
    @State private var bottomPadding: CGFloat = 0
    
    enum Field {
        case username, password
    }
    
    func body(content: Content) -> some View {
        VStack {
            TextField("Username", text: .constant(""))
                .focused($focusedField, equals: .username)
            
            SecureField("Password", text: .constant(""))
                .focused($focusedField, equals: .password)
        }
        .safeAreaInset(edge: .bottom) {
            Color.clear.frame(height: bottomPadding)
        }
        .onChange(of: focusedField) { field in
            withAnimation {
                bottomPadding = field != nil ? padding : 0
            }
        }
    }
}

iOS 17+ Solution

1
2
3
4
5
6
7
@available(iOS 17.0, *)
extension View {
    func keyboardPadding(_ value: CGFloat) -> some View {
        self.safeAreaPadding(.bottom, value)
            .ignoresSafeArea(.keyboard, edges: .bottom)
    }
}

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.