0
点赞
收藏
分享

微信扫一扫

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)


今日职言:如果内容重要,就要重复强调重要的内容。

在本章中,你将学会如何使用​​Gestures​​​手势和​​Animations​​​动画实现​​SwipeCard​​卡片滑动的效果。

在很多​​交友类的App​​​当中,我们可以看到有一个“​​向左向右滑动卡片​​​”的交互,用户向右滑动可以给照片​​喜欢点赞​​​,向左滑动可以给照片点​​不喜欢​​。

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_导航栏

那么在这章,我们来试试构建一个类似​​SwipeCard​​卡片滑动交互效果的简单应用。

好了,我们开始吧!

首先,创建一个新项目,命名为​​SwiftUISwipeCard​​。

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_SwiftUI_02

我们先在​​Assets.xcassets​​导入一批图片,作为素材使用。可以找一些风景图片,或者人像图片、食物图片,只要是一个系列的图片集就行。不要忘记给图片重新命名,以便于我们在代码更好地找到和引用图片。

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_导航栏_03

在实现滑动功能之前,让我们先创建主要的​​UI​​页面,我们将把主页面分成三个部分:

1. ​​TopBarMenu​​顶部导航栏

  1. ​CardView​​卡片视图

3.​​BottomBarMenu​​底部菜单栏

TopBarMenu顶部导航栏

在这里我们创建一个新的结构体页面来展示​​TopBarMenu​​​顶部导航栏视图,我们命名为​​TopBarMenu​​。

代码如下:

//顶部导航栏
struct TopBarMenu: View {
var body: some View {
HStack {
Image(systemName: "ellipsis.circle")
.font(.system(size: 30))

Spacer()

Image(systemName: "heart.circle")
.font(.system(size: 30))
}.padding()
}
}

这里我们没有使用​​.NavigationView​​​顶部导航栏,是因为在很多时候,我们的导航栏都需要很多定制化的功能,而在​​.NavigationView​​​顶部导航栏中可能很难支持到我们实际的业务,所以​​“成熟的”​​程序猿都喜欢自己写顶部导航栏样式。

上面我们做的​​TopBarMenu​​​顶部导航栏很简单,就2张图片,使用横向​​HStack​​​排布。然后我们在​​CardView​​​里引用​​TopBarMenu​​顶部导航栏视图,效果如下:

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_数组_04

CardView卡片视图

接着,我们创建一个新的结构体页面来展示卡片视图,命名为​​CardView​​。

代码如下:

//卡片视图
struct CardView: View {
var body: some View {
Image("image01")
.resizable()
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(10)
.padding(.horizontal, 15)

.overlay(
VStack {
Text("图片01")
.font(.system(.headline, design: .rounded)).fontWeight(.bold)
.padding(.horizontal, 30)
.padding(.vertical, 10)
.background(Color.white)
.cornerRadius(5)
}
.padding([.bottom], 20), alignment: .bottom
)
}
}

​CardView​​​卡片视图也非常简单,我们放在一个​​Image​​​图片,让将一个​​Text​​​文字“悬浮”在图片底部。我们在​​CardView​​​里引用​​CardView​​​卡片视图,由于​​CardView​​​卡片视图和​​TopBarMenu​​​顶部导航栏是纵向排列,我们使用​​VStack​​包裹住。

struct ContentView: View {
var body: some View {
VStack {
TopBarMenu()
CardView()
}
}
}

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_数组_05

BottomBarMenu底部菜单栏

底部导航栏也是如此,我们创建一个新的结构体页面叫做BottomBarMenu。

代码如下:

// 底部导航栏
struct BottomBarMenu: View {
var body: some View {

HStack {

Image(systemName: "xmark")
.font(.system(size: 30))
.foregroundColor(.black)

Button(action: {

}) {

Text("立即选择")
.font(.system(.subheadline, design: .rounded)).bold()
.foregroundColor(.white)
.padding(.horizontal, 35)
.padding(.vertical, 15)
.background(Color.black)
.cornerRadius(10)
}.padding(.horizontal, 20)

Image(systemName: "heart")
.font(.system(size: 30))
.foregroundColor(.black)
}
}
}

​BottomBarMenu​​​底部导航栏也是我们自己写的,使用3个元素,2个​​Image​​​图片,1个​​Text​​​文字按钮。然后也在​​ContentView​​主要页面中展示它,效果如下:

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_底部导航栏_06

我们进一步美化下样式,使用​​Spacer()​​​分开​​CardView​​​卡片视图和​​BottomBarMenu​​​底部导航栏视图,我们保持最小​​20​​的区域,就得到了下面的效果。

struct ContentView: View {
var body: some View {

VStack {

TopBarMenu()
CardView()

Spacer(minLength: 20)

BottomBarMenu()
}
}
}

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_SwiftUI_07

好了,基础的样式我们做完了。

交互逻辑分析

接下来,可以实现​​SwipeCard​​​卡片滑动的效果了。先解释一下​​SwipeCard​​卡片滑动的原理,你可以它想象成一组叠在一起的卡片,每张卡片都显示一张照片。

我们将​​最上面​​​的那张卡,即​​第一张​​​图片,稍微​​向左或向右​​​刷一下,就会​​打开​​​下面的下一张卡片,也就是​​第二张图片​​。

如果你​​放开​​​卡片,卡片会回到​​原来​​​的位置。但如果你​​用力滑动​​​图片卡片,就可以将图片卡片​​“丢掉”​​​,系统就会将把​​第二张图片​​​向前拉变成​​最上面的图片​​展示。

我们了解了原理后,我们先实现​​CardView​​​卡片部分的内容。这里使用​​ZStack​​将一堆卡片“堆在”一起,而图片卡片的遍历方式之前的章节已经学过。

//创建Album定义变量
struct Album: Identifiable {
var id = UUID()
var name: String
var image: String
}

//创建演示数据
var album = [
Album(name: "图片01", image: "image01"),
Album(name: "图片02", image: "image02"),
Album(name: "图片03", image: "image03"),
Album(name: "图片04", image: "image04"),
Album(name: "图片05", image: "image05"),
Album(name: "图片06", image: "image06"),
Album(name: "图片07", image: "image07"),
Album(name: "图片08", image: "image08"),
Album(name: "图片09", image: "image09")
]

由于我们之前定义的​​CardView​​​卡片视图中使用的是​​Image​​​图片和​​Text​​文字。

这里我们定义两个常量替换它,这样我们就可以在​​ContentView​​主视图定义的值了。

let name: String
let image: String

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_Swift_08

然后,我们在​​ContentView​​​主视图使用​​ZStack​​​包裹​​CardView​​​卡片视图,再使用​​ForEach​​​循环遍历​​album​​数组所有数据。

//卡片视图
ZStack {
ForEach(album) { album in
CardView(name: album.name, image: album.image)
}
}

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_底部导航栏_09

我们发现,模拟器突然换了一张图片,这是因为我们定义的​​album​​​图片数组,使用​​ForEach​​​循环时是一张张遍历的,最后遍历完是​​album​​图片数组最后一张图片。

在​​ForEach​​循环中,第一张图片放在了最底下。因此,最后一张图片也就成了最上面的照片。

因此,虽然我们实现了​​album​​图片数组的遍历,但还是存在两个问题:

1、本该是第一张图片,现在变成了最后一张。

2、现在我们只有9张图片卡片,但如果之后我们有更多的图片卡片的时候,我们是否应该为每张图片创建一个卡片视图?

album数组图片排序问题

我们一个个解决,首先第一个问题,卡片顺序的问题。好在​​SwiftUI​​​提供了​​zIndex​​​修饰符来来确定​​ZStack​​​中视图的顺序,​​zIndex​​​值越高,视图​​层级​​也就越高。

我们创建一个方法,来得到卡片视图的​​zIndex​​值。

//获得图片zIndex值

func isTopCard(cardView: Album) -> Bool {
guard let index = album.firstIndex(where: { $0.id == cardView.id }) else {
return false
}
return index == 0

上面的方法函数接受一个卡片视图,并找出它的索引,告诉我们这个卡片视图是不是最上面的那个。

接下来,我们在​​CardView​​卡片视图引用这个方法。

.zIndex(self.isTopCard(cardView: album) ? 1 : 0)

我们为每个卡片视图添加了​​zIndex​​​修饰符,最上面的卡片我们给它赋了一个​​更高​​​的​​zIndex​​值。

于是乎,我们得到了第一张图片作为​​顶部卡片​​展示。

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_底部导航栏_10

视图层级问题

接下来,我们解决​​第二个问题​​。

如果我们以后有无数个卡片,如果要创建无数个视图显然不现实,我们是不是可以想想其他方法?

方法也很简单,其实想想,我们只需要​​2个卡片视图​​​就行了,​​滑动一个​​​,就​​显示另一个卡片视图​​​,再滑动走一个,又回来我们第一个视图,只是展示的图片不一样就行了。这样不管多少图片,我们只需要​​2个卡片视图来回切换​​就可以完成我们想要的效果。

说干就干。

按照原理,我们就不需要初始化那么多图片结构,只需要前2个,当第一个卡片视图被丢掉,我们就添加第二个。

//创建2个卡片视图
var albums: [Album] = {

var views = [Album]()

for index in 0..<2 {
views.append(Album(name: album[index].name, image: album[index].image))
}
return views

SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)_导航栏_11

由于我们定义了一个新的数组​​albums​​​,别忘了在获得图片​​zIndex​​​值的方法里,要把​​index​​​参数读取的值要换成判断​​albums​​数组。

这样,我们就只构建了2个视图,就完成了图片数组的遍历展示。

完整代码如下:

import SwiftUI

//创建Album定义变量
struct Album: Identifiable {
var id = UUID()
var name: String
var image: String

}

//创建演示数据
var album = [ Album(name: "图片01", image: "image01"), Album(name: "图片02", image: "image02"), Album(name: "图片03", image: "image03"), Album(name: "图片04", image: "image04"), Album(name: "图片05", image: "image05"), Album(name: "图片06", image: "image06"), Album(name: "图片07", image: "image07"), Album(name: "图片08", image: "image08"), Album(name: "图片09", image: "image09")]

//创建2个卡片视图
var albums: [Album] = {

var views = [Album]()

for index in 0..<2 {
views.append(Album(name: album[index].name, image: album[index].image))
}
return views
}()

struct ContentView: View {
var body: some View {

VStack {

//顶部导航栏
TopBarMenu()

//卡片视图
ZStack {
ForEach(albums) { album in
CardView(name: album.name, image: album.image)
.zIndex(self.isTopCard(cardView: album) ? 1 : 0)
}
}

Spacer(minLength: 20)

//底部导航栏
BottomBarMenu()
}
}

//获得图片zIndex值
func isTopCard(cardView: Album) -> Bool {
guard let index = albums.firstIndex(where: { $0.id == cardView.id }) else {
return false
}
return index == 0
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

// 顶部导航栏
struct TopBarMenu: View {
var body: some View {

HStack {

Image(systemName: "ellipsis.circle")
.font(.system(size: 30))

Spacer()

Image(systemName: "heart.circle")
.font(.system(size: 30))
}.padding()
}
}

//卡片视图
struct CardView: View {

let name: String
let image: String

var body: some View {

Image(image)
.resizable()
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(10)
.padding(.horizontal, 15)

.overlay(
VStack {
Text(name)
.font(.system(.headline, design: .rounded)).fontWeight(.bold)
.padding(.horizontal, 30)
.padding(.vertical, 10)
.background(Color.white)
.cornerRadius(5)
}
.padding([.bottom], 20), alignment: .bottom
)
}
}

// 底部导航栏
struct BottomBarMenu: View {
var body: some View {

HStack {
Image(systemName: "xmark")
.font(.system(size: 30))
.foregroundColor(.black)

Button(action: {

}) {
Text("立即选择")
.font(.system(.subheadline, design: .rounded)).bold()
.foregroundColor(.white)
.padding(.horizontal, 35)
.padding(.vertical, 15)
.background(Color.black)
.cornerRadius(10)
}.padding(.horizontal, 20)

Image(systemName: "heart")
.font(.system(size: 30))
.foregroundColor(.black)
}
}
}

未完待续

由于​​SwipeCard​​卡片滑动效果涉及的内容太多,为帮助消化,这里分为上下两章来写。

​SwipeCard卡片滑动效果的使用(上)​​的部分就只完成了基础的样式和一些准备工作,涉及到的知识点很多,望花点时间消化。

快来动手试试吧!

如果本专栏对你有帮助,不妨点赞、评论、关注~

举报

相关推荐

0 条评论