Updating element values of an array in swift - arrays

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.

Related

Save button states when we move segmented control

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.

Why have a Array Empty and Error in swift4?

I've spent a few hours trying to get a fetch to work in my film sheet. I need to open film's View of my colectionview Items. I could follow different guide and post but it always give me an empty array. I'm newbie, sorry for my question but I need your help.
Here's my code:
var taskArrayScheda : NewFilm?
override func viewDidLoad() {
super.viewDidLoad()
self.fetchData()
if(self.taskArrayScheda != nil) {
let schedaOk = taskArrayScheda
mostraDatiNellaScheda(schedaOk!)
} else { print("errore array vuoto") }
}
func mostraDatiNellaScheda(_ sched:NewFilm) {
// get title
titoloScheda.text = sched.titolo
}
func fetchData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NewFilm")
do {
taskArrayScheda = try context.fetch(NewAnime.fetchRequest())
💥ERROR ::::: Cannot assign value of type '[Any]' to type 'NewFilm?'
} catch {
print(error)
}
}
The fetch() returns an array. But currently you assign the fetch() result to the single object var taskArrayScheda.
You'll need something like:
var taskArrayScheda: [NewFilm]?
Then you should do:
taskArrayScheda = try context.fetch(NewAnime.fetchRequest()) as? [NewFilm]
I assume here that NewAnime is a subclass of NewFilm, which seems to make sense looking at these two class names.

Swift - Update Value of a specific item in a nested dictionary

I have a tableview and each cells are meant to be linked to an array inside a dictionary.
var buttonAction : [String: [ButtonAction]] = [:]
below is the struct of the buttonAction
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
It gets a bit complicated to explain the whole code but when I connect two buttons together, I can get the data for the variable "singleAction" which then can be added to the button action dictionary "array_linked_of_buttons".
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
let mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER Exists", addAction)
} else {
actionSub.append(singleAction)
print("UPDATED MARKER", actionSub)
}
let action = ButtonAction(action: actionArray[indexRowTag].action, array_linked_of_buttons: actionSub)
//ISSUE--?? mainMarker.buttonAction.updateValue(VALUE forKey: "button actions array")
saveData()
I can workout which item of the dictionary needs to be edited but how do I update that specific value? I am really confused has it just creates a new item but I want to update a previous one.
So for example, I want to append the "array_linked_buttons" of the item 0 and have 4 items instead of 3.
This is how my dictionary looks like if that helps too
I have searched other questioned but I still work it out.
Any help pointing me in the right direction is much appreciated!
Copying instead of changing value happened because you're using struct for ButtonAction which has value semantics and swift create copy on assigning to any variable.
You need to use class instead. Classes have reference semantics, so it won't create new instance on assigning to variable so you'll be able to update property of needed instance.
I worked out i was updating the main dictionary array and not the nested array.
The code below is what I used in the end.
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
var mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER HERE", addAction)
} else {
actionArray[indexRowTag].array_linked_of_buttons.append(singleAction)
}
mainMarkerAction[indexRowTag] = actionArray[indexRowTag]

Using KVO to tell when elements have been added to an array

I want to check if elements have been added to an array in swift using KVO, and I essentially copied the example from Apple's documentation, but when the code runs, it does not catch when the size of the array updates. Here is what I have now:
class ShowDirectory: NSObject {
var shows = [Show]()
dynamic var showCount = Int()
func updateDate(x: Int) {
showCount = x
}
}
class MyObserver: NSObject {
var objectToObserve = ShowDirectory()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "showCount", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("\(newValue) shows were added")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
After I add the shows to the array, I set showCount equal to the number of elements in the array, however, it does not print "X shows were added" to console. My viewDidLoad() function simply calls the function that adds elements to the array, and nothing else at the moment.
You unfortunately cannot add as an observer to an Int, as it does not subclass NSObject
See the Apple Docs and search for "Key-Value Observing"
You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class.
Otherwise, your KVO boiler-plate code looks good to me.
If you want to be notified when your array's contents change, you could try what #Paul Patterson recommends and use a proxy object

How to observe an array of NSObjects in swift?

I am new in swift language and my problem is about how to use observable/observer pattern in swift.
I want to make my array to be observable in my SocketManager class so it can be observed by my UIViewController class. I have used the Observable class written by Andrew J Wagner which I got from this link:
http://www.drewag.me/posts/swift-kvo-substitute-observable-variables
I have the array:
var marketIndexList: Array< MarketIndex > = []
which will get its data from a server. This list will be updated every time a new data received from server. After I got the values of my Array from server I want to make it of type Observable class which is implemented by the above link:
marketIndexList = Observable(marketIndexList)
But I got this error:
'MarketIndex' is not identical to 'AnyObject'
MarketIndex is a class of type NSObject which has some properties of type String.
This is the Observable class that I have used:
import Foundation
class Observable {
typealias DidChangeHandler = (oldValue: Array<MarketIndex>?, newValue: Array<MarketIndex>) -> ()
var value : Array<MarketIndex> = [] {
didSet {
for (owner, handlers) in self.observers {
for handler in handlers {
handler(oldValue: oldValue, newValue: value)
}
}
}
}
init(_ value: Array<MarketIndex>) {
self.value = value
}
func addObserverForOwner(owner: IndexViewController, triggerImmediately: Bool, handler: DidChangeHandler) {
if let index = self.indexOfOwner(owner) {
// since the owner exists, add the handler to the existing array
self.observers[index].handlers.append(handler)
} else {
// since the owner does not already exist, add a new tuple with the
// owner and an array with the handler
self.observers.append(owner: owner, handlers: [handler])
}
if (triggerImmediately) {
// Trigger the handler immediately since it was requested
handler(oldValue: nil, newValue: self.value)
}
}
func removeObserversForOwner(owner: AnyObject) {
if let index = self.indexOfOwner(owner) {
self.observers.removeAtIndex(index)
}
}
// #pragma mark - Private Properties
var observers: [(owner: IndexViewController, handlers: [DidChangeHandler])] = []
// #pragma mark - Private Methods
func indexOfOwner(owner: AnyObject) -> Int? {
var index : Int = 0
for (possibleOwner, handlers) in self.observers {
if possibleOwner === owner {
return index
}
index++
}
return nil
}
}
Can anyone tell me what the problem is?
Also does anyone know a way to observe an array of objects in swift?
I would appreciate any help.
Thanks in advance.
The error is because marketIndexList is defined as Array<MarketIndex> but you assigned Observable instance. Perhaps you wanted to do something like this:
var observableList: Observable = Observable([])
var marketIndexList: Array<MarketIndex> = [MarketIndex(), MarketIndex()]
observableList.value = marketIndexList
// Or maybe
observableList = Observable(marketIndexList)
By the way, you can also use Objective-C KVO from Swift. Just mark the property as dynamic and make sure the class inherits NSObject to make the property observable. For example:
class ObservableClass: NSObject {
dynamic var value = [Int]()
}
This post is good to read for KVO in Swift in addition to what you referred to.
https://medium.com/proto-venture-technology/the-state-of-kvo-in-swift-aa5cb1e05cba

Resources