2

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

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

测试数据 - 国家列表

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
            }
        }
    }
}

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


如果您点击Delete View之外的任何位置,我想向后滑动。那么该怎么做呢?

4

1 回答 1

1

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

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

然后您需要跟踪哪个单元格被刷过,SwiftUI 将数据中继给兄弟姐妹的方式是通过保存在其父级中的 Binding。阅读如何在 SwiftUI 视图中传递数据。如果你想偷懒,你也可以只拥有一个保存所选单元格的静态对象:

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

于 2020-12-08T09:59:25.000 回答