基本上,我试图弄清楚何时更新viewModel,它将通知视图并刷新整个正文。如何避免这种情况。例如,如果我的视图GoLiveView已经存在另一个视图BroadcasterView,并且后来更新了我的goLiveViewModel,则GoLiveView将被刷新,并且它将再次创建BroadcasterView,因为showBroadcasterView = true。因此,它将导致很多问题。
struct GoLiveView: View {
@ObservedObject var goLiveViewModel = GoLiveViewModel()
@EnvironmentObject var sessionStore: SessionStore
@State private var showBroadcasterView = false
@State private var showLiveView = false
init() {
goLiveViewModel.refresh()
}
var body: some View {
NavigationView {
List(goLiveViewModel.rooms) { room in // when goLiveViewModed get updated
NavigationLink(destination: LiveView(clientRole: .audience, room: room, showLiveView: $showLiveView))) {
LiveCell(room: room)
}
}.background(Color.white)
.navigationBarTitle("Live", displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
self.showBroadcasterView = true
}, label: {
Image("ic_go_live").renderingMode(.original)
})).frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(red: 34/255, green: 34/255, blue: 34/255))
.sheet(isPresented: $showBroadcasterView) { // here is problem, get called many times, hence reload whole body ,and create new instances of BroadcasterView(). Because showBroadcasterView = is still true.
BroadcasterView(broadcasterViewModel: BroadcasterViewModel(showBroadcasterView: $showBroadcasterView))
.environmentObject(self.sessionStore)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.clear)
}
}
}
这是我的GoliveViewModel
typealias RoomsFetchOuput = AnyPublisher<RoomsFetchState, Never>
enum RoomsFetchState: Equatable {
static func == (lhs: RoomsFetchState, rhs: RoomsFetchState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading): return true
case (.success(let lhsrooms), .success(let rhsrooms)):
return lhsrooms == rhsrooms
case (.noResults, .noResults): return true
case (.failure, .failure): return true
default: return false
}
}
case loading
case success([Room])
case noResults
case failure(Error)
}
class GoLiveViewModel: ObservableObject {
private lazy var webServiceManager = WebServiceManager()
@Published var rooms = [Room]()
private lazy var timer = Timer()
private var cancellables: [AnyCancellable] = []
init() {
timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(refresh) , userInfo: nil, repeats: true) // call every 4 second refresh
}
func fetch() -> RoomsFetchOuput {
return webServiceManager.fetchAllRooms()
.map ({ result -> RoomsFetchState in
switch result {
case .success([]): return .noResults
case let .success(rooms): return .success(rooms)
case .failure(let error): return .failure(error)
}
})
.eraseToAnyPublisher()
let isLoading: RoomsFetchOuput = .just(.loading)
let initialState: RoomsFetchOuput = .just(.noResults)
let idle: RoomsFetchOuput = Publishers.Merge(isLoading, initialState).eraseToAnyPublisher()
return Publishers.Merge(idle, rooms).removeDuplicates().eraseToAnyPublisher()
}
@objc func refresh() {
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
fetch()
.sink { [weak self] state in
guard let self = self else { return }
switch state {
case let .success(rooms):
self.rooms = rooms
case .failure: print("failure")
// show error alert to user
case .noResults: print("no result")
self.rooms = []
// hide spinner
case .loading: print(".loading")
// show spinner
}
}
.store(in: &cancellables)
}
}
答案 0 :(得分:17)
SwfitUI为此提供了一种模式。它需要使自定义视图符合Equatable
协议
struct CustomView: View, Equatable {
static func == (lhs: CustomView, rhs: CustomView) -> Bool {
// << return yes on view properties which identifies that the
// view is equal and should not be refreshed (ie. `body` is not rebuilt)
}
...
并代替构造添加修饰符.equatable()
,例如
var body: some View {
CustomView().equatable()
}
是的,每次刷新超级视图时都会构造CustomView
的新值(因此请勿使init
变重),但是body
仅当新构建的视图与先前构建的视图不相等时才称为
最后,可以看到将UI层次结构分解为许多视图非常有用,它可以使刷新优化很多(但不仅是良好的设计,可维护性,可重用性,如:^))。