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

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

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

好了,我们开始吧!

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

image.png

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

image.png

在实现滑动功能之前,让我们先创建主要的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顶部导航栏视图,效果如下:

image.png

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


image.png

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主要页面中展示它,效果如下

image.png

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


struct ContentView: View {
    var body: some View {
        VStack {
            TopBarMenu()
            CardView()
            Spacer(minLength: 20)
            BottomBarMenu()
        }
    }
}

image.png

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

交互逻辑分析


接下来,可以实现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


image.png

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


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


image.png


我们发现,模拟器突然换了一张图片,这是因为我们定义的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值。

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

image.png

视图层级问题


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

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


方法也很简单,其实想想,我们只需要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
}()

image.png

由于我们定义了一个新的数组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卡片滑动效果的使用(上)的部分就只完成了基础的样式和一些准备工作,涉及到的知识点很多,望花点时间消化。

快来动手试试吧!




相关文章
|
iOS开发
SwiftUI极简教程13:NavigationView导航栏使用
SwiftUI极简教程13:NavigationView导航栏使用
2690 2
SwiftUI极简教程13:NavigationView导航栏使用
|
存储 Swift
SwiftUI极简教程41:使用Segment、LazyVGrid和ImagePicker构建一个Logo生成器
在本章中,你将学会使用Segment分段器、LazyVGrid垂直网格、ImagePicker图片选择器构建一个Logo生成器。 在上一章中,我们完善了SearchBar搜索栏、TabView底部导航,还有做了一个Loading加载动作。最近突然有个想法,如果把色卡和图片进行组合,这不就是一个简单的Logo了吗?我能不能做个Logo生成器? 说干就干,我们继续完成App的相关内容。
943 0
SwiftUI极简教程41:使用Segment、LazyVGrid和ImagePicker构建一个Logo生成器
|
存储 索引
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
909 0
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
|
API UED 开发者
鸿蒙next版开发:ArkTS组件通用属性(透明度设置)
在HarmonyOS 5.0中,ArkTS引入了透明度设置属性`opacity`,允许开发者自定义组件的透明度,从而创建复杂的视觉效果和提升用户体验。本文详细解读了`opacity`属性的用法,并提供了示例代码,展示了如何在不同透明度下展示组件。透明度设置在UI开发中具有多种用途,如创建重叠效果、增强美观性和实现动画效果。
1373 7
|
iOS开发
加载中,加载中......使用SwiftUI设计2种Loading动画
加载中,加载中......使用SwiftUI设计2种Loading动画
1023 0
|
存储 人机交互 API
8个SwiftUI的小技巧让隔壁同事两眼放光,直呼太卷了
在日常的SwiftUI开发过程中,常常会出现一些“小BUG”让我们措手不及、头顶发凉。这些问题可能是由于我们自己不熟悉SwiftUI语法导致的,也有是SwiftUI本身自带的缺陷,毕竟是一个新兴语言。 那么本章就介绍一些SwiftUI开发的小技巧,帮助我们避避那些让我们头秃的坑。
1552 0
8个SwiftUI的小技巧让隔壁同事两眼放光,直呼太卷了
展开&收起,使用SwiftUI搭建一个侧滑展开页面交互
展开&收起,使用SwiftUI搭建一个侧滑展开页面交互
550 0
|
编解码 Swift iOS开发
SwiftUI极简教程24:构建一个Banner图片轮播(上)
SwiftUI极简教程24:构建一个Banner图片轮播(上)
1362 0
SwiftUI极简教程24:构建一个Banner图片轮播(上)
|
搜索推荐 数据安全/隐私保护
颜值即正义,使用SwiftUI搭建个人信息、账号绑定、通用设置详情页
颜值即正义,使用SwiftUI搭建个人信息、账号绑定、通用设置详情页
488 0
|
vr&ar Swift
大师学SwiftUI第9章Part 1 - 异步并发之Task、Async、Await和错误
苹果系统借助现代处理器的多核可同步执行多条代码,提升同一时间内程序所能执行的任务。例如,一段代码从网上下载文件,另一段代码可以在屏幕上显示进度。此时,我们不能等待第一个执行完后再执行第二个,而必须要同步执行这两个任务。
665 0