iOS开发笔记15:地图坐标转换那些事、block引用循环/weak–strong dance、UICollectionviewLayout及瀑布流、图层混合
1.地图坐标转换那些事
(1)投影坐标系与地理坐标系
地理坐标系使用三维球面来定义地球上的位置,单位即经纬度。但经纬度无法精确测量距离戒面积,也难以在平面地图戒计算机屏幕上显示数据。通过投影的方式可以将其转换成平面的投影坐标系,不同的投影方式可能会带来不同的变形及误差,类似于把一个橘子的橘子皮剥开摊平到桌面。
GPS以及iOS系统定位获得的坐标是地理坐标系WGS1984,Web地图一般用的坐标细是投影坐标系WGS 1984 Web Mercator,国内出于相关法律法规要求,对国内所有GPS设备及地图数据都进行了加密偏移处理,代号GCJ-02,这样GPS定位获得的坐标与地图上的位置刚好对应上,特殊的是百度地图在这基础上又进行一次偏移,所以在处理系统定位坐标及相关地图SDK坐标时需要转换处理下,根据网络资源,目前有一些公开的转换算法。
(2)系统定位坐标显示在原生地图、谷歌地图或高德地图--WGS1984转GCJ-02
苹果地图及谷歌地图用的都是高德地图的数据,所以这三种情况坐标处理方法一样,即将WGS1984坐标转换成偏移后的GCJ-02才可以在地图上正确显示位置。
const double a = 6378245.0;
const double ee = 0.00669342162296594323;
+ (CLLocationCoordinate2D)transform:(CLLocationCoordinate2D) latLng
{
double wgLat = latLng.latitude;
double wgLon = latLng.longitude;
double mgLat;
double mgLon;
if ([self outOfChina:wgLat :wgLon ])
{
return latLng;
}
double dLat = [self transformLat:wgLon-105.0 :wgLat - 35 ];
double dLon = [self transformLon:wgLon-105.0 :wgLat - 35 ];
double radLat = wgLat / 180.0 * M_PI;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * M_PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * M_PI);
mgLat = wgLat + dLat;
mgLon = wgLon + dLon;
CLLocationCoordinate2D loc2D ;
loc2D.latitude = mgLat;
loc2D.longitude = mgLon;
return loc2D;
}
+ (BOOL) outOfChina:(double) lat :(double) lon
{
if (lon < 72.004 || lon > 137.8347) {
return true;
}
if (lat < 0.8293 || lat > 55.8271) {
return true;
}
return false;
}
+ (double) transformLat:(double)x :(double) y
{
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y +
0.2 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 *sin(2.0 * x *M_PI)) * 2.0 /
3.0;
ret += (20.0 * sin(y * M_PI) + 40.0 *sin(y / 3.0 *M_PI)) * 2.0 / 3.0;
ret += (160.0 * sin(y / 12.0 * M_PI) + 320 *sin(y * M_PI / 30.0)) * 2.0 /
3.0;
return ret;
}
+ (double) transformLon:(double) x :(double) y
{
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 /
3.0;
ret += (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0;
ret += (150.0 * sin(x / 12.0 *M_PI) + 300.0 *sin(x / 30.0 * M_PI)) * 2.0 /
3.0;
return ret;
}
(3)使用百度地图SDK
使用百度地图SDK,定位也使用sdk中提供的方法,则获得坐标是百度在GCJ-02基础上再一次偏移的坐标,如果要将定位坐标显示在苹果地图上,则需要转换成GCJ-02下的坐标,JZLocationConverter提供了三种坐标间的转换方法,
(4)参考
2.block引用循环及检测方法&weak–strong dance
使用block需要注意避免引用循环造成内存问题,如图所示,对于strong属性的property或者复用的cell,都是被self持有或者说强引用的,在使用block回调前,需要将self声明为一个weak类型的weakSelf再进行使用,否则将造成引用循环。
但并不是所有block回调处理中都需要使用weakSelf,例如self并没有强引用vc,在vc里使用self,是不会造成引用循环的。
对于类似这种可能造成引用循环的地方,使用Instrumens的Leaks工具是检测不出来的,比较便捷的方法是在controller检测“dealloc”是否执行,当然也有可能是其它原因造成,但只要有引用循环发生则controller肯定无法释放,“dealloc”也是无法执行的。
当然,这是一般情况,有一些特殊情况例如多线程下block,block里的weak变量执行指令前可能已经被释放掉了,这时可以对其__strong 一下,这样系统会在block执行完成后再释放该变量。
3.UICollectionviewLayout及瀑布流
目前用到了一些第三方的UICollectionviewLayout类库,例如CSStickyHeaderFlowLayout,提供header悬停及下拉放大的视差效果等
通过自定义UICollectionviewLayout可以灵活自定义很多布局效果,以经典的瀑布流为例。
其特点是cell的高度不一致,首尾间隔要保持一致,如此一来就需要自定义layout来调整cell的布局位置。
这里cell高度随机生成为40-140之间的数值。
(1)计算并缓存相关布局信息
重点是计算布局这里,第一行Y值都相同,但后续每个cell的高度不同,需要去调整cell的位置,首先找到目前高度最小的那一列(排列第一行时,所有cell的Y值一致,将第一列当做高度最小的一列或者将顶部间距看成是一行高度一致的cell),将cell排列在此列下方,并更新此列的Y值,然后再继续找高度最小的一列,将cell排列在其下方并更新此列Y值,以此类推,不断寻找高度最小的那一列,将后续cell排列在其下方并更新这一列的Y值,即可完成布局,当然设置cell布局时还要将cell之间的间隔考虑进去。
(2)计算contentSize并生成布局
contentSize中关键是高度,这里取高度最大那一列的Y值即为collectionview的高度。
(3)参考
4.图层混合
使用Instruments中的Core Animation工具能够检测图层渲染和动画的相关问题,包括图层混合问题,即当多个图层叠加在一起,颜色不同时,处理这样的颜色混合情况会消耗GPU资源,监测发现这些区域会变红,其它正常区域为绿色
对于UILabel的图层混合问题,将其设置为与背景色一致并裁剪即可。(不会产生离屏渲染)