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