SwiftUI 如何防止视图重新加载整个 body

SwiftUI how to prevent view to reload whole body

基本上我试图弄清楚我的 viewModel 何时更新,它会通知视图并刷新整个 body。如何避免这种情况。例如,如果我的视图 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 { [=13=].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)
    }
}

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 的新值将在每次 superview 刷新时构造(所以 不要让 init 很重 ),但是 body 将被调用 仅当新构建的视图不等于先前构建的视图时

最后,可以看出将 UI 层次结构分解为许多视图非常有用,它可以优化刷新很多(但不仅是良好的设计、可维护性、可重用性等:^ )).

backup