Save button states when we move segmented control - arrays

We have for example segmented control, and array of buttons. If we select one of button in one segment - button need to save state, and than we move to another segment and choose another button. but if we back to previous button - it should be in the position we chose earlier.
that is, each of the segments must store the state of the buttons.
how to do it better?
struct SegmentsModel {
let title: String
var answered: Bool
var buttonIndex: Int? = nil
}
#objc private func buttonsTapped(_ sender: UIButton) {
let _ = buttons.map({$0.isSelected = false})
sender.isSelected = true
guard let index = buttons.firstIndex(of: sender) else { return }
switch index {
case 0:
selectedSegment(segmentedControl, buttonSelected: true, indexButton: 0)
case 1:
selectedSegment(segmentedControl, buttonSelected: true, indexButton: 1)
case 2:
selectedSegment(segmentedControl, buttonSelected: true, indexButton: 2)
case 3:
selectedSegment(segmentedControl, buttonSelected: true, indexButton: 3)
default:
break
}
}
#objc private func selectedSegment(_ sender: UISegmentedControl,
buttonSelected: Bool, indexButton: Int) {
let currentIndex = sender.selectedSegmentIndex
if buttonSelected == true {
buttons[indexButton].isSelected = true
} else {
let _ = buttons.map({$0.isSelected = false})
}
switch currentIndex {
case 0:
arrayOfSegments[0].answered = buttonSelected
arrayOfSegments[0].buttonIndex = indexButton
case 1:
arrayOfSegments[1].answered = buttonSelected
arrayOfSegments[1].buttonIndex = indexButton
case 2:
arrayOfSegments[2].answered = buttonSelected
arrayOfSegments[2].buttonIndex = indexButton
default:
break
}
}

As I mentioned in my comments, there are many ways to achieve what you want. I will share one idea with you.
I see that you tried to store all your buttons in an array and had to keep looping over them to figure out which button was selected previously. Since you want the code to have simpler logic, I will give you a different idea.
One way to achieve what you want is using tags. Every UIView object has a tag and the default is set to 0.
Using storyboard I set the tag of the buttons to 100, 101, 102, 103 and this can be done programmatically also.
You can choose any integers that you like but it is important to give them some unique numbers so when you try to get a view by tag, you get only the view you want.
So after setting the tags, this is what I updated in the code.
struct SegmentsModel {
let title: String
var answered = false
var buttonIndex: Int? = nil
{
// Auto-update the value of answered when value of buttonIndex changes
didSet
{
answered = buttonIndex != nil
}
}
}
I did not do any major updates here. I only added a property observer so when the button index gets set, the answered variable also is auto set so you don't have to anything more for this
Next, below is all the logic to manage the segments and the buttons. I used UIViewController with a StoryBoard so you might need to ignore somethings.
All the important code has comments so you can follow along.
class ViewController: UIViewController {
// I will store the results in this array
var storedResults: [SegmentsModel] = []
// Segment control outlet
#IBOutlet weak var segmentControl: UISegmentedControl!
// Array of buttons
#IBOutlet var buttons: [UIButton]!
override func viewDidLoad() {
super.viewDidLoad()
// Loop over all segments
for index in 0 ..< segmentControl.numberOfSegments
{
// Initialize and store default results values in storedResults array
if let segmentTile = segmentControl.titleForSegment(at: index)
{
storedResults.append(SegmentsModel(title: segmentTile))
}
}
}
// Segment action
#IBAction func selectedSegment(_ sender: UISegmentedControl)
{
// Update the UI of buttons
reloadButtons()
}
#IBAction func buttonTapped(_ sender: UIButton)
{
// Reset your buttons
let _ = buttons.map({$0.isSelected = false})
// Set the current button to selected
sender.isSelected = true
// Get the current result so we can update it
var currentResult = storedResults[segmentControl.selectedSegmentIndex]
// Update the current segments selected button index using tag
currentResult.buttonIndex = sender.tag
// Put the result back into the array as structs as value types
storedResults[segmentControl.selectedSegmentIndex] = currentResult
}
// Reload button data
private func reloadButtons()
{
// Reset your buttons
let _ = buttons.map({$0.isSelected = false})
let currentResult = storedResults[segmentControl.selectedSegmentIndex]
// Check if current index has a selected button and if it does retrieve it
// with a tag
if let selectedButtonIndex = currentResult.buttonIndex,
let selectedButton = view.viewWithTag(selectedButtonIndex) as? UIButton
{
// Show the selected button in the UI
selectedButton.isSelected = true
}
}
}
The end result achieved can be seen in this youtube video which I believe is what you wanted.

Related

Updating element values of an array in swift

I have a lot of experience working with Matlab, but I only recently started programming in Swift 4. My current project involves building a questionnaire. I have used the ‘drag and drop’ feature in Xcode to produce an #IBAction function for a button in storyboard, which can then lead to pressed button changing its appearance. This functionality is contained within the ButtonResponse class in the code snippet below:
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(_ anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse: UIButton {
var responseVariables: ResponseProfile
var checkedImage = UIImage(named: "checkedResponseBox")! as UIImage
var uncheckedImage = UIImage(named: "uncheckedResponseBox")! as UIImage
required init?(coder aDecoder: NSCoder) {
self.responseVariables = ResponseProfile(
responseArray: []
)
super.init(coder: aDecoder)
}
#IBAction func checkboxTapped(_ sender: UIButton) {
switch sender.accessibilityIdentifier {
case "excellent":
let oldResponseStatus = responseVariables.responseArray[0]
if oldResponseStatus == "unchecked"{
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[0] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[0] = "unchecked"
}
case "veryGood":
let oldResponseStatus = responseVariables.responseArray[1]
if oldResponseStatus == "unchecked" {
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[1] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[1] = "unchecked"
}
default: break
}
}
}
I imagined that I could use an array to internally represent the state of the buttons in the user interface (this would be the ‘responseArray’ variable). By changing elements within responseArray following a button press, I thought I could keep track which buttons were pressed and ensure that no more than one button at a time was checked. I incorrectly thought responseArray would be updated, but this is not the case. The array always reverts to its initiation state.
N.B. responseArray contains seven elements because there are seven response options. So far, I have attempted to program only two of the response options: “excellent” and “veryGood”.
In attempting to find a solution, I attempted to simplify the above code in playground:
import UIKit
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(input anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse {
var responseVariables: ResponseProfile
init(){
self.responseVariables = ResponseProfile(responseArray: [])
}
var responseA = ResponseProfile(responseArray: [])
}
var responseOne = ResponseProfile(responseArray: [])
responseOne.responseArray[0] = "checked" //user performs action resulting in first element being changed from a starting value of "unchecked" to "checked"
responseOne.updateArray(input: responseOne.responseArray)
var responseTwo = ResponseProfile(responseArray:[])
responseTwo.responseArray //responseArray revert to initialization values. How can I keep changes to the responseArray?
How can I update responseArray within the ResponseProfile structure without having to create a new variable to record every change? Is this the problem I should be looking at or is there, on a more general level, a better strategy that I should be taking?
I am surprised that I struggled this much to deal with this issue. I thought the answer would be clear if I read the relevant parts of the documentation and studied some example code. All the example code I found was too simplistic and focused on just one iteration of updating the array.
Any comments or suggestions would be much appreciated!
Looking at your playground code, I found that you are passing a blank [] array to argument of ResponseProfile struct during init. and it is always initialising your responseArray.
If you want to pass the things by reference, you can change Response profile to class
and there you can achieve the similar functionalities and use inout parameter to keep the same array without using the function updateArray.
The example I am showing here is for the class and objects of class can be pass by reference. thus keep your previous changes.
var responseTwo = ResponseProfile(responseArray:[])
If you wants to keep the old response, you can pass that array as an argument
var responseTwo = ResponseProfile(responseArray:responseOne.responseArray)
OR
var responseTwo = responseOne
Will keep the responseArray.
You can read more about it, at official blog
Also you can this post with more insight for the case.
Hope it helps.
Thanks for your response Bhavin. By passing responseArray by reference (as Bhavin suggests) to the necessary class (which has turned out to be the ButtonResponse class rather than ResponseProfile), I can give responseArray an initial value. I then use the buttonPress function to update responseArray. See below:
class ButtonResponse: Responses {
var responseArray: [String]
init (responseArray: [String]) {
self.responseArray = responseArray
}
func buttonPress(inputString: String, targetIndex: Int) -> [String] {
//need guard statement to block function when targetIndex > number of elements in responseArray
responseArray[targetIndex] = inputString
return responseArray
}
}
let initiateResponseArray =
["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
var doPress = ButtonResponse(responseArray: initiateResponseArray)
doPress.buttonPress(inputString: "checked", targetIndex: 0)
var getPressInfo1 = doPress.responseArray
print(getPressInfo1)
//prints: ["checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
doPress.buttonPress(inputString: "checked", targetIndex: 1)
var getPressInfo2 = doPress.responseArray
print(getPressInfo2)
//prints: ["checked", "checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
I am still unsure how to implement this solution in the project I am working on. I will create a separate question for this because it seems to raise different issues.

Multiple Switches in an Array are activated when only one is pressed Swift

I am working on an attendance app that uploads and stores all of its data in firebase. Most things are currently working except that sometimes when a switch is pressed in the array of students in the club, 2 of the switches activate in the array but the information normally sent when a switch is pressed is only sent for the switch that I actually pressed.
Here is the TableView cell code:
import UIKit
import Firebase
class StudentCell: UITableViewCell {
var student: Student? { didSet { self.updateUI() }}
#IBOutlet weak var lblStudentName: UILabel!
#IBOutlet weak var lblStudentId: UILabel!
#IBOutlet weak var lblStudentAttending: UISwitch!
let ref = Database.database().reference()
var user: Student! {
didSet{
lblStudentName.text = user.FullName()
lblStudentId.text = user.StudentId
lblStudentAttending.isOn = false
}
}
var switchReference: DatabaseReference?
var switchHandle: DatabaseHandle?
func stopObservation() {
if let handle = switchHandle {
switchReference?.removeObserver(withHandle: handle)
}
}
func startObservation() {
stopObservation()
if let uid = student?.StudentId {
switchReference = ref.child("attendance").child(Global.shared.currentDate).child(uid)
switchReference?.observe(.value, with: { [weak self] (snapshot: DataSnapshot) in
DispatchQueue.main.async {
let isOn = (snapshot.value as? Bool) ?? false
self?.lblStudentAttending.isOn = isOn
}
})
}
}
func updateUI() {
startObservation()
self.lblStudentName.text = self.student?.FullName() ?? ""
self.lblStudentId.text = self.student?.StudentId ?? ""
}
#IBAction func `switch`(_ sender: UISwitch) {
switchReference?.setValue(sender.isOn)
}
}
Does anyone know why this might be happening? If you need more information lmk. TIA -Ben
Does anyone know why this might be happening?
Tables reuse their cells so that they don't have to keep allocating new ones, which is expensive when the user is trying to scroll through a lot of rows quickly. You're setting up an observer for the switch in each cell, which means that you need to stop observing that switch when the cell is no longer visible. You could probably call stopObservation() in the cell's prepareForReuse() method and solve the problem.
A better solution would be to avoid observing the switch altogether, and instead use the target/action mechanism that controls typically use to trigger an action when the user changes their value. That way you don't have to worry about constantly starting and stopping observation.

how can I refer to UILabel or UIButton collections indexPath in array

#IBOutlet var buttons: [UIButton]!
#IBOutlet var labels: [UILabel]!
#IBAction func cevapSeçildi(_ sender: UIButton) {
if sender == buttons[0] {
`enter code here`
labels[0].backgroundColor = UIColor.yellow
}
}
I want this ..
var x : Int
if sender == buttons[x] { labels[x].backgroundColor = UIColor.yellow }
can you help me please
A couple of points:
Using your array of buttons to map to cell indexes will only work for a single-section table view or collection view. If you have a sectioned table view or a collection view that's in rows and columns then that approach won't work.
If you want to make the label yellow on the selected cell but white for all others, it doesn't make sense to change ALL the cells. A table view/collection view only shows a few cells at a time, and as you scroll, the cells get recycled and used for different indexes in your table view/collection view.
If you let me know whether you're using a table view or a collection view I can show you a better way to do this.
EDIT:
Since you're not using a table view or collection view, it does make sense to manipulate the labels directly:
#IBAction func cevapSeçildi(_ sender: UIButton) {
let buttonIndex = buttons.index(of: sender)
for (index, label) in labels.enumerated) {
}
if index == buttonIndex {
label.backgroundColor = UIColor.yellow
} else {
label.backgroundColor = UIColor.white
}
}
You can get the index of the button with
var index = buttons.index(of: sender)
then set
labels[index].backgroundColor = UIColor.yellow
If you would like to set all the other buttons to a different color at the same time, consider this:
let buttonIndex = buttons.index(of: sender)
for var label in labels {
if(labels.index(of: label) == buttonIndex) {
label.backgroundColor = UIColor.yellow
} else {
label.backgroundColor = UIColor.white
}
}

Check for identical values swift

I'm trying to make a memory game where I apply images to 12 different buttons and check if the images are the same when 2 buttons are showing.
-------------------FINISHED FORM?-------------------------
Here is a try at Duncan C's suggestion;
func setImages() {
var values = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]
values.shuffleInPlace()
button1.tag = values[0]
button2.tag = values[1]
button3.tag = values[2]
button4.tag = values[3]
button5.tag = values[4]
button6.tag = values[5]
button7.tag = values[6]
button8.tag = values[7]
button9.tag = values[8]
button10.tag = values[9]
button11.tag = values[10]
button12.tag = values[11]
}
#IBAction func buttonPressed(sender: UIButton) {
var images : [UIImage] = [
UIImage(named:"ye1")!,
UIImage(named:"ye2")!,
UIImage(named:"ye3")!,
UIImage(named:"ye4")!,
UIImage(named:"ye5")!,
UIImage(named:"ye6")!,
UIImage(named:"ye7")!,
UIImage(named:"ye8")!,
UIImage(named:"ye9")!,
UIImage(named:"ye10")!,
UIImage(named:"ye11")!,
UIImage(named:"ye12")!
]
images.shuffleInPlace()
let integrera = (sender.tag - 1)
let imageString:String = String(format: "ye%i", integrera)
if !firstButtonAlreadyPresssed {
firstButtonValue = sender.tag //remember the button for later
firstButtonAlreadyPresssed = true
sender.setImage(UIImage(named: imageString), forState: .Normal)
}
else
//We already have a first button pressed.
if sender.tag == firstButtonValue {
sender.setImage(UIImage(named: imageString), forState: .Normal)
}
else {
let secondimage = (sender.tag)
let secondString : String = String(format: "ye%i", secondimage)
sender.setImage(UIImage(named: secondString), forState: .Normal)
}
firstButtonAlreadyPresssed = false //reset the "isFirstButton" flag for the next time.
}
}
Hmm, there are a bit of lengthy and repetitive code here I believe. You can just add all buttons to the same #IBAction func. So you can have one #IBAction func buttonPressed(sender: UIButton) rather than 12 like you have now (I think, since your function is called #IBAction func button1PRESSED(sender: AnyObject)). So when you first click a button you store the button, and also store if its the first or second button clicked. When you click the second button you check if it has the same UIImage as the first one clicked, otherwise you what ever it is you are supposed to do.
#IBAction func buttonPressed(sender: UIButton) {
if self.firstButtonStored == true {
if self.firstButton.image == sender.image {
// They are the same
} else {
// they are not the same
}
} else {
self.firstButtonStored = true
self.firstButton = sender
}
}
I would also recommend storing all the buttons in one OutletCollection (works like an array) rather than 12 buttons in self. And I would also use something else than UIImage to check if they are the same, not sure if this actually works since you need the image name and not the image itself to make a comparison. Let me know if you need help.
Don't, dont, DON'T create 12 separate variables, card1-card12 and 12 IBActions button1PRESSED-button12PRESSED. This kind of thing where you have lots of exactly the same thing where the only thing that changes is a value is a "code smell". It tells you you are doing it wrong.
Instead, you should find a way to do it where you can use the same IBAction for all the buttons, and index into the array rather than having 12 separate variables.
I would suggest using tags on your buttons. Have button 1 use tag 1, button 2 use tag 2, etc.
Then in your IBAction, fetch the tag from the button and use that to figure out which button was pressed and what to do.
You will also need a flag that tells you if this was the first button pressed or the second, so you can tell if you need to test for matches (if it's the 2nd one) or just remember which one was pressed (if this is the first button pressed.)
var firstButtonValue: Int
var firstButtonAlreadyPresssed: Bool
#IBAction func buttonPressed(sender: UIButton)
{
if !firstButtonAlreadyPresssed
{
firstButtonValue = sender.tag //remember the button for later
firstButtonAlreadyPresssed = true
}
else
{
//We already have a first button pressed.
if sender.tag == firstButtonValue
{
//right answer. Handle it.
}
else
{
//wrong answer. Handle it.
}
firstButtonAlreadyPresssed = false //reset the "isFirstButton" flag for the next time.
}
}
You could have an array of the images to use for each value, and then fetch that image based on the value of firstButtonValue or sender.tag.
An approach, not a full solution
Create a struct Card with value and index properties and make it Equatable by value.
You can add more properties like done, status, image or whatever.
struct Card : Equatable, CustomStringConvertible {
let value : Int
let index : Int
var description : String { return "Card \(index)"}
}
func ==(lhs: Card, rhs: Card) -> Bool
{
return lhs.value == rhs.value
}
Shuffle the values and create an array of 12 cards
var values = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]
values.shuffleInPlace()
var cards = [Card]()
for (index, value) in values.enumerate() {
cards.append(Card(value: value, index: index))
}
Implement a method to find the corresponding card
func twinCard(card : Card) -> Card {
return cards.filter({$0 == card && $0.index != card.index}).first!
}
In Interface Builder assign tags from 0 to 11 to the twelve buttons and connect all buttons to the same #IBAction
#IBAction func buttonPressed(sender: UIButton) {
let tag = sender.tag
let chosenCard = cards[tag]
let otherCard = twinCard(chosenCard)
// The following depends on the design of the UI
// for example get the corresponding button by viewWithTag(otherCard.index)
// or use an array which holds the button references.
...
}
Don't shuffle the view, shuffle the model ;-)

WKPickerItems and ranges

I am trying to make a picker view in Watch Kit from -273 to 273. Sadly Watch Kit only allows a string as title so I converted my range using .map Now when I run the App it display a range from 0 to 546, but will not go into the negative range I tried changing both values but the picker always starts at 0 and won't go back further.
I isolated the problem into these lines:
let pickerItems: [WKPickerItem] = (-273...273).map {
let pickerItem = WKPickerItem()
pickerItem.title = "\($0)"
return pickerItem
}
I just tried it out and your code does show negative values in the picker. Then when the user picks a value you have to take the value and use it to retrieve the "real" value from your pickerItems array:
class InterfaceController: WKInterfaceController {
#IBOutlet var picker: WKInterfacePicker!
var pickerItems: [WKPickerItem]?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
pickerItems = (-273...273).map {
let pickerItem = WKPickerItem()
pickerItem.title = "\($0)"
return pickerItem
}
picker.setItems(pickerItems)
}
#IBAction func pickerDidChange(value: Int) {
let pickedItem = pickerItems![value] // value = 0..576
if let pickedValue = Int(pickedItem.title!) {
print(pickedValue) // -273..273
}
}
}

Resources