【问题标题】:SwiftUI - Hide custom onDelete View on tap gestureSwiftUI - 在点击手势时隐藏自定义 onDelete 视图
【发布时间】:2020-11-28 14:11:37
【问题描述】:

我有一个包含视图列表的 LazyVStack 视图。每个视图都有不同的颜色,并且它们之间有 8 个点间距。因此,我不能使用 List

所以我正在尝试构建一个自定义的拖尾滑动,其功能类似于 ListonDelete 方法。这是我的代码,它并不完美,但我认为我的方向是正确的。

测试数据 - 国家列表

class Data: ObservableObject {
    @Published var countries: [String]
    init() {
        self.countries = NSLocale.isoCountryCodes.map { (code:String) -> String in
            let id = NSLocale.localeIdentifier(fromComponents: [NSLocale.Key.countryCode.rawValue: code])
            return NSLocale(localeIdentifier: "en_US").displayName(forKey: NSLocale.Key.identifier, value: id) ?? "Country not found for code: \(code)"
        }
    }
}

内容视图

struct ContentView: View {
    @ObservedObject var data: Data = Data()
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(data.countries, id: \.self) { country in
                    VStack {
                        SwipeView(content: {
                            VStack(spacing: 0) {
                                Spacer()
                                Text(country)
                                    .frame(minWidth: 0, maxWidth: .infinity)
                                Spacer()
                            }
                            .background(Color.yellow)
                        }, trailingActionView: {
                            Image(systemName: "trash")
                                .foregroundColor(.white)
                        }) {
                            self.data.countries.removeAll {$0 == country}
                        }
                    }
                    .clipShape(Rectangle())
                }
            }
        }
        .padding(.vertical, 16)
    }
}

自定义滑动视图

struct SwipeView<Content: View, TrailingActionView: View>: View {
    let width = UIScreen.main.bounds.width - 32
    
    @State private var height: CGFloat = .zero
    
    @State var offset: CGFloat = 0
    
    let content: Content
    
    let trailingActionView: TrailingActionView
    
    var onDelete: () -> ()
    
    
    init(@ViewBuilder content: () -> Content,
                      @ViewBuilder trailingActionView: () -> TrailingActionView,
                      onDelete: @escaping () -> Void) {
        self.content = content()
        self.trailingActionView = trailingActionView()
        self.onDelete = onDelete
    }
    
    var body: some View {
        ZStack {
            HStack(spacing: 0) {
                Button(action: {
                    withAnimation {
                        self.onDelete()
                    }
                }) {
                    trailingActionView
                }
                .frame(minHeight: 0, maxHeight: .infinity)
                .frame(width: 60)
                Spacer()
            }
            .background(Color.red)
            .frame(width: width)
            .offset(x: width + self.offset)

            content
                .frame(width: width)
                .contentShape(Rectangle())
                .offset(x: self.offset)
                .gesture(DragGesture().onChanged(onChanged).onEnded { value in
                    onEnded(value: value, width: width)
                })
        }
        .background(Color.white)
    }
    
    private func onChanged(value: DragGesture.Value) {
        let translation =  value.translation.width
        
        if translation < 0  {
            self.offset = translation
        } else {
            
        }
    }
    
    private func onEnded(value: DragGesture.Value,width: CGFloat) {
        withAnimation(.easeInOut) {
            let translation = -value.translation.width
            if translation > width - 16 {
                self.onDelete()
                self.offset = -(width * 2)
            }
            
            else if translation > 50 {
                self.offset = -50
            }
            else {
                self.offset = 0
            }
        }
    }
}

它有一个烦人的问题:如果您滑动一行并且不删除它。如果您滑动其他视图,它们不会重置。所有尾随删除视图都是可见的。但是,如果您点击删除视图之外的任何位置,我想重置/向后滑动。


如果您点击删除视图之外的任意位置,我想向后滑动。那么该怎么做呢?

【问题讨论】:

  • 我会将 tag/selection 概念改编为SwipeView,因为它需要在内部对外部条件作出反应,所以保持活动排他性的状态应该位于外部并注入内部通过绑定。它需要对您的代码进行大量更改,因此请先自己尝试一下,如果这样的想法适合您。

标签: ios swift swiftui


【解决方案1】:

首先,要知道刷了哪个单元格,SwipeViews 需要一个 id。如果您不想从外部设置它们,我想这样可以:

struct SwipeView<Content: View, TrailingActionView: View>: View {
    ...
    @State var id = UUID()
    ...
}

然后您需要跟踪哪个单元格被刷过,SwiftUI 将数据中继给兄弟姐妹的方式是通过保存在其父级中的 Binding。 Read up on how to pass data around SwiftUI Views. 如果你想偷懒,你也可以只拥有一个保存所选单元格的静态对象:

class SwipeViewHelper: ObservableObject {
    @Published var swipedCell: UUID?
    private init() {}
    static var shared = SwipeViewHelper()
}

struct SwipeView<Content: View, TrailingActionView: View>: View {
    ...
    @ObservedObject var helper = SwipeViewHelper.shared
    ...
}

然后你必须更新 swipedCell。当我们开始在不同的单元格上滑动时,我们希望单元格关闭:

private func onChanged(value: DragGesture.Value) {
    ...
    if helper.swipedCell != nil {
        helper.swipedCell = nil
    }
    ...
}

当一个单元格打开时,我们保存它:

private func onEnded(value: DragGesture.Value,width: CGFloat) {
    withAnimation(.easeInOut) {
        ...
        else if translation > 50 {
            self.offset = -50
            helper.swipedCell = id
        }
        ...
    }
}

然后我们要响应 swipedCell 的变化。我们可以通过在 SwipeView 的主体中添加一个 onChange 来做到这一点:

.onChange(of: helper.swipedCell, perform: { newCell in
    if newCell != id {
        withAnimation(.easeInOut) {
            self.offset = 0
        }
    }
})

工作要点:https://gist.github.com/Amzd/61a957a1c5558487f6cc5d3ce29cf508

【讨论】:

  • 谢谢。它仅在您点击一行时才有效,但如果您点击 ScrollView 内的某处则它不起作用。
  • @mahan 啊,我误读了,并认为当您开始滑动不同的单元格时必须发生这种情况。您可以在 ScrollView 上添加 .onTapGesture { SwipeViewHelper.shared.swipedCell = nil }
  • 在这种情况下,删除按钮不起作用。
  • 哦 .simultaneousGesture(TapGesture().onEnded { ...}) 怎么样
  • 我测试了它并且同时手势工作。我还建议您在 SwipeView 中使用 GestureState 而不是 onChanged 和 onEnded,因为当 ScrollView 取消 DragGesture 时不会调用 onEnded。 hackingwithswift.com/quick-start/swiftui/…
猜你喜欢
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
  • 1970-01-01
  • 2021-03-02
  • 2021-02-12
  • 1970-01-01
相关资源
最近更新 更多