Animation Effectiveness - ios

for button in buttonGroup {
UIView.animateWithDuration(1, animations: {
button.alpha = 0
})
}
VS
UIView.animateWithDuration(1, animations: {
for button in buttonGroup {
button.alpha = 0
}
})
I have more than 40 buttons in buttonGroup,could be more later, I wonder which way is better?
The standard is resource consumption.

You can test the performance of a block of code in xcodes unit test class and measure the speed of each. Create a Unit test framework and the method you need will be there in the template.
Are all your buttons in the same superview? If so consider embedding them in their own UIView and simply adjusting the alpha on that single view instead of all your UIButtons.

Obviously Second one. Because in case of first the UIView.animateWithDuration is called many (total button in buttonGroup) times. This is not efficient programming and also animation become choppy. But in case of second one UIView.animateWithDuration called only one time and accomplished the same result.

You don't have to write some Unit tests. just take a look at xCode debug navigator (shortcut: cmd + 6), look at app's CPU and memory, compare it. Or maybe you can use instruments to compare it. Personally i prefer the second one. (agree with #Md.Muzahidul Islam)

Related

What are various reasons why UIView.animate() would fail to animate a view?

I cannot, for the life of me, figure out why my view won't animate, so I figured maybe it's because of some weird reason that I don't know about. Does anyone have any ideas on less common ways why UIView.animate() would fail to animate properly? The method I am using to animate my view is:
func animateView() {
print("1")
UIView.animate(withDuration: 5.0, delay: 2.0, options: [], animations: {
self.viewToAnimate.alpha = 0.0
print("2")
}, completion: {_ in
print("3")
})
}
What's even stranger (in my opinion), is that when I run, the console outputs
1
2
3
immediately, and does not take into consideration the delay of 2.0 seconds or the duration of 5.0 seconds... It appears my "animation" is instantaneous, making my view disappear immediately with an alpha of 0.
Any thoughts?
Here are the reasons that I can think of:
1- You can check your device settings General-> Accessibility-> Vision-> Reduce Motion
2- You can check if anywhere in the code you call UIView.setAnimationsEnabled(false) https://developer.apple.com/documentation/uikit/uiview/1622420-setanimationsenabled
3- Try to call layoutIfNeeded on the view before calling the animation on the view. This has to do with how animations work, it needs initial state and end state and then it projects the difference between both states on a time graph. https://www.objc.io/issues/12-animations/animations-explained/
4- You are not calling the method on the main thread which can make it ask weird. Try to call it in a dispatch_main block

Swift How to know if layout finished after layoutIfNeeded()

I have a mathematical program for the iPad using a Big Number library that does calculations, and then updates an array of up to 20,000 UILabels in a UIView (myView) based on those calculations.
The calculations can take about 5 seconds, during which time the backgroundColor of each of the UILabels is set to a color. Because nothing is happening on the screen, I have a blinking UILabel inProgressLabel that informs the user that the system is calculating. I then call layoutIfNeeded, with the idea that when it is finished, the screen will have been updated. Finally, I turn off the blinking UILabel.
Here is the pseudo-code:
inProgressLabel.turnOnBlinking()
for row in 0..<rowCount
{
for col in 0..<colCount
{
// perform some calculation
let z = buttonArray[row][col].performCalculation()
//now set the Label background based on the result of the calculation
buttonArray[row][col].setLabelBackground(z)
}
}
myView.layoutIfNeeded()
inProgressLabel.turnOffBlinking()
My understanding was the layoutIfNeeded() is synchronous. Thus, the screen will update and then, and only then, the blinking inProgressLabel will be turned off. However, the inProgressLabel is actually turning off immediately after layoutIfNeeded is called, and then it can take another five seconds for the array of UILabels to update.
I thought that maybe this is happening because the updating is occurring in a different thread. If this is so, is there a way to know for sure when the UIView, and the array of UILabels, have finished updating (displaying), so that I can then turn off the blinking UILabel?
Many thanks in advance.
You can try
let dispatchGroup = DispatchGroup()
for row in 0..<rowCount
{
for col in 0..<colCount
{
dispatchGroup.enter()
self.doCalcAndUpdateLbl(index: i) { (finish) in
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
myView.layoutIfNeeded()
inProgressLabel.turnOffBlinking()
}
UIKit controls are not thread-safe. Updating UILabels from the background produces undefined behaviour.
layoutIfNeeded performs any pending layout tasks and returns. It has no magical foresight of any changes you may be planning to make in the future.
You need to schedule your work to occur off the main queue, and ensure results are pushed back to the main thread when done. If it makes sense to dispatch various calculations to occur separately, use a dispatch group as Sh_Khan suggests. Otherwise consider just performing the background calculations in a single block and hopping back onto the main queue from there.
It's also highly unlikely that 20,000 UILabels is appropriate; look into using a table view or a collection view. As a general rule, you should try to have active controls only for whatever is presently on screen, and both of those views offer ways very easily to manage that active set. Every view has a memory footprint because the content of views is buffered — you'll hurt your own performance and that of other running applications if you unnecessarily squeeze memory.

How can I animate the movement of a view dynamically and smoothly without stopping - iOS Swift

I have a problem with animation as I want the same animation with different end points, to be called upon finishing the previous animation and to be executed smoothly without slight stop.
Let me elaborate with a sample code and comments.
Func animate()
{
Start animation(completionHandler: {
If condition is true
Repeat the animation and check after it finishes
}
}
but the problem with this code is that, between finishing the first animation and starting another one, there will be a split second stop which is not smooth movement. For example, I have a view on zero point to move with animation to point A (x:100,y:100), and then it will receive point B (x:500, y:900), after it reaches point A it will stop a split second then move to point B. Since the points are Dynamic, I cannot know if there is a new point or not, and if I did i will not know the coordinates beforehand, so I need to keep the logic dynamic and keeps the animation smooth between points.
I tried many logics and results were not the thing I wanted. problems I faced toward a smooth queued animation are:
I tried UIView.animate in the following:
a. I tried setting an animation duration let's say 2 sec, then after it finishes, it calls animation again within completion hander/closure/block, but it will result in a less than second stop between the two animations.
b. I tried to start 2 animations, the second is delayed with the exact duration go the first animation, but results also brief stop between 2 animations.
2.I tried animation with keyframes, but since I don't know all animations in advance, this won't work.
Can you help please with my approach toward a smooth dynamic animation?
Your own pseudocode answers your question. Just test whatever your condition, and if you need to animate again, work out the new start and end points and animate again:
func animate(start:Int, end:Int) {
UIView.animate(withDuration: 5, animations: {
// animate here based on new start and end
}, completion: { _ in
var weShouldDoItAgain = false
// figure out if we should do it again
// if so, change weShouldDoItAgain to true
if weShouldDoItAgain {
var start = start // and now change it to new value
var end = end // and now change it to new value
self.animate(start:start, end:end)
}
})
}
Just change all the values and types to suit your use case.

iOS freezed UI without error logs

I have an issue on my swift iOS app and i'm not able to understand which is the real problem. I have a UITabBarController with five UINavigationControllers on each tab. Main content of these controllers are UICollectionView filled dynamically from a WebService with custom UICollectionViewCells (about 30 for each CollectionView) with custom UIImageViews, custom UILabels and custom UIButtons loaded from custom xib files.
When i push few controllers all is working good (CollectionViews become a bit slower but i think it's normal for memory usage) but when a NavigationController contains 6 or 7 controllers and i try to scroll down, my UI is freezed, the app doesn't crash and i don't receive any kind of runtime error. I can still exit using Home button but the app is completely locked.
I'm dequeuing every cell in every collectionView with identifier and i'm using this attributes in every custom cell for performance:
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen().scale
What i tried
Debug views too see if some UIWindow or UIView is covering the controller view
Stop image loading and show blank imageViews
Clear code used in UICollectionView scroll delegate methods
But the issue is always reproducible on 6th or 7th controller, making some stress test. I gave a look to FPS too, but values are always from 40 to 60 that seems good. CPU usage is always very high, 90/100 % but method didReceiveMemoryWarning is never fired.
My goal would be that my app could push many controllers without freezing or become very very slow, but i can't understand what i'm missing.
Thanks to Philip Mills
Issue is that i was calling this code in my custom UICollectionViewCell setup:
func rotateWithDuration(duration: NSTimeInterval, indeterminate: Bool) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
self.transform = CGAffineTransformRotate(self.transform, CGFloat(M_PI))
}, completion: { finished -> Void in
if indeterminate {
self.rotateWithDuration(duration, indeterminate: indeterminate)
}
})
}
View rotation animation was running indefinitely in every cell, so the CPU was a bit sweat.

How to prevent two tableview cell animations from occurring at same time

I have a tableview that has expandable cells. When you click on a cell a date picker shows inline. When you click on another, a different kind of picker shows. I have set it up so when you click on any cell, any open cells will close, so as to ensure that only one cell is open at a time.
My problem is: the animations for a) the first cell closing, and b) the second cell opening are overlapping, which doesn't look great at all.
When I click a cell I would like the expanded cell to close, and THEN the second cell to expand, with enough of a delay so the animations don't overlap.
I have looked for a Swift solution online but haven't been able to find an answer. I am fairly new to iOS dev, so don't know much about UIAnimation, etc.
Using Xcode 7.2, Swift.
Any help would be appreciated.
You can try to use the completion for that.
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
//your first animation
}, completion: {(finished:Bool) in
//your seconde animation (or whatever you want to be done when the first one is completed)
})
OR
You can use a CATransaction, see : Wait for Swift animation to complete before executing code
OR
You can try to force them to run on the same thread instead of using parallel threads ?
For example, here is how you can force an action to be done on the main thread :
dispatch_async(dispatch_get_main_queue()) {
//your animation
}
There may also be some other solutions,
good luck

Resources