SwiftUI如何防止视图重新加载整个身体 [英] SwiftUI how to prevent view to reload whole body

查看:80
本文介绍了SwiftUI如何防止视图重新加载整个身体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基本上,我试图弄清楚何时更新viewModel,它将通知视图并刷新整个正文.如何避免这种情况.例如,如果我的视图GoLiveView已经存在另一个视图BroadcasterView,并且后来更新了我的goLiveViewModel,则GoLiveView将被刷新,并且它将再次创建BroadcasterView,因为showBroadcasterView = true.因此,它将导致很多问题.

  struct GoLiveView:查看{@ObservedObject var goLiveViewModel = GoLiveViewModel()@EnvironmentObject var sessionStore:SessionStore@State私人var showBroadcasterView = false@State私人var showLiveView = false在里面() {goLiveViewModel.refresh()}var body:some View {NavigationView {List(goLiveViewModel.rooms){当goLiveViewModed得到更新时//NavigationLink(目标:LiveView(clientRole:.audience,房间:room,showLiveView:$ showLiveView))){LiveCell(房间:房间)}} .background(Color.white).navigationBarTitle("Live",displayMode:.inline).navigationBarItems(导致:按钮(操作:{self.showBroadcasterView = true}, 标签: {Image("ic_go_live").renderingMode(.original)})).frame(maxWidth:.infinity,maxHeight:.infinity).background(颜色(红色:34/255,绿色:34/255,蓝色:34/255)).sheet(isPresented:$ showBroadcasterView){//这是问题,被调用了很多次,因此重新加载了整个主体,并创建了BroadcasterView()的新实例.因为showBroadcasterView =仍然为true.BroadcasterView(broadcasterViewModel:BroadcasterViewModel(showBroadcasterView:$ showBroadcasterView)).environmentObject(self.sessionStore).frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.clear)}}} 

这是我的GoliveViewModel

  typealias RoomsFetchOuput = AnyPublisher< RoomsFetchState,Never>枚举RoomsFetchState:Equatable {静态函数==(lhs:RoomsFetchState,rhs:RoomsFetchState)->布尔{开关(lhs,rhs){大小写(.loading,.loading):返回true大小写(.success(让lhsrooms),.success(让rhsrooms)):返回lhsrooms == rhsrooms大小写(.noResults,.noResults):返回true大小写(.failure,.failure):返回true默认值:返回false}}装箱成功案例([Room])案例编号案例失败(错误)} 

  class GoLiveViewModel:ObservableObject {私有懒惰var webServiceManager = WebServiceManager()@已发布的无用室= [Room]()私人惰性var计时器= Timer()私人var cancellables:[AnyCancellable] = []在里面() {timer = Timer.scheduledTimer(timeInterval:4.0,target:self,选择器:#selector(refresh),userInfo:nil,重复次数:true)//每隔4秒刷新一次}func fetch()->RoomsFetchOuput {返回webServiceManager.fetchAllRooms().map({结果->切换结果{案例.success([]):返回.noResults案例让.success(rooms):返回.success(rooms)case .failure(让错误):返回.failure(错误)}}).eraseToAnyPublisher()let isLoading:RoomsFetchOuput = .just(.loading)让initialState:RoomsFetchOuput = .just(.noResults)让空闲:RoomsFetchOuput = Publishers.Merge(isLoading,initialState).eraseToAnyPublisher()返回Publishers.Merge(空闲,房间).removeDuplicates().eraseToAnyPublisher()}@objc func refresh(){cancellables.forEach {$ 0.cancel()}cancellables.removeAll()拿来().sink {[弱自我]状态守卫让自我=自我其他{返回}切换状态{案例让.success(rooms):self.rooms =房间case .failure:print("failure")//向用户显示错误警报case .noResults:打印(无结果")self.rooms = []//隐藏微调器case .loading:print(.loading")//显示微调器}}.store(位于:& cancellables)}} 

解决方案

SwfitUI为此提供了一种模式.它需要使自定义视图符合 Equatable 协议

  struct CustomView:可视的{静态函数==(lhs:CustomView,rhs:CustomView)->布尔{//<<在视图属性上返回是",该属性标识//视图是相等的,不应刷新(即,不重建`body`)}... 

并代替构造添加修饰符 .equatable(),例如

 变量主体:某些视图{CustomView().equatable()} 

是的,每次刷新超级视图时,都会构造 CustomView 的新值(因此,不要使 init 变重),但是<仅当新构建的视图与先前构建的视图不相等时,code> body 才会被称为

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

Basically I try to figure out when my viewModel get updated, it will notify view and it will refresh whole body. How to avoid that. For example if my view GoLiveView already present another view BroadcasterView, and later my goLiveViewModel get updated, GoLiveView will be refreshed, and it will create BroadcasterView again , because showBroadcasterView = true. And it will cause so many issues down the road, because of that.

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)
            }

    }
}

this is my 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)
    }
}

解决方案

SwfitUI has a pattern for this. It needs to conform custom view to Equatable protocol

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)
    }
...

and in place of construction add modifier .equatable(), like

var body: some View {
      CustomView().equatable()
}

yes, new value of CustomView will be constructed every time as superview refreshing (so don't make init heavy), but body will be called only if newly constructed view is not equal of previously constructed

Finally, it is seen that it is very useful to break UI hierarchy to many views, it would allow to optimise refresh a lot (but not only good design, maintainability, reusability, etc. :^) ).

这篇关于SwiftUI如何防止视图重新加载整个身体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆