How to update a dictionary value in an array of dictionaries - arrays

I hate to ask this, as I feel I'm missing something super simple, but I've been banging my head on this issue, way too long.
When a user taps on a cell in my collectionView I store the indexPath.row and a value called timeOnIce in an array called tappedArray
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! BenchCollectionViewCell
if cell.cellBackgroundImageView.image == UIImage(named: "collectionviewcell_60x60_white") {
cell.cellBackgroundImageView.image = UIImage(named: "collectionviewcell_60x60_blue")
let currentPlayerSelected = ["indexPath": indexPath.row, "timeOnIce": 0]
tappedArray.append(currentPlayerSelected)
} else {
cell.cellBackgroundImageView.image = UIImage(named: "collectionviewcell_60x60_white")
tappedArray = tappedArray.filter { $0["indexPath"] != indexPath.row }
}
}
A button to start a timer is pressed
func startTimer() {
//start the timer
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounters), userInfo: nil, repeats: true)
} //startTimer
If two players are tapped (added to tappedArray) the array looks like the following:
tappedArray [["timeOnIce": 0, "indexPath": 1], ["timeOnIce": 0, "indexPath": 0]]
I'm having the hardest time trying to figure out how to update the timeOnIce in the dictionary to include the timerCounter
#objc func updateCounters() {
timerCounter += 1
for row in tappedArray {
print("row \(row)")
}
//Update tableview
tableView.reloadData()
} //updateCounters
This is what row prints out
row ["timeOnIce": 0, "indexPath": 2]
row ["timeOnIce": 0, "indexPath": 1]
row ["timeOnIce": 0, "indexPath": 0]
If I try the following in the for..in loop
row["timeOnIce"] = timerCounter
I get the following error
`Cannot assign through subscript: 'row' is a 'let' constant`
unless I change loop to the following:
for var row in tappedArray {
print("row \(row)")
row["timeOnIce"] = timerCounter
}
But the value doesn't get updated in the array...

Because Dictionary is a value type in Swift, the for loop makes a copy of the items in the array. To update the original, you can use an index into the array like this:
for row in tappedArray.indices {
print("row \(tappedArray[row])")
tappedArray[row]["timeOnIce"] = timerCounter
}

var dict1 = [String: Any]()
dict1.updateValue("Vanilla", forKey: "Ice Cream")
var dict2 = [String: Any]()
dict2.updateValue("World", forKey: "Hello")
var arr = [[String:Any]]()
arr.append(dict1)
arr.append(dict2)
print("arr-original: ", arr)
for (index, _) in arr.enumerated() {
if arr[index].contains(where: { $0.key == "Hello" }) {
arr[index]["Hello"] = "Dude"
}
}
print("arr-updated: ", arr)

Related

How to fix: Fatal error: Index out of range

I am attempting to limit my [String] array to only five values using swifts .prefix
First I take the original array items and splice it using .prefix
let testSlice = Array(items.prefix(5))
let newArray = Array(testSlice)
Then I validated the array holds only five values with a print line.
print("DEV: newArray value: \(newArray)")
if newArray != [] {
cell.profilePicture.playPortalProfilePic(forImageId: newArray[indexPath.item], { error in
if let error = error {
print("Error requesting profile pic: \(String(describing: error))")
}
})
} else {
print("items array was empty, value: \(items)")
}
newArray is then passed to a method provided by the SDK I am using to make requests for profilePictures. The newArray holds those values so [indexPath.item] is appropriate here. When this is functioning correctly it creates cells in a collection view dependent on how many values are in the array.
I am currently seeing Fatal error: Index out of range when this line attempts to run cell.profilePicture.playPortalProfilePic(forImageId: newArray[indexPath.item]
EDIT: Code requested by comments
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
Full method for cell.profilePicture.playPortalProfilePic(forImageId: newArray[indexPath.item] line
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.profilePicture.layer.masksToBounds = true
let testSlice = Array(items.prefix(5))
let newArray = Array(testSlice)
print("DEV: newArray value: \(newArray)")
if newArray != [] {
cell.profilePicture.playPortalProfilePic(forImageId: newArray[indexPath.item], { error in
if let error = error {
print("Error requesting profile pic: \(String(describing: error))")
}
}
)
} else {
print("items array was empty, value: \(items)")
}
cell.backgroundColor = UIColor.cyan
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
return cell
}
You use items as dataSource in numberOfItemsInSection while inside cellForItemAt use another newArray with less size by prefix(5)
hence the crash , so you should either return the count of newArrray.count inside numberOfItemsInSection or use testSlice alone

how to append something to array correctly?

I'm going to make a custom cell, already have some labels on it, then I create a cell object and array, try to append that object to array then show on table, but after append, there's no content in my array's properties
I've tried to find out solutions but likely no one has these problems
//tableview implement
var recordCell : [RecordCell] = []
let note = RecordCell()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.recordCell.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "recordCell",for: indexPath) as! RecordCell
let indexPath = indexPath.row
cell.recordFileName?.text = self.recordCell[indexPath].recordFileName?.text
cell.recordDate?.text = self.recordCell[indexPath].recordDate?.text
cell.delegate = self
cell.playBtn.tag = indexPath
return cell
}
//append to array
let alertController = UIAlertController(title: "請輸入錄音名稱", message: "錄音名稱", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (_) in
var name : String = ""
if(alertController.textFields![0].text == ""){
name = "record"
}else{
name = alertController.textFields![0].text!
}
guard self.audioRecorder == nil else{return}
self.recordNumber += 1
self.record.isEnabled = false
self.pause.isEnabled = true
self.stop.isEnabled = true
let destinationUrl = self.getFileURL().appendingPathComponent("\(name).m4a")
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
self.audioRecorder = try AVAudioRecorder(url: destinationUrl, settings: settings)
self.audioRecorder.record()
self.note.recordFileName?.text = name
self.note.recordDate?.text = self.getDate()
self.recordCell.append(self.note)
} catch {
print("Record error:", error.localizedDescription)
}
}
let cancelAction = UIAlertAction(title: "取消", style: .cancel) { (_) in
self.dismiss(animated: true, completion: {
self.audioRecorder.stop()
})
}
alertController.addTextField { (textField) in
textField.placeholder = "輸入名稱"
textField.keyboardType = .default
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.present(alertController,animated: true,completion: nil)
}
I expect when I append something, there's something in array
After appending a new value to the array, call insertRows method like this
self.recordCell.append(self.note)
self.tableView.insertRows(at: [IndexPath(row: recordCell.count-1, section: 0)], with: .automatic)
I didnt see anything wrong.
Are you sure your code is called? Try giving a print() right before adding and see if it's called.
Maybe it's not called because of guard self.audioRecorder == nil else{return}

Compare 2 Results<CustomObject> by object of selected index of filteredList swift4

I have 2 lists of same custom object i.e. News
class News: Object {
#objc dynamic var title: String?
#objc dynamic var date: Date?
#objc dynamic var contentUrl: String?
}
and 2 lists are
var filteredList: Results<News>! = nil
var newsList: Results<News> {
get {
return realm.objects(News.self).sorted(byKeyPath: "date", ascending:
false)
}
}
I am filtering the list and the filtered data is storing in filteredList.
Now I want to send the IndexPath of newsList to the next VC. When I search in the list, filteredList is appending to the tableView which is absolutely correct. When I selecting row from filteredList it is giving index of filteredList. Now though user selects filteredList's index , it should compare with the object of newList and should return that indexPath /index of newsList
I have done it by my own way
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
IndexPath) {
searchController.dismiss(animated: true, completion: nil)
var sendIndex : IndexPath?
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
if !searchBarIsEmpty(){
for (index,news) in newsList.enumerated(){
if news.title == filteredList[indexPath.row].title{
sendIndex = IndexPath(row: index, section: 0)
}
}
}else{
sendIndex = indexPath
}
if let index = sendIndex{
let vc = NewsDetailsVC(collectionViewLayout: layout)
vc.tappedIndex = index
vc.title = "News Details"
vc.isSelectNews = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
but it can be more optimise.. Please suggest some logic of optimisation
Here's a one-liner for finding the index to be sent.
if !searchBarIsEmpty() {
sendIndex = IndexPath(row: newsList.firstIndex(where: { $0.title == filteredList[indexPath.row].title }), section: 0)
}
Note: title is assumed to be the primary key. Compare with the appropriate primary key.

Swift 3: Remove specific string from array without knowing the indexPath?

I have a UICollectionView that has a bunch of cells. When I select these cells, they change color to look as if they have clearly been selected and I append that hashtag.hashtag_name (String) to my hashtagsArray. If I tap a category (fashion, food, hobbies or music), I append another array to that index path to give the user the cells for that specific category as you can see in my image example below.
What I would like is if I tap a already SELECTED cell to UNSELECT it, for that hashtag.hashtag_name to be removed from my hashtagArray. The issue is that the indexPath for the array that I add in is completely different to the array indexPath when I append it into the hashtagArray so I cannot remove it by calling self.hashtagArray.remove(Int). Here's my code...
import UIKit
class Hashtag: NSObject {
var hashtag_name: String?
var hashtag_color: String?
}
import UIKit
private let reuseIdentifier = "Cell"
class HashtagView: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var hashtagArray: [String] = []
var categoriesArray = [Hashtag]()
var fashionArray = [Hashtag]()
var isFashionSelected: Bool = false
var fashionArrayCount: [Int] = []
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.tintColor = .white
navigationItem.title = "Hashtag"
self.collectionView?.backgroundColor = .white
self.collectionView?.register(HashtagCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.contentInset = UIEdgeInsetsMake(10, 0, 0, 0)
handleFetchCategories()
handleFetchFashionHashtags()
}
func insertCategoryAtIndexPath(element: [Hashtag], index: Int) {
categoriesArray.insert(contentsOf: element, at: index)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = self.collectionView?.cellForItem(at: indexPath) as! HashtagCell
let hashtag = categoriesArray[indexPath.item]
if hashtag.hashtag_name == "FASHION" && isFashionSelected == false {
self.isFashionSelected = true
self.insertCategoryAtIndexPath(element: self.fashionArray, index: indexPath.item + 1)
self.collectionView?.reloadData()
} else if hashtag.hashtag_name == "FASHION" && isFashionSelected == true {
self.isFashionSelected = false
self.categoriesArray.remove(at: self.fashionArrayCount)
self.collectionView?.reloadData()
}
if hashtag.hashtag_name != "FASHION" && hashtag.hashtag_name != "FOOD" && hashtag.hashtag_name != "HOBBIES" && hashtag.hashtag_name != "MUSIC" {
if cell.isCellSelected == false {
cell.isCellSelected = true
if self.hashtagArray.contains(hashtag.hashtag_name!) {
cell.backgroundColor = .white
cell.hashtagLabel.textColor = greenColor
cell.layer.borderColor = greenColor.cgColor
} else {
self.hashtagArray.append(hashtag.hashtag_name!)
}
cell.backgroundColor = .white
cell.hashtagLabel.textColor = greenColor
cell.layer.borderColor = greenColor.cgColor
} else if cell.isCellSelected == true {
cell.isCellSelected = false
// REMOVE UNSELECTED CELL FROM ARRAY.
cell.backgroundColor = greenColor
cell.hashtagLabel.textColor = .white
cell.layer.borderColor = greenColor.cgColor
}
}
}
You can use the index(of: ) method on the array to get the index
if let index = hashtagArray.index(of: "SOMESTRING") {
hashtagArray.remove(at: index)
}
myArray = ["One","Two","Three","Four"]
myArray = myArray.filter{$0 != "Three"}
This will remove the "Three" from myArray. Also look at the following link too:
https://stackoverflow.com/a/44358108/8048468
You could use Array's index(of:) function to get the index of a known element and then remove it using remove(at:).
You could use filter(_:​) to create a new array excluding elements which match some check.
You could look at similar questions and write your own extension method to remove an object by value as in Array extension to remove object by value

Swift: Can't insert NSObject into Array as it wants a [String] instead

I have a model object called Hashtag. This simply contains a optional String variable called hashtagName. I fetch the data from my Firebase Database and append the hashtags to my fashionHashtags, which is a [Hashtag]. The issue I have is that I want to append that to my other categoriesArray by using the insertElementAtIndexPath function. I cannot do this as it wants an array of Strings and not an array of Hashtag. When I autocorrect it, it replaces it with fashionHashtags as! [String] but that creates another error. How do I fix this so it allows me to do so? I would like to stick to the Model Object way of doing things. Thank you guys. An answer would be highly appreciated. Code is below:
class Hashtag: NSObject {
vvar hashtagName: String?
}
private let reuseIdentifier = "Cell"
class HashtagView: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var catagoriesArray: [String] = ["FASHION", "FOOD", "HOBBIES", "MUSIC"]
var fashionHashtags = [Hashtag]()
var foodHashtags = [Hashtag]()
var hobbiesHashtags = [Hashtag]()
var musicHashtags = [Hashtag]()
var hashtagsArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.hashtagsArray.removeAll()
self.navigationController?.navigationBar.tintColor = .white
navigationItem.title = "Hashtag"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Finished", style: .plain, target: self, action: #selector(finishSelectingHashtags))
self.collectionView?.backgroundColor = .white
self.collectionView?.register(HashtagCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.contentInset = UIEdgeInsetsMake(10, 0, 0, 0)
handleFetchFashionHashtags()
}
func insertElementAtIndexPath(element: [String], index: Int) {
catagoriesArray.insert(contentsOf: element, at: index)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.item == 0 {
insertElementAtIndexPath(element: fashionHashtags, index: indexPath.item + 1)
self.collectionView?.performBatchUpdates(
{
self.collectionView?.reloadSections(NSIndexSet(index: 0) as IndexSet)
}, completion: { (finished:Bool) -> Void in
})
}
}
Based upon my understanding, there could be a couple of different approaches. Here would be the approach of looping through the array of Hashtag objects and appending the hashtagName string property to the categoriesArray of strings.
for hashTagItem in fashionHashtags {
if let hashTag = hashTagItem.hashtagName {
// Appends to categoriesArray as a string
categoriesArray.append(hashTag)
}
}
Another approach would be to build a set of strings and then insert it as it makes sense.
var hashTagString: [Strings] = []
for hashTagItem in fashionHashtags {
if let hashTag = hashTagItem.hashtagName {
hashTagStrings.append(hashTag)
}
}
// Insert or add the hash tag strings as it makes sense
categoriesArray += hashTagStrings // Add all at once if it makes sense

Resources