2

I have an array of items (numbers) to be presented to the user using NavigationView, List and a leaf page.

When I update an item (numbers[index] = ...) on a leaf page, it updates the list correctly (which I see when I go back to the list), but not the leaf page itself immediately. I see the change if I go back to the list and re-open the same leaf page.

I would like to understand why it does not update the UI immediately, and how to fix it. Here is the simplified code to re-produce this behavior.

ADDITIONAL INFORMATION This code works fine on Xcode 12. It fails only on Xcode 12.1 (RC1) and Xcode 12.2 (beta3).

import SwiftUI

struct NumberHolder: Identifiable {
    let id = UUID()
    let value:Int
}

struct Playground: View {
    @State var numbers:[NumberHolder] = [
        NumberHolder(value:1),
        NumberHolder(value:2),
        NumberHolder(value:3),
        NumberHolder(value:4),
    ]
    var body: some View {
        NavigationView {
            List(numbers.indices) { index in
                let number = numbers[index]
                NavigationLink(destination: VStack {
                    Text("Number: \(number.value)")
                    Button("Increment") {
                        numbers[index] = NumberHolder(value: number.value + 1)
                    }
                } ) {
                    Text("Number: \(number.value)")
                }
            }
        }
    }
}

struct Playground_Previews: PreviewProvider {
    static var previews: some View {
        Playground()
    }
}
Satoshi Nakajima
  • 1,863
  • 18
  • 29
  • I'm getting this exact issue. Works with iOS 14.1 / Xcode 12.1 but not with iOS 14.2 / Xcode 12.2 RC. Any ideas if this is something they have changed with NavigationLinks or something that's broken, the Apple dev forums are painful to use / get help in – Des Nov 11 '20 at 14:11

3 Answers3

1

Update

Apple has since replied to my Issue stating they have resolved this since watchOS 8 beta 3. I've tested this on WatchOS 9 and iOS 16 and this is indeed now working correctly.


Previous answer:

This had me scratching my day for a few weeks.

It appears there are many features within SwiftUI that do not work in Views that are placed in Lists directly, however if you add a ForEach inside the List said features (such as .listRowPlatterColor(.green) on WatchOS) start to work.

Solution

On iOS 14.2 if you wrap the NavigationLink inside a ForEach the NavigationLink destination (leaf page) will update right away when the data model is updated.

So change your code to

var body: some View {
        NavigationView {
            List {
                ForEach(numbers.indices) { index in
                    let number = numbers[index]
                    NavigationLink(destination: VStack {
                        Text("Number: \(number.value)")
                        Button("Increment") {
                            numbers[index] = NumberHolder(value: number.value + 1)
                        }
                    } ) {
                        Text("Number: \(number.value)")
                    }
                }
            }
        }
    }

Frustratingly, this does not solve the issue when using WatchOS, in WatchOS 7.0 the leaf page is updated, however in WatchOS 7.1 (version goes hand in hand with iOS 14.2 that suffered this "issue") so I have an issue open with Apple FB8892330

Further frustratingly, I still don't know if this is a bug or a feature in SwiftUI, none of the documentation state the requirement for ForEach inside of Lists

Des
  • 314
  • 3
  • 9
0

Try this one. I tested and it works.

import SwiftUI

struct NumberHolder: Identifiable {
    let id = UUID()
    let value:Int
}

struct ContentView: View {   
    @State var numbers:[NumberHolder] = [
        NumberHolder(value:1),
        NumberHolder(value:2),
        NumberHolder(value:3),
        NumberHolder(value:4),
    ]
    var body: some View {
        NavigationView {
            List(numbers.indices) { index in
                NavigationLink(destination: DetailView(numbers: $numbers, index: index)) {
                    Text("Number: \(self.numbers[index].value)")
                }
            }
        }
    }
}

struct DetailView: View {
    @Binding var numbers:[NumberHolder]
    let index: Int
    
    var body: some View {
        VStack {
            Text("Number: \(self.numbers[index].value)")
            
            Button("Increment") {
                numbers[index] = NumberHolder(value: numbers[index].value + 1)
            }
        }
    }
}
Mahmud Ahsan
  • 1,755
  • 19
  • 18
  • Thank you for a quick answer. It indeed solves this particular problem, but I still don't understand why the my sample does not work. In the actual project I am working on, DetailView is not aware that the data is coming from an array, which makes it impossible to use this solution. – Satoshi Nakajima Oct 30 '20 at 16:26
0

I found the answer. It was a bug in Xcode. This code (without any changes) works fine under Xcode 12.0, but fails to update under Xcode 12.2 beta 2.

Satoshi Nakajima
  • 1,863
  • 18
  • 29