实战教程·元宇宙来了,准备好你的电子名片了吗?(六)
前提回顾
在上一章节中,我们学习了Model-View-ViewModel架构模式,并实现了添加、删除方法。其中删除方法使用的是最简单的,利用SwiftUI自带的contextMenu上下文菜单按钮控件,实现长按唤起删除操作,点击删除操作调用ViewModel视图模型中的删除方法删除指定ID的数据项。
而在卡片式列表应用中,常用的删除方法是横向滑动唤起删除的方法,网上也有很多使用SwiftUI自带的EditButton唤起横向删除的方法,但总有这样那样的原因导致自带的滑动删除并不好用。
在本章中,我们将学习一种使用gesture手势修饰符实现滑动删除的交互。
交互动作:向左滑动唤起删除操作
在SwiftUI提供的手势操作中,有onTapGesture点击手势、LongPressGesture长按手势、DragGesture拖拽手势三种主要操作。而要使用这三种手势,需要使用到gesture手势修饰符,如下代码所示:
// 拖拽手势 .gesture( DragGesture() .onChanged { value in // 拖动时的操作 } .onEnded { value in // 拖动结束时操作 } )
使用DragGesture拖拽手势前,需要使用@GestureState属性包装器定义一个拖拽位置参数viewState,用来记录我们的拖拽前的初始位置CGSize.zero,也用来监听和更新UI,如下代码所示:
@State var viewState = CGSize.zero
因为我们要拖动的是单张身份卡片,因此需要给身份卡片构件CardView添加拖动位置的偏移量修饰符,如下代码所示:
//设置只能从右往左拖动 .offset(x: self.viewState.width < 0 ? self.viewState.width : 0)
上述代码中,我们给CardView的视图内容添加了offset偏移量修饰符,设置只能沿X轴拖动,并添加条件如果拖拽前的初始位置viewState小于0,则可被沿X轴横向拖拽,如果大于等于0,即向右边拖拽时,则维持位置为0,保证CardView的视图内容只能从右往左拖动。
紧接着,我们给DragGesture手势增加更新方法,如下代码所示:
// 拖拽手势 .gesture( DragGesture() .onChanged { value in // 拖动时的操作 self.viewState = value.translation } .onEnded { value in // 拖动结束时操作 self.viewState = .zero } )
上述代码中,我们在onChanged拖动时,让身份卡的位置viewState等于拖动的位置translation,如此便实现了卡片拖动动作。而在onEnded拖动结束时,我们让身份卡片回到初始的位置zero。
完成之后,我们可以尝试拖动下卡片,如下图所示:
为了突出当前正在进行拖拽删除的操作,我们可以给身份卡片CardView在拖拽时增加样式,比如在向左拖拽到左边一定位置的时候,让卡片填充一个背景颜色。
那么首先我们先声明拖动到某个位置是操作删除的位置,并还需再声明一个变量告知系统当前是否在执行删除操作,如下代码所示:
@State var valueToBeDeleted: CGFloat = -75 @State var readyToBeDeleted: Bool = false
下一步我们可以在拖动时添加判断当前拖动位置是否达到准备删除的位置,如下代码所示:
self.readyToBeDeleted = self.viewState.width < self.valueToBeDeleted ? true : false
并且在删除时,给身份卡片修改背景色,如下代码所示:
.background(self.readyToBeDeleted ? Color(.systemRed) : .white)
当然,拖动结束后,还需要更新readyToBeDeleted是否操作更新的状态为false,如下代码所示:
self.readyToBeDeleted = false
样式完成后,我们来实现删除逻辑,我们在ViewModel视图模型中创建了删除方法deleteItem,deleteItem方法需要基于卡片ID进行指定删除,而在CardView卡片构件中,并没有ID,而数据集和其ID是在ContentView视图中存在。
要想获得UUID,我们还需要在ViewModel视图模型中创建一个获得数据UUID的方法,如下代码所示:
// 获得数据项的UUID func getItemById(itemId: UUID) -> Model? { return models.first(where: { $0.id == itemId }) ?? nil }
上述代码中,我们创建了一个获得数据项的ID的方法,通过传入点击项的UUID,然后返回对应数据项在数据集中的UUID,告知系统当前操作的数据项是models数组中的哪一个数据。
然后我们再回到ContentView视图中,在CardView身份卡片视图中声明相关的变量,如下代码所示:
var viewModel: ViewModel var itemId: UUID var item: Model? { return viewModel.getItemById(itemId: itemId) }
上述代码中,我们使用全局变量引用ViewModel模型视图,并且声明了两个参数itemId和item。itemId为UUID格式,作为数据项的唯一标识符,item为符合Model模型的数据项,用于通过ID找到Model模型的数据。
由于CardView声明了变量,因此在引用CardView的地方需要绑定相关的参数,如下代码所示:
// 卡片视图 CardView(platformIcon: item.platformIcon, title: item.title, platformName: item.platformName, indexURL: item.indexURL, viewModel: viewModel, itemId: item.id)
下一步,我们就可以拖动结束时,判断身份卡是否拖动到删除的位置,如果是,则调用ViewModel视图模型的deleteItem删除数据项方法,删除指定ID的数据项,如下代码所示:
if self.viewState.width < self.valueToBeDeleted { self.viewModel.deleteItem(itemId: itemId) }
我们尝试拖拽卡片操作下删除操作的交互,如下图所示: