SwiftUI。检测Mac触控板上的手指位置

问题描述 投票:4回答:1

我正在为macOS制作一个SwiftUI应用,我想通过检测用户手指的位置来使用触控板作为(x,y)输入。我希望能够检测到多个手指的位置。休息 在触控板上(不是拖动)。我该怎么做?

类似的问题 以前有人问过这个问题,但我再次问,因为那是近10年前的问题,答案都是用Obj-C的(一个是Swift 3的),我想知道是否有更新的方法论。最重要的是,我不知道如何将Obj-C代码实现到我的SwiftUI应用中,所以如果没有任何更新的方法论,如果有人能解释一下如何实现旧的Obj-C代码,我会很感激。

为了证明我的意思,这个 视频演示 的AudioSwift应用完全满足了我的要求。手写汉字 虽然我不需要识别字符)。

swift macos cocoa swiftui appkit
1个回答
5
投票

一定要把你的任务分割成更小的任务,并逐一完成。用同样的方式提问,避免涉及很多主题的宽泛问题。

目标

  • 轨道垫视图(灰色矩形
  • 上面的圆圈显示手指的物理位置。

enter image description here

步骤1 - AppKit

第一步是创建一个简单的 AppKitTouchesView 通过委托人转发所需的触摸。

import SwiftUI
import AppKit

protocol AppKitTouchesViewDelegate: AnyObject {
    // Provides `.touching` touches only.
    func touchesView(_ view: AppKitTouchesView, didUpdateTouchingTouches touches: Set<NSTouch>)
}

final class AppKitTouchesView: NSView {
    weak var delegate: AppKitTouchesViewDelegate?

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        // We're interested in `.indirect` touches only.
        allowedTouchTypes = [.indirect]
        // We'd like to receive resting touches as well.
        wantsRestingTouches = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func handleTouches(with event: NSEvent) {
        // Get all `.touching` touches only (includes `.began`, `.moved` & `.stationary`).
        let touches = event.touches(matching: .touching, in: self)
        // Forward them via delegate.
        delegate?.touchesView(self, didUpdateTouchingTouches: touches)
    }

    override func touchesBegan(with event: NSEvent) {
        handleTouches(with: event)
    }

    override func touchesEnded(with event: NSEvent) {
        handleTouches(with: event)
    }

    override func touchesMoved(with event: NSEvent) {
        handleTouches(with: event)
    }

    override func touchesCancelled(with event: NSEvent) {
        handleTouches(with: event)
    }
}

步骤2 - 简化触摸结构

第二步是创建一个简单的自定义 Touch 结构,它只保存所有需要的信息,并且与SwiftUI兼容(不是翻转的)。y).

struct Touch: Identifiable {
    // `Identifiable` -> `id` is required for `ForEach` (see below).
    let id: Int
    // Normalized touch X position on a device (0.0 - 1.0).
    let normalizedX: CGFloat
    // Normalized touch Y position on a device (0.0 - 1.0).
    let normalizedY: CGFloat

    init(_ nsTouch: NSTouch) {
        self.normalizedX = nsTouch.normalizedPosition.x
        // `NSTouch.normalizedPosition.y` is flipped -> 0.0 means bottom. But the
        // `Touch` structure is meants to be used with the SwiftUI -> flip it.
        self.normalizedY = 1.0 - nsTouch.normalizedPosition.y
        self.id = nsTouch.hash
    }
}

第3步--为SwiftUI包起来

第三步是创建一个包裹我们AppKit的SwiftUI视图。AppKitTouchesView 观点。

struct TouchesView: NSViewRepresentable {
    // Up to date list of touching touches.
    @Binding var touches: [Touch]

    func updateNSView(_ nsView: AppKitTouchesView, context: Context) {
    }

    func makeNSView(context: Context) -> AppKitTouchesView {
        let view = AppKitTouchesView()
        view.delegate = context.coordinator
        return view
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, AppKitTouchesViewDelegate {
        let parent: TouchesView

        init(_ view: TouchesView) {
            self.parent = view
        }

        func touchesView(_ view: AppKitTouchesView, didUpdateTouchingTouches touches: Set<NSTouch>) {
            parent.touches = touches.map(Touch.init)
        }
    }
}

步骤4 - 制作 TrackPadView

第四步是建立一个 TrackPadView 内部使用我们的TouchesView 并在上面画圆圈代表手指的物理位置。

struct TrackPadView: View {
    private let touchViewSize: CGFloat = 20

    @State var touches: [Touch] = []

    var body: some View {
        ZStack {
            GeometryReader { proxy in
                TouchesView(touches: self.$touches)

                ForEach(self.touches) { touch in
                    Circle()
                        .foregroundColor(Color.green)
                        .frame(width: self.touchViewSize, height: self.touchViewSize)
                        .offset(
                            x: proxy.size.width * touch.normalizedX - self.touchViewSize / 2.0,
                            y: proxy.size.height * touch.normalizedY - self.touchViewSize / 2.0
                        )
                }
            }
        }
    }
}

第5步--在主界面使用 ContentView

第五步是在我们的主视图中使用它,并使用一些接近真实的触控板长宽比。

struct ContentView: View {
    var body: some View {
        TrackPadView()
            .background(Color.gray)
            .aspectRatio(1.6, contentMode: .fit)
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

完成项目

  • 打开Xcode
  • 创建一个新的项目(macOS App & Swift & SwiftUI)
  • 复制& 粘贴 ContentView.swift此要旨
© www.soinside.com 2019 - 2024. All rights reserved.