SwiftUI How to preset a TextField

I have a SwiftUI Form() that I use to enter inital data using TextField()s. I use the data to record into Core Data. I want to be able to re-edit the data using a Form() but cannot figure out how to preset the TextField()s.


How can I preset the TextField()s?

Accepted Reply

Ah, that little problem. Yeah, I've been bitten by that one. Happily, I worked out a nice little suite of Binding initializers to work around the issue in different ways.


I posted an article about it with a full description of the mechanics, but there are a few different problems, each with their own solution. Frequently these build on the built-in Binding initializer that unwraps an Optional value to return a binding to a non-nil value, or a nil binding:


/// Creates an instance by projecting the base optional value to its
/// unwrapped value, or returns `nil` if the base value is `nil`.
public init?(_ base: Binding<Value?>)


For when your value might be nil, and you want to assign a non-nil 'empty' value automatically (e.g. for a non-optional property on a newly-created CoreData object), here's a Binding initializer that initializes that for you:


extension Binding {
    init(_ source: Binding<Value?>, _ defaultValue: Value) {
        // Ensure a non-nil value in `source`.
        if source.wrappedValue == nil {
            source.wrappedValue = defaultValue
        }
        // Unsafe unwrap because *we* know it's non-nil now.
        self.init(source)!
    }
}


If you have a CoreData property that is optional, you might want to map nil to and from a given 'none' value, for instance an empty string. This will do that for you:


init(_ source: Binding<Value?>, replacingNilWith nilValue: Value) {
    self.init(
        get: { source.wrappedValue ?? nilValue },
        set: { newValue in
            if newValue == nilValue {
                source.wrappedValue = nil
            }
            else {
                source.wrappedValue = newValue
            }
    })
}


Using these two is fairly straightforward. Assuming we have an @ObservedObject referring to a CoreData object with non-optional "title: String?" and optional "notes: String?" attributes, we can now do this:


Form {
    // Items should have titles, so use a 'default value' binding
    TextField("Title", text: Binding($item.title, "New Item"))
    
    // Notes are entirely optional, so use a 'replace nil' binding
    TextField("Notes", text: Binding($item.notes, replacingNilWith: ""))
}
Add a Comment

Replies

You bind the value into a TextField using a binding operator. To get one of those from your

NSManagedObject
instance/subclass, you need to drop it into a property that uses the
@ObservedObject
property wrapper. That will then allow you to use
$
-prefix syntax to obtain a binding to the internal value types held by the object, such as string values.


An off-the-cuff example, not tested:


class MyObject: NSManagedObject {
    @NSManaged var name: String
    @NSManaged var info: String
}

struct MyView: View {
    @ObservedObject var coreDataObject: MyObject
    
    var body: some View {
        Form {
            TextField("Name", text: $coreDataObject.name)
            TextField("Info", text: $coreDataObject.info)
        }
    }
}

If by preset you mean prefill, see this SUI form tutorial, just substitute the hardcoded placeholder text w/your applicable (pulled) CD object strings:


h ttps://www.appcoda.com/swiftui-form-ui/

That gives error:


Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'

Ah, that little problem. Yeah, I've been bitten by that one. Happily, I worked out a nice little suite of Binding initializers to work around the issue in different ways.


I posted an article about it with a full description of the mechanics, but there are a few different problems, each with their own solution. Frequently these build on the built-in Binding initializer that unwraps an Optional value to return a binding to a non-nil value, or a nil binding:


/// Creates an instance by projecting the base optional value to its
/// unwrapped value, or returns `nil` if the base value is `nil`.
public init?(_ base: Binding<Value?>)


For when your value might be nil, and you want to assign a non-nil 'empty' value automatically (e.g. for a non-optional property on a newly-created CoreData object), here's a Binding initializer that initializes that for you:


extension Binding {
    init(_ source: Binding<Value?>, _ defaultValue: Value) {
        // Ensure a non-nil value in `source`.
        if source.wrappedValue == nil {
            source.wrappedValue = defaultValue
        }
        // Unsafe unwrap because *we* know it's non-nil now.
        self.init(source)!
    }
}


If you have a CoreData property that is optional, you might want to map nil to and from a given 'none' value, for instance an empty string. This will do that for you:


init(_ source: Binding<Value?>, replacingNilWith nilValue: Value) {
    self.init(
        get: { source.wrappedValue ?? nilValue },
        set: { newValue in
            if newValue == nilValue {
                source.wrappedValue = nil
            }
            else {
                source.wrappedValue = newValue
            }
    })
}


Using these two is fairly straightforward. Assuming we have an @ObservedObject referring to a CoreData object with non-optional "title: String?" and optional "notes: String?" attributes, we can now do this:


Form {
    // Items should have titles, so use a 'default value' binding
    TextField("Title", text: Binding($item.title, "New Item"))
    
    // Notes are entirely optional, so use a 'replace nil' binding
    TextField("Notes", text: Binding($item.notes, replacingNilWith: ""))
}
Add a Comment