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