Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind a computed property in SwiftUI

I have a simple model Claim:

struct Claim: Codable, Identifiable {
    let id: String
    var name: String

    var isSelected: Bool? = nil // used for selecting claims in a list
}

The AddClaimView is for filtering out the claims that is already stored in role.claims, and present them in a list for the user to select for adding to the role.

struct AddClaimView: View {
    var claims: [Claim] // all possible claims
    @Binding var role: Role
    var claimsForAdding: [Claim] {
        self.claims.filter { claim in
            !self.role.claims.contains(claim)
        }
    }
    var body: some View {
        VStack {
            Text("Add claim")
                .font(.largeTitle)
            ClaimsListRows(claims: self.claimsForAdding.sorted())
        }
    }
}

The filtering is done by the computed property claimsForAdding, and here comes the problem. How can I bind the computed property claimsForAdding to @Binding var claims in the ClaimsListRow view, and then add them to role.claims?

@State var claimsForAdding.... obviously doesn't work for a computed property.

struct ClaimsListRows: View {
    @Binding var claims: [Claim]
    var body: some View {
        List(claims) { claim in
// set property isSelected = true when tapping a row
.....

The view that presents AddClaimView in a sheet:

struct RoleDetailed: View {
    @Binding var role: Role
    @State var showAddClaim = false
    @Binding var claims: [Claim]

    var body: some View {
       VStack(alignment: .leading) {
            HStack {
                Text(role.name)
                    .font(.largeTitle)
                Spacer()
            }
            Text("id: " + role.id)
                .font(.footnote)
                .foregroundColor(Color.gray)
            List {
                ForEach(role.claims) { claim in
                    ClaimRow(claim: claim)
                }
            }
        }.font(.headline).padding(10)
        .navigationBarItems(trailing:
            Button(action: {
                self.showAddClaim.toggle()
            }) {
                Text("Add claim")
            }.sheet(isPresented: self.$showAddClaim, onDismiss: {
                print("dismissed")
            }, content: {
                AddClaimView(claims: self.claims, role: self.$role)
            })
        )
    }

}

From the comments I have received, this is the new AddClaimView:

struct AddClaimView: View {
    var claims: [Claim]
    @Binding var role: Role
    var claimsForAdding: [Claim] {
        get {
            self.claims.filter { claim in
                !self.role.claims.contains(claim)
            }
        }
        set {
            var claims = self.claims
            claims.removeAll(where: { claim in
                newValue.contains(claim)
            })
        }
    }
    var body: some View {
        VStack {
            Text("Add claim")
                .font(.largeTitle)
            ClaimsListRows(claims: Binding<[Claim]>(
                get: {
                    self.claims.filter {
                        claim in !self.role.claims.contains(claim)
                    }
                    .sorted(by: { $0.name < $1.name })
                },
                set: { newValue in
                    var claims = self.claims
                    claims.removeAll(where: { claim in
                        newValue.contains(claim)
                    })
                    self.role.claims = claims
                }
            ))
        }
    }
}
like image 726
Ivan C Myrvold Avatar asked Jan 25 '23 10:01

Ivan C Myrvold


1 Answers

You'll need create a custom Binding that updates the role:

ClaimsListRows(claims: Binding<[Claim]>(
    get: {
        self.claims.filter {
            claim in !self.role.claims.contains(claim)
        }
        .sorted(by: { $0.name < $1.name })
    },
    set: { newValue in
        var claims = self.claims
        claims.removeAll(where: { claim in
            newValue.contains(claim)
        })
        self.role.claims = claims
    }
))
like image 91
Yonat Avatar answered Jan 30 '23 01:01

Yonat