前言
對(duì)ARKit感興趣的同學(xué),可以訂閱ARKit教程專(zhuān)題
源代碼地址在這里
正文
在之前的章節(jié),我們學(xué)習(xí)了:
- 檢測(cè)一個(gè)矩形镰矿。
- 檢測(cè)一個(gè)QR碼。
- 在檢測(cè)到的矩形和QR碼上面添加一個(gè)平面。
- 在平面上面顯示內(nèi)容衙猪。
在這一章,我們將會(huì)學(xué)到:
- 通過(guò)使用故事板而不是獨(dú)立的視圖控制器來(lái)改善用戶(hù)交互。
- 切換全屏模式季蚂。
- 檢測(cè)預(yù)定義的圖像茫船。
我們會(huì)采用上一章的代碼繼續(xù)做開(kāi)發(fā)。不過(guò)我們需要移除掉一些無(wú)用的代碼扭屁。
首先算谈,我們把BillboardViewController.xib移除掉,然后再把BillboardView.swift.移除掉然眼。
打開(kāi)BillboardViewController.swift并刪除對(duì)剛剛刪除的BillboardView類(lèi)的引用。此外葵腹,刪除BillboardViewController類(lèi)中的其余代碼高每,但保留其定義。你最終會(huì)得到一個(gè)像這樣的空類(lèi):
class BillboardViewController: UIViewController {
}
接下來(lái)践宴,刪除實(shí)現(xiàn)UICollectionViewDelegateFlowLayout協(xié)議的擴(kuò)展鲸匿。
打開(kāi)ViewController.swift并找到setBillboardImages(_ :)。找到后阻肩,刪除整個(gè)方法带欢。
此外,我們可以刪除BillboardViewDelegate協(xié)議的擴(kuò)展名烤惊。找到以下代碼并將其刪除:
extension ViewController: BillboardViewDelegate {
func billboardViewDidSelectPlayVideo(
_ view: BillboardView) {
createVideo()
}
}
最后乔煞,找到addBillboardNode()。刪除以下代碼行:
let images = [
"logo_1", "logo_2", "logo_3", "logo_4", "logo_5"
].map { UIImage(named: $0)! }
setBillboardImages(images)
之后我們需要添加一些新的文件撕氧。我們首先創(chuàng)建一個(gè)Billboard.storyboard
請(qǐng)注意瘤缩,它由導(dǎo)航控制器和具有三個(gè)不同自定義單元的集合視圖控制器組成。
以上三個(gè)cell用于顯示:
- 視頻播放器
- 圖片
- 網(wǎng)頁(yè)視圖
為此伦泥,我們需要?jiǎng)?chuàng)建三個(gè)cell剥啤。
-
ImageCell:用于顯示一張圖片。
import UIKit class ImageCell: UICollectionViewCell { @IBOutlet weak var imageView: UIImageView! func show(image: UIImage) { imageView.image = image } }
-
WebBrowserCell: 用于展示一個(gè)網(wǎng)頁(yè)視圖不脯。
import UIKit class WebBrowserCell: UICollectionViewCell { @IBOutlet weak var webBrowser: UIWebView! func go(to urlString: String) { guard let url = URL(string: urlString) else { return } let request = URLRequest(url: url) webBrowser.loadRequest(request) } }
-
VideoCell:用于播放視頻府怯。
import UIKit import SpriteKit import AVFoundation import ARKit class VideoCell: UICollectionViewCell { @IBOutlet weak var playButton: UIButton! @IBOutlet weak var playerContainer: UIView! func configure(videoUrl: String, sceneView: ARSCNView, billboard: BillboardContainer) { } @IBAction func play() { } }
創(chuàng)建基于故事板的廣告牌
新廣告牌使用故事板,因此它與之前的實(shí)現(xiàn)有很大不同防楷。打開(kāi)ViewController.swift并滾動(dòng)到renderer(_:nodeFor:)牺丙。
在上一次迭代中,我們?cè)诶锩鎰?chuàng)建了BillboardViewController
addBillboardNode()。由于實(shí)現(xiàn)現(xiàn)在更復(fù)雜冲簿,而不是再次將代碼添加到該方法粟判,最好使用新方法。
在let billboardNode = addBillboardNode()之后添加如下代碼:
createBillboardController()
在上一次迭代中峦剔,我們?cè)?strong>BillboardViewController里面創(chuàng)建了
addBillboardNode()方法档礁。由于實(shí)現(xiàn)現(xiàn)在更復(fù)雜,而不是再次將代碼添加到該方法吝沫,最好使用新方法呻澜。
func createBillboardController() {
// 1
DispatchQueue.main.async {
// 2
let navController = UIStoryboard(name: "Billboard", bundle: nil) .instantiateInitialViewController() as! UINavigationController
// 3
let billboardViewController = navController.visibleViewController as! BillboardViewController
// 4
billboardViewController.sceneView = self.sceneView billboardViewController.billboard = self.billboard
// 5
billboardViewController.willMove( toParentViewController: self) self.addChildViewController(billboardViewController) self.view.addSubview(billboardViewController.view)
// 6
self.show(viewController: billboardViewController)
}
}
上面的代碼作用如下:
- 1: 切換到主線(xiàn)程,以便我們可以處理用戶(hù)界面更新惨险。
- 2: 創(chuàng)建Billboard故事板的初始視圖控制器的實(shí)例羹幸,它是一個(gè)導(dǎo)航控制器。這里的強(qiáng)制轉(zhuǎn)換是一個(gè)很好的調(diào)試工具:如果應(yīng)用程序崩潰辫愉,則意味著存在開(kāi)發(fā)錯(cuò)誤 - 初始視圖控制器很可能不是預(yù)期的導(dǎo)航控制器栅受。
- 3: 導(dǎo)航控制器的根視圖控制器是BillboardViewController。再次強(qiáng)制轉(zhuǎn)換非常方便一屋,以防我們?cè)诠适掳逯懈哪承﹥?nèi)容并忘記更新代碼窘疮。
- 4: 廣告牌將使用場(chǎng)景視圖和廣告牌容器,因此最好在這里設(shè)置它們;但是冀墨,我們必須添加兩個(gè)相應(yīng)的屬性闸衫。
- 5: 要在顯示新視圖控制器之前準(zhǔn)備它,我們需要:
??a:告訴新視圖控制器它將移動(dòng)到ViewController诽嘉。
??b: 將新視圖控制器添加到ViewController蔚出。
??c: 將新視圖控制器的視圖添加到ViewController的視圖中作為子視圖。 - 6: 新視圖控制器已準(zhǔn)備好顯示虫腋,因此我們將其傳遞給show(viewController:)骄酗。
這樣就完成了廣告牌的創(chuàng)建;但是,show方法尚未實(shí)現(xiàn)悦冀。在剛剛創(chuàng)建的方法之后添加如下代碼:
private func show(viewController: BillboardViewController) {
let material = SCNMaterial() material.isDoubleSided = true
material.cullMode = .front
material.diffuse.contents = viewController.view
billboard?.viewController = viewController
billboard?.billboardNode?.geometry?.materials = [material]
}
這與我們?cè)谏弦徽碌?strong>setBillboardImages()中所做的類(lèi)似趋翻。
打開(kāi)BillboardViewController,添加如下代碼:
var sceneView: ARSCNView?
var billboard: BillboardContainer?
構(gòu)建并運(yùn)行應(yīng)用程序盒蟆。注意一旦檢測(cè)到QR碼踏烙,應(yīng)用程序就會(huì)崩潰±龋看看Xcode控制臺(tái)會(huì)發(fā)現(xiàn)一個(gè)無(wú)法識(shí)別的選擇器被發(fā)送到廣告牌視圖控制器:
[RazeAd.BillboardViewController collectionView:numberOfItemsInSection:]: unrecognized selector sent to instance 0x1030e4600
RazeAd[3180:1334506] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RazeAd.BillboardViewController collectionView:numberOfItemsInSection:]:unrecognized selector sent to instance 0x1030e4600'
發(fā)生這種情況是因?yàn)楣适掳逍枰獜V告牌視圖控制器是UICollectionViewController的子類(lèi)讨惩,當(dāng)我們打開(kāi)故事板文件時(shí)可能會(huì)注意到它。
這是一個(gè)簡(jiǎn)單的方法:修改BillboardViewController類(lèi)并使其繼承自UICollectionViewController而不是UIViewController:
class BillboardViewController: UICollectionViewController
與廣告牌互動(dòng)
加載廣告牌故事板后寒屯,就可以專(zhuān)注于在Billboard ViewController類(lèi)中進(jìn)行連接荐捻。
最初的步驟是提供集合視圖;這意味著覆蓋UICollectionViewDataSource協(xié)議定義的一些方法。
打開(kāi)BillboardViewController.swift并且在最后添加如下代碼:
// UICollectionViewDataSource
extension BillboardViewController {
}
我們需要告訴集合視圖三件事:每個(gè)部分的部分?jǐn)?shù)量,每個(gè)部分的項(xiàng)目數(shù)以及每個(gè)項(xiàng)目要顯示的單元格处面。
在擴(kuò)展中添加如下代碼:
override func numberOfSections(
in collectionView: UICollectionView) -> Int {
return 3
}
每個(gè)單元格類(lèi)型都作為單獨(dú)的部分處理厂置。這在處理圖像序列時(shí)很有用。
接下來(lái)是在每個(gè)case下面添加以下方法:
override func collectionView(
_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let currentSection = Section(rawValue: section) else { return 0 }
switch currentSection {
case .images:
return images.count case .video:
return 1 case .webBrowser:
return 1
}
}
只有一個(gè)視頻和一個(gè)Web瀏覽器魂角,因此對(duì)于這些农渊,我們返回1。但是或颊,可能有多個(gè)圖像,因此我們返回images.count以確定需要多少行传于。
最后囱挑,我們必須添加返回給定索引路徑的單元格的方法:
override func collectionView(
_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 1
guard let currentSection =
Section(rawValue: indexPath.section) else { fatalError("Unexpected collection view section") }
// 2
let cellType: Cell switch currentSection {
case .images:
cellType = .cellImage case .video:
cellType = .cellVideo case .webBrowser:
cellType = .cellWebBrowser }
// 3
let cell = collectionView.dequeueReusableCell( withReuseIdentifier: cellType.rawValue, for: indexPath)
// 4
switch cell {
case let imageCell as ImageCell:
let image = UIImage(named: images[indexPath.row])! imageCell.show(image: image)
case let videoCell as VideoCell:
let videoUrl = "https://www.rmp-streaming.com/media/bbb-360p.mp4" if let sceneView = sceneView, let billboard = billboard { videoCell.configure( videoUrl: videoUrl, sceneView: sceneView, billboard: billboard ) } break
case let webBrowserCell as WebBrowserCell:
webBrowserCell.go(to: "https://www.raywenderlich.com")
default:
fatalError("Unrecognized cell") }
return cell
}
這是一個(gè)很長(zhǎng)的,雖然簡(jiǎn)單的實(shí)現(xiàn):
- 1:Section枚舉用于標(biāo)識(shí)每個(gè)部分沼溜。如果其原始值無(wú)法識(shí)別某個(gè)部分平挑,則會(huì)拋出fatalError。
- 2: 另一個(gè)名為Cell的枚舉系草,用于識(shí)別細(xì)胞通熄。
- 3: Cell枚舉使用String類(lèi)型的原始值來(lái)定義單元標(biāo)識(shí)符。指定索引路徑的單元格已出列找都。
- 4: 我們可以根據(jù)細(xì)胞類(lèi)型使用適當(dāng)?shù)臄?shù)據(jù)配置細(xì)胞唇辨。
我們可能已經(jīng)注意到圖像單元格的代碼引用了不存在的圖像屬性。此屬性將包含要在廣告牌中顯示的圖像名稱(chēng)列表能耻。
在billboard屬性之后赏枚,添加以下內(nèi)容:
var images: [String] = [ "logo_1", "logo_2", "logo_3", "logo_4", "logo_5" ]
目前,我們已完成廣告牌視圖控制器晓猛。構(gòu)建并運(yùn)行應(yīng)用程序饿幅。
第一個(gè)單元是視頻播放器 - 它還沒(méi)有實(shí)現(xiàn),所以不要指望它能夠工作戒职。但是栗恩,我們可以向右滑動(dòng)以顯示圖像,并且可以訪(fǎng)問(wèn)最后一個(gè)單元格中的Web瀏覽器洪燥,該單元格將加載www.raywenderlich.com磕秤。
顯示視頻播放器
視頻單元實(shí)現(xiàn)
打開(kāi)VideoCell.swift添加如下代碼:
// 1
var isPlaying = false
// 2
var videoNode: SKVideoNode!
var spriteScene: SKScene!
// 3
var videoUrl: String!
// 4
var player: AVPlayer?
// 5
weak var billboard: BillboardContainer?
weak var sceneView: ARSCNView?
上面代碼作用如下:
- 1: isPlaying是指示視頻當(dāng)前是否正在播放的標(biāo)志。
- 2: 在創(chuàng)建過(guò)程中蚓曼,將ARKit節(jié)點(diǎn)存儲(chǔ)到videoNode中亲澡,將SpriteKit場(chǎng)景存儲(chǔ)到spriteScene中。
- 3: 這包含目標(biāo)視頻的URL纫版。
- 4: 這是負(fù)責(zé)播放視頻的AVPlayer床绪。
- 5: 這些是對(duì)廣告牌容器和ARKit場(chǎng)景視圖的引用。
其中一些屬性可以在configure方法中初始化,該方法當(dāng)前為空癞己。將此內(nèi)容添加到其正文:
self.videoUrl = videoUrl
self.billboard = billboard
self.sceneView = sceneView
要播放視頻膀斋,用戶(hù)將點(diǎn)按“播放”按鈕;這個(gè)按鈕已經(jīng)連接到空的play()動(dòng)作方法,我們只需要提供實(shí)現(xiàn)痹雅。添加以下代碼:
guard let billboard = billboard else { return }
if billboard.isFullScreen {
} else {
// 1
createVideoPlayerAnchor()
// 2
billboard.videoPlayerDelegate?.didStartPlay()
// 3
playButton.isEnabled = false
}
上面代碼作用如下:
- 1: 創(chuàng)建視頻播放器錨點(diǎn)仰担。
- 2: 通知代表視頻開(kāi)始播放。
- 3: 禁用播放按鈕以防止雙擊觸發(fā)相同的操作兩次绩社。
廣告牌容器沒(méi)有isFullScreen屬性摔蓝,但我們可以快速添加它。打開(kāi)BillboardContainer.swift并添加以下內(nèi)容:
var isFullScreen = false
視頻播放器代理方法
在最后一個(gè)代碼段中愉耙,我們通過(guò)委托方法調(diào)用發(fā)送了通知;此委托存儲(chǔ)在廣告牌容器中贮尉,因此我們需要添加協(xié)議定義。仍然在BillboardContainer.swift中朴沿,在import語(yǔ)句之后添加以下內(nèi)容:
protocol VideoPlayerDelegate: class {
func didStartPlay() func didEndPlay()
}
這兩種方法用于在視頻開(kāi)始或停止時(shí)通知應(yīng)用猜谚。
現(xiàn)在,我們需要保留對(duì)要通知的目標(biāo)實(shí)體的引用赌渣。將此屬性添加到BillboardContainer:
weak var videoPlayerDelegate: VideoPlayerDelegate?
要使代理有效魏铅,必須將此屬性設(shè)置為要通知的實(shí)例。在這種情況下坚芜,實(shí)例是ViewController览芳,因?yàn)樗潜仨殞?duì)視頻播放器狀態(tài)的變化作出反應(yīng)的實(shí)體。
打開(kāi)ViewController.swift并找到createBillboard鸿竖,然后在廣告牌實(shí)例化后添加如下代碼:
billboard?.videoPlayerDelegate = self
除了設(shè)置代理之外路操,還必須實(shí)現(xiàn)它。在文件的末尾千贯,添加以下代碼以處理視頻播放器狀態(tài)更改:
extension ViewController: VideoPlayerDelegate {
func didStartPlay() {
// 1
billboard?.billboardNode?.isHidden = true
}
func didEndPlay() {
// 2
billboard?.billboardNode?.isHidden = false
}
}
上面代碼作用如下:
- 1: 在視頻播放器啟動(dòng)時(shí)隱藏廣告牌屯仗。
- 2: 視頻播放器停止時(shí)再次顯示廣告牌。
回到視頻單元格
在VideoCell的play()處理程序中調(diào)用的createVideoPlayerAnchor()方法仍未實(shí)現(xiàn)搔谴。打開(kāi)VideoCell.swift魁袜,在configure()方法之后添加以下內(nèi)容:
func createVideoPlayerAnchor() {
guard let billboard = billboard else { return }
guard let sceneView = sceneView else { return }
// 1
let center = billboard.plane.center *matrix_float4x4(SCNMatrix4MakeRotation( Float.pi / 2.0, 0.0, 0.0, 1.0))
let anchor = ARAnchor(transform: center)
// 2
sceneView.session.add(anchor: anchor)
// 3
billboard.videoAnchor = anchor
}
上面代碼作用如下:
- 1: 創(chuàng)建一個(gè)以廣告牌中心為中心的新錨點(diǎn),并圍繞z軸應(yīng)用通常的90度旋轉(zhuǎn)敦第。
- 2: 將新錨添加到ARKit場(chǎng)景峰弹。
- 3: 保留對(duì)錨點(diǎn)的引用,因?yàn)樯院髮⑿枰玫剿?/li>
視頻播放器創(chuàng)建邏輯更新
我們需要添加一些代碼來(lái)將新創(chuàng)建的錨與SceneKit節(jié)點(diǎn)相關(guān)聯(lián)芜果。 ARSCNView的委托是ViewController類(lèi)鞠呈,因此我們必須在其中創(chuàng)建新節(jié)點(diǎn)。但是右钾,為了保持一致性蚁吝,最好將該任務(wù)保留在VideoCell中旱爆,最簡(jiǎn)單的方法是通過(guò)委托實(shí)現(xiàn)。
打開(kāi)ViewController.swift并找到renderer(_:nodeFor)方法窘茁。在switch語(yǔ)句中怀伦,我們將看到已經(jīng)存在處理視頻節(jié)點(diǎn)創(chuàng)建的情況;但是,它使用的是舊實(shí)現(xiàn)山林。
我們需要?jiǎng)h除包含以下方法的實(shí)現(xiàn):
- addVideoPlayerNode()
- createVideo()
- removeVideo()
找到這三種方法并且刪除它們房待。
現(xiàn)在,我們?cè)?strong>renderer方法中驼抹,找到如下代碼:
node = addVideoPlayerNode()
把這一行代碼替換為:
node = billboard.videoNodeHandler?.createNode()
如前所述桑孩,我們將使用代理來(lái)公開(kāi)視頻單元格方法。打開(kāi)BillboardContainer.swift框冀,在videoPlayerDelegate屬性之前添加:
weak var videoNodeHandler: VideoNodeHandler?
要關(guān)閉循環(huán)洼怔,我們需要定義VideoNodeHandler協(xié)議。在import語(yǔ)句后添加如下代碼:
protocol VideoNodeHandler: class {
func createNode() -> SCNNode?
func removeNode()
}
上面代碼作用如下:
- createNode(): 當(dāng)廣告視圖控制器需要視頻播放器的新SCNNode時(shí)左驾,由廣告視圖控制器調(diào)用。
- removeNode(): 必須刪除節(jié)點(diǎn)時(shí)調(diào)用极谊。
打開(kāi)VideController.swift并在touchesBegun(_:with :)中找到有問(wèn)題的代碼行诡右。將其替換為以下內(nèi)容:
billboard?.videoNodeHandler?.removeNode()
刪除廣告牌后,我們需要重置之前添加的videoNodeHandler屬性轻猖。
找到removeBillboard()并在最后一行之前插入以下代碼行帆吻,其中billboard屬性重置為nil:
billboard?.videoNodeHandler = nil
運(yùn)行應(yīng)用程序。然后掃描QR碼咙边,當(dāng)前的效果是點(diǎn)擊視頻播放器中的播放按鈕......廣告牌消失猜煮,但不顯示視頻。
實(shí)現(xiàn)視頻節(jié)點(diǎn)處理程序
打開(kāi)VideoCell.swift败许。在configure(videoUrl:sceneView:billboard :)結(jié)束時(shí)王带,設(shè)置屬性:
billboard.videoNodeHandler = self
VideoHandlerProtocol分別定義了兩種方法來(lái)為視頻播放器創(chuàng)建和刪除SceneKit節(jié)點(diǎn)。
完成后市殷,構(gòu)建并運(yùn)行應(yīng)用程序愕撰。掃描二維碼后,點(diǎn)擊播放按鈕;廣告牌消失醋寝,并由顯示視頻的簡(jiǎn)約視頻播放器取代搞挣。
點(diǎn)按屏幕上的任意位置即可關(guān)閉視頻播放器并返回廣告牌。
全屏
觸發(fā)全屏模式
我們可以通過(guò)多種方式使用全屏模式音羞,例如使用按鈕或放大/縮小手勢(shì)囱桨。也可以使用雙擊,這個(gè)項(xiàng)目中嗅绰,我們就采用雙擊手勢(shì)舍肠。
打開(kāi)BillboardViewController.swift并且在images屬性之后添加如下代碼:
// 1
let doubleTapGesture = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
// 2
doubleTapGesture.numberOfTapsRequired = 2 doubleTapGesture.addTarget( self, action: #selector(didDoubleTap)) view.addGestureRecognizer(doubleTapGesture)
}
// 3
@objc func didDoubleTap() {
guard let billboard = billboard else { return }
if billboard.isFullScreen {
restoreFromFullScreen()
} else {
showFullScreen()
}
}
如果之前使用過(guò)手勢(shì)搀继,那么這些代碼可能看起來(lái)很熟悉。我們正在使用點(diǎn)擊手勢(shì)識(shí)別器來(lái)檢測(cè)廣告牌視圖控制器上的雙擊:
- 1: 此屬性用于保留對(duì)手勢(shì)的引用貌夕,以防我們要禁用或以其他方式更改手勢(shì)律歼。
- 2: 我們?cè)趘iewDidLoad()中配置并激活點(diǎn)擊手勢(shì)。 numberOfTapsRequired設(shè)置為2**表示我們要捕獲雙擊啡专。
- 3: 在雙擊處理程序中险毁,切換全屏模式。這兩種情況都是由接下來(lái)要實(shí)施的各種方法處理的们童。
進(jìn)入全屏
當(dāng)廣告牌以ARKit模式顯示時(shí)畔况,其導(dǎo)航控制器 - 以及整個(gè)堆棧 - 是ViewController的子節(jié)點(diǎn),因?yàn)檫@是顯示ARKit的視圖控制器慧库。要進(jìn)入全屏模式跷跪,我們必須將廣告牌的視圖與其超級(jí)視圖分離,并將其附加到父視圖控制器的視圖中齐板。
創(chuàng)建廣告牌視圖控制器時(shí)吵瞻,我們將其視圖添加到ViewController的show(viewController :)方法中的SCNMaterial:
private func show(viewController: BillboardViewController) {
let material = SCNMaterial()
...
material.diffuse.contents = viewController.view ...
}
在BillboardViewController的末尾,添加以下代碼:
extension BillboardViewController {
func showFullScreen() {
guard let billboard = billboard else { return }
guard billboard.isFullScreen == false else { return }
// 1
guard let mainViewController = parent as? ViewController else { return }
self.mainViewController = mainViewController mainView = view.superview
// 2
willMove(toParentViewController: nil)
view.removeFromSuperview()
removeFromParentViewController()
// 3
willMove(toParentViewController: mainViewController)
mainViewController.view.addSubview(view)
mainViewController.addChildViewController(self)
// 4
billboard.isFullScreen = true
}
}
上面作用如下:
- 1甘磨; 保持對(duì)父視圖控制器和超級(jí)視圖的引用橡羞。退出全屏模式時(shí),我們需要它們才能恢復(fù)視圖济舆。
- 2: 從各自的父母中刪除視圖控制器及其視圖卿泽。
- 3: 再次將視圖控制器添加到其上一個(gè)父級(jí),并將其視圖添加到父級(jí)視圖(而不是SCNMaterial)滋觉。
- 4: 設(shè)置標(biāo)記以跟蹤全屏模式是打開(kāi)還是關(guān)閉签夭。
現(xiàn)在,我們需要添加用于跟蹤原始父視圖控制器和父視圖的兩個(gè)屬性椎侠。在廣告牌屬性后添加它們:
weak var mainViewController: AdViewController?
weak var mainView: UIView?
退出全屏
要退出全屏模式第租,必須執(zhí)行相反操作:從ViewController視圖中刪除視圖,并在進(jìn)入全屏模式之前將其放回視圖所具有的任何父類(lèi)我纪。
回想一下煌妈,我們?cè)?strong>mainView屬性中保存了這個(gè)值。
打開(kāi)BillboardViewController.swift文件宣羊,在showFullScreen()之后添加此代碼:
func restoreFromFullScreen() {
guard let billboard = billboard else { return }
guard billboard.isFullScreen == true else { return }
guard let mainViewController = mainViewController else { return }
guard let mainView = mainView else { return }
// 1
willMove(toParentViewController: nil)
view.removeFromSuperview()
removeFromParentViewController()
// 2
willMove(toParentViewController: mainViewController)
mainView.addSubview(view) mainViewController.addChildViewController(self)
// 3
billboard.isFullScreen = false
self.mainViewController = nil
self.mainView = nil
}
上面的代碼作用如下:
- 1: 從父視圖中刪除BillboardViewController的視圖璧诵,在本例中是ViewController的視圖。
- 2:將視圖放回到原始視圖中仇冯,該視圖在全屏顯示時(shí)存儲(chǔ)在mainView屬性中之宿。
- 3: 重置全屏圖像,以及對(duì)主視圖控制器和主視圖的引用苛坚。
運(yùn)行應(yīng)用程序并執(zhí)行常規(guī)的QR檢測(cè)比被。然后色难,雙擊廣告牌以轉(zhuǎn)換到全屏。
到此為止等缀,視頻播放器還不能用枷莉。
修復(fù)視頻播放器
打開(kāi)VideoCell.swift。在play()中尺迂,將if billboard.isFullScreen分支留空笤妙,插入以下代碼:
if isPlaying == false {
// 1
createVideoPlayerView()
playButton.setImage( #imageLiteral(resourceName: "arKit-pause"), for: .normal)
} else {
// 2
stopVideo()
playButton.setImage( #imageLiteral(resourceName: "arKit-play"), for: .normal)
}
// 3
isPlaying = !isPlaying
上面的代碼作用如下:
1: 如果沒(méi)有播放視頻,請(qǐng)創(chuàng)建視頻播放器視圖并更改按鈕的圖標(biāo)以顯示paused圖像噪裕。
2: 如果當(dāng)前正在播放視頻蹲盘,請(qǐng)將其停止并將按鈕的圖標(biāo)恢復(fù)為play圖像。
3: 切換isPlaying標(biāo)志膳音,以跟蹤視頻播放狀態(tài)召衔。
接下來(lái)需要做的是創(chuàng)建視頻播放器。在createVideoPlayerAnchor()之后添加如下代碼:
func createVideoPlayerView() {
if player == nil {
guard let url = URL(string: videoUrl) else { return }
player = AVPlayer(url: url)
let layer = AVPlayerLayer(player: player)
layer.frame = playerContainer.bounds
playerContainer.layer.addSublayer(layer)
}
player?.play()
}
如果不存在視頻播放器我們會(huì)創(chuàng)建一個(gè)視頻播放器并且開(kāi)始播放祭陷。接下來(lái)苍凛,我們還需要一個(gè)停止播放的方法:
func stopVideo() {
player?.pause()
}
上面的這個(gè)方法會(huì)讓播放器暫停播放。
構(gòu)建并運(yùn)行應(yīng)用程序兵志。掃描QR碼并雙擊廣告牌進(jìn)入全屏模式醇蝴。然后,點(diǎn)擊play按鈕播放視頻;再次點(diǎn)擊它可暫停視頻毒姨。
檢測(cè)參考圖像
ARKit 1.5引入了一些新功能:能夠檢測(cè)自定義圖像。因此钉寝,我們可能希望讓?xiě)?yīng)用程序識(shí)別一個(gè)或多個(gè)預(yù)定義圖像弧呐,而不是檢測(cè)白色矩形或QR碼。
在下一節(jié)中嵌纲,我們將通過(guò)使用自定義圖像檢測(cè)替換QR代碼檢測(cè)柠贤。但是衔彻,要實(shí)現(xiàn)此目的,有兩個(gè)先決條件:
- Xcode版本大于或等于9.3。
- iOS 系統(tǒng)版本大于或等于11.3窝剖。
我們添加一個(gè)或多個(gè)參考圖像。
選擇參考圖像
第一步是讓?xiě)?yīng)用知道需要檢測(cè)哪些圖像朴读。在Xcode中似踱,打開(kāi)Assets.xcassets目錄,然后單擊位于窗口右下角的+按鈕墓臭。將顯示一個(gè)彈出菜單蘸鲸,其中包含一長(zhǎng)串選項(xiàng)。找到這兩個(gè):New AR Resource Group和New AR Reference Image窿锉。
我們將使用前者創(chuàng)建組酌摇,后者將新圖像添加到AR資源組膝舅。 AR資源組是為ARKit創(chuàng)建的特殊組類(lèi)型。它在運(yùn)行時(shí)加載窑多,ARKit*使用它來(lái)確定它應(yīng)該檢測(cè)哪些圖像仍稀。
創(chuàng)建一個(gè)新的AR資源組并將其命名為RMK-ARKit-triggers。然后埂息,將圖像添加到新創(chuàng)建的組:
- 1: 右鍵選擇logo_3圖片
- 2: 選擇Show in Finder技潘。
- 3: 在Xcode打開(kāi)的Finder窗口中,拖動(dòng)圖像文件并將其放入RMK-ARKit-triggers組耿芹。
- 4: 對(duì)logo_4圖像重復(fù)步驟1-3崭篡。
選擇組中兩個(gè)圖像之一,然后查看“屬性”檢查器吧秕。除了圖像名稱(chēng)琉闪,它還包含兩個(gè)可編輯的參數(shù):大小和度量單位。
尺寸和度量單位屬性用于為現(xiàn)實(shí)世界中的圖像的物理尺寸提供ARKit砸彬。 ARKit使用這些測(cè)量值來(lái)確定相機(jī)的正確視角和距離颠毙。
為寬度和高度輸入0.2,并驗(yàn)證Meters是選定的度量單位砂碉,如下頁(yè)所示蛀蜜。如果在A4或Letter紙張上最大化打印,這大致是圖像的大小增蹭。重復(fù)其他圖像滴某。
請(qǐng)注意,兩個(gè)圖像現(xiàn)在在其各自的右下角顯示警告圖標(biāo)滋迈。將鼠標(biāo)懸停在警告圖標(biāo)上霎奢,然后單擊以顯示更多信息:
上面警告包含的意思如下:
- 1: 圖像彼此太相似了。
- 2: 圖像顏色直方圖不足夠饼灿,這意味著沒(méi)有均勻的顏色分布幕侠。
- 3: 圖像具有相同顏色的大部分。
看看你添加的圖像碍彭,它們并不完全符合第二和第三個(gè)要求 - 但不要擔(dān)心晤硕,這些圖片能用。
注冊(cè)參考圖像
此時(shí)庇忌,應(yīng)用程序不知道這些圖像的存在 - 你必須告訴它們舞箍。這是在ARKit配置期間完成的。
打開(kāi)ViewController.swift并導(dǎo)航到viewWillAppear(_ :)皆疹。使用以下代碼行創(chuàng)建配置:
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.worldAlignment = .camera
// Run the view's session
sceneView.session.run(configuration)
ARWorldTrackingConfiguration繼承的ARConfiguration類(lèi)具有名為detectionImages的屬性;我們可以使用此屬性來(lái)指定希望ARKit識(shí)別的一組圖像创译。
可檢測(cè)圖像捆綁在ARReferenceImage的實(shí)例中,該實(shí)例添加了一些元數(shù)據(jù)墙基,即:
- name软族。
-
physicalSize刷喜。
如前所述,在創(chuàng)建AR資源組時(shí)立砸,可以分配兩者掖疮。
在上面顯示的代碼段中,在運(yùn)行會(huì)話(huà)的最后一行之前颗祝,插入此代碼以加載和設(shè)置參考圖像:
// 1
var triggerImages = ARReferenceImage.referenceImages( inGroupNamed: "RMK-ARKit-triggers", bundle: nil)
// 2
configuration.detectionImages = triggerImages
第一行加載之前添加到RMK-ARKit-triggers AR資源組的圖像浊闪。第二行將這些圖像分配給配置。
需要注意的是referenceImages(inGroupNamed:bundle :)如何返回Set <ARReferenceImage>而不是數(shù)組螺戳。
提供一個(gè)hook
現(xiàn)在搁宾,我們需要ARKit在識(shí)別參考圖像時(shí)通知應(yīng)用程序。識(shí)別后倔幼,ARKit會(huì)自動(dòng)為會(huì)話(huà)添加錨點(diǎn)盖腿。
添加錨點(diǎn)時(shí),有三種可能的方法可以通知:
ARSessionDelegate委托的session(_:didAdd :)损同。
查看(_:didAdd:for :) ARSKViewDelegate委托翩腐。
-
ARSCNViewDelegate委托的rendered(_:didAdd:for:)。
sceneView.session.delegate = self
向下滾動(dòng)以找到ARSessionDelegate擴(kuò)展膏燃,并在最后添加:
// 1
func session(_ session: ARSession,
didAdd anchors: [ARAnchor]) {
// 2
if let imageAnchor = anchors.compactMap({ $0 as? ARImageAnchor }).first {
// 3
self.createBillboard(center: imageAnchor.transform,size: imageAnchor.referenceImage.physicalSize)
}
}
上面代碼作用如下:
- 1: 當(dāng)錨點(diǎn)添加到會(huì)話(huà)時(shí)茂卦,ARKit調(diào)用此委托方法;識(shí)別參考圖像時(shí)創(chuàng)建新錨點(diǎn)。
- 2: 由于該方法接收到一組錨點(diǎn)组哩,因此我們首先按類(lèi)型過(guò)濾它們并僅保留ARImageAnchor的實(shí)例;然后你拿第一個(gè)等龙。
- 3: 我們可以使用該錨點(diǎn)創(chuàng)建廣告牌。
打開(kāi)AdViewController.swift文件伶贰,找到createBillboard()的當(dāng)前實(shí)現(xiàn)蛛砰,并在它之后添加此重載:
func createBillboard(center: matrix_float4x4, size: CGSize) {
// 1
let plane = RectangularPlane(center: center, size: size)
// 2
let rotation = SCNMatrix4MakeRotation(Float.pi / 2, -1.0, 0.0, 0.0)
// 3
let rotatedCenter = plane.center * matrix_float4x4(rotation)
let anchor = ARAnchor(transform: rotatedCenter)
billboard = BillboardContainer( billboardAnchor: anchor, plane: plane)
billboard?.videoPlayerDelegate = self
sceneView.session.add(anchor: anchor)
print("New billboard created")
}
上面代碼作用如下:
- 1: 我們不必使用4個(gè)矩形頂點(diǎn),而是具有參考圖像的中心幕袱,因此我們可以使用RectangularPlane初始化程序的正確重載暴备。
- 2: 旋轉(zhuǎn)矩陣是不同的悠瞬。而不是圍繞z軸旋轉(zhuǎn)90度们豌,我們需要旋轉(zhuǎn)相同的度數(shù),但圍繞x軸浅妆,以及相反的方向望迎。這是SceneKit節(jié)點(diǎn)通常需要的調(diào)整。
- 3: 方法的其余部分與另一個(gè)相同凌外。
注意:如果參考圖像的大小不正確辩尊,ARKit無(wú)論如何都會(huì)識(shí)別它。但是康辑,觀點(diǎn)可能會(huì)發(fā)生變化摄欲。如果圖像較大轿亮,ARKit可能認(rèn)為它比它更接近,而如果它更小胸墙,它可以看得更遠(yuǎn)我注。
在運(yùn)行時(shí)添加參考圖像
能夠檢測(cè)預(yù)定義圖像非常棒,但是必須將其與應(yīng)用程序捆綁在一起可能是一個(gè)相當(dāng)大的限制迟隅,因?yàn)橐滤牵覀儽仨毎l(fā)布新版本的應(yīng)用程序。
在ViewController.swift中智袭,向上滾動(dòng)到viewWillAppear(_ :)并找到創(chuàng)建triggerImages變量的行奔缠。在該行之后添加此代碼:
// 1
let image = UIImage(named: "logo_2")!
// 2
let referenceImage = ARReferenceImage(image.cgImage!, orientation: .up, physicalWidth: 0.2)
// 3
triggerImages?.insert(referenceImage)
上面的代碼作用如下:
- 1: 從圖像資源中找到的logo_2圖像創(chuàng)建UIImage。
- 2: 為它創(chuàng)建一個(gè)參考圖像吼野,通過(guò)0.2米的物理寬度校哎。系統(tǒng)計(jì)算物理高度。
- 3: 將新創(chuàng)建的參考圖像添加到加載到triggerImages變量中的組箫锤。
參考圖像檢測(cè)是不同的用例贬蛙,并不代替QR碼檢測(cè)。使用QR碼谚攒,我們可以存儲(chǔ)附加值的自定義數(shù)據(jù)阳准。
上一章 | 目錄 | 下一章 |
---|