SwiftUI -列表中的自定义滑动操作

lmvvr0a8  于 2023-11-16  发布在  Swift
关注(0)|答案(7)|浏览(145)

如何在SwiftUI中使用自定义Swipe Actions?
我尝试使用UIKit框架来让这些在SwiftUI中工作。但这对我不起作用。

import SwiftUI
import UIKit


    init() {
        override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
            let important = importantAction(at: indexPath)
            return UISwipeActionsConfiguration(actions: [important])
        }
        func importantAction(at indexPath: IndexPath) -> UIContextualAction {
            let action = UIContextualAction(style: .normal, title: "Important") { (action, view, completion) in
                print("HI")
            }
            action.backgroundColor = UIColor(hue: 0.0861, saturation: 0.76, brightness: 0.94, alpha: 1.0) /* #f19938 */
            action.image = UIImage(named: "pencil")
            return action
        }
    }




struct TestView: View {

      NavigationView {
               List {
                    ForEach(appointmentsViewModel.appointments.identified(by: \.id)) { appointment in Row_Appointments(appointment: appointment)
                }.onDelete(perform: delete)
            }
        }
    }
}

字符串

ztyzrc3y

ztyzrc3y1#

现在,在iOS 15,Swift 5.5中,我们可以添加这样的Swipe操作

struct ContentView: View {
    @State private var total = 0

    var body: some View {
        NavigationView {
            List {
                ForEach(1..<100) { i in
                    Text("\(i)")
                        .swipeActions(edge: .leading) {
                            Button {
                                total += i
                            } label: {
                                Label("Add \(i)", systemImage: "plus.circle")
                            }
                            .tint(.indigo)
                        }
                        .swipeActions(edge: .trailing) {
                            Button {
                                total -= i
                            } label: {
                                Label("Subtract \(i)", systemImage: "minus.circle")
                            }
                        }
                }
            }
            .navigationTitle("Total: \(total)")
        }
    }
}

字符串

ego6inou

ego6inou2#

如果您的部署目标是iOS 15(或更高版本),则可以使用swipeActions modifier自定义列表项的滑动操作。

这也适用于watchOS 8和macOS 12。
这些操作系统将于2021年底发布。
在2021年后期版本的SwiftUI之前,不支持List项目的自定义滑动操作。
如果你需要一个旧版本,你可能会更好地实现一个不同的用户界面,比如添加一个切换按钮作为列表项的子视图,或者adding a context menu到列表项。

g6ll5ycj

g6ll5ycj3#

iOS 15+

在iOS 15中,我们终于可以使用原生Swipe Actions了:

func swipeActions<T>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, content: () -> T) -> some View where T : View

字符串
它们可以像onMoveonDelete一样附加到ForEach容器:

List {
    ForEach(appointmentsViewModel.appointments.identified(by: \.id)) { appointment in
        Row_Appointments(appointment: appointment)
    }
    .swipeActions(edge: .trailing) {
        Button {
            print("Hi")
        } label: {
            Label("Important", systemImage: "pencil")
        }
    }
}

dkqlctbz

dkqlctbz4#

基于Michał Ziobro answer使用Introspect简化表视图委托设置。
请注意,这将覆盖表视图委托,并可能BREAK一些现有的表视图行为。虽然诸如header hight之类的事情可以通过自己添加方法来自定义委托来修复,但其他可能无法修复。

struct ListSwipeActions: ViewModifier {

    @ObservedObject var coordinator = Coordinator()

    func body(content: Content) -> some View {

        return content
            .introspectTableView { tableView in
                tableView.delegate = self.coordinator
            }
    }

    class Coordinator: NSObject, ObservableObject, UITableViewDelegate {

        func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
            return .delete
        }

        func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

            let archiveAction = UIContextualAction(style: .normal, title: "Title") { action, view, completionHandler in
                // update data source
                completionHandler(true)
            }
            archiveAction.image = UIImage(systemName: "archivebox")!
            archiveAction.backgroundColor = .systemYellow

            let configuration = UISwipeActionsConfiguration(actions: [archiveAction])

            return configuration
        }
    }
}

extension List {
    func swipeActions() -> some View {
        return self.modifier(ListSwipeActions())
    }
}

字符串

hjzp0vay

hjzp0vay5#

它是能够这样做的方式:

List {
                ForEach(items) { (item) in

                    Text("\(item.title)")
                }
                .onDelete(perform: self.delete)
            }.swipeActions()

字符串
然后需要添加这个swipeActions()修饰符

struct ListSwipeActions: ViewModifier {

    @ObservedObject var coordinator = Coordinator()

    func body(content: Content) -> some View {

        return content
            .background(TableViewConfigurator(configure: { tableView in
                delay {
                    tableView.delegate = self.coordinator
                }
            }))
    }

    class Coordinator: NSObject, ObservableObject, UITableViewDelegate {

        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            print("Scrolling ....!!!")
        }

        func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
            return .delete
        }

        func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

            let isArchived = false
            let title = isArchived ? NSLocalizedString("Unarchive", comment: "Unarchive") : NSLocalizedString("Archive", comment: "Archive")

            let archiveAction = UIContextualAction(style: .normal, title: title, handler: {
                (action, view, completionHandler) in

                // update data source
                completionHandler(true)
            })
            archiveAction.title = title
            archiveAction.image = UIImage(systemName: "archivebox")!
            archiveAction.backgroundColor = .systemYellow

            let configuration = UISwipeActionsConfiguration(actions: [archiveAction])

            return configuration
        }
    }
}

extension List {

    func swipeActions() -> some View {
        return self.modifier(ListSwipeActions())
    }
}


TableViewConfiguratorList后面搜索表视图,

struct TableViewConfigurator: UIViewControllerRepresentable {

    var configure: (UITableView) -> Void = { _ in }

    func makeUIViewController(context: Context) -> UIViewController {

        UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {

        let tableViews = UIApplication.nonModalTopViewController()?.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()

        for tableView in tableViews {
            self.configure(tableView)
        }
    }
}

gojuced7

gojuced76#

很高兴看到iOS 15将期待已久的.swipeActions视图修改器引入SwiftUI中的List,并提供易于使用的API。

List {
    ForEach(store.messages) { message in
        MessageCell(message: message)
            .swipeActions(edge: .leading) {
                Button { store.toggleUnread(message) } label: {
                    if message.isUnread {
                        Label("Read", systemImage: "envelope.open")
                    } else {
                        Label("Unread", systemImage: "envelope.badge")
                    }
                }
            }
            .swipeActions(edge: .trailing) {
                Button(role: .destructive) {
                    store.delete(message)
                } label: {
                    Label("Delete", systemImage: "trash")
                }
                Button { store.flag(message) } label: {
                    Label("Flag", systemImage: "flag")
                }
            }
        }
    }
}

字符串
操作按列出的顺序显示,从起始边开始向内。
上面的例子产生:


的数据
请注意,如果提供了ForEach上可用的onDelete处理程序,则swipeActions将覆盖该处理程序
Read more in Apple's developer docs

pdkcd3nj

pdkcd3nj7#

我想要的是相同的,现在有了以下实现。
SwipeController检查何时执行滑动动作并执行SwipeAction,现在你可以在executeAction函数中的打印行下添加你的滑动动作。但是它最好从这里创建一个抽象类。
然后在SwipeLeftRightContainer结构体中,我们在DragGesture中拥有大部分逻辑。它所做的是当你拖动时,它会改变偏移量,然后调用SwipeController来查看是否达到了向左或向右滑动的阈值。然后当你完成拖动时,它会进入DragGesture的onEnded回调。在这里,我们将重置偏移量,让SwipeController决定执行一个动作。
请记住,视图中的许多变量对于iPhone X来说都是静态的,因此您应该将它们更改为最适合的变量。
这也是为了创建一个左右滑动的动作,当然你可以根据自己的使用来调整它。

import SwiftUI

/** executeRight: checks if it should execute the swipeRight action
    execute Left: checks if it should execute the swipeLeft action
    submitThreshold: the threshold of the x offset when it should start executing the action
*/
class SwipeController {
    var executeRight = false
    var executeLeft = false
    let submitThreshold: CGFloat = 200
    
    func checkExecutionRight(offsetX: CGFloat) {
        if offsetX > submitThreshold && self.executeRight == false {
            Utils.HapticSuccess()
            self.executeRight = true
        } else if offsetX < submitThreshold {
            self.executeRight = false
        }
    }
    
    func checkExecutionLeft(offsetX: CGFloat) {
        if offsetX < -submitThreshold && self.executeLeft == false {
            Utils.HapticSuccess()
            self.executeLeft = true
        } else if offsetX > -submitThreshold {
            self.executeLeft = false
        }
    }
    
    func excuteAction() {
        if executeRight {
            print("executed right")
        } else if executeLeft {
            print("executed left")
        }
        
        self.executeLeft = false
        self.executeRight = false
    }
}

struct SwipeLeftRightContainer: View {
    
    var swipeController: SwipeController = SwipeController()
    
    @State var offsetX: CGFloat = 0
    
    let maxWidth: CGFloat = 335
    let maxHeight: CGFloat = 125
    let swipeObjectsOffset: CGFloat = 350
    let swipeObjectsWidth: CGFloat = 400
    
    @State var rowAnimationOpacity: Double = 0
    var body: some View {
        ZStack {
            Group {
                HStack {
                    Text("Sample row")
                    Spacer()
                }
            }.padding(10)
            .zIndex(1.0)
            .frame(width: maxWidth, height: maxHeight)
            .cornerRadius(5)
            .background(RoundedRectangle(cornerRadius: 10).fill(Color.gray))
            .padding(10)
            .offset(x: offsetX)
            .gesture(DragGesture(minimumDistance: 5).onChanged { gesture in
                withAnimation(Animation.linear(duration: 0.1)) {
                    offsetX = gesture.translation.width
                }
                swipeController.checkExecutionLeft(offsetX: offsetX)
                swipeController.checkExecutionRight(offsetX: offsetX)
            }.onEnded { _ in
                withAnimation(Animation.linear(duration: 0.1)) {
                    offsetX = 0
                    swipeController.prevLocX = 0
                    swipeController.prevLocXDiff = 0
                    self.swipeController.excuteAction()
                }
            })
            Group {
                ZStack {
                    Rectangle().fill(Color.red).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityDelete)
                    Image(systemName: "multiply").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.trailing, 150)
                }
            }.zIndex(0.9).offset(x: swipeObjectsOffset + offsetX)
            Group {
                ZStack {
                    Rectangle().fill(Color.green).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityLike)
                    Image(systemName: "heart").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.leading, 150)
                }
            }.zIndex(0.9).offset(x: -swipeObjectsOffset + offsetX)
        }
    }
    
    var opacityDelete: Double {
        if offsetX < 0 {
            return Double(abs(offsetX) / 50)
        }
        return 0
    }
    
    var opacityLike: Double {
        if offsetX > 0 {
            return Double(offsetX / 50)
        }
        return 0
    }
}

struct SwipeListView: View {
    
    var body: some View {
        ScrollView {
            ForEach(0..<10) { index in
                SwipeLeftRightContainer().listRowInsets(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
            }
        }
    }
    
}

struct SwipeLeftRight_Previews: PreviewProvider {
    static var previews: some View {
        SwipeListView()
    }
}

字符串

相关问题