Cómo implementar tarjetas Tinder Swipe en SwiftUI

En junio, escuchamos sobre SwiftUI por primera vez, una forma totalmente nueva de crear y trabajar con elementos de la interfaz de usuario en aplicaciones iOS y macOS (también iPadOS). Se sentía como Navidad en el verano. ¡Es nuevo, es declarativo, es sexy! Y ahora, solo unas pocas semanas después del lanzamiento de iOS 13, podemos comenzar a usar SwiftUI en todos nuestros proyectos. Aprendamos a usar esta increíble herramienta que Apple nos dio para crear las tarjetas Swipe clásicas de Tinder esque.

En este artículo, me gustaría mostrarle cómo lograr una vista y comportamiento de tarjeta similar a Tinder (deslizar a la acción), con solo unas pocas líneas de código.

Para lograr esto, debemos hacer lo siguiente, en orden:

  • Crear vista de usuario
  • Crear vista de navegación
  • Crear BottomBarView
  • Crear vista deslizante
  • Ponga todo esto dentro de ContentView

Entonces comencemos.

yesca swiftui

Vista de usuario


UserView se construye a partir de dos subvistas, una es NameView que contiene el nombre de usuario, edad y pasatiempos, y la segunda vista es solo una vista de avatar que muestra la imagen de perfil del usuario.

struct NameView: View { let name: String let age: Int let hobby: String var body: some View { VStack(alignment: .leading) { Spacer() Text("\(name), \(age)") .font(.title) .fontWeight(.semibold) .foregroundColor(.white) Text(hobby) .font(.system(size: 16)) .fontWeight(.regular) .foregroundColor(.white) } .padding() } } 

Primero, necesitamos definir NameView , esto representará el nombre de usuario, la edad y el hobby. NameView se ajusta al protocolo View , que se utiliza para definir vistas personalizadas en SwiftUI. El protocolo de vista solo tiene un requisito y es definir la propiedad del cuerpo que debe devolver la estructura de vista y describe su comportamiento. Puede consultar más sobre el protocolo View en la documentación oficial de Apple .

Analicemos los objetos que usamos para definir esta Vista :

  • VStack que actúa como un contenedor para todos los objetos alineándolos verticalmente
  • Espaciador que le dice a SwiftUI que esta vista debe estar alineada en la parte inferior
  • Texto que representa la etiqueta con nombre y edad, con las siguientes propiedades:
  • Segundo objeto de texto que tiene propiedades similares y muestra el pasatiempo del usuario

Tenga en cuenta que no estamos utilizando una declaración de devolución aquí, dentro de la propiedad del cuerpo, sino que estamos devolviendo un VStack. SwiftUI está utilizando la propuesta omit-return implementada en Swift 5.0. Puede consultar más sobre esto aquí .

Vista de avatar


Así es como se define AvatarView :

 struct AvatarView: View { let image: UIImage var body: some View { Image(uiImage: image) .resizable() .overlay( Rectangle() .fill(LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .center, endPoint: .bottom)) .clipped() ) .cornerRadius(12.0) } } 

Vamos a sumergirnos en los componentes que están haciendo esta unidad de avatar:

  • Imagen : que muestra la imagen del usuario
  • redimensionable : este método especifica que la imagen debe cambiar de tamaño para ajustarse al lugar donde está incrustada
  • superposición (Rectángulo) : aquí estamos definiendo un degradado que será un buen fondo para NameView, este degradado comienza en el centro de la imagen y termina en la parte inferior, tiene un color claro al inicio y negro en la parte inferior
  • cornerRadius : la imagen tendrá un radio acorralado

Y ahora incrustemos estas dos vistas en una sola vista de contenedor, llamada UserView .

Vista de usuario


 struct UserView: View { let userModel: UserModel var body: some View { ZStack(alignment: .leading) { AvatarView(image: userModel.image) NameView(name: userModel.name, age: userModel.age, hobby: userModel.hobby) } .shadow(radius: 12.0) .cornerRadius(12.0) } } 

Esto es lo que está pasando:

  • ZStack : esta es una vista de pila que alineará a sus hijos en el mismo eje. Puedes leer más sobre ZStack aquí
  • AvatarView : nuestra vista de avatar que contiene la imagen proporcionada a través de UserModel
  • NameView : nuestra vista de nombre que muestra el nombre según el modelo de usuario

Después de todos estos pasos, ejecute la aplicación. Obtendrá la siguiente pantalla:

avatar de yesca

Agreguemos un pequeño método auxiliar ahora. Antes de mostrarle cómo se define NavigationView, creemos un método auxiliar, que se ve así:

 struct ViewFactory { static func button(_ name: String, renderingMode: Image.TemplateRenderingMode = .original) -> some View { Button(action: {}) { Image(name) .renderingMode(renderingMode) } } } 

Aquí, hemos definido un método de fábrica de botones, que crea un nuevo botón a partir de una imagen determinada y un modo de representación. No hay un controlador de acciones, ya que eso queda fuera del alcance de este artículo.

Vista de navegación


 struct NavigationView: View { var body: some View { HStack { ViewFactory.button("profile_icon") Spacer() ViewFactory.button("fire_icon") .scaleEffect(2) Spacer() ViewFactory.button("chat_icon") } } } 

SwiftUI creará automáticamente los espaciadores de igual ancho, y nos dará la siguiente vista de navegación:

vista de navegación

Bottombarview


 struct BottomBarView: View { var body: some View { HStack { ViewFactory.button("back_icon", renderingMode: .template) .foregroundColor(.orange) .background( GeometryReader { geometry in Circle() .offset(x: 2.5) .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) Spacer() ... } 

En el fragmento de código anterior, hemos definido el primer botón de nuestra vista de barra. Esto es lo que está pasando:

  • ViewFactory.button: aquí estamos utilizando nuestro método auxiliar para definir un botón con imagen con renderingMode .template que le permite poner un color personalizado para esta imagen
  • .foregroundColor - definiendo el color de nuestra vista
  • .background : este método define la vista de fondo del objeto dado
  • GeometryReader : una vista de contenedor que define su contenido en función de su propio tamaño y espacio de coordenadas. Estamos usando esto para obtener el tamaño actual de un botón y definir el círculo de fondo con el marco dado. Obtenga más información sobre los lectores de geometría aquí .
  • Círculo : define la forma del fondo.
  • .offset - desplazamiento del eje x del círculo
  • .foregroundColor - color de tinte circular
  • .shadow - sombra circular
  • .frame : define el marco del círculo usando el tamaño del lector de geometría (aquí estamos definiendo un círculo de fondo, 1.5 veces más grande que el botón actual)

Ahora implementemos el resto de los botones:

 struct BottomBarView: View { var body: some View { HStack { ViewFactory.button("back_icon", renderingMode: .template) .foregroundColor(.orange) .background( GeometryReader { geometry in Circle() .offset(x: 2.5) .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) Spacer() ViewFactory.button("close_icon", renderingMode: .template) .foregroundColor(.red) .background( GeometryReader { geometry in Circle().foregroundColor(.white) .frame(width: geometry.size.width * 2, height: geometry.size.height * 2) .shadow(color: .gray, radius: 12) } ) Spacer() ViewFactory.button("approve_icon", renderingMode: .template) .foregroundColor(.green) .background( GeometryReader { geometry in Circle() .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 2, height: geometry.size.height * 2) } ) Spacer() ViewFactory.button("boost_icon", renderingMode: .template) .foregroundColor(.purple) .background( GeometryReader { geometry in Circle() .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) } .padding([.leading, .trailing]) } } 

Y como resultado ahora tenemos esta hermosa vista:

vista de citas de barra de pestañas

Swipeview


Esta sección es para SwiftUI más avanzado. Aquí es realmente donde las cosas se ponen interesantes. Nos gustaría implementar el gesto de deslizar en la vista de acción. Este comportamiento es un buen caso de uso para un PageViewController, pero este controlador de vista será historia pronto, así que aquí es donde podemos mostrar el verdadero poder de SwiftUI.

Entonces, veamos cómo se implementa SwipeView:

 struct SwipeView: View { @State private var offset: CGFloat = 0 @State private var index = 0 let users = [...] let spacing: CGFloat = 10 var body: some View { GeometryReader { geometry in return ScrollView(.horizontal, showsIndicators: true) { HStack(spacing: self.spacing) { ForEach(self.users) { user in UserView(userModel: user) .frame(width: geometry.size.width) } } } .content.offset(x: self.offset) .frame(width: geometry.size.width, alignment: .leading) .gesture( DragGesture() .onChanged({ value in self.offset = value.translation.width - geometry.size.width * CGFloat(self.index) }) .onEnded({ value in if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.users.count - 1 { self.index += 1 } if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 { self.index -= 1 } withAnimation { self.offset = -(geometry.size.width + self.spacing) * CGFloat(self.index) } }) ) } } } 

Aquí hemos utilizado algunos conceptos nuevos e interesantes de SwiftUI:

  • @ Estado : un valor persistente de un tipo determinado, a través del cual una vista lee y monitorea el valor, lo que significa que cada vez que cambie esta propiedad, la vista se volverá a cargar para ajustarse a la actualización de estado dada. Puede consultar más sobre el estado aquí .
  • DragGesture : este objeto se usará para reconocer cada deslizamiento que el usuario realiza en la pantalla. Puede leer más sobre esto aquí: developer.apple.com/documentation/swiftui/draggesture
  • @ Estado de desplazamiento de var privado: CGFloat = 0 : esta propiedad se utilizará para definir el desplazamiento actual de la vista de desplazamiento cuando los usuarios deslizan el dedo por la pantalla
  • @ State private var index = 0 : esta propiedad define qué vista de usuario está actualmente en la pantalla
  • ScrollView : vista de desplazamiento horizontal sin indicadores, que será un contenedor para nuestra vista de usuario
  • HStack : vista de pila horizontal que contiene todas las vistas de usuario
  • content.offset (self.offset) : está creando una conexión entre el estado de desplazamiento y el desplazamiento de contenido de la vista de desplazamiento. Esto significa que siempre que cambie la propiedad de desplazamiento, el desplazamiento de la vista de desplazamiento también se actualizará

Enumeramos los usuarios existentes creando una vista de usuario para cada elemento:

  • .frame : aquí estamos definiendo el marco de vista de desplazamiento que debe ajustarse al ancho de la pantalla y debe alinearse correctamente con su contenedor
  • .gesture : aquí estamos agregando nuestro objeto DragGesture

DragGesture es un poco complicado, pero sin embargo, agrega toda la lógica de paginación en solo unas pocas líneas de código. Analicemos DragGesture :
  • onChanged () : este bloque se invoca cada vez que el usuario inicia y está en el momento del gesto de arrastre, aquí estamos calculando el desplazamiento actual de la vista del usuario que sigue al dedo del usuario
  • onEnded () : aquí se nos informa cuando finaliza el gesto de arrastre, aquí debemos calcular si el usuario desea deslizar esta vista (izquierda o derecha), o tal vez este gesto fue marcado, y el usuario desea permanecer en esta pantalla
  • withAnimation : este cierre se invoca con animación y permite cambiar el desplazamiento suavemente

Vista de contenido


 struct ContentView: View { var body: some View { VStack { NavigationView() .padding(.bottom) SwipeView() .padding(.bottom) BottomBarView() } .padding() } } 

Nuestra vista de contenido es extremadamente simple en este punto: compone todas las vistas creadas que creamos anteriormente, dentro de una pila vertical ( VStack ). Para NavigationView y SwipeView hemos agregado un relleno predeterminado en la parte inferior, y todo el VStack tiene relleno agregado a todos los bordes.

Eso es todo Hecho Así es como se ve nuestra aplicación ahora:

yesca swiftui

Pensamientos finales


Como podemos ver, SwiftUI es una herramienta muy poderosa, nos brinda una manera fácil de definir y manipular la IU en un breve código declarativo. Los desarrolladores nativos de React reconocerían este paradigma declarativo de inmediato.

Pero recuerde: SwiftUI todavía está en desarrollo y puede ser extremadamente inestable por ahora. Si desea verificar todo el código base para este proyecto, puede encontrarlo en Github .

Si tiene alguna idea o pregunta sobre SwiftUI, no dude en compartirlas en los comentarios. Además, si te ha gustado este artículo, ¡compártelo con tu comunidad para ayudarnos a correr la voz!

Source: https://habr.com/ru/post/476494/


All Articles