How to run two separate ARKit experiences in one iOS app - swift

I have a problem with running two ARKit experiences in one app. I have main menu View Controller which runs either of the experiences. It looks something like that:
MainVC - main UIViewController with two buttons to choose the experience
ARVC1 - first experience UIViewController
ARVC2 - second experience UIViewController
Both experiences are independent and they don't need to pass any data between each other.
Unfortunately when I dismiss ARVC1 or ARVC2 and run the second one, somehow I have traces of the previous ARSession (still frame from previous session is flashing once for a while). Can I reset completely ARSession somehow? It seems that it stays somewhere in the background. I've tried to pause the session when I dismiss any of the View Controllers but it didn't help.
The same happens if I open ARVC1 and press the button to show statistics:
arView.debugOptions = [.showStatistics]
After dismissing ARVC1 and opening it again it still shows statistics so it's running somewhere in the background.

I found the solution to my problem:
After opening and closing ARVC1 and quickly opening ARVC2 I was experiencing flickering of the previous ARSession in current ARSession. To solve this I create ARView in my MainVC and I pass the reference to it to destination controller, where I set up the arView constraints programmatically. To avoid passing the ARAnchors I reset configuration with options in viewDidLoad:
arView.session.run(configuration, options: [.resetTracking,.removeExistingAnchors,.stopTrackedRaycasts])
It helped as well with UINavigationController transition (made it smoother) because ARSession is configured before the View is presented and it eliminates the black flash that occurs at the moment when you run new ARConfiguration with ARSession.
It also eliminates the console error (below) that I was experiencing while quickly closing and opening ViewController containing ARView which sometimes was crashing the app:
[Session] Session (0x160879b40): did fail with error: Error
Domain=com.apple.arkit.error Code=102 "Required sensor failed."
UserInfo={NSLocalizedFailureReason=A sensor failed to deliver the
required input., NSUnderlyingError=0x283f959b0 {Error
Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be
completed" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-12780), NSLocalizedDescription=The operation could not be
completed, NSUnderlyingError=0x283efc8a0 {Error
Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}}},
NSLocalizedRecoverySuggestion=Make sure that the application has the
required privacy settings., NSLocalizedDescription=Required sensor
failed.} 2020-01-09 16:20:38.600835+0000 App[573:231750]
[Technique] World tracking performance is being affected by resource
constraints [3] 2020-01-09 16:20:38.600977+0000
App[573:231750] [Technique] VIO error callback: 8506.590769,
3, Frame time stamps are either out of order or repeating

Related

WKInterfaceInlineMovie Starts Playing Even if setAutoplays(_:) is Set to false

I have an WKInterfaceInlineMovie within an WKInterfaceController. The URL of the video is set at some point after the video file is downloaded. Playing works fine besides this problem which is a different story, I think.
Here's the problem. If I keep the screen open, lower my wrist and then raise it again, I can see the same screen and the video starts playing automatically.
It looks very weird and unexpected especially because I have some custom UI (video progress indicator, animated Play/Pause buttons) which is triggered when I manually start the video but it obviously doesn't react on this unwanted automatic video start. If I close the extension with the Crown button, next time I open the app, it again shows the screen with video and start playing automatically. I can even not using the extension for a while and receive a user notification later – while the custom notification UI is displayed, I can hear the video starts playing somewhere below for a short period of time.
When it happens I always receive two messages in console:
<<<< PlayerRemoteXPC >>>> remoteXPCPlaybackItem_NotificationFilter: [0x128b86e0] I/NQB.01 Received kFigPlaybackItemNotification_FirstVideoFrameEnqueued
<<<< PlayerRemoteXPC >>>> remoteXPCItem_handleFirstFrameNotificationLatch: [0x128b86e0] I/NQB.01 Posting kFigPlaybackItemNotification_FirstVideoFrameEnqueued
I have the Autoplay checkmark unchecked in storyboard. I also tried to set setAutoplays(_:) to false programmatically when the outlet is initialized, or later when a URL is set to the movie. All of this makes no difference.
The behavior is the same no matter was the video playing or not when the screen get deactivated. I tried to call pause() on willDisappear() and didDeactivate() – it also doesn't do any difference.
I even tried to call pause() on didAppear() and willActivate() – didn't help either. Curiously enough, these methods are not called when I lower the wrist and raise it again (however, willDisappear() and didDeactivate() are both called.) But perhaps, it's a different story, too.

White Screen appears at launch when a sound file played SKAction

I am trying to get a background sound file to play in the GameScene.swift file. But whenever I do and call the run.SKAction, the screen turns white and I hear a lot of static until it crashes. When I comment out the run.SKAction the game starts normally. Ive tried changing the formats of the file to WAV, MP3, AAC, CAF. But the same thing happens. I checked to see if I mispelled anything but I did not.
It gives me a message saying: "Message from debugger: Terminated due to memory issue"
IMPORTANT NOTE: the "waitForCompletion is set to TRUE it seems to work fine with a little static at the beginning of the soundtrack then it plays normally. but when "waitForCompletion" is set to FALSE I get the white screen. I Also Sometimes get a message saying:
SKAction: Error loading sound resource: "Entry.m4a"
I tried several different soundtrack files but it always happens.
Here is the beginning of the GameScene():
class GameScene: SKScene {
var SpaceShip2 = SKSpriteNode(imageNamed:"IntroSpaceShip")
var GameSceneSound = SKAction.playSoundFileNamed("BackgroundSound.wav", waitForCompletion: false)
}
Here is the function I used to call the sound:
func playsound(soundVariable: SKAction){
run(SKAction.repeatForever(soundVariable))
}
And here is when I call it:
override func didMove(to view: SKView) {
playsound(soundVariable: GameSceneSound)
}
Can someone please help me!
Thank you in advance!
Since waitForCompletion is set to false, the action is considered to be have been completed immediately when run. Since this is a repeatForever action, the next repetition of the action runs before the the first action of playing the sound has completed. The 2nd repetition runs, immediately causing the 3rd repetition, which causes the 4th one, etc. Eventually you fill up too much memory with all the SKActions and the app is terminated by iOS.
I would suggest having waitForCompletion set to true. That way the 2nd repetition of the action won't start until the 1st action has completed playing the sound.
Hope this helps!

How to put Apple Watch into waterproof mode?

I've got a swimming workout set up.
let configuration = HKWorkoutConfiguration()
configuration.activityType = .swimming
configuration.locationType = .outdoor
do {
workoutSession = try HKWorkoutSession(configuration: configuration)
workoutSession!.delegate = self
healthStore.start(workoutSession!)
} catch let error as NSError {
// Perform proper error handling here...
fatalError("*** Unable to create the workout session: \(error.localizedDescription) ***")
}
But my client is saying that the watch doesn't go into waterproof mode unless he manually makes it.
For context, "waterproof lock" is a mode that the Watch Series 2 is supposed to enter into when starting a swimming workout. From an Apple Support article:
When you start a swimming workout, your Apple Watch automatically locks the screen with Water Lock to avoid accidental taps. When you're done, turn the Digital Crown to unlock the screen and clear any water from your Apple Watch. You hear sounds and may feel some water on your wrist.
This functionality does not appear to be working with my app.
I only have a Series 0 to test on, so I can't confirm what's happening with my code with regards to the waterproof lock.
Am I missing something to make the workout force the watch into waterproof mode?
Available since watchOS 4:
https://developer.apple.com/documentation/watchkit/wkextension/2868458-enablewaterlock
// Only an application which is in an active workout or location session and is foreground is allowed to enable water lock
#available(watchOS 4.0, *)
open func enableWaterLock()
Call WKExtension.shared().enableWaterLock() when starting your swim / water workout.
There is currently no "waterproof mode". My client was under the impression that there was.
The native Apple Exercise app has a button that locks the watch and ignores interaction until the digital crown is rotated. This is not currently reproducible in third party apps.
The solution I have found is to disable all buttons on the watch app. This way, buttons are not accidentally tapped while in the water. The user has to force touch the watch to re-enable the buttons.

ZbarSDK : stop processing without disabling camera

I have an app that should be able to scan a lot of QR Codes in few minutes. So it has to be reactive. I use ZBarSDK and I am satisfied with it.
When data are received by the delegate with processScannedData:(NSString *)scannedData, I present a message that has to be manually dismissed. To be fast, I lock the focus at this distance by interacting with the AVCaptureDevice assuming that next QR Codes will be presented at the same distance.
This system works pretty well except in one situation : When I present my message after a scan, I don't stop the camera to avoid losing the focus I have just locked to. If you present the next QR Code below before dismissing the message, my processScannedData:(NSString *)scannedData delegate method will catch it and ignore the data as expected. The only problem is that if you dismiss the message at that point (keeping the camera above the QR Code), data won't be received but the delegate method has already been fired, you will be forced to look for a few seconds at another point and then come back to your QR Code.
So here is my question : is it possible to tell ZBar that I don't want him to fire the delegate method until I explicitly tell him to do so ? Is it possible to stop processing data without stopping the camera ?
Thank you for your help
I initially thought that the ZBarSDK configuration couldn't be modified while the camera was working, but it actually can.
One can then simply use :
- (void)disableQRCodeDetection
{
// disabling all symbols detection for performance reasons
[self.scanner setSymbology: 0
config: ZBAR_CFG_ENABLE
to: 0];
}
- (void)enableQRCodeDetection
{
// We enable QR Code detection
[self.scanner setSymbology: ZBAR_QRCODE
config: ZBAR_CFG_ENABLE
to: 1];
}

GameCenter login alert

In a game I am developing using GameCenter, I want to handle the following scenario:
the user starts up the game. He is shown the system alert that prompts him to log on GameCenter. He ignores it for now.
after a while, the user wants to log in to GameCenter and clicks on(for instance) the Leaderboards menu item. He choses cancel instead of Log in, for now.
the process repeats several times. Eventually the user DOES want to log in to GameCenter. He clicks the Leaderboard menu item one more time.
In my tests, I have found that the alert popup raised by the call to "authenticateWithCompletionHandler" (as invoked by Apple's sample GameCenterManager) which suggests to log in to GameCenter only appears a limited number of times(4 or 5). The last time it appears, it says "Game Center Disabled, sign in with the Game Center application to enable"Afterwards". Afterwards, calling authenticateWithCompletionHandler no longer does anything visible -no prompt at all.
Playing FruitNinja I tried to replicate this. However, in their case, the popup saying "Game Center Disabled" does appear every time I click on a GameCenter item(Achievements, for instance).
What I'd like to do is to duplicate the functionality: that is, if you are not logged in to GameCenter, to have the standard game center alert appear all the times you click on the Leaderboard menu item.
Is there a way to learn whether the standard 'log in to game center' alert has appeared, or to force it to appear at all times(and not just the first couple of tries)?
Here's an is an idea to workaround this issue:
No matter if a "GC authenticateWithCompletionHandler"-Request is cancelled
by the user tapping "Cancel" in the dialog
or due to the fact that
GC is disabled on the device (which happens after the user has cancelled the alert-dialog exactly 3 times (in iOS 5 at least))
you will always receive an NSError with code 2 saying "The requested operation has been cancelled.".
The only differentiator that i could find is the time passed between the authenticateWithCompletionHandler-Request and the the execution of the completion-Handler.
So when sending the request i am saving the time:
requestTime = [NSDate date];
and in my completion handler i measure the time lapsed:
NSDate* now = [NSDate date];
CFTimeInterval elapsedTimeSinceAuthenticationRequest = [now timeIntervalSinceDate:requestTime];
NSLog(#"time Elapsed: %f", elapsedTimeSinceAuthenticationRequest);
If the user cancelled the request, the time passed will be significantly longer compared to the time passed if GC cancelled the operation. In my tests, it took a user at least one second to cancel the dialog, whereas a GC-cancelled request took less than 0.1 seconds (on my iPhone 4)
Of course, these values may vary depending on the device the code runs on and on what else the processor is busy with at the moment. One pitfall i already examined is the application launch: If you are sending the authenticationRequest during applicationDidFinishLaunching as suggested by Apple, it took much longer for GC to cancel the request in my case, because the device is busy loading views and whatever is necessary to launch the app.
So let me know if you tried this solution and if it worked for you, as will i once i have done further testing...
The behavior is something to the effect of, after N unsuccessful attempts - disable GameCenter for the app. Restarting the app or going to login in gamecenter itself will get it back online.
I forget which doc I read this in, but there is an Apple doc out there that explains this behavior.
I couldn't find a good answer for this either, so I decided to just replicate the message once the I start getting the cancel error. This is still in development but it basically changes the button callback to display the error alert rather than display the leader-board.
Just a note, not sure if this will be approved or not since I am replicating an Apple error message.
-(void) gcLogin: (id) sender {
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
if(error) {
if([[error domain] isEqualToString:GKErrorDomain] && [error code] == GKErrorCancelled) {
[ResourceManager showAlertWithTitle:#"GameCenter Disabled" message:#"Sign in with Game Center application to enable"];
mGameCenterCancelled = YES;
}
NSLog(#"%#", [error description]);
} else {
[self updateMenu];
mGameCenterCancelled = NO;
}
}];
}
I am playing around with Game Center myself right now I have seen the very same behavior. There is nothing in the documentation saying anything about the dialog only showing up the first couple of times.
In my case I would like a way to tell beforehand if the user is already logged into Game Center, so that I can behave appropriately. But now I can not know this before the dialog is shown to the user.
Since we are running in the sandbox during development this behavior might of course be something that behaves differently during production but this is not an easy thing to find out.
May be the link will helpful (First three paragraphs):
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/GameKit_Guide/Users/Users.html#//apple_ref/doc/uid/TP40008304-CH8-SW9
The main point is in a rectangle "Important".
I'm facing the same issue. Though I couldn't find a way to enforce poping up the same dialog for logging into Game Center, I did find a way to implement a warning message saying 'gamecenter is disabled' when user clicks on a leaderboard icon:
if([GKLocalPlayer localPlayer].authenticated == NO)
{
// Prompt a warning message alert saying game center is disabled
}
else
{
// Proceed with opening leaderboard
}
Hope this helps!
It appear that iOS will disable Game Center completely and prevent it from prompting after the user chooses to Disable Game Center (the option would appear on your fifth 5th Game Center cancel sign in).
To restore the device to the original state where the login prompt will appear again. Simply sign in using the Game Center app using a normal working Game Center account (non Tester). Once you are in, Sign Out. It should start prompting you again on within your app.