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.
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) ] } }