SwiftUI: How should ForEach views be formulated to make Strings from an array into Text() views?

Viewed 2010

I have somewhat unsuccessfully tried to figure out how to work with SwiftUI's ForEach view. By following resources such as this, this and this, it seems to me that given an array of strings:

var arrayOfStrings: [String] = ["String1", "String2", "String3"]

...each of the following implementations should work:

ForEach(arrayOfStrings, id: \.self) {
    Text(arrayOfStrings[$0])
}

ForEach(1...arrayOfStrings.count, id: \.self) {
    Text(arrayOfStrings[$0])
}

ForEach(arrayOfStrings, id: \.self) { string in
    Text(string)
}

Yet each of them gives me a compile-time error or crashes the app at runtime.

The above is just example code, but if you need the full context of what I am trying to do, here it is:

struct Infobox<Content>: View where Content: View {
    let edgeSize: CGFloat = 8
    let color: Color = .neoblÄ
    let content: Content

    var body: some View {
        VStack(spacing: 0) {
            BoxSide(.top, withColor: color)
                .frame(height: edgeSize)
                .padding(.horizontal, 0)

            content
                .background(color)

            BoxSide(.bottom, withColor: color)
                .frame(height: edgeSize)
                .padding(.horizontal, 0)
        }
    }

    init(@ViewBuilder _ content: @escaping () -> Content) {
        self.content = content()
    }

    init<T: View>(withHeader headerText: String, forContentArray contentArray: [(text: String, value: String)], splitInTwoColumns twoColumns: Bool, @ViewBuilder ignoreTheClosure: @escaping () -> T) where Content == VStack<TupleView<(HStack<TupleView<(Spacer, Text, Spacer)>>, HStack<TupleView<(VStack<ForEach<ClosedRange<Int>, Int, Text>>, VStack<ForEach<ClosedRange<Int>, Int, Text>>, Spacer, VStack<ForEach<ClosedRange<Int>, Int, Text>>, VStack<ForEach<ClosedRange<Int>, Int, Text>>)>>)>> {

        var column1: [String] = []
        var column2: [String] = []
        var column3: [String] = []
        var column4: [String] = []

        let halfArray = contentArray.count/2
        for (index, content) in contentArray.enumerated() {
            if index+1 <= halfArray {
                column1.append(content.text)
                column2.append(content.value)
            } else {
                column3.append(content.text)
                column4.append(content.value)
            }
        }

        self.init() {

            VStack(alignment: .leading) {
                HStack() {
                    Spacer()
                    Text(headerText)
                        .font(.headline)
                    Spacer()
                }
                HStack {
                    VStack {
                        ForEach(column1, id: \.self) {
                            Text(column1[$0])
                        }
                    }
                    VStack {
                        ForEach(column2, id: \.self) {
                            Text(column2[$0])
                        }
                    }
                    Spacer()
                    VStack {
                        ForEach(column3, id: \.self) {
                            Text(column3[$0])
                        }
                    }
                    VStack {
                        ForEach(column4, id: \.self) {
                            Text(column4[$0])
                        }
                    }
                }
            }
        }
    }
}

Summary: Given an array of (String, String), create an Infobox() consisting of Text() elements divided into four columns (two for "keys" and two for the corresponding values).

If anyone has any ideas of how to fix those ForEach views, I'll give you a cookie :)

1 Answers

This one fails because $0 is a String and you cannot index your string-array with a string

ForEach(arrayOfStrings, id: \.self) {
    Text(arrayOfStrings[$0])
}

This one fails because array indices are 0-based -> Choose a 0 base range: 0..<arrayOfStrings.count to avoid an "Index-out-of-bounds exception"

ForEach(1...arrayOfStrings.count, id: \.self) {
    Text(arrayOfStrings[$0])
}

This one is generally correct, but you may have to reference your member scope-qualified self.arrayOfStrings this applies to all SwiftUI code when your scope is a closure:

ForEach(arrayOfStrings, id: \.self) { string in
    Text(string)
}

This is a more Swifty-way of doing the array access via index:

ForEach(arrayOfStrings.indices) {
  Text(self.arrayOfStrings[$0])
}
Related