增強(qiáng)現(xiàn)實是一種很酷的流行技術(shù)吞鸭,你可以通過特定設(shè)備(比如iPhone 攝像頭或者微軟的 Hololens)來觀察世界潘悼,這個設(shè)備會在真實世界的畫面上疊加額外的信息。
我猜你也許看過標(biāo)志物跟蹤之類的 App,在這個 App 中,當(dāng)你將攝像頭指向某個標(biāo)志物時隙袁,會立即彈出一個 3D 模型。
在這篇 iOS 增強(qiáng)現(xiàn)實教程中,我們會編寫一個 App菩收,利用用戶當(dāng)前位置來找出附近的興趣點(即 POI)梨睁。你可以將這些興趣點添加到 MapView 上,并在鏡頭圖像中顯示它們娜饵。
要找出 POI坡贺,我們可以使用 Google 的 Places API,然后用 HDAugmentedReality 庫將 POI 放到鏡頭視圖中箱舞,并計算出用戶當(dāng)前位置到 POI 的距離遍坟。
開始
Main.storyboard 中有一個 Scene 包含了一個 MapView 和一個 UIButton 按鈕,它們都已經(jīng)正確連接了褐缠。HDAugmentedReality 庫已經(jīng)導(dǎo)入政鼠,另外還有兩個 Swift 文件:PlacesLoader.swift 和 Place.swift。這兩個類后面我們會用來通過 Google 的 Places API 搜索興趣點并將搜索結(jié)果映射成便于使用的對象队魏。
在開始下一步之前,我們首先需要獲取用戶當(dāng)前位置万搔。也就是使用 CLLocationManager胡桨。打開 ViewController.swift 在 mapView 屬性下面添加一個 locationManager 屬性。
這里我們用一個 CLLocationManager 對象來初始化 locationManager瞬雹。
為 ViewController 添加下列擴(kuò)展:
在獲取用戶位置之前必須在 Info.plist 中添加一個 key昧谊。打開 Info.plist 增加一個鍵值對,鍵名為 NSLocationWhenInUseUsageDescription 鍵值為 a value of Needed for AR酗捌。當(dāng)你第一次訪問 iOS 的定位服務(wù)時呢诬,會顯示一個對話框要求用戶授權(quán)。
準(zhǔn)備好這一切之后胖缤,我們來獲取用戶位置尚镰。打開 ViewController.swift ,將 viewDidLoad() 修改為:
locationManager 就基本配置好了哪廓。locationManager 需要一個委托對象狗唉,這樣當(dāng)設(shè)備位置發(fā)生變化時它會通知委托對象。這里我們將委托設(shè)置為 View Controller 自身涡真。此外還需要指定定位所需的精度分俯。這里我們設(shè)置為 KCLLocationAccuracyNearestTenMeters,對于本項目來說這個精度就夠了哆料。最后2行是打開 locationManager 以及向用戶獲取授權(quán)缸剪,如果用戶還沒有進(jìn)行授權(quán)或者拒絕授權(quán)的話。
注意:對于 desiredAccuracy 屬性东亦,你應(yīng)該根據(jù)使用目的選擇能夠滿足需要的最低精度就可以了杏节。為什么?
假設(shè)你只需要百米級別的精度,則 LocationManager 會用蜂窩幸好和網(wǎng)絡(luò)來獲取位置拢锹。因為這能延長電池壽命谣妻,對于 iPhone 來說至關(guān)重要。如果你需要更高的精度卒稳,LocationManager 會使用 GPS 進(jìn)行定位蹋半,這是非常耗電的。同理充坑,一旦我們收到一個可以接受的位置信息之后减江,就應(yīng)當(dāng)立即停止獲取定位信息。
接下來實現(xiàn)委托方法捻爷。在 ViewController 的 CLLocationManagerDelegate 擴(kuò)展中加入代碼:
代碼解釋如下:
每當(dāng) LocationManager 收到一個位置更新信息辈灼,它就會立即通知委托對象,將新的位置信息傳遞給委托也榄。locations 數(shù)組中包含了按時間排序的所有位置巡莹,因此最新的位置應(yīng)當(dāng)是數(shù)組中的最后一個值。首先判斷數(shù)組中是否包含了至少一個值甜紫,并從中獲取最新的一個降宅。然后在控制臺中打印水平精度。這個值表示了當(dāng)前位置的位置半徑囚霸。如果這個值為 50腰根,則說明真實的位置就在這個 50 米半徑范圍內(nèi)。
這里判斷位置精度是否滿足我們的需要拓型。在本例中额嘿,100 米就足夠了。在真實 App劣挫,你可能想要 10 米或者更少的精度册养,但要達(dá)到這種精度很可能要花好幾分鐘的時間(GPS 定位是耗時的)。
首先停止位置更新以減少電池消耗揣云。然后將 mapView 的中心縮放到這個位置捕儒。
在設(shè)備上運行 App,注意控制臺消息邓夕,你會發(fā)現(xiàn)不停地接收到位置信息刘莹,同時精度會變得越來越高。最后地圖中心會縮放到你的當(dāng)前位置焚刚。
注意:除了 horizontalAccuracy 以外還有一個 verticalAccuracy 屬性点弯。不同的是這個屬性是針對海拔的。如果這個值為 50矿咕,表示真實的海拔應(yīng)該在上下 50 米范圍內(nèi)抢肛。這兩個屬性如果為負(fù)狼钮,則表明值無效。
添加 Google Places 框架
現(xiàn)在我們有了用戶位置捡絮,可以來加載 POI 列表了熬芜。這需要用到 Google 的 Places API。
Google 的 PLaces API 需要注冊才能使用福稳。如果你已經(jīng)注冊過 Google 賬號涎拉,比如之前為了使用 Maps API 注冊的 Google 賬號,則只需要在這里選擇 Services的圆。然后跳過后面的幾步直到看到 Enabling the Places API鼓拧。
但是,如果你之前沒有用過 Google Places API越妈,你必須注冊Google 賬號季俩。
你可以跳過第二個界面,來到第三個界面梅掠,點擊 Back to Developer Consoles酌住。
然后點擊左上角的 Project/Create Project,輸入一個項目名稱阎抒。要使用 Places API赂韵,請找到 Google Places API Web Service 一行并點擊鏈接。接著點擊上方的 ENABLE挠蛉。然后點擊 Crendentials 并繼續(xù)后面幾步以獲取 API key。
加載 POI
現(xiàn)在肄满,你有了 API key谴古,請打開 PlacesLoader.swift,然后找到 let apiKey = “Your API key” 一行稠歉,將其中的內(nèi)容替換為你的 API key掰担。
這時最好能進(jìn)行一個測試,但在運行 App 之前怒炸,我們需要打開 ViewController.swift 并在 locationManager 屬性后面添加兩個屬性:
startedLoadingPOIs 用于標(biāo)記請求是否仍然在進(jìn)行带饱,因為 CLLocationManagerDelegate 方法可能被調(diào)用多次,哪怕你停止了位置更新阅羹。通過這個標(biāo)志能避免重復(fù)請求勺疼。places 屬性用于存儲收到的 POI。
現(xiàn)在找到 locationManager(manager: didUpdateLocations:) 方法捏鱼。在 if 語句內(nèi)执庐,在 “More code later …” 注釋之后加入下列代碼:
這段代碼加載了當(dāng)前用戶位置半徑 1000 米范圍內(nèi)的 POI,然后打印到控制臺中导梆。
運行 App轨淌,查看控制臺輸出迂烁。輸出內(nèi)容類似于如下例子:
原諒我糟糕的法語吧 :]
如果返回結(jié)果為 NULL,請嘗試增加半徑值递鹉。
現(xiàn)在盟步,我們的 App 獲取了用戶的位置并加載它附近的 PIO 列表。我們擁有一個用于保存 PIO 的類 Place躏结,雖然我們根本沒有用到它∪磁蹋現(xiàn)在我們應(yīng)該做的就是將 PIO 顯示到地圖上!
顯示 POI
為了在 mapView 上顯示標(biāo)注窜觉,我們還需要用另外一個類谷炸。依次點擊 File\New\File… 菜單,選擇 iOS\Swift File 然后點 Next禀挫。命名文件為 PlaceAnnotation.swift 然后點擊 Create旬陡。
編輯 PlaceAnnotation.swift 的代碼為:
這里我們讓類實現(xiàn) MKAnnotation 協(xié)議并定義了兩個屬性和一個 init 方法。
接下來應(yīng)該做的事情就是將 POI 顯示到地圖上语婴!
回到 ViewController.swift 繼續(xù)編輯 locationManager(manager: didUpdateLocations:) 方法描孟。找到 print(dict) line 并將它替換為:
代碼解釋如下:
guard 語句用于判斷返回結(jié)果的格式是否正確。
遍歷所有 POI砰左。
從 Dictionary 中檢索我們需要的數(shù)據(jù)匿醒。返回數(shù)據(jù)中包含了許多我們并不需要的信息。
用獲取的數(shù)據(jù)創(chuàng)建 Place 對象缠导,然后插入到 places 數(shù)組中廉羔。
創(chuàng)建 PlaceAnnotation,用于在地圖上顯示一個標(biāo)注僻造。
將標(biāo)注添加到 map view憋他,因為這個操作和 UI 相關(guān),所以需要在主線程中進(jìn)行髓削。
運行 App竹挡。這次,地圖上會顯示幾個大頭釘立膛,當(dāng)你點擊其中一個揪罕,你會看到地點的名稱。這個 App 看起來不錯宝泵,但說好的增強(qiáng)現(xiàn)實呢好啰?
HDAugmentedReality簡介
雖然我們做了不少工作,但我們?nèi)匀贿€有重要的事情沒有完成:是時候讓增強(qiáng)現(xiàn)實出場了鲁猩。
在右下角有一顆 Camera 按鈕坎怪。當(dāng)我們點擊這個按鈕,什么也不會發(fā)生廓握。在本節(jié)搅窿,我們會實現(xiàn)這個按鈕的動作處理嘁酿,在攝像頭的視野中增加增強(qiáng)現(xiàn)實體驗。
使用 HDAugmentedReality 庫男应,能夠大大節(jié)省我們的時間闹司。這個框架已經(jīng)包含在我們的開始項目中了。你可以在Github中找到它的最新版本沐飘,但這個東東是干嘛的游桩?
首先,HDAugmentedReality 能為你在攝像頭中增加字幕耐朴,以便顯示實時視頻借卧。
第二,它可以為你添加一個 POI 遮罩層筛峭,用于顯示它們的位置铐刘。
等會你會看到,最后一個功能是我們使用它的最大目的影晓,因為它為我們節(jié)省了大量復(fù)雜的數(shù)學(xué)計算镰吵。如果你想了解 HDAugmentedReality 背后的數(shù)學(xué),請繼續(xù)挂签。
如果你想立即進(jìn)入代碼疤祭,請?zhí)^后面兩節(jié),直接閱讀“開始編碼”一節(jié)饵婆。
警告勺馆,數(shù)學(xué)來了!
如果你看到這里侨核,表明你想學(xué)習(xí) HDAugmentedReality 中的數(shù)學(xué)谓传。非常好!值得夸獎芹关,但是,這個數(shù)學(xué)可不是一般的基礎(chǔ)數(shù)學(xué)紧卒。在下面的示例中侥衬,我們假設(shè)有兩個給定的點 A 和 B,分別表示地球上的兩個坐標(biāo)跑芳。
A 的坐標(biāo)包含了兩個值:經(jīng)度和緯度轴总。這兩個地理學(xué)名詞用來表示笛卡爾二維坐標(biāo)系中某個點的 x/y 坐標(biāo)。
經(jīng)度表示位于英國格林威治以東或者以西的某個點博个。這個值在 +180o 到 -180o 之間怀樟。
緯度表示位于赤道以南或以北的某個點。這個值在 90o(表示北極) 到 -90o (表示南極)之間盆佣,
如果你看一眼標(biāo)準(zhǔn)地球儀往堡,會發(fā)現(xiàn)經(jīng)度線從一極畫到另一極——即所謂的子午線械荷。緯度線則是圍繞地球平行分布,即所謂的緯圈虑灰。你可以翻一下地理課本吨瞎,兩條緯度線大約距離 111 千米,兩條子午線之間的距離也是 111 千米穆咐。
有 360 條緯度線颤诀,每條緯度線表示 360 度中的一度。這樣对湃,你可以用以下公式計算地球上任意兩點之間的距離:
計算出緯度和經(jīng)度距離崖叫,分別構(gòu)成直角三角的兩條直角邊。通過勾股定律拍柒,我們可以計算出斜邊心傀,即兩點間的距離:
很簡單,是吧斤儿?但不幸的是剧包,這個答案是錯誤的。
再看一眼地球儀往果,你會發(fā)現(xiàn)疆液,兩條緯度線之間的距離總是相等的,但經(jīng)度線會在兩極發(fā)生交叉陕贮。因此兩條相鄰經(jīng)度線之間的距離是變化的堕油,越靠近兩級,距離就會越近肮之,當(dāng)?shù)竭_(dá)極點掉缺,距離為 0。也就是說上述公式只在兩點位于赤道時才適用戈擒。當(dāng)兩點靠兩極約近眶明,誤差就越大。
為了精確起見筐高,你可以使用“大圓距離”搜囱。即兩點在球體上的距離,因為地球也是一個球體柑土,準(zhǔn)確說接近于球體蜀肘。這個方法非常實用。已知兩點經(jīng)緯度稽屏,計算兩點間“大圓距離”的公式為:
這個公式可以計算出兩點間的距離扮宠,精度為大約 60 千米,對于你想知道東京和紐約之間的距離來說狐榔,這已經(jīng)足夠好了坛增。兩點間的距離越近获雕,結(jié)果越精確。
呃——最困難的工作終于完成了轿偎。幸運的是 CLLocation 中有一個 distanceFromLocation: 方法典鸡,可以為我們計算出兩點間距離。HDAugmentedReality 也使用這個方法坏晦。
為什么使用 HDAugmentedReality
你可能會問“切萝玷,我還是不明白為什么要使用 HDAugmentedReality ?”確實昆婿,創(chuàng)建和顯示 frame 并不難球碉,你可以從本站找到有關(guān)文章。計算兩點間距離也可以調(diào)用 CLLocation 的方法實現(xiàn)仓蛆,沒有任何難度睁冬。
那為什么我要介紹這個框架?問題在于你必須計算出要在什么地方以及何時顯示每個 POI 的覆蓋物看疙。假設(shè)在設(shè)備朝向東北時豆拨,有一個 POI 剛好位于你的北方。你應(yīng)當(dāng)將 POI 顯示在什么地方 —— 中心還是左邊能庆?頂端還是下方施禾?
這完全取決于設(shè)備在空間中的當(dāng)前位置。如果設(shè)備傾斜向下搁胆,你必須將 POI 稍微向上靠一點弥搞。如果設(shè)備指向南,你根本不能顯示這個 POI渠旁。這就變得復(fù)雜了攀例。
這就是 HDAugmentdReality 最大的功能。它從陀螺儀和指南針讀取有用的信息顾腊,計算出設(shè)備的朝向和傾斜角度粤铭。通過這些參數(shù)來決定一個 POI 是否需要顯示以及顯示的位置在哪。
另外杂靶,你不需要操心如何顯示實時視頻并進(jìn)行復(fù)雜和容易出錯的數(shù)學(xué)計算承耿,你只需要將精力集中在如何編寫一個讓用戶樂于使用的 app。
開始編碼
現(xiàn)在伪煤,看一眼 HDAugmentedReality\Classes 文件夾中的幾個文件:
ARAnnotation: 這個類定義了 POI。
ARAnnotationView: 用于提供 POI 的視圖凛辣。
ARConfiguration: 提供了幾個基本的配置方法和助手方法抱既。
ARTrackingManager: 負(fù)責(zé)了最“沉重”的工作。幸運的是扁誓,這些工作我已經(jīng)為你做好了防泵。
ARViewController: 為你處理所有“可視對象”的控制器蚀之。它顯示了一個視頻直播界面并將標(biāo)注放到這個視圖上。
創(chuàng)建 AR 視圖
打開 ViewController.swift 在 places 屬性后定義新屬性:
找到 @IBAction func showARController(_ sender: Any) 方法捷泞,加入以下代碼:
設(shè)置 arViewController 的數(shù)據(jù)源足删。數(shù)據(jù)源負(fù)責(zé)提供需要顯示的 POI。
修改 arViewController 的屬性锁右。maxVisibleAnnotations 定義在同一時刻最多能顯示幾個標(biāo)注失受。為了讓 App 能夠不卡頓,我們將它設(shè)置為30咏瑟,但如果你位于一個極度活躍的區(qū)域時拂到,很可能無法顯示全位于你附近的所有 POI。
headingSmoothingFactor 用于將 POI 的視圖移動到屏幕上码泞。如果這個值為 1兄旬,意味著不使用任何平滑過渡效果,如果你移動你的 iPhone 視圖將直從一個地方跳到另一個地方余寥。小于 1 表明這個移動將是動畫的悔雹,但值越低可能因為動畫帶來的“滯后感”越強(qiáng)携茂。你可以調(diào)整這個值在平滑和速度之間進(jìn)行取舍。
顯示 arViewController。
你可以看一眼 ARViewController.seift 的其他屬性久免,比如 maxDistance,這個屬性指定了一個范圍撒轮,以米為單位巩掺,在這個范圍中的標(biāo)注將被顯示,而在這個值之外的不被顯示续膳。
實現(xiàn)數(shù)據(jù)源方法
Xcode 在將 dataSource 設(shè)置為 self 這行報錯改艇,要避免這個錯誤必須讓 ViewController 實現(xiàn) ARDataSource 協(xié)議。這個協(xié)議只有一個方法是必須實現(xiàn)的坟岔,這個方法需要返回一個 POI 視圖谒兄。多數(shù)情況下你需要提供一個自定義的視圖。用 cmd+N 快捷鍵新建一個文件社付。選擇 iOS\Swift File 并為新文件取名為 AnnotationView.swift承疲。
編輯新文件的代碼:
一上來就定義一個委托協(xié)議,這個協(xié)議會在后面用到鸥咖。
定義類繼承自 ARAnnotaionView燕鸽,表明這個類將用于作為 POI 的展現(xiàn)視圖。
這個視圖包含一個用于顯示 POI 名字的 Label 啼辣,一個用于顯示距離的 Label啊研。這些代碼聲明了兩個 Label 屬性,第三個屬性會在后面用到。
loadUI() 方法用于加載和配置兩個 Label党远。
還需要在這個類中定義兩個方法:
這個方法在視圖重繪時調(diào)用削解,這里你需要正確設(shè)置 Label 的位置大小以便重置它們。
這個方法通知委托對象這個視圖被點擊了沟娱,以便委托進(jìn)行必要的處理氛驮。
回到 ViewController.swift,添加一個擴(kuò)展:
這里济似,我們創(chuàng)建了一個 AnnotationView 并設(shè)置它的委托屬性矫废,然后返回它。
在可以測試之前碱屁,我們還需要實現(xiàn)另外一個擴(kuò)展:
在調(diào)用相機(jī)之前磷脯,我們必須在 Info.plist 中添加鍵值。打開 Info.plist娩脾,加入一個鍵 NSCameraUsageDescription赵誓,值設(shè)置為 Needed for AR, just like you did for accessing location information。
運行 App柿赊,點擊 camera 按鈕進(jìn)入 AR 界面俩功。當(dāng)你第一次這樣做的時候,系統(tǒng)會彈出一個請求授權(quán)訪問攝像頭的對話框碰声。點擊某個 POI诡蜓,然后注意看控制臺窗口。
收尾工作
現(xiàn)在我們有了一個 AR app胰挑,我們能夠在攝像頭視圖上顯示 POI 并能夠監(jiān)聽到 POI 的點擊事件蔓罚。接下來我們需要定義這個事件的處理邏輯。
打開 ViewController.swift 將 AnnotationViewDelegate 協(xié)議擴(kuò)展修改為:
將 annotationView 的 annotation 轉(zhuǎn)換成 Place 對象瞻颂。
加載這個 Place 對象附加屬性贡这。
對 Place 對象的相關(guān)屬性進(jìn)行賦值丽惭。
showInfoView 方法會在后面進(jìn)行實現(xiàn)。
在 showARController(sender:) 后實現(xiàn)方法:
為了换衬,我們使用一個 Alert View 來顯示 POI 的詳細(xì)信息,標(biāo)題顯示 POI 的地名,消息則顯示 POI 的 infoText术幔。
因為 ViewController 現(xiàn)在不存在于當(dāng)前視圖樹中,我們用 arViewController 來顯示 alert。
運行 App,查看效果硬纤。
結(jié)束
恭喜你,你已經(jīng)知道如何創(chuàng)建自己的基于定位的 AR app! 另外邻辉,你在本教程中也簡單了解了 Google Places API雷客。
上圖為2017年最新的視頻教程資料,搜索2352149755加我好友私聊我上傳視頻教程,有什么不懂的也可以來私聊問我。
不定時更新中缕允。
如果你能明白這些視頻資料的好差,那么你也算是入行了买置,底層和中高層就是這一步之差洒擦。