struct not keeping tuples order when passed (swift3) - ios

My Code produces a a tuples that is displayed on a label on view controller 1. I tried struct the label from vc1 to vc2 but the order is not being kept. All I want to do is replicate the exact order and the way the tuple is displayed on vc 1, on VC 2.
VIEW CONTROLLER 1
import UIKit
var number = [Int]()
var yourArray = [String]()
class ViewController: UIViewController {
#IBOutlet var labez: UILabel!
#IBOutlet var textA: UITextField!
#IBOutlet var textB: UITextField!
#IBAction func move(_ sender: Any) {
bad.mm = [String( labez.text ?? "")]
}
#IBAction func store(_ sender: Any) {
yourArray.append((textA.text!))
number.append(Int(textB.text!)!)
let tuples = zip(yourArray,number)
let sorted = tuples.sorted(by: { this, next in
if this.0 < next.0 {
return true
} else if this.0 == next.0 {
return this.1 < next.1
} else {
return false
}
})
print(sorted)
labez.text = sorted.map { " \($0)" }.joined(separator:"\n")
bad.mm = [String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
)]
}
struct bad {
static var mm = [String]()
}
}
view controller 2
import UIKit
class ViewController2: UIViewController {
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}

Besides my critique of how you designed your data and your naming conventions. I believe what you want is to ASSIGN your bad.mminstead of APPEND.
What is happening is the first time you enter values to bad.mm it is (a, 2). Then when it is appended you add the sorted arrays (a, 1), (a, 2) to the existing string, making it (a, 2), (a, 1), (a, 2) If you assign it it will be now just the new, sorted array, (a, 1), (a, 2).
To assign change
bad.mm.append(String(describing: sorted.map { " \($0)" }.joined(separator:"\n"))
In ViewController class to
bad.mm = String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
In ViewController your move function does a similar thing where it APPENDS to bad.mm where you probably want to assign. However you assign it with a UITextField.Text property which is optional. Using the ?? operator you can give unwrap this optional while providing it a default value. An empty string is often a good default value. So for this I would suggest changing the line inside of #IBAction func move to the following:
bad.mm = labez.text ?? ""
Or just actually delete this line since you assign bad.mm and labez.text at the same time in your earlier function. But that is why you were getting the optional() around your text.
That should give you your desired effect. The reason your "tuple" isn't being passed in the right order is that your not grabbing the sorted tuple from one VC to the next, you are grabbing an improperly formatted string from one VC to another. Consider passing the sorted tuple directly and then formatting the string separately in both ViewControllers to reduce confusion.

Why don't you use prepare(for segue: instead of a struct to pass the tuples.
You can do it like this:
In ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// give your segue an id (in the interface builder)
if segue.identifier == "mySegue" ,
let nextScene = segue.destination as? ViewController2 {
nextScene.yourTuples = yourArray // or anything else
}
}
And in your ViewController2:
class ViewController2: UIViewController {
val yourTuples: [String]()!
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}

Related

How to Check Answer in a Quiz when the Button isSelected

I'm new to Swift and Coding. My english is not so good so apologize in advance!
I want to build a Quiz App. Now I have some trouble with checking the correct Answer because my uibuttons can be selected. The Reason for that ist that I would like to have multiple Answers with multiple Choice and multiple correct answers.
I don't know what kind of Informationen of my Code do you need from me but I will post what I mean it could help.
Thank you so much in advance for your help!!!
Class
import Foundation
class Quiz {
let question: String
let options : [String]
let correctAnswer: [String]
init(question: String, options: [String], correctAnswer: [String]) {
self.question = question
self.options = options
self.correctAnswer = correctAnswer
}
func validateOption(_ index: Int) -> Bool {
let answer = options[index]
return answer == correctAnswer[index]
}
deinit {
}
}
My Viewcontroller
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var quenstionLabel: UILabel!
#IBOutlet var answerButton: [UIButton]!
let quizManager = QuizManager()
override func viewDidLoad() {
super.viewDidLoad()
getNewQuiz()
}
#IBAction func checkAnswerButtonTapped(_ sender: UIButton) {
}
#IBAction func checkMarkAnswerClicked(_ sender: UIButton) {
if sender.isSelected {
sender.isSelected = false
} else {
sender.isSelected = true
}
}
and my Quiz Manager
import Foundation
class QuizManager {
private var quiz: Quiz!
private var _totalAnswers = 0
private var _totalCorrectAnswers = 0
var question: String {
return quiz.question
}
var options: [String] {
return quiz.options
}
var totalAnswers: Int {
return _totalAnswers
}
var totalCorrectAnswers: Int {
return _totalCorrectAnswers
}
func refreshQuiz(){
let randomIndex = Int(arc4random_uniform(UInt32(quizes.count)))
let quizData = quizes[randomIndex]
quiz = Quiz(question: quizData.question, options: quizData.options, correctAnswer: quizData.correctAnswer)
}
func validateAnswer(index: Int){
_totalAnswers += 1
if quiz.validateOption(index){
_totalCorrectAnswers += 1
}
}
If there are needed more Informations, please let me know!!
It would be great, if someone could help me.
You can solve this problem following these steps.
Add an array for your answers in Question Class.
Second Add Delegate in your cell class that will notify your view controller
implement that delegate in view controller and append answers to your model.
On check button tapped simply compare array of your tapped answers with correct answers array.
//Step 1
var myAnswers:[String]
protocol CheckAnswerDelegate : class {
func didPressOptionButton(myAnswer: String)
}
#IBAction func buttonPressed(_ sender: UIButton) {
checkAnswerDelegate?.didPressOptionButton(myAnswer: self.answerButton.titleLabel?.text ?? "")
}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource, CheckAnswerDelegate {}
func didPressOptionButton(myAnswer: String) {
if question1.myAnswers.contains(myAnswer) {
if let firstIndex = question1.myAnswers.firstIndex(where: {$0 == myAnswer}) {
question1.myAnswers.remove(at: firstIndex)
}
}else {
question1.myAnswers.append(myAnswer)
}
self.answerView.reloadData()
}
What I would do is put all of your answer buttons in a list. Then, inside checkAnswerButtonTapped you can run through the list and see which ones of them have isSelected set to true.
for button in buttonList {
var answersSelected = []
if button.isSelected {
answersSelected.append(button.text)
// You may want something different here depending on how the buttons store the answer options
}
}
Once you're done with that, compare them with the predefined correct answers from your QuizManager.
One other thing you could do to make your life a little bit easier is using the .toggle() function with your Boolean variables. Basically it just switches the value of your variable. So in your checkMarkAnswerClicked you could just have sender.isSelected.toggle()

can't append text to string array due to Initializer for conditional binding error

My code below works perfectly if i switch the strings for the ints. However with the String where it is it is not working.
import UIKit
class ViewController: UIViewController {
#IBOutlet var txt: UITextField!
var arrayOfInt = [String]()
#IBAction func submit(_ sender: Any) {
if let text = txt.text {
if let name = String(text){
arrayOfInt.append(name)
}}}
}
You are (unnecessarily) initialising a String from a String using String(text); Unlike Int(_ string:String) this is not a failable initializer.
It is possible that a String cannot be parsed to an Int, so you need to check that Int(text) didn't return nil, but a String can always be initialised from a String.
When you say
if let name = String(text) {
The compiler give you an error because the if let... conditional binding construct requires an optional, but String(text) does not return an optional.
Your code can be simplified to:
class ViewController: UIViewController {
#IBOutlet var txt: UITextField!
var arrayOfString = [String]()
#IBAction func submit(_ sender: Any) {
if let text = sender.text {
arrayOfString.append(text)
}
}
}

How to check if input from UITextField matches Core Data attribute in array?

I have a Core Data object that has two attributes, question and answer. I also have a view that has a label, a UITextField and a button. I want to show the question attribute in the label and then in the UITextField type in the answer attribute for that question to check if it is correct.
So, in viewDidLoad, I ran a fetch on my Card object and put the results into an array. I followed this Stack Overflow post in order to figure out how to iterate through the array to show each question attribute in the label, but now I am unsure how exactly to check the answer attribute for that question. Below is my source code:
var cardFetch = [Card]()
var count = 0
#IBOutlet weak var displayLabel: UILabel!
#IBOutlet weak var inputText: UITextField!
#IBAction func nextButton(sender: AnyObject) {
displayLabel.text = cardFetch[count%cardFetch.count].question
count++
}
Try something like:
#IBAction func nextButton(sender: AnyObject) {
if let questionString = cardFetch[count%cardFetch.count].question as? String {
displayLabel.text = questionString
count++
}
}
#IBAction func checkAnswer(sender: AnyObject) {
if let answerString = cardFetch[count%cardFetch.count].answer as? String {
if let unwrappedTextfieldString = inputText.text as? String {
if answerString == unwrappedTextfieldString {
// Do whatever you want here
}
}
}
}
Note the unwraps that make optional unwrapping crash safe.

Immutable value of type '[String]' only has mutating member

Getting the error of Immutable value of type '[String]' only has mutating member in my swift project and researched some answers and none seem to fix the problem in my context. Take A Look:
import UIKit
class PassionViewController: UIViewController {
var factIndex = 0
var counter = 1
#IBOutlet var QuoteImage: UIImageView!
#IBOutlet weak var funFactLabel: UILabel!
let factBook = FactBook()
let Liked = Favourite()
override func viewDidLoad() {
super.viewDidLoad()
funFactLabel.text = factBook.factsArray[factIndex]
}
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 prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
#IBAction func showFunFact() {
factIndex++
if (factIndex >= factBook.factsArray.count) {
self.factIndex = 0
}
funFactLabel.text = factBook.factsArray[factIndex]
if counter == 36 {
counter = 1
} else {
self.counter++
}
QuoteImage.image = UIImage(named: "frame\(counter).jpg")
}
#IBAction func goBack() {
factIndex--
var number = factBook.factsArray.count-1
if (factIndex < 0){
self.factIndex = number
}
funFactLabel.text = factBook.factsArray[factIndex]
if counter == 1 {
counter = 36
} else {
self.counter--
}
QuoteImage.image = UIImage(named: "frame\(counter).jpg")
}
#IBAction func Like() {
let currentQuote = factBook.factsArray[factIndex]
Liked.favouriteArray.append(currentQuote)
}
}
The let currentQuote = factBook.factsArray[factIndex], factBook.factsArray is retrieved from another view controller which is a array collection. Liked.favouriteArray.append(currentQuote) is to store this element into favouriteArray which is from a struct called Favourite:
import Foundation
struct Favourite {
var favouriteArray:[String] = []
}
However, the line Liked.favouriteArray.append(currentQuote) is generating the error: Immutable value of type '[String]' only has mutating member. How can I resolve this error?
It's unclear because you didn't provide the Favourite type, but I assume it's either a struct, or it's a class that includes factsArray as a let variable. In either case, you're trying to modify a let variable, which is not allowed (mutating an immutable value). Either liked or factsArray must be var. (If Favourite is a struct, then liked must be var in either case.)
Note that Swift functions, methods, properties, and variables should start with a lowercase letter. Leading uppercase letters denote types.

How do you pass data between view controllers in Swift?

I tried creating global variables and updating the information when the view is loaded but data isn't being rendered.
GLOBAL VARIABLES
var viewName:String = ""
var viewDuration:String = ""
var viewPeriod:String = ""
var viewMinAmp:String = ""
var viewMaxAmp:String = ""
var viewStep:String = ""
var viewType:String = ""
Is there a more efficient way of passing information other than having global variables?
#IBOutlet var txtName: UITextField!
#IBOutlet var txtDuration: UITextField!
#IBOutlet var txtPeriod: UITextField!
#IBOutlet var txtMinAmp: UITextField!
#IBOutlet var txtMaxAmp: UITextField!
#IBOutlet var txtStep: UITextField!
#IBOutlet var txtType: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setInfo(viewName, duration: viewDuration, period: viewPeriod, minAmp: viewMinAmp, maxAmp: viewMaxAmp, step: viewStep, type: viewType)
}
func setInfo(name: String, duration: String, period: String, minAmp: String, maxAmp: String, step: String, type: String) {
txtName.text = name
txtDuration.text = duration
txtPeriod.text = period
txtMinAmp.text = minAmp
txtMaxAmp.text = maxAmp
txtStep.text = step
txtType.text = type
}
One solution would be to override prepareForSegue(segue:sender:) from within the view controller which contains the data that you wish to pass to the destination view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "YourSegueName") {
//get a reference to the destination view controller
let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass
//set properties on the destination view controller
destinationVC.name = viewName
//etc...
}
}
For Swift 3.0
final class Shared {
static let shared = Shared() //lazy init, and it only runs once
var stringValue : String!
var boolValue : Bool!
}
To set stringValue
Shared.shared.stringValue = "Hi there"
to get stringValue
if let value = Shared.shared.stringValue {
print(value)
}
For Swift version below 3.0
You can pass data between views using singleton class. It is easy and efficient way. Here is my class ShareData.swift
import Foundation
class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
Now in my ViewControllerOne I can set above variable.
//Declare Class Variable
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
self.shareData.someString ="Some String Value"
}
And in my ViewControllerTwo I can access someString as
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
NSLog(self.sharedData.someString) // It will print Some String Value
}
Personally, I prefer ways as follow:
If you want to jump forward between two view controllers (from A to B), as -pushViewController:animated: in navigation, you can define a property of model for Controller B and expose it publicly, then set this property explicitly before jumping from Controller A, it's pretty straightforward;
In case you want to jump backward from Controller B to A, use Delegate+Protocol mode. Controller B drafts a public protocol and own a "delegate" property, any object who would like to be the delegate of Controller B shall comply and implement its protocol(optionally). then prior to the jumping-backward, Controller B makes its delegate perform certain action(s) listed in protocol, the data could be transferred in this way;
Under certain circumstance, you may want to transfer data from a Controller(or controllers) to other multiple Controllers, use Notification mechanism if this is the case.
Apple has detailed instructions about delegate mode, notification mode in official documentation, check them out in XCode, :)
Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
create a segue between ViewControllerA and ViewControllerB
name the segue with a Identifier in the attributes inspector of it
override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
For step#3,:
if you are not using swift 2.1, please follow #Scott Mielcarski 's answer at this question
for people who are using swift 2.1, or who get error "Cannot convert value of type 'UIViewController' to specified type 'your view Controller class name', After following #Scott Mielcarski 's answer at this question, Please use:let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass instead of
let destinationVC:ViewControllerClass = segue.destinationViewController
This is tested on Swift 2.1.1 and it works for me.
If you don't actually want to pass data between view controllers but rather simply want to store a global variable you can do this:
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")

Resources