In objective-C my animation bit would look something like this:
[UIView animateWithDuration:0.5 animations:^{
[[[_storedCells lastObject] topLayerView] setFrame:CGRectMake(0, 0, swipeableCell.bounds.size.width, swipeableCell.bounds.size.height)];
} completion:^(BOOL finished) {
[_storedCells removeLastObject];
}];
If I translate that into Swift it should look something like this:
UIView.animateWithDuration(0.5, animations: {
self.storedCells[1].topLayerView.frame = CGRectMake(0, 0, cell.bounds.size.width, cell.bounds.size.height)
}, completion: { (finished: Bool) in
//self.storedCells.removeAtIndex(1)
})
It complains on the commented-out line. The error I receive is: Could not find an overload for 'animateWithDuration' that accepts the supplied arguments
I know the completion closure takes a boolean value and returns a void, but I should be able to write something that is not bool related there anyway....right?
Any help is appreciated.
Edit: Here is how I declare the array I'm using in the function:
var storedCells = SwipeableCell[]()
An array that takes SwipeableCell objects.
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 () }
Related
I'm using UIViewPropertyAnimator to control the progress of multiple animations. I have a 2d array of UIView subclasses. I want to display all elements at each index at once. For some reason the compiler won't let me add new animations with a delay by using addAnimations(_:delayFactor:) method. Can someone tell me what am I doing wrong here?
// I'm creating a new viewAnimator without any animations. No problems here
self.viewAnimator = UIViewPropertyAnimator(duration: self.frameDelay * Double(self.spillBubbles.count),
curve: .easeIn,
animations: {})
for (index, views) in self.spillBubbles.enumerated() {
views.forEach({ self.simulationView.addSubview($0) })
// That's where the compiler error occurs
self.viewAnimator!.addAnimations({
views.forEach({ self.simulationView.addSubview($0) })
}, delayFactor: Double(index) / Double(self.spillBubbles.count))
}
When I try to call .addAnimations I get the following message:
Cannot invoke 'addAnimations' with an argument list of type '(() -> (), delayFactor: Double)'
What am I doing wrong here? The same closure called in the UIViewPropertyAnimator initializers works without any issues.
The error message is pretty clear. Just look at the docs if it isn't clear enough:
https://developer.apple.com/documentation/uikit/uiviewpropertyanimator/1648370-addanimations
The delayFactor needs to be a CGFloat, not a Double.
I've just about completed the integration of a photo library based on Objective-C into my code, but I'm stuck when trying to re-write the Objective-C example code into Swift in one spot in particular. Here is the Objective-C code causing the problem.
__weak MHGalleryController *blockGallery = gallery;
gallery.finishedCallback = ^(NSInteger currentIndex,UIImage *image,MHTransitionDismissMHGallery *interactiveTransition,MHGalleryViewMode viewMode){
NSIndexPath *newIndex = [NSIndexPath indexPathForRow:currentIndex inSection:0];
[self.tableView scrollToRowAtIndexPath:newIndex atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [(ImageTableViewCell*)[self.tableView cellForRowAtIndexPath:newIndex] iv];
[blockGallery dismissViewControllerAnimated:YES dismissImageView:imageView completion:nil];
});
};
My assumption is that I need to set the finishedCallback variable to a closure with parameters - similar to the Block above.As such I tried to do the same thing. My block is referenced as a variable "closingBlockInputs" below.
weak var blockedGallery = gallery
var closingBlock = {
(currentIndex:NSInteger, image:UIImage, interactiveTransition: MHTransitionDismissMHGallery, viewMode: MHGalleryViewMode) -> () in
}
var tr = MHTransitionDismissMHGallery()
gallery.finishedCallback = closingBlock(1, UIImage(name:"temp"),tr,MHGalleryViewMode.OverView)
However, when I run the code I get an error like:
() is not convertible to Int, UIImage, MHTransitionDismissMHGallery,
MHGalleryViewMode
I'm pretty sure I have the general flow right, just missing something...
Any help would be greatly appreciated...
The finishedCallback is of type (NSInteger, UIImage, MHTransitionDismissMHGallery, MHGalleryViewMode) -> (), that is, a closure that takes four input parameters and returns Void. In your Swift code, you are calling closingBlock and trying to assign its return value (Void) to finishedCallback, which is why the error is telling you that () (aka Void) is not convertible to the closure's type.
There are a few other things worth noting. UIImage(name: "temp") returns an optional UIImage, but the closure is expecting a non-optional UIImage. The init?(name:) initialiser is failable since there might not be an image file with the specified name. So make sure you unwrap the optional UIImage before passing it as a parameter to the closure.
Next thing to consider is that the types of the closure parameters are inferred by the compiler, so there's no need to write them explicitly.
Also, Swift resolves strong reference cycles in closures using capture lists, rather than by declaring a separate weak version of a variable (see Strong Reference Cycles for Closures in The Swift Programming Language for details of this mechanism).
So I'd expect your code to simply look like this in Swift:
gallery.finishedCallback = { [unowned gallery] currentIndex, image, interactiveTransition, viewMode in
// ...
}
I haven't used Swift that much but coming from Objective C, there's a few things about Swift that's a PITA to get my head around.
In iOS programming, we have animateWithDuration: method, which is part of UIView.
So I tried to use Xcode's autocomplete and begin to type:
UIView.animateWith
The autocomplete shows:
UIView.animateWithDuration(duration: NSTimeInterval, animations: () -> Void)
I then tabbed across to the "duration" field, and then typed in a number:
UIView.animateWithDuration(0.5, animations: () -> Void)
Then I tabbed across again to the animation block, and pressed enter like how I normally do it in Objective C, Xcode now shows:
UIView.animateWithDuration(0.5, animations: { () -> Void in
code
})
So then I tabbed one last time to replace "code" with my code:
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.customView?.transform = CGAffineTransformMakeTranslation(0.0, 0.0);
})
That's when Xcode then gives me the error:
Cannot invoke 'animateWithDuration' with an argument list of type
'(FloatLiteralConvertible, animations: () -> Void)'
I don't understand. That's the autocomplete code that Xcode generated for me, why is it giving me an error ?
I noticed if do a simple statement like:
UIView.animateWithDuration(0.5, animations: { () -> Void in
var num = 1 + 1;
})
It doesn't give me any errors.
Any ideas anyone?
From "Calling Methods Through Optional Chaining":
Any attempt to set a property through optional chaining returns a
value of type Void?, which enables you to compare against nil to see
if the property was set successfully ...
Therefore the type of the expression
self.customView?.transform = CGAffineTransformMakeTranslation(0.0, 0.0)
is Void? (optional Void). And if a closure consists only of a single expression,
then this expression is automatically taken as the return value.
The error message is quite misleading, but it comes from the fact that Void? is
different from Void.
Adding an explicit return statement solves the problem:
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.customView?.transform = CGAffineTransformMakeTranslation(0.0, 0.0)
return
})
Update: Adding an explicit return statement it not necessary
anymore with Swift 1.2 (Xcode 6.3). From the beta release notes:
Unannotated single-expression closures with non-Void return types can now be used in Void contexts.
I have a question concerning the swift implementation of the method mentioned in the title. If I do this:
leadingSpaceConstraint.constant = 0
UIView.animateWithDuration(0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (complete: Bool) in
self.navigationController.returnToRootViewController(true)
})
I get the following problem: Missing argument for parameter 'delay' in call. This only happens if I have the self.navigationController.returnToRootViewController() in the completion part. If I extract that statement into a seperate method like this:
leadingSpaceConstraint.constant = 0
UIView.animateWithDuration(0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (complete: Bool) in
self.returnToRootViewController()
})
func returnToRootViewController() {
navigationController.popToRootViewControllerAnimated(true)
}
Then it works perfectly and does exactly what I want. Of course this does not seem to be the ideal solution and more like a work around. Can anyone tell me what I did wrong or why Xcode (beta 6) is behaving this way?
I presume you mean popToRootViewControllerAnimated in your first snippet, since returnToRootViewController isn't a method on UUNavigationController.
Your problem is that popToRootViewControllerAnimated has a return value (the array of view controllers removed from the navigation stack). This causes trouble even though you're trying to discard the return value.
When Swift sees a function/method call with a return value as the last line of a closure, it assumes you're using the closure shorthand syntax for implicit return values. (The kind that lets you write things like someStrings.map({ $0.uppercaseString }).) Then, because you have a closure that returns something in a place where you're expected to pass a closure that returns void, the method call fails to type-check. Type checking errors tend to produce bad diagnostic messages — I'm sure it'd help if you filed a bug with the code you have and the error message it's producing.
Anyhow, you can work around this by making the last line of the closure not be an expression with a value. I favor an explicit return:
UIView.animateWithDuration(0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (complete: Bool) in
self.navigationController.popToRootViewControllerAnimated(true)
return
})
You can also assign that popToRootViewControllerAnimated call to an unused variable or put an expression that does nothing after it, but I think the return statement is clearest.
I am trying to translate some objective-C Code into Swift. I added the Cocoapod "Masonry" for Autolayout to my project and added a Bridging-Header in order to able to use Objective-C Methods in Swift.
This ObjC Method:
[_tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
should be something like the following Closure:
tableView.mas_makeConstraints({ (make : MASConstraintMaker!) -> Void? in
make.edges.equalTo(self.view)
})
But I am getting an "Could not find member 'mas_makeConstraints'" which is not the error, as the method is indexed and autocomletion gives me the following:
tableView.mas_makeConstraints(block: ((MASConstraintMaker!) -> Void)?)
?!
Am I doing something wrong here?
Just my 2 cents if anyone encounter this case:
This objc
[productView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).with.offset(15);
make.right.equalTo(self.view.mas_right).with.offset(15);
make.top.equalTo(self.view.mas_top).with.offset(15);
make.height.equalTo(productView.mas_width);
}];
will turn into
productView.mas_makeConstraints{ make in
make.left.equalTo()(self.view.mas_left).with().offset()(15)
make.right.equalTo()(self.view.mas_right).with().offset()(-15)
make.top.equalTo()(self.view.mas_top).with().offset()(15)
make.height.equalTo()(productView.mas_width)
return ()
}
This piece here in the method signature:
(block: ((MASConstraintMaker!) -> Void)?)
...is telling you that the block argument is Optional, not the return value, which should be Void (not Void?, where you write (make: MASConstraintMaker!) -> Void?)
also: because Swift Type Inference you don't need to put the types in the block
also also: because Swift Trailing Closures you don't need to put a closure that is the final argument to a method inside of the argument list in parens (and since here it's the only argument, you can leave off the parens entirely)
so really your entire method call with block argument could be re-written as:
tableView.mas_makeConstraints { make in
make.edges.equalTo(self.view)
}
finally: it looks like the instance method you are calling on make.edges returns a block, and thanks to the Swift convenience feature of 'implicit return of single expression blocks' it's may be the case that your block is implicitly returning the value of that expression when it is expecting Void - so in the end, if the above doesn't work you may still need to explicitly return Void by writing your method call as:
tableView.mas_makeConstraints { make in
make.edges.equalTo(self.view)
return ()
}