Bit of a battle, but have managed to come up with an approach that with a little bit of more polishing can do a reasonable approximation of an idiomatic macOS text editing experience in pure SwiftUI (and is only (of necessity) slightly hacky).
A demo of this approach and an explanation of how it works can be found over on GitHub at https://github.com/shufflingB/swiftui-macos-undoable-texteditor.
Post
Replies
Boosts
Views
Activity
Turns out TextField's handling of the bound variable is also inconsistent with attempts to set values that are not permitted directly and this behaviour is also present in the version of SwiftUI shipping with Xcode 12.5.1 .
Demo of this is below. About 90% certain this is a bug in SwiftUI's TextField and is not really related to Xcode Beta iOS14 or 15 (maybe a bit more visible because it's bust with ObservableObjects as first noticed).
Without this working it's pretty awkward to get controlled value TextField working, so if you're also impacted by this please provide Apple with Feedback as the more they get the more likely they are to fix.
import SwiftUI
/*
Run TextFieldWeirdnessDemo for a demo of of how TextField's displayed value goes out of alignment with the state
to which it is bound.
Problem exists in at least Xcode 12.5.1. 13 Beta 2 & 3 tested with iOS 14.5 & 15.0 on the simulator.
1) It should not be possible to enter the "3" in the TextField but it is.
2) Can see @State foo is correctly not being updated but TextField just goes off on its merry way
and shows it.
3) If attempt is made to set a "3" in the other control, that does not show it or update.
Conclusion TextField is broken.
*/
struct ATestCtrl: View {
@Binding var aBoundVariable: String
var body: some View {
VStack {
Text("Value of aBoundVar = \"\(aBoundVariable)\"")
Text("\"\(aBoundVariable)\"")
Button("Set aBoundVar to \"1\"", action: { aBoundVariable = "1" })
Button("Set aBoundVar to \"2\"", action: { aBoundVariable = "2" })
Button("Try to set 3 (invalid)", action: { aBoundVariable = "3" })
}
}
}
struct TextFieldWeirdnessDemo: View {
@State var foo: String = ""
var body: some View {
let fooBinding = Binding(
get: {
foo
},
set: { possibleNewFoo in
guard !possibleNewFoo.contains("3") else {
return
}
foo = possibleNewFoo
})
return
VStack {
VStack {
Text("TextField ctrl bound to aBoundVar, incorrectly allows entry of \"3\"")
TextField("\"\"", text: fooBinding)
.keyboardType(.decimalPad)
.background(Color.accentColor.opacity(0.2))
}
.padding()
.border(Color.red)
.padding()
VStack {
Text("ATestCtrl bound to aBoundVar")
ATestCtrl(aBoundVariable: fooBinding)
}
.padding()
.border(Color.red)
.padding()
Spacer()
VStack {
Text("@State value that's been bound to")
Text("\"\(foo)\"")
}
.padding()
.border(Color.green)
Spacer()
}
}
}
struct FeedBackDemo_Previews: PreviewProvider {
static var previews: some View {
TextFieldWeirdnessDemo()
}
}
Noticed that for macOS there's a prefersDefaultFocus(_:in:). Which combined with some of the WWDC notes from Javier over here https://www.swiftui-lab.com/random-lessons#focus-2 leads me think that directly twiddling FocusState to set the default focus is seen as an anti-pattern.
However, until prefersDefaultFocus (or similar) arrives on iOS I've found that it can be made to work by adding a (hacky) small delay to setting it after onAppear gets executed, e.g. something like this ...
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { /// Anything over 0.5 seems to work
self.focusedField = .field
}
}
The implicit idea of the Form is to provide semantic information about app structure that allows the use of screen readers, application of a set of styles, animations, font sizes etc that are HIG compliant, look nice and work well in a "form" context across Apple's various platforms. Which is a nice idea and could be a massive time saver, particularly if cross platform is needed.
Now the problem is currently (and I've not seen anything offlicial saying it should not be done and nor does it raise build or run time errors/warnings) as the 3rd party SwiftUI Companion app mentions "When using a custom control, results are undefined". Which very much agrees with my experience that it works brilliantly if using bog standard components and no custom animations. But try and embed any custom View and it will go spectacularly wrong at runtime in all sorts of weird and wonderful ways that are next to impossible to debug.
So, is it worth bothering with?
In my opinion, at the moment it really depends on the how easy (or not) it is to achieve the required data collection Form with totally standard control components vs the pain of implementing any custom cross platform support if Form is not used.
I've got the same problem and have reported it to Apple via the Feedback Assistant.
If anyone else experiences the same issue it's a good idea to report it as well because Apple actively uses the number of independently raised, but related Feedback Assistant tickets to determine what to prioritize fixing.
Same problem .
Similar issue with Xcode here:
1) Displaying on the MBP's built in screen, the Preferences window opens without crashing.
2) Try the same on either of my two external, non-Apple monitors, then Xcode crashes.