我正在为macOS制作一个SwiftUI应用,我想通过检测用户手指的位置来使用触控板作为(x,y)输入。我希望能够检测到多个手指的位置。休息 在触控板上(不是拖动)。我该怎么做?
类似的问题 以前有人问过这个问题,但我再次问,因为那是近10年前的问题,答案都是用Obj-C的(一个是Swift 3的),我想知道是否有更新的方法论。最重要的是,我不知道如何将Obj-C代码实现到我的SwiftUI应用中,所以如果没有任何更新的方法论,如果有人能解释一下如何实现旧的Obj-C代码,我会很感激。
一定要把你的任务分割成更小的任务,并逐一完成。用同样的方式提问,避免涉及很多主题的宽泛问题。
NSTouch
does - normalizedPosition
.第一步是创建一个简单的 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)
}
}
第二步是创建一个简单的自定义 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
}
}
第三步是创建一个包裹我们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)
}
}
}
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
)
}
}
}
}
}
ContentView
第五步是在我们的主视图中使用它,并使用一些接近真实的触控板长宽比。
struct ContentView: View {
var body: some View {
TrackPadView()
.background(Color.gray)
.aspectRatio(1.6, contentMode: .fit)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
ContentView.swift
从 此要旨