Skip to content

Instantly share code, notes, and snippets.

@prafullakumar
Last active April 30, 2021 08:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save prafullakumar/a12b15da905fd70b29d8fa316224b167 to your computer and use it in GitHub Desktop.
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
//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