如何在SwiftUI中强制重新创建视图?
我创建了一个视图,它获取并显示数据列表。工具栏中有一个上下文菜单,用户可以在其中更改数据类别。此关联菜单位于列表之外 我想做的是,当用户选择一个类别时,列表应该从后端重新提取数据并重新绘制整个视图 我制作了一个BaseListView,它可以在我的应用程序的各个屏幕中重用,而且由于如何在SwiftUI中强制重新创建视图?,swiftui,Swiftui,我创建了一个视图,它获取并显示数据列表。工具栏中有一个上下文菜单,用户可以在其中更改数据类别。此关联菜单位于列表之外 我想做的是,当用户选择一个类别时,列表应该从后端重新提取数据并重新绘制整个视图 我制作了一个BaseListView,它可以在我的应用程序的各个屏幕中重用,而且由于loadData在BaseListView中,我不知道如何调用它来重新加载数据 我做这件事的时候很好吗?有没有办法强制SwiftUI重新创建整个视图,以便BaseListView在第一次创建时加载数据并呈现子视图 st
loadData
在BaseListView中,我不知道如何调用它来重新加载数据
我做这件事的时候很好吗?有没有办法强制SwiftUI重新创建整个视图,以便BaseListView在第一次创建时加载数据并呈现子视图
struct ProductListView: View {
var body: some View {
BaseListView(
rowView: { ProductRowView(product: $0, searchText: $1)},
destView: { ProductDetailsView(product: $0) },
dataProvider: {(pageIndex, searchText, complete) in
return fetchProducts(pageIndex, searchText, complete)
})
.hideKeyboardOnDrag()
.toolbar {
ProductCategories()
}
.onReceive(self.userSettings.$selectedCategory) { category in
//TODO: Here I need to reload data & recreate entire of view.
}
.navigationTitle("Products")
}
}
extension ProductListView{
private func fetchProducts(_ pageIndex: Int,_ searchText: String, _ complete: @escaping ([Product], Bool) -> Void) -> AnyCancellable {
let accountId = Defaults.selectedAccountId ?? ""
let pageSize = 20
let query = AllProductsQuery(id: accountId,
pageIndex: pageIndex,
pageSize: pageSize,
search: searchText)
return Network.shared.client.fetchPublisher(query: query)
.sink{ completion in
switch completion {
case .failure(let error):
print(error)
case .finished:
print("Success")
}
} receiveValue: { response in
if let data = response.data?.getAllProducts{
let canLoadMore = (data.count ?? 0) > pageSize * pageIndex
let rows = data.rows
complete(rows, canLoadMore)
}
}
}
}
ProductCategory
是一个单独的视图:
struct ProductCategories: View {
@EnvironmentObject var userSettings: UserSettings
var categories = ["F&B", "Beauty", "Auto"]
var body: some View{
Menu {
ForEach(categories,id: \.self){ item in
Button(item, action: {
userSettings.selectedCategory = item
Defaults.selectedCategory = item
})
}
}
label: {
Text(self.userSettings.selectedCategory ?? "All")
.regularText()
.autocapitalization(.words)
.frame(maxWidth: .infinity)
}.onAppear {
userSettings.selectedCategory = Defaults.selectedCategory
}
}
}
由于我的应用程序具有各种具有相同行为(分页、搜索等)的列表视图,因此我创建了如下BaseListView:
struct BaseListView<RowData: StringComparable & Identifiable, RowView: View, Target: View>: View {
enum ListState {
case loading
case loadingMore
case loaded
case error(Error)
}
typealias DataCallback = ([RowData],_ canLoadMore: Bool) -> Void
@State var rows: [RowData] = Array()
@State var state: ListState = .loading
@State var searchText: String = ""
@State var pageIndex = 1
@State var canLoadMore = true
@State var cancellableSet = Set<AnyCancellable>()
@ObservedObject var searchBar = SearchBar()
@State var isLoading = false
let rowView: (RowData, String) -> RowView
let destView: (RowData) -> Target
let dataProvider: (_ page: Int,_ search: String, _ complete: @escaping DataCallback) -> AnyCancellable
var searchable: Bool?
var body: some View {
HStack{
content
}
.if(searchable != false){view in
view.add(searchBar)
}
.hideKeyboardOnDrag()
.onAppear(){
print("On appear")
searchBar.$text
.debounce(for: 0.8, scheduler: RunLoop.main)
.removeDuplicates()
.sink { text in
print("Search bar updated")
self.state = .loading
self.pageIndex = 1
self.searchText = text
self.rows.removeAll()
self.loadData()
}.store(in: &cancellableSet)
}
}
private var content: some View{
switch state {
case .loading:
return Spinner(isAnimating: true, style: .large).eraseToAnyView()
case .error(let error):
print(error)
return Text("Unable to load data").eraseToAnyView()
case .loaded, .loadingMore:
return
ScrollView{
list(of: rows)
}
.eraseToAnyView()
}
}
private func list(of data: [RowData])-> some View{
LazyVStack{
let filteredData = rows.filter({
searchText.isEmpty || $0.contains(string: searchText)
})
ForEach(filteredData){ dataItem in
VStack{
//Row content:
if let target = destView(dataItem), !(target is EmptyView){
NavigationLink(destination: target){
row(dataItem)
}
}else{
row(dataItem)
}
//LoadingMore indicator
if case ListState.loadingMore = self.state{
if self.rows.isLastItem(dataItem){
Seperator(color: .gray)
LoadingView(withText: "Loading...")
}
}
}
}
}
}
private func row(_ dataItem: RowData) -> some View{
rowView(dataItem, searchText).onAppear(){
//Check if need to load next page of data
if rows.isLastItem(dataItem) && canLoadMore && !isLoading{
isLoading = true
state = .loadingMore
pageIndex += 1
print("Load page \(pageIndex)")
loadData()
}
}.padding(.horizontal)
}
private func loadData(){
dataProvider(pageIndex, searchText){ newData, canLoadMore in
self.state = .loaded
rows.append(contentsOf: newData)
self.canLoadMore = canLoadMore
isLoading = false
}
.store(in: &cancellableSet)
}
}
struct BaseListView:视图{
枚举列表状态{
装箱
案例加载更多
满箱
案例错误(错误)
}
typealias DataCallback=([RowData],uLoadMore:Bool)->Void
@状态变量行:[RowData]=数组()
@状态变量State:ListState=.loading
@状态变量searchText:String=“”
@状态变量pageIndex=1
@状态变量canLoadMore=true
@状态变量CancelableSet=Set()
@ObservedObject var searchBar=searchBar()
@状态变量isLoading=false
让rowView:(RowData,String)->rowView
让destView:(RowData)->目标
让dataProvider:(uPage:Int,search:String,complete:@escaping DataCallback)->anyCancelable
var可搜索:布尔?
var body:一些观点{
HStack{
内容
}
.if(searchable!=false){view in
view.add(搜索栏)
}
.hideKeyboardOnDrag()
.onAppear(){
打印(“出现时”)
搜索栏。$text
.debounce(对于:0.8,调度程序:RunLoop.main)
.removeDuplicates()
.sink{text in
打印(“搜索栏更新”)
self.state=.loading
self.pageIndex=1
self.searchText=文本
self.rows.removeAll()
self.loadData()
}.store(位于:&可取消集)
}
}
私有var内容:一些观点{
开关状态{
案例.加载:
返回微调器(isAnimating:true,样式:.large)。橡皮擦到任何视图()
案例错误(let error):
打印(错误)
返回文本(“无法加载数据”).橡皮擦到任何视图()
case.loaded、.loadingMore:
返回
滚动视图{
列表(共:行)
}
.eraseToAnyView()
}
}
私有函数列表(数据:[RowData])->某些视图{
懒散的{
让filteredData=rows.filter({
searchText.isEmpty | |$0.contains(字符串:searchText)
})
ForEach(filteredData){dataItem在
VStack{
//行内容:
如果let target=destView(数据项),!(目标为空视图){
导航链接(目的地:目标){
行(数据项)
}
}否则{
行(数据项)
}
//加载更多指示器
如果case ListState.loadingMore=self.state{
if self.rows.isLastItem(数据项){
分隔符(颜色:。灰色)
加载视图(带文本:“加载…”)
}
}
}
}
}
}
私有func行(dataItem:RowData)->某些视图{
行视图(dataItem,searchText).onAppear(){
//检查是否需要加载下一页数据
如果rows.isLastItem(dataItem)&&canLoadMore&&isLoading{
isLoading=true
状态=.loadingMore
页面索引+=1
打印(“加载页面\(页面索引)”)
loadData()
}
}.padding(.卧式)
}
私有函数loadData(){
dataProvider(pageIndex,searchText){newData,可以在中加载更多
self.state=.loaded
append(contentsOf:newData)
self.canLoadMore=canLoadMore
isLoading=错误
}
.store(位于:&可取消集)
}
}
在BaseListView中,您应该有一个onChange修饰符,用于捕获对用户设置的更改。$selectedCategory并在那里调用loadData
如果您无权访问BaseListView中的userSettings,请将其作为绑定或@EnvironmentObject传递。谢谢。您的意思是在BaseListView中定义一个Bool变量,然后在外部绑定一个变量,然后在需要重新加载数据时切换它吗?然后在加载数据后,我们需要将其切换回false?不,onChange应该注意userSettings.category的更改-当更改时,调用fetch再次获取相应的类别。这将触发列表的重新加载。您可以在要强制重新创建的视图上使用
id
。在这里找到一个演示。谢谢@Asperi。视图按预期重新创建,但是导航栏和搜索栏变得透明。我在这里贴了新问题,你能看一下吗?顺便说一句,请把你的答案复制到一个答案上,我会把它标记为正确答案。