如何在SwiftUI选项卡视图中保持滚动位置
在如何在SwiftUI选项卡视图中保持滚动位置,swiftui,tabview,Swiftui,Tabview,在选项卡视图中使用滚动视图,我注意到当我在选项卡之间来回移动时,滚动视图不会保持其滚动位置。如何更改下面的示例以使ScrollView保持其滚动位置 import SwiftUI struct HomeView: View { var body: some View { ScrollView { VStack { Text("Line 1") Text("Line 2")
选项卡视图中使用滚动视图
,我注意到当我在选项卡之间来回移动时,滚动视图不会保持其滚动位置。如何更改下面的示例以使ScrollView保持其滚动位置
import SwiftUI
struct HomeView: View {
var body: some View {
ScrollView {
VStack {
Text("Line 1")
Text("Line 2")
Text("Line 3")
Text("Line 4")
Text("Line 5")
Text("Line 6")
Text("Line 7")
Text("Line 8")
Text("Line 9")
Text("Line 10")
}
}.font(.system(size: 80, weight: .bold))
}
}
struct ContentView: View {
@State private var selection = 0
var body: some View {
TabView(selection: $selection) {
HomeView()
.tabItem {
Image(systemName: "house")
}.tag(0)
Text("Out")
.tabItem {
Image(systemName: "cloud")
}.tag(1)
}
}
}
不幸的是,鉴于SwiftUI(iOS 13.x/Xcode 11.x)的当前限制,内置组件无法实现这一点
原因有二:
当您从选项卡切换时,SwiftUI将完全处理您的视图。这就是您的滚动位置丢失的原因。它不像UIKit那样有很多屏幕外的UIViewController
没有与UIScrollView.contentOffset
等效的ScrollView。这意味着您无法将滚动状态保存到某个位置,并在用户返回选项卡时将其还原
最简单的方法可能是使用uitabarcontroller
填充UIHostingController
s。这样,当用户在选项卡之间移动时,不会丢失每个选项卡的状态
否则,您可以创建一个自定义选项卡容器,并自己修改每个选项卡的不透明度(而不是有条件地将它们包括在层次结构中,这是导致当前问题的原因)
var主体:一些视图{
ZStack{
self.tab1内容不透明度(self.currentTab==.tab1?1.0:0.0)
self.tab2Content.opacity(self.currentTab==.tab2?1.0:0.0)
self.tab3内容不透明度(self.currentTab==.tab3?1.0:0.0)
}
}
我曾经使用过这种技术,当我想让WKWebView
在每次用户短暂切换时都不会完全重新加载
我推荐第一种技术。特别是如果这是你的应用程序的主导航。此答案作为@arsenius答案中链接的解决方案@oivvio的补充发布,该解决方案介绍如何使用UIABController
填充UIHostingController
s
链接中的答案有一个问题:如果子视图具有外部SwiftUI依赖项,则不会更新这些子视图。对于子视图只有内部状态的大多数情况,这是可以接受的。然而,如果你像我一样,是一个喜欢全局Redux系统的React开发人员,你会遇到麻烦
为了解决这个问题,关键是每次调用updateuivewcontroller
时,都要为每个UIHostingController
更新rootView
。我的代码还避免了创建不必要的UIView
或UIViewControllers
:如果不将它们添加到视图层次结构中,它们的创建成本不会太高,但浪费越少越好
警告:代码不支持动态选项卡视图列表。为了正确地支持这一点,我们希望识别每个子选项卡视图,并执行数组差异以正确地添加、排序或删除它们。这在原则上是可以做到的,但超出了我的需要
我们首先需要一个选项卡item
。通过这种方式,控制器可以获取所有信息,而无需创建任何uitabaritem
:
struct XNTabItem:视图{
标题:字符串
让图像:UIImage?
让身体:AnyView
公共初始化(标题:字符串,图像:UIImage?,@ViewBuilder内容:()->内容){
self.title=标题
self.image=image
self.body=AnyView(content())
}
}
然后我们有控制器:
struct XNTabView:UIViewControllerRepresentable{
让tabItems:[XNTabItem]
func makeUIViewController(上下文:UIViewControllerRepresentableContext)->UIAbbarController{
让rootController=UITabBarController()
rootController.viewControllers=tabItems.map{
让host=UIHostingController(rootView:$0.body)
host.tabBarItem=UITabBarItem(标题:$0.title,图像:$0.image,选择图像:$0.image)
返回主机
}
返回根控制器
}
func updateUIViewController(uuController:UIAbbarController,上下文:UIViewControllerRepresentableContext){
让子项=rootController.viewControllers为![UIHostingController]
用于zip中的(newTab,主机)(self.tabItems,子项){
host.rootView=newTab.body
如果host.tabBarItem.title!=host.tabBarItem.title{
host.tabBarItem.title=host.tabBarItem.title
}
如果host.tabBarItem.image!=host.tabBarItem.image{
host.tabBarItem.image=host.tabBarItem.image
}
}
}
}
子控制器在makeUIViewController
中初始化。无论何时调用updateUIViewController
,我们都会更新每个子控制器的根视图。我没有对rootView
进行比较,因为根据苹果关于视图更新方式的描述,我觉得在框架级别也会进行同样的检查。我可能错了
使用它真的很简单。下面是我从当前正在进行的模拟项目中获取的部分代码:
类模型:ObservieObject{
@已发布变量allHouseInfo=HouseInfo.samples
公共函数flipFavorite(用于id:Int){
如果let index=(allHouseInfo.firstIndex{$0.id==id}){
allHouseInfo[index].isFavorite.toggle()
}
}
}
结构收藏夹视图:视图{
let favorites:[HouseInfo]
var body:一些观点{
如果favorites.count>0{
返回任意视图(滚动视图{
ForEach(收藏夹){
卡片内容(信息:$0)
}
})
}否则{
返回任意视图(文本(“无收藏夹”))
}
}
}
结构ContentView:View{
静态let-housingTabImage=UIImage(系统名称:“house.fill”)
静态let-favoritesTabImage=UIImage(系统名称:“heart.fill”)
@ObservedObject var模型=模型()
我的最爱:[何