mapkit_SwiftUI + MapKit中的多态地图注释

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视图支持三种类型的注释视图: MapPinMapMarkerMapAnnotation 。 但是由于某种原因,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, and MapPin.”

“请勿创建符合此协议的类型。 而是使用框架提供的类型之一MapAnnotationMapMarkerMapPin 。”

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 ? 这是一个基本示例:

Image for post
Fig 1. A map that shows two (fake) restaurants in Oslo, Norway
图1.一张地图,显示挪威奥斯陆的两家(假)餐厅

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对象:

Image for post
Fig 2. Xcode complains that not all paths return the exact same concrete type since the content of each MapAnnotation may differ
图2. Xcode抱怨并非所有路径都返回完全相同的具体类型,因为每个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类型多态返回MapAnnotationMapMarkerMapPin ,则会遇到类似的问题。 当我们尝试概括注释项时,将发生以下情况:

Image for post
Fig 3. We start running into limitations in Swift’s type system
图3.我们开始遇到Swift的类型系统的局限性

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中遇到了另一个问题

Image for post
Fig 4. One of the initializers of the Map struct
图4. Map结构的初始化器之一

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会是什么样?

Image for post
Fig 5. Map with a heterogeneous array of annotation items and annotation views
图5.带有注释项和注释视图的异构数组的映射

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.)

对于上面的示例,我创建了自己的几种类型: MapAnnotationItemAnyMapAnnotationItemAnyMapAnnotation 。 我还用两个方便的初始化程序扩展了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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【课程特点】1、231节大容量课程:包含了SwiftUI的大部分知识点,详细讲解SwiftUI的方方面面;2、15个超级精彩的实例:包含美食、理财、健身、教育、电子商务等各行业的App实例;3、创新的教学模式:手把手教您SwiftUI用户界面开发技术,一看就懂,一学就会;4、贴心的操作提示:让您的眼睛始终处于操作的焦点位置,不用再满屏找光标;5、语言简洁精练:瞄准问题的核心所在,减少对思维的干扰,并节省您宝贵的时间;6、视频短小精悍:即方便于您的学习和记忆,也方便日后对功能的检索;7、齐全的学习资料:提供所有课程的源码,在Xcode 11 + iOS 13环境下测试通过; 更好的应用,更少的代码!SwiftUI是苹果主推的下一代用户界面搭建技术,具有声明式语法、实时生成界面预览等特性,可以为苹果手机、苹果平板、苹果电脑、苹果电视、苹果手表五个平台搭建统一的用户界面。SwiftUI是一种创新、简单的iOS开发的界面布局方案,可以通过Swift语言的强大功能,在所有的Apple平台上快速构建用户界面。 仅使用一组工具和API为任何Apple设备构建用户界面。SwiftUI具有易于阅读和自然编写的声明式Swift语法,可与新的Xcode设计工具无缝协作,使您的代码和设计**同步。自动支持动态类型、暗黑模式、本地化和可访问性,意味着您的**行SwiftUI代码已经是您编写过的非常强大的UI代码了。 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值