SwiftUI 미니멀리스트 튜토리얼 18: SwipeCard 카드 슬라이딩 효과 사용(1부)

함께 쓰는 습관을 들이세요! "너겟 데일리 뉴플랜 · 4월 업데이트 챌린지" 참여 18일차입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다 .

오늘의 말씀: 내용이 중요하면 중요한 내용을 반복하십시오.

Gestures이 장에서는 제스처와 Animations애니메이션을 사용하여 SwipeCard카드를 스와이프 하는 방법을 배웁니다 .

交友类的App그들 중 많은 곳에서 " 向左向右滑动卡片" 상호작용이 있음을 알 수 있습니다. 사용자는 오른쪽으로 스와이프하여 사진을 제공 喜欢点赞할 수 있고 왼쪽으로 스와이프하면 사진 포인트를 줄 수 있습니다 不喜欢.

283.png

SwipeCard따라서 이 장에서는 카드 슬라이딩 상호 작용 효과와 유사한 간단한 응용 프로그램 을 빌드해 보겠습니다 .

좋아, 시작하자!

먼저 이라는 새 프로젝트를 만듭니다 SwiftUISwipeCard.

284.png

먼저 Assets.xcassets자료로 사용할 사진 배치를 가져옵니다. 일련의 사진이라면 풍경 사진이나 인물 사진, 음식 사진을 찾을 수 있습니다. 코드에서 이미지를 더 쉽게 찾고 참조할 수 있도록 이미지 이름을 바꾸는 것을 잊지 마십시오.

285.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顶部导航栏视图,效果如下:

286.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()
        }
    }
}
复制代码

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

288.png

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

struct ContentView: View {
    var body: some View {

        VStack {

            TopBarMenu()
            CardView()

            Spacer(minLength: 20)

            BottomBarMenu()
        }
    }
}
复制代码

289.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
复制代码

290.png

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

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

291.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

그래서 우리는 첫 번째 사진을 顶部卡片디스플레이로 얻었습니다.

292.png

계층 문제 보기

다음으로 해결 第二个问题합니다.

만약 우리가 미래에 무한한 수의 카드를 가진다면 무한한 수의 뷰를 생성하는 것은 분명히 비현실적입니다. 다른 방법을 생각할 수 있습니까?

방법도 매우 간단합니다 사실 생각해보면 우리 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
}()
复制代码

293.png

새로운 배열을 정의하였으므로 이미지 값을 구하는 방법에서 매개변수로 읽은 값을 판정 배열 로 바꾸는 albums것을 잊지 마십시오 .zIndexindexalbums

이런 식으로 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卡片滑动效果的使用(上)의 부분에서는 기본 스타일과 일부 준비 작업만 완료되었습니다.

와서 사용해 보세요!

이 칼럼이 도움이 되셨다면 좋아요, 댓글, 팔로우 부탁드립니다~

추천

출처juejin.im/post/7087760928883605518