Swift 1.2 closure optimisation bug - swift

I have an array of objects which all have a timeStamp (NSDate) and I am trying to sort them with the closure below.
The problem is that this works well without any optimisation, so with debug builds all is fine and dandy. But in with optimisations I get the following EXC_BAD_ACCESS.
reportCells.sort({ (a: UITableViewCell, b: UITableViewCell) -> Bool in
if let first = a as? GreenelyTableViewCell,
second = b as? GreenelyTableViewCell {
if let firstDate = first.timeStamp,
secondDate = second.timeStamp {
let comparison = firstDate.compare(secondDate)
if comparison == NSComparisonResult.OrderedAscending {
return false
} else {
return true
}
}
}
return false
})
Any workarounds?

The issue is you are sorting the cells. Which is not a proper approach first of all. You should sort your data source array. Because table is managing its cells in memory and when you make an array of cells. It will cause issues in backend even if it seems working. Now coming to your problem,
You are sorting cells and when you get back to the tableview listing methods, it agains fetches the data from data source i mean your array of objects. And tableview don't finds proper arrangements of data. I mean your cells are in 1 , 4, 2, 3 order after your sort while your data is stil in 1,2,3,4 order. This will cause table view to throw exception. More over, cells resulebility will cause also issue. So for your is to sort your data before showing it to cell. And I bet your problem will be resolved.

Related

Dealing with index out of bounds in SWIFT in tableview cell

Language : Swift , REALM, Working with tableview cells to display user entries.
I have a problem where I cannot figure out how to display images in a table view cell where each cell has different number of images coming in and the images itself are optional when the user saves their entry.
here is my problem - When I create a new image view in my stack view as you see in the code, to display the second image in the list, I am getting the error that the index path is out of bounds. Also, I cannot unwrap the journalAspects.inPictures[0].realmToThumbNailImage() because I get an error saying that it is not optional.
How do I go about solving this problem of showing different number of images in different cells with out crashing the app.
I have 3 user entries in my app
1st one has text, date and no images
2nd one has text, date and 1 image
3rd one has text, date and 2 images
here is what I want to see in my table view cell
first cell with just text and date
second cell with text, date and 1 image
third cell with text, date and 2 images
Thank you very much and your input is highly appreciated.
here is my code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let displayCell = journalAspectTableViewDispay.dequeueReusableCell(withIdentifier: "firstAddCell", for: indexPath) as! FirstAddTableViewCell
if let journalAspects = RealmEntries?[indexPath.row] {
//MARK: Text display
displayCell.journalTextDisplayLabel.text = journalAspects.realmText
let pictureImageView = UIImageView()
pictureImageView.heightAnchor.constraint(equalToConstant: 70).isActive = true
pictureImageView.widthAnchor.constraint(equalToConstant: 70).isActive = truedisplayCell.stackViewForImageShow.addArrangedSubview(pictureImageView)
let secondpictureImageView = UIImageView()
secondpictureImageView.heightAnchor.constraint(equalToConstant: 70).isActive = true
secondpictureImageView.widthAnchor.constraint(equalToConstant: 70).isActive = true
displayCell.stackViewForImageShow.addArrangedSubview(secondpictureImageView)
if journalAspects.inPictures.count == 0 {
return displayCell
} else {
let imagesComingOut = journalAspects.inPictures[0].realmToThumbNailImage()
secondpictureImageView.image = imagesComingOut
let secondimagesComingOut = journalAspects.inPictures[1].realmToThumbNailImage() -- App crashes
pictureImageView.image = secondimagesComingOut -- App crashes
}
}
return displayCell
}
Hi, Thank you for your feedback, It is working, But when I implement it, I am having a strange problem. As I add new entries, the number of rows in the section won't change, and weird things happen. At first as I run and compile the app from the Xcode, everything seems right. Entries with one image has one image, entries with no image has no image and entries with 2 has 2. But as I scroll up and down, suddenly entries with no image gets populated with some image. Even though, I have added a new entry, the number of rows will still return the same. For instance if I have 10 entries at the start by running the compiler on the Xcode, then I add the 11th entry, as I scroll, the table view adds the 11th entry as 10th and knocks out the first entry. No matter how many I add, they just knock one down and add another at the top making the number of rows I see as a constant number since it is compiled. If you have any idea as to why that is happening, Please let me know Thank you again.
Have you checked that inPictures actually contains 2 images? You check that there's greater than 0, but not > 1. That can be the only reason for an out of range error in that line. A safer way would be:
switch journalAspects.inPictures.count {
case 2:
secondpictureImageView.image = journalAspects.inPictures[0].realmToThumbNailImage()
pictureImageView.image = journalAspects.inPictures[1].realmToThumbNailImage()
case 1:
pictureImageView.image = journalAspects.inPictures[1].realmToThumbNailImage()
// or maybe the other one - hard to tell as you've mixed up sequencing
default: break
}
return displayCell

CollectionView.cellForItem returning nil

I've created a collectionView in Storyboard and then put the Delegate and DataSource methods in an extension to the ViewController which manages that screen.
The collectionView uses a layoutDelegate to show a four-by-four grid of images. All cells are shown in the grid, so a cell not being visible isn't a problem and they are all instances of the class imageCVC, a subclass of UICollectionViewCell
This all loads without a problem, but I now want to manipulate four random images before passing control to the user. Mindful that the collectionView may not have fully loaded by the end of viewDidLoad, I call the routine that chooses which image to manipulate, changeImages() in the viewDidLayoutSubviews method. The function is as follows:
func changeImages() {
collectionView.layoutIfNeeded()
let maxChanges = 30
var imageIndex = 0
var imageChanges 0
while imageChanges < maxChanges {
imageIndex = Int.random(in: 0..<(collectionView.numberOfItems(inSection: 0)))
if let cell = collectionView.cellForItem(as: IndexPath(row: imageIndex, section: 0)) as? imageCVC {
changeCell(cell)
imagesChanges += 1
}
}
}
(EDIT: Incorporated Sam's suggestion (below), but it still always returns nil!)
Unfortunately, whilst the imageIndex gets set correctly (so the collection knows how many elements it has), the cellForItem call always returns nil. I've forced the layout at the beginning of the function, but it has no effect.
Please could someone let me know what I'm doing wrong? Many thanks in advance.
In the following line:
imageIndex = Int.random(in: 0...(collectionView.numberOfItems(inSection: 0)))
The code starts from 0 and goes all the way to the collection view items count, so if the count is 10, the code goes from 0 to 10 including 10 which is 11 items in total. This is probably what is causing the crash since there are only 10 items and we try to access 11 items.
Just change:
0...(collectionView.numberOfItems(inSection: 0)
To
0..<(collectionView.numberOfItems(inSection: 0)
After further investigation, it appears that the collectionView data is not being loaded until after the viewDidLayoutSubviews - which seems a little contradictory to me, but hey, I'm sure there's a good reason... - and so I have implemented what I consider to be a work-around.
I've taken the call to changeImages() out from the viewDidLayoutSubviews and put it into the completion segment of a DispatchQueue.main..., written in the viewDidLoad, as follows:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
self.changeImages()
})
Essentially, I'm giving the system time (0.3 seconds) to complete it's full loading of the subviews, rather than actually placing my code at the correct part of the cycle when I know that the views have been fully loaded. A solution but, I suspect, an inelegant one.
If anyone knows how I should be approaching it, I'd be very interested to hear. Thanks.

Impossible Index of of Range crash

In application I am working on, I have to take one element of the array by specifying element's index in the array, like this array[index].
It's a simple UICollectionView which is populated with items I am getting from the array.
In order to guard against Index out of range exception, I am doing this:
guard index < array.count else { return }
return array[index]
Even though I have this guard, I got an Index out of range exception on the array[index] line (but not always).
I don't know how this can happen.
I have even added another check:
extension Collection where Indices.Iterator.Element == Index {
subscript (optional index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
So I am doing this (this is the actual code snippet from the application):
...
guard let section = chatSections[optional: indexPath.section] else {
return nil
}
guard indexPath.item < section.itemViewModels.count else {
return nil
}
return section.itemViewModels[optional: indexPath.item]
It doesn't always happen, but sometimes I get the Index out of Range exception there.
I was debugging most of the day, trying to figure out conditions when crash happens so I might be able to figure out why, but it seems to happen randomly.
Does anyone have any idea how is this possible? Did anyone encounter this kind of issue?
Are you, by any chance, doing any of your updating from a background thread/queue? If so, make sure your UI interactions are done on the main thread/queue and that you aren't changing the array contents behind your UI's back.
That is, if you're changing the array contents in one queue and trying to update your UI while this is happening, your guard statement could be passing just before the array is modified elsewhere, then by the time the rest of your UI-interacting code executes, the index my no longer be valid.
Without a more complete picture, it's hard to say what's going on, but all these bounds checks you're adding in order to guard against mysteriously-changing array indexes are a big clue to multithreading shenanigans.
I believe you have multiple access to the your data source array (multiple thread try to access add/remove from the array).
to overcome this you should use something to enforce synchronization while accessing your array, there is multiple approce using semaphore or DispatchGroup.
I would recommend to use semaphore since the array is considered as shared resource, example:
private let semaphore = DispatchSemaphore(value: 1)
private var _array:[Item] = []
var array:[Item] {
get {
semaphore.wait()
let result = self._allMessages
defer {
semaphore.signal()
}
return result
}
set {
semaphore.wait()
self. _array = newValue
semaphore.signal()
}
}
and use the array variable to access the array data source not the private _array.

Swift - Detecting whether item was inserted into NSMutableSet

This is more for interest rather than a problem, but I have an NSMutableSet, retrieved from UserDefaults and my objective is to append an item to it and then write it back. I am using an NSMutableSet because I only want unique items to be inserted.
The type of object to be inserted is a custom class, I have overrode hashCode and isEqual.
var stopSet: NSMutableSet = []
if let ud = UserDefaults.standard.object(forKey: "favStops") as? Data {
stopSet = NSKeyedUnarchiver.unarchiveObject(with: ud) as! NSMutableSet
}
stopSet.add(self.theStop!)
let outData = NSKeyedArchiver.archivedData(withRootObject: stopSet)
UserDefaults.standard.set(outData, forKey: "favStops")
NSLog("Saved to UserDefaults")
I get the set, call mySet.add(obj) and then write the set back to UserDefaults. Everything seems to work fine and (as far as I can see) there don't appear to be duplicates.
However is it possible to tell whether a call to mySet.add(obj) actually caused an item to be written to the set. mySet.add(obj) doesn't have a return value and if you use Playgrounds (rather than a project) you get in the output on the right hand side an indication of whether the set was actually changed based on the method call.
I know sets are not meant to store duplicate objects so in theory I should just trust that, but I was just wondering if the set did return a response that you could access - as opposed to just getting the length before the insert and after if I really wanted to know!
Swift has its own native type, Set, so you should use it instead of NSMutableSet.
Set's insert method actually returns a Bool indicating whether the insertion succeeded or not, which you can see in the function signature:
mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element)
The following test code showcases this behaviour:
var set = Set<Int>()
let (inserted, element) = set.insert(0)
let (again, newElement) = set.insert(0)
print(inserted,element) //true, 0
print(again,oldElement) //false,0
The second value of the tuple returns the newly inserted element in case the insertion succeeded and the oldElement otherwise. oldElement is not necessarily equal in every aspect to the element you tried to insert. (since for custom types you might define the isEqual method in a way that doesn't compare each property of the type).
You don't need to handle the return value of the insert function, there is no compiler warning if you just write insert like this:
set.insert(1)

RxCocoa extra argument in call

I am trying to attach data to a UITableView. I have download the project form here and am using the code where data is attached to the tableView: http://yannickloriot.com/2016/01/make-uitableview-reactive-with-rxswift/:
Firstly I have created the following variable:
let currentQuestion: Variable<Taxi?> = Variable(nil)
I then try to do the following:
currentQuestion
.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
But I am getting the following warning: 'Extra argument in call' on the line .bindTo. I have tried adding a new cell and get the same result. Not sure if it is relevant, but I have registered the cell.
I have read here that you can get this warning if the types of the arguments don't match: Swift - Extra Argument in call . However it looks like the arguments match fine.
I am new to Rx and was hope someone could help me understand what might be going wrong here. Thanks.
======
Edit
Here is my new code. I have tried rx_itemsWithCellIdentifier("ChoiceCell") alone and rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self):
let currentQuestion = Variable<[Taxi]>(taxis)
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Where I have used (taxis), it is an array of taxi items. See picture below:
Also once I have called .asObservable(), I have the following:
I managed to print these out by removing the line .bindTo. If I add that line back I get the same error as before.
IMPORTANT: I played around with code base from article I linked to earlier. If I remove from ChoiceCell I can replicate the same error:
// var choiceModel: ChoiceModel? {
// didSet {
// layoutCell()
// }
// }
From experience the extra argument in call message is most often given when you are trying to bind a variable with the wrong expected data type. The first issue is that you are trying to bind a single instance of Taxi to the tableview which is expecting a sequence of observables.
/**
Binds sequences of elements to table view rows.
- parameter cellIdentifier: Identifier used to dequeue cells.
- parameter source: Observable sequence of items.
- parameter configureCell: Transform between sequence elements and view cells.
- parameter cellType: Type of table view cell.
- returns: Disposable object that can be used to unbind.
*/
public func rx_itemsWithCellIdentifier<S : SequenceType, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (source: O) -> (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable
It doesn't seem like the issue is caused by the optional object but I don't see why you would want to bind optional objects to the tableview, so I would advice you to avoid that too. Here is an example which would work.
let currentQuestion = Variable<[Taxi]>([Taxi()])
currentQuestion.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Thanks to Philip Laine answer above: https://stackoverflow.com/a/36536320/2126233
That helped me see that I was making a mistake in regards to what I was observing. It helped me to see the problem I was having in my code.
If you just want to bind to a normal tableViewCell then you need to use tableView.rx_itemsWithCellFactory:
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellFactory) {(tableView, row, item) in
let cell = UITableViewCell()
cell.textLabel?.text = item.distance
return cell
}.addDisposableTo(disposeBag)
If you are using a custom cell then you can use tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self). Here is an example:
currentQuestion
.asObservable()
.filter { $0 != nil }
.map { $0!.choices }
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
For me I was still getting this same error unless I had a property inside the tableView cell that matched the element that was coming out of the array I was binding the tableView to.
So if you have an array of [Taxis] like so then inside the tableViewCell I required a variable that stored a Taxis. Then I was able to compile my project.
So in ChoiceCell I have a var like so:
var taxi: Taxi? {
didSet {
layoutCell()
}
}
I hope this helps anyone else having issues binding a tableViewCell to an array.
Well, this is a late answer but here is your problem. If this can help others to understand it.
You have an array of Taxi elements
when rx_itemsWithCellIdentifier() is called, element is a Taxi (of course, it is an element of the array)
But in your code you do:
cell.choiceModel = element
// Where
var choiceModel: ChoiceModel? {
didSet {
layoutCell()
}
}
So, you are trying to assign a Taxi to a ChoiceModel. This is why your are getting this error. Swift type inference determines that the types mismatch. You can try to comment the line inside the block and the error should disappear.