SwiftUI - Text working, TextField gives error - swiftui

This works:
ForEach(toDoItems) {toDoItem in
Text(toDoItem.title)
}
This gives "Value of type 'NSManagedObject' has no member 'title'":
ForEach(toDoItems) {toDoItem in
TextField("", text: toDoItem.title)
}
Not sure why it no longer works. The data is coming from core data.

Text takes a String into its initializer, because it only displays it and cannot mutate it. Whatever goes into Text is read-only.
TextField takes a Binding, because it mutates the value. The binding connects the TextField and the parent view so it knows about any changes to the input.
e.g.:
struct ContentView: View {
let title: String = "This is a string"
#State var inputString: String = ""
var body: some View {
VStack(alignment: .leading) {
Text(title)
TextField("Here goes a binding",
text: $inputString)
}
}
}

Related

Document is not being marked dirty when element is being changed programmatically

What is wrong with this SwiftUI code?
When the button "Reset Text" is pressed the document data is changed (as seen in the debugger), but the view is not updated.
If the document is then saved, the modified data is not saved.
The method "func fileWrapper" is not called because it does not think that the field "text" has changed.
If characters are typed in the TextEditor so the field is dirty and then saved, the data is saved.
If characters are types in the TextEditor so the field is dirty, then the "Reset Text" button pressed, the TextEditor view is not updated, but the "Reset Text" is saved.
If I wait a while 1 to 3 minutes, the TextEditor will sometimes update to the new text.
import SwiftUI
import UniformTypeIdentifiers
#main
struct DocumentAppApp: App {
var body: some Scene {
DocumentGroup(newDocument: DocumentAppDocument()) { file in
ContentView(document: file.$document)
}
}
}
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
}
class DocumentAppDocument: ObservableObject, FileDocument {
#Published var text: String
init(text: String = "This is some text") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
required init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
struct ContentView: View {
#Binding var document: DocumentAppDocument
var body: some View {
VStack {
TextEditor(text: $document.text)
Button(action: {
// document.objectWillChange.send()
document.text = "Reset Text"
}, label: { Text("Reset Text") })
.frame(width: 200, height: 50)
.border(Color.black)
.padding()
}
}
}
I tried added a call to document.objectWillChange.send() and it made no difference.
I tried as both a struct and a class
I've tried various combinations of #State, #StateObject and #Binding.
It looks like I could call .save on the DocumentGroup, but I couldn't figure out how to get ahold of it.
Test case
Create a new document.
Type in some text -> test is there
Save document.
Open document -> text is there.
Press "Reset Text" Button. Nothing happens.
Save.
Open -> previous text is there.
Type some text, press button -> nothing happens.
Save.
Open -> "Reset Text: text is present.
I found the problem.
class DocumentAppDocument: ObservableObject, FileDocument {
Should not be a class and ObservableObject.
The Binding of the class and DocumentAppDocument and the #Published don't work with each other.
A workaround I used when I had a similar issue was to add a Boolean property to the document struct that stores whether the document has been edited. Changing the value from false to true should mark the document as changed. In your case you would set the property to true when clicking the Reset Text button.

SwiftUI view get views in the body or define view as property

I am trying to get the view inside the body for observing purpose, but looking for different ways the view inside the body can be accessed.
struct ContentView: View {
#State var userNameText: String
var body: some View {
startObservingInput()
return TextField("hello", text: $userNameText)
}
func startObservingInput() {
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
Option 1 is simple, where we pass the TextField view.
Option 2 is something I am looking for, if we can get any view inside the hierarchy. In this case Text Field.
Option 3, Tried to create a property but I get the following error.
ex:
struct ContentView: View {
#State var userNameText: String
var textField = TextField("hello", text: $userNameText)
......
}
Cannot use instance member '$userNameText' within property initializer; property initializers run before 'self' is available
SwiftUI is different from what you're probably used to. Unlike UIKit, you don't "store" views in properties. There's no delegates either.
Instead, you directly pass in a property — userNameText — that will be linked to the text field's text. Since this updates itself automatically, you can use the onChange modifier to observe changes.
struct ContentView: View {
#State var userNameText: String
var body: some View {
TextField("hello", text: $userNameText)
.onChange(of: userNameText) { newValue in
print("Text changed to: \(newValue)")
}
}
}
Here is what I did, look at it:
//
// ViewProp.swift
// SwiftDemo1
//
// Created by CreoleMacbookPro on 12/19/22.
//
import SwiftUI
struct ViewProp: View {
#State var userNameText: String = " "
var body: some View {
let textField: TextField<Text> = TextField("hello", text: $userNameText)
let simpleText: Text = Text("Hello, World!")
let _ = print(type(of: textField))
startObservingInput(textField: textField)
Button {
userNameText = "Jatin Bhuva"
} label: {
Text("Press Me..")
}
// textField
}
func startObservingInput(textField: TextField<Text>) -> some View {
textField
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
struct ViewProp_Previews: PreviewProvider {
static var previews: some View {
ViewProp()
}
}

SwiftUI Picker selection not updated unless value shown in view (using Enum for selection)

import SwiftUI
enum TestEnum: String, CaseIterable {
case firstValue = "First Value"
case secondValue = "Second Value"
case thirdValue = "Third Value"
}
struct TestView: View {
#State private var testEnumSelection = TestEnum.allCases.first!
#State private var isShowingSheet = false
var body: some View {
VStack {
Picker("Test Enum Selection", selection: $testEnumSelection) {
ForEach(TestEnum.allCases, id: \.self) { testEnum in
Text(testEnum.rawValue)
}
}
//Text("Enum Selection: \(testEnumSelection.rawValue)") Enum value not updated if this line is not inlcuded
Button("Show Sheet", action: {
isShowingSheet = true
})
}
.padding()
.sheet(isPresented: $isShowingSheet) {
Text(testEnumSelection.rawValue)
.padding()
}
}
}
I am trying to use an enum value selected from a picker in a sheet view but the value from the picker is not being updated for the sheet. The value does get updated if I show the picker selection on screen elsewhere like in a Text object but I don't want to do that.
Could someone explain to me why I need to show the enum selection for it to be updated for the sheet and how to get around doing this?
The sheet content is created once, so it is not updated when state in parent is updated.
The possible solution is to separate sheet content into standalone view and use binding - bound variable will update view internals.
Here is a modified part (tested with Xcode 13.2 / macOS 12.1)
.padding()
.sheet(isPresented: $isShowingSheet) {
SheetContent(value: $testEnumSelection) // << here !!
}
}
}
struct SheetContent: View {
#Binding var value: TestEnum
var body: some View {
Text(value.rawValue)
.padding()
}
}

swiftui Textfield moves cursor to end with each change

If I pass a binding to the TextField then whenever you edit the centre of the text the cursor will jump to the end of the line after each character insert.
This only occurs on an actual device (iPadOS). The simulator does not display this behaviour.
My work around is to create a state variable that I set onAppear with the value from the binding and copy onEditingChanged. I then pass this state variable to the TextField instead of the binding variable directly. This breaks the one source of truth.
Does anyone have a better solution.
Swift 5
iOS 14.7
XCODE 13 Beta Aug 10
struct infoSheetView: View {
#Binding var cameraURL: String
#State var tmpCameraURL: String = ""
var body: some View {
VStack {
Form{
Section(header: Text("Camera").font(.title)) {
TextField("Camera URL", text: $tmpCameraURL, onEditingChanged: {_ in
cameraURL = tmpCameraURL
}).autocapitalization(.none).disableAutocorrection(true)
}
}.frame(width:400,height:390)
.onAppear() {
tmpCameraURL = cameraURL
}
}
}

String Interpolation with Text View and Textfield in Swiftui

Hi I'm wondering if there's any way to have string interpolation with a textfield and Text in Swiftui. Like
Text("hi \(TextField("Enter your name", $name)")
You can try below code
struct ContentView: View {
#State private var name = ""
var body: some View {
VStack{
TextField("Enter your name", text: $name)
Text("Hi \(name)")
}
}
}
Hope this is what you want
If you really want to do that you can put your statements in an Hstack like so:
import SwiftUI
struct SwiftUIView: View {
#State var name = ""
var body: some View {
HStack {
Text("Hi")
TextField("Name", text: $name)
.frame(width: (name.isEmpty ? 45 : 0) + CGFloat(name.count) * 9)
Text("blabla")
}
}
}
Note: This gives you a dynamic change, but not a perfect one Because the size of each character is different you will get a bigger whitespace at the end. I just choose 9 here, for char width. I still think having a extra Textfield and using the variable then later, is the better option.