SwiftUI, как предотвратить просмотр, чтобы перезагрузить все тело - PullRequest
1 голос
/ 02 марта 2020

В основном я пытаюсь выяснить, когда обновится моя viewModel, она уведомит представление и обновит sh все тело. Как этого избежать. Например, если мое представление 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)
    }
}

1 Ответ

2 голосов
/ 02 марта 2020

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 будет называться , только если вновь созданное представление не равно ранее построенному

Наконец, видно, что очень полезно разбивать иерархию пользовательского интерфейса на множество представлений, это позволило бы оптимизировать refre sh много (но не только хороший дизайн, ремонтопригодность, возможность повторного использования и т. Д. c). : ^)).

...