Search code examples
swiftswiftuiwatchos

SwiftUI - show view during Digital Crown rotation


I'm looking to show Text when scrolling but hide the text when not scrolling using digitalCrownRotation (like the indicator shown when scrolling). Currently it's only working one way when I scroll and doesn't work at too well, would this be possible to accomplish?

extension View {
    func hidden(_ shouldHide: Bool) -> some View {
        opacity(shouldHide ? 0 : 1)
    }
}

struct ContentView: View {
    @State var date: Date = Date()
    @State var scroll: Double = 0.0
    @State var previous: Double = 0.0
    @State var scrolling: Bool = false
    var body: some View {
        VStack {
            Text("\(date.dateFormat("E, d MMM"))")
                .focusable(true)
                .hidden(!scrolling)
                .digitalCrownRotation($scroll, from: 0, through: 365, by: 1, sensitivity: .low, isContinuous: false, isHapticFeedbackEnabled: true)
                .onChange(of: scroll) { value in
                    scrolling = (value > previous)
                    previous = value
                    date = Calendar.current.date(byAdding: .day, value: Int(value), to: Date())!
                }
        }
        .onAppear {
            self.date = Date()
        }
    }
}

enter image description here


Solution

  • You need to show your view while user scrolls and hide when he ended doing so.

    I suggest you using .debounce from Combine. What it does it waits for some time (1 sec in my example, should be fine for you) after each new value passed, and only pass it if no new value was sent during this time.

    So in this case it'll wait 1 sec after last crown touch before hiding the view:

    @State var date: Date = Date()
    @State var scroll: Double = 0.0
    @State var scrolling: Bool = false
    
    private let relay = PassthroughSubject<Double, Never>()
    private let debouncedPublisher: AnyPublisher<Double, Never>
    
    init() {
        debouncedPublisher = relay
            .removeDuplicates()
            .debounce(for: 1, scheduler: RunLoop.main)
            .eraseToAnyPublisher()
    }
    
    var body: some View {
        VStack {
            Text("\(date)")
                .focusable(true)
                .opacity(scrolling ? 1 : 0)
                .digitalCrownRotation($scroll, from: 0, through: 365, by: 1, sensitivity: .low, isContinuous: false, isHapticFeedbackEnabled: true)
                .onChange(of: scroll) { value in
                    withAnimation {
                        scrolling = true
                    }
                    relay.send(value)
                    date = Calendar.current.date(byAdding: .day, value: Int(value), to: Date())!
                }
                .onReceive(
                    debouncedPublisher,
                    perform: { value in
                        withAnimation {
                            scrolling = false
                        }
                    }
                )
        }
        .onAppear {
            self.date = Date()
        }
    }
    

    Result: