When adding a Done button to the keyboard toolbar the toolbar presents and operates as expected on the first view. If you present multiple views using .sheet() once on top of the other, with each keyboard using the same toolbar and Done button, when you get to the third view that has been presented, the toolbar disappears.
I have tried adding a FocusState, FocusValue, NavigationStack and NavigationView, I have tried making sure the field state is cleared and contained within every view but nothing seems to work.
Tested on Version 15.4 iPhone 15 pro sim
import UIKit
import SwiftUI
struct ContentView: View {
@Environment(\.dismiss) var dismiss
@State var isPresenting: Bool = false
@State var text: String = ""
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 32) {
TextField("Enter text", text: $text)
.padding(.top, 20)
.padding(.horizontal, 20)
Button {
isPresenting.toggle()
} label: {
Text("Present View")
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 40)
Spacer()
}
.toolbar {
toolbarContent
}
.sheet(isPresented: $isPresenting) {
SecondView()
}
}
}
@ToolbarContentBuilder
var toolbarContent: some ToolbarContent {
ToolbarItemGroup(placement: .keyboard) {
KeyboardAccessoryDone(title: "Done Content")
}
}
}
struct SecondView: View {
@Environment(\.dismiss) var dismiss
@State var isPresenting: Bool = false
@State var text: String = ""
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 32) {
TextField("Enter text", text: $text)
.padding(.top, 20)
.padding(.horizontal, 20)
Button {
isPresenting.toggle()
} label: {
Text("Present View")
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 40)
Spacer()
}
.toolbar {
toolbarContent
}
.sheet(isPresented: $isPresenting) {
ThirdView()
}
}
}
@ToolbarContentBuilder
var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) {
CancelButton {
dismiss()
}
}
ToolbarItemGroup(placement: .keyboard) {
KeyboardAccessoryDone(title: "Done Second")
}
}
}
struct ThirdView: View {
@Environment(\.dismiss) var dismiss
@State var isPresenting: Bool = false
@State var text: String = ""
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 32) {
TextField("Enter text", text: $text)
.padding(.top, 20)
.padding(.horizontal, 20)
Button {
isPresenting.toggle()
} label: {
Text("Present View")
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 40)
Spacer()
}
.toolbar {
toolbarContent
}
.sheet(isPresented: $isPresenting) {
FourthView()
}
}
}
@ToolbarContentBuilder
var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) {
CancelButton {
dismiss()
}
}
ToolbarItemGroup(placement: .keyboard) {
KeyboardAccessoryDone(title: "Done Third")
}
}
}
struct FourthView: View {
@Environment(\.dismiss) var dismiss
@State var isPresenting: Bool = false
@State var text: String = ""
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 32) {
TextField("Enter text", text: $text)
.padding(.top, 20)
.padding(.horizontal, 20)
Button {
isPresenting.toggle()
} label: {
Text("Present View")
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 40)
Spacer()
}
.toolbar {
toolbarContent
}
}
}
@ToolbarContentBuilder
var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) {
CancelButton {
dismiss()
}
}
ToolbarItemGroup(placement: .keyboard) {
KeyboardAccessoryDone(title: "Done Forth")
}
}
}
public struct KeyboardAccessoryDone: View {
// MARK: - Properties
private let title: String
private let onPress: (() -> ())?
// MARK: - Init
public init(
title: String,
onPress: (() -> ())? = nil
) {
self.title = title
self.onPress = onPress
}
// MARK: - View
public var body: some View {
Spacer()
Button(title) {
if let onPress {
/// If the consumer of this view has chosen to inject a completion closure, then call it.
onPress()
} else {
/// If the consumer of this view has omitted to inject a completion closure, then close the keyboard by calling `endEditing`.
UIApplication.shared.endEditing()
}
}
}
}
public struct CancelButton: View {
private var onPress: (() -> Void)
public init(
onPress: @escaping () -> Void
) {
self.onPress = onPress
}
public var body: some View {
Button {
onPress()
} label: {
Text("Cancel")
}
.accessibilityIdentifier("cancelButton")
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}