獲取GPS坐標(biāo)
在這一節(jié)課,你要在Xcode中創(chuàng)建MyLocations工程染突,并且使用Core Location框架來(lái)定位用戶位置的經(jīng)度和緯度捻爷。
當(dāng)你完成本小節(jié)課時(shí),app看起來(lái)回是這樣的:
我知道這個(gè)界面看起來(lái)非常簡(jiǎn)陋份企,但是之后我們會(huì)完善它也榄。而眼下,唯一重要的事情就是你可以獲得GPS坐標(biāo)薪棒,并且將其展現(xiàn)在屏幕上手蝎。
和往常一樣,我們先一點(diǎn)點(diǎn)的實(shí)現(xiàn)每一個(gè)小功能俐芯,最后來(lái)給它一個(gè)大整容棵介。
打開Xcode,然后創(chuàng)建一個(gè)新的project吧史,這次選擇Tabbed Application模版邮辽。
然后按照下面寫的,填寫必要項(xiàng)目:
1、Product Name:MyLocations
2吨述、Organization Name:你的名字或者你公司的名字
3岩睁、Organization Identifier:你的身份id,可以用自己倒過(guò)來(lái)的域名
4揣云、Language:Swift
5捕儒、Devices:iPhone
復(fù)選框Include Unit和Include UI Test,不要選中
6邓夕、保存
運(yùn)行一下刘莹,app的初始界面會(huì)是這個(gè)樣子:
這個(gè)app在底部有一個(gè)分頁(yè)欄(tab bar),目前包括兩個(gè)頁(yè)面:First和Second焚刚。
雖然我們只做了一點(diǎn)點(diǎn)的工作点弯,但是這個(gè)app現(xiàn)在已經(jīng)擁有三個(gè)視圖控制器了。
1矿咕、它的根控制器是UITabBarController抢肛,并且包含一個(gè)分頁(yè)欄,可以切換不同的界面碳柱。
2捡絮、First tab的視圖控制器
3、Second tab的視圖控制器
這兩個(gè)頁(yè)面分別擁有自己的視圖控制器士聪。在Xcode中锦援,它們默認(rèn)的名稱是FirstViewController和SecondViewController。
故事模版現(xiàn)在是這個(gè)樣子的:
我已經(jīng)把它縮小了剥悟,這樣它們就可以在一個(gè)屏幕上展示全了灵寺。故事模版雖然非常便利,但是它占據(jù)的空間實(shí)在太大了区岗。
和以前一樣略板,你首先在故事模版中將設(shè)備型號(hào)切換為iPhone SE,最后你會(huì)調(diào)整app慈缔,使它適合全部類型的iPhone叮称。
在故事模版底部找到View as(你已經(jīng)使用過(guò)很多次了),將設(shè)備型號(hào)切換為iPhone SE藐鹤。
在這一小節(jié)瓤檐,你將先對(duì)first tab這個(gè)界面進(jìn)行處理。在這個(gè)課程的后半段娱节,你將處理second界面挠蛉,以及自行添加第三個(gè)分頁(yè)。
首先肄满,我們來(lái)個(gè)FirstViewController一個(gè)更好聽的名字谴古。
打開工程導(dǎo)航器质涛,點(diǎn)擊FirstViewController兩次,不要點(diǎn)太快掰担,太快就是雙擊了汇陆,然后將其重命名為CurrentLocationViewController.swift。
打開CurrentLocationViewController.swift带饱,將class這一行修改為:
class CurrentLocationViewController: UIViewController {
切換到Mian.storyboard并且選擇連接到first tab的視圖控制器毡代,打開身份檢查器(Identity inspector),將Class文本框中的內(nèi)容由FirstViewController修改為CurrentLocationViewController:
打開工程設(shè)置界面勺疼,取消選定Landscape Left和Landscape Right復(fù)選框月趟,這樣app就只支持豎屏方向了。
運(yùn)行app恢口,確保前面的修改沒(méi)有問(wèn)題。
無(wú)論何時(shí)穷躁,我在故事模版中修改了什么東西都鏈接時(shí)耕肩,我都會(huì)運(yùn)行一次app,來(lái)驗(yàn)證修改的是否正確问潭,這非常有用猿诸。因?yàn)樵谶@一過(guò)程中很容易遺忘一些微小的步驟。
就像你在Checklists中看到的那樣狡忙,一個(gè)位于導(dǎo)航控制器內(nèi)的視圖控制器會(huì)有一個(gè)Navigation Item對(duì)象梳虽,可以用來(lái)配置導(dǎo)航欄。Tab Bar的工作方式也和這個(gè)類似灾茁。每個(gè)代表tab頁(yè)的視圖控制器都有一個(gè)Tab Bar Item對(duì)象窜觉。
選中“First Screen”中的Tab Bar Item(就是Current Location View Controller)然后打開屬性檢查器。將Title改變?yōu)門ag北专。見下圖:
稍后禀挫,你會(huì)在Tab Bar Item上放置一個(gè)圖片(image);它現(xiàn)在還是使用默認(rèn)圖片拓颓,就是一個(gè)圓圈语婴。
現(xiàn)在你要為first tab設(shè)計(jì)界面。它擁有兩個(gè)button和一些label用來(lái)展示GPS坐標(biāo)以及街道信息驶睦。為了給你節(jié)省點(diǎn)時(shí)間砰左,我們來(lái)一次性的把outlet都添加上。
打開CurrentLocationViewController.swift场航,添加以下代碼:
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var latitudeLabel: UILabel!
@IBOutlet weak var longitudeLabel: UILabel!
@IBOutlet weak var addressLabel: UILabel!
@IBOutlet weak var tagButton: UIButton!
@IBOutlet weak var getButton: UIButton!
@IBAction func getLocation() {
// 暫時(shí)什么都不做
}
將UI設(shè)計(jì)為下面這個(gè)樣子:
Message label位于頂部缠导,并且其寬度應(yīng)該貼到屏幕的兩邊。你將使用這個(gè)標(biāo)簽來(lái)獲取app請(qǐng)求GPS坐標(biāo)時(shí)的狀態(tài)信息旗闽。選中這個(gè)標(biāo)簽酬核,打開屬性檢查器蜜另,將Alignment設(shè)置為cernered,并且把這個(gè)標(biāo)簽和messageLabel oulet鏈接起來(lái)嫡意。
將latitude goes here和longitude goes here標(biāo)簽右對(duì)齊举瑰,并且分別和latitudeLabel以及l(fā)ongitude outlet鏈接起來(lái)。
address goes here標(biāo)簽的寬帶也需要延展至貼近屏幕兩邊蔬螟,并且設(shè)置為50 points高此迅,這樣就可以容納兩行文本。將其屬性檢查器中的Lines選項(xiàng)設(shè)置為0旧巾,這樣它就可以容納多行了耸序。最后將其和addressLabel outlet鏈接起來(lái)。
Tag Location按鈕目前還不做任何工作鲁猩,我們先把它和tabButton outlet鏈接起來(lái)坎怪。
將Get My Location按鈕和getButton鏈接起來(lái),并且在鏈接檢查器中設(shè)置它的Touch Up Inside事件和getLocation鏈接起來(lái)廓握。
運(yùn)行一下app搅窿,確保一切正常。
到目前為止隙券,沒(méi)有遇到什么特殊的東西男应。除了tab bar可能你以前沒(méi)見過(guò)。是時(shí)候引入一些新的東西了娱仔,讓我們來(lái)了解一下Core Location沐飘!
??:因?yàn)樽畛跄闶腔趇Phone SE的大小來(lái)設(shè)計(jì)界面的,所以你最好用在模擬器中也使用iPhone SE來(lái)運(yùn)行app牲迫。如果你在模擬器中用了其他型號(hào)的iPhone耐朴,可能界面看起來(lái)會(huì)面目全非,和以前一樣恩溅,我們會(huì)在課程的最后來(lái)解決這個(gè)問(wèn)題隔箍。
Core Location
絕大多數(shù)的iOS設(shè)備都可以使你獲得自己在這個(gè)星球上的位置,無(wú)論是通過(guò)GPS還是Wifi脚乡,亦或者基站的三角測(cè)量蜒滩。而這一能力正是Core Location賦予你的。
任何一個(gè)app都可以通過(guò)Core Location獲得用戶目前所在的經(jīng)度及緯度奶稠。對(duì)于有指南針的設(shè)備俯艰,還可以給你提供方向(我們的課程中不會(huì)涉及這塊內(nèi)容)。Core Location還可以在你移動(dòng)時(shí)锌订,持續(xù)的更新坐標(biāo)竹握,導(dǎo)航類app就是這個(gè)原理。
從Core Location中獲取位置信息非常容易辆飘,但是你需要躲開一些陷阱啦辐。我們開始的時(shí)候會(huì)簡(jiǎn)單一些谓传,僅僅是請(qǐng)求當(dāng)前的坐標(biāo),看看會(huì)發(fā)生些什么芹关。
打開CurrentLocationViewController.swift,在import UIKit下面添加上一行:
import CoreLocation
這樣你就把Core Location框架添加到app中了续挟,還有比這更簡(jiǎn)單的嗎?
Core Location和iOS SDK中的大部分一樣侥衬,通過(guò)委托工作诗祸,所以你應(yīng)該讓這個(gè)視圖控制器遵循CLLocationManagerDelegate協(xié)議。
改變一下class聲明的這一行:
class CurrentLocationViewController: UIViewController,CLLocationManagerDelegate {
這些都放在一行里面轴总。
并且添加一個(gè)新的屬性:
let locationManager = CLLocationManager()
CLLocationManage就是你用來(lái)獲取GPS坐標(biāo)的對(duì)象直颅。你將這個(gè)對(duì)象的引用放入了一個(gè)常量中,一旦你創(chuàng)建了location manager對(duì)象怀樟,那么locationManager的值就不會(huì)改變了功偿,它始終指向location manager對(duì)象。
這個(gè)新的CLLocationManager對(duì)象并不會(huì)馬上給出GPS坐標(biāo)往堡。為了接收坐標(biāo)脖含,你需要先調(diào)用它的startUpdatingLocation()方法。
除非你是在做導(dǎo)航類app投蝉,否則你不需要你的app持續(xù)獲得用戶的位置信息。這樣耗電量會(huì)非常大征堪。對(duì)于我們這個(gè)app瘩缆,你只需要打開location manager一下,在獲取到可用的位置信息后佃蚜,在立馬把它關(guān)掉庸娱。
這會(huì)比你想象中復(fù)雜一些,目前谐算,我們關(guān)心的還是如何接收GPS坐標(biāo)信息熟尉。
將getLocation()方法修改一下:
@IBAction func getLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
這個(gè)方法是鏈接到Get My Location按鈕上的。它告訴location manager這個(gè)視圖控制器就是它的委托洲脂,并且你想要接收精度為10米的位置信息斤儿。然后你啟動(dòng)了location manager。在這一刻恐锦,CLLocationManager對(duì)象會(huì)發(fā)送位置更新到它的委托往果,比如這個(gè)視圖控制器。
和委托交流一铅,需要添加以下代碼:
//MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("didFailWithError \(error)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last!
print("didUpdateLocations \(newLocation)")
}
它們是用于location manager的委托方法陕贮。暫時(shí),你只是簡(jiǎn)單的在調(diào)試區(qū)域打印一些信息潘飘,來(lái)看看location manager是否工作肮之。
//MARK
在剛才那段代碼的第一行掉缺,是一行以//MARK:開頭的注釋,像這樣的注釋會(huì)在Xcode中將你的源代碼進(jìn)行巧妙的分節(jié)戈擒,你可以在Jump Bar中看到它們:
這里的“-”符號(hào)作用是讓Xcode做出分割線眶明,就是所謂的華麗的分割線。你也可以添加一些類似于//TODO:或者//FIXME:之類的東西峦甩,它們也會(huì)在Jump Bar中出現(xiàn)赘来,其作用相當(dāng)于是一篇文章的小目錄,使你的代碼更加清晰可讀凯傲。
在模擬器中運(yùn)行app犬辰,并且點(diǎn)擊Get My Location按鈕,和作者以往的套路一樣冰单,什么都不會(huì)發(fā)生幌缝。并且作者會(huì)很貼心的告訴你,這是因?yàn)槟銢](méi)有獲取到用戶的許可的緣故诫欠。
在getLocation()方法的頂部添加以下代碼:
let authStatus = CLLocationManager.authorizationStatus()
if authStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
return
}
這些代碼的作用是檢查目前的許可狀態(tài)涵卵,如果是禁止訪問(wèn)位置信息的話,app就會(huì)在用戶使用時(shí)(When In Use)荒叼,進(jìn)行許可請(qǐng)求轿偎。
還有一種請(qǐng)求方法是“Always”,這種授權(quán)可以使app在未激活時(shí)也會(huì)檢查用戶的位置信息被廓。其實(shí)這兩個(gè)種方式就是大家常見的坏晦,使用時(shí)允許和總是允許〖蕹耍總是允許對(duì)導(dǎo)航類app是非常重要的昆婿,但是對(duì)于我們的app,僅僅在用戶使用時(shí)允許就夠了蜓斧。
僅僅是添加這些代碼是不夠的仓蛆,你要需要在Info.plist中添加一個(gè)特殊的鍵值。
打開Info.plist挎春,然后使用鼠標(biāo)右鍵打開菜單看疙,選擇Add Row:
新增一行的時(shí)候,在鍵的位置輸入:NSLocationWhenInUseUsageDescription或者從下拉框中選擇:Privacy - Location When In Use Usage Description直奋。
然后在值的部分勋桶,輸入文本:This app lets you keep track of interesting places. It needs access to the
GPS coordinates for your location.
這里的文本描述了你為什么要獲取用戶的位置信息圾另。
運(yùn)行app吠冤,再次點(diǎn)擊Get My Location按鈕尼酿。
Core Location此時(shí)會(huì)彈出一個(gè)窗口,向用戶請(qǐng)求獲取位置信息的許可:
如果用戶拒絕了這個(gè)請(qǐng)求殉挽,那么Core Location就無(wú)法獲取到用戶的位置信息丰涉。
點(diǎn)擊Don't Allow拓巧,然后再點(diǎn)擊Get My Location按鈕。
Xcode的debug區(qū)域會(huì)顯示出一條消息:
didFailWithError Error Domain=kCLErrorDomain Code=1
這條消息來(lái)自于locationManager(didFailWithError)委托方法一死。它告訴你location manager無(wú)法獲取位置信息肛度。
為什么無(wú)法獲取的原因由一個(gè)Error對(duì)象描述,它是iOS SDK中的標(biāo)準(zhǔn)對(duì)象投慈,用于傳達(dá)錯(cuò)誤信息承耿。你會(huì)在SDK中的許多地方見到它(因?yàn)橛刑嗟牡胤饺菀壮鲥e(cuò)了)
Error對(duì)象有一個(gè)“domain(域)”和“code(代碼)”。在這里domain是kCLErrorDomain伪煤,意思是這個(gè)錯(cuò)誤是來(lái)自Core Location(CL)加袋。code為1,代表CLError.denied抱既,意思是用戶沒(méi)有授權(quán)這個(gè)app可以獲得位置信息职烧。
??:k前綴經(jīng)常被iOS框架用來(lái)表示某個(gè)名稱是常量,我猜這個(gè)k是konstant的首字母防泵。這常量的一種舊式用法蚀之,你不會(huì)經(jīng)常看到它了捷泞,但是它仍舊會(huì)在一些角落里出現(xiàn)足删,比如這里。
在Xcode中停掉app锁右,然后再次運(yùn)行壹堰。
當(dāng)你點(diǎn)擊Get My Location按鈕時(shí),app不會(huì)再次向用戶請(qǐng)求許可骡湖,你會(huì)在debug區(qū)域看到一條和之前一模一樣的報(bào)錯(cuò)信息。
讓我們來(lái)改進(jìn)一下它的用戶體驗(yàn)峻厚,使它更加友好一些响蕴,因?yàn)橛脩舨趴床坏绞裁磮?bào)錯(cuò)信息。
打開CurrentLocationViewController.swift惠桃,添加以下方法進(jìn)去:
func showLocationServicesDeniedAlert() {
let alert = UIAlertController(title: "Location Services Disabled", message: "Please enable location services for this app in Settings.", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert,animated: true,completion: nil)
}
這個(gè)彈出窗口會(huì)展示一條很有用的信息浦夷。如果無(wú)法獲取位置信息的話,這個(gè)app就是無(wú)用的辜王,所以應(yīng)該鼓勵(lì)用戶打開location服務(wù)的授權(quán)劈狐。
在getLocation()方法中調(diào)用這個(gè)新的方法,將下面的代碼放在設(shè)置locationManager的委托之前:
if authStatus == .denied || authStatus == .restricted {
showLocationServicesDeniedAlert()
return
}
如果authorization狀態(tài)為denied或者restricted呐馆,就會(huì)彈出這個(gè)窗口肥缔,提醒用戶去設(shè)置中授權(quán)該app可以使用位置信息。注意這里的||符號(hào)汹来,是邏輯或的意思续膳。當(dāng)兩個(gè)條件之一為true時(shí)改艇,showLocationServicesDeniedAlert()方法就會(huì)被調(diào)用。
再次運(yùn)行app坟岔,點(diǎn)擊Get My Location按鈕谒兄,你現(xiàn)在應(yīng)該看到一個(gè)彈窗,提示你在設(shè)置中開啟允許獲得位置信息社付。
當(dāng)用戶改變主意承疲,想要允許這個(gè)app使用位置信息時(shí),就可以在設(shè)置中開啟這一功能鸥咖。
打開模擬器中app的Setting燕鸽,然后在Privacy->Location中打開允許獲得位置信息,就是私隱->位置信息菜單:
選擇MyLocations扛或,然后選擇While Using the App(使用時(shí)允許)绵咱,來(lái)開啟location服務(wù)∥跬茫回到app悲伶,然后再點(diǎn)擊Get My Location按鈕。
當(dāng)我這樣做時(shí)住涉,我又再Xcode的dubug區(qū)域看到一條報(bào)錯(cuò)信息:
didFailWithError Error Domain=kCLErrorDomain Code=0
這一次有點(diǎn)不一樣麸锉,code為0。這是“l(fā)ocation unknown”的意思舆声,就是說(shuō)Core Location由于某些原因無(wú)法獲取位置信息花沉。
不用對(duì)此太驚訝,當(dāng)你在模擬器中運(yùn)行app時(shí)媳握,顯然你并不具備一個(gè)真正的GPS模塊碱屁。你的Mac有這個(gè)功能,但是無(wú)法分享給模擬器中的app蛾找,幸運(yùn)的事娩脾,我們還是有辦法來(lái)測(cè)試這個(gè)功能。
保持app運(yùn)行打毛,然后在模擬器的菜單中選擇Debug->Location->Apple柿赊。
此時(shí)你應(yīng)該可以在debug區(qū)域看到類似下面的信息:
didUpdateLocations <+37.33259552,-122.03031802> +/- 500.00m (speed -1.00
mps / course -1.00) @ 7/19/16 4:03:52 PM Central European Summer Time
didUpdateLocations <+37.33241023,-122.03051088> +/- 65.00m (speed -1.00
mps / course -1.00) @ 7/19/16 4:03:54 PM Central European Summer Time
didUpdateLocations <+37.33233141,-122.03121860> +/- 50.00m (speed -1.00
mps / course -1.00) @ 7/19/16 4:04:01 PM Central European Summer Time
didUpdateLocations <+37.33233141,-122.03121860> +/- 30.00m (speed 0.00
mps / course -1.00) @ 7/19/16 4:04:03 PM Central European Summer Time
didUpdateLocations <+37.33233141,-122.03121860> +/- 10.00m (speed 0.00
mps / course -1.00) @ 7/19/16 4:04:05 PM Central European Summer Time
debug區(qū)域中的消息在不斷的刷新,幾乎每秒就更新一次位置信息幻枉,雖然經(jīng)緯度沒(méi)有發(fā)生變化碰声。這些坐標(biāo)的位置,其實(shí)是蘋果的總部熬甫,加利佛尼亞胰挑,庫(kù)比蒂諾。
仔細(xì)看看獲取到的坐標(biāo)信息,最初的是"+- 500.00m",中間階段是"+/- 65.00m"和"+/- 50.00m"洽腺,最終保持在"+/- 5.00m"脚粟。
這些信息代表測(cè)量的精度,單位是米蘸朋。模擬器真實(shí)的模擬著真實(shí)設(shè)備上的情況核无,獲取位置時(shí),精度不斷的縮小范圍藕坯。
真實(shí)的iPhone設(shè)備有三種獲取位置信息的方式团南,基站三角測(cè)量,Wifi和GPS炼彪,其中:
基站三角測(cè)量只要手機(jī)有信號(hào)就工作吐根,但是精度不是非常高。
Wifi會(huì)稍微好一些辐马,但是僅僅在你周圍有Wifi時(shí)才能工作拷橘。原理是使用一個(gè)包含無(wú)線網(wǎng)絡(luò)設(shè)備位置信息的一個(gè)大數(shù)據(jù)庫(kù)。
GPS的測(cè)量精度是最高的喜爷,但是由于它是和衛(wèi)星通訊冗疮,所以是三種方法中最慢的,并且時(shí)常在室內(nèi)會(huì)失靈檩帐。
現(xiàn)在你知道了設(shè)備有三種方式獲取位置信息术幔,并且按照速度排序的話,基站三角測(cè)量和Wifi比較快湃密,GPS最慢诅挑。并且這三種方法都不能保證隨時(shí)正常工作。有些設(shè)備甚至沒(méi)有GPS模塊和通信模塊泛源,只能依靠Wifi獲取位置信息拔妥。獲取位置信息這件事一下子就變得看起來(lái)很棘手了。
幸運(yùn)的是达箍,Core Location把這些多種渠道讀取位置信息并且轉(zhuǎn)換為數(shù)字的復(fù)雜工作自己做完了没龙。Core Location不會(huì)一直等待從GPS獲取數(shù)據(jù),而是把能優(yōu)先獲取到的數(shù)據(jù)先展示出來(lái)幻梯,然后再慢慢的提高精度。
練習(xí):如果你手邊有iPhone或者iPod努释、iPad碘梢,在這些設(shè)備上都試試你的app,看看都能得到什么樣的數(shù)據(jù)伐蒂,注意一下它們的不同之處煞躬。