I had to use SceneKit
to display the 3D model. Since the app itself was created with SwiftUI
, I will write how to displaySceneKit
with SwiftUI
.
In addition to displaying 3D models, the following will also be covered.
--Display View screenshot
--Reset display coordinates
-Display switching with and without texture
Based on the following articles and source code [Reference] SceneKit to show 3D content in Swift 5 The 3D model data (sneakers) is also from this source.
import SwiftUI
import SceneKit
struct SceneKitOnSwiftUI1View: View {
var body: some View {
WrappedSceneKit1View()
}
}
struct WrappedSceneKit1View: UIViewRepresentable {
typealias UIViewType = SCNView
func makeUIView(context: Context) -> SCNView {
let scene = SCNScene(named: "converse_obj.obj")
let scnView = SCNView()
scnView.scene = scene
// 2: Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
// 3: Place camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 35)
// 4: Set camera on scene
// scene.rootNode.addChildNode(cameraNode)
// 5: Adding light to scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 35)
scene?.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene?.rootNode.addChildNode(ambientLightNode)
// Allow user to manipulate camera
scnView.allowsCameraControl = true
// Show FPS logs and timming
// sceneView.showsStatistics = true
// Set background color
scnView.backgroundColor = UIColor.white
// Allow user translate image
scnView.cameraControlConfiguration.allowsTranslation = false
// Set scene settings
scnView.scene = scene
return scnView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
What was displayed in UIView
of Storyboard
in the original source is wrapped in UIViewRepresentable
and displayed in SwiftUI
.
I had to create an icon based on the 3D model drawing screen, so that method.
import SwiftUI
import SceneKit
let FINE_NAME = "screenshot.png "
struct SceneKitOnSwiftUI2View: View {
@State var isScreenShot: Bool = false
@State var uiImg: UIImage? = nil
var body: some View {
WrappedSceneKit2View(isScreenShot: self.$isScreenShot, uiImg: self.$uiImg)
.frame(height: 240)
Button(action: {
self.isScreenShot = true
}){
VStack {
Text("ScreenShot")
if let _uiImg = self.uiImg {
Image(uiImage: _uiImg)
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipped()
}
}
}
}
}
struct WrappedSceneKit2View: UIViewRepresentable {
@Binding var isScreenShot: Bool
@Binding var uiImg: UIImage?
typealias UIViewType = SCNView
func makeUIView(context: Context) -> SCNView {
//Same as WrappedSceneKit1View
}
func updateUIView(_ uiView: UIViewType, context: Context) {
if isScreenShot {
//Save screenshot
DispatchQueue.main.async {
//screenshot
self.uiImg = uiView.snapshot()
self.isScreenShot = false
}
}
}
}
The screenshot itself can only be done with uiView.snapshot ()
. In this source code, a screenshot of View
displaying the 3D model at the top of the screen is taken and placed at the bottom of the screen.
By setting allowsCameraControl
of SCNView
to true
, operations such as object rotation can be implemented without writing anything, but there are times when you want to restore it, so reset the display below. I implemented it like this.
import SwiftUI
import SceneKit
struct SceneKitOnSwiftUI3View: View {
@State var isReset: Bool = false
var body: some View {
WrappedSceneKit3View(isReset: self.$isReset)
.frame(height: 240)
Button(action: {
self.isReset = true
}){
Text("Reset")
}
}
}
struct WrappedSceneKit3View: UIViewRepresentable {
@Binding var isReset: Bool
typealias UIViewType = SCNView
func makeUIView(context: Context) -> SCNView {
let scnView = SCNView()
self.setup(scnView)
return scnView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
self.setup(uiView)
DispatchQueue.main.async {
self.isReset = false
}
}
func setup(_ scnView: SCNView) {
let scene = SCNScene(named: "converse_obj.obj")
scnView.scene = scene
//Below, makeUIView()Same as what I wrote in
}
}
setup ()
is prepared so that it can be initialized at any time.
Set the texture in code.
import SwiftUI
import SceneKit
import SceneKit.ModelIO
struct SceneKitOnSwiftUI4View: View {
@State var isColor: Bool = false
var body: some View {
WrappedSceneKit4View(isColor: self.$isColor)
.frame(height: 240)
Button(action: {
self.isColor.toggle()
}){
Text("Texture")
}
}
}
struct WrappedSceneKit4View: UIViewRepresentable {
@Binding var isColor: Bool
typealias UIViewType = SCNView
func makeUIView(context: Context) -> SCNView {
let scnView = SCNView()
self.setup(scnView)
return scnView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
self.setup(uiView)
//If you paste the object directly on the scene, you can not mess with the texture, so have it as a child node
let path = Bundle.main.path(forResource: "converse_obj", ofType: "obj")!
let url = URL(fileURLWithPath: path)
let modelObj = SCNMaterial()
let objNode = SCNNode(mdlObject: MDLAsset(url: url).object(at: 0))
objNode.geometry?.materials = [modelObj]
//The model is"MDL_OBJ_material0"Since multiple items will be retained under the name, leave them empty once before adding them.
uiView.scene?.rootNode.childNodes
.filter({ $0.name == "MDL_OBJ_material0" })
.forEach{ node in
node.removeFromParentNode()
}
uiView.scene?.rootNode.addChildNode(objNode)
// diffuse.Specify the texture in contents
if self.isColor {
modelObj.diffuse.contents = UIImage(named: "converse.jpg ")
} else {
modelObj.diffuse.contents = nil
}
}
func setup(_ scnView: SCNView) {
//Same as WrappedSceneKit3View
}
If you paste the object directly on Scene
, you cannot set the texture, so it is given as a child node.
Source code: SceneKitOnSwiftUI
Recommended Posts