https://youtu.be/ngExUJ7gyb8
Please observe the different behavior between system keyboard and the one using SwiftUI.
The first 5 seconds using spotlight search, you'll notice the typing is complete and the word picker does not flash for every character input.
In the last 5 second, you'll notice using SwiftUI's keyboard, the typing will be break every couple characters, and the word picker flashes for every new character input.
The same behavior can be observed using the simplest code
struct ContentView: View {
#State private var inputText = ""
var body: some View {
VStack(spacing: 10.0) {
TextField("Tap here", text: $inputText)
}
}
}
Is that a bug of SwiftUI? Or did I miss something?
Related
In macOS Ventura 13.0.1 and Xcode 14.1, I'm experiencing an issue where when I type in the beginning of a TextField, the cursor for some reason moves to the back.
I was able to reproduce it very simply:
struct ContentView: View {
#State private var text = "DEFG"
var body: some View {
NavigationSplitView {
Text("Sidebar")
} detail: {
VStack {
TextField("Text", text: $text)
}
}
}
}
To reproduce:
Open the app, and move the cursor to the start of the TextField. (so the insertion point is before the "D".
Type a letter, like "A", and the insertion point should jump to the end for some reason.
I have determined the cause is the VStack. For some reason, removing it fixes this issue entirely. However, in my real app, I do need the VStack as I have other elements too.
I wonder what's going on here. Is there some major oversight I have made? Or could this be an issue with SwiftUI?
If anyone is having trouble reproducing, I can upload a video.
One of my packages that I maintain uses editingChanged on TextField() to detect when the field is actually focused and then modify the text. This has worked fine on other iOS versions but in iOS16 it doesn't seem you're able to update a value from within a TextField if it's being used by that TextField
Is this the wrong way to update an #State property and it was bugged before 16 or is this something that was bugged in 16? If it's bugged, no big deal... but if I'm doing something wrong, of course I would like to fix that.
Below is a simplified version of what my package does, remember this was working before 16. You can even load it into a 15.5 sim and a 16 sim and see the difference.
#State private var text = ""
var body: some View {
Form {
TextField("", text: $text) { editingChanged in
text = "Should update text, but doesn't."
}
Button(action: {
text = "This will update without issue."
}) {
Text("Manually update via button.")
}
}
}
Try to use the following:
TextField("", text: $text)
.onChange(of: text) {
text = "should work now"
}
Is there any way to programmatically move the cursor to a specific text line or select it within a SwifUI TextEditor?
For example, if there is a TextEditor with 10 lines written in it.
When the user presses a button, the cursor will navigate to, or the text will be selected on the 3rd line.
This is currently not possible using the default SwiftUI TextEditor. You can achieve the desired behavior by wrapping NSTextField(/UITextField) in a NSViewRepresentable(/UIViewRepresentable).
I recently implemented this for CodeEditor. You can check out the implementation there (especially the changes from my PR).
However, since you mentioned code in one of the comments, you might also just want to use CodeEditor as a whole.
With my implementation, you can provide the editor with a binding to a Range<String.Index>.
struct ContentView: View {
static private let initialSource = "let a = 42\n"
#State private var source = Self.initialSource
#State private var selection = Self.initialSource.endIndex..<Self.initialSource.endIndex
var body: some View {
CodeEditor(source: $source,
selection: $selection,
language: .swift,
theme: .ocean,
autoscroll: true)
Button("Select All") {
selection = source.startIndex..<source.endIndex
}
}
}
You can move the cursor by updating selection. If the range is "empty" you just move the cursor to the start index. Otherwise the characters from start (included) to end index (excluded) get selected.
The solutions provided here should allow you to find the right String.Index for the line you want to place your cursor in.
If you want to select the whole line, scan the string from this String.Index in both directions until you find a newline character.
A coworker came up with the following SwiftUI example which looks like it works just as expected (you can enter some text and it gets mirrored below), but how it works is surprising to me!
import SwiftUI
struct ContentView: View {
#State var text = ""
var body: some View {
VStack {
TextField("Change the string", text: $text)
WrappedText(text: $text)
}
}
}
struct WrappedText: View {
#Binding var text: String
var body: some View {
Text(text)
}
}
My newbie mental model of SwiftUI led me to think that typing in the TextField would change the $text binding, which would in turn mutate the text #State var. This would then invalidate the ContentView, triggering a fresh invocation of body. But interestingly, that's not what happens! Setting a breakpoint in ContentView's body only gets hit once, while WrappedText's body gets run every time the binding changes. And yet, as far as I can tell, the text state really is changing.
So, what's going on here? Why doesn't SwiftUI re-invoke ContentView's body on every change to text?
On State change SwiftUI rendering engine at first checks for equality of views inside body and, if some of them not equal, calls body to rebuild, but only those non-equal views. In your case no one view depends (as value) on text value (Binding is like a reference - it is the same), so nothing to rebuild at this level. But inside WrappedText it is detected that Text with new text is not equal to one with old text, so body of WrappedText is called to re-render this part.
This is declared rendering optimisation of SwiftUI - by checking & validating exact changed view by equality.
By default this mechanism works by View struct properties, but we can be involved in it by confirming our view to Eqatable protocol and marking it .equatable() modifier to give some more complicated logic for detecting if View should be (or not be) re-rendered.
I'd like to use a SwiftUI TextField and a SwiftUI List to render a "search box" above a list of items. Something roughly like the search box available in Safari's Help menu item...which provides a search box where you can always enter text while simultaneously browsing through the list of results using the up and down arrow keys.
I've played with onMoveCommand, focusable, and adjustments to the "parent" NSWindow, but haven't found a clear and obvious way for the TextField to constantly accept input while still being able to navigate the underlying List using the up and down arrow keys. The following code allows for either text to be entered in the TextField, or list entries to be navigated through, but not both at the same time...
struct ContentView: View {
#State var text: String = ""
#State var selection: Int? = 1
var body: some View {
VStack {
TextField("Enter text", text: $text)
List(selection: $selection) {
ForEach((1...100), id: \.self) {
Text("\($0)")
}
}
}
}
}
You can wrap a NSTextField in a NSViewRepresentable and use the NSTextFieldDelegate to intercept the move up and down keys. You can take a look into my suggestions demo.
Source of a text field with suggestions