swiftui_SwiftUI的混合包

swiftui

介绍 (Introduction)

SwiftUI introduced us to a whole new way of designing and coding interfaces. Gone are the old ways of subclassing UIKit (or AppKit) classes and hardwiring layout constraints. Instead, we now have a nice, declarative way of structuring and styling our controls and making sure the interface updates whenever new information or events arrive.

SwiftUI向我们介绍了一种全新的界面设计和编码方式。 继承UIKit(或AppKit)类和固定布局约束的旧方法已经一去不复返了。 取而代之的是,我们现在有了一种很好的声明式方法来构造和样式化控件,并确保只要有新信息或事件到达,接口就会更新。

To facilitate this new architecture, the good people at Apple took some of Swift’s best features (e.g. protocols, generics, opaque types) and combined them into SwiftUI. However, this comes at a hidden cost: If you’re not already well-versed in these features, there will be a bit of a learning curve and most likely a lot of cryptic error messages that will send you off to your favorite search engine. This article will look at some of these error messages and explain what they mean and what you can do to prevent them.

为了促进这种新架构的发展,Apple的好人采用了Swift的一些最佳功能(例如协议,泛型,不透明类型),并将它们组合到SwiftUI中。 但是,这需要付出一些隐性的代价:如果您还不熟悉这些功能,将会有一些学习过程,并且很可能会出现很多隐含的错误消息,这些错误消息会将您带到您最喜欢的搜索引擎。 本文将研究其中一些错误消息,并解释它们的含义以及如何防止这些错误消息。

建立视图 (Building a View)

When implementing a new SwiftUI view, you typically start small. You add some components to the body, you style them, and handle any interactions. At some point, your simple view starts to get too big or you get a lot of conditional logic or duplication in your body. So, you decide to move some of the logic out of the body and into a separate function. This function will take care of building some complex components for you, and since everything is a View in SwiftUI, you simply define the function type signature like this:

在实现新的SwiftUI视图时,通常从小处着手。 您将一些组件添加到body ,对其进行样式设置并处理任何交互。 在某个时候,您的简单视图开始变得太大,或者您的body了很多条件逻辑或重复项。 因此,您决定将某些逻辑移出body并移至单独的函数中。 该函数将为您构建一些复杂的组件,并且由于一切都是SwiftUI中的View ,因此您只需定义函数类型签名即可,如下所示:

private func buildComplexButton() -> View

Great! Well… apart from the compiler, which complains,

大! 好吧……除了编译器抱怨

“Protocol ‘View’ can only be used as a generic constraint because it has Self or associated type requirements.”

“协议“视图”只能用作通用约束,因为它具有“自身”或关联的类型要求。”

The problem here lies in the last part of the error message: Any object conforming to the View protocol will need to have an associated type Body that determines how the view is actually implemented. Attempting to return just a plain View from your function results in the compiler throwing up its hands and saying, “I don’t know what the return type will be without any additional information on the actual type that is conforming to this protocol.” It’s a bit like returning a generic type (such as Array) without specifying the type parameter list (What does the Array contain?). But that is exactly the point! We don’t want to pin ourselves down to a concrete type just yet. Our function might generate a variety of different views with different concrete types. Luckily, Swift 5.1 introduced the keyword some to help with this, and you’ve already seen it when creating a new view:

这里的问题出在错误消息的最后一部分:任何符合View协议的对象都需要具有一个 关联类型决定视图实际实现方式的Body 。 尝试从函数中仅返回普通View导致编译器举起手来,说:“如果不提供有关符合此协议的实际类型的任何其他信息,我将不知道返回类型将是什么。” 这有点像不指定类型参数列表( Array包含什么?)而返回泛型类型(例如Array )。 但这就是重点! 我们现在还不想将自己固定在一个具体的类型上。 我们的函数可能会生成具有不同具体类型的各种不同视图。 幸运的是,Swift 5.1引入了关键字some来解决这个问题,在创建新视图时您已经看到了它:

var body: some View

Naively, this means what you think it means: We return some View and we don’t really care what kind. This is commonly referred to as an opaque type: a type that has some capabilities (it’s a View), but we don’t know exactly what kind of view. So, we’ll update our function to the new signature and give it an implementation:

天真的,这意味着您认为它意味着什么:我们返回一些 View而我们实际上并不关心哪种类型。 通常将其称为不透明类型:具有某些功能的类型(它是View ),但是我们不确切知道哪种视图。 因此,我们将功能更新为新的签名并为其提供实现:

And all is well again! Well… as long as you make sure that every possible View that you return from this function has the exact same type. The restriction on opaque types is that the compiler will only allow them if every available code path will return the same concrete type. We’re only returning identical buttons, so no issues here. However, suppose we are implementing a user interface for a keypad.

而且一切都很好! 好吧……只要确保从此函数返回的每个可能的View都具有完全相同的类型。 对不透明类型的限制是,仅当每个可用代码路径都返回相同的具体类型时,编译器才允许它们。 我们只返回相同的按钮,因此这里没有问题。 但是,假设我们正在实现键盘的用户界面。

A simple keypad user interface

We’ve chosen to implement this as a grid of Buttons. Since all the buttons are more or less identical and we don’t want to hardcode each and every one of them, we use a builder function to create them. There are two main types of buttons: ones with a text label (the digits) and ones with an image (in this case, the delete and Face ID symbols coming from SF Symbols). Simplified, it looks like this:

我们选择将其实现为Buttons的网格。 由于所有按钮或多或少都是相同的,并且我们不想对每个按钮进行硬编码,因此我们使用了一个builder函数来创建它们。 按钮主要有两种类型:带有文本标签(数字)的按钮和带有图像的按钮(在这种情况下,为SF Symbols的Delete和Face ID符号)。 简化后,它看起来像这样:

We’re still returning Buttons, so this must work, right? Well, the compiler unfortunately says no:

我们仍在返回Buttons ,所以这必须工作,对吗? 好吧,编译器不幸地拒绝了:

“Function declares an opaque return type, but the return statements in its body do not have matching underlying types.”

“函数声明了不透明的返回类型,但是其主体中的return语句没有匹配的基础类型。”

Odd. A button is a button, right? But if we examine the documentation, we will see that Button is actually a generic type and not a plain struct like Text:

奇。 一个按钮就是一个按钮,对不对? 但是,如果我们仔细阅读文档 ,将会发现Button实际上是一个通用类型,而不是像Text这样的普通结构:

struct Button<Label> where Label : View

And this holds for a lot of the SwiftUI built-in types — most notably the ones that can contain other views or content. So, we are trying to return either a Button<Text> or a Button<Image> that the compiler (correctly) identifies as two different types and hence refuses to cooperate. This is one of those situations where the rigorous typing of Swift is working against us.

这适用于许多SwiftUI内置类型-最值得注意的是可以包含其他视图或内容的类型。 因此,我们试图返回Button<Text>Button<Image> ,编译器正确地将它们标识为两种不同的类型,因此拒绝合作。 这是Swift严格键入对我们不利的情况之一。

Fortunately, there are two ways to solve this issue, and both deal with satisfying the compiler just enough that it’ll allow us to compile and run our code:

幸运的是,有两种方法可以解决此问题,并且两种方法都足以使编译器满意,从而使我们能够编译和运行代码:

  1. Embedding our views in a Group, preserving as much type information as possible.

    将我们的意见嵌入到Group ,并保留尽可能多的类型信息。

  2. Wrapping our views in AnyView, effectively removing type information.

    将我们的视图包装在AnyView ,可以有效地删除类型信息。

Both methods have their peculiarities and it’s ultimately up to you to decide which one suits you best.

两种方法都有其独特性,最终由您决定哪种方法最适合您。

嵌入组 (Embedding in a Group)

This is what some people consider the “cleanest” approach because embedding your mixed content in a Group preserves all typing information. However, it introduces some types you might not expect and you’re currently limited to only the simple if statements for any conditional switching. This means no if case let or switch statements. If that’s not an issue, then go right ahead. It looks something like this:

这就是某些人认为的“最干净”的方法,因为将您的混合内容嵌入到Group保留所有键入信息。 但是,它引入了一些您可能不会想到的类型,并且当前您仅限于用于任何条件切换的简单if语句。 这意味着没有if case letswitch语句。 如果这不是问题,那就继续吧。 看起来像这样:

Now, this isn’t some “magic” fix that changes the way opaque types work. It merely introduces some additional types that make sure that from a compiler perspective, this function always returns the same type. If we inspect it, we see that the type returned is:

现在,这不是改变不透明类型工作方式的“魔术”解决方案。 它只是引入了一些其他类型,这些类型可以确保从编译器的角度来看,此函数始终返回相同的类型。 如果我们检查它,我们看到返回的类型是:

Group<_ConditionalContent<Button<Text>, Button<Image>>>

Again, Group is a generic type, but it introduces an additional (generic) type _ConditionalContent that has our button types (again generics) in the type parameter list. And this is actually the trick up SwiftUI’s sleeve: By being smart and introducing additional types, it can preserve all the original types and still make the compiler happy because we’re always returning the same type to satisfy the some View return type. But as I’ve mentioned, you’re limited to what SwiftUI can actually express. So, for example, any complex logic switching is off the table for now. Also, understand that this is a very simple case and it’s already generating a complex result type. Now imagine having a lot of nested logic and generic types, and this will soon become very hard to read and comprehend.

同样, Group是泛型类型,但它引入了一个附加的(泛型)类型_ConditionalContent ,该类型在类型参数列表中具有我们的按钮类型(再次为泛型)。 这实际上是SwiftUI的窍门:通过聪明并引入其他类型,它可以保留所有原始类型,并使编译器满意,因为我们总是返回相同的类型以满足some View返回类型。 但是正如我已经提到的那样,您仅限于SwiftUI可以实际表达的内容。 因此,例如,任何复杂的逻辑切换都暂时不在讨论之列。 另外,请了解这是一个非常简单的案例,并且已经在生成一个复杂的结果类型。 现在想象一下,有很多嵌套的逻辑和泛型类型,而这很快将变得很难阅读和理解。

So, the upside is that we maintain all our type information, but the downside is that we will be generating a lot of complex types and we’re limited to the expressiveness of the SwiftUI view builders.

因此,好处是我们保留了所有类型信息,但缺点是我们将生成许多复杂的类型,并且仅限于SwiftUI视图构建器的表现力。

在AnyView中包装 (Wrapping in AnyView)

Wrapping in AnyView is the other method, and it involves something called type erasure to effectively strip away information regarding the types of the views and making it seem like they’re all the same. It looks something like this:

在AnyView中包装是另一种方法,它涉及一种称为类型擦除的方法,可以有效地剥离有关视图类型的信息,并使它们看起来都一样。 看起来像这样:

We are wrapping our views here in an AnyView that conforms itself to the View protocol and will delegate any calls to it to the wrapped view (our buttons). To the outside world (i.e. the compiler), our function now always returns the exact same type (AnyView) and it will not complain.

我们在这里将视图包装在符合View协议的AnyView ,并将对它的所有调用委派给包装的视图(我们的按钮)。 对于外界(即编译器),我们的函数现在始终返回完全相同的类型( AnyView ),并且不会抱怨。

We can make this even easier by introducing an extension to View to provide a function that can return the type-erased view for us and make it work like many of the other modifiers:

我们可以通过向View引入扩展来提供一个函数,该函数可以为我们返回经过类型擦除的视图并使它像许多其他修饰符一样工作,从而使此操作变得更加容易:

The upside here is that we can use the full expressiveness of Swift (and not just whatever SwiftUI has implemented) with regards to control logic: if case let or switch or even other complex logic — it’s all possible. The downside is that you effectively lose access to the regular types and can only access the parts that AnyView exposes to you. Since, most of the time, the wrapping in AnyView will be the last thing you do, it’s not a very big issue and you can still access all the properties provided by the View protocol (since AnyView conforms to View).

这里的好处是,我们可以在控制逻辑方面使用Swift的完整表达能力(而不仅仅是SwiftUI实现的功能): if case letswitch或什至其他复杂的逻辑-一切皆有可能。 缺点是您实际上无法访问常规类型,并且只能访问AnyView公开给您的部分。 因为在大多数情况下, AnyView的包装将是您要做的最后一件事,所以这不是一个很大的问题,并且您仍然可以访问View协议提供的所有属性(因为AnyView符合View )。

There have been some concerns about performance due to the fact that SwiftUI has to destroy and rebuild the view hierarchy whenever the wrapped View inside the AnyView changes, but if you’re not constantly doing this (and most user interfaces don’t), there should not be an issue.

已经有大约性能,因为这样的事实,SwiftUI具有摧毁并重建视图层次每当包裹有些担忧View里面AnyView变化,但如果你不经常这样做(和大多数的用户界面没有),有应该不是问题。

结论 (Conclusion)

Building complex user interfaces in SwiftUI can quite rapidly become a frustrating experience due to the way the compiler dictates how we can handle generic types, protocols with associated types, and opaque types. Sooner or later, you’ll run into some of the aforementioned issues. We’ve seen two ways to circumvent these issues: one by embedding your content in a Group (type-preserving, but with the caveat that you’re limited to what SwiftUI can express) and one by wrapping in AnyView (effectively hiding type information from the compiler, but gaining more expressiveness). Both methods are valid and can be considered for use in your own apps, and now you should have an idea of why you might choose one over the other.

由于编译器指示我们如何处理通用类型,具有关联类型的协议和不透明类型的方式,因此在SwiftUI中构建复杂的用户界面会很快变得令人沮丧。 迟早,您都会遇到一些上述问题。 我们已经看到了两种方法来解决这些问题:一种方法是将您的内容嵌入到一个Group (保留类型,但是需要注意的是,您限于SwiftUI可以表达的内容),另一种方法是通过包装在AnyView (有效地隐藏类型信息)从编译器,但获得更多的表现力)。 这两种方法都是有效的,可以考虑在自己的应用程序中使用,现在您应该知道为什么可能要选择一种方法了。

As a closing note, it is impressive how Swift preserves all the typing information when building views and how it works “most of the time” given the rigorous type checking that the compiler does. If you’re interested in this, I suggest you look at how ViewBuilder works. This is used under the hood to build SwiftUI views containing one or more child views and provide functionality to support basic logic in your view templates using, for example, TupleView and _ConditionalContent (the latter unfortunately being marked private). Swift by Sundell has a nice overview of many of the Swift 5.1 features that power SwiftUI/ViewBuilder.

作为结束语,令人印象深刻的是,在编译器进行严格的类型检查的情况下,Swift如何在构建视图时保留所有类型的信息,以及“大部分时间”如何工作。 如果您对此感兴趣,建议您查看ViewBuilder工作方式。 它在后台用于构建包含一个或多个子视图的SwiftUI视图,并使用TupleView_ConditionalContent (不幸的是后者被标记为私有)提供功能来支持视图模板中的基本逻辑。 Sundell的Swift很好地概述了支持SwiftUI / ViewBuilder的许多Swift 5.1功能 。

We’ve also sort of glossed over how type erasure exactly works in Swift, but it is actually used in more places in Swift, such as AnySequence and AnyPublisher. In the latter case, it is actually helpful to hide some type information not just from the compiler but also from others.

我们还对类型擦除在Swift中的工作原理进行了一些AnySequence ,但实际上它在Swift中的更多地方都得到了使用,例如AnySequenceAnyPublisher 。 在后一种情况下,不仅对编译器而且对其他类型隐藏一些类型信息实际上是有帮助的。

“When you use type erasure this way, you can change the underlying publisher implementation over time without affecting existing clients.” — Apple’s official documentation

“当您以这种方式使用类型擦除时,您可以随时间更改基础发布者实现,而不会影响现有客户端。” — 苹果官方文档

Again, I recommend an article by Swift by Sundell to get to grips with type erasure.

再次,我推荐Sundell的Swift撰写的一篇文章来处理类型擦除。

翻译自: https://medium.com/better-programming/a-mixed-bag-of-swiftui-11e018a280b7

swiftui

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三年经验前端社招——有赞

大家好&#xff0c;我是若川&#xff0c;祝大家中秋节快乐。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12 参与。…

html的 button点击事件无效,InfoWindow里面加button,监听button点击事件无效 求解啊...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼点击infoWindw中的button按钮&#xff0c;无效果&#xff1b;覆盖默认的dom结构html,body,#container {width: 100%;height: 100%;margin: 0px;}p.my-desc {margin: 5px 0;line-height: 150%;}//创建地图var map new AMap.Map(con…

数据挖掘 点击更多 界面_8(更多)技巧,可快速改善用户界面

数据挖掘 点击更多 界面重点 (Top highlight)Creating beautiful, usable, and efficient UIs takes time, with many design revisions along the way. Making those constant tweaks to produce something that your clients, users, and yourself are truly happy with. I k…

三年经验前端社招——腾讯微保

大家好&#xff0c;我是若川。祝大家中秋节快乐。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12 参与。本文经作者…

matlab绘制路线图_绘制国际水域路线图

matlab绘制路线图Two years ago, Shopify was only available in English. Few people in Germany or Japan had heard about us. We had only just formed the international growth team to make Shopify available to people in their native tongue.两年前&#xff0c;Shop…

2021年江苏高考各科成绩查询,江苏2021年高考总分及各科分数

江苏2021年高考总分及各科分数2021-04-16 08:46:02文/董月江苏高考将实施“33”模式&#xff0c;即语数外三门必考&#xff0c;然后在物理、化学、生物、历史、政治、地理六门学科中任选三门进行考试&#xff0c;并计入总分。“6选3”中的3门以等级确定&#xff0c;折算成分数计…

figma下载_通过构建7个通用UI动画来掌握Figma中的动画

figma下载Originally published on my personal blog.最初发布在我的 个人博客上 。 Most designers will spend many hours perfecting every pixel of their static UI designs but will barely spend any time perfecting the transitions between these pages.大多数设计人…

怎么用计算机上的打印设备打印,电脑中怎么添加打印机设备

电脑中怎么添加打印机设备电脑中怎么添加打印机设备呢&#xff0c;下面小编介绍一下。具体如下&#xff1a;1. 打开电脑&#xff0c;点击“控制面板”图标2. 在如图页面&#xff0c;找到“硬件和声音”&#xff0c;点击打开3. 然后点击”设备和打印机“选项4. 打开后&#xff0…

三年经验前端社招——朴朴科技

大家好&#xff0c;我是若川&#xff0c;祝大家中秋节快乐。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12 参与。…

EL表达式和JSTL标准标签库

一、EL表达式 什么是EL表达式 EL&#xff08;Express Lanuage&#xff09;表达式可以嵌入在jsp页面内部减少jsp脚本的编写EL出现的目的是要替代jsp页面中脚本的编写。EL表达式的作用 EL最主要的作用是获得四大域中的数据// 1. pageContext ${pageScope.key}; // 2. request ${r…

(转)细说Cookie

原文地址&#xff1a;http://www.cnblogs.com/fish-li/archive/2011/07/03/2096903.htmlCookie虽然是个很简单的东西&#xff0c;但它又是WEB开发中一个很重要的客户端数据来源&#xff0c;而且它可以实现扩展性很好的会话状态&#xff0c; 所以我认为每个WEB开发人员都有必要对…

三年经验前端社招——丰巢科技

大家好&#xff0c;我是若川。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12 参与。本文经作者lxcan 授权转载&am…

数字集成电路物理设计_数字世界的物理词汇

数字集成电路物理设计Nineteen Eighty-Four is my favourite novel; I must have read it half a dozen times. There are many reasons why I believe it to be a work of literary genius, but recently I’ve been thinking about one specific aspect of it that has a ver…

yum安装Docker失败No package docker available

2019独角兽企业重金招聘Python工程师标准>>> 原因&#xff1a;yum没有找到docker包&#xff0c;更新epel第三方软件库。 yum install epel-release再yum安装docker&#xff1a; yum install -y docker转载于:https://my.oschina.net/yuantangxi/blog/3033800

黑客宣言_情感设计宣言

黑客宣言重点 (Top highlight)I have a feeling that this article is going to be slightly different from the rest of the articles I’ve recently seen or read. Everybody seems to be fighting on topics such as “UX designer or Product Designer”? “UX/UI is ok…

[转]VS2010中的单元测试

本文转自:http://zxianf.blog.163.com/blog/static/30120701201101011757499/ 在VS2010中&#xff0c;单元测试的功能很强大&#xff0c;使得建立单元测试和编写单元测试代码&#xff0c;以及管理和运行单元测试都变得简单起来&#xff0c;通过私有访问器可以对私有方法也能进行…

三年经验前端社招——Shopee

大家好&#xff0c;我是若川。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12本文经作者lxcan 授权转载&#xff0…

简易拨号器iCall

iCall是由哥开发的一款android平台的电话拨号小工具&#xff0c;简单不能再简单的Android手机拨号程序。 代码不过几行&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/…

苹果手机隐私分析数据是什么_苹果公司以用户为中心的隐私保护方法能教给我们什么?

苹果手机隐私分析数据是什么重点 (Top highlight)Moving beyond the rollercoaster of excitement and controversy Apple has been in for the last week, there was one thing in the WWDC keynote that stood out for me. One thing I really appreciated seeing. There is …

为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘!

1. 前言大家好&#xff0c;我是若川。最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;已经有超50人提交了笔记&#xff0c;群里已经有超1200人&#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12之前写的《学习源码整体架构系…