Launch saved state at startup of app (swift) - ios

what the app does so far is that users go through an array of strings by clicking a button, when they leave and remove the app from multitasking it saves where they were in the array. But when you startup the app you get stuck at the first String but you have to press the button called showFunFact() to get where you left off. I would like the current one to come at startup
Here's the code:
let TechnologyfactBook = TechFactBook()
var TechfactIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showFunFact() {
if (UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
if (TechfactIndex >= TechnologyfactBook.TechfactsArray.count) {
self.TechfactIndex = 0
}
TechfactIndex = NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation")
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
TechfactIndex++
NSUserDefaults.standardUserDefaults().setInteger(TechfactIndex, forKey: "ByteLocation")
NSUserDefaults.standardUserDefaults().synchronize()
}

Just add the code that loads the last location into the viewDidLoad. What the code does is first it checks if the user's last location is greater than the starting position, which I assume is 0. Once it checks this, it will assign the text value to the value that the user last had.
override func viewDidLoad() {
super.viewDidLoad()
//Checks if the value that is saved into the app is greater than the
//starting position the app first starts at (assuming index 0?)
if NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation") != 0 {
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
} else {
//The code that first displays the values if the user
//just started the app
}
}

Related

Swift: How to display two buttons that have been pressed on a label

I am trying to get buttons (they are numbers) when pressed to show two digits in the label, currentChannelLabel, I created. I have been messing around with this and I can either get one number to show, or I get two of the same number to show. How do I fix my code so I get one number to show, then a second number? After this, if another button is selected, it will show the first number again and then the second. For example:
(button 1 is pressed)
currentChannelLabel: 1
(button 2 is pressed)
currentChannelLabel: 12
(button 3 is pressed)
currentChannelLabel: 3
(button 4 is pressed)
currentChannelLabel: 34
here is my code:
#IBAction func channelNumbers(_ sender: UIButton) {
var number = ""
if let pressed = sender.currentTitle {
number = "\(pressed)"
currentChannelLabel.text = number
if let pressed2 = sender.currentTitle{
number += "\(pressed2)"
currentChannelLabel.text = number
}
}
}
The problem with your code is that pressed2 will always be equal to pressed, because it's not going to change within the body of the function — instead, when the second button is pressed, the function is called again.
Here's some code that should do what you want:
#IBAction func channelNumbers(_ sender: UIButton) {
guard let number = currentChannelLabel.text else {
currentChannelLabel.text = sender.currentTitle
return
}
currentChannelLabel = (number.count == 1 ? number : "") + (sender.currentTitle ?? "")
}
Your code doesn't make much sense. You have two nested if let statements. Either the first will fail, or both will succeed.
You need something like this:
#IBAction func channelNumbers(_ sender: UIButton) {
if let pressed = sender.currentTitle {
let oldLabelText = currentChannelLabel.text ?? ""
currentChannelLabel.text = oldLabelText + pressed
}
}
The string you get in pressed will be the title of the button that was pressed. Then you append that to previous text from the label, and put the result back into currentChannelLabel.text.
(Note that storing your string in currentChannelLabel.text is not ideal. You should really keep a string var in your view controller, append to that, and copy that string into currentChannelLabel.text. (View objects should not hold state. They should display info to the user and collect input from the user.)

How can I load all the data stored in the array, when the app starts again (Swift)?

I have to implement a function that loads the stored data into the shopping list array, when the app starts, and a function that stores the current contents of my list when the button is pressed. I used UserDefaults class and it works for the second function (when the button is pressed) but not for the first one (when the app starts). If I restart the app and press the button, I see that only the last input was stored. How can I fix the code if I want to store all data from the array?
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var inputEntered: UITextField!
// keyboard gives up the first responder status and goes away if return is pressed
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
inputEntered.resignFirstResponder()
return true
}
var shoppingList: [String] = []
#IBAction func buttonAddToList(_ sender: UIButton) {
if let item = inputEntered.text, item.isEmpty == false { // need to make sure we have something here
shoppingList.append(item) // store it in our data holder
}
inputEntered.text = nil // clean the textfield input
print(shoppingList.last!) // print the last element to avoid duplicates on the console
storeData()
}
// this function stores the current contents of my list when the button is pressed
func storeData () {
let defaults = UserDefaults.standard
defaults.set(inputEntered.text, forKey: "Saved array")
print(defaults)
}
// to call the function storeDate(), when the app restarts
override func viewDidLoad() {
super.viewDidLoad()
inputEntered.delegate = self
// Do any additional setup after loading the view.
storeData()
}
}
You can add a getter and a setter to your array and persist your values to user defaults. This way you don't need to call storeData and or remembering to load the data when initialising your array:
var shoppingList: [String] {
get {
UserDefaults.standard.stringArray(forKey: "shoppingList") ?? []
}
set {
UserDefaults.standard.set(newValue, forKey: "shoppingList")
}
}
You are calling storeData() in viewDidLoad, but inputEntered is empty then, so you are storing blank data.
Also, defaults.set(inputEntered.text, forKey: "Saved array") doesn't append new data onto the key -- it overwrites what is there. So you are not storing the array, you are only storing the last value.
You need to store shoppingList to store the array.
I'm unsure if I have got what you are asking for but have you tried looking into UITextFieldDelegate.
If you add this, it will ensure add the protocols of which I am sure there is a method that can be called when the user finishes editing text field.

How to add an alert to switch toggle for multiple answers?

I created a Quiz app. There is one question with five answers, where the user can choose one of them. However at the moment it's possible to choose more than one answer. How can I add an alert (UIAlertController) and a restriction so if the user has already chosen one answer and tries to choose a second one ?
var questionIndex = 0
var answersChosen: [Answer] = []
func updateUI() {
let currentQuestion = questions[questionIndex]
let currentAnswers = currentQuestion.answers
let totalProgress = Float(questionIndex) /
Float(questions.count)
numberOfQuestion.text = "\(questionIndex+1)/5"
// navigationItem.title = "Вопрос - \(questionIndex+1)"
mainQuestion.text = currentQuestion.text
progressBar.setProgress(totalProgress, animated:
true)
updateMultipleStack(using: currentAnswers)
}
#IBAction func multipleAnswerButtonPressed(_ sender: Any) {
let currentAnswers = questions[questionIndex].answers
if firstSwitch.isOn {
answersChosen.append(currentAnswers[0])
}
if secondSwitch.isOn {
answersChosen.append(currentAnswers[1])
}
if thirdSwitch.isOn {
answersChosen.append(currentAnswers[2])
}
if fourthSwitch.isOn {
answersChosen.append(currentAnswers[3])
}
if fifthSwitch.isOn {
answersChosen.append(currentAnswers[4])
}
nextQuestion()
}
func updateMultipleStack(using answers: [Answer]) {
firstSwitch.isOn = false
secondSwitch.isOn = false
thirdSwitch.isOn = false
fourthSwitch.isOn = false
fifthSwitch.isOn = false
firstQuestionLbl.text = answers[0].text
secondQuestionLbl.text = answers[1].text
thirdQuestionLbl.text = answers[2].text
fourthQuestionLbl.text = answers[3].text
fifthQuestionLbl.text = answers[4].text
}
override func prepare(for segue: UIStoryboardSegue, sender:
Any?) {
if segue.identifier == "ResultSegue" {
let resultsViewController = segue.destination as! ResultViewController
resultsViewController.responses = answersChosen
}
}
}
The approach I'd take would be to separate the user's selection of an answer (UISwitch collection) from processing the selected answer(Submit Answer button). This would allow the user to change their answer. The previous selection is automatically cleared. The answer is final when pressing a "Submit Answer" button (not shown).
In this code example, the switches are set as an IBOutlet collection. The tag on each switch is set from 0 to 4. All the switches are connected to an IBAction called switchPressed. A variable called selectedAnswer is used to identify the user's final picked answer when the "Submit Answer" is pressed.
#IBOutlet var switches: [UISwitch]!
private let noAnswer = -1
private var selectedAnswer = -1
#IBAction func switchPressed(_ sender: UISwitch) {
// Check if user turned off switch
guard sender.isOn == true else {
selectedAnswer = noAnswer
return
}
// Get user's answer and set all switches
selectedAnswer = sender.tag
switches.forEach { $0.isOn = false }
sender.isOn = true
}
#IBAction func submitAnswer(_ sender: UIButton) {
guard selectedAnswer > noAnswer else { return }
// use selectedAnswer to process answer and go to the next question
answersChosen.append(currentAnswers[selectedAnswer])
}
You could store all the switches in an array named Switches for example and then run a for loop to check if more than one is selected when checking answer.
var count = 0
for switchToggle in switches {
if switchToggle.isOn {
count += 1
if count > 1 {
//Print Warning "only select one"
}
} else {
//append answers
}
}
you could also use this to have multiple answers for certain quiz question
For the warning, you have different options you could use. You could use UIAlertController to display a warning if more than one button is selected but this could become annoying for the user after a while.
Another option could be to use a UILabel which warns the user to only pick one answer. You could animate the label to fade away after a few seconds.

Trigger UIAlertViewController Based on Time

I have UITable to display different animals. When you select a cell in the table, a new view controller with a large UIImage is pushed. Currently, when you zoom in on the image, a UIAlertView is triggered that asks the user if they would like to download hi res images. If they click yes, the "hi-res-flag" is set to "yes" in user defaults and they no longer see the pop up. However, if they select no, the hi-res-flag will continue to pop up each time they zoom in on a photo.
Instead, if they answer no, I would like to have this flag pop up occasionally. Not every time the click a cell in the species table, nor every time they open the app. Something more like once or twice a month. Is there a way to use time in the logic of an iOS app? For instance, erase the value set for "high-res-flag" (if already equals 'no') in user defaults, once a month?
Store the time you showed the alert last in the user preferences, and then check that value every time before you present the alert whether a certain time has passed.
I have written a time checker class that does the job. The code is in Swift. You can use it from your Objective-C code as well. You can find this code in gist here.
Solution
Below, you use the viewWillAppear delegate method to see if the hiResFlag is existing. If it is present and false, then you check to see if you can display the popup:
import UIKit
class ImageViewController: UIViewController {
//Whenever you enter the Image View Controller, you check whether to show popup or not
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let hiResFlag = hiResFlag {
if hiResFlag == false {
if PopUpTimeChecker.shouldShowPopUp() {
self.presentAlert()
}
}
}
}
func presentAlert() {
let alert = UIAlertController.init(title: nil, message: "Show Pop up", preferredStyle: .alert)
let action = UIAlertAction.init(title: "Yeahh!", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
The following code implements the time-checking algorithm. Edit popUpTimeInterval below for setting your minimum time. Right now, it is set to be 15 days (in seconds). Once in every 15 days the pop-up will be shown when you call the shouldShowPopUp method.
import UIKit
//Below 4 variables, I have made them Global. No need to make them global in your case
#objc var popUpTimeInterval: UInt64 = 1296000 //15 days in seconds
#objc var hiResFlag: Bool? {
get {
return UserDefaults.standard.object(forKey: "HiResFlag") as? Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "HiResFlag")
}
}
#objc var isFirstTimePopUp: Bool {
get {
let value = UserDefaults.standard.object(forKey: "IsFirstTimePopUp")
return value == nil ? true : value as! Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "IsFirstTimePopUp")
}
}
#objc var lastDateOfPopUp: Date? {
get {
return UserDefaults.standard.object(forKey: "LastDateOfPopUp") as? Date
}
set {
UserDefaults.standard.setValue(newValue, forKey: "LastDateOfPopUp")
}
}
#objc class PopUpTimeChecker {
#objc static fileprivate func setLastPopUpDate() {
//Setting current date to last shown pop up date
lastDateOfPopUp = Date()
}
#objc static fileprivate func timeIntervalSinceLastPopUp() -> UInt64 {
//Returning how much time (in seconds) has passed from last popup date until now
return UInt64(Date().timeIntervalSince(lastDateOfPopUp!))
}
#objc static func shouldShowPopUp() -> Bool {
//We proceed further only if we have the last date when pop up was displayed, else we create and set it as the current date
if let _ = lastDateOfPopUp {
let timeInterval = timeIntervalSinceLastPopUp()
if timeInterval > popUpTimeInterval {
self.setLastPopUpDate()
return true //Show pop up
} else {
if isFirstTimePopUp {
//If this is the first time, you just allow the pop up to show, don't allow otherwise
isFirstTimePopUp = false
return true
} else {
return false
}
}
} else {
self.setLastPopUpDate() //Since we don't have a last date, we set it here for starting off
return self.shouldShowPopUp() //Recursively call method
}
}
}

User defaults Only Working On Restart

I am trying to implement a light and dark mode in my application. In the settingsViewController I have these lines of code:
//Sets user default for colour
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
//UISegment control for user to pick colour
#IBOutlet var colourSegment: UISegmentedControl!
//Updates lightMode based on user selection
#IBAction func didChangeColours(_ sender: Any) {
if colourSegment.selectedSegmentIndex == 0 {
UserDefaults.standard.set(true, forKey: "lightMode")
} else if colourSegment.selectedSegmentIndex == 1 {
UserDefaults.standard.set(false, forKey: "lightMode")
}
}
In my entryViewController, in my viewDidLoad, I have:
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
if lightMode == false {
Colours.darkMode()
}
customisations()
The issue that I'm running into is that for some reason, my application is only changing it's colour scheme after I restart it. That is, if the user selects the darkIndex of the colourSegment, the application only updates the colour after I restart. I am wondering what is the solution for this.
The problem is in the line -
//Sets user default for colour
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
this line is not for setting the Userdefaults but instead it gets the UserDefaults. Since you use it before setting the default Value, it doesn't reflect the right segmented choice. So your setting part is correct and you should fetch the value only after you have set it.
Also in your entryViewController, instead of using it from settingsVC, do below -
//get from UserDefaults
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
//Compare the previous fetched value
if lightMode == false {
Colours.darkMode()
}
//This function sets the colour for the elements
colours()
Because you are assigning the lightMode value ONLY 1 time during init, you don't reflect the changes to the variable, so it will always be that value
To always get the lastest value, use this:
let lightMode: Bool {
get {
return UserDefaults.standard.bool(forKey: "lightMode")
}
}
Also, you should call the color change immediatelly after change the value
I am assuming that you are returning to your entryViewController from your settingsViewController; Since you are returning to an existing view controller instance, the code in viewDidLoad is not executed when you return.
Move the code to viewWillAppear; This way your code will execute prior to the view controller appearing even when you return to the existing instance:
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
if lightMode == false {
Colours.darkMode()
}
}

Resources