Ccmmutty logo
Commutty IT
4 min read

[SwiftUI]TabViewにスクロールと戻る処理を追加する方法

https://cdn.magicode.io/media/notebox/blob_AXYoxWk

コード

tabViewを用意し、以下のように引数のselectionにhandlerを指定する。
struct TabBarView: View {
    @State private var tabSelection: String
    @State private var tappedTwice: Bool = false

    private let homeView = "Home"

    init() {
        self.tabSelection = homeView
    }
    var body: some View {
        // タブアイコンをタップしたとき、どこをタップしたか、同じアイコンをタップしたかの判定
        let handler = Binding<String>(
            get: { tabSelection },
            set: {
                if $0 == tabSelection {
                    tappedTwice = true
                }
                tabSelection = $0
            }
        )

        ScrollViewReader { proxy in
            TabView(selection: handler) {
                NavigationView {
                    HomeView(scrollId: homeView)
                        .addAction(tappedTwice: $tappedTwice, proxy: proxy, tabSelection: tabSelection, viewName: homeView)
                }
                .tabItem {
                    Image(systemName: "house")
                    Text("ホーム")
                }
                // こちらのtagはルートに戻るときに使っている
                .tag(homeView)
            }
        }
    }
}
次にスクロール機能と戻る機能を追加するmodifierを作る。
struct AddAction: ViewModifier {
    @State var isExistingView = false
    @State var viewId = UUID()

    @Binding var tappedTwice: Bool

    let proxy: ScrollViewProxy
    let tabSelection: String
    let viewName: String

    func body(content: Content) -> some View {
        content
            .id(viewId)
            .onAppear {
                // ios14ではこれらがうまく機能しないのでUIKitのViewWillAppearを使うとうまくいった。
                isExistingView = true
            }
            .onDisappear {
                isExistingView = false
            }
            .onChange(of: tappedTwice, perform: {
                guard $0 else {
                    tappedTwice = false
                    return
                }
                if tabSelection == viewName {
                    if isExistingView {
                        withAnimation {
                            // 一番上に戻る
                            // viewNameはHomeView内で設定したもの
                            proxy.scrollTo(viewName, anchor: .top)
                        }
                    } else {
                        // ルートに戻る
                        viewId = UUID()
                    }
                }
                tappedTwice = false
            })
    }
}

extension View {
    @ViewBuilder func addAction(tappedTwice: Binding<Bool>, proxy: ScrollViewProxy, tabSelection: String, viewName: String) -> some View {
        self.modifier(AddAction(tappedTwice: tappedTwice, proxy: proxy, tabSelection: tabSelection, viewName: viewName))
    }
}
最後にそれぞれのView(今回ならHomeView)にタグを追加する。
struct HomeView: View {
    let scrollId: String

    var body: some View {
        ScrollView {
            Text("何らかのView")
                // スクロールで戻りたい位置につける。基本は一番上
                .id(scrollId)
        }
    }
}

参考

Final codeでidが消えているので注意

Discussion

コメントにはログインが必要です。