如何在SwiftUI中使用自定义滑动动作?
我试图使用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)
}
}
}
}
答案 0 :(得分:5)
它可以通过以下方式完成:
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())
}
}
并拥有TableViewConfigurator
来搜索List
后面的表格视图
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)
}
}
}
答案 1 :(得分:4)
在 iOS 15 中,我们终于可以使用原生的 Swipe Actions:
func swipeActions<T>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, content: () -> T) -> some View where T : View
它们可以像 ForEach
或 onMove
一样附加到 onDelete
容器:
List {
ForEach(appointmentsViewModel.appointments.identified(by: \.id)) { appointment in
Row_Appointments(appointment: appointment)
}
.swipeActions(edge: .trailing) {
Button {
print("Hi")
} label: {
Label("Important", systemImage: "pencil")
}
}
}
答案 2 :(得分:3)
基于Michał Ziobro answer,使用Introspect简化了表格视图委托的设置。
请注意,这将覆盖表视图委托。
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())
}
}
答案 3 :(得分:1)
从Xcode 11 beta 4开始,SwiftUI不支持List
项目的自定义滑动操作。
最好实现一个不同的用户界面,例如添加一个切换按钮作为列表项的子视图,或者将adding a context menu添加到列表项。
请注意,您必须使用Beta 4或更高版本才能在iOS上使用contextMenu
修饰符。
答案 4 :(得分:1)
很高兴看到 iOS 15 通过易于使用的 API 将期待已久的 .swipeActions
视图修饰符引入 SwiftUI 中的 List
。
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")
}
}
}
}
}
动作按列出的顺序出现,从起始边缘向内开始。
上面的例子产生:
请注意,swipeActions
会覆盖 onDelete
处理程序(如果在 ForEach
上提供)
答案 5 :(得分:0)
我想要同样的,现在有以下实现。
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()
}
}
答案 6 :(得分:0)
现在有了 IOS 15 和 Swift 5.5,我们可以添加这样的滑动操作
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)")
}
}
}