100 Days of SwiftUI(DAY 86(Project17 part 1))

SwiftUIでジェスチャーを使用する方法
How to use gestures in SwiftUI

gesture sequencing
ジェスチャシーケンス

DragGesture
LongPressGesture(プレスの最小継続時間を指定して、特定の秒数が経過した後にのみアクションクロージャがトリガーされる minimumDuration: 2)
onTapGesture(count、これらにパラメーターを渡して、ダブルタップ、トリプルタップを処理できる)
highPriorityGesture
simultaneousGesture
RotationGesture
MagnificationGesture

DragGesture、LongPressGesture、MagnificationGesture、RotationGesture、とTapGesture。これらには通常onEnded()、多くの場合特別な修飾子があり、onChanged()ジェスチャーが処理中(の場合onChanged())または完了した場合(の場合onEnded())にアクションを実行できます。

変更ボタンを押すとすぐに、パラメーターがtrueに設定された状態で変更クロージャーが呼び出されます。
ジェスチャが認識される前に離すと(つまり、2秒の認識機能を使用して1秒後に離すと)、変更クロージャが呼び出され、パラメータがfalseに設定されます。
レコグナイザの全長を押し続けると、パラメータがfalseに設定された状態で変更クロージャが呼び出され(ジェスチャが実行されなくなったため)、完了クロージャも呼び出されます。

Text("Hello, World!")
    .onLongPressGesture(minimumDuration: 1, pressing: { inProgress in
        print("In progress: \(inProgress)!")
    }) {
        print("Long pressed!")
    }

import SwiftUI

struct ContentView: View {
    // how far the circle has been dragged
    @State private var offset = CGSize.zero

    // whether it is currently being dragged or not
    @State private var isDragging = false

    var body: some View {
        // a drag gesture that updates offset and isDragging as it moves around
        let dragGesture = DragGesture()
            .onChanged { value in self.offset = value.translation }
            .onEnded { _ in
                withAnimation {
                    self.offset = .zero
                    self.isDragging = false
                }
            }

        // a long press gesture that enables isDragging
        let pressGesture = LongPressGesture()
            .onEnded { value in
                withAnimation {
                    self.isDragging = true
                }
            }

        // a combined gesture that forces the user to long press then drag
        let combined = pressGesture.sequenced(before: dragGesture)

        // a 64x64 circle that scales up when it's dragged, sets its offset to whatever we had back from the drag gesture, and uses our combined gesture
        return Circle()
            .fill(Color.red)
            .frame(width: 64, height: 64)
            .scaleEffect(isDragging ? 1.5 : 1)
            .offset(offset)
            .gesture(combined)
    }
}
/*
struct ContentView: View {
    var body: some View {
//        Text("Hello, World!")
//        .onTapGesture(count: 2) {
//            print("Double tapped!")
//        }
        
//        Text("Hello, World!")
//        .onLongPressGesture {
//            print("Long pressed!")
//        }
        
//        Text("Hello, World!")
//        .onLongPressGesture(minimumDuration: 2) {
//            print("Long pressed!")
//        }
        
        Text("Hello, World!")
        .onLongPressGesture(minimumDuration: 1, pressing: { inProgress in
            print("In progress: \(inProgress)!")
        }) {
            print("Long pressed!")
        }
    }
}
 */

UINotificationFeedbackGeneratorとコアハプティクスを使用して振動を作成する
Making vibrations with UINotificationFeedbackGenerator and Core Haptics

Core Haptics
「ハプティックス」はデバイスに小さなモーターを使用して、タップや振動などの感覚を作り出します。

import SwiftUI
import CoreHaptics


struct ContentView: View {
    @State private var engine: CHHapticEngine?
    //--------------------
    func prepareHaptics() {
        guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

        do {
            self.engine = try CHHapticEngine()
            try engine?.start()
        } catch {
            print("There was an error creating the engine: \(error.localizedDescription)")
        }
    }
    //--------------------
    func complexSuccess() {
        // make sure that the device supports haptics
        guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
        var events = [CHHapticEvent]()

        // create one intense, sharp tap
//        let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
//        let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1)
//        let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: 0)
//events.append(event)

        for i in stride(from: 0, to: 1, by: 0.1) {
            let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(1 - i))
            let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: Float(1 - i))
            let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: 1 + i)
            events.append(event)
        }

        // convert those events into a pattern and play it immediately
        do {
            let pattern = try CHHapticPattern(events: events, parameters: [])
            let player = try engine?.makePlayer(with: pattern)
            try player?.start(atTime: 0)
        } catch {
            print("Failed to play pattern: \(error.localizedDescription).")
        }
    }
    //--------------------
    func simpleSuccess() {
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(.error)
    }
    //--------------------
    var body: some View {
//        Text("Hello, World!")
//            .onTapGesture(perform: simpleSuccess)
        Text("Hello, World!")
        .onAppear(perform: prepareHaptics)
        .onTapGesture(perform: complexSuccess)
    }
}

allowHitTesting()を使用したユーザー対話性の無効化
Disabling user interactivity with allowsHitTesting()

allowsHitTesting
contentShape(Rectangle())

SwiftUIには、ビューのフレームとそのコンテンツの両方を使用する高度なヒットテストアルゴリズムがあります。たとえば、テキストビューにタップジェスチャーを追加すると、テキストビューのすべての部分がタップ可能になります。スペースがある場所を正確に押すと、テキストをタップできません。一方、同じジェスチャーを円にアタッチすると、SwiftUI は円の透明部分を無視します。


import SwiftUI

struct ContentView: View {

    //--------------------
    var body: some View {
        VStack {
            Text("Hello")
            Spacer().frame(height: 100)
            Text("World")
        }
        //.contentShape(Rectangle())
        .onTapGesture {
            print("VStack tapped!")
        }
        
        /*
        Circle()
        .fill(Color.red)
        .frame(width: 300, height: 300)
        .contentShape(Rectangle())
        .onTapGesture {
            print("Circle tapped!")
        }
        */
        /*
        ZStack {
            Rectangle()
                .fill(Color.blue)
                .frame(width: 300, height: 300)
                .onTapGesture {
                    print("Rectangle tapped!")
                }

            Circle()
                .fill(Color.red)
                .frame(width: 300, height: 300)
                .onTapGesture {
                    print("Circle tapped!")
                }
                .allowsHitTesting(false)
        }
         */
    }
}