この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
iPhoneユーザーなら誰しもピンチインアウトでViewを拡大縮小したい時が人生に一度や二度あると思いますが、僕もその時がやってきたので調べてみることにしました。
作ったもの
環境
- Xcode 13.3
MagnificationGesture
Viewを拡大縮小する為には、MagnificationGesture
というジェスチャーを使用する必要があります。
Apple公式ドキュメント: MagnificationGestureに記載されているコードと実行結果のデモを載せておきました。
コード
struct MagnificationGestureView: View {
@GestureState var magnifyBy = 1.0
var magnification: some Gesture {
MagnificationGesture()
.updating($magnifyBy) { currentState, gestureState, transaction in
gestureState = currentState
}
}
var body: some View {
Circle()
.frame(width: 100, height: 100)
.scaleEffect(magnifyBy)
.gesture(magnification)
}
}
@GestureState
はジェスチャーを実行している間にプロパティを更新し、ジェスチャーが終了するとプロパティを初期状態にリセットするプロパティラッパータイプです。
updating(_:body:)
では、第一引数にバインディングする為のGestureState
を渡して、body
は、ジェスチャーの値が変更されたときに呼ばれるコールバックで、ジェスチャー更新後の状態の値が渡ってきます。
ドキュメントにも記載していますが、この実装だとジェスチャーが終わると元の大きさに戻ってしまいます。
デモ
ジェスチャーが終了しても大きさを元の戻らないようにする
今回はジェスチャーを終了しても大きさを元に戻したくなかったので、元に戻らないように実装していきたいます。
MagnificationGesture.Valueは毎回1.0からスタートする
@GestureState
だとジェスチャー後に元の大きさに戻ってしまう為、@State
に変更します。
onChange
を使用して、変更後のMagnificationGesture.Value
(拡大縮小の倍率)を受け取り、magnifyBy
の値を更新後の倍率で更新するような実装にしたところ、MagnificationGesture()
が開始された地点を1.0
としてそこからどのくらい縮小されたか、拡大されたかの倍率がMagnificationGesture.Value
として値が渡ってくることが分かりました。
ジェスチャー内のコードを下記に変更して動きを確認します。
@State private var magnifyBy = 1.0
var magnification: some Gesture {
MagnificationGesture()
.onChanged { value in
magnifyBy = value
}
}
デモ
ピンチアウトを行い、ピンチアウトを終了すると大きさは維持されますが、もう一度MagnificationGesture()
を開始したタイミングでmagnifyBy
に1.0
の値が代入される為、元の大きさに戻っています。
大きさが戻らないように実装する
ジェスチャー部分とプロパティ部分をこのように置き換えました。
@State private var magnifyBy = 1.0
@State private var lastMagnificationValue = 1.0
var magnification: some Gesture {
MagnificationGesture()
.onChanged { value in
// 前回の拡大率に対して今回の拡大率の割合を計算
let changeRate = value / lastMagnificationValue
// 前回からの拡大率の変更割合分を乗算する
magnifyBy *= changeRate
// 前回の拡大率を今回の拡大率で更新
lastMagnificationValue = value
}
.onEnded { value in
// 次回のジェスチャー時に1.0から始まる為、終了時に1.0に変更する
lastMagnificationValue = 1.0
}
}
ジェスチャーの実装について説明していきたいと思います。
前回の拡大率に対して今回の拡大率の割合を計算
今回の拡大率value
が前回の拡大率に対してどのくらい変化したかの割合を出しています。
let changeRate = value / lastMagnificationValue
前回からの拡大率の変更割合分を乗算する
前回からの変更割合分をmagnifyBy
に乗算します。
magnifyBy *= changeRate
前回の拡大率を今回の拡大率で更新
変化率の割合を計算する為に使用しているlastMagnificationValue
を今回のMagnificationGesture.Value
の値で更新します。
lastMagnificationValue = value
ジェスチャー終了時に前回の拡大率を初期化
ジェスチャーが終了した時に呼ばれるonEnded
の実装になります。
次回のジェスチャー開始時にはMagnificationGesture.Value
が1.0
から始まる為、lastMagnificationValue
の値も1.0
にしておきます。
.onEnded { value in
// 次回のジェスチャー時に1.0から始まる為、終了時に1.0に変更する
lastMagnificationValue = 1.0
}
これでピンチイン・ピンチアウトを行なった際に拡大率を維持したまま、表現することが出来るようになりました。
おまけ
作ったもので載せているデモでは、Apple公式から参考に変更していったコードだと拡大する対象がただの黒丸で味気なかったので、Apple標準で用意してくれているPlayStationのロゴを使っています。
実際のアプリでこのシンボルを使用する時は、PlayStationに関連する時にしか使用出来ず、 ロゴに変更は加えることはできないという使用制限があるのでご注意下さい。
var body: some View {
Image(systemName: "logo.playstation")
.resizable()
.frame(width: 100, height: 77.5)
.scaleEffect(scaleValue)
.gesture(magnification)
}
おわりに
PS5のキャッチコピーを調べたら、PLAY HAS NO LIMITSと出てきて、とてもかっこいいと思いました。
私もYATTEMITA HAS NO LIMITSの精神で邁進していきたいと思います。