SwiftUI Minimalist Tutorial 18: Uso del efecto deslizante de la tarjeta SwipeCard (Parte 1)

¡Acostúmbrate a escribir juntos! Este es el día 18 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

Palabra del día: si el contenido es importante, repite el contenido importante.

En este capítulo, aprenderá a usar Gesturesgestos y Animationsanimaciones para SwipeCarddeslizar tarjetas.

En muchos 交友类的Appde ellos, podemos ver que hay una 向左向右滑动卡片interacción " ", el usuario puede deslizar hacia la derecha para dar una foto 喜欢点赞, y deslizar hacia la izquierda puede dar un punto de foto 不喜欢.

283.png

Entonces, en este capítulo, intentemos construir una SwipeCardaplicación simple similar al efecto de interacción de deslizamiento de cartas.

Muy bien, ¡comencemos!

Primero, crea un nuevo proyecto llamado SwiftUISwipeCard.

284.png

Primero Assets.xcassetsimportamos un lote de imágenes para usarlas como materiales. Puede encontrar algunas imágenes de paisajes, imágenes de retratos, imágenes de alimentos, siempre que se trate de una serie de imágenes. No olvide cambiar el nombre de la imagen para que sea más fácil para nosotros encontrar y hacer referencia a la imagen en el código.

285.png

Antes de implementar la función de deslizamiento, UIprimero creemos la página principal, dividiremos la página principal en tres partes:

1.  TopBarMenuBarra de navegación superior

  1. CardViewvista de tarjeta

3. BottomBarMenuBarra de menú inferior

Barra de navegación superior TopBarMenu

Aquí creamos una nueva página de estructura para mostrar TopBarMenula vista de la barra de navegación superior, la llamamos TopBarMenu.

el código se muestra a continuación:

//顶部导航栏
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()
    }
}
复制代码

Aquí no usamos .NavigationViewla barra de navegación superior porque, en muchos casos, nuestra barra de navegación necesita muchas funciones personalizadas y .NavigationViewpuede ser difícil admitir nuestro negocio real en la barra de navegación superior, por lo “成熟的”que a los programadores les gusta escribir su propia barra de navegación superior . estilo de barra.

上面我们做的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值越高,视图层级也就越高。

Creamos un método para obtener el zIndexvalor de la vista de la tarjeta.

//获得图片zIndex值

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

La función del método anterior toma una vista de tarjeta y encuentra su índice, diciéndonos si esta vista de tarjeta es la superior.

A continuación, hacemos CardViewreferencia a este método en la vista de tarjeta.

.zIndex(self.isTopCard(cardView: album) ? 1 : 0)
复制代码

Agregamos modificadores a cada vista de tarjeta zIndex, y para la tarjeta superior le asignamos un 更高valor zIndex.

Entonces, obtuvimos la primera imagen como una 顶部卡片pantalla.

292.png

Ver problemas de jerarquía

A continuación, resolvemos 第二个问题.

Si tenemos una cantidad infinita de tarjetas en el futuro, obviamente no es realista crear una cantidad infinita de vistas. ¿Podemos pensar en otros métodos?

El método también es muy simple, de hecho, piénsalo, solo tenemos que 2个卡片视图hacerlo , deslizar una vez más y volver a nuestra primera vista, pero la imagen que se muestra es diferente. De esta forma, por muchas fotografías que hagamos, solo nos falta conseguir el efecto que queremos.滑动一个显示另一个卡片视图2个卡片视图来回切换

Hazlo.

De acuerdo con el principio, no necesitamos inicializar tantas estructuras de imagen, solo se necesitan las dos primeras. Cuando se descarta la primera vista de tarjeta, agregamos la segunda.

//创建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

Dado que hemos definido una nueva matriz albums, no olvide reemplazar el valor leído por el parámetro con la matriz de juicio zIndexen el método de obtención del valor de la imagen .indexalbums

De esta manera, solo construimos 2 vistas y completamos la visualización transversal de la matriz de imágenes.

El código completo es el siguiente:

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

Continuará

Dado que el SwipeCardefecto de deslizamiento de cartas implica demasiado contenido, para ayudar a la digestión, se divide en dos capítulos.

SwipeCard卡片滑动效果的使用(上)Solo se han completado el estilo básico y algunos trabajos preparatorios en la parte de .

¡Ven y pruébalo!

Si esta columna es útil para usted, haga clic en Me gusta, comente y siga ~

Supongo que te gusta

Origin juejin.im/post/7087760928883605518
Recomendado
Clasificación