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

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.

Related

Using multiple toggles I'would like to regroup all toggles in one action

I'm trying to create an array composed of multiple tools.
The array changes depending on which tools are carried.
Expecting a long list of tools I would like to avoid having to write an action for each single element to add or remove from the array. ( I managed this part as you'll see below)
Instead I'd like to use the name of the toggle switch and apply the same action to all the toggles on the screen.
I came up with this inelegant method:
import UIKit
class mainVC: UIViewController {
var myEquippement: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func shovelSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Shovel")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Shovel")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func hammerSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Hammer")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Hammer")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func screwdriverSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Screwdriver")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Screwdriver")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func sawSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Saw")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Saw")
myEquippement.remove(at: subjectIndex!)
}
}
}
could you point me please to a better way of doing it.
I thought of using something like this:
#IBAction func toggleSwitched(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append(sender.title!)
}
else {
let subjectIndex = myEquippement.firstIndex(of: sender.title!)
myEquippement.remove(at: subjectIndex!)
}
}
but sender.title always returns a nil value, the force unwrapping crashes the app.
Thanks in advance for your help.
One solution would be to have a master array of tool names. When creating a switch, assign its tag property to the corresponding index within that array.
Then in your single switch handler you can use the sender's tag to get the name of the tool from the master list of tool names. That will give you the title for the switch. Then your logic for adding/removing the title from myEquipment can be used.

Swift. Passing persistent data from one View Controller to another and storing within a label array

I am using a segue to go from View Controller 1 to View Controller 2. View Controller 1 has a button that sets the persistent data when it is clicked on:
I declare a global var for user default:
let userDefault = UserDefaults()
Here is my button to set the user default to a string with text values from labels:
#IBAction func saving(_ sender: UIButton) {
let savedText = "Gallon \(gallonTextFieldOutlet.text) is equal to Litre \(litreTextFieldOutlet.text) is equal to Pint \(pintTextFieldOutlet.text)"
userDefault.setValue(savedText, forKey: "SavedConversion")
}
I then get a reference to View Controller 2 and pass this user default when the user goes from View Controller 1 to View Controller 2 via a segue:
// in view controller 2: reference to get persistent data
var volumeDataOne:String?
// in view controller 2: instantiation of my queue class to use methods
var queue = Queue<String>()
// segue action in view controller 1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "savedVolumeData"
{
let historyVC:VolumeHistoryViewController = segue.destination as! VolumeHistoryViewController
if let value = userDefault.value(forKey: "SavedConversion") as? String {
historyVC.volumeDataOne = value
}
}
I get this in the View Controller 2 and I am trying to set this to three labels that I have in this View Controller:
func DisplayVolumeHistory() {
let labelArray = [volumeDataLabelOutlet, volumeDataLabelTwoOutlet, volumeDataLabelThreeOutlet]
if let bindingOptional = volumeDataOne
{
for index in 0..<labelArray.count
{
queue.enqueue(val: bindingOptional)
labelArray[index]?.text = queue.arr[index]
}
}
}
In my specification, I have been told that the data needs to be persistent and that only the last five data can be stored at one time. So I have a class that I have called Queue which is referenced in this function. This function gets called on the viewDidLoad of the View Controller 2.
override func viewDidLoad() {
super.viewDidLoad()
//Debug ...
volumeDataLabelOutlet.text = "na"
volumeDataLabelTwoOutlet.text = "na 2"
volumeDataLabelThreeOutlet.text = "na 3"
//...
DisplayVolumeHistory()
// Do any additional setup after loading the view.
}
I have tested my Queue class in a Playground and it works as expected. The Queue class can be seen here:
class Queue {
var arr = [T]()
func enqueue(val: T){
if(arr.count < 3) {
arr.append(val)
} else {
for i in 0..<arr.count-1 {
arr[i] = arr[i + 1]
}
arr[arr.count - 1] = val
}
}
func dequeue() -> (T?){
if (arr.isEmpty){
return nil
} else {
return arr.remove(at: 0)
}
}
}
Here is my issue that I cannot seem to figure out. In View Controller 2, all of the three labels will have persistent data, but they will all be of the same data,
For example, if I have data as follows:
DATA 1: 555
DATA 2: 700
DATA 3: 62
I would want:
LABEL 1 --> 555
LABEL 2 --> 700
LABEL 3 --> 62
However, currently it will be:
LABEL 1 --> 62
LABEL 2 --> 62
LABEL 3 --> 62
I am unsure as to why debugging. I believe it is because my persistent data in my View Controller 1 is only taking a string, which the Dictionary is overriding as I use the same key.
However, I looked at the documentation and trying to use a user default array did not solve my issue and I am unsure as to what is causing this problem.
I appreciate any guidance and help to try to solve this issue.
Thanks
You are right on your comment,
which the Dictionary is overriding as I use the same key
Each time you tap that button, you are overriding the value with the new one. So you should be seeing always on your v2, the last one.
So probably you should store an array instead of a String.
//on v1
var values = [String]()
#IBAction func saving(_ sender: UIButton) {
let savedText = "Gallon \(gallonTextFieldOutlet.text) is equal to Litre \(litreTextFieldOutlet.text) is equal to Pint \(pintTextFieldOutlet.text)"
values.append(savedText)
UserDefaults.standard.setValue(values, forKey: "SavedConversion")
}
You will pass it to v2 as you are doing now
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "savedVolumeData"
{
let historyVC:VolumeHistoryViewController = segue.destination as! VolumeHistoryViewController
if let values = userDefault.value(forKey: "SavedConversion") as? [String] {
historyVC.volumeDataOne = values //volumeDataOne now needs to be an [String]
}
}
Then on V2, go through your array and labels.

Send many things through a segue - Swift 4

Edit - not really a question anymore - please feel free to delete this post or use it as you wish. A little red box says this post is mostly code and Grammarly is kind of helpful too so just rambling on now until the little red box goes away.
Working solution:
ViewController1 ->
enum to tag many buttons - (change each button tag in interface builder)
enum Answers : Int, CustomStringConvertible {
case bag = 1
case bird = 2
enum for text
var description : String {
switch self {
case .bag: return "Beg"
case .bird: return "Birds"
}
}
enum for images - (stored in Assets)
var pics : String {
switch self {
case .bag: return "beg"
case .bird: return "birds"
}
}
}
Prepare segue - (storyboard should match identifier name)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowAnswers" {
guard
let controller = segue.destination as? proverbsViewController,
let category = sender as? Answers
else { return }
controller.answer = category.description
controller.image = UIImage(named: category.pics)!
}
}
Button to segue - all buttons are connected here (tagged in IB)
#IBAction func onButtonTap(_ sender: UIButton) {
performSegue(withIdentifier: "ShowAnswers", sender: Answers(rawValue: sender.tag))
}
}
Recieve data:
ViewController2 ->
var answer = ""
var image = UIImage()

Swift property nil outside unwindToViewController

To pass data between views, I decided to use a "temporary" object that would act as the data model of my views.
var tempMedecine = TempMedecine()
var xValue = 0
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let dmController = segue.destinationViewController as? JRBDosageMainTableViewController {
dmController.tempMedecine = self.tempMedecine
}
}
#IBAction func unwindToViewController(segue: UIStoryboardSegue) {
if let dosageController = segue.sourceViewController as? JRBDosageMainTableViewController {
self.tempMedecine = dosageController.tempMedecine!
self.xValue = 10
let dosageCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 0))
dosageCell?.detailTextLabel?.text = String(self.tempMedecine.dosageQuantity!) + " " + self.tempMedecine.dosageQuantityType!
}
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if identifier == "saveMedecine" {
print(xValue)
guard tempMedecine.name != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to define a name", preferedStyle: .Alert)
return false
}
guard self.tempMedecine.dosageQuantityType != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to set a quantity", preferedStyle: .Alert)
return false
}
}
else {
return true
}
return false
}
This is some of my code from the "index" viewController where I need to tackle validation.
As you can see all of my viewControllers have a property named tempMedecine. I pass it around and update the data if needed.
The problem is that self.tempMedecine.dosageQuantityType returns nil in the shouldPerformSegueWithIdentifier method but isn't returning nil in the unwindToViewController method.
I figured there could be two instances of my TempMedecine object, but that's not the case. I also thought there might be a problem with the way I pass the tempMedecine variable between my viewControllers but the property tempMedecine.name is effectively transfered, the only difference is that this property is set in the same viewController where I want to implement validation :
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
self.tempMedecine.name = textField.text
return true
}
It's really looking like I'm working with two different scope. As soon as I leave the unwindToViewController method, I would get back to another scope where the tempMedecine variable isn't updated.
But the weird part is when I use a simple variable like xValue. If I update its value in the unwindToViewController method I get the correct value in shouldPerformSegueWithIdentifier
What am I missing? Thanks for your help.
Okay, I fixed it. What happened was that I implemented some code to reset the dataSource which is tempMedecine if the user decided to click on the Back Button in the navigation bar :
if self.isMovingToParentViewController() {
tempMedecine?.dosageQuantity = nil
tempMedecine?.dosageQuantityType = nil
}
The thing is, I never thought the issue could come from this as I can use the tempMedecine data to set the value in my tableView after unwinding to the index viewController but I totally missed the part when object are passed my reference.

Passing data to a specific View Controller without entering the controller Swift

I am making a app with many Views. When I am navigating from one View to another I want the data to be sent to a third view without entering the view. I know that it is a way to do this by using this code:
let nextView = self.storyboard?.instantiateViewControllerWithIdentifier("view3") as lastViewController
nextView.data = 1103
But if I do it this way and don't move to the view the data won't be saved. How can I save the data without entering it? Do I need to open it?
In the first view:
var chosenItem:Int = 0
#IBAction func buttonClicked(sender: AnyObject) {
if sender.tag == 0 {
chosenItem = 1
} else if sender.tag == 1 {
chosenItem = 2
} else if sender.tag == 2 {
chosenItem = 3
}
}
This is how my code looks now:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toView2" {
let nextView = segue.destinationViewController as questionAgeViewController
nextView.firstAnswer = chosenItem
}
}
In the next view I send both the first item and the second. And as you understand this is not the best way to get the information. Is there a better?
I think you might be looking for a global variable. Use this code to make the variables:
struct globalVars {
static var firstVar = "Your text"
static var secondVar = Some number
static var thirdVar = Some array
}
In a other view you can access the information like this:
let globalText = globalVars.firstVar
println(globalText)
To edit the variables just use this code:
globalVars.firstVar = "The new text"
Create a singleton class and add properties into it, as per your requirement.
Now you can set its properties from your firstViewController and access the same from SecondViewController.
class Singleton {
class var sharedInstance: Singleton {
struct Static {
static var instance: Singleton?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = Singleton()
}
return Static.instance!
}
}
Singleton tutorials for Swift
http://thatthinginswift.com/singletons/
http://code.martinrue.com/posts/the-singleton-pattern-in-swift
http://www.raywenderlich.com/86477/introducing-ios-design-patterns-in-swift-part-1

Resources