此答案作为对@arsenius 答案中链接的解决方案@oivvio 的补充发布,关于如何使用填充有UIHostingControllers 的UITabController。
链接中的答案有一个问题:如果子视图具有外部 SwiftUI 依赖项,则这些子视图将不会更新。对于大多数子视图只有内部状态的情况,这很好。但是,如果你像我一样,一个喜欢全球 Redux 系统的 React 开发者,那你就麻烦了。
为了解决这个问题,关键是每次调用updateUIViewController时,为每个UIHostingController更新rootView。我的代码还避免了创建不必要的 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 进行比较,因为根据Apple 关于如何更新视图的描述,我觉得会在框架级别进行相同的检查。我可能错了。
使用起来非常简单。以下是我从我目前正在做的一个模拟项目中获取的部分代码:
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可以放在顶层。