Blocks on Swift (animateWithDuration:animations:completion:)
The completion parameter in animateWithDuration
takes a block which takes one boolean parameter. In Swift, like in Obj-C blocks, you must specify the parameters that a closure takes:
UIView.animateWithDuration(0.2, animations: {
self.blurBg.alpha = 1
}, completion: {
(value: Bool) in
self.blurBg.hidden = true
})
The important part here is the (value: Bool) in
. That tells the compiler that this closure takes a Bool labeled 'value' and returns Void.
For reference, if you wanted to write a closure that returned a Bool, the syntax would be
{(value: Bool) -> bool in
//your stuff
}
How to use completion block for UIView.animate()?
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame
to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue
probably should be inside a completion
closure of the inner animate
call.
UIView Animate and completion block
I was able to reproduce this issue and found a workaround.
When using a UIScrollView
, which UITableView
inherits from, you can't change its contentOffset
is not an animatable property. Instead, you need to use the method setContentOffset:animated:
.
So, what you need to do is as follows:
- Set your view controller as the
UITableView
delegate. I did in onviewDidAppear
. - Set a flag, so you know in the delegate that the scroll happened because you triggered.
- In the delegate method
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
, bounce back the animation (you could here add a delay by usingperformSelector:afterDelay:
.
Here's the code:
@interface MyViewController ()
@property (assign, nonatomic) BOOL shouldReturn;
@end
@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (TRUE) {
NSLog(@"animating table view");
self.shouldReturn = YES;
self.tableView.delegate = self;
[self.tableView setContentOffset:
CGPointMake(self.tableView.contentOffset.x,
self.tableView.contentOffset.y - 60)
animated:YES];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (TRUE) {
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Refresh"];
[refresh addTarget:self action:@selector(refreshTableView:) forControlEvents:UIControlEventValueChanged];
[self setRefreshControl:refresh];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.shouldReturn) {
self.shouldReturn = NO;
[self.tableView setContentOffset:CGPointZero animated:YES];
}
}
@end
How to add a completion to an animation?
Apple documentation says:
completion
A block object to be executed when the animation sequence ends. This
block has no return value and takes a single Boolean argument that
indicates whether or not the animations actually finished before the
completion handler was called.
So you need to give that boolean argument to completion handler:
UIView.animate(withDuration: 3.0, animations: {card.frame = newPosition}, completion: { finish in
if card.frame == newPosition {
card.removeFromSuperview()
}
})
animateWithDuration:animations:completion: in Swift
This is a good one, tricky!
The issue is in your completion block...
A. I would begin by rewriting it like this: (not the final answer, but on our way there!)
{ _ in self.storedCells.removeAtIndex(1) }
(the _
in place of the "finished" Bool, to indicate to the reader that its value isn't being used in the block - you may also consider adding a capture list as necessary to prevent a strong reference cycle)
B. The closure you have written has a return type when it shouldn't! All thanks to Swift's handy feature "implicit returns from single expression closures" - you are returning the result of that expression, which is the element at the given index
(the type of the closure argument for completion
should be ((Bool) -> Void))
This can be resolved as so:
{ _ in self.storedCells.removeAtIndex(1); return () }
UIView animate completion block called twice with TRUE
The completion block should only be called once each time the animation completes. Make sure the animation isn't being called more than once.
Add a breakpoint to the beginning of the code you listed, edit it to log some message like "animation starting" and set it to continue after evaluation.
If you see the message more than once you can set the breakpoint to stop each time and look in the call stack to see where the calls are coming from.
If it isn't getting called twice it might be time to file a radar…
Can't put transform change in animation block of animateWithDuration in Swift
You could omit the Void -> in
altogether in your case; the real culprit that is causing the problem is this line:
self.zigZag!.layer.mask.transform = CGAffineTransformMakeTranslation(100, 0)
because there is a type mismatch.
It should be:
Swift 2
UIView.animateWithDuration(0.5, delay: 0.05, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.view!.layer.mask.transform = CATransform3DMakeTranslation(100.0, 0.0, 0.0)
}, completion: nil)
Swift 3, 4, 5
UIView.animate(withDuration: 0.5, delay: 0.05, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.view!.layer.mask.transform = CATransform3DMakeTranslation(100.0, 0.0, 0.0)
}, completion: nil)
UIView.animateWithDuration:animations:completion: gets canceled in XCTest
For the animation to complete with the correct flag, the view needs to in a UIWindow that is visible.
let window = UIWindow()
window.addSubview(view)
window.isHidden = false
With this, your test succeeds. But UIKit won't clean up the window without giving the run loop an extra kick at the very end of the test. So add
func tearDown() {
super.tearDown()
RunLoop.current.run(until: Date())
}
Then the window (and anything in it) will be deallocated.
Now that it's working, you can save time by reducing your duration. I got the test down to 23ms by using duration: 0.001
Related Topics
My Swift 4 Uiscrollview with Autolayout Constraints Is Not Scrolling
Uiviewcontroller In-Call Status Bar Issue
What Impact Does Simulated Metrics Have
Union Uibezierpaths Rather Than Apend Path
Various Itms Errors When Trying to Submit Archive to App Store
iOS Getting Location Updates When App Terminated Without Using Significantchange
Swift: Retrieving Text from a Uitextfield in a Custom Uitableviewcell and Putting It in an Array
How to Create a Development Framework in iOS Including Swift
Undefined Symbols for Architecture I386: "_Objc_Class_$_Zipexception", Referenced From: Error
Wkwebview Function for Detecting If the Url Has Changed
Memory Leak Every Time Uiscrollview Is Released
Google Maps iOS Sdk: Custom Icons to Be Used as Markers
Create and Perform Segue Without Storyboards
Sharing an Image Between Two Viewcontrollers During a Transition Animation
Swift - How to Record Video in Mp4 Format with Uiimagepickercontroller
How to Remove Special Characters from String in Swift 2
Drawviewhierarchyinrect:Afterscreenupdates: Delays Other Animations