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.
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.
This piece of code ran fine in Swift 3, but now after conversion of code by the Xcode into Swift 4, it is throwing error. I am presenting the next scene in the main queue, based on the values I got from the web-service.
DispatchQueue.main.async {
[weak self] value in
self?.router?.invokeTheSegueAfterTheWebService(navigationKey: arrayElementsGot["questionType"] as! String, givenViewController: self!)
}
In Swift 3, the value parameter was inferred to have the type ().
Swift 4 no longer allows you to provide a single parameter to blocks that contextually should take a different number of parameters. So your code should look like
DispatchQueue.main.async {
[weak self] in
self?.router?.invokeTheSegueAfterTheWebService(navigationKey: arrayElementsGot["questionType"] as! String, givenViewController: self!)
}
If you look at the error you got, it says that it expected () -> Void (e.g. a block with no parameters) but you passed it (_) -> () (e.g. a block with one parameter of unknown type).
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.
1) I'm using a variable as the first argument in UIView.animateWithDuration like so:
var theDelay: Float = 1.0
UIView.animateWithDuration( theDelay, animations: {
cell.frame.origin.y = 0
})
and Xcode6 (Beta 3) is giving me a build error: 'Missing argument for parameter 'delay' in call'.
When I don't use a variable, the function works just fine. I'd like to tweak the variable (as this code is within a loop) when I discovered this issue.
2) Alternatively, I could skip using a variable and include the calculation in-line:
UIView.animateWithDuration( indexPath.row * 1.0, animations: {
cell.frame.origin.y = 0
})
but I am getting the same error 'Missing argument for parameter 'delay' in call'.
What am I doing wrong here?
The error message is misleading. The first parameter of animateWithDuration()
has the type NSTimeInterval (which is a Double), but you pass a Float argument.
Swift does not implicitly convert types.
Changing the variable definition to
let theDelay = 1.0
or an explicit conversion
UIView.animateWithDuration(NSTimeInterval(indexPath.row), animations: {
cell.frame.origin.y = 0
})
should solve the problem.
In Swift 3 this would be
let theDelay = TimeInterval(indexPath.row)
UIView.animate(withDuration: theDelay, animations: {
cell.frame.origin.y = 0
})
I also got this error from a totally unrelated error in the animations block; I was already passing in a valid NSTimeInterval. It had nothing to do with the delay parameter, so the error it was showing me was wrong and had me going in circles for a while.
So make sure you don't have any errors inside the animations block.
UIView.animateWithDuration(interval, animations: { () -> Void in // compiler will complain about this line missing a parameter
// some code
// some code with a syntax error in it, but Xcode won't show the error
// some code
})
Actually Swift is typed language and it need to pass same type arguments as defined
There is no implicit cast in swift.
As animateWithDurationin decleration
class func animateWithDuration(duration: NSTimeInterval, animations: (() -> Void)!) // delay = 0.0, options = 0, completion = NULL
has parameter type of NSTimeInterval which is declared as double if you see its declaration
typealias NSTimeInterval = Double
So it need Double parameter value not Float value.
When you call second timeas in your code than swift is using type interfrence i.e it is automatically defining(not convertting float to double) your indexPath.row * 1.0 to Double.The below code works fine.
var theDelay: NSTimeInterval = 1.0
or
var theDelay: Double = 1.0 //this is same as above
UIView.animateWithDuration( theDelay, animations: {
cell.frame.origin.y = 0
})
Compiler is misguiding you.So always pass parmeter type same as defined in Swift
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 () }