import SwiftUI import UIKit import Combine extension View { /// Puts a Done button above the keyboard. /// /// The numeric fields (Settings → Average fuel economy and Typical /// fill-up, and the Worth the Drive fill-up box in the station list) use /// `.decimalPad`, which has NO Return key. Without this there is no way to /// dismiss the keyboard once such a field is tapped, and a /// `TextField(value:format:)` only commits the typed value when editing /// ends — so the number never stuck either. /// /// WHY IT IS DRAWN HERE RATHER THAN AS `.toolbar(placement: .keyboard)`: /// the keyboard-toolbar form is device-dependent. The same build showed the /// Done button on the iPhone 15 Pro and NOT on the iPhone SE (both iOS /// 27.0.1, same install, same binary), leaving the SE with no way to /// dismiss or commit a numeric field. Drawing the bar in the screen's own /// bottom safe area renders identically on every device, so the accessibility /// of the numeric fields no longer depends on the accessory view appearing. /// /// Apply ONCE per screen, to the list or form containing the fields: any /// keyboard on that screen shows the bar, so attaching it to each field /// would stack several Done buttons. /// /// - Parameter action: clears the screen's focus state. Prefer this where a /// `@FocusState` exists; the default resigns the current first responder, /// which works for fields inside subviews that have no focus binding (the /// fill-up box in the station list is one). func keyboardDoneButton(action: (() -> Void)? = nil) -> some View { modifier(KeyboardDoneBar(action: action)) } } /// Shows a Done bar while a keyboard is up on this screen. /// /// Visibility comes from the keyboard notifications rather than a `@FocusState` /// so the bar also works for fields inside subviews that have no focus binding. private struct KeyboardDoneBar: ViewModifier { let action: (() -> Void)? @State private var isKeyboardVisible = false func body(content: Content) -> some View { content .safeAreaInset(edge: .bottom, spacing: 0) { if isKeyboardVisible { HStack { Spacer() Button("Done") { if let action { action() } else { UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) } isKeyboardVisible = false } .font(.body.weight(.semibold)) } .padding(.horizontal, 20) .padding(.vertical, 10) .frame(maxWidth: .infinity) .background(.bar) } } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in isKeyboardVisible = true } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in isKeyboardVisible = false } } }