animationDidStop not executing - swift

I was working on trying to get a background view for a project I'm making and came across a weird instance.
This is how my code is set up.
import Foundation
import UIKit
class MainMenuViewController: UIViewController, CAAnimationDelegate {
#IBOutlet weak var colorView: UIView!
#IBOutlet weak var startLabel: UILabel!
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
#IBOutlet weak var thirdButton: UIButton!
let gradient = CAGradientLayer()
var gradientSet = [[CGColor]]()
var currentGradient: Int = 0
let gradientOne = gradientColors.lightGrey.cgColor
let gradientTwo = gradientColors.darkGrey.cgColor
let gradientThree = gradientColors.veryDarkGrey.cgColor
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
gradientSet.append([gradientOne, gradientTwo])
gradientSet.append([gradientTwo, gradientThree])
gradientSet.append([gradientThree, gradientOne])
gradient.frame = colorView.bounds
gradient.colors = gradientSet[currentGradient]
gradient.startPoint = CGPoint(x:0, y:0)
gradient.endPoint = CGPoint(x:1, y:1)
gradient.drawsAsynchronously = true
colorView.layer.insertSublayer(gradient, below: thirdButton.layer)
animateGradient()
}
func animateGradient() {
if currentGradient < gradientSet.count - 1 {
currentGradient += 1
} else {
currentGradient = 0
}
let gradientChangeAnimation = CABasicAnimation(keyPath: "colors")
gradientChangeAnimation.duration = 5.0
gradientChangeAnimation.toValue = gradientSet[currentGradient]
gradientChangeAnimation.fillMode = CAMediaTimingFillMode.forwards
gradientChangeAnimation.isRemovedOnCompletion = false
gradient.add(gradientChangeAnimation, forKey: "colorChange")
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if flag == true {
print("animation complete")
gradient.colors = gradientSet[currentGradient]
animateGradient()
}
}
}
The problem I'm having is that when the animation is finished, the 'animationDidStop' never triggers. The first animation runs, but when it's finished it's supposed to run the 'animationDidStop' function and run the 'animateGradient' function on a constant loop. I've looked and looked for solutions online but can't seem to find one. Im running Swift 4 and would really appreciate any help. Thanks!

You left out a line:
gradientChangeAnimation.delegate = self

Related

How to get the current Title of a button in Swift?

What am I doing wrong?
I get this error:
let letterString = sender.title(for: .normal)! // Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
when I tried to get the title of a button in swift like below:
import UIKit
class ViewController: UIViewController {
// My IBOutlets
#IBOutlet var treeImageView: UIImageView!
#IBOutlet var correctWordLabel: UILabel!
#IBOutlet var scoreLabel: UILabel!
// My Outlet Collection
#IBOutlet var letterButtons: [UIButton]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Begin the round.
newRound()
}
var listOfWords = ["estufa", "nevera", "computadora", "empanada", "chuleta", "camarones", "brincar", "correr", "caminar", "tigre", "jirafa", "mono", "kisseemmee", "Tampa", "Orlando"]
let incorrectMovesAllowed = 7
let totalWins = 0
let totalLosses = 0
// My IBActions
#IBAction func letterButtonPressed(_ sender: UIButton) {
sender.isEnabled = false
let letterString = sender.title(for: .normal)! // Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
let letter = Character(letterString.lowercased())
currentGame.playerGuessed(letter: letter)
updateUI()
}
var currentGame: Game!
func newRound() {
let newWord = listOfWords.removeFirst()
currentGame = Game(word: newWord, incorrectMovesRemaining: incorrectMovesAllowed, guessedLetters: [])
updateUI()
}
func updateUI() {
scoreLabel.text = "Wins: \(totalWins), Losses: \(totalLosses)"
treeImageView.image = UIImage(named: "Tree \(currentGame.incorrectMovesRemaining)")
}
}
// Game.swift file code:
import Foundation
struct Game {
var word: String
var incorrectMovesRemaining: Int
var guessedLetters: [Character]
mutating func playerGuessed(letter: Character) {
guessedLetters.append(letter)
if !word.contains(letter) {
incorrectMovesRemaining -= 1
}
}
}
I'm a newbie. This is my first program. I appreciate if you code the solution.
You can get the title of the UIButton using titleLabel property. Check the below code.
sender.titleLabel?.text
As the above code returns optional, you can use optional chain to safely get the string
if let titleLabel = sender.titleLabel {
let title = titleLabel.text
}
OR
You can also use the currentTitle property as below.
sender.currentTitle
You can use:
sender.titleLabel.text

reduce the cell background based on time swift

I would like to make sure that my cell has a background related to the remaining time. in the sense that the closer I get to 0, the more I would like it to be reduced, so that we understand that the timer is about to expire.
according to the elapsed time it automatically reduces from right to left.
this is the code I use in managing the Cell
class TimerCell: UITableViewCell {
#IBInspectable var defaultBackgroundColor: UIColor = .white
#IBInspectable var runningBackgroundColor: UIColor = .white
#IBInspectable var pausedBackgroundColor: UIColor = .white
#IBInspectable var animationDuration: Double = 0
#IBOutlet var timeLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
#IBOutlet var startButton: UIButton!
#IBOutlet var pauseButton: UIButton!
#IBOutlet var stopButton: UIButton!
weak var timer: Timer? {
didSet {
guard let timer = timer else {
updater?.invalidate()
return
}
if case .running = timer.state {
startUpdater()
}
configure(animated: false)
}
}
private weak var updater: Foundation.Timer?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setEditing(_ editing: Bool, animated: Bool) {
print("*** \(Date()) setEditing(\(editing), animated: \(animated)) (timer?.name: \(String(describing: timer?.name)))")
super.setEditing(editing, animated: animated)
configure(animated: animated)
}
func configure(animated: Bool = true) {
guard let timer = timer else {
return
}
UIView.animate(withDuration: animated ? animationDuration : 0) {
guard !self.isEditing else {
self.timeLabel.text = timer.initialTime.hmsString
self.startButton.safelySetIsHidden(true)
self.pauseButton.safelySetIsHidden(true)
self.stopButton.safelySetIsHidden(true)
self.backgroundColor = self.defaultBackgroundColor
return
}
self.timeLabel.text = ceil(timer.timeForState).hmsString
self.nameLabel.text = timer.name
switch timer.state {
case .stopped:
self.stopButton.safelySetIsHidden(true)
self.pauseButton.safelySetIsHidden(true)
self.startButton.safelySetIsHidden(false)
self.backgroundColor = self.defaultBackgroundColor
case .running:
self.startButton.safelySetIsHidden(true)
self.stopButton.safelySetIsHidden( ceil(timer.timeForState) == 0 ? true : false )
self.pauseButton.safelySetIsHidden( ceil(timer.timeForState) == 0 ? true : false )
self.backgroundColor = self.runningBackgroundColor
case .paused:
self.pauseButton.safelySetIsHidden(true)
self.startButton.safelySetIsHidden(false)
self.stopButton.safelySetIsHidden(false)
self.backgroundColor = self.pausedBackgroundColor
}
}
}
#IBAction private func startTimer() {
timer?.state = .running
configure()
startUpdater()
}
#IBAction private func pauseTimer() {
timer?.state = .paused
configure()
}
#IBAction private func stopTimer() {
timer?.state = .stopped
configure()
}
private func startUpdater() {
guard let timer = timer else {
return
}
let date = Date(timeIntervalSinceNow: timer.timeForState.truncatingRemainder(dividingBy: 1))
let updater = Foundation.Timer(fire: date, interval: 1, repeats: true) {
[weak timer] updater in
self.configure()
if timer?.state != .running {
updater.invalidate()
}
}
self.updater = updater
RunLoop.main.add(updater, forMode: .common)
}
}
I think you're after something like this:
That's not trivial to achieve. I did it by adding a CAGradientLayer to the view and animating its locations property. At the same time, I ran the timer to change the label value.
So you might do it that way; you would probably want to tweak the values, of course. This is just a proof-of-concept demo.

How do I assign Y to X?

I couldn't figure out how to copy value of variable into another variable in Swift, an example code for this in python would be
def assignVariable():
x=1
y=x
return y
RESULT 1
When I did this it doesn't seem to work in Swift. Is there any solution to this or am I doing something wrong?
Edit: problem is at
var originalCount=countDown
it gave me Use of unresolved identifier 'countDown' but when I assign it literally it works. Here's my swift code
import Cocoa
class MainWindow: NSWindowController {
var hitCount = 0
var started:Bool = false
var timer = 10
var colorList: [NSColor] = [ NSColor.black,NSColor.blue,NSColor.brown,NSColor.cyan,NSColor.darkGray,NSColor.gray,NSColor.green,NSColor.lightGray,NSColor.magenta,NSColor.orange,NSColor.purple,NSColor.red,NSColor.white,NSColor.yellow]
#IBOutlet weak var button1: NSButton!
#IBOutlet weak var scrubber1: NSScrubber!
#IBOutlet weak var display: NSTextField!
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
var countdown=10
var originalCount=countDown
//(countdown,originalCount) = (10,10) //it works if i use this instead
func startGame(){
if(countDown>0 || started==true){
display.stringValue=String(countDown)
countDown-=1
let seconds = 1.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
self.startGame()
}
}else{
display.stringValue="Done "+String(hitCount)+" Taps in " + String(originalCount) + "Tap to RESET"
started=false
countDown=10;
}
}
#IBAction func labelPress(_ sender: Any) {
display.stringValue="__RESET__"
hitCount=0
countDown=10
started=false
}
#IBAction func buttonPressed(_ sender: Any) {
if started==false{
startGame()
}
button1.bezelColor = colorList[Int.random(in: 0..<colorList.count)]
started=true
button1.title=String(hitCount)
hitCount+=1
}
}
You can't initialise one variable with another at the top level in your class. Looking at your code I don't think that originalCount needs to be a property, move it inside startGame() instead and make it a local variable and also use let since it isn't changing
var countdown=10
func startGame(){
let originalCount = countDown
if(countDown>0 || started==true){
...
}

call a func from different class in selector

I am using UIGestureRecognizer. I am trying to call a func from different class in the selector, but i am getting NSInvalidArgumentException when it executes.
import Foundation
import UIKit
class helperClass {
var onBoardingImageArray : [UIImage]?
var onBoardingPageControl : UIPageControl?
var onBoardingImageView : UIImageView?
init(imageArray : [UIImage] , pageControl : UIPageControl , yourImageView : UIImageView) {
onBoardingImageArray = imageArray
onBoardingPageControl = pageControl
onBoardingImageView = yourImageView
}
#objc func firstImageSwipeGestureAction(gesture :UIGestureRecognizer){
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
if (onBoardingPageControl?.currentPage)! > 0{
print("Swiped right")
onBoardingPageControl?.currentPage -= 1
self.onBoardingImageView?.image = onBoardingImageArray?[(onBoardingPageControl?.currentPage)!]
}
case UISwipeGestureRecognizerDirection.left:
if (onBoardingPageControl?.currentPage)! < (onBoardingImageArray?.count)! - 1{
print("Swiped left")
onBoardingPageControl?.currentPage += 1
self.onBoardingImageView?.image = onBoardingImageArray?[(onBoardingPageControl?.currentPage)!]
}
default:
break
}
}
}
}
import UIKit
class MainController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.addTaped()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addTaped(){
let helpClasses : helperClass = helperClass.init(imageArray: self.firtImageViewArray! , pageControl:firstPageControl , yourImageView: firstImageView)
let firstImageswipeGestureRecognizer = UISwipeGestureRecognizer(target: helpClasses, action: #selector(helpClasses.firstImageSwipeGestureAction))
firstImageswipeGestureRecognizer.direction = .right
self.firstImageView.isUserInteractionEnabled = true
self.firstImageView.addGestureRecognizer(firstImageswipeGestureRecognizer)
let firstImageswipeGestureRecognizerLeft = UISwipeGestureRecognizer(target: helpClasses, action: #selector(helpClasses.firstImageSwipeGestureAction))
firstImageswipeGestureRecognizer.direction = .left
self.firstImageView.isUserInteractionEnabled = true
self.firstImageView.addGestureRecognizer(firstImageswipeGestureRecognizerLeft)
}
#IBOutlet weak var firstImageView: UIImageView!
#IBOutlet weak var firstPageControl: UIPageControl!
let firtImageViewArray : [UIImage]? = [#imageLiteral(resourceName: "Eagle9")]
}
You are setting up everything right, but make one mistake, when you initialise your helpClasses.
Because you declare the helpClasses variable inside the scope of addTaped() function, it will be allocated on the stack. As soon as your function finishes, helpClasses variable will be deallocated, removed from the stack, and it becomes nil. From than on, you are sending messages to an object, what is nil, therefore it is understandable, that nothing happens.
To overcome on this problem, declare your variable on the heap, outside of you functions scope. Best is if you declare it on the scope of your MainController.
Example:
import UIKit
class MainController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.addTaped()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addTaped(){
// Initialise helpclasses here, but not as a local variable!!
helpClasses = helperClass.init(imageArray: self.firtImageViewArray! , pageControl:firstPageControl , yourImageView: firstImageView)
let firstImageswipeGestureRecognizer = UISwipeGestureRecognizer(target: helpClasses, action: #selector(helpClasses.firstImageSwipeGestureAction))
firstImageswipeGestureRecognizer.direction = .right
self.firstImageView.isUserInteractionEnabled = true
self.firstImageView.addGestureRecognizer(firstImageswipeGestureRecognizer)
let firstImageswipeGestureRecognizerLeft = UISwipeGestureRecognizer(target: helpClasses, action: #selector(helpClasses.firstImageSwipeGestureAction))
firstImageswipeGestureRecognizer.direction = .left
self.firstImageView.isUserInteractionEnabled = true
self.firstImageView.addGestureRecognizer(firstImageswipeGestureRecognizerLeft)
}
#IBOutlet weak var firstImageView: UIImageView!
#IBOutlet weak var firstPageControl: UIPageControl!
let firtImageViewArray : [UIImage]? = [#imageLiteral(resourceName: "Eagle9")]
// Move your helpClasses variable here
var helpClasses: helperClass!
}
To call a selector method from another class you just have to do the following :
let recognizer = UISwipeGestureRecognizer(target: objClass,
action: #selector(objClass.actionMethodName))

enable NSButton if NSTextfield is not empty

I am creating a OSX app and would like to enable a button when all textfield are filled. But do not have much experience with osx app as there seem to be some difference from ios.
This is what I have tried.
override func viewDidLoad() {
super.viewDidLoad()
btnCalculate.enabled = false
}
override func controlTextDidChange(obj: NSNotification) {
if panelsWideTextField.stringValue.isEmpty {
btnCalculate.enabled = false
} else {
btnCalculate.enabled = true
}
}
Hope someone has a good tip for me :-)
EDIT:
Complete code.
import Cocoa
//import AppKit
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var panelHightTextField: NSTextField!
#IBOutlet weak var panelWidthTextField: NSTextField!
#IBOutlet weak var panelPitchTextField: NSTextField!
#IBOutlet weak var panelsHighTextField: NSTextField!
#IBOutlet weak var panelsWideTextField: NSTextField!
#IBOutlet weak var resWidthLabel: NSTextField!
#IBOutlet weak var resHightLabel: NSTextField!
#IBOutlet weak var lblScreenWidth: NSTextField!
#IBOutlet weak var lblScreenHight: NSTextField!
#IBOutlet weak var lblScreenArea: NSTextField!
#IBOutlet weak var btnCalculate: NSButton!
#IBOutlet weak var lblAmountPanels: NSTextField!
var panelHight = ""
var panelWidth = ""
var panelPitch = ""
var panelsHigh = ""
var panelsWidth = ""
var resWidth : Float = 0
var resHigh : Float = 0
var screenHight : Float = 0
var screenWidth : Float = 0
var screenArea : Float = 0
var ammountPanels : Float = 0
override func viewDidLoad() {
super.viewDidLoad()
btnCalculate.enabled = false
}
override func controlTextDidChange(obj: NSNotification) {
if panelsWideTextField.stringValue.isEmpty {
btnCalculate.enabled = true
} else {
btnCalculate.enabled = false
}
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func calculateResButton(sender: AnyObject) {
takeUserData()
calculatehight()
calculatewidth()
calculateArea()
calculateAmountPanels()
}
func takeUserData(){
panelHight = panelHightTextField.stringValue
panelWidth = panelWidthTextField.stringValue
panelPitch = panelPitchTextField.stringValue
panelsHigh = panelsHighTextField.stringValue
panelsWidth = panelsWideTextField.stringValue
}
// Calculating resolution and physical hight
func calculatehight(){
let fpanelHight = Float(panelHight)
let fpanelPitch = Float(panelPitch)
let fpanelsHigh = Float(panelsHigh)
resHigh = fpanelHight! * fpanelsHigh! / fpanelPitch!
screenHight = fpanelHight! * fpanelsHigh! / 1_000
printText()
}
// Calculating resolution and physical width
func calculatewidth(){
let fpanelWidth = Float(panelWidth)
let fpanelPitch = Float(panelPitch)
let fpanelsWidth = Float(panelsWidth)
resWidth = fpanelWidth!*fpanelsWidth!/fpanelPitch!
screenWidth = fpanelWidth!*fpanelsWidth! / 1_000
printText()
}
// Calculating sqm of LED screen
func calculateArea(){
let fpanelHight = Float(panelHight)
let fpanelsHigh = Float(panelsHigh)
let fpanelWidth = Float(panelWidth)
let fpanelsWidth = Float(panelsWidth)
screenArea = (fpanelHight! * fpanelsHigh!) * (fpanelWidth! * fpanelsWidth!) / 1_000_000
printText()
}
// Calculating the amount of panels used.
func calculateAmountPanels(){
let fpanelsHigh = Float(panelsHigh)
let fpanelsWidth = Float(panelsWidth)
ammountPanels = (fpanelsWidth! * fpanelsHigh!)
printText()
}
// Outputting text to labels with correct format.
func printText(){
let formatResHigh = String(format: "%0.0f", resHigh)
let formatResWidth = String(format: "%0.0f", resWidth)
let formatScreenHight = String(format: "%0.2f", screenHight)
let formatScreenWidth = String(format: "%0.2f", screenWidth)
let formatScreenArea = String(format: "%0.0f", screenArea)
let formatAmmountPanels = String(format: "%0.0f", ammountPanels)
resHightLabel.stringValue = "\(formatResHigh)px"
resWidthLabel.stringValue = "\(formatResWidth)px"
lblScreenHight.stringValue = "\(formatScreenHight)m"
lblScreenWidth.stringValue = "\(formatScreenWidth)m"
lblScreenArea.stringValue = "\(formatScreenArea) sqm"
lblAmountPanels.stringValue = "\(formatAmmountPanels)"
}
}
I had the same problem but I found simple solution: checkbox can enable and disable NSButton.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var btnCalculate: NSButton!
#IBOutlet weak var checkBox: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
btnCalculate.enabled = false
checkBox.state = 0
}
#IBAction func checkAgree(sender: NSButton) {
if btnCalculate.stringValue.characters.count > 0 && checkBox.state == 1 {
btnCalculate.enabled = true
} else {
btnCalculate.enabled = false
}
}
}