SwiftUI系统光标

n7taea2i  于 2023-02-03  发布在  Swift
关注(0)|答案(5)|浏览(152)

我尝试在MacOS上的SwiftUI中将光标更改为十字线。
我已经将以下代码放入AppDelegateapplicationDidFinishLaunching()函数:

NSCursor.crosshair.set()

加载应用程序时,我看到光标变为crossHair,然后直接切换回标准指针。
如果能听到我做错了什么就太好了。
谢谢大家。

guz6ccqo

guz6ccqo1#

这不是NSCursor的工作方式。它有一个光标堆栈,因此任何标准(或非标准)控件都可以将自己类型的光标推到顶部,并使该光标成为当前光标。因此,您只需放置第一个光标,但随后某个标准视图会使用自己的默认光标替换它。
由于SwiftUI现在不允许本地管理游标,因此解决方案可能是
a)在SwiftUI视图上设置/推动所需光标出现/消失,或
B)使用标准NSView.addCursorRect方法将光标矩形添加到NSHostingController视图。

**更新:**来自现有项目的一些快速演示(自定义按钮解决方案的一部分)

struct DemoCustomCursor: View {
    var body: some View {
        Button(action: {}) {
            Text("Cross Button")
                .padding(20)
                .background(Color.blue)
        }.buttonStyle(PlainButtonStyle())
        .onHover { inside in
            if inside {
                NSCursor.crosshair.push()
            } else {
                NSCursor.pop()
            }
        }
    }
}
ddrv8njm

ddrv8njm2#

下面是Swift Playground代码来说明这个问题:

import Foundation
import SwiftUI
import PlaygroundSupport

PlaygroundPage.current.setLiveView(
    SplitView().frame(width: 600, height:600).border(Color.black)
)

struct SplitView: View {
    @State var position: CGFloat = 10.0
    var body: some View {
        Text("top")
            .frame(height: position)
        PaneDivider(position: $position)
        Text("bottom")
        Spacer()
    }
}

struct PaneDivider: View {
    @Binding var position: CGFloat
    @GestureState private var isDragging = false // Will reset to false when dragging has ended
       
    var body: some View {
        Rectangle().frame(height:10).foregroundColor(Color.gray)
        .onHover { inside in
            if !isDragging {
                if inside { NSCursor.resizeUpDown.push() }
                else {  NSCursor.pop() }
                }
        }
        .gesture(DragGesture()
            .onChanged { position += $0.translation.height  }
            .updating($isDragging) { (value, state, transaction) in
                if !state { NSCursor.resizeUpDown.push() } // This is overridden, something else in the system is pushing the arrow cursor during the drag
                state = true
            }
            .onEnded { _ in NSCursor.pop() })
    }
}

设置断点-[NSCursor set]显示它在拖入过程中持续触发

Setting a breakpoint on -[NSCursor set] shows it firing constantly during the drag in
  * frame #0: 0x00000001a48c2944 AppKit`-[NSCursor set]
    frame #1: 0x00000001a491cc4c AppKit`forwardMethod + 200
    frame #2: 0x00000001a491cc4c AppKit`forwardMethod + 200
    frame #3: 0x00000001a4924428 AppKit`-[NSView cursorUpdate:] + 132
    frame #4: 0x00000001a48cb2e4 AppKit`-[NSWindow(NSCursorRects) _setCursorForMouseLocation:] + 432
    frame #5: 0x00000001a47f0d70 AppKit`_NSWindowDisplayCycleUpdateStructuralRegions + 544
    frame #6: 0x00000001a47ec028 AppKit`__NSWindowGetDisplayCycleObserverForUpdateStructuralRegions_block_invoke + 428
    frame #7: 0x00000001a47e59ec AppKit`NSDisplayCycleObserverInvoke + 188
    frame #8: 0x00000001a47e5568 AppKit`NSDisplayCycleFlush + 832
    frame #9: 0x00000001a81a8544 QuartzCore`CA::Transaction::run_commit_handlers(CATransactionPhase) + 120
    frame #10: 0x00000001a81a754c QuartzCore`CA::Transaction::commit() + 336
    frame #11: 0x00000001a488e6e0 AppKit`__62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke + 304
    frame #12: 0x00000001a4fe2a10 AppKit`___NSRunLoopObserverCreateWithHandler_block_invoke + 64
    frame #13: 0x00000001a1f28e14 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
    frame #14: 0x00000001a1f28c60 CoreFoundation`__CFRunLoopDoObservers + 572
    frame #15: 0x00000001a1f281a8 CoreFoundation`__CFRunLoopRun + 764
    frame #16: 0x00000001a1f27734 CoreFoundation`CFRunLoopRunSpecific + 600
    frame #17: 0x00000001a9e25b84 HIToolbox`RunCurrentEventLoopInMode + 292
    frame #18: 0x00000001a9e258f8 HIToolbox`ReceiveNextEventCommon + 552
    frame #19: 0x00000001a9e256b8 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 72
    frame #20: 0x00000001a47114ec AppKit`_DPSNextEvent + 836
    frame #21: 0x00000001a470fe8c AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1292
    frame #22: 0x00000001a4701d18 AppKit`-[NSApplication run] + 596
    frame #23: 0x00000001a46d3728 AppKit`NSApplicationMain + 1064
    frame #24: 0x00000001c25f11b4 SwiftUI`generic specialization <SwiftUI.TestingAppDelegate> of function signature specialization <Arg[0] = Existential To Protocol Constrained Generic> of SwiftUI.runApp(__C.NSApplicationDelegate) -> Swift.Never + 96
    frame #25: 0x00000001c2e34ba0 SwiftUI`SwiftUI.runApp<τ_0_0 where τ_0_0: SwiftUI.App>(τ_0_0) -> Swift.Never + 220
    frame #26: 0x00000001c29de854 SwiftUI`static SwiftUI.App.main() -> () + 128
    frame #27: 0x0000000100c8c540 Calculator`static CalculatorApp.$main(self=Calculator.CalculatorApp) at CalculatorApp.swift:5:1
    frame #28: 0x0000000100c8c5e0 Calculator`main at CalculatorApp.swift:0
    frame #29: 0x00000001a1e48420 libdyld.dylib`start + 4

SwiftUI之前的问题似乎与http://cocoadev.github.io/CursorFlashingProblems/相同
在DragGesture的开头添加NSApp.windows[0].disableCursorRects(),在.onEnded中添加NSApp.windows[0].enableCursorRects(),我在这里解决了这个问题。
除了NSApp.windows[0]并不总是前窗口外,该死的NSApp.windows.forEach { $0.disableCursorRects() }却能工作。)

camsedfj

camsedfj3#

macCatalyst中不存在onHover,但您可以自己添加它。https://stackoverflow.com/a/60748101/1450348解释了替代解决方案

ngynwnxp

ngynwnxp4#

就像评论中提到的Avitzur,有一个 Flink 的问题,使光标改变为默认值(特别是如果你有一些动画发生时,悬停在视图上),我做了以下:

import SwiftUI

 enum CursorEvent {
    case hover
    case drag
 }

 extension View {
    @ViewBuilder
    func cursor(
        _ cursor: NSCursor,
        on event: CursorEvent = .hover,
        shouldKeepDefaultCursor: Bool = false,
        currentWindow: NSWindow = << Pass your default window >>
    ) -> some View {
        if shouldKeepDefaultCursor {
            self
        } else {
            switch event {
            case .hover:
                self.onHover { isInside in
                    isInside ? cursor.enable(on: currentWindow) : cursor.disable(on: currentWindow)
                }
            case .drag:
                self.simultaneousGesture(
                    DragGesture()
                        .onChanged { _ in
                            cursor.enable(on: currentWindow)
                        }
                        .onEnded { _ in
                            cursor.disable(on: currentWindow)
                        }
                )
            }
        }
    }
 }

 extension NSCursor {
    fileprivate func enable(on window: NSWindow) {
        window.disableCursorRects()
        self.push()
    }

    fileprivate func disable(on window: NSWindow) {
        NSCursor.pop()
        window.enableCursorRects()
    }
 }

用法:

Button("Click Me") {
   print("Button Clicked")
 }
 .cursor(.pointingHand)
vq8itlhq

vq8itlhq5#

我一直在尝试解决这个问题,这个页面不断出现。所以我想我应该发布我的解决方案

import SwiftUI

public struct HoverCursorModifier: ViewModifier {
    
    @State var isHovered: Bool = false
    
    let pointerImage : NSImage
    
    init(imageName: String){
        self.pointerImage = NSImage(named: imageName) ?? NSImage()
    }
    
    init(systemSymbolName: String){
        self.pointerImage = NSImage(systemSymbolName: systemSymbolName , accessibilityDescription: nil) ?? NSImage()
    }
    
    public func body(content: Content) -> some View {
        
        content
            .onHover { isHovered in
                self.isHovered = isHovered
                
                DispatchQueue.main.async {
                    
                    if self.isHovered {
                        // Looks like ugly hack, but otherwise cursor gets reset to standard arrow.
                        // See https://stackoverflow.com/a/62984079/7964697 for details.
                        NSApp.windows.forEach { $0.disableCursorRects() }
                        NSCursor(image: pointerImage, hotSpot: NSPoint(x: 9, y: 9)).push()
                    } else {
                        NSCursor.pop()
                        NSApp.windows.forEach { $0.enableCursorRects() }
                    }
                    
                }
                
            }
        
    }
    
}

用途
如果你想使用SF字体,就把它加到你的按钮上

.modifier(HoverCursorModifier(systemSymbolName: "hand.point.up.fill"))

或者使用资源中的图像

.modifier(HoverCursorModifier(imageName: "<YOUR IMAGE NAME GOES HERE>"))

我建议对按钮使用样式。你可以给样式添加修饰符,这样你所有的按钮都能得到相同的悬停效果。例如

import SwiftUI

struct StylePrimaryButton: ButtonStyle  {
    
    func makeBody(configuration: ButtonStyle.Configuration) -> some View {
        PrimaryButton(configuration: configuration)
    }
    
    struct PrimaryButton: View {
        
        let configuration: ButtonStyle.Configuration
        @Environment(\.isEnabled) private var isEnabled: Bool
       
        var body: some View {
            
            configuration.label
                .padding(10)
                .font(.body)
                .foregroundColor(.white)
                .background(.black)
                .cornerRadius(5)
                .opacity(configuration.isPressed ? 0.8 : isEnabled ? 1 : 0.5)
                .scaleEffect(configuration.isPressed ? 0.95 : 1)
                .animation(.easeOut(duration: 0.2), value: configuration.isPressed)
                .contentShape(Rectangle())
                .modifier(HoverCursorModifier(systemSymbolName: "hand.point.up.fill"))
            
        }
        
    }
    
}

然后把你的扣子这样做

Button {
        // DO Something
    } label: {
        Text("Hello World")
    }
    .buttonStyle(StylePrimaryButton())

相关问题