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.
}
}
Related
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.
}
}
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 {
I have an issue with my code which exemplified by a simplified version below. I expect for "2" to be printed before "1". However, that is not the case.
Does this have something to do with the fact that the code doesn't actually run from top to bottom?
if moveConclusion.patternDetected == true {
for i in 0...8 {
if pressedArray[i] {
self.panlButtons[i].backgroundColor = UIColor.clear
UIView.animate(withDuration: 0.1, animations:{
self.panlButtons[i].backgroundColor = self.correctColour
//self.panlButtons[i].transform = CGAffineTransform(rotationAngle: CGFloat.pi / -12)
}, completion: { finished in
UIView.animate(withDuration: 0.1, animations:{
self.panlButtons[i].backgroundColor = UIColor.clear
// self.panlButtons[i].transform = CGAffineTransform(rotationAngle: CGFloat.pi / 12)
print("2")
})
})
}
}
print("1")
}
Output:
1
2
2
2
...
Calling UIView.animate orders the animation; it does not perform it. The animation engine will perform it — later. You are handing to the animation engine two blocks to be executed later, meaning after all your code comes to an end (to be technical, it's when the next screen refresh frame comes along and the current CATransaction is committed):
The animations block will run after all your code has finished and it's time for the animation to start.
The completion block will run after the animation ends (that's what "completion" means).
UIView.animate runs on the main thread and is asynchronous. So as soon as that code is called it dispatches it onto the NEXT runloop, (which is after the this function terminates, which means after the entire for loop).
In other words, the UIView.animate call essentially just queues it for the next run loop. And the print("1") statement still occurs on the current run loop.
Then the animation block is run, and once the animation block finishes, the completion block runs. So the print for the 2 comes WAAAAAY later in terms of CPU processing, and is definitely the expected behavior.
This is because print("2") executes approximately 0.1-0.2 seconds after this code starts, but print("1") happens after the for loop executes.
The animations do not execute synchronously with the for loop. The print("2") will happen during the second animation, but by that time, the for loop has completed and print("1") has executed.
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.
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.