[SwiftUI] TabView のインジケータをカスタマイズする

2 min read読了の目安(約2200字

ページングUI やページャーやタブUI と言われている、「横スワイプしてページを切り替えるUI」のインジケータをカスタマイズした話です。

困ったこと

横スワイプしてページを切り替えるUI は、SwiftUI ではTabViewPageTabViewStyle を利用することで実現することができます。

しかしTabView のPageTabViewStyle ではデフォルトのインジケータを表示してくれるものの、そのインジケータはほぼカスタマイズできません。
今回はインジケータの座標を変更して、任意の位置にインジケータを表示することをやりたいと思いました。

対処方法

デフォルトのインジケータを使わず、独自のインジケータUI を使うという発想になります。
以下が独自に実装したインジケータUI のコードです。

struct TabIndexView: View {
    let numberOfPages: Int
    let currentIndex: Int
    
    var dotSize = CGFloat(5)
    var spacing = CGFloat(6)
    var dotColor = Color.white
    var borderColor = Color.white
    
    var body: some View {
        HStack(spacing: spacing) {
            ForEach(0..<numberOfPages) { index in
                if index == currentIndex {
                    Circle()
                        .fill(dotColor)
                        .frame(width: dotSize, height: dotSize)
                } else {
                    Circle()
                        .stroke(borderColor, lineWidth: 0.5)
                        .frame(width: dotSize, height: dotSize)
                }
            }
        }
        
    }
}

次のコードで実装したインジケータを利用します。
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) によってデフォルトのインジケータUI を非表示にしています。
また、.overlayのところで任意のpadding を与えることによって、インジケータが表示される位置を調整しています。

    @State private var currentTabViewIndex = Int(0)

    var content: some View {
        TabView(selection: $currentTabViewIndex) {
            pageView
                .tag(0)
            pageView
                .tag(1)
            pageView
                .tag(2)
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        .overlay(
            VStack {
                Spacer()
                TabIndexView(numberOfPages: 3, currentIndex: currentTabViewIndex)
                    .padding(.bottom, 44)
            }
        )
    }

独自のUI のため、もちろんドットなどの見た目も自由に変更することができます。
末尾の参考記事のようにアニメーションを付けたりすると格好いいかもしれません。

参考

Custom Paging UI in SwiftUI | Better Programming