斯坦福大学iOS开发公开课总结(十四 & 十五) :Core Location, MapKit, 在地图上标识 Flickr 摄影师作品Demo

本总结将第十四和十五课放在了一起,原因有二:第一是略去了ipad开发Demo的部分(因为笔者木有ipad,无法进行调试)。第二是两节课都讲解了关于地图框架的相关知识,故将二者放在一起总结。

在本篇总结的最后,会给大家讲解在地图上显示Flickr上摄影师的照片作品。

Network Activity Indicator


顾名思义,该控件叫做网络活动指示器。当app有网络活动时,可以让状态栏左边的小圆圈滚动用来提示用户当前的网络状态。

@property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; 
```
如果设定为YES,状态栏上的小转轮就会转,反之亦然。

>注意:应用中的所有线程都可使用这个转轮,我们需要通过各种方法来向用户准确显示转轮的状态。

#Core Location
-------------
通过该框架的基本类:```CLLocation```,我们能获得设备处于地球上的位置信息。

##Core Location几个重要的属性:
####1. 坐标属性
```
typedef struct {
CLLocationDegrees latitude;    //double value
CLLocationDegrees longitude;   //double value
} CLLocationCoordinate2D;
```
####2. 高度
```
@property(readonly, nonatomic) CLLocationDistance altitude; //单位是米
```

####3. 变化精度:

```
@property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy;//水平精度
@property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy;//高度精度
```

如何获得CLLocation?
通过实例化```CLLocationManager```类,让其告诉它的代理当前设备所处的位置。
下面来介绍一下```CLLocationManager```:

# CLLocationManager

-----
## CLLocationManager的工作步骤:
1.查看硬件是否支持位置更新。
2.实例化```CLLocationManager```让其告诉它的代理当前的位置。
3.设置位置更新的类型(精度)。

```
@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy; //期望的经度
@property(assign, nonatomic) CLLocationDistance distanceFilter;  //更新到该距离之内不要告诉我更新了多少
```

4.开始位置监控。
```
- (void)startUpdatingLocation;//开始更新位置
- (void)stopUpdatingLocation;//停止位置更新
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations;//位置更新的代理方法
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;//更新失败
```

##位置监控的类型:

####1. 基于精度的监控
   
```
extern const CLLocationAccuracy kCLLocationAccuracyBestForNavigation; //最精确,但是非常耗能
extern const CLLocationAccuracy kCLLocationAccuracyBest;
extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;
```
> 注意:精度越高,耗电量越大

####2. 位置发生重大变化时更新。

```
- (void)startMonitoringSignificantLocationChanges;
- (void)stopMonitoringSignificantLocationChanges ;
```
该方法在前台和后台都能监控位置的变化,甚至关掉app后,也可以启动应用告诉用户位置更新:
```
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //如果```launchOptions```存在```UIApplicationLaunchOptionsLocationKey```,说明程序启动的原因是因为位置发生了重大变化
    return YES;
}
```

####3. 进入某个区域更新。

3.1设定一个圆形的区域,经过该区域的时候会更新

```
- (void)startMonitoringForRegion:(CLRegion *)region;
- (void)requestStateForRegion:(CLRegion *)region;
```

3.2 通过一个信标来监控

```
@property (readonly, nonatomic) CLLocationDistance maximumRegionMonitoringDistance;//设置最大监控距离

- (void)startRangingBeaconsInRegion:(CLBeaconRegion *)region;//设置信标
```

####4. 监控前进的方向

#MapKit
---------
MapKit是用于显示地图的框架,它通过```MKMapView```来显示地图。
我们来看一下该框架中几个比较重要的元素:

##1. MKMapView

MKMapView就是用来显示地图的View。

MKMapView的属性:
```
@property (nonatomic) MKMapType mapType;// MKMapTypeStandard : 标准;MKMapTypeSatellite:卫星;MKMapTypeHybrid:叠加
@property (nonatomic) BOOL showsUserLocation; //显示用户的地点
@property (nonatomic, readonly, getter=isUserLocationVisible) BOOL userLocationVisible;
//用户坐标是否可见
@property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled; //是否可放大缩小
@property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled; //是否可旋转
@property (nonatomic, getter=isPitchEnabled) BOOL pitchEnabled; //3D效果

```

##2. MKAnnotationView
在```MKMapView```视图里,可以显示用于标注具体位置的“大头针” ,它是MapKit框架里的```AnnotationView```。

MKAnnotationView的属性:
```
@property (nonatomic, strong, nullable) id <MKAnnotation> annotation;
@property (nonatomic, strong, nullable) UIImage *image;//大头针的图像
@property (strong, nonatomic, nullable) UIView *leftCalloutAccessoryView;//左附属对话框
@property (strong, nonatomic, nullable) UIView *rightCalloutAccessoryView;//右附属对话框
@property (nonatomic, getter=isDraggable) BOOL draggable //是否可拖动

```

大头针被点击时调用的方法:
```
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
```

## 3. id<MKAnnotation>
AnnotationView的数据源就是:id<MKAnnotation>,任何遵从该协议的对象都可以成为AnnotationView的数据源,也就是说,任何遵守    ```MKAnnotation```协议的对象你都可以将其放入地图中。

我们先看一下在MKMapView里的关于MKAnnotation的属性:
```
@property (nonatomic, readonly) NSArray<id<MKAnnotation>> *annotations;//包含MapView所显示的所有Annotaion
```

注意:annotations是只读的数组,只能添加或者删除。

```
- (void)addAnnotation:(id <MKAnnotation>)annotation;
- (void)addAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
- (void)removeAnnotation:(id <MKAnnotation>)annotation;
- (void)removeAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
```

MKAnnotation协议的方法:

```


@protocol MKAnnotation <NSObject>
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate; //坐标

@optional
@property (nonatomic, readonly, copy, nullable) NSString *title;//标题
@property (nonatomic, readonly, copy, nullable) NSString *subtitle;//副标题

- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate ;//设置坐标
```

那么二者是如何关联的呢?
通过MKMapView的代理方法:
```
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    //提供一个 annotation,返回一个 MKAnnotationView
}
```
##3. Callout(对话框)
点击大头针(MKAnnotationView),会出现一个白底的对话框,它被叫做```callout```,可以设置它的主标题和副标题。另外还有左右附属实图,它们可以显示图片或者箭头,也可被点击。


#Demo
----
##Demo需求:
- 显示从flickr抓取的摄影师列表。
- 点击列表中的一项,打开地图,在当前摄影师所照照片的地点显示大头针。
- 点击其中的一个大头针,显示照片详情:缩略图和名称。
- 点击箭头按钮,滑入显示照片的页面,显示原始照片。

##Demo效果图:

![在地图显示照片拍摄位置](http://upload-images.jianshu.io/upload_images/859001-269dd622430d4972.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

##重要代码段和知识点:

####1. 更改Core Data模型
在上一节课的基础上,我们需要在模型里的```Photo```实体添加经度和纬度的属性,还有大头针缩略图的URL属性。
在更新属性后,一定要重新生成对应该实体的类文件,并且要将原app删除,因为数据库前后是不兼容的。


![更新模型](http://upload-images.jianshu.io/upload_images/859001-0bf538fa1a08fe3d.gif?imageMogr2/auto-orient/strip)



####2. 新建PhotosByPhotographerMapViewController.h,用来显示```MKMapView```

因为要在地图上显示摄影师所照照片的位置,因此,该类的数据源来自摄影师模型:```Photographer```。

```
#import <UIKit/UIKit.h>
#import "Photographer.h"
@interface PhotosByPhotographerMapViewController : UIViewController
@property (nonatomic, strong) Photographer *photographer;//公共API:摄影师
@end
```

```
#import "PhotosByPhotographerMapViewController.h"

#import <MapKit/MapKit.h>

@interface PhotosByPhotographerMapViewController ()<MKMapViewDelegate>
@property (strong, nonatomic) IBOutlet MKMapView *mapView;//地图view
@property (nonatomic,strong) NSArray *photosByPhotographer;//装入摄影师拥有的照片的数组
@end

@implementation PhotosByPhotographerMapViewController
@end
```

####3. 导入Mapkit的framework

需要注意的是,除了要在类文件引用```<MapKit/MapKit.h>```框架以外,还要手动向项目中添加该框架:

![手动添加MapKit框架.gif](http://upload-images.jianshu.io/upload_images/859001-8fb04dfbac7bbdd7.gif?imageMogr2/auto-orient/strip)


####4. 更新photographer和mapView后更新annotation:
```
- (void)setMapView:(MKMapView *)mapView
{
    _mapView = mapView;

    //设置代理
    self.mapView.delegate = self;

    //更新
    [self updateMapViewAnnotations];
}

- (void)setPhotographer:(Photographer *)photographer
{
    _photographer = photographer;
    //导航栏标题
    self.title = photographer.name;
    //准备更新数组,要事先设置其为nil,否则不会生成新的
    self.photosByPhotographer = nil;
    [self updateMapViewAnnotations];
}

- (void)updateMapViewAnnotations
{
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView addAnnotations:self.photosByPhotographer];
    [self.mapView showAnnotations:self.photosByPhotographer animated:YES];
}
```
####5. 自定义点击大头针后显示的view
```
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{

    //类似UITableviewCell的复用
    static NSString *reuseId = @"PhotosByPhotographerMapViewController";    

    MKPinAnnotationView *view = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];    

    if (!view) {
        view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
       //是否显示callout
        view.canShowCallout = YES;
        //设置左部分的callout:UIImageView
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 46, 46)];
        view.leftCalloutAccessoryView = imageView;
        //设置右部分的callout:UIButton
        UIButton *disclosurebutton = [[UIButton alloc] init];
        [disclosurebutton setBackgroundImage:[UIImage imageNamed:@"disclosure"] forState:UIControlStateNormal];
        [disclosurebutton sizeToFit];
        view.rightCalloutAccessoryView = disclosurebutton;
    }

    view.annotation = annotation;
    return view;

}
```
####6. 点击大头针,更新callout左侧显示的缩略图
```

/**
 *  点击大头针view
 *
 *  @param mapView 大头针所属的mapView
 *  @param view    大头针view
 */
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    [self updateLeftCalloutAccessoryViewInAnnotationView:view];
}

/**
 *  更新callout里的图片(在左侧)
 *
 *  @param annotationView 当前被点击的大头针view
 */
- (void)updateLeftCalloutAccessoryViewInAnnotationView:(MKAnnotationView *)annotationView
{
    UIImageView *imageView = nil;
    if ([annotationView.leftCalloutAccessoryView isKindOfClass:[UIImageView class]]) {
        imageView = (UIImageView *)annotationView.leftCalloutAccessoryView;
    }
    if (imageView) {
        Photo *photo = nil;
        if ([annotationView.annotation isKindOfClass:[Photo class]]) {
            photo = (Photo *)annotationView.annotation;
        }
        if (photo) {
            NSString *urlString = photo.thumbnailURL;
            imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
        }
    }
}
```

>注意:显示图片的代码:``` imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];```方法会阻塞主线程,实际操作中应该放在子线程中执行。详情请参考笔者另一篇讲解关于多线程的博客:[最浅显易懂的iOS多线程技术 - GCD的教程](http://www.jianshu.com/p/6e74f5438f2c)。


####7. 点击callout,在下一页面显示原图
```
/**
 *  点击callout实行跳转
 *
 *  @param mapView 当前的mapView
 *  @param view    当前callout所属的AnnotationView
 *  @param control callout内部被点击的控件
 */
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
    [self performSegueWithIdentifier:@"Show Photo" sender:view];
}

/**
 *  调转执行前的代码
 *
 *  @param segue  连接前后两个控制器的segue
 *  @param sender 被点击的AnnotaionView
 */
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
 
    if ([sender isKindOfClass:[MKAnnotationView class]]) {
        [self prepareViewController:segue.destinationViewController
                           forSegue:segue.identifier
                   toShowAnnotation:((MKAnnotationView *)sender).annotation];
    }
}

/**
 *  为目标控制器准备数据(图片的URL)
 *
 *  @param vc              目标控制器
 *  @param segueIdentifier segue.identifier
 *  @param annotation      被点击的AnnotaionView
 */
- (void)prepareViewController:(id)vc
                     forSegue:(NSString *)segueIdentifier
             toShowAnnotation:(id <MKAnnotation>)annotation
{
    Photo *photo = nil;
    if ([annotation isKindOfClass:[Photo class]]) {
        photo = (Photo *)annotation;
    }
    if (photo) {
        if (![segueIdentifier length] || [segueIdentifier isEqualToString:@"Show Photo"]) {
            if ([vc isKindOfClass:[ImageViewController class]]) {
                ImageViewController *ivc = (ImageViewController *)vc;
                ivc.imageURL = [NSURL URLWithString:photo.imageURL];
                ivc.title = photo.title;
            }
        }
    }
}
```
>注意:这里的``` ivc.imageURL = [NSURL URLWithString:photo.imageURL];```代码同样会阻塞主线程,实际操作中应该放在子线程来做!

>而且,本demo的图片地址应该都是在墙外的,所以最好先让电脑翻墙,然后在模拟器上运行比较好。

#最后的话
----
如果哪位小伙伴想拿到本文Demo的代码请不要客气,可以进入我的[GitHub](https://github.com/Shijie0111/Stanford_iOS_Lecture_DemoBundle)下载哦~    这一系列到现在为止的所有Demo都在里面,分为英文注释版本和中文注释版本两种。

十分欢迎给笔者的代码和文笔抛出宝贵的意见和建议~

本文已在版权印备案,如需转载请访问版权印。48422928

[获取授权](http://101702070004705.bqy.pub)

#### --------------------------------  2018年7月17日更新 -------------------------------- 


**注意注意!!!**

笔者在近期开通了个人公众号,主要分享编程,读书笔记,思考类的文章。

- **编程类**文章:包括笔者以前发布的精选技术文章,以及后续发布的技术文章(以原创为主),并且逐渐脱离 iOS 的内容,将侧重点会转移到**提高编程能力**的方向上。
- **读书笔记类**文章:分享**编程类**,**思考类**,**心理类**,**职场类**书籍的读书笔记。
- **思考类**文章:分享笔者平时在**技术上**,**生活上**的思考。

>因为公众号每天发布的消息数有限制,所以到目前为止还没有将所有过去的精选文章都发布在公众号上,后续会逐步发布的。

**而且因为各大博客平台的各种限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~**

扫下方的公众号二维码并点击关注,期待与您的共同成长~

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

推荐阅读更多精彩内容