简体   繁体   English

SwiftUI - 在点击手势时隐藏自定义 onDelete 视图

[英]SwiftUI - Hide custom onDelete View on tap gesture

I have LazyVStack view that contains a list of views.我有包含视图列表的LazyVStack视图。 Each one of the views has a different color and there is 8 points space between them.每个视图都有不同的颜色,它们之间有8个点的空间。 Threrefore, I can not use List .因此,我不能使用List

So I am trying to build a custom trailing swipe that functions similar to the onDelete method of List .因此,我正在尝试构建一个自定义的尾随滑动,其功能类似于ListonDelete方法。 This is my code and it is not perfect, but I am on the right directin, I think.这是我的代码,它并不完美,但我认为我的方向是正确的。

Test Data - List of countries测试数据 - 国家列表

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

ContentView内容视图

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

Custom SwipeView自定义滑动视图

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

It has one annoying problem: If you swipe a row and do not delete it.它有一个烦人的问题:如果您滑动一行并且不删除它。 And if you swipe another views, they don not reset.如果您滑动其他视图,它们不会重置。 All the trailing Delete Views are visible.所有尾随删除视图都是可见的。 But I want to reset/ swipe back if you tap anywhere outside the Delete View.但是,如果您点击删除视图之外的任何位置,我想重置/向后滑动。


I want to swipe back if you tap anywhere outside the Delete View .如果您点击Delete View之外的任何位置,我想向后滑动。 So how to do it?那么该怎么做呢?

First off, to know which cell is swiped the SwipeViews needs an id.首先,要知道哪个单元格被刷了 SwipeViews 需要一个 id。 If you don't want to set them from external I guess this will do:如果您不想从外部设置它们,我想这会:

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

Then you need to track which cell is swiped, the SwiftUI way of relaying data to siblings is by a Binding that is saved in it's parent.然后您需要跟踪哪个单元格被刷过,SwiftUI 将数据中继给兄弟姐妹的方式是通过保存在其父级中的绑定。 Read up on how to pass data around SwiftUI Views. 阅读有关如何在 SwiftUI 视图周围传递数据的信息。 If you want to be lazy you can also just have a static object that saves the selected cell:如果你想偷懒,你也可以只使用一个 static object 来保存选定的单元格:

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

Then you have to update the swipedCell.然后你必须更新 swipedCell。 We want the cell to close when we START swiping on a different cell:当我们开始在不同的单元格上滑动时,我们希望单元格关闭:

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

And when a cell is open we save it:当一个单元格打开时,我们保存它:

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

Then we have to respond to changes of the swipedCell.然后我们必须响应 swipedCell 的变化。 We can do that by adding an onChange inside the body of SwipeView:我们可以通过在 SwipeView 的主体中添加一个 onChange 来做到这一点:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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