この記事を書こうと思ったきっかけ
・SwiftUIで自作しているアプリで表題の機能を追加してみたいと思った。
・SwiftUIの画面について学習がしたかった。
今回作成するサンプル
・カードは合計五枚あり左右どちらかにスワイプすると次のカードを表示する
・スワイプされるとどちらにスワイプされたかprintする
1.変数を定義する
下記コードで変数を定義しております。
cardOffset: CGFloat = 0は後述しますが
現在のカードの基準の位置になります。
こちらの値が変化することでスワイプ判定となります。
struct ContentView: View {
let cardSize = CGSize(width: 300, height: 400)
let cardCount = 5
@State private var currentIndex = 0
//カードが表示されているかどうかを示すBool値
@State private var isCardVisible = true
@State private var cardOffset: CGFloat = 0
2.cardのviewを作成する
まずCardView用のカスタムviewを定義します
struct CardView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ZStack {
content
}
}
}
その後contentViewで上記で定義したCardViewを使いカードのレイアウトを作ります。
Text("Card (currentIndex + 1)")は.onAppear(画面表示時に呼ばれる)で設定した
currentIndexに+ 1しているので5となります。
var body: some View {
ZStack {
if isCardVisible {
CardView {
Text("Card \(currentIndex + 1)")
.font(.title)
.foregroundColor(.white)
}
.frame(width: cardSize.width, height: cardSize.height)
.background(Color.blue)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 1)
)
.clipped()
}
}.onAppear {
self.currentIndex = cardCount - 1
}
3.スワイプされた際のメソッドを定義する
printされる内容以外は同じないようになっております。
本来はスワイプされた方向によって処理が異なるものを入れることが多いため
メソッドを分けています。
メソッドの内容はそれぞれ呼ばれる度に現在のカード枚数のカウントを-1して
0枚になったときに1で変数定義しているisCardVisibleをfalseにしてカードを
表示しないようにしてます。
private func handleSwipeLeft() {
currentIndex -= 1
//0枚になったら非表示にする
if currentIndex < 0 {
currentIndex = 0
isCardVisible = false
}
print("swipe left")
}
private func handleSwipeRight() {
currentIndex -= 1
//0枚になったら非表示にする
if currentIndex < 0 {
currentIndex = 0
isCardVisible = false
}
print("swipe right")
}
}
4.スワイプの判定を追加する
追記内容を入れたら基本的に動くと思います。
簡単な図で説明すると青がカードの初期位置(cardOffset)となります。
これがどれだけ位置が変わったかによってDragGestureでスワイプ処理を判定しています。
下記図の黄色の-50を超えるとhandleSwipeLeft()が処理され、
緑の50を超えるとhandleSwipeRight()が処理されます。
その後 self.cardOffset = 0で初期位置にカードを配置しています。
var body: some View {
ZStack {
if isCardVisible {
CardView {
Text("Card \(currentIndex + 1)")
.font(.title)
.foregroundColor(.white)
}
.frame(width: cardSize.width, height: cardSize.height)
.background(Color.blue)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 1)
)
.clipped()
//----------------ここから--------------
.offset(x: cardOffset, y: 0)
.animation(.spring())
.gesture(
DragGesture()
.onChanged { value in
self.cardOffset = value.translation.width
}
.onEnded { value in
withAnimation(.spring()) {
//value.translation.widthの値によってスワイプ方向を判定
if value.translation.width < -50 {
self.handleSwipeLeft()
} else if value.translation.width > 50 {
self.handleSwipeRight()
}
self.cardOffset = 0
}
}
)
//-----------------ここまで追記----------------
}
}
.onAppear {
self.currentIndex = cardCount - 1
}
}
まとめ
今回はスワイプの処理について記載してみました。
ご指摘事項や修正案ありましたら教えてただけますと幸いです。