Swift UIView.animateWithDuration and For-Loops - ios

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

Related

Expected Sequence expression swift 3

I'm using a loop to set the original image for tab bar items so the unselected state isn't gray (my original icons are white). However, it looks like the recent Xcode 8 update broke the code:
for (items in 0 ..< tabBar.items!.count ){
let tabItemIndex = tabBar.items![items]
tabItemIndex.image = tabItemIndex.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
}
}
I'm getting the following errors on the first line: Expected 'in' after for-each pattern, Expected Sequence expression for for-each loop, and Expected pattern.
Can anyone please help me fix this solution? It worked great until today.
Thanks!!
for x in y is an actual expression in Swift. You cannot break it up with parentheses, for (x in y) — that separates the for from the in and causes the expression to seem like nonsense to the compiler.
So, delete the parentheses and all will be well.
You have some issues on how you're creating your loop, and some very unsafe forced unwrapping. Try this:
if let items = tabBar.items {
for tabBarItem in items {
if let image = tabBarItem.image {
tabBarItem.image = image.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
}
}
}
Or even cleaner, like this:
tabBar.items?.forEach { tabBarItem in
if let image = tabBarItem.image {
tabBarItem.image = image.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
}
}
In Swift, unlike in Objective-C, there are no parentheses involved in this structure. Remove the parentheses to fix your syntax error.
However, there are style and safety issues here other than the syntax error: see picciano's answer for a much cleaner and safer way to rewrite the loop.

How to make sequence in Swift?

I'm trying to make a simple sequence of two actions but Xcode is saying that I have an extra argument in call. I tried to translate Apples Obj-C example into Swift and it's not going very well. What am I doing wrong?
func dead() {
let animateAction = SKAction.animateWithTextures(self.catArray, timePerFrame: 0.09)
let ending = SKAction.runBlock(self.gameOver)
let sequence = SKAction.sequence(actions: animateAction, ending)
self.cat.runAction(sequence)
}
The sequence method of SKAction requires an Array of AnyObject. To fix this, you will need to call the method with the two actions you declared earlier in an array like this:
let sequence = SKAction.sequence([animateAction, ending])
self.cat.runAction(sequence)

Swift: Random number arrays inside images and variables with a for loop

I am creating a game in which, depending on the number of 'swipes' chosen to do, (let's say 3), 3 different patterns show on the screen, one by one. I am working on developing the first pattern.
So I have this:
if (swipes.no_of_swipes) == 3 {
swipeArray = Array<UInt32>(count: 3, repeatedValue: 0)
for i in 0 ..< 3 {
swipeArray[i] = arc4random_uniform(84)}
}
As far as I am aware, this code creates an array with three UInts which can be accessed by doing swipeArray[0], swipeArray[1], and swipeArray[2]. My first question is how long will this swipeArray stay the same? Until the close the view? Should I have a 'refresh button' when the user loses - and if so, how would I make one?
Then I have a property observer. You will notice the for loop, which I am using to keep code concise. I understand that I could do something like x++ somewhere in here so that it will go through each one.
var playBegin: Bool = false{
didSet {
if playBegin == true {
println("\(playBegin)")
var swipes = Menu()
if (swipes.no_of_swipes) == 3 {
for i in 0 ..< 3 {
patternRoom.image = UIImage(named: "pattern\(swipeArray[x])")
//rest of code
}
}
}
The pattern image comes from a set of 84 images named like pattern7 and pattern56. My second question is, how could I code the for loop to go through each swipeArray[x].
Thank you in advance,
Will
how long will this swipeArray stay the same?
This is a bit too open ended. It’ll stay the same until you assign a new value to it, either from this same bit of code or a different part. Only you can know when that will be, by looking at your code.
Since you express an interest in keeping the code concise, here’s a couple of code tips.
You might think about writing your first snippet’s loop like this:
swipeArray = (0..<swipes.no_of_swipes).map { _ in
arc4random_uniform(84)
}
This combines creating a new array and populating the values. By the way, just in case you don’t realize, there’s no guarantee this array won’t contain the same value twice.
It’s also probably better to make swipeArray of type [Int] rather than [UInt32], and to convert the result of arc4random to an Int straight away:
Int(arc4random_uniform(84))
Otherwise the UInt32s will probably be a pain to work with.
For your second for loop, you can do this:
for i in swipeArray {
patternRoom.image = UIImage(named: "pattern\(i)")
// rest of code
}
When writing Swift, usually (but not always), when you find yourself using array[x] there’s a better more expressive way of doing it.

Swift playground execution speed

Is there a way to increase execution speed of a playground?
I want to iterate many cycles and not to wait 10 minutes.
For example:
import UIKit
var count = 0
for var i = 0; i < 1000000000; i++ {
count++
}
This code will execute a way too long. But I want to get quick result.
One of the biggest performance killer is the output at the right side of the playground. Now I will show you how to minimize this output.
See at the end for your example code.
Best Performance
The most performant way is to make all the performance critical code in a .swift file inside the Sources folder in the playground.
Note: In order to use the functions, classes, properties and methods from the Sources folder you have to mark them public. If you want to subclass a class it has to be marked open.
Good Performance but ugly code
The following method (I think this is not official/intended) can be used to disable the playground output but also leads to ugly code. However it is good for temporary disabling the output.
There are two main ways (and two tricks) to achieve the minimum amount of output (If you find a better way let us know):
Use parenthesis around Void (or Void?) expressions like assignments (normally leads to no output, see also 3.).
var x = 0 // output: 0
(x = 1) // NO output
(x = 2 * x - 1) // NO output
(x.negate()) // NO output
Note: In Swift an assignment returns Void and in case of optional chaining it is Void?.
var x: (Int, Int)? = nil
if (x?.0 = 0) != nil {
// assignment was successful (x!=0 and now x=(0, x.1))
} else {
// assignment was not successful (x==nil)
}
Initialize and declare variables separately.
var x: Int // NO output
(x = 0) // NO output
If 1. does not work add an additional no-op (no operation) line above or below ().
This happens in single line closures (and probably in some other contexts) for example: (see also the code below)
[1, 4, 5, 6].mmap{
() // without this line the line below would yield to an output
($1 = $0 + 1)
} as [Int]
Instead of wrapping every line in parenthesis you can also use a tuple of all the expressions which is then assigned to a variable:
var a: Any // this may be a useful definition in this context
var x: Int
var y: Int
(a = (x = 0,
y = 1,
x = y + 1,
y = x*x))
However this could lead to a indentation disaster...
Where it does not work (I've found no way how to remove the output; This list is probably not complete):
returns in functions and closures
Declaration of Optional variables e.g.: var x: Int?
An example of a new map method on Sequence
Usage: See above at Point 3.
The signature of Sequence.map is
func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]
Since I have not found a way how to remove the output of returns one can use a closure with an inout argument (get the "return" value with an assignment). A possible signature could then be:
func mmap<U>(_ transform: (Element, inout U?) -> ()) -> [U]
so we can pass nil in the inout argument since it is a good default for every possible U without imposing a constraint on U which could require an instance generator (e.g.: init() { ... }).
Unfortunately Swfit has a hard time to infer U so you would need to help the compiler with explicit type annotations. In addition var newElement: U? does return nil in the sidebar.
Now I will use Any instead of U?:
extension Sequence {
// ATTENTION: this is not as performant as the normal `map`!
func mmap<U>(transform: (Element, inout Any) -> ()) -> [U] {
var result: [U]
(result = [U]())
for element in self {
var newElement: Any
(newElement = 0) // some placeholder element
(transform(element, &newElement))
// assume the inout element to be of type `U`
(result.append(newElement as! U))
}
return result // the ONLY output in this method
}
}
Your example code
Using Swift 4
var count = 0
for i in 0..<1_000_000_000 {
(count += 1)
if count % 100_000 == 0 {
// print only every 100_000th loop iteration
print(count)
}
}
Without the parenthesis: about 10.000 loop iterations per second
With parenthesis: about 10.000.000 loop iterations per second !!!
I feel your pain, I was playing around with printing 2D functions to [Double] then converting to UIImageView. One of the steps was iterating over millions of pixels, and it took forever.
Anything computationally intensive, or repetitive, or potentially time consuming should be put in the "Sources" folder of the playground. That way the code is precompiled before your playground is run. Put the output of that for loop in a public function that you can call from the playground. Then you won't have to sit there watching the playground count all the times it went through the for loop.
I had a such problem and I have solved it after days of trials. All I needed to do is move all my code in folder Sources of playground. So, after that the execution speed was enhanced.
I hope it helps you.
Note: don't forget to use open classes.
To speed up the build in Xcode Playground and prevent the loading icon to keep spinning forever:
go to the sidebar on the right, and change iOS to macOS in Playground Settings
instead of importing UIKit, import Foundation
(this would work if you're not trying to use specific stuff from the UIKit framework)

UIView.animateWithDuration completion

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.

Resources