20170226
因项目已准备上线,部分功能已更新。
20170305
(上线中遇到的坑)[http://www.jianshu.com/p/75cbea8f60ed]
前言
开发这个app纯粹是为了兴趣和练手。目前没有上线,发出来纯粹是为了可以更好的交流。程序中有很多不成熟的地方,也希望看到的人可以批评指正。
效果图
效果图.gif- 示例demo中针对每种功能都有一个单独的XIB及功能描述。
- 我们需要用到的几个功能包括:
- 基础的
BMKMapView
—— 百度地图中,最基础也是最重要的地图类。覆盖物的添加,标注的添加等等的代理回调,都与他相关 - 区域搜索功能 —— 用于行政区域边界的绘制,绘制区域的覆盖物。
- 定位功能 —— 用于定位当前位置
- 路径规划功能 —— 暂时还未用到
- 基础的
框架的搭建
构想
百度地图提供的每一个工具类,都有对应的代理。因为设计的页面(一期)只有一个ViewController
,如果将所有的代理实现及功能都放在一起,代码会非常乱。
管理地图的生命周期:自2.0.0起,BMKMapView新增viewWillAppear、viewWillDisappear方法来控制BMKMapView的生命周期,并且在一个时刻只能有一个BMKMapView接受回调消息,因此在使用BMKMapView的viewController中需要在viewWillAppear、viewWillDisappear方法中调用BMKMapView的对应的方法,并处理delegate,代码如下:
(void)viewWillAppear:(BOOL)animated {
[_mapView viewWillAppear];
_mapView.delegate = self; // 此处记得不用的时候需要置nil,否则影响内存的释放
}
-(void)viewWillDisappear:(BOOL)animated {
[_mapView viewWillDisappear];
_mapView.delegate = nil; // 不用时,置nil
}
百度工具类的单例实现
- 根据上述的构想,将
定位功能
,路径规划
,区域搜索
功能都做成单例。在单例的调用中,放入基础的地图类BMKMapView
类。单例的初始化可以写为
[[BaiduLocationTool initInstanceWithMapView:self.BaseBaiduMapView] startLocation];
// 默认画出鼓楼区的边界,设置district对应的delegate
[BaiduDistrictTool initInstanceWithMapView:self.BaseBaiduMapView];
- 设置代理和单例初始化的同时,要开启上述的地图管理生命周期。这里的做法,是用一个广播通知给每个工具类单例,控制各个工具类的代理开关。
[[NSNotificationCenter defaultCenter] postNotificationName:BAIDU_DELEGATE_CTRL_RADIO
object:DELEGATE_ON];
-
工具类的简述及对应方法(图)
工具类流程图.png
鉴权问题
导航模式
框架综述
上述的做法,主要是为了修改代理对应的工具时,便于维护和封装。
页面上类的描述
参考图-
ViewController
—— 整体的控局类。当Overlay
(覆盖物)和Annotation
(标注)添加入本类的BMKMapView
时,会触发相应的代理。-
MyPinAnnotationView
标注视图类。这里自定义后可以去替换他的背景图。
-
-
BottomDistrictView
—— 底部栏,根据福州五区设置。在选择后会弹出。他包含以下几个subview
:-
tableview
显示下部的站点列表- 我们的
TableViewCell
是自定义的Cell
,包含三个部分,左侧的区域logo
,中间的站点信息和右侧的出发按键(GotoView) -
GotoView
也是一个自定义的XIB
,包括一个画好的轮子和一个按键
自定义cell
- 我们的
- 区域名按钮 ——
DistrictButton
,用于选择/切换行政区域。
选中后,触发以下功能- 如果
BottomDistrictView
未弹出,那么弹出 - 顶部一共有 6个,本身选中时会发送一个广播,让其他5个按键不处于选中状态(背景图及样式修改)
- 从
model
中取出对应区域的站点信息数组,写入当前的tableview
中 - 从
model
中取出对应区域的站点信息的经纬度,转化为一个标注数组annotation
,通过代理传值发送给ViewController
,让主页添加对应标注 - 通过代理传值,将当前的区域名称发送给
ViewController
,让主页添加覆盖物overlays
- 如果
-
数据的存储
app中有两种数据需要交互。
- 站点信息,包括 站点名,所属行政区域,站点地址,站点的经纬度。
- 行政区域边界的坐标。
站点信息
将站点信息存储成JSON
格式,调试中的测试需要,将数据放至七牛云存储。获取一个URL
.
- 经纬度
因并没有办法获取到完整的站点的经纬度列表,demo中用到的是自己写死的固定数据。在主页上添加一个tapGestureRecongnizer
返回点击后的经纬度进行测试。如下:
-(IBAction)fetchPoitCoordiate:(UITapGestureRecognizer *)sender {
CGPoint point = [sender locationInView:self.BaseBaiduMapView];
CLLocationCoordinate2D coo = [self.BaseBaiduMapView convertPoint:point toCoordinateFromView:self.BaseBaiduMapView];
NSLog(@"经纬度:%lf, %lf", coo.longitude, coo.latitude);
}
申请到的数据返回后,采用MJExtension
作转化。因Xcode 8禁止了插件,所以我们无法使用JSONFormatt
作快速转化。推荐使用一个第三方开发者写的mac app
叫WHC_DataModelFactory
,支持OC
和Swift
站点信息的类,包含有以下的方法。
stationinfo class
行政区域边界
百度地图SDK中,行政区域的绘制是通过覆盖物来完成的。
- 一般而言,这个数据不会经常变动,我这里采用的是将五个行政区域的边界,用
plist
存储成一个键值对。
行政区域边界说明.png - 专门写了一个
plistManager
类来管理plist在沙盒中的存储。若读出的字典为空或对应路径的文件不存在,就调用BaiduMapTool
类搜索边界数据,然后创建一个。 - 拿到坐标信息后,去调用
BaiduMapTool
类中对应的方法,产生一个覆盖物对象。BaiduMapTool
类中有一个接口
@property (nonatomic,strong) NSMutableDictionary *districtPolyganDict; //存放区域overlay的字典
键值对为: 区域名称 -> 覆盖物.
将刚才获取到的覆盖物对象存入到对应的区域下。等待调用。
需要注意的是,按键在调用“全市”这个需求的时候,我们需要的是将五个区的覆盖物拼接在一起,因此整个字典的分布为:
xx区 -> 对应覆盖物对象
xx区 -> 对应覆盖物对象
...
全市 -> 上述覆盖物对象组成的数组
主页和底部栏之间的数据交互方式
本应用的设想是:
主页和底部栏之间,通过代理交互数据,以下简称主页为A,底部栏View为B
示意图
-
主页和底部栏之间的数据交互顺序,其实分别遵循下图的左右两个部分。
但是特别容易忽略的一点是,假设我们从A发起,一直执行到最后一条,选中了B的cell,那么B会继续循环执行右边的操作,造成这个过程一直在死循环。解决的方式是,在发送代理通知时,在各自的类中添加标志位,判断是手势出发的选中,还是传值执行代理时代码触发的选中。
![Uploading CD7983B696EFC3FC3A5C8846F9BD4CA9_264088.gif . . .]
交互代理示意图.png
还未完成的部分及疑问
- 顶部栏的路径规划/搜索功能,设置功能,包括更新数据,设置偏好等。
- 判断当前的位置是否处于五区内,若处于,显示当前位置半径xx公里内的自行车站点作为“附近站点”
- 便民自行车官网上,可以观察到目前福州全市150多个站点的地理位置标注以及信息。在我打开网页的前端源代码时可以清楚的看到坐标点的信息。那么是否有办法通过搭建一个自己的前端,去“福州便民自行车”官网上爬取他相应的信息,生成json信息。这样我们的数据就不是死的固定数据,而是有一个前端逻辑做索引。
Many thanks.
2017.1.15
网友评论