I have a textfield that can be modified programatically. It works fine, until the form section is conditionally displayed, in that case the app freezes when I use the textfield.
Here is my code (I removed unnecessary stuff):
struct test: View {
@Environment(\.presentationMode) var presentationMode
@State private var displaySection = true
@State private var amount: Double?
@FocusState private var isFocused: Bool
var body: some View {
NavigationView{
Form {
if displaySection { // <-- works well without that condition, otherwise app freezes
Section {
VStack {
HStack {
Text("Amount EUR")
Spacer()
TextField("Type amount", value: $amount, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)
}
Text("Set MAX (999)")
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
isFocused = false
amount = 999
}
}
}
}
}
}
}
}
I need to have some sections hidden in the form so I'm stuck on that, didn't succeed to skirt this issue. Problem appears on simulator as well on device.
Is this behavior normal, or is there a possible workaround ? Thanks for your help :)
CodePudding user response:
Divide and conquer.
You presented a really weird behaviour in your app, I would never imagine such bug. I tested your code, it actually freezes the app.
Now, I tested the solution below: move your Section
to a separate view. In my Xcode, it works.
struct Example: View {
@Environment(\.presentationMode) var presentationMode
@State private var displaySection = true
var body: some View {
NavigationView{
Form {
if displaySection { // <-- works well ALSO WITH that condition
ConditionalSection()
}
Button {
withAnimation {
displaySection.toggle()
}
} label: {
Text("Display or not display?")
}
}
}
}
}
struct ConditionalSection: View {
@State private var amount: Double?
@FocusState private var isFocused: Bool
var body: some View {
Section {
VStack {
HStack {
Text("Amount EUR")
Spacer()
TextField("Type amount", value: $amount, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)
}
Text("Set MAX (999)")
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
isFocused = false
amount = 999
}
}
}
}
}
CodePudding user response:
So with Xcode 13.2.1 on Intel and iOS 15.2 I can only get this example code to lock-up in the simulator and on a real device if I type a number of more than three digits in the 'Amount EUR' and then attempt to 'Set the Max to 999' i.e. a three digit number.
In previous versions of SwiftUI the only input binding TextField would work with was String. It seems likely that although the current version accepts binding to numerics the way it is doing this is to map, and the underlying mechanism remains that of the original String based TextField (typing in the TextField with an attached keyboard still unfortunately renders non-numeric characters regardless of keyboardType as it did the last time I experimented https://gist.github.com/shufflingB/23daafa5253c3355cdf18934599cd54c)
For whatever reason this new(ish) mapping process appears to be getting confused when more than three digits are entered and the TextField is then by default automatically adding a number grouping character.
Two work-arounds seem to address the problem:
Push the change to
amount
triggered bySet the Max ...
to a later state update cycle usingDispatchQueue.main.asyncAfter
Give an option to remove the troublesome grouping character from the TextField.
struct ContentView: View {
@Environment(\.presentationMode) var presentationMode
@State private var displaySection = true
@State private var amount: Double?
@FocusState private var isFocused: Bool
var body: some View {
NavigationView {
Form {
if displaySection {
Section {
VStack {
HStack {
Text("Amount EUR")
Spacer()
TextField("Type amount", value: $amount, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)
/* Alternative to Dispatch, remove number grouping
TextField("Type amount", value: $amount, format: .number.grouping(.never))
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)
*/
}
Text("Set MAX (999)")
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
isFocused = false
DispatchQueue.main.asyncAfter(deadline: .now() 0.1) { // <- Push change to subsequent update cycle
amount = 999
}
}
}
}
}
}
}
}
}