【SwiftUI】ピンチによるViewの拡大と縮小
概要
SwiftUIを使ってピンチイン・ピンチアウトによってView(画像に関わらずViewコンポーネント)を拡大・縮小する方法について記載します。
環境
- Swift: 5.2.4
- Xcode: 11.6
サンプル環境
struct ContentView: View { var body: some View { ZStack { Circle() .foregroundColor(Color.blue) .frame(width: 200, height: 200) } } }
今回は、画面に円を一つ配置し、この円をピンチジェスチャーで拡大/縮小します。
ピンチジェスチャーの取得
SwiftUIのViewコンポーネントには、gesture
というModifierが用意されています。
このgestureというModifierを使うとタップやドラッグ、ピンチといったジェスチャーを簡単にトラッキングすることができます。
struct ContentView: View { var body: some View { ZStack { Circle() .foregroundColor(Color.blue) .frame(width: 200, height: 200) .scaleEffect(self.scale) .gesture(MagnificationGesture() .onChanged { value in print("onChanged: ", value) } .onEnded { value in print("onEnded: ", value) } ) } } }
ピンチのジェスチャーを取得するには、 MagnificationGesture()
を gesture
Modifierに渡します。
Gestureコンポーネントには、「値の変更」をコールバックする onChanged
、「イベントの完了」をコールバックする onEnded
などイベント検知のModifierが用意されており、これらをセットすることによりジェスチャーの値を取得できます。
MagnificationGestureを使用した場合、onChanged、onEndedに渡される value
には、現在の拡大倍率を基準とした拡大率(縮小率)がCGFloat型で取得できます。
Viewを拡大・縮小する
struct ContentView: View { @State var scale: CGFloat = 1.0 var body: some View { ZStack { Circle() .foregroundColor(Color.blue) .frame(width: 200, height: 200) .scaleEffect(self.scale) .gesture(MagnificationGesture() .onChanged { value in self.scale = value } .onEnded { value in print("onEnded: ", value) } ) } } }
拡大率は取得できたのでこれを使用して、円のView自体をピンチジェスチャーに合わせて拡大・縮小します。
Viewの拡大・縮小を行うには、scaleEffect
というModifierを使います。scaleEffectに元の大きさを1.0とした拡大率をセットすることでViewを拡大(縮小)することができます。
先程記述した、MagnificationGestureのonChanged
で変更倍率を受け取り、@Stateで宣言したscale
変数に値をセットします。
そして、scale
変数をscaleEffect
にセットすることで、ピンチジェスチャーの倍率変更に合わせてViewを拡大(縮小)することができます。
変更倍率を保持する
上記までの処理を動かすと一見上手く動いているように見えますが、今は ピンチジェスチャーを行うたびに画像の大きさが1.0倍に一旦戻ってしまう
という状態になっています。
これは、onChanged
, onEnded
で取得できる value
が 「現在のサイズからの拡大率」であるためです。
(元画像から2倍に拡大されていても、ピンチジェスチャーが一度終わっていれば次のピンチジェスチャーでは1.0から始まる)
これだと使いにくいため、ピンチジェスチャーが複数回行われても、元画像の拡大率を維持した状態になるように変更します。
struct ContentView: View { @State var lastValue: CGFloat = 1.0 @State var scale: CGFloat = 1.0 var body: some View { ZStack { Circle() .foregroundColor(Color.blue) .frame(width: 200, height: 200) .scaleEffect(self.scale) .gesture(MagnificationGesture() .onChanged { value in // 前回の値から拡大率を計算する let delta = value / self.lastValue // 現在の拡大率を再設定する self.scale = self.scale * delta // 現在の拡大率を覚えておく self.lastValue = value } .onEnded { value in // 次のジェスチャーイベントではvalueはまた1.0から始まるため // ジェスチャーイベント完了時に1.0に戻しておく self.lastValue = 1.0 } ) } } }
上記が変更後のコードです。
onChange
で valueをそのままscale
にセットするのではなく、(同一ジェスチャーイベントでの)前回のvalue(lastValue
)との拡大率の変更を計算してscaleにセットしています。
またジェスチャーイベントで取得できるvalueは、毎回1.0から始まるため、onEnded
で lastValue
変数を1.0に初期化しています。
このようにすることで、拡大率をリセットさせずにピンチジェスチャーによる拡大・縮小を行えます。