基于 Swift 多地图源业务向地图控件实现(一):封装地图源

上一篇谈了整体的设计思路,这篇谈一下具体的实现设计。因为我的项目里第一个接入的地图源是高德地图,这里的接口以高德地图作为示范。

既然要接入多个地图源,可以良好的支持地图源切换,那么第一步就是隔离具体地图源。隔离具体实现最常使用的方式就是使用接口隔离。UITableView 中常用的 UITableViewDataSource 也是类似的机制,使用接口隔离了具体的 dataSource 实现。

我们定义一个 protocol 来声明地图源应该提供的能力:

public protocol VendorMapView: class {
    /// 实际坐标转换到指定 View 上坐标
    func convert(coordinate: CLLocationCoordinate2D, toPointTo view: UIView?) -> CGPoint
    
    /// 转换 View 上的点为实际坐标
    func convert(point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D
    
    func setCenter(coordinate: CLLocationCoordinate2D)    
}

我简化了代码,这里只声明了核心的坐标转换方法和作为演示的设置地图中心坐标的方法。声明实现的对象需要是类是因为我们明确的知道实现这个接口的对象是具体的地图源,是 UIView 类型。

下一步要做的是让地图源实现这个接口。

import MAMapKit

extension MAMapView: VendorMapView {

    public func convert(coordinate: CLLocationCoordinate2D, toPointTo view: UIView?) -> CGPoint {
        return convert(coordinate, toPointTo: view)
    }
    
    public func convert(point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D {
        return convert(point, toCoordinateFrom: view)
    }

    public func setCenter(coordinate: CLLocationCoordinate2D) {
        setCenter(coordinate, animated: true)
    }

}

到这里我们已经隔离了具体的地图源了。假设我们自定义地图名为 MeshMapView,现在在我们自定义地图中声明地图代理:

public class MeshMapView: UIView {
    
    public static var currentMapVendor = MapVendor.gaode
    
    var gaodeMap: MAMapView?
    var baiduMap: BMKMapView?

    var map: VendorMapView? {
        switch MeshMapView.currentMapVendor {
        case .gaode:
            return gaodeMap
        case .baidu:
            return baiduMap
        }
    }
}

extension MeshMapView {
    
    /// 地图提供商
    public enum MapVendor: CaseIterable {
        case gaode, baidu
        
        var descrption: String {
            switch self {
            case .gaode:
                return "高德"
            case .baidu:
                return "百度"
            }
        }
    }
}

因为地图控件是针对业务封装的,可能有很多业务相关的枚举类型,因此在单独的 extension 中声明地图控件的相关枚举。我们需要知道当前的地图源是哪一个供应商,因此使用 MapVendor 列出所有的地图供应商。

在我的业务场景里,如果在某个页面选择了某个地图源,那么之后所有的地图控件都使用这个地图源。从这个需求出发,因此当前选择的地图源是一个全局的设置,因此声明为静态属性。
具体地图源的选择分发我们用 VendorMapView 类型的 map 进行隔离。

接着补充一下控件的初始化方法:

import SnapKit

public class MeshMapView: UIView {

    public init() {
        super.init(frame: CGRect.zero)
        addVendorMapView()
    }

    private func addVendorMapView() {
        switch MeshMapView.currentMapVendor {
        case .gaode:
            let gaodeMap = MAMapView(frame: CGRect.zero)
            gaodeMap.mapType = .satellite
            gaodeMap.zoomLevel = 16.5
            addSubview(gaodeMap)
            gaodeMap.snp.makeConstraints { (make) in
                make.edges.equalToSuperview()
            }
            self.gaodeMap = gaodeMap
        case .baidu:
           // 。。。
        }
    }
}

到这里的代码实现了通过 currentMapVendor 属性可以配置地图控件的地图源。如果要增加一个地图源,只需要让新地图源实现 VendorMapView,MapVendor 枚举增加一个类型,最后在地图控件中增加实例的初始化方法。这个设计对地图源的新增开放,不需要修改原有的代码逻辑,通过新增加代码就可以实现,容易维护。
不过上面的 addVendorMapView 方法还有优化的空间。每个地图的初始化配置的逻辑是具体实现,严格的说和 MeshMapView 并不直接相关,MeshMapView 不关心具体地图供应商的配置。因此可以把地图源初始化配置代码移到地图源自身扩展中:

public protocol VendorMapView: class {
    func initialConfig()
}

extension MAMapView: VendorMapView {
    func initialConfig() {
        mapType = .satellite
        zoomLevel = 16.5
    }
}

但是初始化配置的代码写在一个地方也是可以接受的。好处是如果一个通用的配置,比如地图的默认 zoomLevel 要改为 10,如果初始化代码写在一起只在一个地方改就可以了,不用去四处找。这里我的想法是虽然几个地图源初始化配置写在一起方法的长度可能会有三四十行,但是初始化代码逻辑复杂度很低,写在一个方法里也是可以接受的。看开发者个人喜好了。

最后一步我们要暴露自定义地图控件的地图相关方法。因为这类方法只是封装了一层,最后是直接调用到具体地图源,不是业务相关的,因此建议单独写在一个 extension 里:

extension MeshMapView {
    
    public func setCenter(coordinate: CLLocationCoordinate2D) {
        map?.setCenter(coordinate: standardCoordinate)
    }
}

到这里我们就完成地图源的隔离与封装。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容