Adding Sprite to scenes causing EXC_BAD_ACCESS - ios

So I couldn't find a good answer that works for my problem. I am creating a SpriteKit game on iOS. Im just going to give you guys a quick overview of my setup and then go over the problem. I think that's the best way to explain this.
The Setup:
Basically in my didMove(to view:) method I call two helper functions: setupNodes() and setupLevel(). What these functions do is load the different scenes and SKSpriteNodes into memory and then add them to the scene. Here is the code I use to do this:
func setupNodes(){
doubleVent = (loadForegroundOverlayTemplate("DoubleVent").copy() as! SKSpriteNode)
}
func loadForegroundOverlayTemplate(_ fileName: String) -> SKSpriteNode{
let overlayScene = SKScene(fileNamed: fileName)!
let overlayTemplate = overlayScene.childNode(withName: "ForegroundOverlay")!.copy() as! SKSpriteNode
return overlayTemplate
}
This now stores the scene which is a child of an SKNode called "ForegroundOverlay". I then pass this to a function called "createForegroundOverlay()" like so:
let overlaySprite = doubleVent
createForegroundOverlay(doubleVent)
and then "createForegroundOverlay" adds the SKSpriteNode stored in doubleVent to the scene like so:
func createForegroundOverlay(_ overlayTemplate: SKSpriteNode) {
let foregroundOverlay = overlayTemplate.copy() as? SKSpriteNode
lastOverlayPosition.x = lastOverlayPosition.x + (lastOverlayWidth + ((foregroundOverlay?.size.width)!)/2)
lastOverlayWidth = (foregroundOverlay?.size.width)!/2.0
foregroundOverlay?.position = lastOverlayPosition
//TESTING PURPOSES
if TESTING_NODE_POS{
print("The last overlay position x:\(lastOverlayPosition.x), y:\(lastOverlayPosition.y)")
print("The last overlay width is \(lastOverlayWidth)")
print("Adding a foreground overlay \(count)")
count += 1
}
//TESTING PURPOSES
if TESTING_BOX_OUTLINE {
let foregroundPos = foregroundOverlay?.position
let boxLocation = foregroundOverlay?.childNode(withName: "Box1")?.position
if boxLocation != nil {
print("The location of the box \(String(describing: boxLocation))")
print("The foregroundLocation is \(String(describing: foregroundPos))")
print("last overlay position is \(lastOverlayPosition)")
}
}
//add the foreground overlay as a child of the foreground node
fgNode.addChild(foregroundOverlay!)
}
the variables for positioning - lastOverlayPosition, lastOverlayWidth etc.. - are just properties of my GameScene class used to know where and when to add the overlay that was passed.
The fgNode is a node that I stored from my GameScene.sks file like so:
let worldNode = self.childNode(withName: "World")!
bgNode = worldNode.childNode(withName: "Background")!
bgCityNode = worldNode.childNode(withName: "BackgroundCity")
cityBGOverlayTemplate = (bgCityNode.childNode(withName: "Overlay")!.copy() as! SKNode)
cityOverlayWidth = bgCityNode.calculateAccumulatedFrame().width
backgroundOverlayTemplate = (bgNode.childNode(withName: "Overlay")!.copy() as! SKNode)
backgroundOverlayWidth = backgroundOverlayTemplate.calculateAccumulatedFrame().width
fgNode = worldNode.childNode(withName: "Foreground")!
This was also done in my setupNodes() method.
The problem:
So maybe some of you guys have already seen the problem, but the problem is that when I launch my game it crashes and I get the message:
"Thread 1: EXC_BAD_ACCESS: (code = 1, address = 0x78)"
It is the exact same message every single time a crash occurs. I think I understand what the error is saying. Basically there is a pointer pointing to some location in memory (0x78) that has nothing there. So dereferencing that pointer obviously causes a fault. Here is where I get confused... This only happens about 50% of the time. when I run and build the project it builds successfully every time and then crashes 50% of the time with that error message. Secondly, this occurs at the very beginning. This is odd to me because how can some memory already be freed resulting in a bad pointer at the very beginning of the game. Also if the crash doesn't occur at launch then a crash never occurs except for when I reload the scene after the game over scene is displayed, which is basically the same as the game being relaunched.
With some time I narrowed the problem to one line of code:
fgNode.addChild(foregroundOverlay!)
if I comment this line out, no crash ever occurs (I tried building and running 50 times). The problem must be with the foregroundOverlay variable, which was setup using the code in the setup section of this discussion. So I have no idea how to fix this... Any Ideas??
P.S. it might also be worth noting that adding a child to the scene in this way only noticeably became a problem when I upgraded to xCode 10.0 and that I have considered using zombies, but I don't think that would help since the crash only happens at the launch of the game.
If I was unclear anywhere please let me know.

Related

Reset ARSCNView background contents to its original source

I'm capturing ARFrame's and applying filters, which works fine, but I want to turn off the filters and go back to the original camera feed, but I'm running into issues. This code is applying the filter:
func session(_ session: ARSession, didUpdate frame: ARFrame) {
let filterImage = setFilter(session.currentFrame)
sceneView.scene.background.contents = context.createCGImage(filterImage, from: filterImage.extent)
}
Once I set sceneView.scene.background.contents, I cannot set it back to the original source. The original source is an object called: SCNCaptureDeviceOutputConsumerSource, which is not in the documentation. I tried saving that object and setting the background contents with it again, but it will just display the last frame it was holding (so there will be a still image). It does not continuously update. I don't know how to make sceneView.scene.background.contents extract data from the same source as it was before I replaced it.
I tried setting sceneView.session.delegate = nil, but that did not work, it just stops updating, and the screen looks like it freezes.
Is there a way to reset ARSCNView background contents to the original source it was getting data from?
If I reload the ARSCNView, it works, but it takes at least a second and a half to reload:
sceneView.session.delegate = nil
sceneView = nil
sceneView = ARSCNView(frame: view.bounds)
view.addSubview(sceneView)
sceneView.delegate = self
sceneView.session.run(ARFaceTrackingConfiguration())
Thank you for any help you can give.
I'm new to Stackoverflow posting, If the quality of this answer isn't that high, that is the reason for it. I had the same problem as you. Here's how I fixed it.
First set the sceneView.scene.background.contents as a variable.
Then you can change the sceneView.scene.background.contents to for example UIColor.lightGray.
If you want to reset it back to its original Source, just do as follows.
sceneView.scene.background.contents = originalSource
Just make sure to not change the variable originalSource, because then you are deleting your originalSource.
Hope this helped,
Best Regards

addPeriodicTimeObserver keeps AVPlayer instances

After being all over Stack Overflow and the deepest corners of the internet, I'm yet to find the solution. I hope someone out there will be able help me out with a problem I've had for days now.
The app in question has a collection view with lots of items. When you click on it you get to a preview collection view. And finally (where I need help) when you click "GO" you come to a collection view with cells that fills out the entire screen. It consists of an AVPlayerLayer and AVPlayer. Each time you scroll to right or left you see another video. The following code works:
UICollectionViewCell
class PageCell: UICollectionViewCell {
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var playerItem: AVPlayerItem?
var videoAsset: AVAsset?
var indexPath: IndexPath?
var page: Page? {
didSet {
guard let unwrappedPage = page, let unwrappedIndexPath = indexPath else { return }
addingVideo(videoID: unwrappedPage.steps[unwrappedIndexPath.item].video)
}
}
func setupPlayerView() {
player = AVPlayer()
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = .resizeAspectFill
videoView.layer.addSublayer(playerLayer!)
layoutIfNeeded()
playerLayer?.frame = videoView.bounds
}
func addingVideo(videoID: String) {
setupPlayerView()
guard let url = URL(string: videoID) else { return }
videoAsset = AVAsset(url: url)
activityIndicatorView.startAnimating()
videoAsset?.loadValuesAsynchronously(forKeys: ["duration"]) {
guard self.videoAsset?.statusOfValue(forKey: "duration", error: nil) == .loaded else { return }
self.player?.play()
self.playerItem = AVPlayerItem(asset: self.videoAsset!)
self.player?.replaceCurrentItem(with: self.playerItem)
}
}
In the UICollectionViewController I'm reusing cells. Since there is no way to know the number of AVPlayer instances, I simply made a helper variable inside the PageCell and it seems like three cells are getting reused (the normal amount when dequeuing and reusing cells). Now when I close this UICollectionViewController the AVPlayer instances seems to disappear/close.
Now, the problem arises when I want to loop the videos. Using AVPlayerLooper is not an option because it simply is too laggy (I've implemented it in a dusin different ways without luck). So my solution was to use a period time observer inside the videoAsset?.loadValuesAsynchronously block:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = self.player?.currentItem?.duration{
if progressTime == totalDuration {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
}
})
Problem
Video showcasing problem: Problem video
The problem arises when having +17 AVPlayer instances running simultaneously then suddenly the videos won't load anymore. iOS devices have a hardware limitation of everything from 4 to 18 AVPlayer instances running at the same time (most likely depending on RAM), see the following Stack Overflow posts just to mention a few:
AVPlayerItemStatusFailed error when too many AVPlayer instances created
How many AVPlayers are allowed to be created at the same time?
AVPlayer not able to play after playing some items
Loading issues with AVPlayerItem from local URL
See also these articles for more insight:
Building native video Pins
Too Many AVPlayers?
Because the problem only occurs when adding the time observer I suspect that it keeps the AVPlayer instances "alive" even though I've closed the collection view.
Notes to Problem video: Every time I press "GO" one AVPlayer instance is created. When I swipe to the right two more cells are created hence two more AVPlayer instances. All in all 3 AVPlayer instances are created each time accumulating in the end to about 17 instances and the videos will not load anymore. I've tried scrolling through all the videos each time I've pressed "GO" but this does not change the outcome with a maximum of three cells being reused all the time.
What I've tried to solve the problem
Try 1:
I made playerLayer a global variable and in viewDidDisappear inside the UICollectionViewController I added:
playerLayer?.player = nil
This resulted in one AVPlayer instance to disappear/close when I closed the cells onto the "GO" page (i.e. the view disappeared). This meant that I hit 17 instances a bit later than when I didn't add this code. Together with the code above I also tried adding playerLayer = nil, playerLayer?.player?.replaceCurrentItem(with: nil) and playerLayer?.removeFromSuperlayer() but the only thing that changed anything was playerLayer?.player = nil. It should be noted that if I do not scroll and simply open the first video and close, open the first video and close and so on, I could do it "forever" without any problems. So, in this case one instance was created and then closed/disappeared afterwards.
Video showcasing try 1: Try 1 video
Try 2:
I changed the addPeriodicTimeObserver block to:
self.timeObserver = playerLayer?.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale:
2), queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = playerLayer?.player?.currentItem?.duration{
if progressTime == totalDuration {
playerLayer?.player?.seek(to: kCMTimeZero)
playerLayer?.player?.play()
}
}
})
In this block I essentially changed all self.player? to playerLayer?.player?.
This made me able to open and close the view with the videos as much as I wanted - so the AVPlayer instances were somehow closing/disappearing. Though now the looping didn't work. The first video would loop initially. But then I swiped to the second cell and this video would not loop. Then I swiped back to the first cell and now this wouldn't loop either. If I added playerLayer?.player = nil like in "Try 1" no effect occurred with neither the open and close nor the loops.
Video showcasing try 2: Try 2 video
Try 3:
I made the timeObserver variable global and tried many many things. But when I tried to remove the observer(s) it always resulted in the following error:
'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'
The error indicates that the time observers are clearly all over the place. This was clear when I added helper variables (counters) inside the addPeriodicTimeObserver block and found out that the time observers were quickly adding up. At one point with all the different combinations of the code presented in this post, I was able to remove the time observer at any time during the scrolling. But this resulted in only removing a single time observer out of many - resulting in the same situation as in "Try 1" where I was able to remove a single AVPlayer instance each time I closed the view.
General note:
To test whether or not it really was only three AVPlayer instances that were created each time I press "GO" and swiped I made a test where I scrolled through over 20 videos, but there was no problem loading them. Therefore I'm quite sure, as mentioned earlier, that a maximum of three AVPlayer instances are created in the view each time.
Conclusion
The problem is, as I see it, that when I add the time observers in order to loop the videos they accumulate and keep the AVPlayer instances "alive". Remember that I had no problem at all without the time observers. When applying playerLayer?.player = nil it seems as though, I was able to "save" an instance but then again it's so hard to tell when I don't know the amount of AVPlayer instances currently "active". Simply put, all I wanna do is to delete all AVPlayer instances the moment the view disappears and it will be happy days. If anyone got this far, I will be overly grateful if you are able to help me out.
There is a retain cycle. Use weak self in escaping closures. The retain cycle you created was:
cell -> Player -> observer closure -> cell
Try this:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { [weak self] (progressTime) in
if let totalDuration = self?.player?.currentItem?.duration{
if progressTime == totalDuration {
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
}
})

Vuforia iOS example project can't show AR model after replace .dat and .xml image target

I got Vuforia official example project and someone else's Vuforia Swift project. I have to do a lot of configuration to make both projects finish compile. (something like vuforiaLicenseKey and vuforiaDataSetFile)After that, I found the AR model doesn't show on the demo image target after I replace the demo database by my database.
I have updated source code to do so. When I debug, I can see my image target has been detected. And, both official example and other one's example start draw OpenGL 3D model or draw Scene kit node. Problem is I can't see them on screen. But if I configure example database back, I can see the AR view working.
The below code is the swift project example from yshrkt/VuforiaSampleSwift . It is working, and the condition of trackerableName == "coaster" is true. It means the app detected the coaster image.
func vuforiaManager(_ manager: VuforiaManager!, didUpdateWith state: VuforiaState!) {
for index in 0 ..< state.numberOfTrackableResults {
let result = state.trackableResult(at: index)
let trackerableName = result?.trackable.name
//print("\(trackerableName)")
if trackerableName == "coaster" {
boxMaterial.diffuse.contents = UIColor.yellow
if lastSceneName != "coaster" {
manager.eaglView.setNeedsChangeSceneWithUserInfo(["scene" : "coaster"])
lastSceneName = "coaster "
}
} else {
print("\(trackerableName?.description)")
boxMaterial.diffuse.contents = UIColor.blue
if lastSceneName != "chips" {
manager.eaglView.setNeedsChangeSceneWithUserInfo(["scene" : "chips"])
lastSceneName = "chips"
}
}
}
}
And, it invoked the below fun. Everything looks right.
(void)setNeedsChangeSceneWithUserInfo: (NSDictionary*)userInfo {
SCNScene* scene = [self.sceneSource sceneForEAGLView:self userInfo:userInfo];
if (scene == nil) {
return;
}
SCNCamera* camera = [SCNCamera camera];
_cameraNode = [SCNNode node];
_cameraNode.camera = camera;
_cameraNode.camera.projectionTransform = _projectionTransform;
[scene.rootNode addChildNode:_cameraNode];
_renderer.scene = scene;
_renderer.pointOfView = _cameraNode;
}
Same project, before I update my image target database, it works fine, detect image target then draw model on AR view. But, after I replace database it only can detect image target, it can't draw model on AR view anymore.
Two possible things that may explain this - if your target size is wrong or if there's something wrong in the 'coaster' scene. You should first try to use the 'coaster' scene with the 'chips' target. If you can see the scene, the problem is probably with your target size. If it doesn't work, try using you new target with the 'chips' scene.
This should help you isolate the problem, and perhaps bring more details here.

MKMapSnapshotter memory crash - multiple calls in a UITableView

We have an iOS app that has a UITableView containing map snapshots that are generated when the cells appear. The sample list we are using is simply showing the map snapshots based on a lat/long provided in a Model class. I started noticing memory crashes, so I reduced the code to its bare minimum. The crashes still occur, when we are only doing the snapshot with nothing even being done to the results. See below for the code, which is contained in our custom cell and called via the cellForItemAtIndexPath method:
private func testMapSnapshot(viewModel: StreamViewModel)
{
let latDelta:CLLocationDegrees = 0.005
let lonDelta:CLLocationDegrees = 0.005
let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
let location:CLLocationCoordinate2D = CLLocationCoordinate2DMake(viewModel.coordinate.latitude, viewModel.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(location, span)
let options = MKMapSnapshotOptions()
options.region = region
options.size = mapImageView.frame.size
options.scale = UIScreen.mainScreen().scale
viewModel.mapSnapshotter = MKMapSnapshotter(options: options)
viewModel.mapSnapshotter!.startWithCompletionHandler() { snapshot, error in
// do nothing
}
}
In didEndDisplayingCell, I am making sure to cancel the mapSnapshotter. See for reference (we keep the list of models in our main VC class that contains the tableview):
func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath)
let model = viewModel?[indexPath.item] {
model.mapSnapshotter?.cancel()
model.mapSnapshotter = nil
}
}
Note that before doing this last step, it was crashing way earlier. But now, if you start scrolling down the list fast, it starts stuttering and won't stop stuttering. If you go up and down a list of about 150 rows, it'll take less than 30 seconds before we start seeing memory warnings and then a crash.
I ran this through Instruments, but it wasn't very helpful. It looks as if the Heap and Anonymous VM allocations are gradually going up, possibly causing the crash. See for reference:
I saw this post out there:
MKMapSnapshotter uses incredible amounts of CPU & RAM But it's unanswered and doesn't really address why the memory wouldn't be released.
Any thoughts on where to go on this? Thank you in advance, and please let me know if I can provide more info.
Although I was not able to find a fix to this particular issue, I was able to resolve it by calling the map snapshotter only when the scrollView stops - it then grabs all the visible cells and loads only for those. That way it minimizes the number of calls to this API and prevents memory issues, as opposed to it constantly calling it as you scroll down a list via the cellForRow method.

AVAudioPlayer Error in Swift?

So I'm trying to play a random audio track from an array when a certain view loads. Everything's fine, but when I go to load that view, I get an error. Here is the class code of the View Controller:
class SoundGameViewController: UIViewController {
var levelOneSounds : [NSURL]!
var levelOneAudioPlayer: AVAudioPlayer! = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var error : NSError?
levelOneSounds = NSBundle.mainBundle().URLsForResourcesWithExtension("mp3", subdirectory: "Level 1") as! [NSURL]
var randomSound = levelOneSounds[Int(arc4random_uniform(UInt32(levelOneSounds.count)))]
levelOneAudioPlayer = AVAudioPlayer(contentsOfURL: randomSound, error: &error)
if error != nil { println(error!) }
levelOneAudioPlayer.play()
}
The error shows up on the last line, levelOneAudioPlayer.play(). This is the error:
Thread 1: EXC_BAD_INSTRUCTION . . . (code = exc_1386_INVOP, subcode = 0 x 0
However, with one of the sounds, it actually ends up working and plays the sound when the view loads. But with all the other sounds, the view doesn't load at all and I get the error. Sometimes, the view does load but I get no sound and no error. Here is an image of the folders I hold all of the sounds in: http://i.stack.imgur.com/JBdGU.png
I have tried numerous times asking questions on Stack Overflow and research about it, but I just can't seem to get it. I have also looked for many tutorials on debugging, but I guess I'm not experienced enough to really understand what I need to do in this situation. I'm 13, and I don't have Apple Developer for much longer, and this is becoming really bothersome. I'll go as far as sending someone the project if they can fix this error.
I'm new to programming, so I'm sorry if I made an obvious mistake. Thanks!

Resources