swiftui_如何使用SwiftUI构建设计系统

卢承弼
2023-12-01

swiftui

Building a design system to support one product is not easy - it has to be robust and flexible at the same time for scalability. Though challenging, lots of great resources have shared useful principles and approaches that help teams build a good system both visually and programmatically. Standing on their shoulders, this article tries to contribute to an untouched ground by focusing on building a good system in SwiftUI.

构建支持一个产品的设计系统并不容易-它必须同时具有鲁棒性和灵活性以实现可伸缩性。 虽然具有挑战性, 很多 巨大 资源都共享有用的原则和方法,帮助球队在视觉和编程方式构建了良好的系统。 站在他们的肩膀上,本文致力于通过专注于在SwiftUI构建良好的系统,为未开发的领域做出贡献。

为什么我写这篇文章 (Why do I write this article)

During my first summer in ITP at New York, I'm lucky to have the opportunity to work as an iOS developer intern at Line Break Studio. One task I've been assigned to is building a design system in two steps: first visually in Sketch, and then programmatically in SwiftUI. The experience of experimenting with the new framework and building a design system with it has been amazing, but also buggy along the way. That's why we'd like to share our experience with the community, hopefully making your development process easier.

在纽约ITP的第一个暑假期间,我很幸运有机会在Line Break Studio担任iOS开发人员实习生。 我分配给的一项任务是分两个步骤构建设计系统:首先在Sketch中直观地SwiftUI ,然后在SwiftUI编程方式进行。 尝试新框架并使用它构建设计系统的经验令人赞叹,但同时也遇到了很多麻烦。 这就是为什么我们希望与社区分享我们的经验,希望使您的开发过程更轻松。



什么是SwiftUI (What is SwiftUI)

Apple released this groundbreaking new framework in WWDC 2019, which is one of the bests in years. From the point of view as a web developer, the project development experience in SwiftUI is closer to which in conventional front-end stack and frameworks.

苹果在WWDC 2019中发布了这一突破性的新框架,这是多年来最好的框架之一。 从Web开发人员的角度来看, SwiftUI的项目开发经验与传统前端堆栈和框架中的经验更为接近。

This is definitely an awesome move because programming interface and managing states are drastically easier than before. And the best part of this improvement is that one can integrate UIKit and SwiftUI smoothly. To learn the basics of SwiftUI, the official tutorials provided by Apple are very helpful.

这绝对是一个了不起的举动,因为编程接口和状态管理比以前容易得多。 而这一改进的最好之处在于,它可以平滑地集成UIKit和SwiftUI 。 要学习SwiftUI的基础知识,Apple提供的官方教程非常有帮助。

示范项目 (The demo project)

For demonstration purpose, I put up a simplified version of design system we built in Line Break Studio. It a set of button components in different forms, which are built on top of two lower level parts: typography and colorPalette.

出于演示目的,我提出了我们在Line Break Studio中构建的设计系统的简化版本。 它是一组不同形式的按钮组件,它们建立在两个较低级别的部分之上: 印刷术colorPalette

The project is public on GitHub, and I'm using Xcode 11 Beta 5 for development. An Airtable base as design system management hub (read more about workflow management) is also public for reference.

该项目在GitHub上公开 ,并且我正在使用Xcode 11 Beta 5进行开发。 作为设计系统管理中心的Airtable基地 ( 更多有关工作流管理的信息 )也已公开供参考。



建筑设计系统原理 (Principles of building design system)

Design system in code is a middleware between designers and developers. Developer of the system takes inputs from design system in visual form, and produces API that's identical with which for further development. Following two principles should be recognized to complete this system in code:

代码设计系统是设计人员和开发人员之间的中间件。 系统开发人员以可视形式从设计系统中获取输入,并生成与进一步开发相同的API。 在代码中完成此系统应遵循以下两个原则:

1.与令牌通信 (1. Communicate with tokens)

Fundamentally, the purpose of having a design system in program is not about better code management or development efficiency, but to make sure the view is consistent with design files. To achieve that goal, using tokens to signify certain color, font, size or any visual elements is crucial to maintain quality of communication between developers, designers and managers in a team.

从根本上说,在程序中拥有设计系统的目的不是要提高代码管理或开发效率,而是要确保视图与设计文件一致。 为了实现该目标,使用令牌表示某些颜色,字体,大小或任何视觉元素对于维持团队中开发人员,设计人员和管理人员之间的沟通质量至关重要。

2.等级层次 (2. Levels of hierarchy)

In EightShapes' article, it points out that we should "Show options first, then decisions next", because "You can't make decisions without options."

ThreeShapes的文章中 ,指出我们应该“先显示选项,然后显示决定”,因为“没有选择就无法做出决定”。

This kind of ordering architecture loosens  the degree of coupling between different levels, hence providing more flexibility and dynamic for possible revisions. The way I structure the levels is in this order from bottom to top: material → base → token. But it could be anyway the team's comfortable with.

这种订购体系结构放松了不同级别之间的耦合程度,因此为可能的修订提供了更大的灵活性和动态性。 我构建关卡的方式是从下到上的:材料→基础→标记。 但无论如何,这可能是团队所适应的。



深入研究代码 (Diving into code)

Following section is a list of highlights we'd like to point out based on our experience. Please visit the GitHub repo for complete code. Any feedbacks or critics are welcome for improvements.

以下部分是根据经验我们要指出的重点。 请访问GitHub存储库以获取完整代码。 欢迎任何反馈或批评者进行改进。

1.层次结构的架构 (1. Architecting levels of hierarchy)

There're two ways of stacking materials at lower level to construct tokens at highest level:

有两种方法可以在较低级别堆叠材料以在最高级别构建令牌:

  • Use enum for type safety and code literacy

    使用enum类型的安全性和代码素养

Advantages of using enum in code as grouping wrapper or parameter in function have already been well recognized. One point worths mentioning here is the implementation of levels of hierarchy.

在代码中使用枚举作为分组包装器或函数中的参数的优势已广为人知。 这里值得一提的是层次结构的实现。

We always store the raw values, including font size (CGFloat) and font name (String), at the lowest level, because we don't want to mess around with it. But because raw value must be a literal in enum, we can't just assign a case to be a value from the other enum.

我们始终将原始值存储在最低级别,包括字体大小( CGFloat )和字体名称( String ),因为我们不想弄乱它。 但是因为原始值必须是枚举中的文字,所以我们不能仅将一个case指定为另一个枚举中的值。

To work around this problem, we implement a function getValue, which returns the raw value in switch case when necessary.

要解决此问题,我们实现了一个getValue函数,该函数在必要时返回switch情况下的原始值。

  • Use struct for easier structure

    使用struct简化结构

Though enum is great, we don't need its unique feature in some cases. For example, because Xcode takes care of the heavy job of processing dynamic colors, and no parameter options are required in API endpoint, we can set up color palettes by simple two levels of struct.

尽管枚举很棒,但是在某些情况下我们不需要它的独特功能。 例如,由于Xcode负责处理动态颜色,而API端点中不需要参数选项,因此我们可以通过简单的两个级别的结构来设置调色板。

2. API端点的命名清晰明了 (2. Clear and straightforward naming of API endpoint)

Naming convention is another broad topic for discussion and debate. In addition to basic Swift conventions, the only two rules we abide are, 1) no acronym and 2) making it simple. For example, to use typography and color system, instead of creating new endpoints, we make extension from Font and Color structs. This approach decreases the effort to memorize unfamiliar API names for developers.

命名约定是另一个广泛讨论和辩论的话题。 除了基本的Swift约定外 ,我们遵守的唯一两个规则是:1)不使用首字母缩写词; 2)使其变得简单。 例如,要使用字体和颜色系统,而不是创建新的端点,我们从Font和Color结构进行扩展。 这种方法减少了为开发人员记忆不熟悉的API名称的工作。

3.通过两种模式动态管理颜色集 (3. Manage color sets dynamically in two modes)

So dark mode has become a standard in industry, and both iOS and Android team have implemented this feature. It's a good trend for users, but could bring designers and developers some challenges, including managing and naming the color sets, especially gray scale ones.

因此,暗模式已成为行业标准, iOSAndroid团队均已实现此功能。 对于用户而言,这是一个好趋势,但可能给设计人员和开发人员带来一些挑战,包括管理和命名颜色集,尤其是灰度色域。

To think and communicate about gray scale colors dynamically, using terms like white, light, black or dark doesn't work. Because if we referred to a dynamic color #000000 (black in HEX) black or dark in light color scheme, how do you talk about this particular color, which should turn into #FFFFFF (white in HEX), in dark color scheme? defaultDark or lightDark?

使用白色浅色黑色深色之类的术语动态地思考和交流灰度颜色是行不通的。 因为如果我们将动态颜色#000000 (十六进制中的黑色)称为light color scheme 黑色深色 ,那么您如何谈论这种特定颜色,在dark color scheme #FFFFFF#FFFFFF (十六进制中的白色dark color schemedefaultDarklightDark吗?

It is very confusing to name gray scale dynamic color sets in conventional approach. To avoid this confusion, we use theme and contrast to manage one set of color in light and dark schemes instead.

在常规方法中命名灰度动态颜色集非常令人困惑。 为了避免这种混淆,我们使用themecontrast来管理一组颜色的lightdark的方案来代替。

Note that a gray scale color doesn't always need to be reversed in opposite color mode. In these situations that light color remains light and dark remains dark, we simply name name it light or dark instead.

请注意,在反色模式下不一定总是需要反转灰度颜色。 在这些情况下,浅色保持明暗,深色保持暗淡,我们仅将其命名为浅色或深色。

Once we wrap our head around this naming method, managing this architecture of color palette is easy in Xcode. To create a color set, simply create a new Asset Catalog file → add a new Color Set → and change Appearances to Any, Light, Dark will do.

一旦我们绕过这种命名方法,就可以在Xcode轻松管理调色板的体系结构。 要创建颜色集,只需创建一个新的Asset Catalog文件→添加一个新的Color Set →并将Appearances更改为Any, Light, Dark

4. environment设置 (4. environment settings)

One awesome feature in SwiftUI framework is the environment modifier, which provides ability to control environment values on target view. In terms of building design system, this ability provides convenient approach to change app's font at root level. And the other advantage of using environmentValue is to change and test light and dark color schemes in development.

SwiftUI框架中的一项很棒的功能是环境修改器 ,它提供了在目标视图上控制环境值的功能。 就建筑设计系统而言,此功能提供了在根级别更改应用程序字体的便捷方法。 使用environmentValue的另一个优势是在开发中更改和测试明暗方案。

5. buttonStyle和按钮标签 (5.  buttonStyle and button label)

Comparing to the old days in UIKit, constructing reusable buttons in SwiftUI is drastically easier. The Button view consists of two parts, which are action closure (event to be fired as button is pressed) and label (body of the button). The view can then be chained with a modifier buttonStyle. To learn details about building reusable buttons,I recommend reading Alejandro's tutorial, which is comprehensive and useful.

UIKit的过去相比,在SwiftUI中构造可重复使用的按钮非常容易。 Button视图由两部分组成,分别是action关闭(按下按钮时将触发的事件)和label (按钮的主体)。 然后可以使用修饰符buttonStyle链接视图。 要了解有关构建可重用按钮的详细信息,我建议阅读Alejandro的教程 ,该教程是全面而有用的。

In our customized button components, first step is to create two structs, including TokenButtonLabel and TokenButtonStyle. These two structs are programmed according to the types of buttons we have in design files. For example, there're only two types of labels: icon and text. Each type has an according init function designed with different parameters for new instances.

在我们自定义的按钮组件中,第一步是创建两个结构,包括TokenButtonLabelTokenButtonStyle 。 这两个结构是根据设计文件中按钮的类型进行编程的。 例如,标签只有两种:图标和文本。 每种类型都有一个根据init函数设计的,用于新实例的不同参数。

On the other hand, there're four major types of button styles: circle icon, icon, capsule and text. To follow ButtonStyle protocol, a makeBody func has to be implemented. This function brings us a configuration property, providing a native isPressed value to monitor if the button is pressed or not.

另一方面,按钮样式有四种主要类型:圆圈图标,图标,胶囊和文本。 要遵循ButtonStyle协议,必须实现makeBody函数。 此函数为我们带来了一个configuration属性,它提供一个本地的isPressed值来监视按钮是否被按下。

Finally, stacking on top of TokenButtonLabel and TokenButtonStyle, the endpoint of the button component API will be TokenButton - a grouping that wraps content and style of button together, conforming to the button types in visual design system.

最后,堆叠在TokenButtonLabelTokenButtonStyle ,按钮组件API的端点将是TokenButton一个将按钮的内容和样式包装在一起的分组,符合可视化设计系统中的按钮类型。

6. AnyView作为包装器 (6. AnyView as wrapper)

As we're dealing with the makeBody function brought by ButtonStyle protocol, we found a useful tip to work with View. To store a view in a variable, the type annotation could be indicated as AnyView, which works as a general container of views in SwiftUI.

当我们处理ButtonStyle协议带来的makeBody函数时,我们发现了一个有用的技巧来使用View 。 为了将视图存储在变量中,类型注释可以表示为AnyView ,它在SwiftUI中用作视图的常规容器。

In our case, because we want to add the opacity modifier to configuration.label to all types of buttons, instead of doing so repeatedly in each switch case, it makes more sense to chain the modifier at the end altogether. We can achieve this pattern by using the advantage of AnyView in this way:

在我们的例子中,因为我们想将opacity修饰符添加到configuration.label中的所有类型的按钮上,而不是在每种switch情况下都重复执行此switch ,因此将修饰符全部链接到最后更加有意义。 我们可以通过以下方式利用AnyView的优势来实现此模式:

7.建立具有mutating功能的视图修饰符 (7. Build view modifier with mutating function)

To update styles of the buttons dynamically, we can build our own modifier. First instantiate customized mutable state properties in view, and then create a mutating function which returns a Self type after updating the target state property.

要动态更新按钮的样式,我们可以构建自己的修饰符。 首先在视图中实例化自定义的可变状态属性,然后创建一个mutating函数,该变量函数在更新目标状态属性后返回Self类型。

8.棘手的边框样式 (8. Tricky border style)

One drawback of SwiftUI is styling a circle shape with circular border is not straightforward at all. I struggled for a while, and finally found a solution here on StackOverflow. A clipShape and an overlay modifier are required to make it work.

SwiftUI的一个缺点是设计带有圆形边框的圆形形状根本不简单。 我挣扎了一段时间,终于在StackOverflow上找到了解决方案 。 需要使用clipShapeoverlay修改器才能使其正常工作。



结论 (Conclusion)

SwiftUI is an incredible improvement Apple makes. Though flaws still exist, building a robust and flexible design system with it, and furthermore complicated UI in iOS is way efficient than ever. I hope this article is helpful for any iOS team trying to build UI, and always welcome to any feedbacks!

SwiftUI是Apple做出的不可思议的改进。 尽管仍然存在缺陷,但是使用它构建健壮且灵活的设计系统,以及iOS中复杂的UI比以往任何时候都更加高效。 希望本文对尝试构建UI的所有iOS团队有所帮助,并随时欢迎您提供任何反馈!

 Read more of my works at vinceshao.com / Follow me on Twitter or LinkedIn

阅读我的作品在vinceshao.com /跟随我的微博LinkedIn

翻译自: https://www.freecodecamp.org/news/how-to-build-design-system-with-swiftui/

swiftui

 类似资料: