SceneKit crash after dismissing VC - ios

Project: https://github.com/cgranthovey/SCNViewTest
The app has 2 VCs.
When I segue to the 2nd VC I set up the SCNScene with the code below.
override func viewDidLoad() {
super.viewDidLoad()
mySCNView.autoenablesDefaultLighting = true
mySCNView.allowsCameraControl = true
mySCNView.backgroundColor = UIColor.black
}
override func viewWillAppear(_ animated: Bool) {
let pyramid = SCNNode()
pyramid.geometry = SCNPyramid(width: 1, height: 2, length: 1)
pyramid.geometry?.firstMaterial?.diffuse.contents = UIColor.purple
pyramid.geometry?.firstMaterial?.diffuse.contents = UIColor.white
pyramid.position = SCNVector3(0, 0, 0)
pyramid.eulerAngles = SCNVector3(x: 0, y: 10, z: 180 * Float.pi)
let shapeScene = SCNScene()
shapeScene.rootNode.addChildNode(pyramid)
mySCNView.scene = shapeScene
}
If I then flick the image and have it spin, and while it's spinning I press the back button
#IBAction func backBtnPress(_ sender: AnyObject){
self.navigationController?.popViewController(animated: true)
}
I get a crash.
Thread 1: EXC_BAD_ACCESS (code=1, address=0x101f2210)
However if I wait for the spinning to stop and then press the back button there is no crash. Any ideas what's going on or how to fix this?

Per my suggestion, after you expanded the stack trace you see an interesting piece of information. Specifically, the code causing the crash seems related to the inertia system of the SCNView's camera controller.
Attempting to stop the inertia before exiting the scene's view controller appears to mitigate the issue in my brief testing. In my experience with iOS 11, this crash only ever happened on the simulator for me, but I was using modal presentations. I think this is likely a bug in SCNCameraController.
#IBAction func backBtnPress(_ sender: AnyObject){
chariotView.defaultCameraController.stopInertia()
self.navigationController?.popViewController(animated: true)
}

Related

How do I start and stop Confetti View on a single UIbutton?

I am a beginner in Xcode. I am trying to make an app which displays confetti on a button click. However, when I press the button, the confetti is displayed but it never stops. It blocks the buttons and the view. I am using a Cocoapod - SAConfettiView.
This code in on Xcode 10. I believe that the Confetti blocks the main view. I use playConfetti as IBaction function.
#IBAction func playConfetti(_ sender: UIButton, forEvent event: SAConfettiView) {
let confettiView = SAConfettiView(frame: self.view.bounds)
confettiView.intensity = 1
confettiView.type = .Star
confettiView.startConfetti()
view.addSubview(confettiView)
}
Do I have to use the If-Else statement?
Thank you!
Updated Answer
To stop the SAConfettiView call the function,
confettiView.stopConfetti()
Change your Button Action code to the following:
let confettiView = SAConfettiView()
#IBAction func startClicked(_ sender: Any)
if confettiView.isActive() {
confettiView.stopConfetti()
confettiView.removeFromSuperview()
}
else{
confettiView = SAConfettiView(frame: self.view.bounds)
confettiView.intensity = 1
confettiView.type = .Star
confettiView.startConfetti()
view.addSubview(confettiView)
self.view.bringSubviewToFront(sender as! UIButton)
}
}

NVActivityIndicatorView not working on button press

I have the following code for a button in my application to test my implementation of NVActivityIndicatorView:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
sleep(2)
self.stopAnimatingActivityIndicator()
}
The view controllers in my application also have this extension:
extension UIViewController: NVActivityIndicatorViewable {
func startAnimatingActivityIndicator() {
let width = self.view.bounds.width / 3
let height = width
let size = CGSize(width: width, height: height)
startAnimating(size, message: "Loading...", type: NVActivityIndicatorType.circleStrokeSpin)
}
func stopAnimatingActivityIndicator() {
self.stopAnimating()
}
}
The loading animations work elsewhere in the same view controller (i.e., the viewDidLoad() function) but for some reason I'm unable to get the loading animation to work on this button. The button is connected correctly as the application does sleep for the appropriate amount of time, but the loading animations fail to run.
Thanks in advance for the help!
The Indicator won't spin because the main thread is asleep. Use a 2 second timer to turn off the spinning instead.
#FryAnEgg coming in with the solution! The sleep(2) was preventing the loading animations from running.
Here's my updated code:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.stopAnimatingActivityIndicator()
}
}

Passing data between SpriteKit scenes (Swift)

I have been following an old Ray Wenderlich tutorial: https://www.raywenderlich.com/1514-introduction-to-the-sprite-kit-scene-editor and have encountered a problem. I have run up against this problem numerous times and even found a "solution" on the stackoverflow site: Passing Data Between Scenes (SpriteKit)
This is the same code as the tutorial (with a few tweaks as it was written in Swift 2). I feel it a very simply elegant solution and not full of code using NSdefaults etc.
This is the call to the new scene, please note the soundToPlay object:
func gameOver(didWin: Bool) {
let menuScene = MenuScene(size: self.size)
menuScene.scaleMode = .aspectFill
menuScene.soundToPlay = didWin ? "fear_win.mp3" : "fear_lose.mp3"
let transition = SKTransition.flipVertical(withDuration: 1.0)
self.view?.presentScene(menuScene, transition: transition)
}
And this is the scene I am calling:
import SpriteKit
class MenuScene: SKScene {
var soundToPlay: String!
override func sceneDidLoad() {
self.backgroundColor = SKColor(red: 0, green:0, blue:0, alpha: 1)
// Setup label
let label = SKLabelNode(fontNamed: "AvenirNext-Bold")
label.text = "Press anywhere to play again!"
label.fontSize = 55
label.horizontalAlignmentMode = .center
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addChild(label)
// Play sound
if let soundToPlay = soundToPlay {
run(SKAction.playSoundFileNamed(soundToPlay, waitForCompletion: false))
}
}
The data is never passed and the object has a value of nil
I suspect it's something that has changed in Swift as the tutorial is very old as is the suggested "solution" on the previous stackoverflow question.
If that is the case, what is now the best way to achieve this?
The problem with your sound is that you are setting the variable after you call sceneDidLoad (this happens during init), so the sound will not play.
To fix this, do this code in your didMove method:
override func didMove(to view: SKView) {
guard let soundToPlay = soundToPlay else {fatalError("Unable to find soundToPlay")}
run(SKAction.playSoundFileNamed(soundToPlay, waitForCompletion: false))
}
The actual issue to your crashing is not visible here.
SKTransitions are bugged when you use SKLightNode, so by calling a scene with a transition, it is going to crash. SKTransitions have always been bugged with the introduction of Metal, so the only thing you can do is report it to apple and pray they do not tell you it is intentional.
As of now, keep track of all your light sources, and when you transition, set the isEnabled property to false.
You may need to create a screen shot of your scene prior to disabling the light source to not have it look silly.
What you are doing is, as you reload the scene (let menuScene = MenuScene(size: self.size)) you are resetting all the variables that were defined.
What you could do is use UserDefaults:
func gameOver(didWin: Bool) {
let menuScene = MenuScene(size: self.size)
menuScene.scaleMode = .aspectFill
let soundToPlay = didWin ? "fear_win.mp3" : "fear_lose.mp3"
UserDefaults.standard.set(soundToPlay, forKey: "soundToPlay")
let transition = SKTransition.flipVertical(withDuration: 1.0)
self.view?.presentScene(menuScene, transition: transition)
}
And:
import SpriteKit
class MenuScene: SKScene {
var soundToPlay: String!
override func sceneDidLoad() {
self.backgroundColor = SKColor(red: 0, green:0, blue:0, alpha: 1)
// Setup label
let label = SKLabelNode(fontNamed: "AvenirNext-Bold")
label.text = "Press anywhere to play again!"
label.fontSize = 55
label.horizontalAlignmentMode = .center
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addChild(label)
// Play sound
if let soundToPlay = UserDefaults.standard.string(forKey: "soundToPlay") {
run(SKAction.playSoundFileNamed(soundToPlay, waitForCompletion: false))
}
}
Then, put this in your AppDelegate's applicationWillTerminate function:
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
UserDefaults.standard.removeObject(forKey: "soundToPlay")
}
If you do not put that in the AppDelegate then soundToPlay will remain after closing the app altogether.
I don't know if UserDefaults is the best way to do this, but I've struggled with this when I was making my iOS app and I just did this. So if anyone else has a better answer, please notify me as well!

UIMenuController.setMenuVisible(_:animated) show the menu but it immediately disappears only after the first time I launch the app

I am trying to show a menu. Right now I am having a problem where the menu shows up and disappears automatically as soon as is show up. This only happens the first time the code runs after I launch my app. After that it shows up and stays open as expected.
self.view.becomeFirstResponder()
let menu = UIMenuController.shared
menu.menuItems = actions // actions is initialized above
menu.setTargetRect(CGRect(x: 300, y: 300, width: 0, height: 0), in: self.view)
menu.setMenuVisible(true, animated: true)
I was able to get around this problem by putting this code in my viewDidLoad()
override func viewDidLoad() {
// other stuff ...
UIMenuController.shared.setMenuVisible(true, animated: false)
}
Because there are no actions and I'm not setting the target rect, it doesn't show anything, but it bipasses the bug that only occurs the first time I call this function.
Does anybody know a more kosher way solve this?

iOS Swift MapKit will not reload

I have a MapKit View on my storyboard - set to hidden. With the following code I can unhide it from view using a button(1) click and it loads perfectly. Another button(2) click will hide the map. The issue is reloading the map. How do I reload the map with button(1). Currently, the map will not show again.
Here is the entire code to load the map:
#IBAction func ButtonOne(sender: UIButton) {
self.myMap()
}//End Button(1)
#IBAction func ButtonTwo(sender: UIButton) {
self.mapView.hidden = true
}//End Button(2)
func myMap() {
let userLocation = self.mapView.userLocation
let region = MKCoordinateRegionMakeWithDistance(
userLocation.location.coordinate, 2000, 2000)
self.mapView.hidden = false
self.mapView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height * 0.75)
self.mapView.setRegion(region, animated: true)
}
I am just going to use 'exit(1)' when disconnecting/hiding then map when the call ends. BTT the user will really have no use for still being able to use the app.

Resources