Anonymous
NSPanGesture не работает на AppKit, но UIPanGesture работает на iOS (мультиплатформенное приложение)
Сообщение
Anonymous » 31 май 2024, 20:35
Я нашел старую библиотеку SwiftUI, которую пытался портировать на MacOS. Полный репозиторий можно найти здесь:
https://github.com/cyrilzakka/SlidingRuler . По сути, это скользящая линейка с привязкой тикера. Скриншот доступен ниже:
[![введите описание изображения здесь][1]][1]
В настоящее время код отлично работает на iOS, и я могу перетаскивать ползунок влево и вправо для изменения значений. В macOS (AppKit), хотя пользовательский интерфейс выглядит хорошо, перетаскивание ползунка, похоже, не работает.
Вот HorizontalPanGesture.swift
Код: Выделить всё
import SwiftUI
import CoreGeometry
#if canImport(UIKit)
struct HorizontalDragGestureValue {
let state: UIGestureRecognizer.State
let translation: CGSize
let velocity: CGFloat
let startLocation: CGPoint
let location: CGPoint
}
protocol HorizontalPanGestureReceiverViewDelegate: AnyObject {
func viewTouchedWithoutPan(_ view: UIView)
}
class HorizontalPanGestureReceiverView: UIView {
weak var delegate: HorizontalPanGestureReceiverViewDelegate?
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
delegate?.viewTouchedWithoutPan(self)
}
}
extension View {
func onHorizontalDragGesture(initialTouch: @escaping () -> () = { },
prematureEnd: @escaping () -> () = { },
perform action: @escaping (HorizontalDragGestureValue) -> ()) -> some View {
self.overlay(HorizontalPanGesture(beginTouch: initialTouch, prematureEnd: prematureEnd, action: action))
}
}
private struct HorizontalPanGesture: UIViewRepresentable {
typealias Action = (HorizontalDragGestureValue) -> ()
class Coordinator: NSObject, UIGestureRecognizerDelegate, HorizontalPanGestureReceiverViewDelegate {
private let beginTouch: () -> ()
private let prematureEnd: () -> ()
private let action: Action
weak var view: UIView?
init(_ beginTouch: @escaping () -> () = { }, _ prematureEnd: @escaping () -> () = { }, _ action: @escaping Action) {
self.beginTouch = beginTouch
self.prematureEnd = prematureEnd
self.action = action
}
@objc func panGestureHandler(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
let velocity = gesture.velocity(in: view)
let location = gesture.location(in: view)
let startLocation = location - translation
let value = HorizontalDragGestureValue(state: gesture.state,
translation: .init(horizontal: translation.x),
velocity: velocity.x,
startLocation: startLocation,
location: location)
self.action(value)
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let pgr = gestureRecognizer as? UIPanGestureRecognizer else { return false }
let velocity = pgr.velocity(in: view)
return abs(velocity.x) > abs(velocity.y)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive event: UIEvent) -> Bool {
beginTouch()
return true
}
func viewTouchedWithoutPan(_ view: UIView) {
prematureEnd()
}
}
@Environment(\.slidingRulerStyle) private var style
let beginTouch: () -> ()
let prematureEnd: () -> ()
let action: Action
func makeCoordinator() -> Coordinator {
.init(beginTouch, prematureEnd, action)
}
func makeUIView(context: Context) -> UIView {
let view = HorizontalPanGestureReceiverView(frame: .init(size: .init(square: 42)))
let pgr = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.panGestureHandler(_:)))
view.delegate = context.coordinator
pgr.delegate = context.coordinator
view.addGestureRecognizer(pgr)
context.coordinator.view = view
// Pointer interactions
if #available(iOS 13.4, *), style.supportsPointerInteraction {
pgr.allowedScrollTypesMask = .continuous
view.addInteraction(UIPointerInteraction(delegate: context.coordinator))
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) { }
}
@available(iOS 13.4, *)
extension HorizontalPanGesture.Coordinator: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
.init(shape: .path(Pointers.standard), constrainedAxes: .vertical)
}
}
#elseif canImport(AppKit)
struct HorizontalDragGestureValue {
let state: NSGestureRecognizer.State
let translation: CGSize
let velocity: CGFloat
let startLocation: CGPoint
let location: CGPoint
}
protocol HorizontalPanGestureReceiverViewDelegate: AnyObject {
func viewTouchedWithoutPan(_ view: NSView)
}
class HorizontalPanGestureReceiverView: NSView {
weak var delegate: HorizontalPanGestureReceiverViewDelegate?
override func touchesEnded(with event: NSEvent) {
super.touchesEnded(with: event)
delegate?.viewTouchedWithoutPan(self)
}
}
extension View {
func onHorizontalDragGesture(initialTouch: @escaping () -> () = { },
prematureEnd: @escaping () -> () = { },
perform action: @escaping (HorizontalDragGestureValue) -> ()) -> some View {
self.overlay(HorizontalPanGesture(beginTouch: initialTouch, prematureEnd: prematureEnd, action: action))
}
}
private struct HorizontalPanGesture: NSViewRepresentable {
typealias Action = (HorizontalDragGestureValue) -> ()
class Coordinator: NSObject, NSGestureRecognizerDelegate, HorizontalPanGestureReceiverViewDelegate {
private let beginTouch: () -> ()
private let prematureEnd: () -> ()
private let action: Action
weak var view: NSView?
init(_ beginTouch: @escaping () -> () = { }, _ prematureEnd: @escaping () -> () = { }, _ action: @escaping Action) {
self.beginTouch = beginTouch
self.prematureEnd = prematureEnd
self.action = action
}
@objc func panGestureHandler(_ gesture: NSPanGestureRecognizer) {
print("PanGesture handler called")
let translation = gesture.translation(in: view)
let velocity = gesture.velocity(in: view)
let location = gesture.location(in: view)
let startLocation = location - translation
let value = HorizontalDragGestureValue(state: gesture.state,
translation: .init(horizontal: translation.x),
velocity: velocity.x,
startLocation: startLocation,
location: location)
self.action(value)
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: NSGestureRecognizer) -> Bool {
guard let pgr = gestureRecognizer as? NSPanGestureRecognizer else { return false }
let velocity = pgr.velocity(in: view)
return abs(velocity.x) > abs(velocity.y)
}
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldReceive touch: NSTouch) -> Bool {
beginTouch()
return true
}
func viewTouchedWithoutPan(_ view: NSView) {
prematureEnd()
}
}
@Environment(\.slidingRulerStyle) private var style
let beginTouch: () -> ()
let prematureEnd: () -> ()
let action: Action
func makeCoordinator() -> Coordinator {
.init(beginTouch, prematureEnd, action)
}
func makeNSView(context: Context) -> some NSView {
let view = HorizontalPanGestureReceiverView(frame: .init(size: .init(square: 42)))
let pgr = NSPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.panGestureHandler(_:)))
view.delegate = context.coordinator
pgr.delegate = context.coordinator
view.addGestureRecognizer(pgr)
context.coordinator.view = view
return view
}
func updateNSView(_ nsView: NSViewType, context: Context) { }
}
#endif
и как он используется:
Код: Выделить всё
public var body: some View {
let renderedValue: CGFloat, renderedOffset: CGSize
(renderedValue, renderedOffset) = renderingValues()
return FlexibleWidthContainer {
ZStack(alignment: .init(horizontal: .center, vertical: self.verticalCursorAlignment)) {
Ruler(cells: self.cells, step: self.step, markOffset: self.markOffset, bounds: self.bounds, formatter: self.formatter)
.equatable()
.animation(nil)
.modifier(InfiniteOffsetEffect(offset: renderedOffset, maxOffset: self.cellWidthOverflow))
self.style.makeCursorBody()
}
}
.modifier(InfiniteMarkOffsetModifier(renderedValue, step: step))
.propagateWidth(ControlWidthPreferenceKey.self)
.onPreferenceChange(MarkOffsetPreferenceKey.self, storeValueIn: $markOffset)
.onPreferenceChange(ControlWidthPreferenceKey.self, storeValueIn: $controlWidth) {
self.updateCellsIfNeeded()
}
.transaction {
if $0.animation != nil { $0.animation = .easeIn(duration: 0.1) }
}
.onHorizontalDragGesture(initialTouch: firstTouchHappened,
prematureEnd: panGestureEndedPrematurely,
perform: horizontalDragAction(withValue:))
}
Кажется, я не могу найти, где я могу пойти не так.
Единственной другой потенциально (но менее вероятной) проблемной частью может быть CADisplayLink , который мне пришлось реализовать в macOS по-другому:
Код: Выделить всё
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
import CoreVideo
#endif
struct VSynchedTimer {
typealias Animations = (TimeInterval, TimeInterval) -> ()
typealias Completion = (Bool) -> ()
private let timer: SynchedTimer
init(duration: TimeInterval, animations: @escaping Animations, completion: Completion? = nil) {
self.timer = .init(duration, animations, completion)
}
func cancel() {
timer.cancel()
}
}
private final class SynchedTimer {
private let duration: TimeInterval
private let animationBlock: VSynchedTimer.Animations
private let completionBlock: VSynchedTimer.Completion?
#if canImport(UIKit)
private weak var displayLink: CADisplayLink?
#elseif canImport(AppKit)
private var displayLink: CVDisplayLink?
#endif
private var isRunning: Bool
private let startTimeStamp: TimeInterval
private var lastTimeStamp: TimeInterval
deinit {
cancel()
}
init(_ duration: TimeInterval, _ animations: @escaping VSynchedTimer.Animations, _ completion: VSynchedTimer.Completion? = nil) {
self.duration = duration
self.animationBlock = animations
self.completionBlock = completion
self.isRunning = true
self.startTimeStamp = CACurrentMediaTime()
self.lastTimeStamp = startTimeStamp
self.displayLink = self.createDisplayLink()
}
func cancel() {
guard isRunning else { return }
isRunning.toggle()
#if canImport(UIKit)
displayLink?.invalidate()
#elseif canImport(AppKit)
CVDisplayLinkStop(displayLink!)
#endif
self.completionBlock?(false)
}
private func complete() {
guard isRunning else { return }
isRunning.toggle()
#if canImport(UIKit)
displayLink?.invalidate()
#elseif canImport(AppKit)
CVDisplayLinkStop(displayLink!)
#endif
self.completionBlock?(true)
}
@objc private func displayLinkTick(_ displayLink: CADisplayLink) {
guard isRunning else { return }
let currentTimeStamp = CACurrentMediaTime()
let progress = currentTimeStamp - startTimeStamp
let elapsed = currentTimeStamp - lastTimeStamp
lastTimeStamp = currentTimeStamp
if progress < duration {
animationBlock(progress, elapsed)
} else {
complete()
}
}
#if canImport(UIKit)
private func createDisplayLink() -> CADisplayLink {
let dl = CADisplayLink(target: self, selector: #selector(displayLinkTick(_:)))
dl.add(to: .main, forMode: .common)
return dl
}
#elseif canImport(AppKit)
private func createDisplayLink() -> CVDisplayLink? {
var cvDisplayLink: CVDisplayLink?
CVDisplayLinkCreateWithActiveCGDisplays(&cvDisplayLink)
guard let displayLink = cvDisplayLink else { return nil }
CVDisplayLinkSetOutputCallback(displayLink, { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, userInfo) -> CVReturn in
guard let context = userInfo else { return kCVReturnError }
let synchedTimer = Unmanaged.fromOpaque(context).takeUnretainedValue()
synchedTimer.displayLinkTick()
return kCVReturnSuccess
}, Unmanaged.passUnretained(self).toOpaque())
CVDisplayLinkStart(displayLink)
return displayLink
}
@objc private func displayLinkTick() {
guard isRunning else { return }
let currentTimeStamp = CACurrentMediaTime()
let progress = currentTimeStamp - startTimeStamp
let elapsed = currentTimeStamp - lastTimeStamp
lastTimeStamp = currentTimeStamp
if progress < duration {
animationBlock(progress, elapsed)
} else {
complete()
}
}
#endif
}
Будем очень признательны за любую помощь в отладке. Добавление других встроенных жестов SwiftUI в работу с представлением (onTap, перетаскивание и т. д.), но я хочу, чтобы обе платформы были одинаковыми.
[1]:
https://i.sstatic.net/Ch38BDrk.png
Подробнее здесь:
https://stackoverflow.com/questions/785 ... tiplatform
1717176927
Anonymous
Я нашел старую библиотеку SwiftUI, которую пытался портировать на MacOS. Полный репозиторий можно найти здесь: https://github.com/cyrilzakka/SlidingRuler. По сути, это скользящая линейка с привязкой тикера. Скриншот доступен ниже: [![введите описание изображения здесь][1]][1] В настоящее время код отлично работает на iOS, и я могу перетаскивать ползунок влево и вправо для изменения значений. В macOS (AppKit), хотя пользовательский интерфейс выглядит хорошо, перетаскивание ползунка, похоже, не работает. Вот HorizontalPanGesture.swift [code]import SwiftUI import CoreGeometry #if canImport(UIKit) struct HorizontalDragGestureValue { let state: UIGestureRecognizer.State let translation: CGSize let velocity: CGFloat let startLocation: CGPoint let location: CGPoint } protocol HorizontalPanGestureReceiverViewDelegate: AnyObject { func viewTouchedWithoutPan(_ view: UIView) } class HorizontalPanGestureReceiverView: UIView { weak var delegate: HorizontalPanGestureReceiverViewDelegate? override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) delegate?.viewTouchedWithoutPan(self) } } extension View { func onHorizontalDragGesture(initialTouch: @escaping () -> () = { }, prematureEnd: @escaping () -> () = { }, perform action: @escaping (HorizontalDragGestureValue) -> ()) -> some View { self.overlay(HorizontalPanGesture(beginTouch: initialTouch, prematureEnd: prematureEnd, action: action)) } } private struct HorizontalPanGesture: UIViewRepresentable { typealias Action = (HorizontalDragGestureValue) -> () class Coordinator: NSObject, UIGestureRecognizerDelegate, HorizontalPanGestureReceiverViewDelegate { private let beginTouch: () -> () private let prematureEnd: () -> () private let action: Action weak var view: UIView? init(_ beginTouch: @escaping () -> () = { }, _ prematureEnd: @escaping () -> () = { }, _ action: @escaping Action) { self.beginTouch = beginTouch self.prematureEnd = prematureEnd self.action = action } @objc func panGestureHandler(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) let velocity = gesture.velocity(in: view) let location = gesture.location(in: view) let startLocation = location - translation let value = HorizontalDragGestureValue(state: gesture.state, translation: .init(horizontal: translation.x), velocity: velocity.x, startLocation: startLocation, location: location) self.action(value) } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { guard let pgr = gestureRecognizer as? UIPanGestureRecognizer else { return false } let velocity = pgr.velocity(in: view) return abs(velocity.x) > abs(velocity.y) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive event: UIEvent) -> Bool { beginTouch() return true } func viewTouchedWithoutPan(_ view: UIView) { prematureEnd() } } @Environment(\.slidingRulerStyle) private var style let beginTouch: () -> () let prematureEnd: () -> () let action: Action func makeCoordinator() -> Coordinator { .init(beginTouch, prematureEnd, action) } func makeUIView(context: Context) -> UIView { let view = HorizontalPanGestureReceiverView(frame: .init(size: .init(square: 42))) let pgr = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.panGestureHandler(_:))) view.delegate = context.coordinator pgr.delegate = context.coordinator view.addGestureRecognizer(pgr) context.coordinator.view = view // Pointer interactions if #available(iOS 13.4, *), style.supportsPointerInteraction { pgr.allowedScrollTypesMask = .continuous view.addInteraction(UIPointerInteraction(delegate: context.coordinator)) } return view } func updateUIView(_ uiView: UIView, context: Context) { } } @available(iOS 13.4, *) extension HorizontalPanGesture.Coordinator: UIPointerInteractionDelegate { func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { .init(shape: .path(Pointers.standard), constrainedAxes: .vertical) } } #elseif canImport(AppKit) struct HorizontalDragGestureValue { let state: NSGestureRecognizer.State let translation: CGSize let velocity: CGFloat let startLocation: CGPoint let location: CGPoint } protocol HorizontalPanGestureReceiverViewDelegate: AnyObject { func viewTouchedWithoutPan(_ view: NSView) } class HorizontalPanGestureReceiverView: NSView { weak var delegate: HorizontalPanGestureReceiverViewDelegate? override func touchesEnded(with event: NSEvent) { super.touchesEnded(with: event) delegate?.viewTouchedWithoutPan(self) } } extension View { func onHorizontalDragGesture(initialTouch: @escaping () -> () = { }, prematureEnd: @escaping () -> () = { }, perform action: @escaping (HorizontalDragGestureValue) -> ()) -> some View { self.overlay(HorizontalPanGesture(beginTouch: initialTouch, prematureEnd: prematureEnd, action: action)) } } private struct HorizontalPanGesture: NSViewRepresentable { typealias Action = (HorizontalDragGestureValue) -> () class Coordinator: NSObject, NSGestureRecognizerDelegate, HorizontalPanGestureReceiverViewDelegate { private let beginTouch: () -> () private let prematureEnd: () -> () private let action: Action weak var view: NSView? init(_ beginTouch: @escaping () -> () = { }, _ prematureEnd: @escaping () -> () = { }, _ action: @escaping Action) { self.beginTouch = beginTouch self.prematureEnd = prematureEnd self.action = action } @objc func panGestureHandler(_ gesture: NSPanGestureRecognizer) { print("PanGesture handler called") let translation = gesture.translation(in: view) let velocity = gesture.velocity(in: view) let location = gesture.location(in: view) let startLocation = location - translation let value = HorizontalDragGestureValue(state: gesture.state, translation: .init(horizontal: translation.x), velocity: velocity.x, startLocation: startLocation, location: location) self.action(value) } func gestureRecognizerShouldBegin(_ gestureRecognizer: NSGestureRecognizer) -> Bool { guard let pgr = gestureRecognizer as? NSPanGestureRecognizer else { return false } let velocity = pgr.velocity(in: view) return abs(velocity.x) > abs(velocity.y) } func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldReceive touch: NSTouch) -> Bool { beginTouch() return true } func viewTouchedWithoutPan(_ view: NSView) { prematureEnd() } } @Environment(\.slidingRulerStyle) private var style let beginTouch: () -> () let prematureEnd: () -> () let action: Action func makeCoordinator() -> Coordinator { .init(beginTouch, prematureEnd, action) } func makeNSView(context: Context) -> some NSView { let view = HorizontalPanGestureReceiverView(frame: .init(size: .init(square: 42))) let pgr = NSPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.panGestureHandler(_:))) view.delegate = context.coordinator pgr.delegate = context.coordinator view.addGestureRecognizer(pgr) context.coordinator.view = view return view } func updateNSView(_ nsView: NSViewType, context: Context) { } } #endif [/code] и как он используется:[code]public var body: some View { let renderedValue: CGFloat, renderedOffset: CGSize (renderedValue, renderedOffset) = renderingValues() return FlexibleWidthContainer { ZStack(alignment: .init(horizontal: .center, vertical: self.verticalCursorAlignment)) { Ruler(cells: self.cells, step: self.step, markOffset: self.markOffset, bounds: self.bounds, formatter: self.formatter) .equatable() .animation(nil) .modifier(InfiniteOffsetEffect(offset: renderedOffset, maxOffset: self.cellWidthOverflow)) self.style.makeCursorBody() } } .modifier(InfiniteMarkOffsetModifier(renderedValue, step: step)) .propagateWidth(ControlWidthPreferenceKey.self) .onPreferenceChange(MarkOffsetPreferenceKey.self, storeValueIn: $markOffset) .onPreferenceChange(ControlWidthPreferenceKey.self, storeValueIn: $controlWidth) { self.updateCellsIfNeeded() } .transaction { if $0.animation != nil { $0.animation = .easeIn(duration: 0.1) } } .onHorizontalDragGesture(initialTouch: firstTouchHappened, prematureEnd: panGestureEndedPrematurely, perform: horizontalDragAction(withValue:)) } [/code] Кажется, я не могу найти, где я могу пойти не так. Единственной другой потенциально (но менее вероятной) проблемной частью может быть CADisplayLink , который мне пришлось реализовать в macOS по-другому: [code]#if canImport(UIKit) import UIKit #elseif canImport(AppKit) import AppKit import CoreVideo #endif struct VSynchedTimer { typealias Animations = (TimeInterval, TimeInterval) -> () typealias Completion = (Bool) -> () private let timer: SynchedTimer init(duration: TimeInterval, animations: @escaping Animations, completion: Completion? = nil) { self.timer = .init(duration, animations, completion) } func cancel() { timer.cancel() } } private final class SynchedTimer { private let duration: TimeInterval private let animationBlock: VSynchedTimer.Animations private let completionBlock: VSynchedTimer.Completion? #if canImport(UIKit) private weak var displayLink: CADisplayLink? #elseif canImport(AppKit) private var displayLink: CVDisplayLink? #endif private var isRunning: Bool private let startTimeStamp: TimeInterval private var lastTimeStamp: TimeInterval deinit { cancel() } init(_ duration: TimeInterval, _ animations: @escaping VSynchedTimer.Animations, _ completion: VSynchedTimer.Completion? = nil) { self.duration = duration self.animationBlock = animations self.completionBlock = completion self.isRunning = true self.startTimeStamp = CACurrentMediaTime() self.lastTimeStamp = startTimeStamp self.displayLink = self.createDisplayLink() } func cancel() { guard isRunning else { return } isRunning.toggle() #if canImport(UIKit) displayLink?.invalidate() #elseif canImport(AppKit) CVDisplayLinkStop(displayLink!) #endif self.completionBlock?(false) } private func complete() { guard isRunning else { return } isRunning.toggle() #if canImport(UIKit) displayLink?.invalidate() #elseif canImport(AppKit) CVDisplayLinkStop(displayLink!) #endif self.completionBlock?(true) } @objc private func displayLinkTick(_ displayLink: CADisplayLink) { guard isRunning else { return } let currentTimeStamp = CACurrentMediaTime() let progress = currentTimeStamp - startTimeStamp let elapsed = currentTimeStamp - lastTimeStamp lastTimeStamp = currentTimeStamp if progress < duration { animationBlock(progress, elapsed) } else { complete() } } #if canImport(UIKit) private func createDisplayLink() -> CADisplayLink { let dl = CADisplayLink(target: self, selector: #selector(displayLinkTick(_:))) dl.add(to: .main, forMode: .common) return dl } #elseif canImport(AppKit) private func createDisplayLink() -> CVDisplayLink? { var cvDisplayLink: CVDisplayLink? CVDisplayLinkCreateWithActiveCGDisplays(&cvDisplayLink) guard let displayLink = cvDisplayLink else { return nil } CVDisplayLinkSetOutputCallback(displayLink, { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, userInfo) -> CVReturn in guard let context = userInfo else { return kCVReturnError } let synchedTimer = Unmanaged.fromOpaque(context).takeUnretainedValue() synchedTimer.displayLinkTick() return kCVReturnSuccess }, Unmanaged.passUnretained(self).toOpaque()) CVDisplayLinkStart(displayLink) return displayLink } @objc private func displayLinkTick() { guard isRunning else { return } let currentTimeStamp = CACurrentMediaTime() let progress = currentTimeStamp - startTimeStamp let elapsed = currentTimeStamp - lastTimeStamp lastTimeStamp = currentTimeStamp if progress < duration { animationBlock(progress, elapsed) } else { complete() } } #endif } [/code] Будем очень признательны за любую помощь в отладке. Добавление других встроенных жестов SwiftUI в работу с представлением (onTap, перетаскивание и т. д.), но я хочу, чтобы обе платформы были одинаковыми. [1]: https://i.sstatic.net/Ch38BDrk.png Подробнее здесь: [url]https://stackoverflow.com/questions/78558650/nspangesture-not-working-on-appkit-but-uipangesture-works-on-ios-multiplatform[/url]