swift - 是否有 SwiftUI "drag enter"手势?

标签 swift swiftui gesture drag

我正在尝试处理进入 SwiftUI View 的拖动手势。然而,DragGesture仅当事件源自 View 本身时才触发(更新、更改)。

我当然可以想到各种解决方法,但我想仔细检查是否有一种简单/有意的方法可以做到这一点。

我正在寻找类似于 UIKit touchDragEnter 的东西,或 JavaScript ondragenter .

最佳答案

我最终在覆盖 View 中拦截触摸,然后在触摸位置包含在其边界内时突出显示项目。我尝试了 onDrop 和使用 UIKit 手势/操作,但它们都没有缺点。所以我选择实现George's comment above相反。

enter image description here

I incorporated a model object where I store the frames on every update, so highlight effect can adopt dynamically. It makes the solution animation proof, while picker also can be displayed anywhere on the screen.

import SwiftUI


class Model: ObservableObject {
    
    let coordinateSpace = "CoordinateSpace"
    
    @Published var isDragged = false
    @Published var highlightedNumber: Int? = nil
    @Published var selectedNumber: Int? = nil
    
    /// Frames for individual picker items (from their respective `GeometryReader`).
    private var framesForNumbers: [Int: CGRect] = [:]
    
    func update(frame: CGRect, for number: Int) {
        framesForNumbers[number] = frame
    }
    
    /// Updates `highlightedNumber` according drag location.
    func update(dragLocation: CGPoint) {
        
        // Lookup if any frame contains drag location.
        for (eachNumber, eachFrame) in framesForNumbers {
            if eachFrame.contains(dragLocation) {
                
                // Publish.
                self.highlightedNumber = eachNumber
                return
            }
        }
        
        // Reset otherwise.
        self.highlightedNumber = nil
    }
    
    /// Updates `highlightedNumber` and `selectedNumber` according drop location.
    func update(isDragged: Bool) {
        
        // Publish.
        self.isDragged = isDragged
        
        if isDragged == false,
           let highlightedNumber = self.highlightedNumber {
            
            // Publish.
            self.selectedNumber = highlightedNumber
            self.highlightedNumber = nil
        }
    }
}

struct ContentView: View {
    
    @StateObject var model = Model()
    
    var body: some View {
        ZStack {
            TouchesView(model: model, isDragged: $model.isDragged)
            CanvasView(number: $model.selectedNumber)
                .allowsHitTesting(false)
            PickerView(model: model, highlightedNumber: $model.highlightedNumber)
                .allowsHitTesting(false)
        }
        .ignoresSafeArea()
    }
}

/// Handles drag interactions and updates model accordingly.
struct TouchesView: View {
    
    var model: Model
    @Binding var isDragged: Bool
    
    var body: some View {
        Rectangle()
            .foregroundColor(isDragged ? .orange : .yellow)
            .coordinateSpace(name: model.coordinateSpace)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { value in
                        model.update(dragLocation: value.location)
                        model.update(isDragged: true)
                    }
                    .onEnded { state in
                        model.update(dragLocation: state.location)
                        model.update(isDragged: false)
                    }
                )
    }
}

/// Shows the selected number.
struct CanvasView: View {
    
    @Binding var number: Int?
    
    var body: some View {
        VStack {
            Text(number.string)
                .font(.system(size: 100, weight: .bold))
                .foregroundColor(.white)
                .offset(y: -50)
        }
    }
}

/// Displays a picker to select number items from.
struct PickerView: View {
    
    var model: Model
    @Binding var highlightedNumber: Int?
    
    var body: some View {
        HStack(spacing: 5) {
            PickerItemView(number: 1, model: model, highlightedNumber: $highlightedNumber)
            PickerItemView(number: 2, model: model, highlightedNumber: $highlightedNumber)
            PickerItemView(number: 3, model: model, highlightedNumber: $highlightedNumber)
        }
        .opacity(model.isDragged ? 1 : 0)
        .scaleEffect(model.isDragged ? 1 : 0.5, anchor: .top)
        .blur(radius: model.isDragged ? 0 : 10)
        .animation(.spring(response: 0.15, dampingFraction: 0.4, blendDuration: 0.5))
    }
}

/// Shows a number item (also highlights it when `highlightedNumber` matches).
struct PickerItemView: View {
    
    let number: Int
    var model: Model
    @Binding var highlightedNumber: Int?
    
    var body: some View {
        Text(String(number))
            .font(.system(size: 25, weight: .bold))
            .foregroundColor(isHighlighted ? .orange : .white)
            .frame(width: 50, height: 50)
            .background(isHighlighted ? Color.white : Color.orange)
            .cornerRadius(25)
            .overlay(
                RoundedRectangle(cornerRadius: 25)
                    .stroke(Color.white, lineWidth: 2)
            )
            .overlay(GeometryReader { geometry -> Color  in
                self.model.update(
                    frame: geometry.frame(in: .named(self.model.coordinateSpace)),
                    for: self.number
                )
                return Color.clear
            })
            .animation(.none)
    }
}

extension PickerItemView {
    
    var isHighlighted: Bool {
        self.highlightedNumber == self.number
    }
}

fileprivate extension Optional where Wrapped == Int {
    
    var string: String {
        if let number = self {
            return String(number)
        } else {
            return ""
        }
    }
}

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

Only thing I dislike about this is that this method is esentially the same I did 15 years ago in a Flash app. I was hoping to something less "manual".

关于swift - 是否有 SwiftUI "drag enter"手势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69055198/

相关文章:

ios - 链接器命令失败(Apple Mach-O)

ios - 关于在现有 iOS 应用程序中集成 Firebase 的链接问题

uiactivityviewcontroller - 在 SwiftUI 中显示 'UIActivityViewController'

iphone - 滑动手势有帮助吗?仅 LeftSwipe 有效,而 RightSwipe 无效

iphone - 使用手势缩放 UIlabel 文本

ios - SwiftUI:菜单打开时如何忽略背景上的点击?

swift - 不占全屏宽度时获取UILabel宽度?

SwiftUI TextField 可触摸区域

swift - 如何使用 SwiftUI EnvironmentObject 进行引用

java - Android AccessibilityService onGesture() 正在禁用触摸