Perform SCNAction using a button (SwiftUI) - ios

With UIKit I have created a SceneView with an object, that can perform SCNActions when a button has been pressed:
import UIKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet weak var ScenekitView: SCNView!
var scene:SCNScene!
var ship:SCNNode!
#IBAction func button(_ sender: Any) {
let sequence = SCNAction.sequence([SCNAction.moveBy(x: 0, y: 0, z: -10, duration: 0.5),SCNAction.moveBy(x: 0, y: 0, z: 10, duration: 0.5)])
ship.runAction(sequence)
}
override func viewDidLoad() {
super.viewDidLoad()
scene = SCNScene(named: "mainScene.scn")!
var cameraNode: SCNNode!
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
cameraNode.scale = SCNVector3(1, 1, 0.5)
cameraNode.eulerAngles = SCNVector3(0, 0, 0)
ScenekitView.scene = scene
ship = scene.rootNode.childNode(withName: "ship reference", recursively: true)!
let pos = SCNVector3Make(0, 0, 0)
ship.runAction(SCNAction.move(to: pos, duration: 1))
}
}
Now I want to achieve the same using SwiftUI.
While I am able to, in this example, let the camera perform an action once the view has loaded, I have no idea how to let it perform another action after the press of a button.
Especially because the node is defined in a function in another sub view.
This is what I got so far:
import SwiftUI
import SceneKit
struct ContentView: View {
var body: some View{
VStack {
ScenekitView().ignoresSafeArea()
Button("move") {
//here I would like to call a function including a SCNAction
}
}
}
}
struct ScenekitView : UIViewRepresentable {
func makeUIView(context: Context) -> SCNView {
let scene = SCNScene(named: "SceneFile.scn")!
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
cameraNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 5, duration: 1)))
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
scnView.allowsCameraControl = false
}
}
Thanks for any helpful ideas.

You can create an object owned by the parent view (ContentView) that distributes the scene reference to the ScenekitView and allows access from the Button.
You might want to adjust details (like where the camera setup is done), but this is the general concept:
class SceneManager : ObservableObject {
let scene = SCNScene(named: "SceneFile.scn")!
var ship:SCNNode
init() {
ship = scene.rootNode.childNode(withName: "ship reference", recursively: true)!
}
func move() {
let pos = SCNVector3Make(0, 0, 0)
ship.runAction(SCNAction.move(to: pos, duration: 1))
}
}
struct ContentView: View {
#StateObject var sceneManager = SceneManager()
var body: some View{
VStack {
ScenekitView(scene: sceneManager.scene)
.ignoresSafeArea()
Button("move") {
sceneManager.move()
}
}
}
}
struct ScenekitView : UIViewRepresentable {
var scene : SCNScene
func makeUIView(context: Context) -> SCNView {
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
cameraNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 5, duration: 1)))
let scnView = SCNView()
scnView.scene = scene
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.allowsCameraControl = false
}
}

Related

Swift - Problem with scrollview (Need to print when dragging down)

I am testing the swiping controller/gesture from Jake Spracher with his SnapchatSwipeView (https://github.com/jakespracher/Snapchat-Swipe-View )
I have setup a topVC,leftVC,rightVC and middleVC(the main VC).
I manage to capture when the user swipe from the center to the rightVC or to the left VC, with this :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if delegate != nil && !delegate!.outerScrollViewShouldScroll() && !directionLockDisabled {
let newOffset = CGPoint(x: self.initialContentOffset.x, y: self.initialContentOffset.y)
self.scrollView!.setContentOffset(newOffset, animated: false)
}
if (scrollView.contentOffset) == CGPoint(x: 750.0, y: 0.0) {
print ("TEST OK LEFT!")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadRinkbox"), object: nil)
}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 0.0) {
print ("TEST OK LEFT!")
}
if (scrollView.contentOffset) == CGPoint(x: -750.0, y: 0.0) {
print ("TEST OK RIGHT")
}
if (scrollView.contentOffset) == CGPoint(x: 375, y: 0.0) {
print ("TEST OK!")
}
}
But I cannot manage to capture when the user swipe from the centerVC to the topVC, and from topVC to centerVC. I have tried a lot of things but I didn't manage to do it.
My code is for swift 4.
For clarity, I put here the two full swift files. Many, million thanks for those that can help me !
ContainerViewController.swift
//
// ContainerViewController.swift
// SnapchatSwipeView
//
// Created by Jake Spracher on 8/9/15.
// Copyright (c) 2015 Jake Spracher. All rights reserved.
//
import UIKit
import AVFoundation
protocol SnapContainerViewControllerDelegate {
// print "Snapcontainerview"
func outerScrollViewShouldScroll() -> Bool
}
class SnapContainerViewController: UIViewController, UIScrollViewDelegate {
var captureSession : AVCaptureSession!
private var current: UIViewController
// print "2"
var deeplink: DeeplinkType? {
didSet {
handleDeeplink()
}
}
init() {
current = SplashViewController()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var topVc: UIViewController?
var leftVc: UIViewController!
var middleVc: UIViewController!
var rightVc: UIViewController!
var bottomVc: UIViewController?
var directionLockDisabled: Bool!
var horizontalViews = [UIViewController]()
var veritcalViews = [UIViewController]()
var initialContentOffset = CGPoint() // scrollView initial offset
var middleVertScrollVc: VerticalScrollViewController!
var scrollView: UIScrollView!
var delegate: SnapContainerViewControllerDelegate?
class func containerViewWith(_ leftVC: UIViewController,
middleVC: UIViewController,
rightVC: UIViewController,
topVC: UIViewController?=nil,
bottomVC: UIViewController?=nil,
directionLockDisabled: Bool?=false) -> SnapContainerViewController {
let container = SnapContainerViewController()
container.directionLockDisabled = directionLockDisabled
container.topVc = topVC
container.leftVc = leftVC
container.middleVc = middleVC
container.rightVc = rightVC
container.bottomVc = bottomVC
return container
}
var scrollOffSetClosure: ((_ offset: CGFloat) -> Void)?
// code truncated for brevity
func scrollViewDidScroll2(_ scrollView: UIScrollView) {
scrollOffSetClosure!(scrollView.contentOffset.x)
print("test here")
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
print("end scroll")
}
override func viewDidLoad() {
super.viewDidLoad()
addChildViewController(current)
current.view.frame = view.bounds
view.addSubview(current.view)
current.didMove(toParentViewController: self)
}
func setupVerticalScrollView() {
middleVertScrollVc = VerticalScrollViewController.verticalScrollVcWith(middleVc: middleVc as! Camera,
topVc: topVc,
bottomVc: bottomVc)
delegate = middleVertScrollVc
}
func setupHorizontalScrollView() {
scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
print ("6")
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
scrollView.frame = CGRect(x: view.x,
y: view.y,
width: view.width,
height: view.height
)
self.view.addSubview(scrollView)
let scrollWidth = 3 * view.width
let scrollHeight = view.height
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
leftVc.view.frame = CGRect(x: 0,
y: 0,
width: view.width,
height: view.height
)
middleVertScrollVc.view.frame = CGRect(x: view.width,
y: 0,
width: view.width,
height: view.height
)
rightVc.view.frame = CGRect(x: 2 * view.width,
y: 0,
width: view.width,
height: view.height
)
addChildViewController(leftVc)
addChildViewController(middleVertScrollVc)
addChildViewController(rightVc)
scrollView.addSubview(leftVc.view)
scrollView.addSubview(middleVertScrollVc.view)
scrollView.addSubview(rightVc.view)
leftVc.didMove(toParentViewController: self)
middleVertScrollVc.didMove(toParentViewController: self)
rightVc.didMove(toParentViewController: self)
scrollView.contentOffset.x = middleVertScrollVc.view.frame.origin.x
scrollView.delegate = self
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// print ("Scrollviewvillbegindragging scrollView.contentOffset \(scrollView.contentOffset)")
self.initialContentOffset = scrollView.contentOffset
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if delegate != nil && !delegate!.outerScrollViewShouldScroll() && !directionLockDisabled {
let newOffset = CGPoint(x: self.initialContentOffset.x, y: self.initialContentOffset.y)
// print(newOffset.x)
//print(newOffset.y)
// Setting the new offset to the scrollView makes it behave like a proper
// directional lock, that allows you to scroll in only one direction at any given time
self.scrollView!.setContentOffset(newOffset, animated: false)
// print ("newOffset \(newOffset)")
// tell child views they have appeared / disappeared
}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 667.0) {print ("aaa!")}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: -667.0) {print ("bbb!")}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 375) {print ("aaccca!")}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: -375) {print ("ddddd!")}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 0.0) {print ("eeeeeee!")}
// if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 667.0) {print ("ffffff!")}
// if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 667.0) {print ("aggggggggaa!")}
// print ("scrollViewDidScroll scrollView.contentOffset \(scrollView.contentOffset)")
// scrollView.contentOffset
if (scrollView.contentOffset) == CGPoint(x: 750.0, y: 0.0) {
print ("TEST OK LEFT!")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadRinkbox"), object: nil)
}
if (scrollView.contentOffset) == CGPoint(x: 0.0, y: 0.0) {
print ("TEST OK RIGHT!")
}
if (scrollView.contentOffset) == CGPoint(x: -750.0, y: 0.0) {
print ("TEST OK LEFT")
}
if (scrollView.contentOffset) == CGPoint(x: 375, y: 0.0) {
print ("TEST LEFT/RIGHT OK!")
}
}
private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) {
print ("Ca va dans animateFadeTransition")
current.willMove(toParentViewController: nil)
addChildViewController(new)
transition(from: current, to: new, duration: 0.3, options: [.transitionCrossDissolve, .curveEaseOut], animations: {
}) { completed in
self.current.removeFromParentViewController()
new.didMove(toParentViewController: self)
self.current = new
completion?()
}
}
private func animateDismissTransition(to new: UIViewController, completion: (() -> Void)? = nil) {
print ("Ca va dans animateDismissTransition")
let initialFrame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)
current.willMove(toParentViewController: nil)
addChildViewController(new)
new.view.frame = initialFrame
transition(from: current, to: new, duration: 0.3, options: [], animations: {
new.view.frame = self.view.bounds
}) { completed in
self.current.removeFromParentViewController()
new.didMove(toParentViewController: self)
self.current = new
completion?()
}
}
}
and VerticalScrollViewController
/ MiddleScrollViewController.swift
// SnapchatSwipeView
//
// Created by Jake Spracher on 12/14/15.
// Copyright © 2015 Jake Spracher. All rights reserved.
//
import UIKit
class VerticalScrollViewController: UIViewController, SnapContainerViewControllerDelegate {
var topVc: UIViewController!
var middleVc: UIViewController!
var bottomVc: UIViewController!
var scrollView: UIScrollView!
class func verticalScrollVcWith(middleVc: UIViewController,
topVc: UIViewController?=nil,
bottomVc: UIViewController?=nil) -> VerticalScrollViewController {
let middleScrollVc = VerticalScrollViewController()
middleScrollVc.topVc = topVc
middleScrollVc.middleVc = middleVc
middleScrollVc.bottomVc = bottomVc
return middleScrollVc
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view:
setupScrollView()
}
func setupScrollView() {
scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.bounces = false
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
scrollView.frame = CGRect(x: view.x, y: view.y, width: view.width, height: view.height)
self.view.addSubview(scrollView)
let scrollWidth: CGFloat = view.width
var scrollHeight: CGFloat
switch (topVc, bottomVc) {
case (nil, nil):
scrollHeight = view.height
middleVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
addChildViewController(middleVc)
scrollView.addSubview(middleVc.view)
middleVc.didMove(toParentViewController: self)
case (_?, nil):
scrollHeight = 2 * view.height
topVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
middleVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
addChildViewController(topVc)
addChildViewController(middleVc)
scrollView.addSubview(topVc.view)
scrollView.addSubview(middleVc.view)
topVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
scrollView.contentOffset.y = middleVc.view.frame.origin.y
case (nil, _?):
scrollHeight = 2 * view.height
middleVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
bottomVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
addChildViewController(middleVc)
addChildViewController(bottomVc)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(bottomVc.view)
middleVc.didMove(toParentViewController: self)
bottomVc.didMove(toParentViewController: self)
scrollView.contentOffset.y = 0
default:
scrollHeight = 3 * view.height
topVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
middleVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
bottomVc.view.frame = CGRect(x: 0, y: 2 * view.height, width: view.width, height: view.height)
addChildViewController(topVc)
addChildViewController(middleVc)
addChildViewController(bottomVc)
scrollView.addSubview(topVc.view)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(bottomVc.view)
topVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
bottomVc.didMove(toParentViewController: self)
scrollView.contentOffset.y = middleVc.view.frame.origin.y
}
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
}
// MARK: - SnapContainerViewControllerDelegate Methods
func outerScrollViewShouldScroll() -> Bool {
if scrollView.contentOffset.y < middleVc.view.frame.origin.y || scrollView.contentOffset.y > 2*middleVc.view.frame.origin.y {
return false
} else {
return true
}
}
}
In MiddleScrollViewController.swift, make the controller conform to UIScrollViewDelegate:
class VerticalScrollViewController: UIViewController,
SnapContainerViewControllerDelegate,
UIScrollViewDelegate {
In that class, in setupScrollView(), set the delegate:
func setupScrollView() {
scrollView = UIScrollView()
// set the delegate to self
scrollView.delegate = self
// the rest of the existing code...
Still in that class, implement didScroll (or whatever other delegate funcs you want to handle):
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Vertical Scroll - contentOffset:", scrollView.contentOffset)
}
Now you should get lots of print lines in the debug console when you scroll vertically:
Vertical Scroll - contentOffset: (0.0, 561.0)
Vertical Scroll - contentOffset: (0.0, 560.0)
Vertical Scroll - contentOffset: (0.0, 559.0)
Vertical Scroll - contentOffset: (0.0, 558.0)
If you want your SnapContainerViewController class to be informed when vertical scrolling takes place, you'll probably want to use a new protocol/delegate so VerticalScrollViewController can send that information.

How to use "*.scnp" file in SwiftUI for button click (iOS)?

I have "Explode.scnp" SceneKit file. It's already configured, the texture has been set.
How we can use it in SwiftUI?
For example, after the button clicks the background will be animated once.
var body: some View {
ZStack {
VStack {
Button(action: {
Particles()
}) {
Text("Animate")
}
}
}
}
This code works for scn. but how to use it with scnp?
import SwiftUI
import SceneKit
struct ScenekitView : UIViewRepresentable {
let scene = SCNScene(named: "art.scnassets/ship.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
}
#if DEBUG
struct ScenekitView_Previews : PreviewProvider {
static var previews: some View {
ScenekitView()
}
}
#endif
Moreover, Xcode version is 11.3.1.
When I try to create a new file I have this:
And the extension is SKS... any ideas?
Reworked the code according to #Asperi version:
import SwiftUI
import SceneKit
struct ScenekitView : UIViewRepresentable {
#Binding var exploding: Bool
//let scene = SCNScene(named: "SceneKit.scnassets/Explode.scnp")!
let scene = SCNScene(named: "SceneKit.scnassets/scene.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "blow", recursively: true)!
// animate the 3d object
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
if exploding {
if let scene = scene.rootNode.childNode(withName: "SceneKit.scnassets/scene", recursively: false),
let particles = SCNParticleSystem(named: "SceneKit.scnassets/Explode", inDirectory: nil) {
let node = SCNNode()
node.addParticleSystem(particles)
node.position = scene.position
scnView.scene?.rootNode.addChildNode(node)
scene.removeFromParentNode()
}
}
}
}
Errors related to the path names ...
// retrieve the ship node
here. Tried .sks, .scnp ... any ideas?
Here is modified code with demo. Tested with Xcode 11.4 / macOS 10.15.4
Note: as I don't know your project structure, all dependent resource files were added as Resources (not in Assets). Just in case.
struct DemoSceneKitParticles: View {
#State private var exploding = false
var body: some View {
VStack {
ScenekitView(exploding: $exploding)
Button("BOOM") { self.exploding = true }
}
}
}
struct ScenekitView : NSViewRepresentable {
#Binding var exploding: Bool
let scene = SCNScene(named: "ship.scn")!
func makeNSView(context: NSViewRepresentableContext<ScenekitView>) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateNSView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = NSColor.black
if exploding {
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true),
let particles = SCNParticleSystem(named: "Explosion", inDirectory: nil) {
let node = SCNNode()
node.addParticleSystem(particles)
node.position = ship.position
scnView.scene?.rootNode.addChildNode(node)
ship.removeFromParentNode()
}
}
}
}
Update: variant for iOS
Tested with Xcode 11.4 / iOS 13.4
Full module code (resource files as before at top level)
import SwiftUI
import SceneKit
struct DemoSceneKitParticles: View {
#State private var exploding = false
var body: some View {
VStack {
ScenekitView(exploding: $exploding)
Button("BOOM") { self.exploding = true }
}
}
}
struct ScenekitView : UIViewRepresentable {
#Binding var exploding: Bool
let scene = SCNScene(named: "ship.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
if exploding {
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true),
let particles = SCNParticleSystem(named: "Explosion", inDirectory: nil) {
let node = SCNNode()
node.addParticleSystem(particles)
node.position = ship.position
scnView.scene?.rootNode.addChildNode(node)
ship.removeFromParentNode()
}
}
}
}
struct DemeSKParticles_Previews: PreviewProvider {
static var previews: some View {
DemoSceneKitParticles()
}
}

How to transform multiple view to same location(CGPoint)

I've got multiple UIImageViews, spread across in a view.
#IBOutlet var leftIVs: [UIImageView]!
#IBOutlet var topIVs: [UIImageView]!
#IBOutlet var rightIVs: [UIImageView]!
I'm trying to create a function using UIView.animate... functions and 'transform' property which brings all of these UIImageViews at the center of the superview. I'm using the following code:
let performInitialTransformation: ((UIImageView)->()) = { (card) in
let cardCenter = CGPoint(x: card.frame.midX, y: card.frame.midY)
let viewCenter = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
let deltaPoint = cardCenter - viewCenter //also tried (viewCenter - cardCenter)
UIView.animate(withDuration: 0.5, animations: {
card.transform = CGAffineTransform.init(translationX: deltaPoint.x, y: deltaPoint.y)
}) { (done) in
}
}
for card in topIVs {
performInitialTransformation(card)
}
for card in leftIVs {
performInitialTransformation(card)
}
for card in rightIVs {
performInitialTransformation(card)
}
I'm using this static function:
extension CGPoint {
static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: rhs.x - lhs.x, y: rhs.y - lhs.y)
}
}
NOTE: Also, I will be bringing those images back to there original position afterward for which I will use CGAffineTransform.identity
The images are not being shown in the center. How can I achieve this? Thanks in advance!
I think your problem was that you didn't reduce the size of the image view so that they stack at the exact centre of the screen.
let deltaX = (self.view.center.x - card.frame.minX) - card.frame.width/2
let deltaY = (self.view.center.y - card.frame.minY) - card.frame.height/2
UIView.animate(withDuration: 1) {
card.transform = .init(translationX: deltaX, y: deltaY)
}
What is about to use center
UIView.animate(withDuration: 0.5) {
images.forEach { $0.center = superview.center }
}
The full example
var subviewes = [UIView]()
var view = UIView(frame: CGRect.init(x: 0, y: 0, width: 30, height: 30))
view.backgroundColor = .yellow
subviewes.append(view)
view = UIView(frame: CGRect.init(x: 0, y: 0, width: 20, height: 20))
view.backgroundColor = .green
subviewes.append(view)
view = UIView(frame: CGRect.init(x: 0, y: 0, width: 10, height: 10))
view.backgroundColor = .red
subviewes.append(view)
subviewes.forEach { self.view.addSubview($0) }
UIView.animate(withDuration: 0.5) {
subviewes.forEach { $0.center = self.view.center }
}

How to use ScnNode as a ScnLight?

var sun = SCNNode(geometry: SCNSphere(radius:0.35))
sun.geometry?.firstMaterial?.diffuse.contents=#imageLiteral(resourceName: "Sun")
sun.position=SCNVector3(0,0,-1)
And i want to use the sun SCNSphere as a omni light source.
let OmniLight = SCNLight()
OmniLight.type = SCNLight.LightType.omni
OmniLight.color = UIColor.white
But if i run this code, the sun is full black.
add it to the scene:
scene.rootNode.addChildNode(sun)
Here's example playground code:
import UIKit
import SceneKit
import PlaygroundSupport
// create a scene view with an empty scene
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 640, height: 480))
var scene = SCNScene()
sceneView.scene = scene
PlaygroundPage.current.liveView = sceneView
// default lighting
sceneView.autoenablesDefaultLighting = false
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// your code (minus the image)
var sun = SCNNode(geometry: SCNSphere(radius:0.35))
sun.geometry?.firstMaterial?.diffuse.contents = UIColor.orange
sun.position=SCNVector3(0,0,-1)
let OmniLight = SCNLight()
OmniLight.type = SCNLight.LightType.omni
OmniLight.color = UIColor.white
// add to scene
scene.rootNode.addChildNode(sun)

make vertical moving background

I'm beginner in Spritekit and I try to make background move vertically
I did know how
this what in the controller
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)
}
and this the scene
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 1...3{
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: 0)
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}
}
the problem is the background move horizontally and after it arrives to the end of screen disappear, then begins move from the beginning of the screen again
I want it work vertically without disappearing
The background move horizontally because declarations of moveBy (shiftBackground, replaceBackground) works on the x axe (horizontally). You need to work on the y axe (vertically).
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 0)
At the first line, the background move from it position to it position - it height. At the second line, it return to it first position (I think moveBy is relative).
_
The background move again because you use the function SKAction.repeatForever (movingAndReplacingBackground). I think you can remove it.
let movingAndReplacingBackground = SKAction.sequence([shiftBackground,replaceBackground])
Edit :
I forget the loop.
In the loop, you give to the background an initial position.
background.position = CGPoint(x: backgroundTexture.size().width/2 + (backgroundTexture.size().width * CGFloat(i)), y: self.frame.midY)
Try to replace it by 0, middle if you want an immediately visible background
background.position = CGPoint(x: 0, y: self.frame.midY)
Or -height, 0 if you want the background appear by the top of the screen. In this case you need to replace shiftBackground and replaceBackground
background.position = CGPoint(x: -backgroundTexture.size().height, y: 0)
and
let shiftBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: 0, duration: 0)
I'm not sure, but I think, if you want only one mouvement, you can remove th sequence :
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let scrollByTopBackground = SKAction.moveBy(x: O, y: backgroundTexture.size().height, duration: 5)
background = SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: -backgroundTexture.size().height, y: self.frame.midY)
background.size.height = self.frame.height
background.run(scrollByTopBackground)
self.addChild(background)
}
}
Edit to answer you, try this...
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 0...2 {
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: self.frame.midY + (backgroundTexture.size().height * CGFloat(i)))
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}

Resources