Use of local variable 'updateTimer' before its declaration Egg timer - xcode

I just started learning programming and the instructor asked us to make an egg timer app.
I tried to run her example solution but XCode shows an issue with the code at #selector(updateTimer). The error says Use of local variable 'updateTimer' before its declaration.
This is the code:
class ViewController: UIViewController {
#IBOutlet weak var progress: UILabel!
let eggTimes = ["Soft" :3, "Medium":4, "Hard":6]
var secondsRemaining = 60
var timer = Timer()
#IBAction func hardnessSelected(_ sender: UIButton) {
timer.invalidate()
let hardness = sender.currentTitle!
secondsRemaining = eggTimes[hardness]!
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:
#selector(updateTimer), userInfo: nil, repeats: true)
func updateTimer() {
if secondsRemaining > 0 {
print("\(secondsRemaining) seconds left to finish")
secondsRemaining -= 1
}
else {
timer.invalidate()
progress.text = "DONE"
}
}
}
}

The issue you're having is that the func updateTimer is declared under the #selector and as such isn't "available yet" to put it simply. What you probably wanted is to move the function outside the hardnessSelected as such:
class ViewController: UIViewController {
#IBOutlet weak var progress: UILabel!
let eggTimes = ["Soft" :3, "Medium":4, "Hard":6]
var secondsRemaining = 60
var timer = Timer()
#IBAction func hardnessSelected(_ sender: UIButton) {
self.timer.invalidate()
let hardness = sender.currentTitle!
self.secondsRemaining = self.eggTimes[hardness]!
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
if self.secondsRemaining > 0 {
print("\(self.secondsRemaining) seconds left to finish")
self.secondsRemaining -= 1
}
else {
self.timer.invalidate()
self.progress.text = "DONE"
}
}
}
You also need to add #objc in front of the function name to expose it to ObjC runtime. You can read more about that here.

Related

How to update a Status Item created by AppDelegate from NSViewController

I'm trying to create a Countdown Timer application that runs in the Menu Bar, with no window or dock icon. I've been building this off of mostly tutorials I find online and I know the code is kind of messy (I plan to clean up after it functions properly). The issue I'm running into. In the AppDelegate I create the StatusBar item with no issue, but I can't figure out how to update it from the viewController. It instead is creating a new StatusBar item.
//AppDelegate info
class AppDelegate: NSObject, NSApplicationDelegate
{
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification)
{
menuBarRefresh(self)
}
func menuBarRefresh(_ sender: Any?)
{
if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
//button.title = initialTime.stringValue
button.action = #selector(togglePopover(_:))
}
popover.contentViewController = TimerViewController.freshController()
}
#objc func togglePopover(_ sender: Any?)
{
if popover.isShown
{
closePopover(sender: sender)
}
else
{
showPopover(sender: sender)
}
}
func showPopover(sender: Any?)
{
if let button = item.button
{
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
func closePopover(sender: Any?)
{
popover.performClose(sender)
}
//Controller code
import Cocoa
import AVFoundation
//Checking to ensure entered data is numeric
extension String
{
var isNumeric: Bool
{
let range = self.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted)
return (range == nil)
}
}
class TimerViewController: NSViewController
{
//Here's the texts fields for the user to enter content.
#IBOutlet var hourInput: NSTextField!
#IBOutlet var minuteInput: NSTextField!
#IBOutlet var secondInput: NSTextField!
//This is the label used to display the counter
#IBOutlet var initialTime: NSTextField!
//Here are the variables we're going to need
var hours = Int() //Place holder for the hours
var minutes = Int() //Place holder for the hours
var seconds = Int() //Place holder for the hours
var timer = Timer() //The timer we'll use later
var audioPlayer = AVAudioPlayer() //The audio player
var timeRemaining = Int() //Place holder for the total 'seconds' to be counted
var firstRun = Bool()
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
override func viewDidLoad()
{
super.viewDidLoad()
getData() //Pull last saved time from Core Data and load it.
hourInput.stringValue = "\(hours)" //Loading the hours into the hours field
minuteInput.stringValue = "\(minutes)" //Loading the minutes into the minutes field
secondInput.stringValue = "\(seconds)" //Loading the seconds into the seconds field
initialTime.stringValue = "00:00:00" //Resetting the 'counter' to 0
firstRun = true
updateStatusBar(self)
//Here we load up the audio file for the 'done' chime. If not available we print the catch
do
{
let audioPath = Bundle.main.path(forResource: "Done", ofType: "m4a")
try audioPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: audioPath!))
}
catch
{
print("No Joy")
}
/* if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
button.title = initialTime.stringValue
button.action = #selector(togglePopover(_:))
}
*/ }
}
// MARK: Storyboard instantiation
extension TimerViewController
{
static func freshController() -> TimerViewController
{
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
let identifier = NSStoryboard.SceneIdentifier("TimerViewController")
guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? TimerViewController
else
{
fatalError("Why can't I find TimerViewController? - Check Main.storyboard")
}
return viewcontroller
}
}
//Button actions follow
extension TimerViewController
{
#IBAction func clearButton(_ sender: Any)
{
clearFields()
timer.invalidate()
audioPlayer.stop()
}
#IBAction func pauseButton(_ sender: Any)
{
timer.invalidate()
}
#IBAction func quitButton(_ sender: Any)
{
exit(0)
}
#IBAction func startButton(_ sender: Any)
{
grabData()
setData()
timeRemaining = (hours*3600)+(minutes*60)+seconds
if timeRemaining <= 0
{
initialTime.stringValue = "Enter Time"
}
else
{
displayTime()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.startCountDown), userInfo: nil, repeats: true)
clearFields()
updateStatusBar(self)
}
}
}
//MARK: Other Functions
extension TimerViewController
{
func displayTime()
{
let secondsDisplay = String(format: "%02d", (timeRemaining%60))
let minutesDisplay = String(format: "%02d", (timeRemaining%3600)/60)
initialTime.stringValue = "\(timeRemaining/3600):\(minutesDisplay):\(secondsDisplay)"
}
func grabData()
{
hours = hourInput.integerValue
minutes = minuteInput.integerValue
seconds = secondInput.integerValue
}
func clearFields()
{
hourInput.stringValue = ""
minuteInput.stringValue = ""
secondInput.stringValue = ""
initialTime.stringValue = "00:00:00"
}
func setData()
{
setHour()
setMinute()
setSecond()
}
func getData()
{
getHour()
getMinute()
getSecond()
}
#objc func showTimer(_ sender: Any?)
{
print("Are we here")
}
#objc func startCountDown()
{
timeRemaining -= 1
displayTime()
updateStatusBar(self)
print(timeRemaining)
if timeRemaining == 0
{
timer.invalidate()
audioPlayer.play()
}
}
/* func setNeedsStatusBarAppearanceUpdate()
{
button.image = NSImage(named: NSImage.Name("2"))
button.action = #selector(showTimer(_:))
}
*/
func updateStatusBar(_ sender: Any?)
{
if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
button.action = #selector(showTimer(_:))
button.title = initialTime.stringValue
}
//let menu = NSMenu()
//menu.addItem(NSMenuItem(title: "Clear Timer", action: #selector(AppDelegate.theDv2), keyEquivalent: "R"))
//menu.addItem(NSMenuItem(title: "Quit Timer", action: #selector(AppDelegate.quit), keyEquivalent: "Q"))
//item.menu = menu
}
}
//There's a bunch of CoreData stuff after here but I left that out. I'm just using CoreData mainly to learn how to and functional reason is to store and load the last used time
As it currently works, I get two StatusBar items instead of creating one with the AppDelegate then updating that one from the ViewController.
Yup... Id-10-t error here. Just had to declare 'item' outside the class and all is well. After getting some good sleep and time away from the computer I realized I was not declaring 'item' globally.

How to update the NSProgressIndicator?

I have problem with NSProgress. The problem is that NSProgressIndicator is not updating during the process and showing only a small completed portion at the end of process. The localizedDescription is also showing only at the end of the process, but as 100% completed.
So, I have a class with one method findRepeatsWithProgressReporting using NSProgress
class TestProgress: NSObject, ProgressReporting
{
let progress: Progress
override init()
{
progress = Progress()
super.init()
}
func findRepeatsWithProgressReporting(stringToSearch: String, minimalLength: Int, maximalLength: Int) -> [String]
{
var arrayOfRepeats = [String]()
progress.totalUnitCount = Int64((minimalLength...maximalLength).count)
for i in minimalLength...maximalLength
{
let arrayOfStrings = stringToSearch.chopString(stringOut: stringToSearch, length: i)
let arrayOfUniqueStrings = Array(Set(arrayOfStrings))
for each in arrayOfUniqueStrings
{
let arrayOfNSRanges = stringToSearch.searchForNSRangesOfStringInString(stringOut: stringToSearch, stringIn: each)
var positions = String()
if arrayOfNSRanges.count > 1
{
for each1 in arrayOfNSRanges
{
let repeatStart = String(each1.location + 1)
let repeatEnd = String(each1.location + each1.length)
positions += "(" + repeatStart + "-" + repeatEnd + ")"
}
let stringToShow = each + " " + positions
arrayOfRepeats.append(stringToShow)
}
}
progress.completedUnitCount += 1
}
return arrayOfRepeats
}
}
Then, in myVewContrloler I have parentProgress repeatsProgress having totalUnitCount: 10 and have added the task of the method findRepeatsWithProgressReporting as childProgress to the parentProgress repeatsProgress using repeatsProgress.becomeCurrent(withPendingUnitCount: 10).
private var progressObservationContext = 0
class myVewContrloler: NSViewController
{
...
var testProgress = TestProgress ()
var repeatsProgress = Progress()
#IBOutlet weak var repeatsSearchProgressBar: NSProgressIndicator!
#IBOutlet weak var repeatsPercentText: NSTextField!
#IBOutlet weak var minimalLength: NSTextField!
#IBOutlet weak var maximalLength: NSTextField!
#IBOutlet var foundRepeats: NSTextView!
#IBAction func actionFindRepeats(_ sender: AnyObject)
{
repeatsProgress = Progress(totalUnitCount: 10)
let options : NSKeyValueObservingOptions = [.new, .old, .initial, .prior]
repeatsProgress.addObserver(self, forKeyPath: "fractionCompleted", options: options, context: &progressObservationContext)
repeatsProgress.addObserver(self, forKeyPath: "localizedDescription", options: options, context: &progressObservationContext)
var arrayOfRepeats = [String]()
repeatsProgress.becomeCurrent(withPendingUnitCount: 10)
arrayOfRepeats = testProgress.findRepeatsWithProgressReporting(stringToSearch: stringToSearch, minimalLength: minimalLength.integerValue, maximalLength: maximalLength.integerValue)
...
repeatsProgress.removeObserver(self, forKeyPath: "fractionCompleted")
repeatsProgress.removeObserver(self, forKeyPath: "localizedDescription")
repeatsProgress.resignCurrent()
}
}
The last part is for KVO :
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
guard context == &progressObservationContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == "fractionCompleted"
{
OperationQueue.main.addOperation{
let progress = object as! Progress
self.repeatsSearchProgressBar.doubleValue = progress.fractionCompleted
self.repeatsPercentText.stringValue = progress.localizedDescription
}
}
}
I have added
print("Observed Something")
inside of the
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{ ...
and what I see is two times printing the "Observed Something"immediately after start and six times at the end, no printing in between (as it expected to be for the updating process). What can be the reason ?
This seems like a concurrency problem. Since func actionFindRepeats(_ sender: AnyObject) is running in the main thread, it's concurring with the UI updates, which affects the NSProgressIndicator directly.
See the last example of that answer for more details about that:
https://stackoverflow.com/a/35810608/4370893
You can try adding all the content of your actionFindRepeats function into that block and see if it works:
DispatchQueue.global().async {
// qos' default value is ´DispatchQoS.QoSClass.default`
}
Reference for that block:
https://stackoverflow.com/a/37806522/4370893

Swift Multiples App running error

I have running an issue with the Multiples App. I was just trying out and see if I can get the function right. However, this app can only run once, the addition itself is correct but when the game starts over, it will throw an error. Please help ~~
class ViewController: UIViewController {
#IBOutlet weak var AddBtn: UIButton!
#IBOutlet weak var TextInput: UITextField!
#IBOutlet weak var ResultLabel: UILabel!
var CurrentNum = 0
var SecondNum = 0
let MaxNum = 30
func currentNumUpdate(sum: Int, Mul: Int, nSum: Int){
ResultLabel.text = "\(sum) + \(Mul) = \(nSum)"
}
#IBAction func AddtionBtn(sender: UIButton) {
let TotalSum = CurrentNum + SecondNum
currentNumUpdate(CurrentNum, Mul: SecondNum, nSum: TotalSum)
CurrentNum += SecondNum
if (TextInput != nil && TextInput != ""){
SecondNum = Int(TextInput.text!)!
}
if GameOver(){
GameStartOver()
}
}
func GameOver() -> Bool{
if (CurrentNum > MaxNum){
return true
}else {
return false
}
}
func GameStartOver(){
ResultLabel.hidden = false
TextInput.text = ""
AddBtn.hidden = false
ResultLabel.text = "Again"
}
}

Why doesnt my timer countdown to 0 in Swift?

I have this timer and it starts at 3 and it should countdown to 0 but it stops at 2. I don't get why it doesn't go all they way down to 0. Can you please let me know what Im doing wrong with my code. Thank you!
class GameScene: SKScene, SKPhysicsContactDelegate {
var timerToStartGame = 3
var timerCountDownLabel: SKLabelNode! = SKLabelNode()
override func didMoveToView(view: SKView) {
timerCountDownLabel = SKLabelNode(fontNamed: "TimeBurner")
timerCountDownLabel.fontColor = UIColor.whiteColor()
timerCountDownLabel.zPosition = 40
timerCountDownLabel.fontSize = 60
timerCountDownLabel.position = CGPointMake(self.size.width / 2.4, self.size.height / 1.5)
self.addChild(timerCountDownLabel)
var clock = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("countdown"), userInfo: nil, repeats: true)
}
func countdown() {
timerCountDownLabel.text = String(timerToStartGame--)
if timerToStartGame == 0 {
doAction()
}
}
}
The problem occurs because you decrease it after displaying using -- after the var. Move it to the front and start from 4.
timerCountDownLabel.text = String(--timerToStartGame)

Sigabrt swift Xcode (making a timer)

I'm trying to make a stopwatch, and I can't seem to understand why I am getting a sigabrt with my application. Sorry I didn't know what to include, so I included everything.
var timer = NSTimer()
var minutes: Int = 0
var seconds: Int = 0
var fractions: Int = 0
var startStopWatch: Bool = true
var StopwatchString: String = ""
#IBOutlet weak var display: UILabel!
#IBAction func start(sender: UIButton) {
if startStopWatch == true {
NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("updateStopwatch"), userInfo: nil, repeats: true)
startStopWatch = false
} else {
timer.invalidate()
startStopWatch = true
}
}
#IBAction func reset(sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
display.text = "00:00.00"
}
func updateStopWatch() {
fractions += 1
if fractions == 100 {
seconds += 1
fractions = 0
}
if seconds == 60 {
minutes += 1
seconds = 0
}
let fractionsString = fractions > 9 ? "\(fractions)" : "0\(fractions)"
let secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
StopwatchString = "\(minutesString):\(secondsString).\(fractionsString)"
display.text = StopwatchString
}
I cut and pasted your code into a project and when I tapped the start button the first line of the error message said:
-[My.ViewController updateStopwatch]: unrecognized selector sent to instance 0x7fe0034b3520
That tells you all you need to know. You send the updateStopwatch message to your view controller but it doesn't implement that method. I noticed though that your ViewController does implement an updateStopWatch method. Looks like a basic capitalization error to me.
Always, always, always, read at least the first line of the error message.
Here are some fixes:
+I changed the timer variable initially to nil - it will be saved in the start function.
+Your if statement in the start function looked like this:
if startStopWatch == true
But it should be like this:
if (startStopWatch)
Hope this helpes :)
var timer = nil
var minutes = 0
var seconds = 0
var fractions = 0
var startStopWatch = true
var StopwatchString = ""
#IBOutlet weak var display: UILabel!
#IBAction func start(sender: UIButton) {
if (startStopWatch == true) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("updateStopwatch"), userInfo: nil, repeats: true)
startStopWatch = false
//I added one thing. If the start button is pushed the text will change to stop. You can change this if you like.
sender.setTitle("Stop", forState: .Normal)
//
} else {
timer.invalidate()
startStopWatch = true
}
}
#IBAction func reset(sender: UIButton) {
display.text! = "00:00.00"
}
override func viewDidLoad() {
super.viewDidLoad()
display.text! = "00:00.00"
}
func updateStopWatch() {
fractions++
if (fractions == 100) {
seconds++
fractions = 0
}
if (seconds == 60) {
minutes++
seconds = 0
}
var fractionsString = fractions > 9 ? "\(fractions)" : "0\(fractions)"
var secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
var minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
StopwatchString = "\(minutesString):\(secondsString).\(fractionsString)"
display.text! = StopwatchString
}

Resources