Search code examples
core-dataswiftuiswiftui-form

My segmented picker has normal Int values as tags, How is this passed to and from CoreData?


My SwiftUI segmented control picker uses plain Int ".tag(1)" etc values for its selection.

CoreData only has Int16, Int32 & Int64 options to choose from, and with any of those options it seems my picker selection and CoreData refuse to talk to each other.

How is this (??simple??) task achieved please?

I've tried every numeric based option within CoreData including Int16-64, doubles and floats, all of them break my code or simply just don't work.

Picker(selection: $addDogVM.gender, label: Text("Gender?")) {
    Text("Boy ♂").tag(1)
    Text("?").tag(2)
    Text("Girl ♀").tag(3)
}

I expected any of the 3 CoreData Int options to work out of the box, and to be compatible with the (standard) Int used by the picker.


Solution

  • Each element of a segmented control is represented by an index of type Int, and this index therefore commences at 0.

    So using your example of a segmented control with three segments (for example: Boy ♂, ?, Girl ♀), each segment is represented by three indexes 0, 1 & 2.

    If the user selects the segmented control that represents Girl ♀, then...

    segmentedControl.selectedSegmentIndex = 2
    

    When storing a value using Core Data framework, that is to be represented as a segmented control index in the UI, I therefore always commence with 0.

    Everything you read from this point onwards is programmer preference - that is and to be clear - there are a number of ways to achieve the same outcome and you should choose one that best suits you and your coding style. Note also that this can be confusing for a newcomer, so I would encourage patience. My only advice, keep things as simple as possible until you've tested and debugged and tested enough to understand the differences.

    So to continue:

    The Apple Documentation states that...

    ...on 64-bit platforms, Int is the same size as Int64.

    So in the Core Data model editor (.xcdatamodeld file), I choose to apply an Integer 64 attribute type for any value that will be used as an Int in my code.

    Also, somewhere, some time ago, I read that if there is no reason to use Integer 16 or Integer 32, then default to the use of Integer 64 in object model graph. (I assume Integer 16 or Integer 32 are kept for backward compatibility.) If I find that reference I'll link it here.

    I could write about the use of scalar attribute types here and manually writing your managed object subclass/es by selecting in the attribute inspector Class Codegen = Manual/None, but honestly I have decided such added detail will only complicate matters.

    So your "automatically generated by Core Data" managed object subclass/es (NSManagedObject) will use the optional NSNumber? wrapper...

    You will therefore need to convert your persisted/saved data in your code.

    I do this in two places... when I access the data and when I persist the data.

    (Noting I assume your entity is of type Dog and an instance exists of dog i.e. let dog = Dog())

    // access
    tempGender = dog.gender as? Int
    
    // save
    dog.gender = tempGender as NSNumber?
    

    In between, I use a "temp" var property of type Int to work with the segmented control.

    // temporary property to use with segmented control
    private var tempGender: Int?
    

    UPDATE

    I do the last part a little differently now...

    Rather than convert the data in code, I made a simple extension to my managed object subclass to execute the conversion. So rather than accessing the Core Data attribute directly and manipulating the data in code, now I instead use this convenience var.

    extension Dog {
        var genderAsInt: Int {
            get {
                guard let gender = self.gender else { return 0 }
                return Int(truncating: gender)
            }
            set {
                self.gender = NSNumber(value: newValue)
            }
        }
    } 
    

    Your picker code...

    Picker(selection: $addDogVM.genderAsInt, label: Text("Gender?")) {
        Text("Boy ♂").tag(0)
        Text("?").tag(1)
        Text("Girl ♀").tag(2)
    }
    

    Any questions, ask in the comments.