Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InputAccessoryView / View Pinned to Keyboard with SwiftUI

Tags:

swiftui

Is there an equivalent to InputAccessoryView in SwiftUI (or any indication one is coming?)

And if not, how would you emulate the behavior of an InputAccessoryView (i.e. a view pinned to the top of the keyboard)? Desired behavior is something like iMessage, where there is a view pinned to the bottom of the screen that animates up when the keyboard is opened and is positioned directly above the keyboard. For example:

Keyboard closed:

keyboard closed

Keyboard open:

keyboard open

like image 522
zzz Avatar asked Jul 08 '19 19:07

zzz


2 Answers

iOS 15.0+

macOS 12.0+,Mac Catalyst 15.0+

ToolbarItemPlacement has a new property in iOS 15.0+

keyboard

On iOS, keyboard items are above the software keyboard when present, or at the bottom of the screen when a hardware keyboard is attached. On macOS, keyboard items will be placed inside the Touch Bar. https://developer.apple.com

struct LoginForm: View {
    @State private var username = ""
    @State private var password = ""
    var body: some View {
        Form {
            TextField("Username", text: $username)
            SecureField("Password", text: $password)

        }
        .toolbar(content: {
            ToolbarItemGroup(placement: .keyboard, content: {
                Text("Left")
                Spacer()
                Text("Right")
            })
        })
    }
}


iMessage like InputAccessoryView in iOS 15+.


struct KeyboardToolbar<ToolbarView: View>: ViewModifier {
    private let height: CGFloat
    private let toolbarView: ToolbarView
    
    init(height: CGFloat, @ViewBuilder toolbar: () -> ToolbarView) {
        self.height = height
        self.toolbarView = toolbar()
    }
    
    func body(content: Content) -> some View {
        ZStack(alignment: .bottom) {
            GeometryReader { geometry in
                VStack {
                    content
                }
                .frame(width: geometry.size.width, height: geometry.size.height - height)
            }
            toolbarView
                .frame(height: self.height)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}


extension View {
    func keyboardToolbar<ToolbarView>(height: CGFloat, view: @escaping () -> ToolbarView) -> some View where ToolbarView: View {
        modifier(KeyboardToolbar(height: height, toolbar: view))
    }
}

And use .keyboardToolbar view modifier as you would normally do.


struct ContentView: View {
    @State private var username = ""
    
    var body: some View {
        NavigationView{
            Text("Keyboar toolbar")
                .keyboardToolbar(height: 50) {
                    HStack {
                        TextField("Username", text: $username)
                    }
                    .border(.secondary, width: 1)
                    .padding()
                }
        }
    }
}
like image 109
mahan Avatar answered Sep 19 '22 22:09

mahan


I've solved this problem using 99% pure SwiftUI on iOS 14. In the toolbar you can show any View you like.

That's my implementation:

import SwiftUI

 struct ContentView: View {

    @State private var showtextFieldToolbar = false
    @State private var text = ""

    var body: some View {
    
        ZStack {
            VStack {
                TextField("Write here", text: $text) { isChanged in
                    if isChanged {
                        showtextFieldToolbar = true
                    }
                } onCommit: {
                    showtextFieldToolbar = false
                }
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            }
        
             VStack {
                Spacer()
                if showtextFieldToolbar {
                    HStack {
                        Spacer()
                        Button("Close") {
                            showtextFieldToolbar = false
                            UIApplication.shared
                                    .sendAction(#selector(UIResponder.resignFirstResponder),
                                            to: nil, from: nil, for: nil)
                        }
                        .foregroundColor(Color.black)
                        .padding(.trailing, 12)
                    }
                    .frame(idealWidth: .infinity, maxWidth: .infinity,
                           idealHeight: 44, maxHeight: 44,
                           alignment: .center)
                    .background(Color.gray)   
                }
            }
        }
    }
}

 struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
like image 32
DungeonDev Avatar answered Sep 20 '22 22:09

DungeonDev