82课:开发一个App,记录值得回忆的地方

课程笔记文集地址:Udemy课程:The Complete iOS 9 Developer Course - Build 18 Apps

用老师的原话来说,这是一个大项目,好吧,视频的长度是46分钟,也算是大项目吧!

一、Storyboard 布局

新建工程,使用一个 Navigation Controller ,这是之前的课程里没有讲过的知识点,设为 Initial View Controller,Segue(show) 连接一个 Map View Controller(点击 Bar Button Item 跳转到 Map View Controller。

注意这个 Navigation Controller 自带一个 TableViewController.

82课:开发一个App,记录值得回忆的地方_第1张图片

Map View Controller 拖入 MKMapView 控件,设置 AutoLayout 约束。创建 Outlet 连接。

二、新建类文件

给 Storyboard 里每个界面创建类文件,去 Storyboard 中关联一下。TableViewController 拖动简历 delegate 和 datasource。

TableViewController 类文件里完成必须要有的 2 个 datasource 方法。

三、获取用户位置需要进行的操作。

获取用户的位置需要进行的操作之前的课程里已经讲过了,也写到笔记里了,我在这里再重复一遍。

1、引入 CoreLocation.framework

步骤如下图,5步点击 + 号按钮:


82课:开发一个App,记录值得回忆的地方_第2张图片

点击 + 后,弹出框里输入查询:

82课:开发一个App,记录值得回忆的地方_第3张图片

选择 CoreLocation.framework,最终效果如下图:

82课:开发一个App,记录值得回忆的地方_第4张图片

这两个地方有了变化。

2、设置 plist 文件,请求用户权限

只在需要用的时候才获取用户的位置信息,NSLocationWhenInUseUsageDescription

如下图,两个里只需写一个即可:

82课:开发一个App,记录值得回忆的地方_第5张图片

Value 一栏输入的内容,是你在想用户请求获取位置信息权限时,显示给用户的一段内容,这段内容的目的是告诉用户为什么要使用你的位置,从而用户能够根据你说的理由决定是否给你这个权限。

3、类文件的代码

在与Map View Controller 关联的类文件里开始写代码,如下图中所标出的内容:

82课:开发一个App,记录值得回忆的地方_第6张图片

然后在 viewDidLoad 里写入下列代码:

    override func viewDidLoad() {
        super.viewDidLoad()
        //创建实例
        manager = CLLocationManager()
        //协议委托
        manager.delegate = self
        //设置用户位置的精确度
        manager.desiredAccuracy = kCLLocationAccuracyBest
        //请求权限(这里和plist文件里的设置要一致)
        manager.requestWhenInUseAuthorization()
        //开始获取位置
        manager.startUpdatingLocation()        
    }

四、获取用户位置之后进行处理

在这一部分里,所有的操作都是在与Map View Controller 关联的类文件进行的。

1、让地图控件显示的图像正好是用户位置的周围

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        let userLocation:CLLocation = locations[0] as! CLLocation
        
        let latitude:CLLocationDegrees = userLocation.coordinate.latitude
        
        let longitude:CLLocationDegrees = userLocation.coordinate.longitude
        
        let latDelta:CLLocationDegrees = 0.01
        
        let lonDelta:CLLocationDegrees = 0.01
        
        let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
        
        let coordinate:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
        
        let region:MKCoordinateRegion = MKCoordinateRegionMake(coordinate, span)
        
        map.setRegion(region, animated: false)
        
    }

2、创建手势

override func viewDidLoad() {
    super.viewDidLoad()

        ...

    let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.action(_:)))
        
    uilpgr.minimumPressDuration = 2.0
        
    map.addGestureRecognizer(uilpgr)
}

创建 action 方法,和之前课程学到的方法差不多,不过多了一个 if 判断。这个好处就是,长按立马进行操作,而不是长按,抬起手指,然后再进行操作。能够提高用户体验,没有 if ,用户会觉得等待时间过长了。

func action(gestureRecognizer: UIGestureRecognizer) {
     //提高用户体验的关键点
     if gestureRecognizer.state == UIGestureRecognizerState.Began {
        //记录用户长按所在的点的位置
        var touchPoint = gestureRecognizer.locationInView(self.map)
        //将手指所在的点的位置转换成地图上的2D坐标数值
        var newCoordinate: CLLocationCoordinate2D = map.convertPoint(touchPoint, toCoordinateFromView: self.map)

        let location = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)
        CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
            //在后面的部分,我们会在这个闭包里进行大量的代码编程,闭包之外的代码在后面的会省略掉
        })

        //这之后的代码都是添加图钉标记,在后面的操作里会发现,这部分放到了闭包里
        var annotation = MKPointAnnotation()        
        annotation.coordinate = newCoordinate        
        annotation.title = "New Place"     
        map.addAnnotation(annotation)     
    }   
}

�3、处理闭包里的操作

闭包里的有关操作在 78 课里讲过一部分,这里又根据这次的需求添加了一些变化:

            CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in                              
                var title = ""              
                if (error == nil) {                   
                    //if statement was changed
                    if let p = placemarks?[0] {                      
                        var subThoroughfare:String = ""
                        var thoroughfare:String = ""     
                        if p.subThoroughfare != nil {                           
                            subThoroughfare = p.subThoroughfare!                           
                        }                        
                        if p.thoroughfare != nil {                            
                            thoroughfare = p.thoroughfare!                            
                        }                       
                        title = "\(subThoroughfare) \(thoroughfare)"                                                
                    }                   
                }
                
                if title.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) == "" {                    
                    title = "Added \(NSDate())"                    
                }
                //这个 places 是一个全局数组变量,数组的类型是词典类型,词典的键值都是 String 类型,老师在 TableViewController 里创建的,不过既然是全局变量,在这个类的上面创建也是一样的
                //给数组添加了两个元素
                places.append(["name":title,"lat":"\(newCoordinate.latitude)","lon":"\(newCoordinate.longitude)"])
                //是的,把添加图钉的操作放到了闭包里了
                print(places)                
                let annotation = MKPointAnnotation()                
                annotation.coordinate = newCoordinate                
                annotation.title = title                
                self.map.addAnnotation(annotation)                
            })

五、完善 TableViewController

1、全局数组变量

刚刚提到的 places 全局数组变量,这行代码要鞋子 class 之外:

var places = [Dictionary()]

2、修改 datasource 的两个方法

现在,可以使用 places 数组了。

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return places.count
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell  
        cell.textLabel?.text =  places[indexPath.row]["name"]
        return cell
    }

3、数组的第一个元素是空的字典

是的,我一开始没有看懂为什么要添加这个代码:

    override func viewDidLoad() {
        super.viewDidLoad()
        if places.count == 1 {            
            places.removeAtIndex(0)            
            places.append(["name":"Taj Mahal","lat":"27.175277","lon":"78.042128"])            
        }
    }

于是,我加上几个 print 来查看一下原因,如下图:

82课:开发一个App,记录值得回忆的地方_第7张图片

看到控制台输入的打印信息了吗?你还没有创建任何新的地点,这个数组里已经有一个元素了,所以需要这段代码去掉第一个元素,新建一个默认的地点(当然你也可以没有默认地点,一开始就空白,不过这个操作会稍微复杂一丁点)。

六、地图显示某个点的位置

我感觉到这一步才开始学习新东西,前面那些都是之前课程里涉及过的。。。是的,这节视频长达 46 分钟,到 37分钟才开发讲新的东西。。。实际上新东西只有10来分钟而已嘛!!

需求如下:点击 TableViewController 上某一行地点,跳转地图界面,且地图显示的就是刚刚选择的地点。

1、创建 Segue

选中 cell,拖到地图界面,点击 show(Selection Segue,别选错了),如下图:


82课:开发一个App,记录值得回忆的地方_第8张图片

2、记住用户点击了哪一行

创建全局变量,在 places 下面写下:

var activePlace = -1

然后使用 TableView 里的一个方法:

override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
    activePlace = indexPath.row
    return indexPath
}

3、通过 Segue 传值

在 Storyboard 中,对点击 + 产生的 Segue 输入 identifier:newPlace

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {        
    if segue.identifier == "newPlace" {            
        activePlace = -1            
    }        
}

这样,如果 active 值是 -1,则是在新建图钉,如果不是 -1,则是在查看已有地点的信息。

4、确保 TableView 上的数据是最新的

这个简单:

override func viewWillAppear(animated: Bool) {
    tableView.reloadData()        
}

5、区分新建和查看

这上面的需要是要查看已有的地点信息,点击 + 则是要新建地点,都是在这个 Map View Controller 进行,所以需要区分哪个请求是新建,哪个是查看。

区分方法如下,用 if 语句来判断:

    override func viewDidLoad() {
        super.viewDidLoad()       
        manager = CLLocationManager()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest

        //如果值为 -1,则是在新建图钉
        if activePlace == -1 {            
            manager.requestWhenInUseAuthorization()
            manager.startUpdatingLocation()
        //如果不是 -1,则是在查看之前记录的地点    
        } else {
            //把字典里的字符串转换成数值
            let latitude = NSString(string: places[activePlace]["lat"]!).doubleValue
            //把字典里的字符串转换成数值
            let longitude = NSString(string: places[activePlace]["lon"]!).doubleValue
            //这往下的内容没有什么变化了,之前都学过了
            let coordinate = CLLocationCoordinate2DMake(latitude, longitude)            
            let latDelta:CLLocationDegrees = 0.01            
            let lonDelta:CLLocationDegrees = 0.01            
            let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)            
            let region:MKCoordinateRegion = MKCoordinateRegionMake(coordinate, span)            
            self.map.setRegion(region, animated: true)
            let annotation = MKPointAnnotation()           
            annotation.coordinate = coordinate           
            annotation.title = places[activePlace]["name"]          
            self.map.addAnnotation(annotation)          
        }
        
        let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.action(_:)))        
        uilpgr.minimumPressDuration = 2.0        
        map.addGestureRecognizer(uilpgr)        
    }

七、结束

恩,最后十分钟的讲解还是不错的!之前有些拖沓了,就当是巩固吧。

你可能感兴趣的:(82课:开发一个App,记录值得回忆的地方)