在最后幾節(jié)中奏路,我們能夠檢測(cè)到一個(gè)平面并顯示一個(gè)焦點(diǎn)方塊阔蛉,以幫助我們?yōu)槟P椭付ㄒ粋€(gè)位置弃舒。我們也熟悉了熱門測(cè)試和世界變換。現(xiàn)在状原,我們擁有顯示虛擬對(duì)象所需的所有工具聋呢。在本教程中,我們將學(xué)習(xí)如何檢索模型并使用按鈕的觸發(fā)器將其呈現(xiàn)在場(chǎng)景中颠区。一旦顯示坝冕,我們將隱藏焦點(diǎn)方塊。
下載
要學(xué)習(xí)本教程瓦呼,您需要Xcode 9或更高版本喂窟,以及Focus Square的最終Xcode項(xiàng)目测暗。您可以下載本節(jié)的最終Xcode項(xiàng)目,以幫助您與自己的進(jìn)度進(jìn)行比較磨澡。
基本視圖
在Main.Storyboard中碗啄,我們已經(jīng)提到ARSCNView默認(rèn)放在視圖控制器的頂部。但是稳摄,如果沒有UIView作為基礎(chǔ)稚字,則僅限于您可以在用戶界面上執(zhí)行的操作。為了能夠正確添加我們的按鈕厦酬,我們必須刪除當(dāng)前的 ARSCNView并首先從對(duì)象庫添加UIView作為底層胆描。接下來,選擇相同的ARKit SceneKit View并將其放回UIView之上仗阅。調(diào)整大小以填充整個(gè)視圖控制器昌讲。
約束
然后,單擊Storyboard編輯器左下角的第四個(gè)圖標(biāo)减噪,將新約束添加到場(chǎng)景視圖中短绸。定義約束以確保您的用戶界面適應(yīng)不同的屏幕尺寸或設(shè)備方向。設(shè)置為0的頂部筹裕,左醋闭,右和底部。確保它們都被約束到視圖而不是安全區(qū)域朝卒,然后單擊Add Constraints证逻。安全區(qū)域是凹口下方和主頁指示器上方的邊距,通常是屏幕的可見部分抗斤。此外瑟曲,請(qǐng)確保未選中“ 限制到邊距”。
如果被限制在安全區(qū)域而不是超級(jí)視圖豪治,這就是看起來的樣子洞拨,顯然,這看起來并不好看负拟。
重新 Outlet
請(qǐng)記住烦衣,一個(gè)IBOutlet將sceneView鏈接到ARSCNView?因?yàn)槲覀儎h除了舊的ARSCNView掩浙,所以它打破了這個(gè)Outlet花吟。我們需要重新考慮新的。為此厨姚,請(qǐng)打開“ 助理”編輯器衅澈,該圖標(biāo)看起來像兩個(gè)交織在一起的圓圈。現(xiàn)在谬墙,我們并排放置兩個(gè)分屏今布,非常適合連接经备。在右側(cè),我們有ViewController.swift部默,在那里我們可以找到該出口的聲明侵蒙。單擊并拖動(dòng)左側(cè)的圓圈,它應(yīng)該是第15行傅蹂,然后釋放到ARSCNView上》坠耄現(xiàn)在,關(guān)閉助理編輯份蝴。
添加按鈕
我們想在視圖中添加一個(gè)按鈕犁功,用作在場(chǎng)景中添加模型的觸發(fā)器。從對(duì)象庫中婚夫,將UIButton拖動(dòng)到場(chǎng)景視圖的頂部浸卦。在“ 屬性”檢查器中,刪除“ 按鈕”標(biāo)題并將圖像設(shè)置為“ 按鈕/添加”请敦。
約束到底部20但這次是在安全區(qū)域,并取消選中Constrain到邊距储玫。然后侍筛,將鼠標(biāo)懸停在左側(cè)的“ 對(duì)齊”圖標(biāo)上,并在“容器”中選中“水平”以在屏幕中水平居中撒穷。
添加按鈕功能
我們剛剛在屏幕上添加了按鈕匣椰,但它根本沒有做任何事情。當(dāng)我們觸摸它時(shí)端礼,讓按鈕執(zhí)行某些操作∏菪Γ現(xiàn)在,打開Assistant編輯器并控制將故事板中的按鈕拖到ViewController類蛤奥。代碼中的順序并不重要佳镜,因?yàn)槲覀兩院髸?huì)移動(dòng)此函數(shù)。原因是我們不能在擴(kuò)展類中執(zhí)行此操作凡桥。將Connection更改為Action蟀伸,將其命名為addObjectButtonTapped。保持原樣缅刽。完成后啊掏,關(guān)閉“ 助理”編輯器。
@IBAction func addObjectButtonTapped(_ sender: Any) {
print("Add button tapped")
}
讓我們運(yùn)行應(yīng)用程序來查看我們的新按鈕衰猛。但在此之前迟蜜,評(píng)論一些印刷品陳述是明智的。轉(zhuǎn)到updateFocusSquare()并注釋掉這些代碼行啡省。
// print("Focus square hits a plane")
// print("Focus square does not hit a plane")
對(duì)象添加文件
讓我們創(chuàng)建另一個(gè)swift文件娜睛,以便在場(chǎng)景中添加模型髓霞。右鍵單擊視圖控制器+ ARSCNViewDelegate.swift并選擇新建文件...。然后微姊,選擇Swift File酸茴,單擊Next。稱之為ViewController + ObjectAddition兢交,然后是Create薪捍。
導(dǎo)入套件(Kits)
與往常一樣,用以下框架替換Foundation配喳。然后酪穿,向ViewController添加擴(kuò)展。
import UIKit
import SceneKit
import ARKit
extension ViewController {}
檢索模型
在擴(kuò)展內(nèi)部晴裹,創(chuàng)建一個(gè)新函數(shù)來檢索我們選擇的模型是一個(gè)很好的主動(dòng)被济。此函數(shù)僅在此文件中使用,因此我們將采用fileprivate涧团。將有一個(gè)String類型的參數(shù)只磷,它將有兩個(gè)名稱。在函數(shù)外部使用的那個(gè)被命名泌绣,而在函數(shù)內(nèi)使用的是名稱钮追。它將返回一個(gè)可選的SCNNode。
fileprivate func getModel(named name: String) -> SCNNode? {}
與飛船場(chǎng)景類似阿迈,我們將使用我們指定的名稱調(diào)用場(chǎng)景元媚。然后,檢索該場(chǎng)景SketchUp的父節(jié)點(diǎn)苗沧。我們遞歸設(shè)置為false以返回具有該名稱的直接子節(jié)點(diǎn)刊棕。如果為true,它將解析所有節(jié)點(diǎn)待逞,直到找到它為止甥角。我們知道SketchUp是場(chǎng)景中唯一的節(jié)點(diǎn),所以在我們的情況下识樱,真實(shí)的不準(zhǔn)確蜈膨。之后,我們將變量名稱分配給模型的名稱牺荠。最后翁巍,此函數(shù)將在調(diào)用時(shí)返回模型。
let scene = SCNScene(named: "art.scnassets/\(name)/\(name).scn")!
guard let model = scene.rootNode.childNode(withName: "SketchUp", recursively: false) else {return nil}
model.name = name
return model
可選:PIVOT POINT FIX
如果您需要將模型的軸心點(diǎn)修改為所有3軸的中心休雌,那么您可以在此處執(zhí)行此操作灶壶。您可以使用以下公式。別客氣杈曲。
let min = model.boundingBox.min
let max = model.boundingBox.max
model.pivot = SCNMatrix4MakeTranslation(
min.x + (max.x - min.x) / 2,
min.y + (max.y - min.y) / 2,
min.z + (max.z - min.z) / 2)
顯示模型
我們剛剛完成了這個(gè)功能驰凛,現(xiàn)在胸懈,我們準(zhǔn)備在點(diǎn)擊按鈕時(shí)在場(chǎng)景中顯示我們的模型。讓我們轉(zhuǎn)到ViewController.swift并剪切動(dòng)作函數(shù)addObjectButtonTapped并將其粘貼到這里以將其全部放在一個(gè)地方恰响。
我們首先確保焦點(diǎn)方塊首先存在趣钱,因?yàn)樗辉跈z測(cè)到表面時(shí)才出現(xiàn)在屏幕上。
guard focusSquare != nil else {return}
我們選擇展示的模型是iPhoneX胚宦。因此首有,我們將使用getModel函數(shù)檢索該模型。如果由于某種原因它失敗了枢劝,我們將打印一條消息給我們井联。然后,讓我們用一個(gè)小消息將它添加到場(chǎng)景中您旁。
let modelName = "iPhoneX"
guard let model = getModel(named: modelName) else {
print("Unable to load \(modelName) from file")
return
}
sceneView.scene.rootNode.addChildNode(model)
print("\(modelName) added successfully")
運(yùn)行應(yīng)用程序烙常。您將意識(shí)到該設(shè)備不僅站起來而且漂浮在空中。當(dāng)然鹤盒,我們已經(jīng)在場(chǎng)景中添加了我們的模型蚕脏,我們還沒有把它放在表面上。所以侦锯,讓我們這樣做驼鞭。
命中測(cè)試
顯然,我們將再次使用命中測(cè)試率触,方法與之前相同终议。
let hitTest = sceneView.hitTest(screenCenter, types: .existingPlaneUsingExtent)
guard let worldTransformColumn3 = hitTest.first?.worldTransform.columns.3 else {return}
model.position = SCNVector3(worldTransformColumn3.x, worldTransformColumn3.y, worldTransformColumn3.z)
翻轉(zhuǎn)設(shè)備
要將電話平放在桌子上汇竭,請(qǐng)打開iPhoneX.scn葱蝗。在“ 節(jié)點(diǎn)”檢查器中,將x Euler Angle重置為0细燎。
讓我們?cè)僭囈淮巍两曼,F(xiàn)在,我們的設(shè)備看起來更像是在房間里玻驻。
縮放模型
如果您選擇了其他型號(hào)悼凑,您可能已經(jīng)注意到尺寸不合適。因此璧瞬,我們將擴(kuò)展它們中的每一個(gè)户辫。我們?cè)?strong>iPhoneX的場(chǎng)景編輯器中完成了它。現(xiàn)在嗤锉,我們?cè)谶@里撤消它并代之以編碼渔欢。讓我們?yōu)樗羞吔鐚⒈壤呕氐?strong>1。
回到ViewController + ObjectAddition并在getModel函數(shù)中瘟忱,我們首先為比例聲明一個(gè)變量奥额,然后根據(jù)模型設(shè)置不同的值苫幢。在我們的情況下,使用[switch]控制流來匹配我們?cè)O(shè)置的許多條件是完美的垫挨。switch語句必須是詳盡的韩肝,這就是為什么有一個(gè)默認(rèn)情況來涵蓋所有其他方案。
var scale: CGFloat
switch name {
case "iPhoneX": scale = 0.025
case "iPhone6s": scale = 0.025
case "iPhone7": scale = 0.0001
case "iPhone8": scale = 0.000008
case "iPhone8Plus": scale = 0.000008
case "iPad4": scale = 0.0006
case "MacBookPro13": scale = 0.0029
case "iMacPro": scale = 0.0245
case "AppleWatch": scale = 0.0000038
default: scale = 1
}
在返回之前將模型縮放到我們之前分配的值九榔。
model.scale = SCNVector3(scale, scale, scale)
場(chǎng)景模型
知道我們?cè)趫?chǎng)景中有多少模型會(huì)很高興哀峻。在ViewController.swift中,將一個(gè)新的類變量聲明為一個(gè)節(jié)點(diǎn)數(shù)組帚屉,我們將其初始化為空谜诫。
var modelsInTheScene: Array<SCNNode> = []
返回ViewController + ObjectAddition.swift,并在addObjectButtonTapped操作方法的末尾攻旦,將您添加的每個(gè)模型追加到數(shù)組modelsInTheScene中喻旷。然后,打印該數(shù)組的計(jì)數(shù)牢屋。
modelsInTheScene.append(model)
print("Currently have \(modelsInTheScene.count) model(s) in the scene")
我們?nèi)绾芜\(yùn)行應(yīng)用程序并堅(jiān)果且预?
焦點(diǎn)方塊隱藏/顯示選項(xiàng)
當(dāng)我們?cè)谄聊簧巷@示模型時(shí),我們?nèi)匀豢吹浇裹c(diǎn)方塊干擾了我們漂亮的模型烙无。如果我們?cè)诎仓煤箅[藏它锋谐,你怎么說?
在FocusSquare類中截酷,讓我們創(chuàng)建一個(gè)函數(shù)來為焦點(diǎn)方塊的表示設(shè)置動(dòng)畫涮拗。將隱藏和顯示兩種情況,因此隱藏值是布爾值迂苛。然后我們聲明一個(gè)SCNAction用于淡入淡出三热,淡出用于隱藏和淡入顯示。這些行動(dòng)將運(yùn)行根據(jù)是否隱藏是真還是假三幻,一前一后就漾。為此目的使用序列。
func setHidden(to hidden: Bool) {
var fadeTo: SCNAction
if hidden {
fadeTo = .fadeOut(duration: 0.5)
} else {
fadeTo = .fadeIn(duration: 0.5)
}
let actions = [fadeTo, .run({ (focusSquare: SCNNode) in
focusSquare.isHidden = hidden
})]
runAction(.sequence(actions))
}
視圖觀點(diǎn)
下一步將有點(diǎn)棘手念搬。如果我們看到模型抑堡,我們希望隱藏焦點(diǎn)方塊,對(duì)吧朗徊?但是首妖,如果我們?cè)谄聊簧峡床坏饺魏蝺?nèi)容呢?我們?cè)俅涡枰鼇磉x擇下一個(gè)位置爷恳。我們?cè)谄聊簧峡吹降氖遣粩嘧兓挠欣拢晕覀冃枰?strong>updateFocusSquare()中實(shí)現(xiàn)它。在那里,讓我們將pointOfView設(shè)置為場(chǎng)景視圖的視角妒貌。
guard let pointOfView = sceneView.pointOfView else {return}
然后通危,讓我們將firstVisibleModel的定義作為場(chǎng)景中的第一個(gè)模型。我們正在使用第一個(gè)返回滿足條件的第一個(gè)元素的方法灌曙。如果節(jié)點(diǎn)從視角可見菊碟,它將返回true或false 。
let firstVisibleModel = modelsInTheScene.first { (node) -> Bool in
return sceneView.isNode(node, insideFrustumOf: pointOfView)
}
隱藏/顯示焦點(diǎn)方塊
現(xiàn)在在刺,如果第一個(gè)模型是可見的而不是零逆害,則模型將在視圖中可見。請(qǐng)記住蚣驼,如果顯示模型魄幕,我們將隱藏焦點(diǎn)方塊,反之亦然颖杏。如果這兩個(gè)因子的值不相等纯陨,我們將改變焦點(diǎn)平方的isHidden值。
let modelsAreVisible = firstVisibleModel != nil
if modelsAreVisible != focusSquareLocal.isHidden {
focusSquareLocal.setHidden(to: modelsAreVisible)
}
實(shí)際上留储,這一切都令人困惑翼抠。我們實(shí)際上沒有選擇,因?yàn)楣?jié)點(diǎn)具有isHidden的屬性获讳,并且不顯示一個(gè)for阴颖。好吧,不是我所知道的丐膝。
那么量愧,讓我們來看看這兩個(gè)場(chǎng)景。如果modelsAreVisible為true且focusSquareLocal.isHidden為false帅矗,則表示兩者都可見偎肃,然后使setHidden為true(與modelsAreVisible值相同)以隱藏焦點(diǎn)方塊。另一方面损晤,如果modelsAreVisible為false且focusSquareLocal.isHidden為true软棺,則兩者都無處可見红竭,然后setHidden為false以顯示焦點(diǎn)方塊尤勋。聽起來很合乎邏輯。有了它茵宪,讓我們最后一次運(yùn)行應(yīng)用程序最冰。
結(jié)論
經(jīng)過漫長的旅程,我們終于將我們的模型添加到我們的環(huán)境中稀火,好像它們屬于它暖哨。我們?cè)诒竟?jié)中也學(xué)到了其他有用的概念。我們?cè)诠适掳逯卸ㄖ屏宋覀兊囊晥D凰狞,并在代碼中播放動(dòng)畫篇裁。在下一課中沛慢,我們將使用虛擬對(duì)象本身。敬請(qǐng)關(guān)注达布。