创建自定义视图修饰符的能力是 SwiftUI 中的一项强大功能。在本文中,我们将介绍如何使用此功能使构建 UI 变得更加容易的示例。如果你不熟悉SwiftUI中的 ViewModifiers 以及如何创建自定义的,你可以在这里阅读它们
本文的目标是介绍在 SwiftUI 中创建自定义修饰符和样式的一些不同方法,以及如何使用它们使构建 UI 更具声明性,同时仍然实现干净一致的最终输出。
我们要构建的最终 UI 是这样的:
让我们考虑屏幕上的所有单独组件:
如果您在不使用任何修饰符的情况下构建此屏幕,代码将如下所示:
struct ContentView: View { var body: some View { VStack (alignment: .leading) { Image("feature") .resizable() .aspectRatio(contentMode: .fill) .frame(minWidth: 0, maxWidth: .infinity) .frame(height: 220) .cornerRadius(12) .padding(.bottom, 12) Text("Custom ViewModifiers in SwiftUI are the best!") .foregroundColor(Color("titleTextColor")) .font(.system(size: 20, weight: .bold)) .padding(.bottom, 12) Text("Custom ViewModifiers in SwiftUI let you create resuable styles that can be applied to all your views") .foregroundColor(Color("bodyTextColor")) .font(.system(size: 14, weight: .medium)) Spacer() Button(action: { }) { Text("Label") .font(.system(size: 14, weight: .medium)) } .frame(minWidth: 0, maxWidth: .infinity) .padding(.horizontal, 10) .padding(.vertical, 12) .background(Color.blue) .foregroundColor(Color.white) .cornerRadius(12) } .padding(.all, 16) } }
某些元素的样式(例如标题和详细信息文本)必须重复
必须在多个地方更改一些常见样式(元素填充、圆角半径等)
现在您可以通过创建自定义视图以 UIKit 的方式解决这个问题,但我不喜欢这种方法,因为它涉及摆脱内置视图并使新团队成员的入职更加困难。一种更简单的方法是定义一些可以应用的通用视图修饰符,而不是样式本身。
让我们分解一下我们需要的常见样式:
让我们从拐角半径开始:
struct CommonCornerRadius: ViewModifier { func body(content: Content) -> some View { content .cornerRadius(12) } }
这个相当简单,它允许我们为元素应用一个通用的角半径。这使得全局更改应用程序样式变得更加容易,而无需创建自定义视图或必须在代码库中进行多项更改。
struct FullWidthModifier: ViewModifier { func body(content: Content) -> some View { content .frame(minWidth: 0, maxWidth: .infinity) } }
这使得全宽视图更容易实现,不再需要手动添加.frame
!
struct TitleTextModifier: ViewModifier { func body(content: Content) -> some View { content .foregroundColor(Color("titleTextColor")) .font(.system(size: 20, weight: .bold)) } } struct BodyTextModifier: ViewModifier { func body(content: Content) -> some View { content .foregroundColor(Color("bodyTextColor")) .font(.system(size: 14, weight: .medium)) } }
这将允许通用的文本样式,通常您会创建自定义文本组件或实用程序函数并通过代码添加 UI 组件。
extension Image { func aspectFill() -> some View { self .resizable() .aspectRatio(contentMode: .fill) } }
好吧,你懂我了……这不是自定义视图修改器,而是一个简单的扩展。这是因为 ViewModifiers apple to the generic Views and some functions such as resizable
仅适用于图像,使用扩展和自定义修饰符的组合有助于解决这个问题。
struct FullWidthButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .fullWidth() .foregroundColor(Color.white) .font(.system(size: 14, weight: .medium)) .padding(.horizontal, 10) .padding(.vertical, 12) .background(configuration.isPressed ? Color.blue.opacity(0.2) : Color.blue) } } struct FullWidthButton: ViewModifier { func body(content: Content) -> some View { content .buttonStyle(FullWidthButtonStyle()) } }
最后,这是针对按钮的,请注意,虽然我们可以简单地创建一个 ViewModifier 来实现相同的效果,但点击时按钮的外观不会改变。这是因为在按钮上设置.background
会强制它在点击和未点击状态下都使用该背景。 ButtonStyle
让我们可以根据按钮是否被按下来改变按钮的不透明度。
现在为了方便起见,我喜欢使用这些修饰符进行扩展:
extension View { func commonCornerRadius() -> some View { modifier(CommonCornerRadius()) } func fullWidth() -> some View { modifier(FullWidthModifier()) } func title() -> some View { modifier(TitleTextModifier()) } func body() -> some View { modifier(BodyTextModifier()) } func fullWidthButton() -> some View { modifier(FullWidthButton()) } } extension Image { func aspectFill() -> some View { self .resizable() .aspectRatio(contentMode: .fill) } }
struct ContentView: View { var body: some View { VStack (alignment: .leading) { Image("feature") .aspectFill() .fullWidth() .frame(height: 220) .commonCornerRadius() .padding(.bottom, 12) Text("Custom ViewModifiers in SwiftUI are the best!") .title() .padding(.bottom, 12) Text("Custom ViewModifiers in SwiftUI let you create resuable styles that can be applied to all your views") .body() Spacer() Button(action: { }) { Text("Awesome") } .fullWidthButton() .commonCornerRadius() } .padding(.all, 16) } }
干净多了!现在乍一看,这感觉比简单地手动设置样式需要更多的代码和精力,但从长远来看,这将节省很多精力。就个人而言,这种方法还通过更多地依赖通用修饰符而不是基于逐个视图的样式来鼓励您的应用程序的样式更加一致。
仅此而已!希望这可以帮助您更快、更轻松地构建您的应用程序,另一个好处是这些修饰符可以放入您的任何应用程序中并进行调整以符合其样式指南。我也一直在开发一个库来进一步推进这个,你可以在这里查看(PS:在写这篇文章的时候,这个库还处于非常早期的阶段,repo 是空的 :p 但敬请期待)
也发布在这里。