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)
}
}
}
この回答は、UITabController
で満たされたUIHostingController
の使用方法について、@ arseniusからの回答にリンクされているソリューション@oivvioの補足として投稿されています。
リンクの答えには1つの問題があります。子ビューに外部SwiftUI依存関係がある場合、それらの子は更新されません。これは、子ビューに内部状態しかないほとんどの場合に問題ありません。ただし、私と同じように、グローバルなReduxシステムを楽しんでいるReact開発者である場合は、問題が発生します。
この問題を解決するには、rootView
が呼び出されるたびにUIHostingController
ごとにupdateUIViewController
を更新することが重要です。私のコードは、不要なUIView
またはUIViewControllers
の作成も回避しています。これらをビュー階層に追加しない場合、作成するのにそれほどコストはかかりませんが、それでも無駄を少なくすればするほど効果的です。
警告:コードは動的タブビューリストをサポートしていません。これを正しくサポートするために、各子タブビューを識別し、配列のdiffを実行して、それらを正しく追加、順序付け、または削除します。それは原則として可能ですが、私の必要を超えています。
最初に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
が呼び出されるたびに、各子コントローラーのルートビューを更新します。 Appleの説明によると、同じチェックがフレームワークレベルで行われると思うので、私はしない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
を最上位に配置できます。