Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 :)

like image 278
Robin Avatar asked Oct 18 '25 14:10

Robin


2 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])
}
like image 160
nine stones Avatar answered Oct 20 '25 06:10

nine stones


struct ContentView: View {
let numbers = [1, 2, 3, 4, 5]

var body: some View {
    VStack {
        ForEach(Array(numbers.enumerated()), id: \.element) { index, number in
             Text("Number \(number) is at index \(index)")
        }
        
    }
}

}

like image 34
Jay Avatar answered Oct 20 '25 08:10

Jay



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!