【SwiftUI】ドラッグでImageの位置を移動する

2022.03.29

ドラッグで画面に配置した顔のパーツ達を移動したくなってきたので移動してみることにしました。

作ったもの

顔のパーツをドラッグすると位置を移動することができ、ドラッグを終了すると終了した位置に顔のパーツが配置されます。

環境

  • Xcode 13.3

ドラッグで移動出来るViewを作成する

DraggableImageというViewを作成しました。

import SwiftUI

struct DraggableImage: View {

    @State private var location: CGPoint
    @State private var isDragging = false
    private let systemName: String

    init(_ systemName: String,
         defaultLocation: CGPoint) {
        self.systemName = systemName
        self.location = defaultLocation
    }

    /// Drag Gesture
    var dragGesture: some Gesture {
        DragGesture()
            .onChanged { value in
                location = value.location
                isDragging = true
            }
            .onEnded { _ in
                isDragging = false
            }
    }

    var body: some View {
        Image(systemName: systemName)
            .resizable()
            .scaledToFit()
            .foregroundColor(isDragging ? .blue : .black)
            .frame(width: 100)
            .position(location)
            .gesture(dragGesture)
    }
}

DragGesture

ドラッグジェスチャーが行われた際、任意の処理を実行する為にDragGesture()を使用します。

/// Drag Gesture
var dragGesture: some Gesture {
    DragGesture()
        .onChanged { value in
            location = value.location
            isDragging = true
        }
        .onEnded { _ in
            isDragging = false
        }
}
  • onChanged
    • ドラッグジェスチャーが行われて、value: DragGesture.Valueが変更される度に実行される処理
  • onEnded
    • ドラッグジェスチャーが終了した際に実行される処理

今回はonChangeの際に、DragGesture.Valueから変更後のlocationを取得出来るので、Imagelocationに変更後の値を代入しています。

DragGesture.Value

今回はlocationしか使っていませんが、DragGesture.Valueのプロパティは他にもあります。

  • location: CGPoint
    • 現在のドラッグジェスチャーイベントのロケーション
  • predictedEndLocation: CGPoint
    • 現在のドラッグスピードに基づき、もし今ドラッグが停止したらこの位置になるであろうと予測されたロケーション
  • predictedEndTranslation: CGSize
    • 現在のドラッグスピードに基づき、もし今ドラッグが停止したらこのくらい移動するであろうと予測された移動量
  • startLocation: CGPoint
    • ドラッグジェスチャーが開始された最初のイベント時点でのロケーション
  • time: Date
    • 現在のドラッグジェスチャーイベントに基づく、時間
  • translation: CGSize
    • ドラッグジェスチャーの開始地点から現在のイベントの地点までの合計移動量

ImageにGestureとlocationを反映する

var body: some View {
    Image(systemName: imageName)
        .resizable()
        .scaledToFit()
        .foregroundColor(self.isDragging ? Color.blue : Color.black)
        .frame(width: 100)
    // Drag gestureでロケーションが変更されると反映される
        .position(location)
    // Drag gestureをアタッチ
        .gesture(dragGesture)
    }

Image.position@Statelocationを渡して、ドラッグジェスチャーでロケーションが変わる毎に反映されるようにしています。

@State private var location: CGPoint

ContentViewにDraggableViewを配置する

FaceParts

まずは顔のパーツの表現する為にFacePartsというenumを用意しました。

enum FaceParts: String, Identifiable, CaseIterable {

    case leftEye
    case rightEye
    case nose
    case mouth

    var id: String { rawValue }

    // ImageのsystemName
    var imageName: String {
        switch self {
        case .leftEye, .rightEye:
            return "eye"
        case .nose:
            return "nose"
        case .mouth:
            return "mouth"
        }
    }

    // Positionの初期値
    var defaultPosition: CGPoint {
        switch self {
        case .leftEye:
            return CGPoint(x: 120, y: 200)
        case .rightEye:
            return CGPoint(x: 260, y: 200)
        case .nose:
            return CGPoint(x: 190, y: 300)
        case .mouth:
            return CGPoint(x: 190, y: 430)
        }
    }
}

ContentView

用意したFacePartsForEachで各顔のパーツを描画しています。

import SwiftUI

struct ContentView: View {

    var body: some View {
        ZStack {
            Rectangle()
                .ignoresSafeArea()
                .foregroundColor(.paleOrange)

            ForEach(FaceParts.allCases) {
                DraggableImage($0.imageName,
                               defaultLocation: $0.defaultPosition)
            }
        }
    }
}

Color.paleOrange

今回は背景として使用しているRactangle()の色をColorextensionで作成したpaleOrangeにしました。

extension Color {
    static let paleOrange = Color(red: 245 / 255,
                                  green: 218 / 255,
                                  blue: 195 / 255,
                                  opacity: 1)
}

以上で完成です。

おわりに

SwiftUIでのドラッグジェスチャーを調査する為に作ってみましたが、もう少し一工夫すれば子どもに喜んでもらえそうな何かが作れそうな気がしました。

もう少し遊びながら調べていきたいと思います。

参考