UIView.animateWithDuration completion - ios

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.

Related

Different types of closure syntax in swift - which one is correct?

I'm pretty curious which one of these syntax statements is (more) correct.
Playground happily compiles both cases.
Method 1
// copied from SO and this appears clear to me
UIView.animate(
withDuration: 3.0,
animations: {
},
completion: { (Bool) in
// completion code
}
)
Method 2
UIView.animate(
withDuration: 3.0,
animations: {
// code
}) {(Bool) in
// code when finished?
// argument label completion missing?
}
Why rounded brackets in 2nd method are closed before last argument stated? Or is that another implementation of UIView.animation?
Both of them are correct.
It is the usual closure syntax in a function call.
It represents a Trailing closure.
If you need to pass a closure expression to a function as the
function’s final argument and the closure expression is long, it can
be useful to write it as a trailing closure instead. A trailing
closure is written after the function call’s parentheses, even though
it is still an argument to the function. When you use the trailing
closure syntax, you don’t write the argument label for the closure as
part of the function call.
You can read more about trailing closures from https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
The difference between both methods is following:
Method 1: Regular closure
Method 2: Trailing closure.
Last closure parameter in the signature of a function can be written in shorter syntax. If the second parameter would be completion, and animations parameter would be the last, the trailing closure would apply to animations etc.
So it has to stand as the last (or the only) closure parameter.
If you miss a completion label, you are free to type it like this:
UIView.animate(withDuration: 3.0, animations: {
}) {(completion: Bool) in
}
For completion of your question as well: It is the same implementation of an identical function, but a different syntax.

Xcode 8 / Swift 3: "Expression of type UIViewController? is unused" warning

I've got the following function which compiled cleanly previously but generates a warning with Xcode 8.
func exitViewController()
{
navigationController?.popViewController(animated: true)
}
"Expression of type "UIViewController?" is unused".
Why is it saying this and is there a way to remove it?
The code executes as expected.
TL;DR
popViewController(animated:) returns UIViewController?, and the compiler is giving that warning since you aren't capturing the value. The solution is to assign it to an underscore:
_ = navigationController?.popViewController(animated: true)
Swift 3 Change
Before Swift 3, all methods had a "discardable result" by default. No warning would occur when you did not capture what the method returned.
In order to tell the compiler that the result should be captured, you had to add #warn_unused_result before the method declaration. It would be used for methods that have a mutable form (ex. sort and sortInPlace). You would add #warn_unused_result(mutable_variant="mutableMethodHere") to tell the compiler of it.
However, with Swift 3, the behavior is flipped. All methods now warn that the return value is not captured. If you want to tell the compiler that the warning isn't necessary, you add #discardableResult before the method declaration.
If you don't want to use the return value, you have to explicitly tell the compiler by assigning it to an underscore:
_ = someMethodThatReturnsSomething()
Motivation for adding this to Swift 3:
Prevention of possible bugs (ex. using sort thinking it modifies the collection)
Explicit intent of not capturing or needing to capture the result for other collaborators
The UIKit API appears to be behind on this, not adding #discardableResult for the perfectly normal (if not more common) use of popViewController(animated:) without capturing the return value.
Read More
SE-0047 Swift Evolution Proposal
Accepted proposal with revisions
When life gives you lemons, make an extension:
import UIKit
extension UINavigationController {
func pop(animated: Bool) {
_ = self.popViewController(animated: animated)
}
func popToRoot(animated: Bool) {
_ = self.popToRootViewController(animated: animated)
}
}
Note that adding something like #discardableResult func pop(animated: Bool) -> UIViewController? will result in the same warning you are trying to avoid.
With the extension you can now write:
func exitViewController()
{
navigationController?.pop(animated: true)
}
func popToTheRootOfNav() {
navigationController?.popToRoot(animated: true)
}
Edit: Added popToRoot too.
In Swift 3, ignoring the return value of a function that has a declared return value results in a warning.
One way to opt out of this is to mark the function with the #discardableResult attribute. Since you don't have control over this function, that won't work.
The other method to get rid of the warning is to assign the value to _. This tells the compiler you know the method returns a value but you don't want to retain it in memory.
let _ = navigationController?.popViewController(animated: true)
Although it work correctly if kept as it is but the number of warning increases.
The solution is to simply replace it with underscore ( _ ) though it seems to be ugly.
Eg. _ = navigationController?.popViewController(animated: true)
Use discardableResult in this condition.
According to < Swift Programming Language > , chapter Language Reference - Attributes.
discardableResult
Apply this attribute to a function or method declaration to suppress the compiler warning when the function or method that returns a value is called without using its result.
There is also a demo in < Swift Programming Language >, chapter Language Guide - Methods.
#discardableResult
mutating func advance(to level: Int) -> Bool {
...
return true
}
Because it’s not necessarily a mistake for code that calls the advance(to:) method to ignore the return value, this function is marked with the #discardableResult attribute. For more information about this attribute, see Attributes.
If you want to go the road of extensions like CodeReaper's answer you should use #descardableResult. This keeps all the possibilities, but silences the warning.
import UIKit
extension UINavigationController {
#discardableResult func pop(animated: Bool) -> UIViewController? {
return self.popViewController(animated: animated)
}
#discardableResult func popToRoot(animated: Bool) -> [UIViewController]? {
return self.popToRootViewController(animated: animated)
}
}
Another way is you can unwrap the self.navigationController? value and call the popViewController function.
if let navigationController = navigationController {
navigationController.popViewController(animated: true)
}

Swift UIView.animateWithDuration and For-Loops

I have a group of UIImageViews that are stored in an array. I want to animate a certain amount of these ImageViews in the same way. Thus, I have been trying to use the following code:
var lowerViews: [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
var startingIndex = 1;
UIView.animateWithDuration(0.3, delay: 0.1, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
for index in startingIndex..< lowerViews.count {
lowerViews[index].frame.origin.y += 100
}
}, completion: nil)
However at this line:
for index in startingIndex..< lowerViews.count {
Xcode gives me the error:
Expected '{' to start the body of each for-loop
However, I don't believe that this is really the issue. This seems to me that it is an arbitrary syntax error that Xcode gets because I am using the for-loop inside the 'animation' parameter. Since I am still learning a lot about Swift I do not know why this wouldn't work, and so if this assumption is correct, I would like to know why and how I can get around this.
If this is not the case, please let me know, because either way I need to get around the problem.
Thanks in advance
This is a tricky error (note spaces around ..<).
for index in startingIndex ..< lowerViews.count {
will work or
for index in startingIndex..<lowerViews.count {
will work but:
for index in startingIndex..< lowerViews.count {
won't work.
The reason for this is the fact that when startingIndex..< is used, then ..< is considered to be a postfix (unary) operator (not an infix operator). Therefore the whole expression stops making sense and you start getting strange errors.
Also see what are the rules for spaces in swift

How do I call animateAlongsideTransition in Swift?

I've tried so many combinations in order to call animateAlongSideTransition in Swift for a transition coordinator. I feel like I'm missing something very stupid.
If I want to call this (from Swift docs):
func animateAlongsideTransition(_ animation: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!,
completion completion: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!) -> Bool
How would I do it? I just want to pass some things in the animation block and nothing in the completion block.
This is definitely what you want to do:
coordinator.animateAlongsideTransition({ context in
// do whatever with your context
context.viewControllerForKey(UITransitionContextFromViewControllerKey)
}, completion: nil)
You can also omit the parameters if you use variables like $0 for the first implicit parameter and so
coordinator.animateAlongsideTransition({
$0.viewControllerForKey(UITransitionContextFromViewControllerKey)
}, completion: nil)
The in syntax surprises at first, but you have to learn it only once :)
The curly brackets defines the block inside the function
You use in to separate the parameters from the block body
But as I said above, you can omit the parameters by using $0, $1, $2 and so...
It seems that there's a more verbose syntax, but it definitely not fits the Swift spirit (and I'm too lazy to post it there)
Hope it helps (and I don't forget anything...)
Edit:
Another pro tip is when the block is the only parameter, you can even omit the parentheses
(The next will not work, but it's to figure the idea)
coordinator.animateAlongsideTransition{
$0.viewControllerForKey(UITransitionContextFromViewControllerKey)
}
You do it like this (at least in Swift 1.2):
transitionCoordinator.animateAlongsideTransition({context in //things to animate, for example: view.alpha = 0.5
}, completion: nil)

animateWithDuration:animations:completion: 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 () }

Resources