Limiting the Number of Bool (True) Values

I have the following lines of code where I show a bunch of checkboxes, each of which can toggle between on and off with a tap.

import SwiftUI

struct ContentView: View {
    @State private var viewModel = ContentViewModel()
    
    var body: some View {
        VStack(alignment: .leading) {
            List {
                ForEach(viewModel.models, id: \.id) { model in
                    CheckButtonView(id: model.id, text: model.name, isOn: model.isOn) { id, bool in
                        updateDate(id: id, bool: bool)
                    }
                }
            }
        }
    }
    
    func updateDate(id: String, bool: Bool) {
        for i in 0..<viewModel.models.count {
            let oldModel = viewModel.models[i]
            if oldModel.id == id {
                let newModel = Content(id: oldModel.id, name: oldModel.name, isOn: bool)
                viewModel.models.remove(at: i)
                viewModel.models.insert(newModel, at: i)
                break
            }
        }
         
        var count = 0
        for i in 0..<viewModel.models.count {
            let model = viewModel.models[i]
            if model.isOn {
                count += 1
            }
        }
    }
}

struct CheckButtonView: View {
    let id: String
    let text: String
    @State var isOn: Bool
    var callBack: (String, Bool) -> Void
    
    var body: some View {
        HStack {
            Button {
                isOn.toggle()
                callBack(id, isOn)
            } label: {
                Image(systemName: isOn ? "checkmark.square.fill" : "square")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 18)
                    .tint(!isOn ? .black : .blue)
            }
            
            Text(text)
                .font(.subheadline)
            
            Spacer()
        }
        .frame(maxWidth: .infinity)
    }
}

struct Content {
    let id: String
    let name: String
    let isOn: Bool
}

class ContentViewModel: ObservableObject {
    @Published var models = [Content]()
    @Published var canChange = true
    
    init() {
        models = [
            Content(id: UUID().uuidString, name: "Jim", isOn: false),
            Content(id: UUID().uuidString, name: "Jenny", isOn: false),
            Content(id: UUID().uuidString, name: "Nancy", isOn: false),
            Content(id: UUID().uuidString, name: "Natalie", isOn: false)
        ]
    }
}

According to the picture above, I have two checkboxes that are turned on. Now, what I want to do is let the user turn on as many as two checkboxes only. Can someone think of a good way of doing that? Thanks.

Answered by Claude31 in 815563022

If I understand well, you want to allow a max of 2 checked on. And nothing should happen if trying to check a third (except maybe an error message).

Here is how I did this:

struct ContentView: View {
    @State private var viewModel = ContentViewModel()
    @State var allowMoreChecks = true   // <<-- ADDED
    
    var body: some View {
        VStack(alignment: .leading) {
            List {
                ForEach(viewModel.models, id: \.id) { model in
                    CheckButtonView(id: model.id, text: model.name, isOn: model.isOn, moreAllowed: allowMoreChecks) { id, bool in
                        updateDate(id: id, bool: bool)  // <<-- ADDED moreAllowed
                    }
                }
            }
        }
    }
    
    func updateDate(id: String, bool: Bool) {
        for i in 0..<viewModel.models.count {
            let oldModel = viewModel.models[i]
            if oldModel.id == id {
                let newModel = Content(id: oldModel.id, name: oldModel.name, isOn: bool)
                viewModel.models.remove(at: i)
                viewModel.models.insert(newModel, at: i)
                break
            }
        }
         
        var count = 0
        for i in 0..<viewModel.models.count {
            let model = viewModel.models[i]
            if model.isOn {
                count += 1
            }
        }
        allowMoreChecks = count < 2     // <<-- ADDED
    }
}

struct CheckButtonView: View {
    let id: String
    let text: String
    @State var isOn: Bool
    var moreAllowed : Bool  // <<-- ADDED
    var callBack: (String, Bool) -> Void
    
    var body: some View {
        HStack {
            Button {
                if !moreAllowed && !isOn {. // <<-- ADDED
                    print("not authorized")
                } else {
                    isOn.toggle()
                    callBack(id, isOn)
                }
            } label: {
                Image(systemName: isOn ? "checkmark.square.fill" : "square")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 18)
                    .tint(!isOn ? .black : .blue)
            }
            
            Text(text)
                .font(.subheadline)
            
            Spacer()
        }
        .frame(maxWidth: .infinity)
    }
}

struct Content {
    let id: String
    let name: String
    let isOn: Bool
}

class ContentViewModel: ObservableObject {
    @Published var models = [Content]()
    @Published var canChange = true
    
    init() {
        models = [
            Content(id: UUID().uuidString, name: "Jim", isOn: false),
            Content(id: UUID().uuidString, name: "Jenny", isOn: false),
            Content(id: UUID().uuidString, name: "Nancy", isOn: false),
            Content(id: UUID().uuidString, name: "Natalie", isOn: false)
        ]
    }
}
Accepted Answer

If I understand well, you want to allow a max of 2 checked on. And nothing should happen if trying to check a third (except maybe an error message).

Here is how I did this:

struct ContentView: View {
    @State private var viewModel = ContentViewModel()
    @State var allowMoreChecks = true   // <<-- ADDED
    
    var body: some View {
        VStack(alignment: .leading) {
            List {
                ForEach(viewModel.models, id: \.id) { model in
                    CheckButtonView(id: model.id, text: model.name, isOn: model.isOn, moreAllowed: allowMoreChecks) { id, bool in
                        updateDate(id: id, bool: bool)  // <<-- ADDED moreAllowed
                    }
                }
            }
        }
    }
    
    func updateDate(id: String, bool: Bool) {
        for i in 0..<viewModel.models.count {
            let oldModel = viewModel.models[i]
            if oldModel.id == id {
                let newModel = Content(id: oldModel.id, name: oldModel.name, isOn: bool)
                viewModel.models.remove(at: i)
                viewModel.models.insert(newModel, at: i)
                break
            }
        }
         
        var count = 0
        for i in 0..<viewModel.models.count {
            let model = viewModel.models[i]
            if model.isOn {
                count += 1
            }
        }
        allowMoreChecks = count < 2     // <<-- ADDED
    }
}

struct CheckButtonView: View {
    let id: String
    let text: String
    @State var isOn: Bool
    var moreAllowed : Bool  // <<-- ADDED
    var callBack: (String, Bool) -> Void
    
    var body: some View {
        HStack {
            Button {
                if !moreAllowed && !isOn {. // <<-- ADDED
                    print("not authorized")
                } else {
                    isOn.toggle()
                    callBack(id, isOn)
                }
            } label: {
                Image(systemName: isOn ? "checkmark.square.fill" : "square")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 18)
                    .tint(!isOn ? .black : .blue)
            }
            
            Text(text)
                .font(.subheadline)
            
            Spacer()
        }
        .frame(maxWidth: .infinity)
    }
}

struct Content {
    let id: String
    let name: String
    let isOn: Bool
}

class ContentViewModel: ObservableObject {
    @Published var models = [Content]()
    @Published var canChange = true
    
    init() {
        models = [
            Content(id: UUID().uuidString, name: "Jim", isOn: false),
            Content(id: UUID().uuidString, name: "Jenny", isOn: false),
            Content(id: UUID().uuidString, name: "Nancy", isOn: false),
            Content(id: UUID().uuidString, name: "Natalie", isOn: false)
        ]
    }
}

You could simply disable the checkboxes that aren't ticked when your count var is 2.

Limiting the Number of Bool (True) Values
 
 
Q