How to detect a tap gesture location in SwiftUI?

前端 未结 3 1568
小蘑菇
小蘑菇 2020-12-01 12:39

(For SwiftUI, not vanilla UIKit) Very simple example code to, say, display red boxes on a gray background:

struct ContentView : View {
    @State var points:         


        
相关标签:
3条回答
  • 2020-12-01 13:04

    Well, after some tinkering around and thanks to this answer to a different question of mine, I've figured out a way to do it using a UIViewRepresentable (but by all means, let me know if there's an easier way!) This code works for me!

    struct ContentView : View {
        @State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
        var body: some View {
            return ZStack(alignment: .topLeading) {
                Background {
                       // tappedCallback
                       location in
                        self.points.append(location)
                    }
                    .background(Color.white)
                ForEach(self.points.identified(by: \.debugDescription)) {
                    point in
                    Color.red
                        .frame(width:50, height:50, alignment: .center)
                        .offset(CGSize(width: point.x, height: point.y))
                }
            }
        }
    }
    
    struct Background:UIViewRepresentable {
        var tappedCallback: ((CGPoint) -> Void)
    
        func makeUIView(context: UIViewRepresentableContext<Background>) -> UIView {
            let v = UIView(frame: .zero)
            let gesture = UITapGestureRecognizer(target: context.coordinator,
                                                 action: #selector(Coordinator.tapped))
            v.addGestureRecognizer(gesture)
            return v
        }
    
        class Coordinator: NSObject {
            var tappedCallback: ((CGPoint) -> Void)
            init(tappedCallback: @escaping ((CGPoint) -> Void)) {
                self.tappedCallback = tappedCallback
            }
            @objc func tapped(gesture:UITapGestureRecognizer) {
                let point = gesture.location(in: gesture.view)
                self.tappedCallback(point)
            }
        }
    
        func makeCoordinator() -> Background.Coordinator {
            return Coordinator(tappedCallback:self.tappedCallback)
        }
    
        func updateUIView(_ uiView: UIView,
                           context: UIViewRepresentableContext<Background>) {
        }
    
    }
    
    0 讨论(0)
  • 2020-12-01 13:13

    I was able to do this with a DragGesture(minimumDistance: 0). Then use the startLocation from the Value on onEnded to find the tap's first location.

    0 讨论(0)
  • 2020-12-01 13:22

    Just in case someone needs it, converted the above answer into a view modifier which also takes a CoordniateSpace as an optional parameter

    import SwiftUI
    import UIKit
    
    public extension View {
      func onTapWithLocation(coordinateSpace: CoordinateSpace = .local, _ tapHandler: @escaping (CGPoint) -> Void) -> some View {
        modifier(TapLocationViewModifier(tapHandler: tapHandler, coordinateSpace: coordinateSpace))
      }
    }
    
    fileprivate struct TapLocationViewModifier: ViewModifier {
      let tapHandler: (CGPoint) -> Void
      let coordinateSpace: CoordinateSpace
    
      func body(content: Content) -> some View {
        content.overlay(
          TapLocationBackground(tapHandler: tapHandler, coordinateSpace: coordinateSpace)
        )
      }
    }
    
    fileprivate struct TapLocationBackground: UIViewRepresentable {
      var tapHandler: (CGPoint) -> Void
      let coordinateSpace: CoordinateSpace
    
      func makeUIView(context: UIViewRepresentableContext<TapLocationBackground>) -> UIView {
        let v = UIView(frame: .zero)
        let gesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tapped))
        v.addGestureRecognizer(gesture)
        return v
      }
    
      class Coordinator: NSObject {
        var tapHandler: (CGPoint) -> Void
        let coordinateSpace: CoordinateSpace
    
        init(handler: @escaping ((CGPoint) -> Void), coordinateSpace: CoordinateSpace) {
          self.tapHandler = handler
          self.coordinateSpace = coordinateSpace
        }
    
        @objc func tapped(gesture: UITapGestureRecognizer) {
          let point = coordinateSpace == .local
            ? gesture.location(in: gesture.view)
            : gesture.location(in: nil)
          tapHandler(point)
        }
      }
    
      func makeCoordinator() -> TapLocationBackground.Coordinator {
        Coordinator(handler: tapHandler, coordinateSpace: coordinateSpace)
      }
    
      func updateUIView(_: UIView, context _: UIViewRepresentableContext<TapLocationBackground>) {
        /* nothing */
      }
    }
    
    0 讨论(0)
提交回复
热议问题