6月,我们首次听说了
SwiftUI-一种在iOS和macOS(也包括iPadOS)应用程序中创建和使用UI元素的全新方法。 夏天感觉像圣诞节。 它是新的,是声明性的,是性感的! 而现在,在iOS 13发布几周之后,我们就可以开始在所有项目中使用SwiftUI。 让我们学习如何使用Apple提供给我们的惊人工具来创建经典的Tinder风格的刷卡。
在本文中,我想向您展示如何仅用几行代码就能实现类似Tinder的卡片视图和行为(轻拂以行动)。
为此,我们需要执行以下操作:
- 创建用户视图
- 创建导航视图
- 创建BottomBarView
- 创建swipeview
- 将所有内容放到ContentView中
因此,让我们开始吧。
使用者检视
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 )内构成了我们先前创建的所有视图。 对于
NavigationView和
SwipeView,我们在底部添加了一些默认填充,并且整个
VStack的所有边缘都添加了填充。
就是这样 完成 这是我们的应用现在的样子:
最后的想法
如我们所见,SwiftUI是一个非常强大的工具,它为我们提供了一种在简短的声明性代码中定义和操作UI的简便方法。
React Native开发人员会立即认识到这种声明式范例。
但是请记住:SwiftUI仍在开发中,目前可能非常不稳定。 如果您想检查该项目的所有代码库,可以在
Github上找到它。
如果您对SwiftUI有任何想法或问题,请随时在评论中分享。 另外,如果您喜欢这篇文章,请与您的社区分享,以帮助我们传播信息!