apache sca_带有sca swiftui的应用

apache sca

应用程式开发(App Development)

At Bending Spoons, we continuously strive to improve our technologies. We recently hit one of the ceilings of our current technology stack, and we decided to explore the surroundings, to understand how we can improve.

弯曲勺子,我们不断努力改进我们的技术。 最近,我们达到了当前技术栈的最高标准,因此我们决定探索周围的环境,以了解如何改进。

We found a promising approach in the Swift Composable Architecture, developed by pointfree.co. The focus of their architecture is on composability, testability, modularization, and ergonomy. Thus, they:

我们在pointfree.co开发的Swift Composable Architecture中找到了一种有前途的方法。 他们的体系结构的重点是可组合性,可测试性,模块化和人体工程学。 因此,他们:

  • Start from units of reusable code that can be easily composed.

    从易于编写的可重用代码单元开始。
  • Units of code are trivial to test.

    代码单元很容易测试。
  • The architecture provides utilities to compose, scope, and assemble these units, fostering modularization.

    该体系结构提供了用于组合,确定范围和组装这些单元的实用程序,从而促进了模块化。
  • All the APIs of the composable architecture are polished to be extremely easy to use.

    可组合体系结构的所有API都经过精心修饰,以使其非常易于使用。

In this series of episodes, of which this is the first one, I’d like to build an app leveraging this architecture. In today’s article, I’ll tackle the UI.

在这一系列剧集中,这是第一集,我想利用这种架构构建一个应用程序。 在今天的文章中,我将介绍UI。

The composable architecture shines with SwiftUI, the new UI framework birth by Apple, and I’ll use it for the app. A small disclaimer is deserved: this is my first attempt at SwiftUI.

SwiftOS是Apple诞生的新UI框架,可组合的体系结构令人眼前一亮,我将在应用程序中使用它。 一个小小的免责声明是应得的:这是我第一次使用SwiftUI。

最终应用 (The Final App)

The goal of this series of articles is to write a simple Snake app. Snake is a game where the player controls a snake that has to eat some mouses that pops on the screen. Every time the snake swallows a mouse, it grows. The snake moves continuously forward, so the player controls its direction: the game ends when there is no space available for the snake to move.

本系列文章的目的是编写一个简单的Snake应用程序。 蛇是一种游戏,玩家在游戏中控制着一条蛇,该蛇必须吃掉一些弹出屏幕的鼠标。 蛇每吞下一只老鼠,它就会长大。 蛇不断向前移动,因此玩家可以控制蛇的方向:没有足够的空间移动蛇时,游戏结束。

These screens show the final UI of the game and a loss situation:

这些屏幕显示了游戏的最终用户界面和损失情况:

Image for post

The green dot is the head of the snake. The user can control it through the arrows buttons at the bottom of the screen. I hope my UI designers would forgive me for my lack of designing skills!

绿点是蛇的头。 用户可以通过屏幕底部的箭头按钮来控制它。 我希望我的UI设计师能原谅我缺乏设计技能!

Image for post

SwiftUI (SwiftUI)

UI组件 (UI Components)

The new framework from Apple is highly declarative. Several containers can be used to layout the components automatically. The most used ones are:

苹果公司的新框架具有高度的声明性。 可以使用几个容器来自动布局组件。 最常用的是:

  • VStack which arranges all its content vertically, aligning it centrally by default;

    VStack垂直排列所有内容,默认情况下将其居中对齐;

  • HStack which arranges all its content horizontally, aligning it centrally by default.

    HStack将所有内容水平排列,默认情况下将其居中对齐。

Then, for repeated elements, we can use the ForEach construct: it allows the construction of a UI component for each item in a collection.

然后,对于重复的元素,我们可以使用ForEach构造:它允许为集合中的每个项目构造UI组件。

There are also some UI elements that can be used to render the content on the screen. The most used is the Text that allows rendering some text (or emojis 😏).

还有一些UI元素可用于在屏幕上呈现内容。 最常用的是Text ,允许绘制一些文字(或表情😏)。

Finally, there are UI elements that can be used to perform some actions, like the Button used in this app. The lovely thing about SwiftUI is that it is incredibly composable: the content of a button, for example, can be a very complex set of other SwiftUI components!

最后,有些UI元素可用于执行某些操作,例如此应用程序中使用的Button 。 SwiftUI的妙处在于它具有令人难以置信的可组合性:例如,按钮的内容可以是一组非常复杂的其他SwiftUI组件!

呈现内容 (Rendering the content)

Now that we know how to compose the SwiftUI elements together let’s have a look at how we can render our content.

现在,我们知道了如何将SwiftUI元素组合在一起,让我们看一下如何呈现内容。

SwiftUI takes inspiration from many other frameworks that leverage the idea of 2-ways binding. We can construct a particular object that the UI observes: as soon as the observed object changes, the UI will update itself with the new representation. Furthermore, the UI can modify the observed object: for example, when the user types something in a text field that is connected to an object, the fields in the object will be updated as well.

SwiftUI从许多其他框架中汲取了灵感,这些框架利用了2路绑定的思想。 我们可以构造UI观察到的特定对象:一旦观察到的对象发生更改,UI就会使用新的表示形式更新自身。 此外,UI可以修改观察到的对象:例如,当用户在连接到对象的文本字段中键入内容时,对象中的字段也将被更新。

Some property wrappers and protocols power the observation mechanism. We need to declare an NSObject as conforming to the ObservableObject. This is the special object that will be observed by the UI. All the properties that can update the UI must be marked with the @Published property wrapper: this will create a Combine Publisher for that property under the hood.

一些属性包装器和协议为观察机制提供了动力。 我们需要声明一个NSObject符合ObservableObject 。 这是UI会观察到的特殊对象。 必须使用@Published属性包装器标记所有可以更新UI的属性:这将在@Published属性创建一个Combine Publisher

Once declared in the SwiftUI file, the observable object should be tagged by the @Observed property wrapper. Thanks to this pair of features, the SwiftUI magic can happen.

在SwiftUI文件中声明后,可观察对象应使用@Observed属性包装器进行标记。 多亏这对功能,SwiftUI的魔力才得以实现。

建模游戏 (Modeling the Game)

Now that we explored how SwiftUI works, on a very high level, let’s start building the app. As for every application, the first step is to model the domain we want to implement.

现在,我们已经从高角度探讨了SwiftUI的工作原理,让我们开始构建该应用程序。 对于每个应用程序,第一步是为我们要实现的领域建模。

方向 (Direction)

Our snake can move in 4 directions (up, right, down, and left), so we need an enum for that. Moreover, we know that, while it is moving to the right, we can not make it move immediately to the left and vice-versa. This also holds for up and down movement. Thus, we need a way to understand whether two directions are opposite. We can model these requirements as follow:

我们的蛇可以在四个方向(上,右,下和左)移动,因此我们需要一个enum 。 而且,我们知道,尽管它向右移动,但不能使其立即向左移动,反之亦然。 这也适用于上下运动。 因此,我们需要一种方法来了解两个方向是否相反。 我们可以将这些需求建模如下:

(Snake)

The snake has a head, that is what the player controls, and a body that grows every time a mouse is eaten. The snake lives in the game field. We can think about the field as a grid of rows and columns, so the head and the body will occupy adjacent positions in this grid. To describe that, we can create a Location object which holds a row and a col and thus the head will occupy a Location and the body will occupy an [Location]. Finally, we know the snake will move in a single Direction at any time.

蛇的头是玩家控制的,而每当吃掉鼠标时,蛇的身体就会长大。 蛇生活在游戏场中。 我们可以将字段视为行和列的网格,因此头部和身体将占据该网格中的相邻位置。 为了描述这一点,我们可以创建一个Location对象,该对象包含row和一个col ,因此头部将占据一个Location ,而主体将占据一个[Location] 。 最后,我们知道蛇会随时Direction一个Direction移动。

The Location model
位置模型
The Snake model
蛇模型

In the snippet above, I added a bit of logic. In particular, how to create a snake with a given length and the head Location, and how to validate a move. The focus of this article is on the UI, so I simplified the logic a bit, without checking if the snake hits itself.

在上面的代码段中,我添加了一些逻辑。 特别是如何创建具有给定长度和头部位置的蛇,以及如何验证移动。 本文的重点是UI,因此我略微简化了逻辑,而没有检查蛇是否击中了自己。

In the last part of the snippet, instead, I implemented the movement. This is a little tricky: we need to distinguish the case when the snake eats the mouse from the case when it doesn’t. As an optimization, instead of moving all the body locations toward the new head, we drop the last bit and insert a new element in the old head location. In this way, the snake will always grow in O(1).

相反,在代码片段的最后部分中,我实现了该运动。 这有点棘手:我们需要区分蛇吞噬鼠标的情况和蛇吞噬鼠标的情况。 作为一种优化,我们没有移动所有身体位置到新的头部,而是丢弃了最后一位,并在旧的头部位置插入了新元素。 这样,蛇将始终在O(1)生长。

游戏引擎 (GameEngine)

The last bit of the model is the object which controls the game. That is the SwiftUI’s ObservableObject which the View will be bound to. The GameEngine publish both the snake object and the mouse location so that the UI knows where to draw them. Here is the code:

模型的最后一位是控制游戏的对象。 这就是视图将绑定到的SwiftUI的ObservableObjectGameEngine发布snake对象和mouse位置,以便UI知道在何处绘制它们。 这是代码:

The game engine
游戏引擎

In the snippet, you can see also a utility to generate a random location that does not overlap with the snake and the main game method that makes the snake move.

在代码段中,您还可以看到一个实用程序,用于生成与蛇不重叠的随机位置以及使蛇移动的主要游戏方法。

风景 (The View)

Here we are, let’s write our SwiftUI view. As any View, what is rendered is what is returned by the body property.

现在,我们来编写SwiftUI视图。 与任何View ,呈现的内容是body属性返回的内容。

Let’s start by defining the models we want to render: in this case, we just need an @Observed var gameEngine: GameEngine(). We will construct the engine in the View’s initializer and Swift will generate it for us.

让我们从定义要渲染的模型开始:在这种情况下,我们只需要一个@Observed var gameEngine: GameEngine() 。 我们将在View的初始化器中构造引擎,而Swift将为我们生成它。

Then, by observing our final results, we have two different areas to represent: the top area with the snake and the mouse, and the bottom area with the buttons. Those two elements can be arranged vertically by using a VStack element.

然后,通过观察最终结果,我们可以用两个不同的区域表示:顶部区域是蛇和鼠标,底部区域是按钮。 这两个元素可以通过使用VStack元素垂直排列。

Let’s start with the game field. We can decompose it in rows and cols. Each location can contain the mouse, the head of the snake, a part of the snake body, or nothing. Let’s model the cell as a Text whose content depends on the model. Also, to keep all the same spacing, let’s use a monospaced font and let’s fix the frame size to avoid some weird misalignments:

让我们从游戏领域开始。 我们可以将其分解为行和列。 每个位置都可以包含鼠标,蛇的头,蛇的身体的一部分或什么都不包含。 让我们将单元格建模为Text其内容取决于模型。 另外,为了保持所有相同的间距,让我们使用等宽字体,并固定frame大小以避免一些奇怪的未对准:

Function to create a cell.
创建单元格的功能。

Then, for each row, we need to create all the columns. To keep the things easy, I decided that the game will take place in an 11x11 grid. So we can create the columns by stacking 11 cells horizontally (with the HStack element). We also need to keep track of the row, to pass it to the cell’s factory method:

然后,对于每一行,我们需要创建所有列。 为了使事情变得简单,我决定将游戏放置在11x11网格中。 因此,我们可以通过水平堆叠11个单元(使用HStack元素)来创建列。 我们还需要跟踪行,将其传递给单元格的工厂方法:

Function that create a row with 11 cells
创建具有11个单元格的行的函数

Finally, let’s isolate the game field code in its function. What is missing is the code that can generate all the rows of the gaming field. We can use a VStack to stack the row vertically. We also add a black border and small padding:

最后,让我们在功能中隔离游戏领域代码。 缺少的是可以生成游戏场所有行的代码。 我们可以使用VStack垂直堆叠行。 我们还添加了黑色边框和小填充:

NOTE: while working on the grid I noticed that if we set a border width equals to 1, SwiftUI simply ignores it…

注意:在网格上工作时,我注意到如果将边框宽度设置为1,SwiftUI只会忽略它…

Let’s move to the commands now. We have two rows of buttons. So we would need a VStack to model the two rows. In the first row, we only have the up button: we don’t need other containers. The bottom row, instead, has three buttons. So we need a HStack to arrange them. All the buttons share the same style. Thus we can factor out a function to create them:

现在转到命令。 我们有两排按钮。 因此,我们需要一个VStack对两行进行建模。 在第一行中,我们只有up按钮:我们不需要其他容器。 底行有三个按钮。 因此,我们需要一个HStack来安排它们。 所有按钮共享相同的样式。 因此,我们可以分解出一个函数来创建它们:

Function to create a button
创建按钮的功能

You can see that the Button takes a closure as the first parameter: this code is executed when the button is tapped.

您可以看到Button以闭包作为第一个参数:点击该按钮时将执行此代码。

Thanks to this utility, our command-generating function is pretty simple. We can add a little bit of padding to equilibrate the spacings:

借助此实用程序,我们的命令生成功能非常简单。 我们可以添加一些填充以平衡间距:

Function that creates the commands. I don’t know why, but the `right` button is not rendered correctly by the GitHub’s gists.
创建命令的功能。 我不知道为什么,但是GitHub的要旨无法正确显示“向右”按钮。

Finally, we can put all the UI together, with a nice spacer to keep the elements at the proper distance:

最后,我们可以将所有UI放在一起,并带有一个漂亮的间隔符,以将元素保持在适当的距离:

Final code for the ContentView
ContentView的最终代码

结论(Conclusion)

And that’s it: we created our slightly complex UI by decomposing it in small units that we then plugged together. We could have also exported the code in subviews instead of in factory functions: for complex projects, where common elements of UI are reused, that approach makes very sense, and it is much more maintainable than this one.

就是这样:我们通过将其分解成小单元来创建我们稍微复杂的UI,然后将它们插入在一起。 我们还可以将代码导出到子视图中,而不是在工厂函数中:对于复杂的项目,其中UI的常见元素被重用,这种方法非常有意义,并且比这种方法更具可维护性。

This article is the first one of a series where I’ll journal the creation of this game. Today, we have seen how fast and easy it is to create a complex UI with the new Swift UI Apple’s framework. UIKit would have been much more cumbersome: it would have required a UICollectionView, with its own DataSource and Delegate and manually layout the collection and the UIButtons by setting their frame.

本文是我将记录该游戏创建过程的系列文章的第一篇。 如今,我们已经看到了使用新的Swift UI Apple框架创建复杂的UI的便捷性。 UIKit会麻烦得多:它需要一个UICollectionView ,它自己的DataSourceDelegate并通过设置它们的框架来手动布置集合和UIButtons

SwiftUI is extremely powerful, and it took around a couple of hours to achieve this result: I think it’s a really good time, considering that this is my first approach to the framework.

SwiftUI非常强大,花了大约两个小时才能达到这个结果:考虑到这是我对框架的第一种方法,我认为这是一个非常好的时机。

In the next weeks, we will see how to plug the Swift Composable Architecture into our project to manage the logic in a decoupled and more organized way. Stay tuned!

在接下来的几周中,我们将看到如何将Swift Composable Architecture插入到我们的项目中,以一种分离的,更有条理的方式管理逻辑。 敬请关注!

翻译自: https://medium.com/swlh/an-app-with-sca-swiftui-95e44d8e8701

apache sca

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值