Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

swift - Toggle selection in a list - SwiftUI

I hope I can explain my question clearly.

I want to select some courses from list via toggle but whatever I've tried it didn't work.

What am I supposed to do?

Thank you for your time. Bests, Murat

struct SubjectCardsView: View {
    // MARK: - Properties
    @State var courses: [Course] = Bundle.main.decode("courses.json")
    
    @State private var toggle: Bool = false
    
    // MARK: - Body
    var body: some View {
        
        NavigationView {
            
            List {
                
                ForEach(courses) { course in
                    Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
                        ForEach(course.courseName, id: .name) { item  in
                            Toggle(isOn: $toggle, label: {
                                Text(item.name)
                            })
                            
                        }
                    }
                }
            }
            
            .listStyle(InsetGroupedListStyle())
            .navigationBarTitle("Choose your subject", displayMode: .inline).font(.system(size: 16, weight: .medium, design: .rounded))
            .navigationBarItems(leading: Button(action: {
                
            }, label: {
                Text("Cancel")
            }), trailing: Button(action: {
                
            }, label: {
                Text("Save")
            }))
            
            
        } // NavigationView
    }
}

Course part!

import Foundation
import SwiftUI

struct Course: Codable, Identifiable {
    
    var id: Int
    var title: String
    var subjectCount: String
    var courseName: [Content]
    var isToggled = false
    
    private var imageName: String
    var image: Image {
        Image(imageName)
    }

    enum LessonSegment: String, CaseIterable, Identifiable {
        case overview
        case resources

        var id: String { self.rawValue }
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case title
        case subjectCount
        case imageName
        case courseName
   
    }
}

struct Content: Codable {
    
    var id: Int
    var name: String
    var content: String
    var assessment: String
    var notify: String
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your @State private var toggle: Bool = false doesn't make sense. You have many courses, not a single course. Each course should have it's own toggle on/off, which is what you started to do with:

struct Course: Codable, Identifiable {
    var isToggled = false /// here!

    ...
}

To use this, you can reference each course's isToggled inside the ForEach, like this:

ForEach(courses) { course in

    Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
        ForEach(course.courseName, id: .name) { item  in

            ///          here!
            Toggle(isOn: course.isToggled, label: {
                Text(item.name)
            })
            
        }
    }
}

However, this won't work. course.isToggled is a Bool, not a Binding<Bool>, which the Toggle expects.

Where can you get Binding<Bool>? From the @State var courses: [Course], of course! sorry for pun


The Binding<> part comes from the @State declaration.

Properties that are marked with @State, like your @State var courses: [Course], include a projectedValue that has the Binding<> type.

You can access the projectedValue by adding a $ to the property. So, if you write $courses, that will have type Binding<[Course]>.

Xcode autocompletion for $courses

But, your toggle expects Binding<Bool>, not Binding<[Course]>.

Toggle(isOn: Binding, label: { Text("A cool toggle") })

This is where the Bool part comes in.

You will need to replace the Binding's value, [Course], with a Bool. Well, we had a Bool before, right?

struct Course: Codable, Identifiable {
    var isToggled = false /// this is a Bool!

Each course has a isToggled, which is a Bool. From earlier on in this answer, we got this inside the ForEach:

ForEach(courses) { course in

    ...

    ///          getting the Bool, which unfortunately doesn't work (yet)
    Toggle(isOn: course.isToggled, label: {

... We need to somehow combine the Binding<> with the Bool. This means that we must

  • reference $courses (to get the Binding<>)
  • get each courses' isToggled

And... tada!

$courses[index].isToggled /// has type Binding<Bool>

To get index, we'll need to loop over courses.indices instead of directly looping over courses.

ForEach(courses.indices) { index in

    ...

    ///          this works! 
    Toggle(isOn: $courses[index].isToggled, label: {

Then, just replace every occurrence of course in your old code's ForEach with courses[index]. Here's the full working example:

ForEach(courses.indices) { index in
    Section(header: Text(courses[index].title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
        ForEach(courses[index].courseName, id: .name) { item  in

            /// $courses[index].isToggled is a Binding<Bool>
            Toggle(isOn: $courses[index].isToggled, label: {
                Text(item.name)
            })
        }
    }
}

As a convenience so you don't have to do courses[index] every time you want the current course, you can use Array(zip as shown in this answer to loop over a (Int, Course). This also assigns a unique id for every Section inside the loop, so any transitions you add will work out smoothly.

ForEach(Array(zip(courses.indices, courses)), id: .1.id) { (index, course) in

    Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
        ForEach(course.courseName, id: .name) { item  in

            Toggle(isOn: $courses[index].isToggled, label: {
                Text(item.name)
            })
        }
    }
}

Well (Int, Course) is actually (Range<Array<Course>.Index>.Element, Course) but that's pretty much the same thing.

Final result:

Toggles inside each row of the ForEach working

Edit for isToggled inside Content, not Course:

ForEach(Array(zip(courses.indices, courses)), id: .1.id) { (index, course) in
    Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
        ForEach(Array(zip(course.courseName.indices, course.courseName)), id: .1.id) { (itemIndex, item) in

            ///          here!
            Toggle(isOn: $courses[index].courseName[itemIndex].isToggled, label: {
                Text(item.name)
            })
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...