SwiftUI如何防止视图重新加载整个身体
Posted
技术标签:
【中文标题】SwiftUI如何防止视图重新加载整个身体【英文标题】:SwiftUI how to prevent view to reload whole body 【发布时间】:2020-06-14 08:25:08 【问题描述】:基本上,我试图弄清楚我的 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)
【问题讨论】:
【参考方案1】: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 层次结构分解为多个视图非常有用,它将允许优化刷新很多(但不仅是良好的设计、可维护性、可重用性等:^)。
【讨论】:
有一个错误导致每次用户进行选择时都会使用选择器重新渲染视图。这反过来又导致选择器在用户做出选择后在视觉上重置为其初始值。你的解决方案很棒,直到现在我才知道这个内置功能 谢谢。这个解决方案给了我在切换 UI 时随机挂断的答案。我还发现这篇文章解释得很详细-swiftui-lab.com/equatableview @Seitenwerk 你的问题到底是什么,你是如何解决的。我想我遇到了类似的问题。我目前正在使用 SwiftUIFlux,一旦它被更改(也是手动),它会自动更新选择器的日期,因此会导致选择器立即关闭。所以我不知何故需要打破这个更新周期,但还没有找到可行的解决方案。 如果您在View
中使用@EnvironmentObject
,则此解决方案不起作用以上是关于SwiftUI如何防止视图重新加载整个身体的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Touch Down 实现时防止 SwiftUI 上的重新触发
如何防止 SwiftUI 像使用 @StateObject 一样重新初始化我的包装属性?
SwiftUI:防止 Image() 将视图矩形扩展到屏幕边界之外
如何阻止 SwiftUI 重新加载我通过 UIViewRepresentable 使用的 WKWebView 页面?
SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了啥?