Last active
April 30, 2021 08:11
-
-
Save prafullakumar/a12b15da905fd70b29d8fa316224b167 to your computer and use it in GitHub Desktop.
This is a demo to Show form as SwiftUI View with ability to move from x text field to x+1 text field. It also have interactive keyboard dismiss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Allow to move next text field on Next Tap | |
//Adds next in toolbar if keypad is number pad | |
//Allow interactive keyboard dismiss | |
import SwiftUI | |
import Introspect | |
@main | |
struct DemoFormKeyboardIssueFix: App { | |
@State var name: String = "" | |
@State var profession: String = "" | |
@State var education: String = "" | |
@State var address: String = "" | |
@State var city: String = "" | |
@State var postalCode: String = "" | |
@State var state: String = "" | |
@State var country: String = "" | |
@State var fieldFocus: [Bool] = Array(repeating: false, count: 7) | |
var body: some Scene { | |
WindowGroup { | |
NavigationView { | |
ScrollView { | |
VStack {///tag text fields 0-n order | |
Section(header: SectionHeader(title: "Please Enter your name")) { | |
FormTextField ( | |
placeholder: "Name", | |
text: $name, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
tag: 0 | |
).modifier(DefaultTextFieldTheme()) | |
} | |
Section(header: SectionHeader(title: "Please Enter Profession")) { | |
FormTextField ( | |
placeholder: "Profession", | |
text: $profession, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
tag: 1 | |
).modifier(DefaultTextFieldTheme()) | |
} | |
Section(header: SectionHeader(title: "Please Enter Highest Education")) { | |
FormTextField ( | |
placeholder: "Education", | |
text: $education, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
tag: 2 | |
).modifier(DefaultTextFieldTheme()) | |
} | |
Section { | |
FormTextField ( | |
placeholder: "Street/Road/House No", | |
text: $address, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
tag: 3 | |
).modifier(DefaultTextFieldTheme()) | |
} | |
Section { | |
FormTextField ( | |
placeholder: "City", | |
text: $city, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
tag: 4 | |
).modifier(DefaultTextFieldTheme()) | |
.id(4) | |
} | |
Section { | |
FormTextField ( | |
placeholder: "PIN", | |
text: $postalCode, | |
focusable: $fieldFocus, | |
returnKeyType: .next, | |
keyboardType: .numberPad, | |
tag: 5 | |
).modifier(DefaultTextFieldTheme()) | |
} | |
Section { | |
FormTextField ( | |
placeholder: "Country", | |
text: $country, | |
focusable: $fieldFocus, | |
returnKeyType: .done, | |
tag: 6 | |
) | |
.modifier(DefaultTextFieldTheme()) | |
} | |
} | |
}.introspectScrollView { (scrollView) in | |
scrollView.keyboardDismissMode = .interactive | |
}.navigationBarTitle("Demo", displayMode: .inline) | |
.navigationBarItems(trailing: Button(action: { | |
//go to next screen | |
}, label: { | |
Text("Next") | |
})) | |
} | |
} | |
} | |
} | |
struct SectionHeader: View { | |
let title: String | |
var body: some View { | |
HStack { Text(title) | |
.textCase(.none) | |
.foregroundColor(.primary); | |
Spacer() | |
}.padding() | |
} | |
} | |
struct DefaultTextFieldTheme: ViewModifier { | |
func body(content: Content) -> some View { | |
content.padding() | |
.background(Color(UIColor.tertiarySystemBackground.withAlphaComponent(0.5))) | |
.cornerRadius(8) | |
.padding() | |
} | |
} | |
struct FormTextField: UIViewRepresentable { | |
let placeholder: String | |
@Binding var text: String | |
var focusable: Binding<[Bool]>? = nil | |
var returnKeyType: UIReturnKeyType = .next | |
var autocapitalizationType: UITextAutocapitalizationType = .none | |
var keyboardType: UIKeyboardType = .default | |
var tag: Int | |
func makeUIView(context: Context) -> UITextField { | |
let textField = UITextField(frame: .zero) | |
textField.delegate = context.coordinator | |
textField.placeholder = placeholder | |
textField.returnKeyType = returnKeyType | |
textField.autocapitalizationType = autocapitalizationType | |
textField.keyboardType = keyboardType | |
textField.textAlignment = .left | |
textField.tag = tag | |
//toolbar | |
if keyboardType == .numberPad { ///keyboard does not have next so add next button in the toolbar | |
var items = [UIBarButtonItem]() | |
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) | |
let toolbar: UIToolbar = UIToolbar() | |
toolbar.sizeToFit() | |
let doneButton = UIBarButtonItem(title: "Next", style: .plain, target: context.coordinator, action: #selector(Coordinator.showNextTextField)) | |
items.append(contentsOf: [spacer, doneButton]) | |
toolbar.setItems(items, animated: false) | |
textField.inputAccessoryView = toolbar | |
} | |
//Editin listener | |
textField.addTarget(context.coordinator, action: #selector(Coordinator.textFieldDidChange(_:)), for: .editingChanged) | |
return textField | |
} | |
func updateUIView(_ uiView: UITextField, context: Context) { | |
uiView.text = text | |
if let focusable = focusable?.wrappedValue { | |
if focusable[uiView.tag] { ///set focused | |
uiView.becomeFirstResponder() | |
} else { ///remove keyboard | |
uiView.resignFirstResponder() | |
} | |
} | |
} | |
func makeCoordinator() -> Coordinator { | |
Coordinator(self) | |
} | |
final class Coordinator: NSObject, UITextFieldDelegate { | |
let formTextField: FormTextField | |
var hasEndedViaReturn = false | |
weak var textField: UITextField? | |
init(_ formTextField: FormTextField) { | |
self.formTextField = formTextField | |
} | |
func textFieldDidBeginEditing(_ textField: UITextField) { | |
self.textField = textField | |
guard let textFieldCount = formTextField.focusable?.wrappedValue.count else { return } | |
var focusable: [Bool] = Array(repeating: false, count: textFieldCount) //remove focus from all text field | |
focusable[textField.tag] = true ///mark current textField focused | |
formTextField.focusable?.wrappedValue = focusable | |
} | |
///work around for number pad | |
@objc func showNextTextField() { | |
if let textField = self.textField { | |
_ = textFieldShouldReturn(textField) | |
} | |
} | |
func textFieldShouldReturn(_ textField: UITextField) -> Bool { | |
hasEndedViaReturn = true | |
guard var focusable = formTextField.focusable?.wrappedValue else { | |
textField.resignFirstResponder() | |
return true | |
} | |
if (textField.tag + 1) != focusable.count { ///move focus to next text field if exist | |
focusable[textField.tag + 1] = true | |
} | |
focusable[textField.tag] = false ///remove focus from current text field | |
formTextField.focusable?.wrappedValue = focusable | |
return true | |
} | |
func textFieldDidEndEditing(_ textField: UITextField) { | |
if hasEndedViaReturn == false {///user dismisses keyboard | |
guard let textFieldCount = formTextField.focusable?.wrappedValue.count else { return } | |
///reset all text field, so that makeUIView cannot trigger keyboard | |
formTextField.focusable?.wrappedValue = Array(repeating: false, count: textFieldCount) | |
} else { | |
hasEndedViaReturn = false | |
} | |
} | |
@objc func textFieldDidChange(_ textField: UITextField) { | |
formTextField.text = textField.text ?? "" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment