Search code examples
swiftuiwidgetkitswiftui-foreach

How to get continuous index numbers from nested ForEach loop?


I'm using a 4-block grid for my Widget Extension.

I want to populate these grids with an array of elements.

let exampleArray = ["test1","test2","test3","test4"]

When I use two nested ForEach loops, I can't get the element in the 4th index.

VStack {
    ForEach((0..<2)) { column in
        HStack {
            ForEach((0..<2)) { index in
                ZStack (alignment: .bottomLeading) {
                    Text(newsList[(index+column)])  // 0+0, 1+0, 0+1, 1+1
                    // Text = test1, test2, test2, test3
                }
            }
        }
    }
}

Any way I can work around this? Because it's in View, I can't use any operations.

Full code of the View:

struct AppWidgetEntryView : View {
    var entry: Provider.Entry
    
    @Environment(\.widgetFamily)
    var widgetFamily

    var body: some View {
        VStack(alignment: .trailing, spacing: 6) {
            Image("logo")
                .frame(maxWidth: .infinity, alignment: .leading)

            if widgetFamily == .systemLarge {
                VStack {
                ForEach((0..<2)) { column in
                    HStack {
                        ForEach((0..<2)) { index in
                            ZStack (alignment: .bottomLeading) {
                                if let url = URL(string: imageList[(index+column)]), let imageData = try? Data(contentsOf: url),
                                let uiImage = UIImage(data: imageData) {

                                Image(uiImage: uiImage)
                                    .centerCropped()
                                    .frame(maxHeight: 150, alignment: .center)
                                    .cornerRadius(10)
                                            .overlay(RoundedRectangle(cornerRadius: 10)
                                            .stroke(Color.gray, lineWidth: 1))
                                            .shadow(radius: 10)
                                } else {
                                    Image("ph_background")
                                        .centerCropped()
                                        .frame(maxHeight: 150, alignment: .center)
                                        .cornerRadius(10)
                                                .overlay(RoundedRectangle(cornerRadius: 10)
                                                .stroke(Color.gray, lineWidth: 1))
                                                .shadow(radius: 10)
                                }
                                
                                Text(newsList[(index+column)])
                                    .font(.system(size: 12))
                                    .foregroundColor(.white)
                                    .fontWeight(.light)
                                    // .frame(maxHeight: 50)
                                    .background(Rectangle().fill(Color.black).blur(radius: 20))
                                    .padding(.bottom, 5)
                                    .padding(.leading, 5)
                                    .padding(.trailing, 5)
                                    .padding(.top, 5)
                            }
                        }
                    }
                }
            }
        }
    }
}

Solution Please check the accepted answer.

struct AppWidgetEntryView : View {
    var entry: Provider.Entry
    
    @Environment(\.widgetFamily)
    var widgetFamily

    var body: some View {
        var columns: [GridItem] =
                 Array(repeating: .init(.fixed(100)), count: 2)

        if widgetFamily == .systemLarge {
        LazyVGrid(columns: columns) {
            ForEach((0..<4)) { index in
                ZStack (alignment: .bottomLeading) {
                    if let url = URL(string: imageList[(index)]), let imageData = try? Data(contentsOf: url),
                    let uiImage = UIImage(data: imageData) {

                    Image(uiImage: uiImage)
                        .centerCropped()
                        .frame(maxHeight: 150, alignment: .center)
                        .cornerRadius(10)
                                .overlay(RoundedRectangle(cornerRadius: 10)
                                .stroke(Color.gray, lineWidth: 1))
                                .shadow(radius: 10)
                    } else {
                        Image("ph_background")
                            .centerCropped()
                            .frame(maxHeight: 150, alignment: .center)
                            .cornerRadius(10)
                                    .overlay(RoundedRectangle(cornerRadius: 10)
                                    .stroke(Color.gray, lineWidth: 1))
                                    .shadow(radius: 10)
                    }
                    
                    Text(newsList[(index)])
                        .font(.system(size: 12))
                        .foregroundColor(.white)
                        .fontWeight(.light)
                        // .frame(maxHeight: 50)
                        .background(Rectangle().fill(Color.black).blur(radius: 20))
                        .padding(.bottom, 5)
                        .padding(.leading, 5)
                        .padding(.trailing, 5)
                        .padding(.top, 5)
                }
            }
        }
        }
    }
}

Solution

  • Use LazyVGrid

    I feel like you really should be using LazyVGrid for this...

    https://developer.apple.com/documentation/swiftui/lazyvgrid

    It works like a List but it places each item in a grid of columns.

    Using this will solve this problem for you.

    Without LazyVGrid

    If you really don't want to use a LazyVGrid (please use this though). Then the calculation you are using is not correct. The calculation is something like...

    index = row*numberOfCols + column
    

    You can't just use the row as that will only increase the index by 1 per row. But you need to increase the index by the number of columns per row (2 in your case).