Mark
Mark

Reputation: 18224

How can I make NSCursor hide in full screen macOS window?

import SwiftUI
import Combine

struct ContentView: View {
    @State private var activityTimer: Cancellable?
    @State private var activityCountDownInterval: TimeInterval = 3
    @State private var isActive: Bool = true
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onAppear {
            startActivityTimer()
        }
        .onContinuousHover { phase in
            switch phase {
            case .active(_):
                resetActivityTimer()
                isActive = true
            case .ended: ()
            }
        }
        .onChange(of: isActive) { oldValue, newValue in
            if isActive == false {
                NSCursor.setHiddenUntilMouseMoves(true)
            }
        }
    }
    
    private func startActivityTimer() {
        activityTimer?.cancel()
        
        activityTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect().prefix(3).sink { _ in
            if activityCountDownInterval > 1 {
                activityCountDownInterval -= 1
            } else {
                activityCountDownInterval = 3
                activityTimer?.cancel()
                isActive = false
            }
        }
    }
    
    private func resetActivityTimer() {
        activityCountDownInterval = 3
        startActivityTimer()
    }
}

#Preview {
    ContentView()
}

You will notice the cursor will hide in window mode, but when run in full screen the cursor does not hide automatically.

However if you tweak the code a bit: Text("Hello, world! \(isActive)") This will make it work, so I am assuming it's a layout update problem.

The modern .pointerVisibility(.hidden) also does not work.

Upvotes: 0

Views: 22

Answers (1)

Mark
Mark

Reputation: 18224

Workaround

Mimic a hidden cursor by using a transparent pixel as a cursor.

class Cursor {
    static var isHidden: Bool {
        get {
            NSCursor.current.image == _clearImage
        }
        set {
            if newValue {
                if NSCursor.current.image != _clearImage {
                    NSCursor(image: _clearImage, hotSpot: .zero).push()
                }
            } else {
                if NSCursor.current.image == _clearImage {
                    NSCursor.pop()
                }
            }
        }
    }
    
    private static var _clearImage: NSImage = {
        let image = NSImage(size: NSSize(width: 1, height: 1))
        image.lockFocus()
        NSColor.clear.set()
        NSBezierPath(rect: NSRect(origin: .zero, size: image.size)).fill()
        image.unlockFocus()
        return image
    }()
}

Whenever you like to hide the cursor you can simply put Cursor.isHidden = true or Cursor.isHidden = false.

This will make sure the cursor hides and shows in full screen on macOS Sequoia (15.2) and XCode 16.2.

Upvotes: 0

Related Questions