Home > Software engineering >  How can I change a specific number of items in a LazyGrid SwiftUI?
How can I change a specific number of items in a LazyGrid SwiftUI?

Time:12-19

I want to be able to select a number of items (e.g. the first 20) in the grid and change the color of the elements.

import SwiftUI

struct ContentView: View {
    @State var showView = false
    @State private var birthDate = Date.now
    struct Day: Identifiable {
        let id = UUID()
        let value: Int
    }
    
    struct Month {
        let name: String
        let numberOfDays: Int
        var days: [Day]

        init(name: String, numberOfDays: Int) {
            self.name = name
            self.numberOfDays = numberOfDays
            self.days = []

            for n in 1...numberOfDays {
                self.days.append(Day(value: n))
            }

        }
    }
    
    let year = [
        Month(name: "Youth", numberOfDays: 12),
        Month(name: "Teenager", numberOfDays: 7),
        Month(name: "20s - 30s", numberOfDays: 20),
        Month(name: "Middle ages", numberOfDays: 26),
        Month(name: "Retirement", numberOfDays: 35),
    ]
    let layout = [
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
        ]
    
    var body: some View {
        VStack {
                   DatePicker(selection: $birthDate, in: ...Date.now, displayedComponents: .date) {
                   }

                   Text("Your Birthdate is \(birthDate.formatted(date: .long, time: .omitted))")
               
            ScrollView {
                
                LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {
                    ForEach(year, id: \.name){ month in
                        Section(header: Text(verbatim: month.name).font(.headline)) {
                            ForEach(month.days) { day in
                                Capsule()
                            }
                       }
                    }
                }.padding(5)
            }  
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This is what it looks like.

screenshot of the app currently

and this is what I want to happen - ill integrate the date but need a way to change a specific number of these (in this example 7 of them have been changed:

same screenshot with 7 elements selected and changed colour from blue to black

I have tried a ForEach but can't seem to get it working, any help would be appreciated.

CodePudding user response:

  1. you need a new property in your Day struct to keep track of selection. I called it selected.

  2. you have to declare year as a @State var – so it is the source of truth for your view, and can be modified.

  3. To make day changeable in the view, you have to pass year, month and day down using the $ initializer of ForEach.

( 2. and 3. can be solved more elegantly by using an ObservableObject class for your base data)

  1. Change the capsule color based on day.selected.

  2. On Tap gesture toggle day.selected.

struct ContentView: View {
    
    @State var showView = false
    @State private var birthDate = Date.now
    
    
    struct Day: Identifiable {
        let id = UUID()
        let value: Int
        var selected: Bool = false // new property here
    }
    
    struct Month {
        let name: String
        let numberOfDays: Int
        var days: [Day]

        init(name: String, numberOfDays: Int) {
            self.name = name
            self.numberOfDays = numberOfDays
            self.days = []

            for n in 1...numberOfDays {
                self.days.append(Day(value: n))
            }

        }
    }
    
    @State var year = [   // define year as State to make it changeable
        Month(name: "Youth", numberOfDays: 12),
        Month(name: "Teenager", numberOfDays: 7),
        Month(name: "20s - 30s", numberOfDays: 20),
        Month(name: "Middle ages", numberOfDays: 26),
        Month(name: "Retirement", numberOfDays: 35),
    ]
    
    let layout = [
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
            GridItem(.flexible(minimum: 40)),
        ]
    
    var body: some View {
        VStack {
            DatePicker(selection: $birthDate, in: ...Date.now, displayedComponents: .date) {
            }
            
            Text("Your Birthdate is \(birthDate.formatted(date: .long, time: .omitted))")
            
            ScrollView {
                
                LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {
                    ForEach($year, id: \.name){ $month in  // binding init
                        Section(header: Text(verbatim: month.name).font(.headline)) {
                            ForEach($month.days) { $day in // binding init
                                Capsule()
                                    .frame(height: 20)
                                    .foregroundColor(day.selected ? .red : .cyan) // different colors based on selected
                                    .onTapGesture {
                                        day.selected.toggle() // on Tap toggle selected
                                    }
                            }
                        }
                    }
                }.padding(5)
            }
        }
    }
}

CodePudding user response:

Hope this help ):

import SwiftUI

struct ContentView: View {
    @State var showView = false
    @State private var birthDate = Date.now
    struct Day: Identifiable {
        let id = UUID()
        let value: Int
    }

    struct Month {
        let name: String
        let numberOfDays: Int
        var days: [Day]

        init(name: String, numberOfDays: Int) {
            self.name = name
            self.numberOfDays = numberOfDays
            days = []

            for n in 1 ... numberOfDays {
                days.append(Day(value: n))
            }
        }
    }

    let year = [
        Month(name: "Youth", numberOfDays: 12),
        Month(name: "Teenager", numberOfDays: 7),
        Month(name: "20s - 30s", numberOfDays: 20),
        Month(name: "Middle ages", numberOfDays: 26),
        Month(name: "Retirement", numberOfDays: 35),
    ]
    let layout = [
        GridItem(.flexible(minimum: 40)),
        GridItem(.flexible(minimum: 40)),
        GridItem(.flexible(minimum: 40)),
        GridItem(.flexible(minimum: 40)),
        GridItem(.flexible(minimum: 40)),
    ]
    @State private var selected: Set<Day.ID> = []
    init() {
        guard let selectedDays = year.first?.days else { return }
        _selected = .init(initialValue: .init(selectedDays.map { $0.id }))
    }

    var body: some View {
        VStack {
            DatePicker(selection: $birthDate, in: ...Date.now, displayedComponents: .date) {}

            Text("Your Birthdate is \(birthDate.formatted(date: .long, time: .omitted))")

            ScrollView {
                LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders]) {
                    ForEach(year, id: \.name) { month in
                        Section(header: Text(verbatim: month.name).font(.headline)) {
                            ForEach(month.days) { day in
                                Capsule()
                                    .fill(isSelected(id: day.id) ? .gray : .cyan)
                            }
                        }
                    }
                }.padding(5)
            }
        }
    }

    private func isSelected(id: Day.ID) -> Bool {
        return selected.contains(id)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  • Related