Learn Courses My Dashboard

Cannot assign value of type 'Binding<[String]>' to type '[String]'

Hello!

I’m trying to store items from a selectable list as an array to core data and it is currently in the form of Binding<[String]>. The attribute’s type I am trying to store it to is a [String] (essentially I want to be able to show a list of those selected items in a separate view). When I try to save that array to core data through a function, I get the error “Cannot assign value of type ‘Binding<[String]>’ to type ‘[String]’”.

In general, what is the process for converting a Binding<[String]> to a [String] so that I can save it? Is there an example of this that you can show?

Thanks!

@camoroso89

Hi Chris,

Can you show your code that you have including how you have defined the variable that is storing the [String] array that you want to save to Core Data.

It might be as simple as wrapping the data item with Array(dataItem).

Paste your code in as text rather than post a screen shot.

To format the code nicely, place 3 back-ticks ``` on the line above your code and 3 back-ticks ``` on the line below your code. Like this:

```
Code goes here
```

The 3 back-ticks must be the ONLY characters on the line. The back-tick character is located on the same keyboard key as the tilde character ~ (which is located below the Esc key - QWERTY keyboard).

Alternatively after you paste in your code, select that code block and click the </> button on the toolbar. This does the same thing as manually typing the 3 back ticks on the line above and below your code.

This also makes it easier for anyone assisting as they can copy the code and carry out some testing.

I back-tracked a little just to show more perspective on what I’m trying to do. I’m starting off with an empty array called “selectedItems” and another array called “allItems” which is pulling all records currently stored in the AllItems entity. The goal is to allow the user to use the MultiSelectPickerView to select which records they want from allItems array and store those records in the selectedItems array. Then on dismissal of that view, that selectedItems array needs to be saved as a property of another entity, let’s call it Group entity (with a one-to-many relationship between Group and selectedItems).

I started off just trying to figure out how to save the name of selectedItem as each element in an array and present that as a list of names in a separate view that uses the Group entity. Ideally though, since each selectedItem record actually has multiple properties other than name that would be beneficial to the user, the user should be able to access all properties in that selectedItem’s record when they navigate to that specific Group. So long term I’m not sure if simply saving an array will provide much value.

I know this is probably a lot to process, but how do you think I should proceed if I want to join all the properties of selectedItems with the corresponding Group object that is being created? For reference, this MultiSelectPickerView is navigated to from a button that is part of a view in which a new Group object is being created, so imagine the process like CreateGroupView>(click button called “Select Items”)>iOSView>MultiSelectPickerView.

import SwiftUI

struct ContentView: View {
    
    @FetchRequest(
    entity: AllItems.entity(),
    sortDescriptors: []
    )
    var itemsArray: FetchedResults<AllItems>
    
    
    @State private var filterBy = ""
    
    
    
    @State var selectedItems = [String]()
    
    private var allItems:[AllItems] {
        if filterBy.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
        
        return Array(itemsArray)
        }
        else {
       
        return itemsArray.filter { r in
            return r.name.contains(filterBy)
        }
    }
    }
    
    var body: some View {

        iOSview(selectedItems: selectedItems, allItems: allItems)

    }
}


struct iOSview:View {
    @State var selectedItems:[String]
    @State var allItems:[String]
    
    var body: some View {
        NavigationView {
            Form {
                
                Section("Choose your items:", content: {
                   
                    NavigationLink(destination: {
                        MultiSelectPickerView(allItems: allItems, selectedItems: $selectedItems)
                            .navigationTitle("Choose Your Items")
                    }, label: {
                        
                        HStack {
                            Text("Select Items:")
                                .foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
                            Spacer()
                            Image(systemName: "\($selectedItems.count).circle")
                                .foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
                                .font(.title2)
                        }
                    })
                })
               
                Section("My selected items are:", content: {
                    Text(selectedItems.joined(separator: "\n"))
                        .foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
                })
            }
            .navigationTitle("My Items")
        }
    }
}


struct MultiSelectPickerView: View {
    // The list of items we want to show
    @State var allItems: [String]

    
    @Binding var selectedItems: [String]

    var body: some View {
        Form {
            List {
                ForEach(allItems, id: \.self) { item in
                    Button(action: {
                        withAnimation {
                            if self.selectedItems.contains(item) {

                                self.selectedItems.removeAll(where: { $0 == item })
                            } else {
                                self.selectedItems.append(item)
                            }
                            //self.selectedItems are what I am trying to save to an object as an array property
                        }
                    }) {
                        HStack {
                            Image(systemName: "checkmark")
                                .opacity(self.selectedItems.contains(item) ? 1.0 : 0.0)
                            Text(item)
                        }
                    }
                    .foregroundColor(.primary)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


Thanks!

@camoroso89

Can you post a screenshot of your CoreData Entity - AllItems - that you have configured showing the attributes and also post your code for your PersistenceController ( in your Persistence.swift file).

Snip 2022-12-28 16.27.33

import Foundation
import CoreData


extension AllItems {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<AllItems> {
        return NSFetchRequest<AllItems>(entityName: "AllItems")
    }

    @NSManaged public var certLevel: String?
    @NSManaged public var dateEntered: Date
    @NSManaged public var email: String
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var numberReviews: Double

}

extension AllItems : Identifiable {

}
import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = AllItems(context: viewContext)
            newItem.id = UUID()
        }
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "Item_Project")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}


@camoroso89

Hi Chris,

Just prior to seeing your recent reply I think I have got something working with just two attributes timestamp (which I have now named dateEntered) and name which match just two of the attributes you have.

This is the code I have thus far (linked below).

The problem is (at least how I saw it) was that you were trying to deal with itemsArray as type [AllItems] and allItems as type [String] and you might have had a different thought process to me which is why you went down the path you did. I changed them to be all the same Type [AllItems] and then made some adjustments to make it all work.

At least I think this is what you might have been trying to do. Anyway, have a look and let mew know if that is somewhere close to what you wanted to make happen.

Having them be the same type makes sense. Using this file you provided, I added a few lines in iOSView to try to save selectedItems as a property called itemList when an instance of the Group entity is created where

         Button("Done") {
          timeToSave = true
          dismiss()
        }
    }
    func saveItemList() {
        if timeToSave == true {
            let group = Group(context: viewContext)
            group.itemList = selectedItems

        }
        
    }
extension Group {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Group> {
        return NSFetchRequest<Group>(entityName: "Group")
    }

    @NSManaged public var itemList: [AllItems]
    @NSManaged public var id: UUID

}

extension Group : Identifiable {

}

Now I’m trying to double check that the item was saved correctly and display the itemList in a GroupDetailView after creating a new instance of a group object, but in the simulator the following code just produces an empty array “[ ]”.

Text("\(String(describing: group.itemList))")

So I’m trying to determine whether the “[ ]” is because of incorrectly writing the line above or if it’s because itemList is truly blank and it never saved correctly, or both. What would be the correct way to display the contents of the itemList property in a view? And do you think that’s the only issue or does it seem like it also didn’t save correctly?

In terms of a database, what is the relationship between the entity AllItems and Group? That’s important for this to work.

The files that you see in that project -
AllItems+CoreDataClass and
AllItems+CoreDataProperties
are generated from the Xcode menu via Editor then Create NSManagedObject Subclass after having created the Entity AllItems and the properties as you see. The settings for CodeGen have been set to Manual/None in order to be able to generate those files. Those files are not objects that you edit outside of the CoreData .xcdatamodeld file without knowing the implications.

If you are not familiar with CoreData then this might be a bit confusing.

To be honest, I am not sure what you want to achieve.

I’ve done those steps, but I think I’ve been approaching this the wrong way (lots of learning to do).

Perhaps the better way to go about this (starting with the file you put together) is to save each selectedItem as individual instances in a seperate entity (let’s call this ItemList). Ultimately I want these selected items to be associated with a Group object so that when the user selects that group, this list of selectedItems that were selected from the picker are populated. This way, any additional properties that the selectedItem has can also be accessed and not just the name of the item.

At the start, imagine in the app, in one view the user adds items to the AllItems entity, like “chair”, “desk”, “table”. Then, the user creates a new Group in a separate view (in this case I’ll use the example of each group instance being a new office room being set up), so the user is creating an office object in a view and then selecting which items from the AllItems entity they want to add to that office in the MultiSelectPickerView in your file.

So when the user uses the picker and selectedItems are saved, they are saved to the ItemList entity where each row is an appended selectedItem instance (if they select all three, they would add “chair”, “desk”, and “table” as 3 new instances). Now each selectedItem instance within the ItemList entity needs to have some sort of common identifier that allows it to join with the group/office object that was created (would UUID be the best way to go about that?)

So basically, instead of tying the AllItems to the Group/office in a many to one relationship, I am now trying to tie the selectedItems (from the Alltems entity) to the ItemList entity in a many to one relationship and then tie the ItemList entity to the Group/office entity in a one-to-one relationship through some sort of common identifier attribute.

Hopefully that makes more sense. Starting with your file, would you be able to show me how all of that comes together? If I saw how those relationships are done correctly and saved to core data correctly that would clear up a lot for me and I could run with that.

@camoroso89

Hi Chris,

Yes I understand what you are getting at.

It might be a good idea to have a look at this tutorial from Paul Hudson where he discusses a one to many relationship and how to set it up CoreData. He uses Candy and Country where the same Candy can be found in many Country (countries) but hopefully you can see how it fits with your scenario.

https://www.hackingwithswift.com/books/ios-swiftui/one-to-many-relationships-with-core-data-swiftui-and-fetchrequest

While you are watching that video I will try and figure out something in that project. I might rename AllItems to Furniture and Group to Office just so that it fits in with the scenario you have described.

@camoroso89

Hey Chris,

Can’t get this thing to work yet. Thought it was straight forward but nope.

Use the wrappedValue

selectedItems.wrappedValue

See more here

Thanks @mikaelacaron! Have you seen any sample projects similar to what I laid out in my previous post that I could possibly learn from? My goal is to structure it like a relational database like how you mentioned in a previous thread about having a foreign key relationship (it’s essentially the same problem on both threads that I’ve been trying to work out).

@camoroso89

Hi Chris,

I’m still on this case and I have something that works but before I share it, what is the ultimate name of the Entities that you intend to have and what properties will they have?

It might be better for me to mirror your entity relationship, even if it just the bare bones as far as properties are concerned, rather than giving you something that bears no resemblance to what you ultimately want to have.

Cheers

Hey Chris - Thanks for staying on it, I appreciate it!

Below is how I’m looking at it. I wasn’t sure if UUID is the best type to use for joining these entities and using to fetch results later in a separate view, but let me know what you think. Ultimately ItemList is a list that is joining each selected furniture item’s ID for each office object created (identified by officeID)

Thanks!

@camoroso89

Hi Chris,

That was kind of what I thought you might have had in mind after already doing it a bit differently.

What software are you using to create your ER diagram? Looks rather snappy.

I was going to draw the diagram so you can see what I had already done.

Essentially there is a Furniture entity and an Office entity. The Office entity has a transformable property furnitureItems which is an an array of [UUID] and stores the id of the furniture items that have been allocated
So there is a one to many relationship between Furniture and Office via that array.

  • You can create an Office
  • Add and remove furniture for that Office
  • You can delete an Office
  • You can add new Furniture items, edit the itemName and delete them.
  • NOTE: When you delete a furniture item it will firstly be removed from all Offices that have that item allocated. It does not warn you so that may or may not require some logic to prevent accidental deletions of allocated items.

When you first run the program it populates the furniture array with some random items just so that you have something to mess around with.

I’m not sure that building the relationship with a separate table to link the furniture and the Office in the way that you have drawn your diagram is any more advantageous so let me know what you reckon.

Project:

Haha I actually just used excel real quick, removed the gridlines and added arrows. But for more advanced visualizing and planning, Whimsical is a pretty cool product.

What you built here is great, I can definitely run with this and learn from it. Thank you so much for putting this together!

No worries at all. Hope it all makes sense to you.