AVCaptureSession Pausing Session - ios

When I want to pause the session, this was the only solution for me:
func pauseSession () {
self.sessionQueue.async {
if self.session.isRunning {
self.session.stopRunning()
}
}
}
func resumeSession () {
self.sessionQueue.async {
if !self.session.isRunning {
self.session.startRunning()
}
}
}
This seems to completely stop the session, which is fine, yet looks expensive.
The issue I seem to have is if pause and resume are called near each other in time, the whole app freezes for about 10 seconds, till going back to being responsive. This is mostly due to it still hasn't finished the last process (whether to stop or start).
Is there a solution to this?
The native camera app seems to do this fine. If you open it, open the last photo, you can see the green indicator on the top right going off, meaning the session has paused/stopped. If you swipe down on the photo, the session resumes. If you swipe and let it get canceled, quickly swipe again you can see the session pauses and resumes quickly over and over without any issues.

You may need to change async to sync
self.sessionQueue.sync {

Related

Why is the view not updated on time?

I'm trying to change an UIImageView alpha attribute on click. However, the change of the attribute is done after all the code-lines are run, and I want it to be as soon as the click is done. To simplify the code, I changed all the code-lines for a sleep(2) as it is giving the same effect.
The click function
#IBAction func click_btn() {
imageView.alpha = 0.5
sleep(2)
}
I've tried using DispatchQueue and async methods but the result is the same.
Here's the result, as you can see, when I click the button the Image doesn't change until the 2 sleep seconds pass. How can I change the alpha property immediately after the click?
That is the way the event loop on iOS (and Mac OS) works. UI changes are queued up as you run through your code, and then applied once you return and the app visits the event loop. Your delay is PREVENTING the change from taking place. If you get rid of the sleep it will work as intended.
Note that normally button taps fire on "touch up inside", which means the button doesn't respond until the user releases their finger. You can attach the action to a touch down event instead if you want.
What is it you actually want to accomplish?
Edit:
Rewrite your code like this:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue.main.async() {
//Code that takes 2 seconds to run
}
}
That will cause your function to return immediately, and your alpha change to take place. On the next pass through the event loop, the system will find your async task on the main queue, pick it up and start processing it. Note that for the 2 seconds it takes to run, the UI will freeze. It might be better to run the slow code on a background queue and only update the UI once the "slow bits" are finished:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue. global().async {
// Run the code that takes 2 seconds to run on a background thread
DispatchQueue.main.async() {
// Do your UI updates that have to run after the slow code finishes on the main thread.
}
}

How to immediately redraw a Label (NSTextField) in Swift? [duplicate]

I'm trying to change an UIImageView alpha attribute on click. However, the change of the attribute is done after all the code-lines are run, and I want it to be as soon as the click is done. To simplify the code, I changed all the code-lines for a sleep(2) as it is giving the same effect.
The click function
#IBAction func click_btn() {
imageView.alpha = 0.5
sleep(2)
}
I've tried using DispatchQueue and async methods but the result is the same.
Here's the result, as you can see, when I click the button the Image doesn't change until the 2 sleep seconds pass. How can I change the alpha property immediately after the click?
That is the way the event loop on iOS (and Mac OS) works. UI changes are queued up as you run through your code, and then applied once you return and the app visits the event loop. Your delay is PREVENTING the change from taking place. If you get rid of the sleep it will work as intended.
Note that normally button taps fire on "touch up inside", which means the button doesn't respond until the user releases their finger. You can attach the action to a touch down event instead if you want.
What is it you actually want to accomplish?
Edit:
Rewrite your code like this:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue.main.async() {
//Code that takes 2 seconds to run
}
}
That will cause your function to return immediately, and your alpha change to take place. On the next pass through the event loop, the system will find your async task on the main queue, pick it up and start processing it. Note that for the 2 seconds it takes to run, the UI will freeze. It might be better to run the slow code on a background queue and only update the UI once the "slow bits" are finished:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue. global().async {
// Run the code that takes 2 seconds to run on a background thread
DispatchQueue.main.async() {
// Do your UI updates that have to run after the slow code finishes on the main thread.
}
}

SKNode's action run completion block does not get called

I have a watchOS 4 app which displays SpriteKit animations (SKActions) on top of the UI. Everything works fine in simulator and also on device first couple of times, then after some time when app is in background, and it is started, animations just freeze and completion block for the most long-lasting animation is not called. Any idea what might be the issue?
This is how I run my actions, caller is waiting for completion closure in order to hide the spritekit scene:
private func runActions(with icon: SKShapeNode?, completion: #escaping () -> Void) {
if let icon = icon, let scaleAction = scaleAction, let bg = background {
self.label?.run(fadeInOutAction)
icon.run(scaleAction)
icon.run(fadeInOutAction)
bg.run(backgroundAction, completion: completion)
} else {
completion()
}
}
And yes, I am aware that SKScene is paused when app moves to background. I am doing this in willActivate of my InterfaceController:
if scene.scene?.isPaused == true {
scene.scene?.isPaused = false
}
I want to emphasize that this works first always. It begins to fail after the app has been backgrounded for some time. Especially if I start the app from complication and try to immediately fire these animations, then this freezing happens.
Can I answer my own question? I guess I can? Here goes:
I finally solved this. It turns out that the WKInterfaceScene in WatchKit has ALSO an isPaused property that you need to turn false sometimes. So now in willActivate of my InterfaceController I will also check that and turn it false if it is true. Since I made this change, I haven't seen a single hiccup, freeze or anything weird anymore.
Case closed, I guess. I leave this here for future generations who might face this issue.

My iOS app freezes but no error appears

Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.

MPMoviePlayerController seek forward in fullscreen mode until end is stuck

There seems to be a problem with the MPMoviePlayerController where once you're in fullscreen mode and you hold down the fast forward button, letting it seek forward (playing at fast speed) all the way to the end of the video.
Thereafter the you just get a black screen and it's stuck. In other words it does not respond to any taps gestures and you can not get out of this situation. Has anyone else encountered this problem?
Is there anyway to work around it in code?
It seems it's an iOS bug since fast backward to the very beginning won't cause the black screen but fast forward to the end will, and after that the 'play'/'pause' call to the video player never works. I temporarily fix this by adding protected logic into the scrubber refresh callback:
let's assume that monitorPlaybackTime will be called in 'PLAY_BACK_TIME_MONITOR_INTERVAL' seconds period to refresh the scrubber, and in it I add a check logic:
NSTimeInterval duration = self.moviePlayer.duration;
NSTimeInterval current = self.moviePlayer.currentPlaybackTime;
if (isnan(current) || current > duration) {
current = duration;
} else if (self.moviePlayer.playbackState == MPMoviePlaybackStateSeekingForward) {
if (current + self.moviePlayer.currentPlaybackRate*PLAY_BACK_TIME_MONITOR_INTERVAL > duration) {
[self.moviePlayer endSeeking];
}
}
A workaround to solve the black screen, not perfect, hope it can help.
I'm guessing you are not handling the MPMoviePlayerPlaybackDidFinishNotification. You really should if you're not.
Still its unexpected for me that the movie player would go into a "stuck" state like you describe. I would more readily expect it to stop playback automatically and reset when it reaches the end. Anyway, I think your problem will go away if you observe the MPMoviePlayerPlaybackDidFinishNotification and handle the movie controller appropriately.
Ran into the same issue on iOS6. Managed to fix it by registering for the MPMoviePlayerPlaybackDidFinishNotification (as suggested by Leuguimerius) with the following implementation:
- (void)playbackDidFisnish:(NSNotification *)notif {
if (self.player.currentPlaybackTime <= 0.1) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.player stop];
[self.player play];
[self.player pause];
});
}
}
Where self.player is the associated MPMoviePlayerController instance. The check against currentPlaybackTime serves to distinguish the more standard invocations of playbackDidFinish (where the movie is allowed to play at normal speed until its end) from those scenarios where the user fast forwards until the end. Stopping then playing and pausing results in a usable, visually consistent interface even when fast-forwarding to the end.
None of the aforementioned solutions worked for me, so this is what I ended up doing:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("moviePlayerLoadStateDidChange"), name: MPMoviePlayerLoadStateDidChangeNotification, object: nil)
func moviePlayerLoadStateDidChange() {
let loadState = moviePlayerController?.loadState
if loadState == MPMovieLoadState.Unknown {
moviePlayerController?.contentURL = currentmovieURL
moviePlayerController?.prepareToPlay()
}
}
I think the issue is that when the seek foraward button is single pressed, it wants to skip to the next video, that's why a loading indicator appears. Listening for the load state change event, you can specify what the next video should be, and if you don't have any, you can just give it the same url.

Resources