简体   繁体   中英

SwiftUI: Two-finger swipe ( scroll ) gesture

I'm interested in 2-finger swipe ( scroll ) gesture.

Not two-finger drag, but 2-finger swipe (without press). Like used in Safari to scroll up and down.

As I see noone of basic gestures will work for this: TapGesture - is not; LongPressGesture - not; DragGesture - not; MagnificationGesture - not; RotationGesture - not;

Have anyone some ideas how to do this?

I need at least direction to look at.


  • This is MacOS project
  • And by the way I cannot use UI classes in my project, I cannot re-made project to catalist

With due respect to @duncan-c 's answer, the more effective way is to use the NSResponder 's scrollWheel(with: NSEvent) mechanism to track two-finger scrolling (one finger on the Apple Mouse).

However it's only available under NSView , so you need to integrate it into SwiftUI using NSRepresentableView .

Here is a complete set of working code that scrolls the main image using the scroll wheel. The code uses delegates and callbacks to pass the scroll event back up the chain into SwiftUI:

//
//  ContentView.swift
//  ScrollTest
//
//  Created by TR Solutions on 6/9/21.
//

import SwiftUI

/// How the view passes events back to the representable view.
protocol ScrollViewDelegateProtocol {
  /// Informs the receiver that the mouse’s scroll wheel has moved.
  func scrollWheel(with event: NSEvent);
}

/// The AppKit view that captures scroll wheel events
class ScrollView: NSView {
  /// Connection to the SwiftUI view that serves as the interface to our AppKit view.
  var delegate: ScrollViewDelegateProtocol!
  /// Let the responder chain know we will respond to events.
  override var acceptsFirstResponder: Bool { true }
  /// Informs the receiver that the mouse’s scroll wheel has moved.
  override func scrollWheel(with event: NSEvent) {
    // pass the event on to the delegate
    delegate.scrollWheel(with: event)
  }
}

/// The SwiftUI view that serves as the interface to our AppKit view.
struct RepresentableScrollView: NSViewRepresentable, ScrollViewDelegateProtocol {
  /// The AppKit view our SwiftUI view manages.
  typealias NSViewType = ScrollView
  
  /// What the SwiftUI content wants us to do when the mouse's scroll wheel is moved.
  private var scrollAction: ((NSEvent) -> Void)?
  
  /// Creates the view object and configures its initial state.
  func makeNSView(context: Context) -> ScrollView {
    // Make a scroll view and become its delegate
    let view = ScrollView()
    view.delegate = self;
    return view
  }
  
  /// Updates the state of the specified view with new information from SwiftUI.
  func updateNSView(_ nsView: NSViewType, context: Context) {
  }
  
  /// Informs the representable view  that the mouse’s scroll wheel has moved.
  func scrollWheel(with event: NSEvent) {
    // Do whatever the content view wants
    // us to do when the scroll wheel moved
    if let scrollAction = scrollAction {
      scrollAction(event)
    }
  }

  /// Modifier that allows the content view to set an action in its context.
  func onScroll(_ action: @escaping (NSEvent) -> Void) -> Self {
    var newSelf = self
    newSelf.scrollAction = action
    return newSelf
  }
}

/// Our SwiftUI content view that we want to be able to scroll.
struct ContentView: View {
  /// The scroll offset -- when this value changes the view will be redrawn.
  @State var offset: CGSize = CGSize(width: 0.0, height: 0.0)
  /// The SwiftUI view that detects the scroll wheel movement.
  var scrollView: some View {
    // A view that will update the offset state variable
    // when the scroll wheel moves
    RepresentableScrollView()
      .onScroll { event in
        offset = CGSize(width: offset.width + event.deltaX, height: offset.height + event.deltaY)
      }
  }
  /// The body of our view.
  var body: some View {
    // What we want to be able to scroll using offset(),
    // overlaid (must be on top or it can't get the scroll event!)
    // with the view that tracks the scroll wheel.
    Image(systemName:"applelogo")
      .scaleEffect(20.0)
      .frame(width: 200, height: 200, alignment: .center)
      .offset(offset)
      .overlay(scrollView)
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Edit: Correcting my answer cover Mac OS

Scrolling up and down is a NSPanGestureRecognizer . That has a numberOfTouchesRequired property that lets you make it respond to 2 fingers if desired.

Mac OS does not have a swipe gesture recognizer.

The standard UISwipeGestureRecognizer does exactly what you want. Just set numberOfTouchesRequired to 2.

...Although I'm not sure mobile Safari uses swipe gestures. It might be a 2-finger drag with some special coding.

import Combine

@main
struct MyApp: App {
    @State var subs = Set<AnyCancellable>() // Cancel onDisappear

    @SceneBuilder
    var body: some Scene {
        WindowGroup {
            SomeWindowView()
                /////////////
                // HERE!!!!!
                /////////////
                .onAppear { trackScrollWheel() }
        }
    }
}

/////////////
// HERE!!!!!
/////////////
extension MyApp {
    func trackScrollWheel() {
        NSApp.publisher(for: \.currentEvent)
            .filter { event in event?.type == .scrollWheel }
            .throttle(for: .milliseconds(200),
                      scheduler: DispatchQueue.main,
                      latest: true)
            .sink {
                if let event = $0 {
                    if event.deltaX > 0 { print("right") }
                    if event.deltaX < 0 { print("left") }
                    if event.deltaY > 0 { print("down") }
                    if event.deltaY < 0 { print("up") }
                }
            }
            .store(in: &subs)
    }
}

I'm interested in 2-finger swipe ( scroll ) gesture.

Not two-finger drag, but 2-finger swipe (without press). Like used in Safari to scroll up and down.

As I see noone of basic gestures will work for this: TapGesture - is not; LongPressGesture - not; DragGesture - not; MagnificationGesture - not; RotationGesture - not;

Have anyone some ideas how to do this?

I need at least direction to look at.


  • This is MacOS project
  • And by the way I cannot use UI classes in my project, I cannot re-made project to catalist

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM