How to reload data without using onAppear in SwiftUI in watchOS

Issue #468

From onAppeear

Adds an action to perform when the view appears.

In theory, this should be triggered every time this view appears. But in practice, it is only called when it is pushed on navigation stack, not when we return to it.

So if user goes to a bookmark in a bookmark list, unbookmark an item and go back to the bookmark list, onAppear is not called again and the list is not updated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import SwiftUI

struct BookmarksView: View {
let service: Service
@State var items: [AnyItem]
@EnvironmentObject var storeContainer: StoreContainer

var body: some View {
List(items) { item in
makeItemRow(item: item)
.padding([.top, .bottom], 4)
}
.onAppear(perform: {
self.items = storeContainer.bookmarks(service: service).map({ AnyItem(item: $0) })
})
}
}

So instead of relying on UI state, we should rely on data state, by listening to onReceive and update our local @State

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct BookmarksView: View {
let service: Service
@State var items: [AnyItem]
@EnvironmentObject var storeContainer: StoreContainer

var body: some View {
List(items) { item in
makeItemRow(item: item)
.padding([.top, .bottom], 4)
}
.onAppear(perform: {
self.reload()
})
.onReceive(storeContainer.objectWillChange, perform: { _ in
self.reload()
})
}

private func reload() {
self.items = storeContainer.bookmarks(service: service).map({ AnyItem(item: $0) })
}
}

Inside our ObservableObject, we need to trigger changes notification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final class StoreContainer: ObservableObject {
let objectWillChange = PassthroughSubject<(), Never>()

func bookmark(item: ItemProtocol) {
defer {
objectWillChange.send(())
}
}

func unbookmark(item: ItemProtocol) {
defer {
objectWillChange.send(())
}
}
}

Updated at 2020-10-03 10:43:47

Comments