如何在SwiftUI中实现火种刷卡

6月,我们首次听说了SwiftUI-一种在iOS和macOS(也包括iPadOS)应用程序中创建和使用UI元素的全新方法。 夏天感觉像圣诞节。 它是新的,是声明性的,是性感的! 而现在,在iOS 13发布几周之后,我们就可以开始在所有项目中使用SwiftUI。 让我们学习如何使用Apple提供给我们的惊人工具来创建经典的Tinder风格的刷卡。

在本文中,我想向您展示如何仅用几行代码就能实现类似Tinder的卡片视图和行为(轻拂以行动)。

为此,我们需要执行以下操作:

  • 创建用户视图
  • 创建导航视图
  • 创建BottomBarView
  • 创建swipeview
  • 将所有内容放到ContentView中

因此,让我们开始吧。

火种swiftui

使用者检视


UserView由两个子视图构建,一个是NameView ,其中包含用户名,年龄和爱好,第二个视图只是一个头像视图,用于显示用户的个人资料图片。

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

首先,我们需要定义NameView ,它将代表用户名,年龄和爱好。 NameView符合View协议,该协议用于在SwiftUI中定义自定义视图。 视图协议只有一个要求,那就是定义body属性,该属性应返回视图结构并描述其行为。 您可以在Apple官方文档中 查看有关View协议的更多信息。

让我们分解一下用于定义此View的对象:

  • VStack像所有对象的容器一样垂直对齐
  • 告诉SwiftUI该视图应该在底部对齐的空格
  • 用名称和年龄表示标签的文本 ,具有以下属性:
  • 具有相似属性并显示用户爱好的第二个Text对象

请注意,这里我们不在body属性内使用return语句,而是返回了VStack。 SwiftUI使用的是Swift 5.0中实现的省略返回提议。 您可以在此处查看有关此内容的更多信息。

头像视图


这是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) } } 

让我们深入研究构成此化身单元的组件:

  • 图片 -显示用户的图片
  • 可调整大小 -此方法指定图像应调整大小以适合其嵌入位置
  • overlay(Rectangle) -在这里我们定义渐变,它将是NameView的一个很好的背景,该渐变从图像中心开始,在底部结束,它在开始时具有清晰的颜色,在底部具有黑色
  • cornerRadius-图片将具有圆角半径

现在,我们将这两个视图嵌入到一个名为UserView的容器视图中。

使用者检视


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

这是怎么回事:

  • ZStack-这是一个堆栈视图,它将其子对象在同一轴上对齐。 您可以在这里阅读有关ZStack的更多信息
  • AvatarView-我们的头像视图包含通过UserModel提供的图像
  • NameView-我们的名称视图,根据用户模型显示名称

完成所有这些步骤后,运行该应用程序。 您将获得以下屏幕:

火种头像

让我们现在添加一个小的辅助方法。 在向您展示如何定义NavigationView之前,让我们创建一个辅助方法,如下所示:

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

在这里,我们定义了一个按钮工厂方法,该方法根据给定的图像和渲染模式创建一个新按钮。 没有动作处理程序,因为这超出了本文的范围。

导航视图


 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将自动使垫片的宽度相等,并为我们提供以下导航视图:

导航视图

底视


 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-在这里我们使用辅助方法来定义带有renderMode .template图像的按钮,该按钮允许您为该图像放置自定义颜色
  • .foregroundColor-定义视图的颜色
  • .background-此方法定义给定对象的背景视图
  • GeometryReader-一个容器视图,根据其自身大小和坐标空间定义其内容。 我们正在使用它来获取按钮的当前大小,并使用给定的框架定义背景圆。 在此处了解有关几何阅读器的更多信息。
  • -定义背景形状
  • .offset-圆x轴偏移
  • .foregroundColor-圆圈色调颜色
  • .shadow-圆形阴影
  • .frame-使用几何读取器的尺寸定义圆框(此处定义背景圆,比当前按钮大1.5倍)

现在让我们实现其余的按钮:

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

结果,我们现在有了这个美丽的景色:

标签栏约会视图

滑动查看


本节适用于更高级的SwiftUI。 这确实是让事情变得有趣的地方。 我们想在动作视图上实现滑动手势。 此行为是PageViewController的一个很好的用例,但是此视图控制器将很快成为历史,因此我们可以在这里展示SwiftUI的真正功能。

因此,让我们看看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) } }) ) } } } 

在这里,我们使用了一些新的有趣的SwiftUI概念:

  • @ State-给定类型的持久值,视图通过该值读取并监视该值,这意味着只要此属性发生更改,就将重新加载视图以适应给定的状态更新。 您可以在此处查看有关State的更多信息。
  • DragGesture-此对象将用于识别用户在屏幕上进行的每一次滑动。 您可以在此处阅读有关此内容的更多信息: developer.apple.com/documentation/swiftui/draggesture
  • @ State private var offset:CGFloat = 0-当用户在屏幕上滑动时,此属性将用于定义当前滚动视图的偏移量
  • @ State private var index = 0-此属性定义屏幕上当前正在显示的用户视图
  • ScrollView-不带指示器的水平滚动视图,它将作为我们用户视图的容器
  • HStack-包含所有用户视图的水平堆栈视图
  • content.offset(self.offset) -它在偏移状态和滚动视图内容偏移之间创建连接。 这意味着,只要offset属性发生变化,滚动视图的偏移量也会被更新。

我们通过为每个元素创建一个UserView来枚举现有用户:

  • .frame-在这里,我们定义的滚动视图框架应适合屏幕的宽度,并应与其容器正确对齐
  • .gesture-在这里我们添加了DragGesture对象

DragGesture有点复杂,但是尽管如此,它仅在几行代码中添加了所有分页逻辑。 让我们分解一下DragGesture
  • onChanged() -每当用户启动并且处于拖动手势时都会调用此块,此处我们正在计算跟随用户手指的当前用户视图偏移量
  • onEnded() -在此我们将在拖动手势结束时收到通知,在这里我们需要计算用户是否要滑动该视图(向左或向右),或者是否已标记了此手势,并且用户希望停留在此屏幕上
  • withAnimation-此闭包与动画一起调用,并允许平滑地更改偏移量

内容浏览


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

现在,我们的内容视图非常简单-它在垂直堆栈( VStack )内构成了我们先前创建的所有视图。 对于NavigationViewSwipeView,我们在底部添加了一些默认填充,并且整个VStack的所有边缘都添加了填充。

就是这样 完成 这是我们的应用现在的样子:

火种swiftui

最后的想法


如我们所见,SwiftUI是一个非常强大的工具,它为我们提供了一种在简短的声明性代码中定义和操作UI的简便方法。 React Native开发人员会立即认识到这种声明式范例。

但是请记住:SwiftUI仍在开发中,目前可能非常不稳定。 如果您想检查该项目的所有代码库,可以在Github上找到它。

如果您对SwiftUI有任何想法或问题,请随时在评论中分享。 另外,如果您喜欢这篇文章,请与您的社区分享,以帮助我们传播信息!

Source: https://habr.com/ru/post/zh-CN476494/


All Articles