SwiftUI equivalent of input accessory view to a UIViewController, i.e. view above Keyboard is always visible like in iMessage
Asked Answered
H

1

9

Simple input accessory view on UIViewController

I want to have an input accessory view like you see in iMessage. The bar is always visible at the bottom of the screen. When the TextField gets focus, the keyboard jumps in and the bar automatically moves up, while sticking at the top of the keyboard.

Input accessory must be visible even if no TextInput has focus

In UIKit the ViewController can override inputAccessory and become the first Responder. So the Input accessory is permanentely visible. I could not find a way to do this in SwiftUI.

Keyboard and accessory must shrink the scrollview

When the keyboard pops up, the scroll view which holds the messages and fills the screen must shrink accordingly. The same applies when dragging the scrollview down und moving the keyboard interactively. The view has to be resized while dragging.

Most important: must work when dragging a scrollview down and moving the keyboard interactively

The most important requirement is that this must work smoothly when dragging then scrollview which moves the keyboard down interactively. It's the same behavior in iMessage or WhatsApp. The input accessory must move while dragging and also the scrollview must be resized. After the keyboard is dismissed the toolbar / input accessory must stay visible at bottom.

Not a duplicate

I read InputAccessoryView / View Pinned to Keyboard with SwiftUI, but this is not similar to my question. I need the toolbar to be visible, even if the keyboard is not shown. Also the accepted answer in that question does not work properly with iOS 15. Dragging to dismiss the keyboard leaves a white gap.

UIKit works

In the UIKit implementation the holding view is an UITableViewController with keyboard dismiss interactive set. That means, when the keyboard is shown, you can drag the view and in the same move drag the keyboard and the accessory view down until the keyboard dissappears. The accessory view still remains at bottom though.

Implementing in UIKit is straight forward and looks like this. Everything comes right out of the box.

class ChatTableViewController: UITableViewController {
...

    override var inputAccessoryView: UIView? {
        return inputBar
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }
...
}

UIKit implementation looks fine

UIKit implementation looks fine

Need help with SwiftUI (Xcode 13 beta 5, iOS 15)

For SwiftUI I already found out that in iOS 15 there is a placement: .keyboard parameter, that allows putting a toolbar on top of the keyboard. The problem is, that this only works if a TextField is focussed. If no TextField is focussed, i.e. the keyboard is not visible or dismissed, the toolbar is not visible.

struct Chat: View {
    @State private var text: String = ""
    
    var body: some View {
        ZStack {
            ScrollView {
                ScrollViewReader { value in
                    LazyVStack(spacing: 15) {
                        ForEach((1...10), id: \.self) { x in
                            let isOwner = x % 2 == 0;
                            
                            ChatMessage(isOwner: isOwner)
                                .padding(.leading, isOwner ? 90 : 0)
                                .padding(.trailing, isOwner ? 0 : 90)
                        }
                    }
                    .padding(.horizontal)
                    .padding(.top, 10)
                    .onAppear {
                        value.scrollTo(10, anchor: .bottomTrailing)
                    }
                }
            }
            .background(Color.red)
            .ignoresSafeArea(.keyboard)

            VStack {
                // Toolbar only visible if this box has focus
                TextField("", text: $text)
                    .padding(10)
                    .background(Color(.systemGray6))
                    .cornerRadius(5)
            }
        }
        .navigationBarTitle("test")
        .navigationBarTitleDisplayMode(.inline)
        .onAppear {
            UIScrollView.appearance().keyboardDismissMode = .interactive
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                HStack {
                    TextField("", text: $text)
                        .padding(10)
                        .background(Color(.systemGray6))
                        .cornerRadius(5)

                    Text("Hello")
                }
                .padding(.vertical, 5)
                .padding(.horizontal)
                .background(Color.blue)
            }
        }
    }
}

But without focus on the TextField, the toolbar is not visible at all

But without focus on the TextField, the toolbar is not visible at all

Hellkite answered 20/8, 2021 at 23:52 Comment(7)
@LeoDabus Please have a look in my details. It's not about assigning a toolbar to a view. I want that toolbar to be permanently visible, even if you TextField has focus.Hellkite
I’m having the exact same issue and love to find a solution to this.Oxyacetylene
Have you figured out a way?Barbur
Sorry, still no way found :(Hellkite
Please try my solution github.com/frogcjn/BottomInputBarSwiftUICorneliacornelian
Thank you so much @Corneliacornelian your solution works well! It's true, swift ui is still at this day full of wierd bugs. UIKIT recently has keyboardLayoutGuide which makes all kinds of work like this mich easier. But this guide is still not available in swift ui. I switched to UIKit which works very well nower days.Hellkite
Does this answer your question? InputAccessoryView / View Pinned to Keyboard with SwiftUICorneliacornelian
F
2

As long as your view is anchored to the bottom of the viewport in some way, it should move up when the virtual keyboard is displayed. For example, here I'm using an .overlay modifier to display a simple view, using a VStack with a Spacer() to ensure the view is at the bottom of the container:

NavigationView {
  EventForm()
    .overlay {
      VStack {
        Spacer()
        // replace the view below with your toolbar
        Text("Toolbar")
          .padding()
          .frame(maxWidth: .infinity)
          .background(Color.cyan)
      }
    }
}

Toolbar staying above keyboard

When the keyboard isn't present, the view will by default avoid the safe area (notice how the coloured background expands downwards).

If you want to avoid that behaviour, then you can use the .ignoresSafeArea modifier, but you must take care to exclude the keyboard from the list of safe area views to ignore. Right now, the only safe area values are .container, and .keyboard, so you can be explicit:

.ignoresSafeArea(.container, edges: .bottom)
Frodine answered 21/8, 2021 at 10:5 Comment(3)
Thanks for the answer. There are two missing points here. The first one I was not clear enough in my question, so I added this detail. When the keyboard moves up, the filling view must shrink accordingly. The second and most important is that resizing also happens when the scrollview is dragged down und the keyboard is moved interactively.Hellkite
I don't know what an EventForm is.. but I've tried the same code replacing event form with a Rectangle().fill(.red) and the view does not go up when they keyboard comes into the screen.Ton
The bad news is that if you use interactively dismissing the keyboard, there is a layout bug.Corneliacornelian

© 2022 - 2024 — McMap. All rights reserved.