Good day,
I have an NSCollectionView which requires a lot of adding and delete. I have a function that adds to the end of the NSCollectionView perfectly and that is working effectively but my function to delete items in the NSCollectionView works sometimes but other times it throws an error below is the error
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
2021-02-19 09:08:34.718073+0100 Kokoca[33243:2652479] Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
Ive saved a user default to tell me which section and item the user is deleting. I've made it so that the user cannot delete the first item due to it being very essential but when I try to delete other items it works sometimes until it crashes when I keep trying to delete more items. I'm not very familiar with collectionViews so any assistance would be appreciated. Thanks in advance. Below is my code:
#objc func deleteview() {
var newSearch = UserDefaults.standard.integer(forKey: "DeletedTab")
let newSection = UserDefaults.standard.integer(forKey: "DeletedSection")
if newSearch == 0 {
return
}
else {
self.theTabList.remove(at: newSearch)
print(newSearch)
print(newSection)
var set: Set<IndexPath> = [IndexPath(item: newSearch, section: newSection)]
tabCollectionView.deleteItems(at: set)
theViewList.remove(at: newSearch)
theViewList[newSearch].removeFromSuperview()
var defaults = UserDefaults.standard
var theCurrentTab = tabCollectionView.item(at: IndexPath(item: theTabList.count - 1, section: 0)) as! TabItem
theCurrentTab.isSelected = true
print("TheTabList: " + String(theTabList.count))
print(theTabList)
}
}
}
Any ideas why its buggy? would appreciate it.
Related
I am building an app that uses a collectionView to display recipes. When the user scrolls to the bottom I call my server and fetch more recipes. Currently I call reloadData() after the server responds with new recipes. This works, but it reloads everything when all I need to do is load the new recipes. I've read similar posts indicating I can use insertItems - but for me this crashes with: libc++abi.dylib: terminating with uncaught exception of type NSException
Here is my code:
func updateRecipes(recipesToAdd: Array<Recipe>) {
let minNewRecipesIndex = (self.recipes.count + 1)
recipes += recipesToAdd
DispatchQueue.main.async {
if recipesToAdd.count == self.recipes.count {
self.collectionView?.reloadData()
} else {
let numberOfItems: [Int] = Array(minNewRecipesIndex...self.recipes.count)
self.collectionView?.insertItems(at: numberOfItems.map { IndexPath(item: $0, section: 0) })
// this crashes, but self.collectionView.reloadData() works
}
}
}
Even a simple hard coded - self.collectionView?.insertItems(at: IndexPath(item: 1, section: 0)) - crashes.
Two issues:
minNewRecipesIndex must be self.recipes.count. Imagine an empty array (.count == 0), the index to insert an item in an empty array is 0, not 1.
numberOfItems must be Array(minNewRecipesIndex...self.recipes.count - 1) or Array(minNewRecipesIndex..<self.recipes.count). Again imagine an empty array. Two items are inserted at indices 0 and 1, minNewRecipesIndex is 0 and self.recipes.count is 2, so you have to decrement the value or use the half open operator.
If the code still crashes use a for loop wrapped in beginUpdates() / endUpdates() and insert the items one by one at the last index.
Got a question about parse query.limit with swift
using below settings and limit to pull objects from parse
func getUsernames()
query.limit = self.limit
query.findObjectsInBackgroundWithBlock //display on TableView
self.usernamesArray.append(someuser)
self.tableView.reloadData()
}
then in tableView:willDisplayCell
if indexPath.row == self.usernamesArray.count - 1 {
self.limit += 5
self.getUsernames()
}
and in viewDidAppear
if self.usernames.count == 0 {
self.limit = self.limit + 10
self.getUsernames()
}
this works fine. whenever my table scrolls through the second last Cell another 5 is ready which is what i expected yay!.
problem is if usernamesArray.count has total value of 50 and when the last cell(50th cell/count) has reached/scrolled the tableView:willDisplayCell is keep getting called and the self.limit is keep increasing 55, 60, 65 etc .... it doesn't stop when it reaches the LAST Cell or Last data in array. it keeps using the LTE data and query.limit number increases 5 by 5 (when there isn't anymore array value available)
am i doing this right? or should i try different approach?
any master of swift will be appreciated! Thanks
No, you aren't doing it right. First, by just increasing the limit and querying you're always getting another copy of the data you already have and maybe a few additional items. You should be changing the offset (skip) for each new query, not the limit.
You also need to check the response to see how many items were returned. If the number of items returned is less than the request limit then you know you're at the end of the data and you shouldn't make any more requests. Set a flag so that when scrolling you know there isn't anything else you can load.
I found it easier to create a resultSet class that handles all this information.
class ParseResultSet: NSObject {
var limit = 20
var skip = 0
var total = 0
var limitReached = false
var orderByAscendingKey: String?
var orderByDescendingKey: String?
var searchActive: Bool?
func increaseSkipByLimit() {
skip += limit
}
func increaseTotal(byNumber: Int) {
total += byNumber
if byNumber == 0 {
self.limitHasBeenReached()
skip = total
}
else {
self.increaseSkipByLimit()
}
}
func limitHasBeenReached() {
limitReached = true
}
}
I then use a method where I get the objects from Parse in a completion block. I check if the limit has been reached and increment the total if it hasn't
func getObjects(classname: String, include: [String], completion: #escaping (_ result: [PFObject]) -> Void) {
if self.resultSet.limitReached == false || self.resultSet.searchActive == true {
fetchObjectsFromClass(parseClass: classname, include: include, completion: { [weak self] (objects) in
self?.resultSet.increaseTotal(byNumber: objects.count)
completion(objects)
})
}
else {
print("LIMIT REACHED")
}
}
In my case, the fetchObjectsFromClass is a global function where the query is generated and returns an array of PFObjects.
Hopefully this gives you an idea of what you need to do
I've setup a table to pull data from a database. The user can manually delete items from the table (and thus the database) via checkbox (table.editing = true, iirc) and a delete button. This can be done one at a time, or all at a time.
Unfortunately, whenever I check everything for deletion, the app crashes with the following error:
fatal error: Array index out of range
This does not happen if I select and delete only one or any number of the table rows, as long as I don't select everything.
Here's my code for the delete button:
func deleteButtonPressed(sender: AnyObject) {
if (self.pureSteamFormView.tableCalibration.editing == true) {
if (self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.count >= 1) {
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
let calibTable : FormSteamPurityCalibration = self.steamPurityCalibrationTableList[indexPath.row] /* <--- ERROR HERE */
DatabaseManager.getInstance().deleteData("FormSteamPurityCalibration", "ID = \(calibTable.ID)")
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
}
self.pureSteamFormView?.tableCalibration.reloadData()
}
}
}
Near as I can figure, it is attempting to remove the row at an index, an index that may no longer exist (?) due to the previous row also being deleted, but I'm not sure about that.
I tried putting the following code:
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
In its own for-loop block, and the error promptly move there.
I also tried removing the removeAtIndex part completely, relying on the reloadData() to perhaps update the table automatically, but it doesn't work - the data is deleted from the database, but remains on the table (although moving away from that view and going back there updates the table).
Any suggestions please? Thanks.
Your problem here is that you are deleting the lowest indexes before the bigger ones. Let me explain with an example:
Image you have 4 elements in your array:
let array = ["Element1", "Element2", "Element3", "Element4"]
You are trying to remove the elements at index 1 et 3:
for index in [1, 3] {
array.removeAtIndex(index)
}
Your program will first remove element at index 1, leaving you with the following array:
["Element1", "Element3", "Element4"]
On the second pass of the loop it will try to remove the element at index 3. Which does not exist anymore because it has moved to index 2.
One solution to this is to start removing element with the greater index before, so in your code you could change
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
to
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row > $1.row}) {
A better solution would be to filter your data array to include only the elements you whish to keep, so instead of:
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
let calibTable : FormSteamPurityCalibration = self.steamPurityCalibrationTableList[indexPath.row]
DatabaseManager.getInstance().deleteData("FormSteamPurityCalibration", "ID = \(calibTable.ID)")
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
}
you could do:
self.steamPurityCalibrationTableList.filter {
if let index = self.steamPurityCalibrationTableList.indexOf ({ $0 })
{
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows! {
if indexPath.row == index { return false }
}
return true
}
}
I have a simple piece of code that I guess I'm using local and global variables in it. But, I have a hard time understanding what's going wrong in here. I am setting "var hhhh:Int = 0" at first. Then, inside the if statement, I set "hhhh = appleCount["count"] as! Int". Since appleCount["count"] is not zero and has some value, hhhh gets its' value (I tried that uisng a print statement and hhhh is not zero inside if statement), but, later when I print hhhh with print("(hhhh)") outside if, I again get zero for its' value. Does it have something to do with local and global variables? I'm trying to communicate with Parse in the code by the way.
Thanks a lot for your kind help
import UIKit
import Parse
class NewsPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad(
var hhhh:Int = 0
var tttt:Int = 0
var cccc:Int = 1
if cccc == 1 {
var query = PFQuery(className: "Count")
query.getObjectInBackgroundWithId("RhC25gVjZm", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let appleCount = object {
appleCount["count"] = appleCount["count"] as! Int + 1
hhhh = appleCount["count"] as! Int
appleCount.saveInBackground()
}
})
}
print(hhhh)
}
}
It does not have to do with local and global variables. It has to do with background threads. The code in brackets {} after the parameter label "block" will run in a background thread at a later time.
Your print(hhhh) is running before the block has had a chance to change hhhh. Move the print statement back inside the block so you can see the variable being set.
osteven response helped me a lot understanding the problem. Thanks a lot man. In addition to osteven's response, I just waned to add that a major part of my problem was coming because I was trying to do some mathematical operations on the objects I was trying to save in Parse. So, I also figured that I could create an array, save my objects inside that array, and then access the key and update the values. Here is a sample code of what I am using right now. It does some mathematical operation on two different objects saved in Parse and updates the label's text on screen. For accessing the two objects in Parse and updating them I'm using an array.
Hope the answers here will help someone in future as the awesome people of StackOverFlow are helping me now.
Peace!
var hhhh : [Int] = []
#IBOutlet weak var jPercent: UILabel!
#IBOutlet weak var yPercent: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
var query = PFQuery(className: "Count")
if cccc == 1 {
query.getObjectInBackgroundWithId("DcU9tdRdnl", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let jCount = object {
jCount["count"] = jCount["count"] as! Int + 1
jCount.saveInBackground()
}
})
} else if cccc == 2 {
query.getObjectInBackgroundWithId("5Bq4HJbFa3", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let yCount = object {
yCount["count"] = yCount["count"] as! Int + 1
yCount.saveInBackground()
}
})
}
//shouldn't use same query for findObjectsInBackgroundWithBlock and getObjectInBackgroundWithId otherwise you'll get a runtime error
var query2 = PFQuery(className: "Count")
query2.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let users = objects {
for object in users {
if let user = object["count"] as? Int {
self.hhhh.append(user)
}
}
}
var gggg = 100*Float(self.hhhh[0])/(Float(self.hhhh[0]+self.hhhh[1]))
self.yPercent.text = String(format: "%.1f", gggg) + "%"
self.jPercent.text = String(format: "%.1f", 100 - gggg) + "%"
print(self.hhhh[0])
}
}
Yes there are other SO answers for the same error. But I cannot find a case that represents mine closely enough for me to figure it out. Please help.
These lines aren't related. They are in separate blocks. However they are giving the same error.
'array' is unavailable: please construct an Array from your lazy sequence: Array(...)
EDIT: These fixed thanks to Vadian. Correct code posted for whoever drops by :)
//let recordDic = YALCity.defaultContent().values.array[selectedIndexPath.row] // re-written
let recordArray = Array(YALCity.defaultContent().values)
let recordDic = recordArray[selectedIndexPath.row]
//let cityName = YALCity.defaultContent().keys.array[indexPath.row] // re-written
let cityArray = Array(YALCity.defaultContent().keys)
let cityName = cityArray[indexPath.row]
Fixed this one.
// for element in recordDic.keys.array { // error
for element in Array(recordDic.keys) // fixed :)
Still can't get this one.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return YALCity.defaultContent().keys.array.count
} // error
OMG I got it!
return Array(YALCity.defaultContent().keys).count
The error message says that you cannot use array as a property. The suggested syntax is
let cityArray = Array(YALCity.defaultContent().keys)
let cityName = cityArray[indexPath.row]