diff --git a/iosApp/iosApp/Contractor/ContractorFormSheet.swift b/iosApp/iosApp/Contractor/ContractorFormSheet.swift index 1b55d92..5bf8380 100644 --- a/iosApp/iosApp/Contractor/ContractorFormSheet.swift +++ b/iosApp/iosApp/Contractor/ContractorFormSheet.swift @@ -112,6 +112,7 @@ struct ContractorFormSheet: View { TextField(L10n.Contractors.phoneLabel, text: $phone) .keyboardType(.phonePad) .focused($focusedField, equals: .phone) + .keyboardDismissToolbar() } HStack { @@ -203,6 +204,7 @@ struct ContractorFormSheet: View { .keyboardType(.numberPad) .focused($focusedField, equals: .postalCode) .frame(maxWidth: 100) + .keyboardDismissToolbar() } } header: { Text(L10n.Contractors.addressSection) @@ -220,6 +222,7 @@ struct ContractorFormSheet: View { TextEditor(text: $notes) .frame(height: 100) .focused($focusedField, equals: .notes) + .keyboardDismissToolbar() } } header: { Text(L10n.Contractors.notesSection) diff --git a/iosApp/iosApp/Documents/DocumentFormView.swift b/iosApp/iosApp/Documents/DocumentFormView.swift index 8878947..211e079 100644 --- a/iosApp/iosApp/Documents/DocumentFormView.swift +++ b/iosApp/iosApp/Documents/DocumentFormView.swift @@ -145,6 +145,7 @@ struct DocumentFormView: View { Section(L10n.Documents.warrantyClaims) { TextField(L10n.Documents.claimPhoneOptional, text: $claimPhone) .keyboardType(.phonePad) + .keyboardDismissToolbar() TextField(L10n.Documents.claimEmailOptional, text: $claimEmail) .keyboardType(.emailAddress) TextField(L10n.Documents.claimWebsiteOptional, text: $claimWebsite) @@ -327,6 +328,7 @@ struct DocumentFormView: View { TextField(L10n.Documents.descriptionOptional, text: $description, axis: .vertical) .lineLimit(3...6) + .keyboardDismissToolbar() } header: { Text(L10n.Documents.basicInformation) } footer: { @@ -358,6 +360,7 @@ struct DocumentFormView: View { .textInputAutocapitalization(.never) TextField(L10n.Documents.notesOptional, text: $notes, axis: .vertical) .lineLimit(3...6) + .keyboardDismissToolbar() } .listRowBackground(Color.appBackgroundSecondary) diff --git a/iosApp/iosApp/Helpers/KeyboardDismissToolbar.swift b/iosApp/iosApp/Helpers/KeyboardDismissToolbar.swift new file mode 100644 index 0000000..ecae677 --- /dev/null +++ b/iosApp/iosApp/Helpers/KeyboardDismissToolbar.swift @@ -0,0 +1,28 @@ +import SwiftUI + +/// A view modifier that adds a keyboard toolbar with a "Done" button to dismiss the keyboard. +/// Use this for numeric keyboards (.numberPad, .decimalPad, .phonePad) and multi-line text fields +/// that don't have a return key for dismissal. +struct KeyboardDismissToolbar: ViewModifier { + func body(content: Content) -> some View { + content + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Done") { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + .foregroundColor(Color.appPrimary) + .fontWeight(.medium) + } + } + } +} + +extension View { + /// Adds a keyboard toolbar with a "Done" button to dismiss the keyboard. + /// Use this for numeric keyboards and multi-line text fields. + func keyboardDismissToolbar() -> some View { + modifier(KeyboardDismissToolbar()) + } +} diff --git a/iosApp/iosApp/Onboarding/OnboardingVerifyEmailView.swift b/iosApp/iosApp/Onboarding/OnboardingVerifyEmailView.swift index bf54616..8f68945 100644 --- a/iosApp/iosApp/Onboarding/OnboardingVerifyEmailView.swift +++ b/iosApp/iosApp/Onboarding/OnboardingVerifyEmailView.swift @@ -52,6 +52,7 @@ struct OnboardingVerifyEmailContent: View { .textContentType(.oneTimeCode) .focused($isCodeFieldFocused) .accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.verificationCodeField) + .keyboardDismissToolbar() .onChange(of: viewModel.code) { _, newValue in // Limit to 6 digits if newValue.count > 6 { diff --git a/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift b/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift index 3d971e4..d513b71 100644 --- a/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift +++ b/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift @@ -55,6 +55,7 @@ struct VerifyResetCodeView: View { .multilineTextAlignment(.center) .keyboardType(.numberPad) .focused($isCodeFocused) + .keyboardDismissToolbar() .onChange(of: viewModel.code) { _, newValue in // Limit to 6 digits if newValue.count > 6 { diff --git a/iosApp/iosApp/ResidenceFormView.swift b/iosApp/iosApp/ResidenceFormView.swift index 5f6be63..2a23e02 100644 --- a/iosApp/iosApp/ResidenceFormView.swift +++ b/iosApp/iosApp/ResidenceFormView.swift @@ -156,11 +156,13 @@ struct ResidenceFormView: View { .accessibilityIdentifier(AccessibilityIdentifiers.Residence.yearBuiltField) } .listRowBackground(Color.appBackgroundSecondary) + .keyboardDismissToolbar() Section(header: Text(L10n.Residences.additionalDetails)) { TextField(L10n.Residences.description, text: $description, axis: .vertical) .lineLimit(3...6) .accessibilityIdentifier(AccessibilityIdentifiers.Residence.descriptionField) + .keyboardDismissToolbar() Toggle(L10n.Residences.primaryResidence, isOn: $isPrimary) .accessibilityIdentifier(AccessibilityIdentifiers.Residence.isPrimaryToggle) diff --git a/iosApp/iosApp/Task/CompleteTaskView.swift b/iosApp/iosApp/Task/CompleteTaskView.swift index b54f533..5a2d3aa 100644 --- a/iosApp/iosApp/Task/CompleteTaskView.swift +++ b/iosApp/iosApp/Task/CompleteTaskView.swift @@ -112,6 +112,7 @@ struct CompleteTaskView: View { .foregroundStyle(.secondary) } .padding(.leading, 12) + .keyboardDismissToolbar() } label: { Label(L10n.Tasks.actualCost, systemImage: "dollarsign.circle") } @@ -132,6 +133,7 @@ struct CompleteTaskView: View { TextEditor(text: $notes) .frame(minHeight: 100) .scrollContentBackground(.hidden) + .keyboardDismissToolbar() } } footer: { Text(L10n.Tasks.optionalNotes) diff --git a/iosApp/iosApp/Task/TaskFormView.swift b/iosApp/iosApp/Task/TaskFormView.swift index d4575e9..d1ac16c 100644 --- a/iosApp/iosApp/Task/TaskFormView.swift +++ b/iosApp/iosApp/Task/TaskFormView.swift @@ -188,6 +188,7 @@ struct TaskFormView: View { TextField(L10n.Tasks.descriptionOptional, text: $description, axis: .vertical) .lineLimit(3...6) .focused($focusedField, equals: .description) + .keyboardDismissToolbar() } header: { Text(L10n.Tasks.taskDetails) } footer: { @@ -232,6 +233,7 @@ struct TaskFormView: View { TextField(L10n.Tasks.customInterval, text: $intervalDays) .keyboardType(.numberPad) .focused($focusedField, equals: .intervalDays) + .keyboardDismissToolbar() } DatePicker(L10n.Tasks.dueDate, selection: $dueDate, displayedComponents: .date) @@ -274,6 +276,7 @@ struct TaskFormView: View { .focused($focusedField, equals: .estimatedCost) } .listRowBackground(Color.appBackgroundSecondary) + .keyboardDismissToolbar() if let errorMessage = viewModel.errorMessage { Section { diff --git a/iosApp/iosApp/VerifyEmail/VerifyEmailView.swift b/iosApp/iosApp/VerifyEmail/VerifyEmailView.swift index 13b02f8..2a36468 100644 --- a/iosApp/iosApp/VerifyEmail/VerifyEmailView.swift +++ b/iosApp/iosApp/VerifyEmail/VerifyEmailView.swift @@ -67,6 +67,7 @@ struct VerifyEmailView: View { .padding(.horizontal) .focused($isFocused) .accessibilityIdentifier(AccessibilityIdentifiers.Authentication.verificationCodeField) + .keyboardDismissToolbar() .onChange(of: viewModel.code) { _, newValue in // Limit to 6 digits if newValue.count > 6 {