Swift Ints and some Doubles working but not all Doubles - ios

What I am trying to do is increase the rate that the method spawnEnemy() is called on, and it works well using the starred numbers as Ints, but I felt like it goes by way too quickly so I thought of using Doubles and using the decimal point. However, if I go into the tenths spot of subtracting an amount from spawnRate in
self.spawnRate=spawnRate - 1.0 (I use anything but 0) spawnEnemy() will only be called on once in the simulator then nothing happens. Any advice or suggestions would be great. Thank you!
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var points: Int = 0
var tickCount=0.0 // **
var spawnRate=15.0 // Spawn every 15 seconds to start with..... **
var spawnTimer:NSTimer!
override func viewDidLoad() {
super.viewDidLoad()
self.tickCount=self.spawnRate
self.spawnTimer=NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("tick"), userInfo: nil, repeats: true)
}
func tick() {
if (--tickCount == 0) { // **
self.spawnEnemy()
self.spawnRate=spawnRate - 0.5 // Change this to determine how quickly the spawn rate increases..... **
self.tickCount=self.spawnRate
}
}
func randomPoint() -> CGPoint {
let randomPoint: CGPoint = CGPoint(x:CGFloat(arc4random()%320),y:CGFloat(568-arc4random()%390))
return randomPoint
}
func randomColor() -> UIColor {
let red = CGFloat(drand48())
let green = CGFloat(drand48())
let blue = CGFloat(drand48())
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
func spawnEnemy() {
let enemy: UIButton = UIButton(frame: CGRect(x: 160, y: 160, width: 100, height: 100))
enemy.backgroundColor = randomColor()
enemy.center = randomPoint()
enemy.addTarget(self, action: Selector("buttonPushed:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(enemy)
}
func buttonPushed(sender : UIButton) {
if sender.frame.height < 50 || sender.frame.width < 50 {
sender.frame = CGRectMake(sender.frame.origin.x, sender.frame.origin.y, 50, 50)
sender.backgroundColor = randomColor()
sender.center = randomPoint()
return
}
while sender.backgroundColor != UIColor.blackColor() {
points = points + 1
scoreLabel.textAlignment = .Center
scoreLabel.text = "\(points)"
scoreLabel.backgroundColor = UIColor.blackColor()
sender.backgroundColor = UIColor.blackColor()
}
}
}

I think what's happening is you're trying to test a double with the equality operator. The problem is that there's always a bit of complexity in how doubles are stored that makes it very difficult to compare them with equality. Instead you need to do something like this:
if (--tickCount <= 0) {
That way you catch it going negative for sure, otherwise it can go right past 0 and continue to count down.

Related

making buttons change colors in swift

Does anyone know how to make buttons change colors in swift? I have rectangle shaped buttons with text inside and a hollow background and I want the hollow space to change colors every 3 seconds or so. Can anyone help, I would greatly appreciate it!
Can you try
var counter = 0
var pageChangeTimer: Timer?
let colors = [UIColor.red,UIColor.green,,,,,,,,,] //,,,, set more
pageChangeTimer = Timer.scheduledTimer( withTimeInterval: 3.0 , repeats: true) { [weak self] timer in
if counter == colors.count {
counter = 0
}
self.btn.backgroundColor = colors[counter]
counter += 1
}
Extending #Sh_Khan's answer by generating random colors instead of an array of colors. I hope this helps:
Generating random colors
var counter = 0
var pageChangeTimer: Timer?
pageChangeTimer = Timer.scheduledTimer( withTimeInterval: 3.0 , repeats: true) { [weak self] timer in
if counter == colors.count {
counter = 0
}
self.btn.backgroundColor = colors[counter]
counter += 1
}
func getRandomColor() -> UIColor{
//Generate between 0 to 1
let red:CGFloat = CGFloat(drand48())
let green:CGFloat = CGFloat(drand48())
let blue:CGFloat = CGFloat(drand48())
return UIColor(red:red, green: green, blue: blue, alpha: 1.0)
}
Try this
var net = Bool()
override func viewDidLoad() {
super.viewDidLoad()
net = true
var timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
// update method
#objc func update() {
// Something cool
if net == true {
// changebtn is the outlet of UIButton
changebtn.backgroundColor = UIColor.blue
net = false
}else{
changebtn.backgroundColor = UIColor.black
net = true
}
}

CALayer animation beginTime

I'm developing a keyframe animation editor for iOS that allows the user to create simple animations and view them, plotted against a timeline. The user is able to drag the timeline in order to change the current time.
This means that I need to be able to start an animation at a user specified time.
Whilst I can achieve this behaviour, i also am experiencing an annoying glitch every time I re-add the animation to the layer. This glitch causes the first frame of the animation to flash very quickly before CoreAnimation respects the begin time. I can mitigate the effects of this ever so slightly by setting the layer.alpha to 0 before the flush, and 1 after the flush however this still results in a nasty single frame(ish) flash!
I have recreated a sample view controller that demonstrates the code required to "change the time" of the running animation but as this project is so simple, you don't really see the negative effects of the flush:
https://gist.github.com/chrisbirch/5cafca50804cf9d778ccd0fdc9e68d56
Basic idea behind the code is as follows:
Each time the user changes the current time, I restart the animation and fiddle with the CALayer timing properties like so (addStoppedAnimation:line 215):
ani = createGroup()
animatableLayer.speed = 0
animatableLayer.add(ani, forKey: "an animation key")
let time = CFTimeInterval(slider.value)
animatableLayer.timeOffset = 0
animatableLayer.beginTime = animatableLayer.superlayer!.convertTime(CACurrentMediaTime(), from: nil) - time
CATransaction.flush()
animatableLayer.timeOffset = time// offset
print("Time changed \(time)")
The glitch is caused by me having to call CATransaction.flush right before I set timeOffset. Failure to call this flush results in the begin time being ignored it would seem.
I feel like I have scoured the entire internet looking for a solution to this problem but alas Im think I'm stuck.
My question is this:
Can anyone shed any light onto why it is I need to call CATransaction.flush in order for the beginTime value that I set to take effect? Looking at apple code I didn't ever see them using flush for this purpose so perhaps I have got something obvious wrong!
Many thanks in advance
Chris
Using your test code from the gist I have updated it to check for an animation so that there is no need to readd it. You could us a unique ID to track all animations and store it in a dictionary with view attributes. I did not implement this part but that is how I would do it. Hopefully I understood your issue enough. Also I used Xcode 9 and I am not sure of the code differences. I changed a few logic pieces so let me know if this fixes the issue.
UUID().uuidString //for unique string in implementation
//from your code just slightly altered.
//
// ViewController.swift
// CATest
//
// CATestViewController.swift
// SimpleCALayerTest
import UIKit
class CATestViewController: UIViewController, CAAnimationDelegate{
var slider : UISlider!
var animatableLayer : CALayer!
var animationContainerView : UIView!
var centerY : CGFloat!
var startTranslationX : CGFloat!
var endTranslationX : CGFloat!
let duration = 10.0
///boring nibless view setup code
override func loadView() {
let marginX = CGFloat(10)
let marginY = CGFloat(10)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
view.backgroundColor = .lightGray
slider = UISlider(frame: CGRect(x: marginX, y: 0, width: 200, height: 50))
slider.maximumValue = Float(duration)
slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
slider.addTarget(self, action: #selector(sliderDragStart(_:)), for: .touchDown)
slider.addTarget(self, action: #selector(sliderDragEnd(_:)), for: .touchUpInside)
//A view to house an animated sublayer
animationContainerView = UIView(frame: CGRect(x: marginX, y: 50, width: 200, height: 70))
//add a play button that will allow the animation to be played without hindrance from the slider
let playButton = UIButton(frame: CGRect(x: marginX, y: animationContainerView.frame.maxY + marginY, width: 200, height: 50))
playButton.setTitle("Change Frame", for: .normal)
playButton.addTarget(self, action: #selector(playAnimation), for: .touchUpInside)
view.addSubview(playButton)
//add a stopped ani button that will allow the animation to be played using slider
let addStoppedAniButton = UIButton(frame: CGRect(x: playButton.frame.origin.x, y: playButton.frame.maxY + marginY, width: playButton.frame.width, height: playButton.frame.size.height))
addStoppedAniButton.setTitle("Pause", for: .normal)
addStoppedAniButton.addTarget(self, action: #selector(cmPauseTapped(_:)), for: .touchUpInside)
view.addSubview(addStoppedAniButton)
let animatableLayerWidth = animationContainerView.bounds.width / CGFloat(4)
centerY = animationContainerView.bounds.midY
startTranslationX = animatableLayerWidth / CGFloat(2)
endTranslationX = animationContainerView.bounds.width - animatableLayerWidth / CGFloat(2)
animationContainerView.backgroundColor = .white
animationContainerView.layer.borderColor = UIColor.black.withAlphaComponent(0.5).cgColor
animationContainerView.layer.borderWidth = 1
view.addSubview(slider)
view.addSubview(animationContainerView)
//Now add a layer to animate to the container
animatableLayer = CALayer()
animatableLayer.backgroundColor = UIColor.yellow.cgColor
animatableLayer.borderWidth = 1
animatableLayer.borderColor = UIColor.black.withAlphaComponent(0.5).cgColor
var r = animationContainerView.bounds.insetBy(dx: 0, dy: 4)
r.size.width = animatableLayerWidth
animatableLayer.frame = r
animationContainerView.layer.addSublayer(animatableLayer)
self.view = view
}
#objc func cmPauseTapped(_ sender : UIButton){
if animatableLayer.speed == 0{
resume()
}else{
pause()
}
}
#objc func sliderChanged(_ sender: UISlider){
if animatableLayer.speed == 0{
let time = CFTimeInterval(sender.value)
animatableLayer.speed = 0
animatableLayer.timeOffset = time// offset
print("Time changed \(time)")
}
}
var animations = [CAAnimation]()
func addAnimations(){
let ani = CAAnimation()
animations.append(ani)
}
#objc func sliderDragStart(_ sender: UISlider)
{
if animatableLayer.speed > 0{
animatableLayer.speed = 0
}
addStoppedAnimation()
}
func pause(){
//just updating slider
if slider.value != Float(animatableLayer.timeOffset){
UIView.animate(withDuration: 0.3, animations: {
self.slider.setValue(Float(self.animatableLayer.timeOffset), animated: true)
})
}
animatableLayer.timeOffset = animatableLayer.convertTime(CACurrentMediaTime(), from: nil)
animatableLayer.speed = 0
}
func resume(){
if let _ = animatableLayer.animationKeys()?.contains("an animation key"){
animatableLayer.speed = 1.0;
let pausedTime = animatableLayer.timeOffset
animatableLayer.beginTime = 0.0;
let timeSincePause = animatableLayer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
animatableLayer.beginTime = timeSincePause;
return
}
print("Drag End with need to readd animation")
ani = createGroup()
animatableLayer.speed = 1
animatableLayer.add(ani, forKey: "an animation key")
let time = CFTimeInterval(slider.value)
animatableLayer.timeOffset = time
animatableLayer.beginTime = CACurrentMediaTime()
}
#objc func sliderDragEnd(_ sender: UISlider){
resume()
}
//Animations
var ani : CAAnimationGroup!
func createGroup() -> CAAnimationGroup
{
let ani = CAAnimationGroup()
ani.isRemovedOnCompletion = false
ani.duration = 10
ani.delegate = self
ani.animations = [createTranslationAnimation(),createColourAnimation()]
return ani
}
func createTranslationAnimation() -> CAKeyframeAnimation
{
let ani = CAKeyframeAnimation(keyPath: "position")
ani.delegate = self
ani.isRemovedOnCompletion = false
ani.duration = 10
ani.values = [CGPoint(x:0,y:centerY),CGPoint(x:endTranslationX,y:centerY)]
ani.keyTimes = [0,1]
return ani
}
func createColourAnimation() -> CAKeyframeAnimation
{
let ani = CAKeyframeAnimation(keyPath: "backgroundColor")
ani.delegate = self
ani.isRemovedOnCompletion = false
ani.duration = 10
ani.values = [UIColor.red.cgColor,UIColor.blue.cgColor]
ani.keyTimes = [0,1]
return ani
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("Animation Stopped")
}
func animationDidStart(_ anim: CAAnimation) {
print("Animation started")
}
func addStoppedAnimation()
{
if let _ = animatableLayer.animationKeys()?.contains("an animation key"){
slider.value += 0.5
sliderChanged(slider)
return
//we do not want to readd it
}
ani = createGroup()
animatableLayer.speed = 0
animatableLayer.add(ani, forKey: "an animation key")
let time = CFTimeInterval(slider.value)
animatableLayer.timeOffset = time
animatableLayer.beginTime = CACurrentMediaTime()
}
#objc func playAnimation(){
addStoppedAnimation()
}
}

Using Decreasing Variable as NSTimeInterval in swift

I am trying to decrease a Double variable by 0.05 every second to speed up the function spawnEnemy() but I cannot find a way that works well. So, I am trying to pretty much "spawn" more enemies(UIButtons) in a shorter amount of time. Any help would be great and very appreciated. Thank you! My code below compiles fine but does not speed up the NSTimer controlling the function spawnEnemy()
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var points: Int = 0
func randomPoint() -> CGPoint {
let randomPoint: CGPoint = CGPoint(x:CGFloat(arc4random()%320),y:CGFloat(568-arc4random()%390))
return randomPoint
}
func randomColor() -> UIColor {
let red = CGFloat(drand48())
let green = CGFloat(drand48())
let blue = CGFloat(drand48())
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
func spawnEnemy() {
let enemy: UIButton = UIButton(frame: CGRect(x: 160, y: 160, width: 100, height: 100))
enemy.backgroundColor = randomColor()
enemy.center = randomPoint()
enemy.addTarget(self, action: Selector("buttonPushed:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(enemy)
}
func buttonPushed(sender : UIButton) {
if sender.frame.height < 50 || sender.frame.width < 50 {
sender.frame = CGRectMake(sender.frame.origin.x, sender.frame.origin.y, 50, 50)
sender.backgroundColor = randomColor()
sender.center = randomPoint()
return
}
sender.backgroundColor = UIColor.blackColor()
points = points + 1
scoreLabel.textAlignment = .Center
scoreLabel.text = "\(points)"
scoreLabel.backgroundColor = UIColor.blackColor()
}
func decreaseDouble() -> Double {
var num: Double = 2.0
num = num - 0.05
return num
}
override func viewDidLoad() {
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(decreaseDouble(), target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("decreaseDouble"), userInfo: nil, repeats: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Below is my new code. Any finals things or should this work? I also added a while statement so you can only receive points while the enemy is not black,otherwise you could just keep clicking on wherever enemies have been spawned(but were clicked) and keep getting points. However, now when i run the application two enemies are already spawned but i can still click on them(only being allowed to get two points)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var points: Int = 0
func randomPoint() -> CGPoint {
let randomPoint: CGPoint = CGPoint(x:CGFloat(arc4random()%320),y:CGFloat(568-arc4random()%390))
return randomPoint
}
func randomColor() -> UIColor {
let red = CGFloat(drand48())
let green = CGFloat(drand48())
let blue = CGFloat(drand48())
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
func spawnEnemy() {
let enemy: UIButton = UIButton(frame: CGRect(x: 160, y: 160, width: 100, height: 100))
enemy.backgroundColor = randomColor()
enemy.center = randomPoint()
enemy.addTarget(self, action: Selector("buttonPushed:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(enemy)
}
func buttonPushed(sender : UIButton) {
if sender.frame.height < 50 || sender.frame.width < 50 {
sender.frame = CGRectMake(sender.frame.origin.x, sender.frame.origin.y, 50, 50)
sender.backgroundColor = randomColor()
sender.center = randomPoint()
return
}
while sender.backgroundColor != UIColor.blackColor() {
points = points + 1
scoreLabel.textAlignment = .Center
scoreLabel.text = "\(points)"
scoreLabel.backgroundColor = UIColor.blackColor()
sender.backgroundColor = UIColor.blackColor()
}
delayTime -= 0.05
}
var delayTime: Double = 2.0
override func viewDidLoad() {
super.viewDidLoad()
delayTime = 2.0
NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(delayTime, target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
Repeating NSTimers always fire on the same interval once you start them.
If you want to use a decreasing interval then instead of a repeating timer I suggest use use a single "one-shot" timer (not 2 timers). Create the timer with repeats:false. Then in your timer method (spawnEnemy, in your case) decrement your time interval and use it to create a new timer (also with repeats:false) using the new, shorter interval and calling the same method.
Your decreaseDouble function isn't going to work as written. It will always return the same value of 2.0 - 0.05, since you set num to 2.0 each time the function called. Instead get rid of the decreaseDouble function. Create an instance variable delayValue and assign it a value (2.0, for example) in your viewDidLoad. Then just subtract 0.05 from it each time spawnEnemy is called, spawn a new enemy, and create a new timer if delayValue hasn't gotten too small. (When delayValue gets really small you're going to be spawning 10 enemies a second. Eventually delayValue will go to zero, and a timer interval should not be <= 0.0.
You have the right idea. I would use the repeating timer as a 'tick' and then keep two properties - One is the tick count and the other is the tick count before a new enemy is spawned. By decreasing the latter you can speed up the rate of spawning.
Something like this
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var points: Int = 0
var tickCount=0
var spawnRate=100 // Spawn every 10 seconds to start with
var spawnTimer:NSTimer!
override func viewDidLoad() {
super.viewDidLoad()
self.tickCount=self.spawnRate
self.spawnTimer=NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("tick"), userInfo: nil, repeats: true)
}
func tick() {
if (--tickCount == 0) {
self.spawnEnemy()
self.spawnRate=spawnRate - 2 // Change this to determine how quickly the spawn rate increases
self.tickCount=self.spawnRate
}
}
You can modify the tick rate and tick decrement amounts as required to give you the range you are after

Spawning UIView every second at random CGPoint in Swift

I am trying to "spawn" a UIView with a random UIColor as its background at a random CGPoint. I am using the code below which compiles fine with no bugs or anything but when it runs in the simulator I click start(the button on the first ViewController) but then go to ViewTwo but nothing happens(no "enemies" are spawned). Thank you for any help.
import UIKit
var randomPoint: CGPoint = CGPoint(x: Int(arc4random()%1000), y: Int(arc4random()%1000))
class ViewController: UIViewController {
#IBOutlet weak var startGameButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
class ViewTwo : UIViewController {
var timer = NSTimer()
func randomColor() -> UIColor {
let red = CGFloat(drand48())
let green = CGFloat(drand48())
let blue = CGFloat(drand48())
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
func spawnEnemy() {
let enemy: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
enemy.backgroundColor = randomColor()
enemy.center = randomPoint
self.view.addSubview(enemy)
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Your randomPoint needs to be recalculated in spawnEnemy, and is generating a random x y from 0 to 1000 (and should also be a float) It will likely be off the screen.

Add Target for UIButton Not Called

I have two view controllers that I am currently working with. In my CathyTaskLogMessageViewController, I have this code:
func defaultPictureButtonTapped(sender: UIButton) {
let imageViewerViewController = ImageViewerViewController()
imageViewerViewController.image = self.defaultPictureButton.imageView!.image
imageViewerViewController.cancelButtonImage = UIImage(named: "cancelButtonImage")
imageViewerViewController.centerPictureFromPoint(self.defaultPictureButton.frame.origin, ofSize: self.defaultPictureButton.frame.size, withCornerRadius: self.defaultPictureButton.layer.cornerRadius)
self.view.addSubview(imageViewerViewController.view)
}
And in my ImageViewerViewController, I have this code:
var image: UIImage!
var imageView: UIImageView!
var scrollView: UIScrollView!
var disableSavingImage: Bool!
var pan: UIPanGestureRecognizer!
var cancelButton: UIButton!
var cancelButtonImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.0)
setCancelButton()
}
func setCancelButton() {
self.cancelButton = UIButton(type: UIButtonType.RoundedRect)
self.cancelButton.addTarget(self, action: "cancelButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
self.cancelButton.setImage(cancelButtonImage, forState: UIControlState.Normal)
self.cancelButton.tintColor = UIColor.whiteColor()
self.cancelButton.frame = CGRectMake(self.view.frame.size.width - (self.cancelButtonImage.size.width * 2) - 18, 18, self.cancelButtonImage.size.width * 2, self.cancelButtonImage.size.height * 2)
self.view.addSubview(self.cancelButton)
}
func cancelButtonTapped(sender: UIButton) {
print("cancelButtonTapped")
}
func centerPictureFromPoint(point: CGPoint, ofSize size: CGSize, withCornerRadius radius: CGFloat) {
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.view.backgroundColor = UIColor(colorLiteralRed: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
}) { (finished: Bool) -> Void in
}
}
Now, when I tap the cancel button, it should print "cancelButtonTapped" in the console. However, there is not output from the console.
I think the problem has to do with the button being in a different subview perhaps? I'm not exactly sure. Anybody have a solution to this problem?
Also, when the cancel button loads, it loads from the left and moves to the right. Is there a way that the button just appears on the top right hand corner?
Thank you in advance for your help.
change background color, so you can identify the problem.
put this line in viewDidLayoutSubviews() this method so it loads from the left and moves to the right can solve
code:
override func viewDidLayoutSubviews() {
self.cancelButton.frame = CGRectMake(self.view.frame.size.width - (self.cancelButtonImage.size.width * 2) - 18, 18, self.cancelButtonImage.size.width * 2, self.cancelButtonImage.size.height * 2)
}

Resources