I have a collection view where it needs to store which button is pressed. This is like a quiz where they have options A, B, C, D. The problem is that it will select the item with this code:
extension QuestionCell {
func configure(with model: Question) {
factLabel.text = model.fact
questionLabel.text = model.question
revenueLabel.text = model.revenue
let views = answersStack.arrangedSubviews
for view in views {
view.removeFromSuperview()
}
for (id, answer) in model.answers {
print(index)
let answerLabel = UILabel()
answerLabel.text = answer
answersStack.addArrangedSubview(answerLabel)
let answerButton = UIButton()
answerButton.tag = index
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
}
But then if you swipe a couple times and then come back to this question the image will be white again. How can I fix this problem. Also I attached how the screen looks.
This is how it looks like
~ Thanks
You could solve this with an Answer class like this. In case you're new to classes, I should point out that you won't want to write this code inside another class definition (like your ViewController class.)
class Answer{
var wasSelected = false
var text : String
init(text : String) {
self.text = text
}
}
class ButtonWithAnswer : UIButton{
var answer : Answer?
}
But now your model.answers will look like this, and will be of type [String : Answer]:
model.answers = ["id1" : Answer(text : "answer 1"), "id2" : Answer(text: "answer 2"), "id3":Answer(text: "answer 3")]
The idea is that instead of having model.answers contain strings, they contain a new custom type that we made up called Answer that can keep track of the text for the answer (which is a property called text of type String) as well as whether or not it was selected (which is a property called wasSelected of type Bool). Your for loop will now look like this:
for (id, answer) in model.answers {
let answerLabel = UILabel()
answerLabel.text = answer.text //this comes from our new class definition
answersStack.addArrangedSubview(answerLabel)
//Change this to our new custom button subclass
let answerButton = ButtonWithAnswer()
//this line straight up won't work since you got rid of index
//answerButton.tag = index
//use this instead
answerButton.answer = answer
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
//make it so that any answers that were previously selected are now selected when the view reloads
answerButton.isSelected = answer.wasSelected
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
There's one last piece of this. We need to make sure that we make our answer.wasSelected equal to true when we tap the button that corresponds with that answer. So in answerPressed we should do something like this.
func answerPressed(_ sender : UIButton){
let answerForButton = (sender as! ButtonWithAnswer).answer!
answerForButton.wasSelected = true
}
That should about cover it. If this looks like something totally new then you should look up tutorials on classes, objects, and init methods in Swift before you dive in or ask a programmer for help in person. But I explained it about as well as I can without actually being there in person to give you a complete walkthrough.
Related
I'm wondering if there is a more efficient way to code an action that is the same with the exception of which button has been pressed and which item in a struct it relates to. Basically, I have a struct of 10 variables all of which are a boolean type and I have 10 buttons. When the user presses the button, I want to check whether it has already been pressed (using the struct) and then change the background of the button depending on the state and reverse the state. I've copied my current code for one of the buttons but thought I should be able to avoid doing this 10 times!
#IBAction func architectureButtonPressed(_ sender: Any) {
if myInterests.architecture {
myInterests.architecture = false
architectureButton.setBackgroundImage(imageUncheckedNarrow, for: .normal)
} else {
myInterests.architecture = true
architectureButton.setBackgroundImage(imageCheckedNarrow, for: .normal)
}
}
Well one simple way is to have each UIButton point to the same architectureButtonPressed IBAction method. Since the button that's pressed is passed into the method (sender) you can consult it's tag property to know the index of which field in your struct should be updated. (And then you might want to change your struct to just store an array of 10 bools, but up to you).
Then for each UIButton, whether programmatically in storyboard or nib, you'd assign the appropriate index value to the button's tag field.
Create yours IBOutlet for each button.
Create a array and store all buttons like : var arrayButtons : [UIButton] = []
arrayButtons.append[button1]
arrayButtons.append[button2]
...
Create a array of booleans to store true/false: var arrayBools : [Bool] = [] and initialize if some value.
Note that the indexes of the arrayButtons and arrayBools must be same related.
Create selector function to listen touch buttons.
button.addTarget(self, action: #selector(my_func), for: .touchUpInside)
#objc func my_func(_ sender : UIButton) {
for i in 0...arrayButtons.size-1 {
if arrayButtons[i] == sender {
if arrayBooleans[i] {
arrayBooleans[i] = false
arrayButtons[i].setImage()
} else {
arrayBooleans[i] = true
arrayButtons[i].setImage()
}
}
}
}
My suggestion is to manage the images in Interface Builder via State Config (Default/Selected)
Then assign an unique tag starting from 100 to each button and set the isSelected value in the IBAction to the corresponding struct member in a switch statement:
#IBAction func buttonPressed(_ sender: UIButton) {
switch sender.tag {
case 100: myInterests.architecture = sender.isSelected
case 101: myInterests.art = sender.isSelected
...
default: break
}
}
Alternatively use Swift's native KVC with WriteableKeypath
let keypaths : [WritableKeyPath<Interests,Bool>] = [\.architecture, \.art, \.fashion, \.history, \.localCulture, \.music, \.nature, \.shopping, \.sport, \.anything]
#IBAction func buttonPressed(_ sender: UIButton) {
let index = sender.tag - 100
let keypath = keypaths[index]
myInterests[keyPath: keypath] = sender.isSelected
}
I have collection view where you can select 4 buttons, it is like a quiz with A, B, C, D. I need to store which one they clicked before they go to the next question (They will swipe to go to the next question since it is a collection view) The controller looks like this:
First: Essentially the code used to display the image above, I have created a database which is parsed using this:
struct Question {
let fact: String
let question: String
let answers: [String: String]
let correctAnswer: String
let revenue: String
init?(with dictionary: [String: Any]) {
guard let fact = dictionary["fact"] as? String,
let question = dictionary["question"] as? String,
let answerA = dictionary["answer_a"] as? String,
let answerB = dictionary["answer_b"] as? String,
let answerC = dictionary["answer_c"] as? String,
let answerD = dictionary["answer_d"] as? String,
let revenue = dictionary["revenue"] as? String,
let correctAnswer = dictionary["correctAnswer"] as? String else { return nil }
self.fact = fact
self.question = question
self.revenue = revenue
var answersDict = [String: String]()
answersDict["answer_a"] = answerA
answersDict["answer_b"] = answerB
answersDict["answer_c"] = answerC
answersDict["answer_d"] = answerD
self.answers = answersDict
self.correctAnswer = correctAnswer
}
Second: Then I display using this code:
extension QuestionCell {
func configure(with model: Question) {
factLabel.text = model.fact
questionLabel.text = model.question
revenueLabel.text = model.revenue
let views = answersStack.arrangedSubviews
for view in views {
view.removeFromSuperview()
}
for (id, answer) in model.answers {
print(index)
print(id)
let answerLabel = UILabel()
answerLabel.text = answer
answersStack.addArrangedSubview(answerLabel)
let answerButton = UIButton()
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
}
}
Is there a way to store the button I clicked? Thanks
Well there is one sure shot way out of this situation.
Make a custom cell for the collectionView.
Add button outlets and action the the customCell's class
Create and make use of delegate methods in customCell's class and when implementing the customCell in your ViewController set the delegate to
self.
Trigger the delegate methods when button actions are done (inside your custom cell).
Provide your customCell the current indexpath when using it in cellForItemAt method.
Make use of that indexPath to decide which button was triggered.
You should be thinking something close to this approach for a robust solution.
The way I've handled this in the past is to use the tag on a UIButton and just keep track of which tag is currently selected. With this approach I can use the same IBAction for every button and all I need to do is pull the tag from the sender in the function body. While maybe not as flexible and robust is an approach using subclassing, it's a bit quicker to implement.
First set your tags when you create your buttons (I use 100-104 to avoid conflicts with other buttons). Since you're creating your buttons in a CollectionView, you'll need to set the tag in your configure() function:
func configure(with model: Question) {
...
for (id, answer) in model.answers {
...
answerButton.setImage(imageSelected, for: .selected)
answerButton.tag = index
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
}
}
Create an instance variable:
var selectedAnswerIndex = -1
Then assign this IBAction to each of you buttons:
func answerPressed(_ sender: UIButton){
selectedAnswerIndex = sender.tag
hilightNewOne(sender: sender)
}
I have been stuck on this now for hours and hours, I just can't figure out how to do it and I'm getting tunnel vision with it so I need some help. So here are the bones of my app hope you can help...
I am building a Quiz app. I have the Quiz part working, that is I have created a structure and defined a question and answer section. I then display the questions on screen and hide the answer until the user pressed the reveal answer button.The users can swipe left or right to go forward or back between the questions.
I want to incorporate letting the user be able to save some questions and come back to them in a different view controller at a later stage. When the save button is clicked I want the question to be saved and placed into the saved view controller. I want it in the form of a question so I can let the user flick through all their saved questions. I was trying to save using NSUserDefaults.
The code for the Questions view controller:
import Foundation
import UIKit
struct Question {
var Question : String!
var Answers : String!
}
class Defence: UIViewController {
#IBOutlet weak var labelForQuestion: UILabel!
#IBOutlet weak var textBoxForAnswer: UITextView!
var QNumber : Int = 0
override func viewDidLoad() {
//hiding answer
textBoxForAnswer.hidden = true
//components for swiping left
let swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondLeft:")
swipeLeft.direction = .Left
view.addGestureRecognizer(swipeLeft)
//components for swiping Right
let swipeRight = UISwipeGestureRecognizer(target: self, action: "respondRight:")
swipeRight.direction = .Right
view.addGestureRecognizer(swipeRight)
Questions = [
Question(Question: "what colour is the sky", Answers: "blue"),
Question(Question: "what colour is the grass", Answers: "green",
Question(Question: "what colour is the sea", Answers: "blue",
Question(Question: "what is 1 plus 1", Answers: "2"),
Question(Question: "what is 2 plus 2", Answers: "4"),
Question(Question: "what is 3 plus 3", Answers: "6"),
]
pickQuestion()
}
func pickQuestion() {
if Questions.count > 0 {
Num.text = String(QNumber + 1)
labelForQuestion.text = Questions[QNumber].Question
textBoxForAnswer.text = Questions[QNumber].Answers
}
}
//Action for left swipe
func respondLeft(gesture: UIGestureRecognizer) {
if QNumber == (Questions.count - 1) {
//if last question do nothing so it doesnt go out of bounds
} else {
QNumber++;
pickQuestion()
}
}
//Action for Right Swipe
func respondRight(gesture: UIGestureRecognizer) {
if QNumber == 0 {
//if last question do nothing so it doesnt go out of bounds
} else {
QNumber--;
pickQuestion()
}
}
#IBAction func showAnswer(sender: AnyObject) {
textBoxForAnswer.hidden = false
}
#IBAction func save(sender: AnyObject) {
****Have tried many things here with NSUserDefaults and appending to a dictionary so I could see the saved question in the other view controllers. this is where I need help****
}
#IBAction func sections(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
That is the code for the Questions and displaying the questions to the users. Now I want to save the selected question when the save button is clicked. I need this saved so I can present these saved questions in another view controller and enabled the user to flick through their saved Questions. How do I do it?
If you only want to save and retrieve string type:-
var Question: String!
//save questions from here {func save() }
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(questions, forKey: "key_questions")
//retrieve questions from another view controller
let defaults = NSUserDefaults.standardUserDefaults()
let quest = defaults.stringForKey("key_questions")
print("quest") //questions saved before from first view controller
OR,
If you want to save and retrieve an array of object
var Question: [[String:AnyObject]]!
//save questions from here {func save() }
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(questions, forKey: "key_questions")
//retrieve questions from another view controller
let defaults = NSUserDefaults.standardUserDefaults()
let quest = defaults.objectForKey("key_questions") as? [[String:AnyObject]]
print("quest") //questions saved before from first view controller
I use this function to create a NSMenuitems. They all get tagged with 2.
func addToComputerInfoMenu (title: String)
{
let addToComputerItem : NSMenuItem = NSMenuItem(title: "\(title)" , action: #selector(openWindow), keyEquivalent: "")
addToComputerItem.attributedTitle = NSAttributedString(string: "\(title)", attributes: [NSFontAttributeName: NSFont.systemFontOfSize(14), NSForegroundColorAttributeName: NSColor.blackColor()])
addToComputerItem.tag = 2
addToComputerItem.enabled = true
computerInfoMenu.addItem(addToComputerItem)
}
I would like to programmatically delete all items with "2" tag. I tried using .itemWithTag and .indexOfItemWithTag. I can't seem to iterate through the list.
let itemswithindex2 = computerInfoMenu.itemWithTag(2)
I found a way to accomplish my goal. Not sure its the best solution but it works.
for item in computerInfoMenu.itemArray
{
if (item.tag) == 2
{
computerInfoMenu.removeItem(item)
}
}
It's like this:
var i = 0
button.addTarget(self, action:"showContent", forControlEvents: UIControlEvents.TouchUpInside)
and the function "showContent" is like this:
func showContent(i: Int) {
//do sth.
}
I want to pass the variable i to function showContent when that button be touched, how could i do ?
In the target-action pattern, you can't do that. You can either use "showContent", in which case your function will be:
func showContent() {
}
… or you can add a colon ("showContent:") in which case your function will be:
func showContent(sender : UIButton) {
// do something with sender
}
This helps enforce the broader Model / View / Controller pattern by making it difficult for your views to be "smart". Instead, your model and controller objects should handle i. A button simply displays what it's told, and tells your controller when it's tapped.
You can read i, and respond to it, in the appropriate method. For example:
class ViewController : UIViewController {
var i = 0
var button = UIButton(type: .System)
var label = UILabel()
override func viewDidLoad() {
button.addTarget(self, action:"showContent", forControlEvents: UIControlEvents.TouchUpInside)
}
func showContent() {
i++
label.text = "Current value of i: \(i)"
}
}