swiftanimationswiftuicursorwkwebview

SwiftUI animation causes WkWebView pointer to flicker


I have two a WkWebView with an embedded YouTube player and a counter underneath that updates at a certain interval with a small animation. Every update causes the cursor to flicker back and forth between pointer and regular cursor:

Demo

Any way to fix this that doesn't involve getting rid of the animation?

VStack {
    PlayerWebView()
    ControlsView()
}
struct ControlsView: View {
    @State private var counter: Int = 0
    @State private var timer: Timer? = nil

    var body: some View {
        HStack {
            Text("Counter: \(counter)")
                .animation(.default, value: counter)
                .contentTransition(.numericText(countsDown: true))
                .padding()
                .onTapGesture {
                    toggleTimer()
                }
        }
        .onAppear {
            startTimer()
        }
    }
    
    private func toggleTimer() {
        if timer == nil {
            startTimer()
        } else {
            stopTimer()
        }
    }

    private func startTimer() {
        stopTimer()
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
            counter += 1
        }
    }

    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
}
struct PlayerWebView: NSViewRepresentable {
    func makeNSView(context: Context) -> WKWebView {
        let webView = WKWebView(frame: .zero)
        let url = URL(string: "https://www.youtube.com/embed/Ir1xi2zeuug")!
        webView.load(URLRequest(url: url))
        return webView
    }

    func updateNSView(_ nsView: WKWebView, context: Context) {
        // Handle updates if needed
    }
}

Solution

  • One way to work around the issue is to keep the cursor from changing in the first place. Doesn't solve the root cause, but at least the symptom.

    struct PlayerWebView: NSViewRepresentable {
        func makeNSView(context: Context) -> WKWebView {
            let webView = WKWebView(frame: .zero)
            let url = URL(string: "https://www.youtube.com/embed/Ir1xi2zeuug")!
            webView.load(URLRequest(url: url))
            webView.navigationDelegate = context.coordinator // <- add coordinator
            return webView
        }
    
        func updateNSView(_ nsView: WKWebView, context: Context) {
            // Handle updates if needed
        }
    
        func makeCoordinator() -> Coordinator {
            Coordinator()
        }
    
        // v set cursor to default to avoid flickering
        class Coordinator: NSObject, WKNavigationDelegate {
            func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                let js = """
                    var style = document.createElement('style');
                    style.innerHTML = '* { cursor: default !important; }';
                    document.head.appendChild(style);
                    """
                webView.evaluateJavaScript(js, completionHandler: nil)
            }
        }
    }