mapkit
On WWDC 2020, Apple announced a much sought-after addition to SwiftUI, namely the new SwiftUI-native Map
view. 🗺 Finally we don’t have to create messy wrappers around MKMapView
anymore. 🙌 However, there’s a catch.
在WWDC 2020上,Apple宣布了SwiftUI广受欢迎的功能,即新的SwiftUI本机Map
视图。 🗺最后,我们不再需要围绕MKMapView
创建混乱的包装器。 🙌但是,有一个陷阱。
“Maps only show annotation views of the same type, backed by a single collection.” — Apple’s official documentation
“地图仅显示相同类型的注释视图,并由单个集合支持。” —苹果官方文档
At first glance I found this to be a bit strange. The Map view supports three types of annotation views: MapPin
, MapMarker
, and MapAnnotation
. But for some reason, Apple won’t let us use these types together, although doing so is both possible and common practice with MKMapView
. How come? 🤔
乍一看,我发现这有点奇怪。 Map视图支持三种类型的注释视图: MapPin
, MapMarker
和MapAnnotation
。 但是由于某种原因,Apple不允许我们一起使用这些类型,尽管使用MKMapView
既可以这样做,也可以作为惯例。 怎么会? 🤔
While UIKit is object-oriented, SwiftUI is protocol-oriented (remember Crusty from WWDC 2015?) Because of this, virtually all data types in SwiftUI are either structs or enums used in conjunction with protocols. This comes with a set of challenges that I’ll elaborate on in a bit.
虽然UIKit是面向对象的,但是SwiftUI是面向协议的(还记得WWDC 2015中的Crusty吗?)因此,SwiftUI中的几乎所有数据类型实际上都是与协议结合使用的结构或枚举。 这将带来一系列挑战,我将在后面详细阐述。
Each of the aforementioned annotation views implements MapAnnotationProtocol
. If we look at Apple’s documentation for this protocol, we’re told not to make our own types conform to it:
每个上述注释视图均实现MapAnnotationProtocol
。 如果我们查看Apple的有关此协议的文档,就会被告知不要使我们自己的类型符合该协议:
“Don’t create types conforming to this protocol. Instead, use one of the framework-provided types
MapAnnotation
,MapMarker
, andMapPin
.”“请勿创建符合此协议的类型。 而是使用框架提供的类型之一
MapAnnotation
,MapMarker
和MapPin
。”
Very little information is disclosed about MapAnnotationProtocol
, but from what I’ve gathered it contains one undocumented field: _annotationData of type _MapAnnotationData. Xcode won’t autocomplete it, nor will it provide any information about it, but it’s still exposed to us because Swift’s grammar requires that all protocol fields be public, which means that we’ll be able to use it further down in the article. 👏
很少公开有关MapAnnotationProtocol
,但是据我收集的信息,它包含一个未记录的字段: _MapAnnotationData类型的_annotationData。 Xcode不会自动完成它,也不会提供有关它的任何信息,但是它仍然对我们开放,因为Swift的语法要求所有协议字段都是公开的,这意味着我们将能够在本文的后续部分使用它。 👏
So how does Apple want you to use Map
? Here’s a basic example:
那么Apple如何让您使用Map
? 这是一个基本示例:
In the example above the collection of annotation items is homogeneous. If you look at the annotationContent(annotationItem:) function you’ll see that it returns an opaque type, which is a common theme in SwiftUI. Since the return type is opaque, all paths of the function must return the exact same type, which means that you may not return multiple MapAnnotation objects in the same function where the content type differs:
在上面的示例中,注释项的集合是同质的。 如果您查看注解 Content (annotationItem :)函数,您会发现它返回不透明类型,这是SwiftUI中的常见主题。 由于返回类型是不透明的,因此该函数的所有路径都必须返回完全相同的类型,这意味着您不能在内容类型不同的同一函数中返回多个MapAnnotation对象:
You run into a similar problem if you want to return either MapAnnotation
, MapMarker
, or MapPin
polymorphically based on a generic AnnotationItem type. Here’s what happens when we try to generalize the annotation items:
如果要基于通用的AnnotationItem类型多态返回MapAnnotation
, MapMarker
或MapPin
,则会遇到类似的问题。 当我们尝试概括注释项时,将发生以下情况:
Annotation items must conform to Identifiable, which is a PAT (protocol with an associated type), and PATs, unlike plain protocols, don’t have an existential. This is what’s causing us the errors in fig 3. The term “existential” is seldom used, because in most cases you don’t need to explicitly know about them. If you’d like to learn more about them, you can do so here. Long story short: protocols have an existential, but PATs don’t, thus PATs can’t be used as types on their own. They can only be used to constrain a generic type.
注释项必须符合Identifiable ,这是PAT(具有关联类型的协议),并且PAT与普通协议不同,它不存在。 这就是导致我们出现图3中错误的原因。很少使用“存在”一词,因为在大多数情况下,您无需明确了解它们。 如果您想了解更多有关它们的信息,可以在这里进行 。 长话短说:协议具有存在性,但是PAT没有,因此不能将PAT单独用作类型。 它们只能用于约束通用类型。
There was a proposal to enhance existentials so that they’d be applicable in more contexts, but progress on this topic has been slow. In Swift’s generics manifesto, generalized existentials are listed as an unlikely future feature addition. Generalized existentials would have made it possible to create a more elegant API for heterogeneous collections of polymorphic types, but alas, we don’t have such a language feature available to us as of now, so we’ll have to resort to a different solution.
有人提出了增强存在性的建议 ,以便将它们应用于更多的上下文中,但是该主题的进展缓慢。 在Swift的泛型宣言中, 广义的存在性被列为未来不太可能增加的功能。 通用的存在性将有可能为多态类型的异构集合创建更优雅的API,但是,las,到目前为止,我们还没有可用的语言功能,因此我们不得不求助于其他解决方案。
We run into yet another problem with MapAnnotationProtocol:
我们在MapAnnotationProtocol中遇到了另一个问题:
As can be seen in fig 4, the generic Annotation type is constrained by MapAnnotationProtocol. This means that the type returned by the annotationContent(annotationItem:) function must be a concrete type, which is why we use the opaque return type syntax (i.e. the some keyword.)
如图4所示,通用注释类型受MapAnnotationProtocol约束。 这意味着注解内容(annotationItem :)函数返回的类型必须为具体类型,这就是为什么我们使用不透明的返回类型语法(即some关键字)的原因。
So at this point it’s clear why Apple limited the interface of Map
to only support homogeneous collections of map annotation items and views, but is there perhaps a way around this? Turns out, there is.
因此,在这一点上很清楚,为什么Apple将Map
的界面限制为仅支持同类的地图注释项和视图集合,但是也许有办法解决吗? 事实证明, 有 。
Fortunately, there is a workaround for both of the aforementioned problems which you may be familiar with: type erasure. It is in fact possible to create a type-erased version of MapAnnotationProtocol and it actually works in spite of one of the fields being undocumented / hidden from us. One thing to take note of though, is that MapAnnotationProtocol isn’t a PAT, so creating a type-erased version of it wouldn’t have been necessary had it not been for the fact that the annotationContent(annotationItem:) function must return a concrete implementation of MapAnnotationProtocol as opposed to the existential of MapAnnotationProtocol. Fortunately for us, a type-erased version of MapAnnotationProtocol satisfies this requirement.
幸运的是,对于您可能熟悉的上述两个问题,都有一个解决方法: type erasure 。 实际上,可以创建一个类型擦除的MapAnnotationProtocol版本,并且尽管其中一个字段未被记录/对我们隐藏了,但它实际上仍有效。 不过,需要注意的一件事是MapAnnotationProtocol不是PAT,因此不必创建一个类型擦除的版本, 除非注解Content (annotationItem :)函数不必返回一个事实。与MapAnnotationProtocol的存在相反的MapAnnotationProtocol的具体实现。 对我们来说幸运的是, MapAnnotationProtocol的类型擦除版本可以满足此要求。
So what could Map look like with some type erasure thrown in?
那么,如果插入某种类型的擦除, Map会是什么样?
For the example above I created a couple of types of my own: MapAnnotationItem, AnyMapAnnotationItem, and AnyMapAnnotation. I also extended Map with two convenience initializers that work with these types (notice how the initializer in fig 5 doesn’t require you to provide a annotationContent(annotationItem:) function.)
对于上面的示例,我创建了自己的几种类型: MapAnnotationItem , AnyMapAnnotationItem和AnyMapAnnotation 。 我还用两个方便的初始化程序扩展了Map ,这些初始化程序可以使用这些类型(请注意,图5中的初始化程序不需要您提供AnnotationContent(annotationItem :)函数。)
Each annotation item type conforms to MapAnnotationItem, which also implements Identifiable. AnyMapAnnotationItem is used in the example above to create a heterogeneous array of annotation items by erasing the the type of each concrete MapAnnotationItem instance. In addition to backing each annotation with model data, MapAnnotationItem is also responsible for defining what the annotation view should look like with its annotation property (akin to how views that implement the View protocol define what they should look like with their body property.)
每种注释项目类型都符合MapAnnotationItem,后者还实现了Identifiable 。 上面的示例中使用AnyMapAnnotationItem通过删除每个具体MapAnnotationItem实例的类型来创建注释项的异构数组。 除了使用模型数据支持每个注释外, MapAnnotationItem还负责定义其注释属性应具有的注释视图外观(类似于实现View协议的视图如何通过其body属性定义其外观)。
Then there’s the AnyMapAnnotation type, which is the type-erased version of MapAnnotationProtocol:
然后是AnyMapAnnotation类型,它是MapAnnotationProtocol的类型擦除版本:
So Apple doesn’t really want us to implement this protocol ourselves, but I think this is a reasonable use case.
因此,Apple确实不希望我们自己实现此协议,但是我认为这是一个合理的用例。
Then there’s the final piece of the puzzle which are these initializer extensions that tie everything together:
然后是难题的最后一部分,这些初始化程序扩展将所有内容捆绑在一起:
Simply include these files in your project and you’ll be able to mix and match annotation items and annotation views. ✨
只需将这些文件包括在您的项目中,即可混合和匹配注释项和注释视图。 ✨
结论 (Conclusion)
This concludes my article. I’m curious to see whether Apple will update Map
to support heterogeneous annotation views out of the box in a future update. I wrote this article on 19 July 2020 after encountering the limitations described above in Xcode 12 beta 2 and felt compelled to share my learnings with others who might find this interesting or useful. Considering that Xcode 12 is still in beta and that this new Map API is unstable, we may well see changes to it in the coming weeks or months.
我的文章到此结束。 我很好奇苹果是否会在以后的更新中立即更新Map
以支持异构注释视图。 在遇到上述Xcode 12 beta 2中所述的局限性之后,我于2020年7月19日写了这篇文章,并感到不得不与可能会觉得有趣或有用的其他人分享我的经验。 考虑到Xcode 12仍处于测试阶段,并且这个新的Map API不稳定,因此我们很可能会在未来几周或几个月内对其进行更改。
翻译自: https://medium.com/swlh/map-and-heterogeneous-annotation-views-in-swiftui-mapkit-93cd17e98ae1
mapkit