AVCaptureSession's stopRunning - when to call - ios

I'm making an app that takes pictures. It consists of two view controllers connected via a tabBarController. On one of the viewControllers is the camera screen. It has a UIView which has the sole purpose of displaying the contents of AVCaptureVideoPreviewLayer.
I've hooked up the inputs and outputs, and have a live feed of the camera's view displayed on my UIView. Since I have 2 view controllers connected by a tabBarController, I make sure I call stopRunning whenever I'm not on my camera's viewController - when the user tabs to the other viewController.
Currently, I've implemented it as follows:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
session.startRunning()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
session.stopRunning()
}
Whenever I'm on the camera screen with the session running, my iPhone 5 heats up - something I expected - probably due to the AVCaptureVideoPreviewLayer object's demand for resources. My above attempt is to stop the session wherever it's not needed. However, I feel like I haven't covered all the grounds.
If my app goes to background, will the session continue? I used some println statements to track my viewController life - cycle methods, and found that viewWillDisappear is not called when app is retired to the background.
Does that mean my session continues to run and my camera is still trying to provide the feed? I'm suspect this may be an issue, because the phone still feels warm after a long while.

Related

Swift call Function when ViewController is visible

I have a small problem I sadly couldn't solve. I have a few ViewControllers. I need to call a function when going back from a ViewController to the previous ViewController (which does not reload, so viewDidLoad / Appear is not called).
Mustn't there be anything like viewDidLoad, but gets called every time the ViewController is visible?
This is how I open the ViewControllers:
let vc = storyboard?.instantiateViewController(identifier: "levels_vc") as! LevelsViewController
present(vc, animated: true)
This is the way I try to get the event, but it only gets called the first time (when the ViewController loads the first time), but not when I open a new ViewController from this one and close the new one.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("VC appeared!")
}
You can use viewWillAppear or viewDidAppear for that purpose!.
viewWillAppear() is called every time the view will appear on the screen.
This can be useful for views that need to be updated every time they’re scrolled to. For example, the Now Playing section in a music app or any view that is updated when it is scrolled to.
viewDidAppear is called when the view has appeared on the users screen.
This is a good place to “lazy-load” any other elements onto your screen. If your user interface is very complicated, you don’t want to stall or cause a delay in the previous mentioned methods.
It’s better to load something onto the screen and then call some methods to update the views for some large applications.
This is also a create place to start any animations.
That would be ViewDidAppear() ViewDidAppear() - Apple Developer

ARKit: pausing session does not stop the scene to be called

I'm using ARKit with SpriteKit. My AR feature is not a core feature in my app, users may optionally navigate to a viewController with an ARSKView where I configure and set an SKScene in the ARsession.
I pause the session when the user navigates back:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
then the viewController is pop from the navigationController where it was pushed. But I'm seeing that the func update(_ currentTime: TimeInterval) in my SKScene subclass keeps being called... which is the appropriate way to also stop that? I thought that pausing the ARSession would also stop the scene.
EDIT: when I navigate back from the view controller that holds the ARSKView and it is deinitialized, I see that the SKScene is still in memory and a new one is created every time I navigate again to the AR related view controller. So, if I navigate there N times, I see I have N SKScenes in memory. How could I appropiately remove the scene when navigating back? The scene property of the ARsession is read only.
that sounds like a memory retain issue. Make sure that any node in you SKScene subclass is properly removed and deinitialized.
Set your scene delegate to nil, set any node or array of nodes referenced in your VC to nil.
I ran a simple test with 2 VC embedded in a navigation controller, the first one has a view with a start button, the second one is the ARVC.
I run the session, set my custom scene delegate to ARVC, when I hit the back button everything stops by itself and my ARVC is being popped. Nothing in memory.
So make sure that you manually remove all your nodes and their animations, constraints, actions and anything that relates to you custom SKScene subclass.
Also don't be afraid to call sceneView.presentScene(nil) if that can help.
I couldn't find any official documentation about this, apparently there is not proper method to stop a session yet. What I found that was similar to your scenario is this work around.
It suggests you create ARSKView as disposable and before leaving your controller, pause the session, remove the view from superview and assign it to nil.

iOS app written in swift is crashing when returning to a view

I'm writing an app that loads an options screen (scene 1) where the user will fill in some text fields then segue to a new scene (scene 2). After the user finished with scene 2, the user can click a button which will segue them back to scene 1 to fill out the options again. On scene 1, I'm setting the first text field to become the first responder so the keyboard automatically appears when the view loads.
override func viewDidLoad() {
super.viewDidLoad()
self.numeratorBegin.becomeFirstResponder()
// Do any additional setup after loading the view.
}
This works great when the app loads. The keyboard appears and the cursor is in the numeratorBegin text field. However, when the user finishes with scene 2 and presses the button to go back to scene 1, the app crashes. The crash does not occur when the first responder is not being set in viewDidLoad. The debugger shows the following line causing the crash with the message Thread 1: EXC_BAD_ACCESS(Code=2, address=hexHere)
class AppDelegate: UIResponder, UIApplicationDelegate {
The idea is to have the numeratorBegin text field to become the first responder every time the view loads. For the life of me I can't discover why the app is crashing.
Adding some more points to Alain T.'s answer.
Instead of
self.numeratorBegin.becomeFirstResponder() inside override func viewDidLoad()
add it to override func viewWillAppear()
and self.numeratorBegin.resignFirstResponder() inside override func viewWillDisappear()
If you try to use becomeFirstResponder()/resignFirstResponder() in other thread, then it causes a crash. So it's better to follow the above methods which always executes in main thread.
I suspect that becomeFirstResponder sets some internal states/variables that depend on the view hierarchy. When viewDidLoad() is called, the view hierarchy is not setup yet so these states may be skewed somehow and affect the release cycle of some UI component.
You should call becomeFirstResponder in viewWillAppear instead. The view hierarchy is ready at that point.

ios 8 swift - doing stuff after displaying a view

I'm working on a app which has a button to open a view that access the camera.
So, I have a button to trigger a segue (using the storyboard) and in my other view, inside viewDidLoad(), I'm doing all I need to start the video capture.
The problem is that between touching the button and view been showed it take some small amount of time and I don't like it.
If I comment all the stuff regarding the video capture, the view show up instantly.
So, I think the lag is du to the preparation to access the camera.
How to display the view, an empty view, and then doing the stuff to show the camera?
If you want to show the new view controller and then activate the camera, move your code from viewDidLoad into viewDidAppear. You could try viewWillAppear, but depending on what your code does, you may just see the same issue you have now.
You have to be careful here to only do your activation once as viewWill/DidAppear can be called multiple times: especially as a result of being exposed when a controller you push on top is popped.
Just use viewDidAppear to load all the methods in that view controller instead of using the viewDidLoad method like so:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}

How do I performSegue before the views appear without the under-layer flash?

I'm checking to see if a user is logged in. If logged in, then skip the LoginViewController (Initial View Controller), and head over to the MainViewController.
I've tried doing this check within LoginViewController in countless places of the lifecycle without the under-layer flashing.
By under-layer flashing, I mean you can see the LoginViewController for a split second before the MainViewController comes up.
I have solved this 1 way, but it feels extremely hacky:
In my LoginViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
calls++
if calls == 2 && NSUserDefaults.standardUserDefaults().objectForKey("AuthToken") != nil {
self.performSegueWithIdentifier("loginUserNoAnim", sender: self)
}
}
The "loginUserNoAnim" is a custom Segue I created with no animation.
viewDidLayoutSubviews() gets called in my code 3 times. The first two times, I get warnings that my ViewController is not in the window hierarchy similar to this: Warning: Attempt to present <PSLoginViewController: 0x1fda2b40> on <PSViewController: 0x1fda0720> whose view is not in the window hierarchy! This creates a two extremely quick white flashes in the UI. I got rid of them by checking how many calls were made via the call variable.
Now, this is 100% hacky and not acceptable.
Is there a way to solve this issue by using the iOS lifecycle? I've even tried using the appdelegate method:
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
But I still saw the under-layer flash. What is the best way to mitigate it?
I think the following StackOverflow answer is a good explanation how to switch between login and main view controllers at the application start:
Programmatically set the initial view controller using Storyboards
Note that if you provide sign out functionality in the app, you should then direct user to the log in view controller as it is checked only on application start.

Resources