I have an image that I want to move 10 separate times in a row. Each time I want it to do a check, as notated by the print statement of checkForStuff. I noticed that the loop starts and it times out while the first of the 10 moves occurs. Any thoughts on how to chain animations, or do I need to manually write each chain in the sequential closure?
#objc func moveBox(sender: UIButton!) {
print("Started")
let imageToMove = UIImageView()
imageToMove.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
imageToMove.backgroundColor = .red
self.view.addSubview(imageToMove)
var counter = 0
var altitude = 100
while counter <= 10 {
print("looping")
UIView.animate(withDuration: 0.5, animations: {
imageToMove.frame = CGRect(x: 50, y: altitude, width: 50, height: 50)
}, completion: { finished in
counter += 1
altitude += 50
print("Check For Stuff")
})
}
}
In short, in should move 50 pixels down, execute the closure, move 50 pixels down again. and again, and again.
Try Recursion as suggested by #matt
func animate(view: UIView, altitude: Int, numberOfTime: Int ) {
if numberOfTime == 0 { return }
UIView.animate(withDuration: 0.5, animations: {
view.frame = CGRect(x: 50, y: altitude, width: 50, height: 50)
}, completion: { finished in
self.animate(view: view, altitude: altitude + 50, numberOfTime: numberOfTime - 1)
})
}
Use this as
#objc func moveBox(sender: UIButton!) {
print("Started")
let imageToMove = UIImageView()
imageToMove.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
imageToMove.backgroundColor = .red
self.view.addSubview(imageToMove)
animate(view: imageToMove, altitude: 100, numberOfTime: 10)
}
Related
I am trying to update the progress bar after the user selects the desired value.
Values are taken from model and assigned to the label:
let volumeArray = [20, 50, 100, 150, 200, 250, 300, 330, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1500, 2000]
Custom progress bar code:
let center = view.center
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 1.5 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = .round
view.layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.systemBlue.cgColor
shapeLayer.lineWidth = 10
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
shapeLayer.strokeEnd = 0
view.layer.addSublayer(shapeLayer)
But how can I make sure that the progress bar is updated when the user selects a value. For example, from an array let volumeArray = [20, 50, 100, 150, 200, 250, 300, 330, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1500, 2000]
But how can I make sure that the progress bar is updated when the user selects a value. For example, the user selected the number 100 from the array. The maximum value that the user must type, for example, is 2000. And based on the maximum value and the value selected by the user, the progress bar is updated by the corresponding percentage. I wrote a function
func updateProgress() {
if progressBar.progress != 1 {
progressBar.progress += Float(milileters2.volumeFromMilimetersVC / 10)
}
shapeLayer.strokeEnd = progress
}
But unfortunately, it does not work. I would be grateful for any help since I am still a beginner
UPD. Data is taken from another ViewController
var addDrinks = AddDrinksViewController()
var drinksToMainVC: [String] = []
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? MainViewController
vc?.delegate = self
vc?.addVolume(volumeFromMilimetersVC)
vc?.addedDrinksArray.append(contentsOf: drinksToMainVC)
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
volumeFromMilimetersVC = volume.volumeArray[row]
drinksToMainVC.append(drinks?.imageName ?? "nil")
}
Attached gif. As you can see, a value is selected from Picker View, which is then successfully written to the label, but the round progress bar is not updated.
In the function updateProgress, it is not shown where progress variable is getting updated. Progress variable can be updated as below.
let volumeArray = [20, 50, 100, 150, 200, 250, 300, 330, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1500, 2000]
let volumeMax = volumeArray.max()
//strokeEnd needs to be between 0 and 1, 2000 will correspond to 1 and others as a fraction of 1
let progress = CGFloat(milileters2.volumeFromMilimetersVC)/CGFloat(volumeMax!)
shapeLayer.strokeEnd = progress
I am creating a game, this function is used as a power up to shrink the sprite. I want this function to execute for 5 seconds, and attach this 5 second timer to a label as a count down.
After the function ends, return the sprite to its original size.
The function just shrinks the node by setting the scale.
func shrinkShip() { spaceShip.scale(to: CGSize(width: 60, height: 40)) }
Sticking with SpriteKit I would just do the following
func shrinkShip(duration: Double) {
let originalSize = spaceShip.size
spaceShip.scale(to: CGSize(width: 60, height: 40))
for x in 0..<Int(duration) {
//increment the timer every 1 second for the duration
self.run(SKAction.wait(forDuration: 1 * Double(x))) {
self.timerLabel.text = String(Int(duration) - x)
}
}
self.run(SKAction.wait(forDuration: duration)) {
//return the spaceship to full size
self.spaceShip.scale(to: originalSize)
//hide the timer
self.timerLabel.text = ""
self.timerLabel.isHidden = true
}
}
You can animate the view like this:
UIView.animate(withDuration: 5, animations: {
self.spaceShip.scale(to: CGSize(width: 60, height: 40))
},
completion: { _ in
UIView.animate(withDuration: 2) {
self.spaceShip.transform = .identity // Reset your view
}
})
You can try something like this
UIView.animate(withDuration: 5, animations: {
self.spaceShip.transform = CGAffineTransform(scaleX: 60, y: 40)
},
completion: { _ in
UIView.animate(withDuration: 2) {
self.spaceShip.transform = .identity // Reset your view
}
})
I'm having a hard time with animations and PanGestureRecognizers, I want a view that slides by dragging up or dawn with your finger. It has 4 parts
1 - 2 the height, width and bottom constraints need to be changed
2 - 3 the height is the maximum height and you can slide it in or out
3 - 4 the card is at its maximum height and you can swipe it down
here is an image for a better understanding
thanks!
this is how I would do it.
var theView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// First create your view.
theView = UIView(frame: CGRect(x: 40, y: self.view.bounds.height - 120, width: self.view.frame.width - 80, height: 100))
// set its background color
theView.backgroundColor = UIColor.black
// Create the round corners
theView.layer.cornerRadius = 20
// And add it to the view
self.view.addSubview(theView)
// Create the UIPanGestureRecognizer and assign the function to the selector
let gS = UIPanGestureRecognizer(target: self, action: #selector(growShink(_:)))
// Enable user interactions on the view
theView.isUserInteractionEnabled = true
// Add the gesture recognizer to the view
theView.addGestureRecognizer(gS)
}
// The control value is used to determine wether the user swiped up or down
var controlValue:CGFloat = 0
var animating = false
// The function called when the user swipes
#objc func growShink(_ sender:UIPanGestureRecognizer){
let translation = sender.location(in: theView)
// Check the control value to see if it has been set
if controlValue != 0 && !animating{
// if it has been set check that the control value is greater than the new value recieved by the pan gesture
if translation.x < controlValue{
animating = true
// If it is, change the values of the frame using animate
UIView.animate(withDuration: 0.5, animations: {
self.theView.frame = CGRect(x: 0, y: self.view.bounds.height - 300, width: self.view.frame.width, height: 300)
}, completion: {finished in
UIView.animate(withDuration: 1.0, animations: {
self.theView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
}, completion: {finished in self.animating = false; self.controlValue = 0})})
}else if translation.x > controlValue{
animating = true
// else, change the values of the frame back.
UIView.animate(withDuration: 0.5, animations: {
self.theView.frame = CGRect(x: 0, y: self.view.bounds.height - 300, width: self.view.frame.width, height: 300)
}, completion: {finished in
UIView.animate(withDuration: 1.0, animations: {
self.theView.frame = CGRect(x: 40, y: self.view.bounds.height - 120, width: self.view.frame.width - 80, height: 100)
}, completion: {finished in self.animating = false; self.controlValue = 0})})
}
}
// set the control value to the value received from the pan gesture
controlValue = translation.x
}
}
You can adjust the width and height values however you want.
Every time I run this swift playground (on Xcode), I get an error on the last line saying:
'PlaygroundView' cannot be constructed because it has no accessible initialisers.
I also have another error on line 4 saying:
class 'PlaygroundView' has no initializers.
import UIKit
import PlaygroundSupport
class PlaygroundView:UIViewController {
var introImage:UIImageView
var introButton:UIButton
override func loadView() {
print("workspace started running.")
//Main Constant and Variable Declarations/General Setup
let mainView = UIView()
//Main View Modifications & styling
mainView.backgroundColor = UIColor.init(colorLiteralRed: 250, green: 250, blue: 250, alpha: 1)
func addItem(item: UIView) {
mainView.addSubview(item)
}
//Sub-Element Constant and Variable Declarations
introImage = UIImageView(frame: CGRect(x: 87.5, y: -300, width: 200, height: 200))
introImage.layer.borderColor = UIColor.black.cgColor
introImage.layer.borderWidth = 4
introImage.alpha = 0
introImage.transform = CGAffineTransform(rotationAngle: -90.0 * 3.14/180.0)
introImage.image = UIImage(named: "profile_pic.png")
introImage.image?.accessibilityFrame = introImage.frame
introImage.layer.cornerRadius = 100
addItem(item: introImage)
introButton = UIButton(frame: CGRect(x: 87.5, y: 900, width: 200, height: 30))
introButton.alpha = 0
introButton.setTitle("Tap to meet me", for: .normal)
introButton.titleLabel?.font = UIFont(name: "Avenir Book", size: 20)
introButton.backgroundColor = UIColor.black
introButton.layer.cornerRadius = 15
introButton.layer.borderWidth = 5
addItem(item: introButton)
introButton.addTarget(self, action: #selector(self.introButtonAction), for: .touchDown)
UIView.animate(withDuration: 1.5, delay: 1, options: .curveEaseInOut, animations: {
self.introImage.frame = CGRect(x: 87.5, y: 175, width: 200, height: 200)
self.introButton.frame = CGRect(x: 87.5, y: 400, width: 200, height: 30)
self.introImage.transform = CGAffineTransform(rotationAngle: 0.0 * 3.14/180.0)
self.introImage.alpha = 1
self.introButton.alpha = 1
}) { (mainAnimationComped) in
if mainAnimationComped == true {
print("introduction animation comped.")
}
}
}
//User Interface Actions
func introButtonAction() {
print("user interacted with user interface")
}
}
PlaygroundPage.current.liveView = PlaygroundView()
How would I fix this problem?
That's because in Swift, you need to initialize variables before use them in the code. On the other hand, since in this case you set them explicitly in your function, you might declare them as implicitly unwrapped optionals
var introImage:UIImageView!
var introButton:UIButton!
and this should work without changing anything else in your code
Add ? after introImage and introButton
var introImage:UIImageView?
var introButton:UIButton?
I have 2 animations that get triggered by pressing 2 different buttons. How can I determine which one I see first when they are literally occupying the same space and are on top of each other. Is there some code I can use to determine the hierarchy of what UIImageView is shown first?
Here is an example of what I am dealing with. I have a "lightning" image and a cloud image.
//Lightning
#IBAction func lightningOne(sender: UIButton) {
self.darkCloud.hidden = false
let lightning = UIImageView()
lightning.image = UIImage(named: "lightning")
lightning.frame = CGRect(x:-41, y:29, width:30, height:105)
self.view.addSubview(lightning)
let options = UIViewAnimationOptions.CurveEaseIn
UIView.animateWithDuration(0.1, delay: 0.5, options: options, animations: {
lightning.frame = CGRect(x: -45, y: 34, width: 30, height: 105)
}, completion: { animationFinished in
self.darkCloud.hidden = false
UIView.animateWithDuration(0.0, delay: 0.0, options: options, animations: {
self.view.addSubview(lightning)
lightning.frame = CGRect(x: 41, y: 34, width: 30, height: 105)
}, completion: { animationFinished in
self.darkCloud.hidden = false
UIView.animateWithDuration(0.7, delay: 0.0, options: options, animations: {
self.view.addSubview(lightning)
lightning.frame = CGRect(x: 41, y: 35, width: 30, height: 105)
}, completion: { animationFinished in
lightning.removeFromSuperview()
self.darkCloud.hidden = true
})
})
})
}
Here is the code I use to show the cloud.
// Clouds
func movingClouds() {
let options = UIViewAnimationOptions.Repeat
let cloud = UIImageView()
cloud.image = UIImage(named: "cloud1")
cloud.frame = CGRect(x: -80, y: 110, width: 78, height: 42)
self.view.addSubview(cloud)
UIView.animateWithDuration(140.0, delay: 0.0, options: options, animations: {
cloud.frame = CGRect(x: 350, y: 110, width: 78, height: 42)
}, completion: nil)
}
How can I make sure the lightning image shows up behind the cloud when I trigger it and they happen to cross each other?