Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refresh number of ForEach's displaying elements after array's size changes (SwiftUI, Xcode 11 Beta 5)

Im trying to implement a view that can change the amount of displaying items (created by a ForEach loop) if the content array's size changes, just like how a shopping app might change its number of items available after the user pull to refresh

Here are some code I have tried so far. If I remember correctly, these worked with Xcode beta 4, but with beta 5:

  • If the array's size increases, the loop will still display the originial number of elements
  • Array's size decrease will cause an index out of range error

Code:

import SwiftUI

struct test : View {
    @State var array:[String] = []
    @State var label = "not pressed"
    var body: some View {
        VStack{
            Text(label).onTapGesture {
                self.array.append("ForEach refreshed")
                self.label = "pressed"
            }
            ForEach(0..<array.count){number in
                Text(self.array[number])
            }
        }
}
}

#if DEBUG
struct test_Previews: PreviewProvider {
    static var previews: some View {
        test()
    }
}
#endif

I'm new to SwiftUI and GUI programming in general, and just feels like every content is defined at launch time and really hard to make changes afterwards (For example: reset a view after the user navigate away then return to it). Solutions to the loop problem or any tips for making views more dynamic would be greatly appreciated!

like image 673
Nguyễn Khắc Hào Avatar asked Aug 02 '19 17:08

Nguyễn Khắc Hào


1 Answers

Beta 5 Release Notes say:

The retroactive conformance of Int to the Identifiable protocol is removed. Change any code that relies on this conformance to pass .self to the id parameter of the relevant initializer. Constant ranges of Int continue to be accepted:

List(0..<5) {
   Text("Rooms")
}

However, you shouldn’t pass a range that changes at runtime. If you use a variable that changes at runtime to define the range, the list displays views according to the initial range and ignores any subsequent updates to the range.

You should change your ForEach to receive an array, instead of range. Ideally an Identifiable array, to avoid using \.self. But depending on your goal, this can still work:

import SwiftUI

struct ContentView : View {
    @State var array:[String] = []
    @State var label = "not pressed"
    var body: some View {
        VStack{
            Text(label).onTapGesture {
                self.array.append("ForEach refreshed")
                self.label = "pressed"
            }
            ForEach(array, id: \.self) { item in
                Text(item)
            }
        }
    }
}

Or as rob mayoff suggested, if you need the index:

struct ContentView : View {
    @State var array:[String] = []
    @State var label = "not pressed"
    var body: some View {
        VStack{
            Text(label).onTapGesture {
                self.array.append("ForEach refreshed")
                self.label = "pressed"
            }
            ForEach(array.indices, id: \.self) { index in
                Text(self.array[index])
            }
        }
    }
}```
like image 150
kontiki Avatar answered Oct 20 '22 00:10

kontiki