问题描述
我正在为MacOS开发一个SwiftUI应用程序,我想通过检测用户手指的位置将触控板用作(x,y)输入.我想能够检测到在触控板上静止的多个手指(而不是拖动).我该怎么办?
步骤1-AppKit
- SwiftUI并未提供所有必需的信息
- AppKit&
NSTouch
可以-normalizedPosition
.
第一步是创建一个简单的 AppKitTouchesView
通过委托转发所需的触摸.
import SwiftUI导入AppKit协议AppKitTouchesViewDelegate:AnyObject {//仅提供`.touching`触摸.func touchesView(_视图:AppKitTouchesView,didUpdateTouchingTouches触摸:Set< NSTouch>)}最后一班AppKitTouchesView:NSView {弱var委托:AppKitTouchesViewDelegate?覆盖init(frame frameRect:NSRect){super.init(frame:frameRect)//我们只对`.indirect`接触感兴趣.allowedTouchTypes = [.indirect]//我们也希望获得休息.wantRestingTouches = true}需要初始化吗?(编码器:NSCoder){fatalError("init(coder :)尚未实现")}私人函式handleTouches(事件:NSEvent){//仅获取所有`.touching`触摸(包括`.began`,`.moved`和`.stationary`).let touches = event.touches(matching:.touching,in:self)//通过委托转发它们.委托?.touchesView(自己,didUpdateTouchingTouches:触摸)}覆盖func touchesBegan(带有事件:NSEvent){handleTouches(with:事件)}覆盖func touchesEnded(带有事件:NSEvent){handleTouches(with:事件)}覆盖func touchesMoved(带有事件:NSEvent){handleTouches(with:事件)}覆盖func touchesCancelled(带有事件:NSEvent){handleTouches(with:事件)}}
步骤2-简化的触摸结构
第二步是创建一个简单的自定义 Touch
结构,该结构仅包含所有必需的信息,并且与SwiftUI兼容(不翻转 y
).
struct Touch:可识别的{//`Identifiable`->"ForEach"必须使用"id"(请参见下文).let id:整数//装置上的标准化触控X位置(0.0-1.0).让normalizedX:CGFloat//装置上的标准化触控Y位置(0.0-1.0).让归一化Y:CGFloatinit(_ nsTouch:NSTouch){self.normalizedX = nsTouch.normalizedPosition.x//将`NSTouch.normalizedPosition.y`翻转->0.0表示底部.但是//`Touch`结构旨在与SwiftUI一起使用->翻转.self.normalizedY = 1.0-nsTouch.normalizedPosition.yself.id = nsTouch.hash}}
第3步-将其包装为SwiftUI
-
NSViewRepresentable
文档 -
Binding
文档
第三步是创建一个包裹我们的AppKit AppKitTouchesView
视图的SwiftUI视图.
struct TouchesView:NSViewRepresentable {//最新的触摸列表.@Binding var触摸:[触摸]func updateNSView(_ nsView:AppKitTouchesView,context:Context){}func makeNSView(context:Context)->AppKitTouchesView {让视图= AppKitTouchesView()view.delegate = context.coordinator返回视图}func makeCoordinator()->协调员{协调员(个体经营)}类协调器:NSObject,AppKitTouchesViewDelegate {让父母:TouchesViewinit(_ view:TouchesView){self.parent =视图}func touchesView(_视图:AppKitTouchesView,didUpdateTouchingTouches触摸:Set< NSTouch>){parent.touches = touches.map(Touch.init)}}}
第4步-创建 TrackPadView
第四步是创建一个 TrackPadView
,它在内部确实使用了 TouchesView
并在其上绘制表示手指物理位置的圆圈.
struct TrackPadView:视图{私人让touchViewSize:CGFloat = 20@State var触摸:[触摸] = []var body:some View {ZStack {GeometryReader {代理在TouchesView(touchs:self.$ touches)ForEach(self.touches){圆圈().foregroundColor(Color.green).frame(宽度:self.touchViewSize,高度:self.touchViewSize).抵消(x:proxy.size.width * touch.normalizedX-self.touchViewSize/2.0,y:proxy.size.height * touch.normalizedY-self.touchViewSize/2.0)}}}}}
第5步-在主 ContentView
中使用它第五步是在主视图中以接近真实触控板纵横比的纵横比使用它.
struct ContentView:视图{var body:some View {TrackPadView().background(颜色.灰色).aspectRatio(1.6,contentMode:.fit).填充().frame(maxWidth:.infinity,maxHeight:.infinity)}}
完成项目
- 打开Xcode
- 创建一个新项目(macOS App& Swift& SwiftUI)
- 复制并复制从此要点 粘贴
ContentView.swift
I'm making a SwiftUI app for macOS and I'd like to use the trackpad as an (x, y) input by detecting the position of the user's fingers. I wanna be able to detect multiple fingers that are resting on the trackpad (not dragging). How do I do that?
A similar question has been asked before, but I'm asking again because that was from nearly 10 years ago, the answers are all in Obj-C (one in Swift 3), and I'm wondering if there's an updated methodology. Most importantly, I've no clue how to implement the Obj-C code into my SwiftUI app so if there isn't any updated methodology, I'd appreciate if someone could just explain how to implement the old Obj-C code.
To demonstrate what I mean, this video demo of the AudioSwift app does exactly what I want. macOS itself also uses this for hand-writing Chinese (although I don't need to recognize characters).
Always split your task into smaller ones and do them one by one. Ask in the same way and avoid broad questions touching lot of topics.
Goal
- Track pad view (gray rectangle)
- Circles on top of it showing fingers physical position
Step 1 - AppKit
- SwiftUI doesn't provide all the required information
- AppKit &
NSTouch
does -normalizedPosition
.
First step is to create a simple AppKitTouchesView
forwarding required touches via a delegate.
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)
}
}
Step 2 - Simplified touch structure
Second step is to create a simple custom Touch
structure which holds all the required information only and is SwiftUI compatible (not flipped 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
}
}
Step 3 - Wrap it for the SwiftUI
NSViewRepresentable
documentationBinding
documentation
Third step is to create a SwiftUI view wrapping our AppKit AppKitTouchesView
view.
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)
}
}
}
Step 4 - Make a TrackPadView
Fourth step is to create a TrackPadView
which internally does use ourTouchesView
and draws circles on it representing physical location of fingers.
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
)
}
}
}
}
}
Step 5 - Use it in the main ContentView
Fifth step is to use it in our main view with some aspect ratio which is close to the real trackpad aspect ratio.
struct ContentView: View {
var body: some View {
TrackPadView()
.background(Color.gray)
.aspectRatio(1.6, contentMode: .fit)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Complete project
- Open Xcode
- Create a new project (macOS App & Swift & SwiftUI)
- Copy & paste
ContentView.swift
from this gist
这篇关于SwiftUI:在Mac触控板上检测手指位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!