Search code examples
macosswiftuicore-databinding

Cannot convert return expression of type 'FetchedResults<User>.Element?' (aka 'Optional<User>') to return type 'Binding<User>'


I try to pass a fetched object User to my detail-view, when user is selected in sidebar-view. I don't understand the error message:

Cannot convert return expression of type 'FetchedResults<User>.Element?' (aka 'Optional<User>') to return type 'Binding<User>'.

my ContentView:

struct ContentView: View {

 @Environment(\.managedObjectContext) var moc

 @FetchRequest(sortDescriptors: [SortDescriptor(\.name)])
 private var users: FetchedResults<User>

 @State private var selectedUserID: User.ID?
 @State private var defaultUserID: User.ID?

 var body: some View {
    NavigationSplitView {
        SidebarView(selection: selection)
        
    } detail: {
        if selection.wrappedValue != nil {
            SecretDetailView(user: selectedUser)    //<-- passing user object to sub-view
        } else {
            Text("Please select a user")
        }
    }
 }


 private var selection: Binding<User.ID?> {
    Binding(get: { selectedUserID ?? defaultUserID }, set: { selectedUserID = $0 })
 }

 private var selectedUser: Binding<User> {
    users.filter({ $0.id == selection.wrappedValue }).first   //<-- ERROR!
 }
}

my detail-View with user-Binding:

struct SecretDetailView: View {

 @Binding var user: User                      //<-- user Binding in sub-view

 var secretItems: [SecretItem] {
    return user.secretItems
        .filter {
            searchText.isEmpty ? true : $0.userName.localizedCaseInsensitiveContains(searchText)
        }
        .sorted(using: sortOrder)
 }

 @State var searchText: String = ""
 @State private var selection = Set<SecretItem.ID>()

 @State var sortOrder: [KeyPathComparator<SecretItem>] = [
    .init(\.userName, order: SortOrder.forward)
 ]

 var table: some View {
    Table(selection: $selection, sortOrder: $sortOrder) {
        TableColumn("User Name", value: \.userName)

        TableColumn("Password", value: \.password) { secretItem in
            Text(secretItem.password)
        }
    } rows: {
        ForEach(secretItems) { secretItem in
            TableRow(secretItem)
        }
    }
 }

 var body: some View {
    
    ZStack(alignment: .bottom) {
        
        table
            .searchable(text: $searchText)
            .navigationTitle(user.name!)
....

Solution

  • Binding to a FetchRequest/NSManagedObject is a fallacy.

    The FetchRequest is read-only and Binding is for value types.

    The only way to change a FetchRequest is by affecting the store directly.

    What you need is an

    @ObservedObject var user: User
    

    In the detail view, to edit and see changes of an NSManagedObject

    You can use

    @State private var selectedUser:User?
    

    But you won’t be able to observe changes to the properties of the object just changed to selectedUser as a whole.

    Anytime you are creating a Binding without a working get and set you should assume that there is a better approach. Binding is by definition a two way connection.