Make an object/image appear and disappear randomly in Xcode - ios

I have to make a target appear and disappear randomly, e.g. in a space of 20 seconds but want to fix the flash first. The object is inserted programmatically and is just a png with collision. When the ball hits it, it disappears and the score is added. This part already works. Current code below
Spawn the enemy(is part of the function for the other 4 enemies)
let enemy1 = UIImageView(image: nil)
enemy1.image = UIImage(named: "enemy2.png")
enemy1.frame = CGRect(x: w*0.85, y: h*0.035, width: w*0.12, height: h*0.22)
self.view.addSubview(enemy1)
Add 1 to score when hit:
collisionBehaviour = UICollisionBehavior(items:[enemy1])
dynamicAnimator.addBehavior(collisionBehaviour)
collisionBehaviour.action = {
for boulderView in self.bouldersArray{
if boulderView.frame.intersects(enemy1.frame){
//enemy1.removeFromSuperview()
if enemy1.superview != nil{
self.scoreCount += 1
self.scoreCountLabel.text = "Score: \(self.scoreCount)"
enemy1.removeFromSuperview()
}
}
}
}
It's very hardcoded, please don't fix that. Tried an if statement to make it appear and disappear from subview, but it didn't work at all, just failed madly.

To make it disappear, set the alpha property to 0
enemy1.alpha = 0
To make it appear, set the alpha property to 1
enemy1.alpha = 1
if you wish to animate the disappearing use UIView Animate method
UIView.animate(withDuration: 0.3, delay: 0.5, options: .curveEaseOut, animations: {
self.enemy1.alpha = 0
}, completion: nil)

Related

Why does CGAffineTransform(scaleX:y:) cause my view to rotate as if I'd called CGAffineTransform(rotationAngle:)?

I took Paul Hudson's (Hacking with Swift) animation tutorial (His Project 15) and tried to extend it to see the effect of multiple animations layered on one another.
There are four distinct animations: scale, translate, rotate and color. Instead of a single button that cycles through all of them, as in his tutorial, I used four buttons to allow selection of each animation individually. I also modified his "undo" animation by reversing the original animation rather than using CGAffineTransform.identity, since the identity transform would undo all the animations to that point.
My problem is that when I click my c button, it does the appropriate scaling the first click but, rather than scale the penguin back to its original size on the second click, it rotates the view. Subsequent clicks of the scale button continue to rotate the view as if I were clicking the rotate button.
There are other anomalies, such as the first click of the move button moves the penguin appropriately but following that with a click of the rotate button, both rotates the penguin and moves it back to the original position. I'm not sure why it moves back but I accept that that might be my own ignorance of the animation system.
I've added print statements and put in breakpoints to debug the scale problem. Everything in the code seems to be working exactly as coded but the animations defy logic! Any help would be appreciated.
The complete program is relatively simple:
import UIKit
class ViewController: UIViewController {
var imageView: UIImageView!
var scaled = false
var moved = false
var rotated = false
var colored = false
#IBOutlet var scaleButton: UIButton!
#IBOutlet var moveButton: UIButton!
#IBOutlet var rotateButton: UIButton!
#IBOutlet var colorButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "penguin"))
imageView.center = CGPoint(x: 200, y: 332)
view.addSubview(imageView)
}
#IBAction func tapped(_ sender: UIButton) {
sender.isHidden = true
var theAnimation: ()->Void
switch sender {
case scaleButton:
theAnimation = scaleIt
case moveButton:
theAnimation = moveIt
case rotateButton:
theAnimation = rotateIt
case colorButton:
theAnimation = colorIt
default:
theAnimation = { self.imageView.transform = .identity }
}
print("scaled = \(scaled), moved = \(moved), rotated = \(rotated), colored = \(colored)")
UIView.animate(withDuration: 1, delay: 0, options: [], animations: theAnimation) { finished in
sender.isHidden = false
}
}
func scaleIt() {
print("scaleIt()")
let newScale: CGFloat = self.scaled ? -1.5 : 1.5
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
self.scaled.toggle()
}
func moveIt() {
print("moveIt()")
let newX: CGFloat = self.moved ? 0 : -50
let newY: CGFloat = self.moved ? 0 : -150
self.imageView.transform = CGAffineTransform(translationX: newX, y: newY)
self.moved.toggle()
}
func rotateIt() {
print("rotateIt()")
let newAngle = self.rotated ? 0.0 : CGFloat.pi
self.imageView.transform = CGAffineTransform(rotationAngle: newAngle)
self.rotated.toggle()
}
func colorIt() {
print("colorIt()")
let newAlpha: CGFloat = self.colored ? 1 : 0.1
let newColor = self.colored ? UIColor.clear : UIColor.green
self.imageView.alpha = newAlpha
self.imageView.backgroundColor = newColor
self.colored.toggle()
}
}
By any combination of clicking the buttons, I can only arrive at 10 configurations of the Penguin (five with CGAffineTransforms and the same five modified by the color button).
I'll use these images to answer some of your questions. I've tried to label these images "Configuration1A, Configuration2A, ... Configuration1B", where the B configurations are identical to the A configurations but with the color button's effect. Perhaps the labels will show up in my post (I'm very new to posting on StackOverflow).
Here are the five configurations with the first one repeated using the color button:
I first tried repeated button pressings. For each series of pressing a given button, I first returned the penguin to its original position, size, angle and color. For these repeated button pressings, the behavior I observed was as follows:
Button Observed behavior
scale Penguin scales from Configuration1A to Configuration2A (as expected).
scale Penguin rotates by pi from Configuration2A to Configuration4A (WHAT???).
scale Penguin rotates by pi from Configuration4A to Configuration2A (WHAT???).
scale ... steps 2 and 3, above, repeat indefinitely (WHAT???).
move Penguin moves (-50, -150), Configuration1A to Configuration3A (as expected).
move Penguin moves (50, 150) Configuration3A to Configuration1A (as expected).
move ... behavior above repeats indefinitely (as expected).
rotate Penguin rotates by pi, Configuration1A to Configuration5A (as expected).
rotate Penguin rotates by pi, Configuration5A to Configuration1A (as expected).
rotate ... behavior above repeats indefinitely (as expected).
color Penguin's color changes, Configuration1A to Configuration1B (as expected).
color Penguin's color changes, Configuration1B to Configuration1A (as expected).
color ... behavior above repeats indefinitely (as expected).
The opposite of scale 1.5 (50% bigger) is not -1.5, it's 0.5. (50% of it's original size)
Actually, wouldn't you want it to alternate between a scale of 1.5 (50% bigger) and 1.0 (normal size?)
Assuming you do want to alternate between 50% bigger and 50% smaller, change your scaleIt function to:
func scaleIt() {
print("scaleIt()")
let newScale: CGFloat = self.scaled ? -1.5 : 0.5
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
self.scaled.toggle()
}
When you set the X or Y scale to a negative number, it inverts the coordinates in that dimension. Inverting in both dimensions will appear like a rotation.
As somebody else mentioned, the way you've written your functions, you won't be able to combine the different transforms. You might want to rewrite your code to apply the changes to the view's existing transform:
func scaleIt() {
print("scaleIt()")
// Edited to correct the math if you are applying a scale to an existing transform
let scaleAdjustment: CGFloat = self.scaled ? -1.5 : 0.66666
self.imageView.transform = self.imageView.transform.scaledBy(x: scaleAdjustment, y: scaleAdjustment)
self.scaled.toggle()
}
Edit:
Note that changes to transforms are not "commutative", which is a fancy way of saying that the order in which you apply them matters. Applying a shift, then a rotate, will give different results than applying a rotate, then a shift, for example.
Edit #2:
Another thing:
The way you've written your functions, they will set the view to its current state, and then invert that current state for next time (you toggle scaled after deciding what to do.)
That means that for function like moveIt() nothing will happen the first time you tap the button. Rewrite that function like this:
func moveIt() {
print("I like to moveIt moveIt!")
self.moved.toggle() //Toggle the Bool first
let newX: CGFloat = self.moved ? 0 : -50
let newY: CGFloat = self.moved ? 0 : -150
self.imageView.transform = self.imageView.transform.translatedBy(x: newX, y: newY)
}

Please, how do I change this animation from kotlin to swift?

I investigated but I could not find anything like this for Swift. Can you please help me make this animation in Swift?
Val animador = ValueAnimator.ofFloat(0.0f ,1.0f)
animador.repeatCount = ValueAnimator.INFINITE
animador.interpolator= LinearInterpolator()
animador.duration = 10000L
animador.addUpdateListener { animation ->
val progreso = animador.animatedValue as Float
Val anchura = fondo!!.width
fondo!!.traslationx = transicionX
fondo2!!.traslationx = transicion X -anchura
}
animador.start()
This may not be exactly right, but try this:
let anchura = fondo.frame.width
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
fondo.frame.origin.x = CGFloat(transicionX)
fondo2.frame.origin.x = CGFloat(transicionX) - anchura
})
Swift animations start automatically (no need to start() or anything).
Edit:
Inside the animations block, you just put the final value that you want. This means that you don't need to calculate the progress or anything, and you probably don't even need a transicionX variable. Instead, try this:
let anchura = fondo.frame.width /// let's say this is 50
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
fondo.frame.origin.x += anchura /// move fondo to the right by 50
fondo2.frame.origin.x -= anchura /// move fondo2 to the left by 50
})
Another edit: made it += and -= to animate on top of existing values

Why are my UI elements not resetting correctly after being animated/scaled off screen?

So I'll give as much information about this project as I can right up front. Here is an image of a section of the storyboard that is relevant to the issue:
And here is the flow of the code:
1) A user plays the game. This scrambles up the emoji that are displayed and will eventually hide all of the emoji on the right side.
2) When someone wins the game, it calls
performSegue(withIdentifier: "ShowWinScreenSegue", sender: self)
Which will perform the segue the red arrow is pointing to. This segue is a modal segue, over current content, cross dissolve.
3) Stuff goes on here, and then I try to get back to the game screen so the user can play another game. Here is my current code for that
// self.delegate is the GameController that called the segue
// it's set somewhere else in the code so I can call these reset functions
GameController.gs = GameState()
guard let d = self.delegate else {
return
}
d.resetGameToMatchState()
dismiss(animated: true, completion: {
print("Modal dismiss completed")
GameController.gs = GameState()
self.delegate?.resetGameToMatchState()
})
So here's where the issue is. You can see that I have to call delegate?.resetGameToMatchState() twice for anything to actually happen. If I remove the top one, nothing happens when I call the second one and vice-versa. What makes this so annoying is that the user will see a weird jump where all the ui goes from the old state to the new state because it's updating so late and spastically.
What I've tried so far
So this whole issue has made me really confused on how the UI system works.
My first thought was that maybe the function is trying to update the UI in a thread that's executing too early for the UI thread. So I put the whole body of resetGameToMatchState in a DispatchQueue.main.async call. This didn't do anything.
Then I thought that it was working before because when the WinScreenSegue was being dismissed before (when it was a "show" segue) it was calling GameController's ViewDidAppear. I tried manually calling this function in the dismiss callback, but that didn't work either and feels really hacky.
And now I'm stuck :( Any help would be totally appreciated. Even if it's just a little info that can clear up how the UI system works.
Here is my resetGameToMatchState():
//reset all emoji labels
func resetGameToMatchState() {
DispatchQueue.main.async {
let tier = GameController.gs.tier
var i = 0
for emoji in self.currentEmojiLabels! {
emoji.frame = self.currentEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
i=0
for emoji in self.goalEmojiLabels! {
emoji.frame = self.goalEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
//match state
for i in 1...4 {
if GameController.gs.currentEmojis[i] == GameController.gs.goalEmojis[i] {
self.currentEmojiLabels?.findByTag(tag: i)?.isHidden = true
}
}
//reset highlight
let f = self.highlightBarInitFrame
let currentLabel = self.goalEmojiLabels?.findByTag(tag: tier)
let newSize = CGRect(x: f.origin.x, y: (currentLabel?.frame.origin.y)!, width: f.width, height: (currentLabel?.frame.height)! )
self.highlightBarImageView.frame = newSize
//update taps
self.updateTapUI()
//update goal and current emojis to show what the current goal/current selected emoji is
self.updateGoalEmojiLabels()
self.updateCurrentEmojiLabels()
}
}
UPDATE
So I just found this out. The only thing that isn't working when I try to reset the UI is resetting the right side emoji to their original positions. What I do is at the start of the app (in viewDidLoad) I run this:
for emoji in currentEmojiLabels! {
currentEmojiLabelInitFrames.append(emoji.frame)
}
This saves their original positions to be used later. I do this because I animate them to the side of the screen before hiding them.
Now when I want to reset their positions, I do this:
var i = 0
for emoji in self.currentEmojiLabels! {
emoji.frame = self.currentEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
this should set them all to their original frame and scale, but it doesn't set the position correctly. It DOES reset the scale though. What's weird is that I can see a tiny bit of one of the emoji off to the left of the screen and when they animate, they animate from far off on the left. I'm trying to think of why the frames are so off...
UPDATE 2
So I tried changing the frame reset code to this:
emoji.frame = CGRect(x: 25, y: 25, width: 25, height: 25)
Which I thought should reset them correctly to the top left, but it STILL shoves them off to the left. This should prove that the currentEmojiLabelInitFrames are not the issue and that it has something to do with when I'm setting them. Maybe the constraints are getting reset or messed up?
Your first screen, GameController, should receive a viewWillAppear callback from UIKit when the modal WinScreenController is being dismissed.
So its resetGameToMatchState function could simply set a property to true, then your existing resetGameToMatchState could move into viewWillAppear, checking first if the property is being set.
var resetNeeded: Bool = false
func resetGameToMatchState() {
resetNeeded = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// reset code here
}
TLDR; Reset an element's scale BEFORE resetting it's frame or else it will scale/position incorrectly
Finally figured this out. Here's a bit more background. When an emoji is animated off the screen, this is called:
UIView.animate(withDuration: 1.5, animations: {
let newFrame = self.profileButton.frame
prevLabel?.frame = newFrame
prevLabel?.transform = CGAffineTransform(scaleX: 0.1, y: 0.1);
}) { (finished) in
prevLabel?.isHidden = true
}
So this sets the frame to the top left of the screen and then scales it down. What I didn't realize is that when I want to reset the element, I NEED to set the scale back to normal before setting the frame. Here is the new reset code:
for emoji in self.currentEmojiLabels! {
emoji.transform = CGAffineTransform(scaleX: 1, y: 1) //this needs to be first
emoji.frame = self.currentEmojiLabelInitFrames[i] //this needs to be after the scale
emoji.isHidden = false
i+=1
}

iOS animation malfunctioning, Y-Coordinate Problems

For hours I've been trying to solve the smallest animation glitch. My code successfully moves the view off screen, then animates it back in. It gets the x-coordinations right but the Y axis has behavior I don't understand. Here's the code:
func listTrans_slideIn (slideFrom: String) {
//var newFrame = tableView.frame
tableView.frame.origin.x = 1000
//tableView.frame.origin.y = 100
print("Table pushed to side")
UIView.animateKeyframesWithDuration(1.375 /*Total*/, delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 1/1, animations:{
self.tableView_toTLG_Top.constant = 130
self.tableView_toSV_Left.constant = 0
self.tableView_toSV_Right.constant = 0
self.setupView()
//newFrame.origin.y = self.hdrBox.frame.height+50
//newFrame.origin.x = 0
//self.tableView.frame = newFrame
self.tableView.layoutIfNeeded()
})
},
completion: { finished in
if (!finished) { return }
})
}
The weird behavior is that if I put the correct y-coordinate in the animation keyframe, it comes in too high but then settles at the correct coordinate. If I put in a y-Coordinate that is too low, it comes in at the correct height but then settles too low.
As you can see, I've tried using frames and constraints. I've tried changing the height that I move it off screen to, but that seems to have no effect.
Anyone have any idea why I've spent half my day seeing this bug?
Can you try something like this :
// Use the same value here and below, it moves it out of the screen
self.tableView.center.y -= 500
UIView.animateWithDuration(1.0, animations: { () -> Void in
// Then it comes back
self.tableView.center.y += 500
})
This turned out to be an order of operations problem. The solution required that I move the animation to take place in the viewDidAppear.
Other notes:
To make the animation look as smooth as possible, consider turning off the segue's animation.
Also make sure you're running the segue call and animation in your main thread so that everything happens smoothly and without delay

IOS Autoscroll Label scrolling from right to left

I am using the library AutoScrollLabel for autoScrollLabel like marquee and its working perfect when I need the label to scroll from left to right.
But when I have to use the direction from right to left I have facing this problem:
the label begin from the last word to the first?
Why?
You can find below my code:
self.autoScrollLabel.textColor = [UIColor whiteColor];
self.autoScrollLabel.labelSpacing = 1; // distance between start and end labels
self.autoScrollLabel.pauseInterval = 0;
self.autoScrollLabel.scrollSpeed = 70; // pixels per second
self.autoScrollLabel.textAlignment = NSTextAlignmentCenter;
self.autoScrollLabel.fadeLength = 0;
self.autoScrollLabel.scrollDirection = CBAutoScrollDirectionRight;
self.autoScrollLabel.text = [NSString stringWithFormat:#"%#%#",#"",#"hello hello1 hello1 heeloo3 hello3 hebe hehehe hgdghdhg he e e hehee hehee hehehehehehhe"];
I've never used AutoScrollLabel class, but I think in order to it worked properly you should change property
self.autoScrollLabel.scrollDirection = CBAutoScrollDirectionLeft
when you scroll to the left
for Swift you you can call this method :
func startMarqueeLabelAnimation() {
DispatchQueue.main.async(execute: {
UIView.animate(withDuration: 20.0, delay: 1, options: ([.curveLinear, .repeat]), animations: {() -> Void in
self.marqueeLabel.center = CGPoint(x: 0 - self.marqueeLabel.bounds.size.width / 2, y: self.marqueeLabel.center.y)
}, completion: nil)
})
}

Resources