함께 쓰는 습관을 들이세요! "너겟 데일리 뉴플랜 · 4월 업데이트 챌린지" 참여 18일차입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다 .
오늘의 말씀: 내용이 중요하면 중요한 내용을 반복하십시오.
Gestures
이 장에서는 제스처와 Animations
애니메이션을 사용하여 SwipeCard
카드를 스와이프 하는 방법을 배웁니다 .
交友类的App
그들 중 많은 곳에서 " 向左向右滑动卡片
" 상호작용이 있음을 알 수 있습니다. 사용자는 오른쪽으로 스와이프하여 사진을 제공 喜欢点赞
할 수 있고 왼쪽으로 스와이프하면 사진 포인트를 줄 수 있습니다 不喜欢
.
SwipeCard
따라서 이 장에서는 카드 슬라이딩 상호 작용 효과와 유사한 간단한 응용 프로그램 을 빌드해 보겠습니다 .
좋아, 시작하자!
먼저 이라는 새 프로젝트를 만듭니다 SwiftUISwipeCard
.
먼저 Assets.xcassets
자료로 사용할 사진 배치를 가져옵니다. 일련의 사진이라면 풍경 사진이나 인물 사진, 음식 사진을 찾을 수 있습니다. 코드에서 이미지를 더 쉽게 찾고 참조할 수 있도록 이미지 이름을 바꾸는 것을 잊지 마십시오.
스와이프 기능을 구현하기 전에 먼저 메인 페이지를 만들고 UI
메인 페이지를 세 부분으로 나눕니다.
1. TopBarMenu
상단 탐색 모음
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
顶部导航栏视图,效果如下:
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()
}
}
}
复制代码
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
主要页面中展示它,效果如下:
我们进一步美化下样式,使用Spacer()
分开CardView
卡片视图和BottomBarMenu
底部导航栏视图,我们保持最小20
的区域,就得到了下面的效果。
struct ContentView: View {
var body: some View {
VStack {
TopBarMenu()
CardView()
Spacer(minLength: 20)
BottomBarMenu()
}
}
}
复制代码
好了,基础的样式我们做完了。
交互逻辑分析
接下来,可以实现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
复制代码
然后,我们在ContentView
主视图使用ZStack
包裹CardView
卡片视图,再使用ForEach
循环遍历album
数组所有数据。
//卡片视图
ZStack {
ForEach(album) { album in
CardView(name: album.name, image: album.image)
}
}
复制代码
我们发现,模拟器突然换了一张图片,这是因为我们定义的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
그래서 우리는 첫 번째 사진을 顶部卡片
디스플레이로 얻었습니다.
계층 문제 보기
다음으로 해결 第二个问题
합니다.
만약 우리가 미래에 무한한 수의 카드를 가진다면 무한한 수의 뷰를 생성하는 것은 분명히 비현실적입니다. 다른 방법을 생각할 수 있습니까?
방법도 매우 간단합니다 사실 생각해보면 우리 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
}()
复制代码
새로운 배열을 정의하였으므로 이미지 값을 구하는 방법에서 매개변수로 읽은 값을 판정 배열 로 바꾸는 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
내용이 너무 많아 소화를 돕기 위해 2장으로 나누어 진행합니다.
SwipeCard卡片滑动效果的使用(上)
의 부분에서는 기본 스타일과 일부 준비 작업만 완료되었습니다.
와서 사용해 보세요!
이 칼럼이 도움이 되셨다면 좋아요, 댓글, 팔로우 부탁드립니다~