【Swift UI】独自カルーセルUIの実装方法!スワイプでコンテンツを切り替え
この記事からわかること
- Swift UIでカルーセルUIを実装するには?
- @GestureStateの使い方
- ドラッグ(スワイプ)したビューを移動させる方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.0
- Swift:5.9
- macOS:Sonoma 14.1
カルーセルとは?
カルーセルとはスライドやタップなどによってコンテンツをスライドさせて複数のコンテンツを表示するビューのことです。自動でコンテンツをスライドさせるものはスライドショーなどと呼ばれたりします。
Swift UIでスワイプすることで表示しているコンテンツが切り替わるようなビューを実装してみたので実装方法をまとめておきます。
Swift UIで実装してみる
仕組み
- HStackはコンテンツ全体の大きさになる
- 表示エリアはページ1つ分(clippedして余分な部分が表示されないように)
- スワイプ中はdragOffsetに変化量を格納しHStackを一時的にずらす
- スワイプ完了後は「currentIndex * width」分だけHStackをずらす
struct CarouselView: View {
// カルーセル自体とコンテンツ単体の横幅と高さ
public var width: CGFloat
public var height: CGFloat
// 初期表示コンテンツ番号
@State private var currentIndex = 1
// コンテンツ数と内容
@State private var pages = [0, 1, 2]
// スワイプされた際にHStackごとビューを動かす量
@GestureState private var dragOffset: CGFloat = 0
var body: some View {
// 表示
GeometryReader { geometry in
HStack(spacing: 0) {
ForEach(pages, id: \.self) { page in
Text("page\(page)")
.foregroundColor(.white)
.frame(width: width, height: height)
.background(page == 1 ? Color.black : Color.gray)
}
}
// X方向にスワイプされた量だけHStackをずらす
// dragOffsetにはスワイプされている間だけその値が格納されスワイプが終了すると0になる
.offset(x: dragOffset)
// スワイプが完了してcurrentIndexが変化すると[currentIndex * width]分だけHStackをずらす
.offset(x: -CGFloat(self.currentIndex) * width)
.gesture(
DragGesture(minimumDistance: 0)
// スワイプの変化を観測しスワイプの変化分をHStackのoffsetに反映(スワイプでビューが動く部分を実装)
.updating(self.$dragOffset, body: { (value, state, _) in
print("X方向にスワイプされた量:\(value.translation.width)")
// スワイプ変化量をdragOffsetに反映
state = value.translation.width
// スワイプが完了するとdragOffsetの値は0になる
})
.onEnded { value in
print("最終的な変化量:\(value.translation.width)")
// 最終的な変化量が正数ならcurrentIndexを減らし、負数なららcurrentIndexを増やす
let newIndex = value.translation.width > 0 ? self.currentIndex - 1 : self.currentIndex + 1
// コンテンツ数を最大/最小のインデックスとする(-1や2以上にならないように制御)
self.currentIndex = max(0, min(2, newIndex))
}
)
}// アニメーションをなめらかに
.animation(.interpolatingSpring(mass: 0.6, stiffness: 150, damping: 80, initialVelocity: 0.1))
.frame(width: width, height: height)
.clipped()
}
}
実際に使用する際は以下のように使用します。
struct ContentView: View {
var body: some View {
CarouselView(width: 300, height: 200)
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。