在ScrollView
内使用TabView
时,我注意到当我在选项卡之间来回移动时,ScrollView不会保持其滚动位置。如何更改以下示例以使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)
}
}
}
答案 0 :(得分:2)
不幸的是,鉴于SwiftUI(iOS 13.x / Xcode 11.x)的当前局限性,内置组件根本不可能做到这一点。
两个原因:
ScrollView
等效的UIScrollView.contentOffset
。这意味着您无法将滚动状态保存在某处,并在用户返回到选项卡时将其恢复。您最简单的方法可能是使用充满UITabBarController
的{{1}}。这样,当用户在每个选项卡之间移动时,您就不会丢失它们的状态。
否则,您可以创建一个自定义标签容器,并自己修改每个标签的不透明度(与有条件地将它们包含在层次结构中相反,这是当前导致问题的原因)。
UIHostingController
当我想防止var body: some View {
ZStack {
self.tab1Content.opacity(self.currentTab == .tab1 ? 1.0 : 0.0)
self.tab2Content.opacity(self.currentTab == .tab2 ? 1.0 : 0.0)
self.tab3Content.opacity(self.currentTab == .tab3 ? 1.0 : 0.0)
}
}
每次用户短暂跳开时完全重新加载时,我就使用了这种技术。
我会推荐第一种技术。特别是如果这是您应用的主要导航方式。
答案 1 :(得分:1)
此答案是对@arsenius答案中链接的解决方案@oivvio的补充,该解决方案涉及如何使用由UITabController
填充的UIHostingController
。
链接中的答案有一个问题:如果子视图具有外部SwiftUI依赖关系,则不会更新这些子视图。对于大多数子视图仅具有内部状态的情况,这很好。但是,如果您像我一样,是一个享誉全球的Redux系统的React开发人员,则会遇到麻烦。
为了解决此问题,关键是每次调用rootView
时为每个UIHostingController
更新updateUIViewController
。我的代码还避免了创建不必要的UIView
或UIViewControllers
:如果不将它们添加到视图层次结构中,创建它们并不会那么昂贵,但是浪费越少越好。
警告:该代码不支持动态标签视图列表。为了正确地支持这一点,我们希望识别每个子选项卡视图,并进行数组比较以正确添加,排序或删除它们。原则上可以做到,但超出了我的需要。
我们首先需要一个TabItem
。这样,控制器就可以获取所有信息,而无需创建任何UITabBarItem
:
struct XNTabItem: View {
let title: String
let image: UIImage?
let body: AnyView
public init<Content: View>(title: String, image: UIImage?, @ViewBuilder content: () -> Content) {
self.title = title
self.image = image
self.body = AnyView(content())
}
}
然后我们有了控制器:
struct XNTabView: UIViewControllerRepresentable {
let tabItems: [XNTabItem]
func makeUIViewController(context: UIViewControllerRepresentableContext<XNTabView>) -> UITabBarController {
let rootController = UITabBarController()
rootController.viewControllers = tabItems.map {
let host = UIHostingController(rootView: $0.body)
host.tabBarItem = UITabBarItem(title: $0.title, image: $0.image, selectedImage: $0.image)
return host
}
return rootController
}
func updateUIViewController(_ rootController: UITabBarController, context: UIViewControllerRepresentableContext<XNTabView>) {
let children = rootController.viewControllers as! [UIHostingController<AnyView>]
for (newTab, host) in zip(self.tabItems, children) {
host.rootView = newTab.body
if host.tabBarItem.title != host.tabBarItem.title {
host.tabBarItem.title = host.tabBarItem.title
}
if host.tabBarItem.image != host.tabBarItem.image {
host.tabBarItem.image = host.tabBarItem.image
}
}
}
}
儿童控制器在makeUIViewController
中初始化。每当调用updateUIViewController
时,我们都会更新每个子控制器的根视图。我没有做rootView
的比较,因为根据苹果公司关于视图更新的描述,我觉得同样的检查将在框架级别进行。我可能错了。
使用它非常简单。以下是我从当前正在执行的模拟项目中获取的部分代码:
class Model: ObservableObject {
@Published var allHouseInfo = HouseInfo.samples
public func flipFavorite(for id: Int) {
if let index = (allHouseInfo.firstIndex { $0.id == id }) {
allHouseInfo[index].isFavorite.toggle()
}
}
}
struct FavoritesView: View {
let favorites: [HouseInfo]
var body: some View {
if favorites.count > 0 {
return AnyView(ScrollView {
ForEach(favorites) {
CardContent(info: $0)
}
})
} else {
return AnyView(Text("No Favorites"))
}
}
}
struct ContentView: View {
static let housingTabImage = UIImage(systemName: "house.fill")
static let favoritesTabImage = UIImage(systemName: "heart.fill")
@ObservedObject var model = Model()
var favorites: [HouseInfo] {
get { model.allHouseInfo.filter { $0.isFavorite } }
}
var body: some View {
XNTabView(tabItems: [
XNTabItem(title: "Housing", image: Self.housingTabImage) {
NavigationView {
ScrollView {
ForEach(model.allHouseInfo) {
CardView(info: $0)
.padding(.vertical, 8)
.padding(.horizontal, 16)
}
}.navigationBarTitle("Housing")
}
},
XNTabItem(title: "Favorites", image: Self.favoritesTabImage) {
NavigationView {
FavoritesView(favorites: favorites).navigationBarTitle("Favorites")
}
}
]).environmentObject(model)
}
}
状态被提升为Model
的根级别,并且它携带了突变帮手。在CardContent
中,您可以通过EnvironmentObject
访问状态和助手。更新将在Model
对象中进行,并传播到ContentView
,并通知我们的XNTabView
并更新每个UIHostController
。
编辑:
.environmentObject
可以放在顶层。答案 2 :(得分:0)
re.sub('\s+', ' ', word)
现在将在选项卡之间移动时保持选项卡中的滚动位置,因此不再需要原始问题答案中的方法。