save a highscore with UserDefaults in Swift 3 [closed] - swift

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
class ThirdViewController: UIViewController {
var i = 3
var a: Double = 0
var fastestScore:Float = 100000000000000.0
var randomNumberX = Int(arc4random_uniform(375))
var randomNumberY = Int(arc4random_uniform(667))
#IBOutlet weak var fastestScoreLabel: UILabel!
#IBOutlet weak var homeButtonLabel: UIButton!
#IBOutlet weak var finalLabel: UILabel!
#IBOutlet weak var playAgainButtonLabel: UIButton!
#IBOutlet weak var circleImage: UIButton!
#IBOutlet weak var secondTimerLabel: UILabel!
#IBOutlet weak var counterLabel: UILabel!
var timerA = Timer()
var timerB = Timer()
#IBAction func homeButton(_ sender: Any) {
nextPage = false
}
#IBAction func playAgainButton(_ sender: Any) {
randomNumberX = Int(arc4random_uniform(375))
randomNumberY = Int(arc4random_uniform(667))
homeButtonLabel.isHidden = true
counterLabel.text = "3"
i = 3
timerA = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ThirdViewController.counterFunc), userInfo: nil, repeats: true)
a = 0
counterLabel.isHidden = false
playAgainButtonLabel.isHidden = true
circleImage.isHidden = true
finalLabel.isHidden = true
secondTimerLabel.isHidden = true
fastestScoreLabel.isHidden = true
}
#IBAction func circleButton(_ sender: Any) {
fastestScoreLabel.isHidden = false
circleImage.isHidden = true
homeButtonLabel.isHidden = false
timerB.invalidate()
playAgainButtonLabel.isHidden = false
var saying = ""
if a < 0.2 {
saying = "That's actually pretty good \(a) seconds is pretty fast"
} else if a >= 0.2 && a <= 0.45 {
saying = "\(a) seconds is not really that good"
} else {
saying = "\(a) seconds? Seriously? Are you even trying?"
}
finalLabel.text = saying
finalLabel.isHidden = false
if Float(a) < fastestScore {
fastestScore = Float(a)
UserDefaults.standard.set(fastestScore, forKey: "Fastest Score")
let savedFastestScore = UserDefaults.standard.float(forKey: "Fastest Score")
fastestScoreLabel.text = String(savedFastestScore)
}
}
func secondTimer() {
a = a + 0.01
secondTimerLabel.text = String(a)
}
func counterFunc() {
if i > 1 {
i -= 1
counterLabel.text = String(i)
} else {
timerB = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ThirdViewController.secondTimer), userInfo: nil, repeats: true)
circleImage.isHidden = false
self.circleImage.center = CGPoint(x:randomNumberX, y:randomNumberY)
counterLabel.isHidden = true
timerA.invalidate()
secondTimerLabel.isHidden = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
fastestScoreLabel.isHidden = true
homeButtonLabel.isHidden = true
playAgainButtonLabel.isHidden = true
circleImage.isHidden = true
secondTimerLabel.isHidden = true
timerA = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ThirdViewController.counterFunc), userInfo: nil, repeats: true)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I have tried many different times using userDefaults to try and save the highscore when you close out but I just can't seem to do it. If someone has any idea on how to do it I would really appreciate it lol.

You still haven't clearly explained what problem you're seeing, but my guess is that your high score doesn't appear to be saved across restarts of the app.
The problem is that you initialize fastestScore to 100000000000000.0 when your ThirdViewController is created. You don't load it from UserDefaults. Thus even if there is a stored fastestScore, you don't load it at launch.
You should fix this with two changes. First, in your application delegate, you should register a default high score:
let bestScoreKey = "bestScore"
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, willFinishLaunchingWithOptions options: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
UserDefaults.standard.register(defaults: [
bestScoreKey: 1_000_000_000.0
])
return true
}
}
You should also register any other default settings there.
Second, in ThirdViewController, you should initialize fastestScore from UserDefaults. You can also save changes back to UserDefaults in the property's didSet observer:
var fastestScore: Double = UserDefaults.standard.double(forKey: bestScoreKey) {
didSet {
UserDefaults.standard.set(fastestScore, forKey: bestScoreKey)
}
}
Other tips:
There is no reason to make fastestScore a Float if your scores are Double. Just save it as a Double.
Don't repeat string keys. The compiler won't catch your spelling errors. Put the key in a constant like I did with bestScoreKey.
You can use _ in long numbers to make them more readable.
Insulting your player is a questionable marketing strategy. How would you have felt if the first comment on your question was “Seriously? Are you even trying?”

When you are saving high scores, make sure that the current score you have is greater than the new score.
suppose the current high score is 100
var fastestScore = 100
var currentScore: Int?
if currentScore < fastestScore {
Userdefaults.standard.set(currentScore, forKey: "Fastest Score")
}
The code above will go to the function where you need to save the high score. Maybe in your game over scene.
To retrieve the low score, do this
var savedFastestScore = UserDefaults.standard.float(forKey: "Fastest Score")

Related

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){
...
}

Error in Swift 5 with Xcode when creating a timer out of a label

I'm using Swift 5 in Xcode to code an app. On one view controller, I am creating a timer, which counts from 20 minutes down to 0. I have what I think is a successful code, but it throws back one error. In the line
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(SkippingViewController.updateTimer), userInfo: nil, repeats: true)
it gives an error saying Type 'SkippingViewController' has no member 'updateTimer' (SkippingViewController is the name of the view controller for the page of the app my timer is on)
How can I resolve this issue?
import UIKit
class SkippingViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var startWorkoutButton: UIButton!
#IBOutlet weak var pauseWorkoutButton: UIButton!
var timer = Timer()
var counter = 20.00
var isRunning = false
override func viewDidLoad() {
super.viewDidLoad()
timeLabel.text = "\(counter)"
startWorkoutButton.isEnabled = true
pauseWorkoutButton.isEnabled = false
// Do any additional setup after loading the view.
}
#IBAction func startWorkoutButtonDidTouch(_ sender: Any) {
if !isRunning {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(SkippingViewController.updateTimer), userInfo: nil, repeats: true)
startWorkoutButton.isEnabled = false
pauseWorkoutButton.isEnabled = true
isRunning = true
}
}
#IBAction func pauseWorkoutButtonDidTouch(_ sender: Any) {
func updateTimer() {
counter -= 0.01
timeLabel.text = String(format: "%.01f", counter)
}
}
}
Your problem is, that there is no method called 'updateTimer' in SkippingViewController.swift. You falsely put the method inside of the method 'pauseWorkoutButtonDidTouch'. In order to resolve the error insert the following code into SkippingViewController.swift:
#objc func updateTimer() {
counter -= 0.01
timeLabel.text = String(format: "%.01f", counter)
}

Swift: another timer starting when application enters background

I've been struggling to figure this out for a few days: I need to create a timer that the user can't kill, so once they start it, even if they kill the app or it enters background, it will pick up where it left off, and to achieve this I am saving the date when the app terminated and then calculating the difference, so that part works fine.
The problem I keep running into however is that a second timer seems to start if I minimise the app and bring it back. I'm not sure how timers are managed in the background, but nothing of what I tried (calling timer.invalidate() when applicationWillResignActive gets called, calling it in deinit() ) seems to work. The behaviour I see after this is that the timer will count like this: 80 - 78 - 79 - 76 - 77..
There's also a problem where the timer will sometime run past the time it's supposed to run for after killing the app, but I can't find the exact cause for that because it doesn't always happen.
Any idea what I'm doing wrong?
Thanks a lot.
class Focus: UIViewController {
// MARK: Variables
var timer = Timer()
let timeToFocus = UserDefaults.standard.double(forKey: "UDTimeToFocus")
let currentFocusedStats = UserDefaults.standard.integer(forKey: "UDFocusStats")
// MARK: Outlets
#IBOutlet weak var progress: KDCircularProgress!
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var focusTimeLabel: UILabel!
#IBOutlet weak var stepNameLabel: UILabel!
#IBOutlet weak var focusAgain: UIButton!
#IBOutlet weak var allDone: UIButton!
#IBOutlet weak var help: UIButton!
#IBOutlet weak var dottedCircle: UIImageView!
// MARK: Outlet Functions
#IBAction func helpTU(_ sender: Any) { performSegue(withIdentifier: "ToFocusingHelp", sender: nil) }
#IBAction func helpTD(_ sender: Any) { help.tap(shape: .rectangle) }
#IBAction func allDoneTU(_ sender: Any) {
UserDefaults.standard.set(false, forKey: "UDFocusIsRunning")
UserDefaults.standard.set(false, forKey: "UDShouldStartFocus")
completeSession()
hero(destination: "List", type: .zoomOut)
}
#IBAction func allDoneTD(_ sender: Any) { allDone.tap(shape: .rectangle) }
#IBAction func focusAgainTU(_ sender: Any) {
UserDefaults.standard.set(currentFocusedStats + Int(timeToFocus), forKey: "UDFocusStats")
UserDefaults.standard.set(true, forKey: "UDShouldStartFocus")
initFocus()
}
#IBAction func focusAgainTD(_ sender: Any) { focusAgain.tap(shape: .rectangle) }
// MARK: Class Functions
#objc func initFocus() {
var ticker = 0.0
var angle = 0.0
var duration = 0.0
if UserDefaults.standard.bool(forKey: "UDShouldStartFocus") == true {
UserDefaults.standard.set(Date(), forKey: "UDFocusStartDate")
UserDefaults.standard.set(false, forKey: "UDShouldStartFocus")
ticker = timeToFocus
duration = timeToFocus
angle = 0.0
print("starting")
} else {
let elapsedTime = difference(between: UserDefaults.standard.object(forKey: "UDFocusStartDate") as! Date, and: Date())
let timeLeft = timeToFocus - elapsedTime
ticker = timeLeft
duration = timeLeft
angle = elapsedTime / (timeToFocus / 360)
}
// Timer
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if ticker > 0 {
self.timeLabel.text = "\(Int(ticker))s"
ticker -= 1
}
}
timer.fire()
// Progress Circle
progress.animate(fromAngle: angle, toAngle: 360, duration: duration) { completed in
if completed { self.completeSession() }
}
// UI Changes
allDone.isHidden = true
focusAgain.isHidden = true
help.isHidden = false
}
func completeSession() {
// The timer gets fired every time, but this will invalidate it if it's complete
timer.invalidate()
timeLabel.text = "Done"
help.isHidden = true
allDone.isHidden = false
focusAgain.isHidden = false
}
// MARK: viewDidLoad
override func viewDidLoad() {
initFocus()
allDone.isHidden = true
focusAgain.isHidden = true
if timeToFocus < 3600 { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60)) minutes" }
else if timeToFocus == 3600 { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60/60)) hour" }
else { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60/60)) hours" }
stepNameLabel.text = UserDefaults.standard.string(forKey: "UDSelectedStep")
// This resumes the timer when the user sent the app in the background.
NotificationCenter.default.addObserver(self, selector: #selector(self.initFocus), name: NSNotification.Name(rawValue: "WillEnterForeground"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.fadeProgress), name: NSNotification.Name(rawValue: "WillEnterForeground"), object: nil)
}
#objc func fadeProgress(){
// This function is called both when the view will enter foreground (for waking the phone or switching from another app) and on viewWillAppear (for starting the app fresh). It will fade the progress circle and buttons to hide a flicker that occurs.
timeLabel.alpha = 0
dottedCircle.alpha = 0
progress.alpha = 0
allDone.alpha = 0
focusAgain.alpha = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
UIButton.animate(withDuration: 0.5, animations: {
self.timeLabel.alpha = 1
self.dottedCircle.alpha = 1
self.progress.alpha = 1
self.allDone.alpha = 1
self.focusAgain.alpha = 1
})
})
}
// MARK: viewWillAppear
override func viewWillAppear(_ animated: Bool) { fadeProgress() }
}
It seems the problem is that you create a local timer variable inside initFocus() but you call invalidate inside completeSession for another timer defined there:
class Focus: UIViewController {
// MARK: Variables
var timer = Timer()

Swift Progress View with NSTimer

I have a Progress view bar that I would like to use to indicate time. This is my first project in Swift, and I am unsure how to go about this. So any help/ advise would be appreciated ...
(Using Xcode 7.2 and Swift 2.0)
Below Is my view controller. When 'btnPlaySession' is triggered, the content on the view controller is changed every 20 seconds. While the timer is counting to 20, id like to indicate this with the progress bar (so the progress bar resets, each time the content changes).
class CreatedSessionViewController: UIViewController {
var createdSession: [YogaPose]!
var poseDuration: Double = 20.00
var timer = NSTimer!()
var currentPoseIndex = 1
//Outlets:
#IBOutlet var poseProgressView: UIProgressView!
#IBOutlet var lblPoseCount: UILabel!
#IBOutlet var lblPoseName: UILabel!
#IBOutlet var imgPose: UIImageView!
#IBOutlet var tvDescription: UITextView!
// Do any additional setup after loading the view:
override func viewDidLoad() {
super.viewDidLoad()
displayFirstPoseInArray()
}
func displayFirstPoseInArray(){
lblPoseCount.text = (String(currentPoseIndex) + "/" + String(createdSession.count))
lblPoseName.text = createdSession[0].title
imgPose.image = UIImage(named: String(format: "%d.jpg", (createdSession[0].id)!))
tvDescription.text = createdSession[0].desc
}
#IBAction func btnPlaySession(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(poseDuration, target: self, selector: "getNextPoseData", userInfo: nil, repeats: true)
}
func getNextPoseData(){
if (currentPoseIndex < createdSession.count){
setProgressBar()
lblPoseCount.text = (String(currentPoseIndex + 1) + "/" + String(createdSession.count))
lblPoseName.text = createdSession[currentPoseIndex].title
imgPose.image = UIImage(named: String(format: "%d.jpg",(createdSession[currentPoseIndex].id)!))
tvDescription.text = createdSession[currentPoseIndex].desc
currentPoseIndex += 1
print(currentPoseIndex)
}
}
func setProgressBar(){
}
}
OK - so if you want the progress bar to update every second, then you need a timer that fires every second - but which does it 20 times, and calls setProgressBar as selector instead of getNextPoseData
within setProgressBar, you need to increment a class-level attribute, indexProgressBar perhaps, and simply set the progress bar attribute progress to 1.0 / indexProgressBar
if indexProgressBar == 20, then call getNextPoseData, and reset your progress bar
and here's a simplified version of how you might do that
class ViewController: UIViewController
{
#IBOutlet weak var progressBar: UIProgressView!
var timer = NSTimer!()
var poseDuration = 20
var indexProgressBar = 0
var currentPoseIndex = 0
override func viewDidLoad()
{
super.viewDidLoad()
// initialise the display
progressBar.progress = 0.0
}
#IBAction func cmdGo(sender: AnyObject)
{
// display the first pose
getNextPoseData()
// start the timer
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "setProgressBar", userInfo: nil, repeats: true)
}
func getNextPoseData()
{
// do next pose stuff
currentPoseIndex += 1
print(currentPoseIndex)
}
func setProgressBar()
{
if indexProgressBar == poseDuration
{
getNextPoseData()
// reset the progress counter
indexProgressBar = 0
}
// update the display
// use poseDuration - 1 so that you display 20 steps of the the progress bar, from 0...19
progressBar.progress = Float(indexProgressBar) / Float(poseDuration - 1)
// increment the counter
indexProgressBar += 1
}
}
For Continues loader
func setProgress() {
time += 0.1
ProgressBar.setProgress(time / 3, animated: true)
if time >= 3 {
self.time = 0.01
ProgressBar.progress = 0
let color = self.downloadProgressBar.progressTintColor
self.downloadProgressBar.progressTintColor = self.downloadProgressBar.trackTintColor
self.downloadProgressBar.trackTintColor = color
}

Unrecognized selector for instance error Swift

Im creating a simple typing app with timer. The timer should be in a form of a progress bar that decrements every second.
To implement the progressbar, I intended to set the progress to 0.1 lesser to the current progress every 1 second. But there is an "Unrecognized selector for instance " error when i set the progress.
Is there any other way to work around.
import Foundation
import UIKit
class TestView: UIViewController, UITextInputTraits {
#IBOutlet weak var TestLabel: UILabel!
#IBOutlet weak var TypingField: UITextField!
#IBOutlet weak var progressView: UIProgressView!
var time : Float = 0.0
var timer: NSTimer!
var test = 0;
var progress : Float = 1
var myMutableString = NSMutableAttributedString()
var testStringArray = ["abode" , "tutorial" , "completed",
"war", "method", "continue",
"machine", "texting" , "iterate"]
var idx = 0;
var setProg : Float = 1
func textFieldDidChange(textField: UITextField) {
let s = TypingField.text!
if(s.characters.last == " "){
let word = s.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
if(!word.isEmpty){
print(testStringArray[idx])
if(word == testStringArray[idx]){
idx++;
TypingField.text = "";
TestLabel.text = testStringArray[idx];
}else{
TypingField.text = "";
}
}
}
}
func setProgress() {
setProg -= 0.1
progressView.progress = setProg <-- cannot decrement progress bar
}
override func viewDidLoad() {
super.viewDidLoad()
TypingField.autocorrectionType = .No
TypingField.autocapitalizationType = .None
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
TestLabel.text = testStringArray[idx];
TypingField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You have coded scheduledTimerWithTimeInterval in a manner like it is done with Objective-C. In Swift it is done differently. Instead of:
selector:Selector("setProgress")
just use:
selector:"setProgress"
Change the function declaration for func setProgress() to:
func setProgress(sender: NSTimer)
And change Selector("setProgress") to "setProgress:"
EDIT:
Apparently the method setProgress already exists, so you'll need to rename the function, e.g. to setProgressBar or something to that effect.
Method 'setProgress' with Objective-C selector 'setProgress:' conflicts with setter for 'progress' with the same Objective-C selector
I think the error was fix when I delete and recreate the progressbar.. it may have been as MikeG suggested about incorrect connection on the outlet. Anyways thanks all for your help..