Example of code for animation:
func startAnimation(index: Int) {
var jumpImages = ["Jump_1","Jump_2","Jump_3","Jump_4","Jump_5","Jump_6","Jump_7","Jump_8","Jump_9","Jump_10"]
if index == 1 {
jumpImages = ["Jump_11","Jump_21","Jump_31","Jump_41","Jump_51","Jump_61","Jump_71","Jump_81","Jump_91","Jump_101"]
}
var images = [UIImage]()
for image in jumpImages{
images.append(UIImage(named: image)!)
}
self.imageView.frame.origin.y = self.imageView.frame.origin.y + 100.0
self.imageView.animationImages = images
self.imageView.animationDuration = 2.0
self.imageView.animationRepeatCount = 1
self.imageView.startAnimating()
}
startAnimation(index: 0)
...
startAnimation(index: 1)
NOTE: Both startAnimation calls are in the main thread but not in the same run loop.
In my case I want to change jumpImages to another set of images. I should cancel the previous animation and start the new one but it looks like I set the last image in jumpImages sequence instead.
How to solve this issue?
Okay, I think I'm understanding your problem now -- from your statement, "[...] but it looks like I set the last image in jumpImages sequence instead," I think you mean that when you call startAnimation(index: 1), you are seeing just the last frame of the index 0 animations, and none of the index 1 animations.
Assuming that is right, your problem here going to be a race condition.
When you call startAnimation() then second time with index 1, the first animation may or may not still be in progress.
The solution will be to call self.imageView.stopAnimating() before you change all the images and start the new animation. The best practice would be to check the imageView.isAnimating flag before you call it. Something like this:
func startAnimation(index: Int) {
var jumpImages = ["Jump_1","Jump_2","Jump_3","Jump_4","Jump_5","Jump_6","Jump_7","Jump_8","Jump_9","Jump_10"]
if index == 1 {
jumpImages = ["Jump_11","Jump_21","Jump_31","Jump_41","Jump_51","Jump_61","Jump_71","Jump_81","Jump_91","Jump_101"]
}
var images = [UIImage]()
for image in jumpImages{
images.append(UIImage(named: image)!)
}
if self.imageView.isAnimating {
self.imageView.stopAnimating()
}
self.imageView.frame.origin.y = self.imageView.frame.origin.y + 100.0
self.imageView.animationImages = images
self.imageView.animationDuration = 2.0
self.imageView.animationRepeatCount = 1
self.imageView.startAnimating()
}
startAnimation(index: 0)
...
startAnimation(index: 1)
Also, since you're in a function and not within a closure, you can remove all those references to self., which makes things a bit shorter:
func startAnimation(index: Int) {
var jumpImages = ["Jump_1","Jump_2","Jump_3","Jump_4","Jump_5","Jump_6","Jump_7","Jump_8","Jump_9","Jump_10"]
if index == 1 {
jumpImages = ["Jump_11","Jump_21","Jump_31","Jump_41","Jump_51","Jump_61","Jump_71","Jump_81","Jump_91","Jump_101"]
}
var images = [UIImage]()
for image in jumpImages{
images.append(UIImage(named: image)!)
}
if imageView.isAnimating {
imageView.stopAnimating()
}
imageView.frame.origin.y = imageView.frame.origin.y + 100.0
imageView.animationImages = images
imageView.animationDuration = 2.0
imageView.animationRepeatCount = 1
imageView.startAnimating()
}
startAnimation(index: 0)
...
startAnimation(index: 1)
Related
I want to animate two textviews, TV1 is on screen and TV2 is offscreen. When a user clicks a button TV1 should reduce its size to 1/2 and TV2 should move from offscreen to lower half of the parent view.
I have this code but only the first view is being animated:
func toggleFrames() {
var frame1H = self.view.frame.size.height
var frame2H = CGFloat(0.0)
var frame2Y = CGFloat(0.0)
var txtV1Frame = txtContents.frame
var txtV2Frame = txtChanges.frame
if bViewingChanges {
frame1H = frame1H/2
frame2Y = (self.view.frame.size.height/2) - 10.0
frame2H = frame1H
}
txtV1Frame.size.height = frame1H
txtV2Frame.size.height = frame2H
txtV2Frame.origin.y = 550.0
NSAnimationContext.runAnimationGroup({_ in
NSAnimationContext.current.duration = 0.5
txtContents.animator().frame = txtV1Frame //reduce size of text view 1
txtChanges.animator().frame = txtV2Frame //<--Grumble, not doing anything
}, completionHandler:{
print("Done animation")
})
}
I'm trying to animate something that spins / left to right, but whenever I call
spinLeft() or spinRight() then the animation always starts from frame 0.
In other words, I want to be able to say spin something 4 out of 10 frames, stop, then
spin in the opposite direction, FROM frame 4. Right now, it resets to frame 0.
var textures = [SKTexture]() // Loaded with 10 images later on.
var sprite = SKSpriteNode()
func spinLeft() {
let action = SKAction.repeatForever(.animate(with: textures, timePerFrame: 0.1))
sprite.run(action)
}
func spinRight() {
let action = SKAction.repeatForever(.animate(with: textures, timePerFrame: 0.1)).reversed()
sprite.run(action)
}
You could do this (Syntax may be a little off, but you get the point):
The key here is the .index(of: ... ) which will get you the index.
func spinUp() {
let index = textures.index(of: sprite.texture)
if index == textures.count - 1 {
sprite.texture = textures[0]
}
else {
sprite.texture = textures[index + 1]
}
}
func spinDown() {
let index = textures.index(of: sprite.texture)
if index == 0 {
sprite.texture = textures[textures.count - 1]
}
else {
sprite.texture = textures[index - 1]
}
}
func changeImage(_ isUp: Bool, _ amountOfTime: CGFloat) {
let wait = SKAction.wait(duration: amountOfTime)
if isUp {
run(wait) {
self.imageUp()
}
}
else {
run(wait) {
self.imageDown()
}
}
}
If you use something like a swipe gesture recognizer, you can use it's direction to set the isUp Bool value and the velocity of that swipe for the amountOfTime for the changeImage function.
The changeImage will only change the image once, so you will need to handle this somewhere else, or create another function if you want it to continuously spin or die off eventually.
Hope this helps!
I want to show a note/label on my tableview background when/if there is no data to load, or data is being fetched etc.
I can't see what am I doing wrong here. Xcode is showing a warning "Will never be executed" on this line of code: if mostUpTodateNewsItemsFromRealm?.count < 1 {
Here is the method.
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// create a lable ready to display
let statusLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
statusLabel.textColor = globalTintColor
statusLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = statusLabel
self.tableView.backgroundView?.backgroundColor = colourOfAllPickerBackgrounds
// 1) Check if we have tried to fetch news
if NSUserDefaults.standardUserDefaults().valueForKey("haveTriedToFetchNewsForThisChurch") as! Bool == false {
statusLabel.text = "Busy loading news..."
} else {
// If have tried to fetch news items = true
// 2) check church has channels
let numberOfChannelsSubscribedToIs = 0
if let upToDateSubsInfo = upToDateChannelAndSubsInfo {
let numberOfChannelsSubscribedToIs = 0
for subInfo in upToDateSubsInfo {
if subInfo.subscribed == true {
numberOfChannelsSubscribedToIs + 1
}
}
}
if numberOfChannelsSubscribedToIs < 1 {
// if no channels
// show messsage saying you aren't subscribed to any channels.
statusLabel.text = "Your church hasn't setup any news channels yet."
} else {
// 3) if we have tried to fetch news AND the church DOES have channels
// check if we have any news items to show
if mostUpTodateNewsItemsFromRealm?.count < 1 {
// If no news items
statusLabel.text = "Your church hasn't broadcast and news yet."
} else {
// if have tried to fetch AND church has channels AND there ARE news items
// remove the background image so doesn't show when items load.
self.tableView.backgroundView = nil
}
}
}
// in all circumstances there will be one section
return 1
}
Your code first created a constant:
let numberOfChannelsSubscribedToIs = 0
And then you check whether it is less than 1:
if numberOfChannelsSubscribedToIs < 1
Since it is a constant, it will never change. This means that the if clause will always be executed. Thus, the else clause will never be executed.
So first you need to make this constant variable:
var numberOfChannelsSubscribedToIs = 0
Then just change this:
if subInfo.subscribed == true {
numberOfChannelsSubscribedToIs + 1
}
to this:
if subInfo.subscribed == true {
numberOfChannelsSubscribedToIs += 1
}
This way, numberOFChannelSubscribedToIs can be some number other than 0. And the else clause can be executed.
var and let are very different!
I am working on a small SpriteKit game.
I have a "Tips" section on the Home Screen that I want to pulse in and out, each time displaying different Tips.
I have a method that works, which I wrote myself, but it's messy and I'm sure there's an better way it could be done. I was hoping someone could show me a way that maybe I missed (or went a long way around doing).
This is how I currently do it:
func createTipsLabels(){
//create SKLabelNodes
//add properties to Labels
//tip1Label... etc
//tip2Label... etc
//tip3Label... etc
//now animate (or pulse) in tips label, one at a time...
let tSeq = SKAction.sequence([
SKAction.runBlock(self.fadeTip1In),
SKAction.waitForDuration(5),
SKAction.runBlock(self.fadeTip1Out),
SKAction.waitForDuration(2),
SKAction.runBlock(self.fadeTip2In),
SKAction.waitForDuration(5),
SKAction.runBlock(self.fadeTip2Out),
SKAction.waitForDuration(2),
SKAction.runBlock(self.fadeTip3In),
SKAction.waitForDuration(5),
SKAction.runBlock(self.fadeTip3Out),
SKAction.waitForDuration(2),
])
runAction(SKAction.repeatActionForever(tSeq)) //...the repeat forever
}
//put in separate methods to allow to be called in runBlocks above
func fadeTip1In() { tip1Label.alpha = 0; tip1Label.runAction(SKAction.fadeInWithDuration(1)) ; print("1") }
func fadeTip1Out(){ tip1Label.alpha = 1; tip1Label.runAction(SKAction.fadeOutWithDuration(1)); print("2") }
func fadeTip2In() { tip2Label.alpha = 0; tip2Label.runAction(SKAction.fadeInWithDuration(1)) ; print("3") }
func fadeTip2Out(){ tip2Label.alpha = 1; tip2Label.runAction(SKAction.fadeOutWithDuration(1)); print("4") }
func fadeTip3In() { tip3Label.alpha = 0; tip3Label.runAction(SKAction.fadeInWithDuration(1)) ; print("5") }
func fadeTip3Out(){ tip3Label.alpha = 1; tip3Label.runAction(SKAction.fadeOutWithDuration(1)); print("6") }
How can I optimise this?
There is no need to create multiple labels nor do multiple actions, just create an array of what you want to do, and iterate through it.
func createTipsLabels()
{
let tips = ["1","2","3","4","5"];
var tipCounter = 0
{
didSet
{
if (tipCounter >= tips.count)
{
tipCounter = 0;
}
}
}
tipLabel.alpha = 0;
let tSeq = SKAction.sequence([
SKAction.runBlock({[unowned self] in self.tipLabel.text = tips[tipCounter]; print(tips[tipCounter]); tipCounter+=1;}),
SKAction.fadeInWithDuration(1),
SKAction.waitForDuration(5),
SKAction.fadeOutWithDuration(1),
SKAction.waitForDuration(2)
])
tipLabel.runAction(SKAction.repeatActionForever(tSeq)) //...the repeat forever
}
I am trying to write a routine to roll a die. I want the face to change several times before landing on the final number. The code below does not show the die changing faces. I added the sleep statement in hopes that it would give it time to update but it just stays with the initial face until the end and then shows the final face. Is there a statement to add after changing the texture to force the view to update?
func rollDice() {
var x: Int
for var i = 0; i < 50; i++
{
x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
pieces[3].texture = dice[0].faces[x]
NSThread.sleepForTimeInterval(0.1)
print(x)
}
}
As has been pointed out in the comments, the screen only redraws on the main thread. Therefore, you could let the dice rolls take place on a background thread, and redraw the screen on the main thread:
func rollDice() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
for i in 0..<50 {
let x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
dispatch_async(dispatch_get_main_queue) {
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
pieces[3].texture = dice[0].faces[x]
NSThread.sleepForTimeInterval(0.1)
print(x)
}
}
}
}
Thank you for the help. After reading some about Timers, I came up with the following code:
func rollDice() {
rollCount = 0
timer = NSTimer.scheduledTimerWithTimeInterval(0.05, target:self, selector: "changeDie", userInfo: nil, repeats: true)
}
func changeDie () {
x = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
print(x)
let texture = dice[0].faces[x]
let action:SKAction = SKAction.setTexture(texture)
pieces[3].runAction(action)
++rollCount
if rollCount == 20 {
timer.invalidate()
}
}