Adding placeholder images to UICollectionView cells while actual images are being downloaded - swift

I have a collection view that is being populated with images created from parsed data. It is being populated and updated by using an NSFetchedResultsController as the data source. The code is in Swift 4, so to do so I am using the data source methods for the collection view along with the delegate methods for the NSFetchedResultsControllerDelegate as shown here:
func collectionView (_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}else {
return 0
}
}
func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Setting cell with photo")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! CollectionViewCell
let photoForCell = fetchedResultsController.object(at: indexPath)
cell.cellImage.image = UIImage(data: photoForCell.imageData!)
print(photoForCell.imageData!)
return cell
}
I am trying to calculate how many images were pulled in the parsed data and use this information to temporarily populate that many cells in the collection view with the same placeholder image in all of them. Then, once the images have all been downloaded and saved the NSFetchedResultsController will trigger the data source methods to refresh and populate the collection view with the new photos instead of the placeholder photos.
I have not been able to find any videos or posts that show how to do so. I tried creating a variable that holds the count of objects in the parsed data. That variable is then used in an if statement in the (:numberOfItemsInSection:) and (:cellForItemAt:) to determine whether it should populate the collection from saved photos or using the temporary photos like this:
func collectionView (_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.placeHolderCount != nil {
return placeHolderCount
}
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}else {
return 0
}
}
func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Setting cell with photo")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! CollectionViewCell
let photoForCell = fetchedResultsController.object(at: indexPath)
if self.placeHolderCount != nil {
cell.cellImage.image = #imageLiteral(resourceName: "VirtualTourist_5112")
return cell
}
cell.cellImage.image = UIImage(data: photoForCell.imageData!)
print(photoForCell.imageData!)
return cell
}
Unfortunately, that causes the app to crash with an error saying "NSInvalidArgumentException reason: 'no object at index 0 in section at index 0"
During my troubleshooting, I found that this crash happens at the point when the view context is saved after downloading all the images. Everything in the application works perfectly including loading the downloaded images until I add the changes to the delegate methods. Does anyone know if I am on the right track? Should I be going about it in a completely different way? I have seen external libraries around that will accomplish this, but I cannot use any external libraries or frameworks for this particular application.

Related

Make the first cell automatically selected when VC opens is not working

I have a ViewController that have a collectionView and I managed to make it selectable and all but the problem is that I have a checkmark image that stays in the first cell when the VC opens but in fact the cell is not selected at all and still the checkmark is there!
Code of the VC:
var selected = IndexPath(item: 0, section: 0)
var properties = connectedProperties(StatusCode: 0)
var propertiesNew = connectedProperties(StatusCode: 0)
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return properties.Result?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dashboardCollectionViewCell", for: indexPath) as? dashboardCollectionViewCell else { return UICollectionViewCell() }
let currentPropertie = properties.Result?[indexPath.row]
cell.checkMarkButton.isHidden = !(indexPath == selected)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = properties.Result?[indexPath.row]
changeCustomerKey.DefaultsKeys.keyTwo = indexPath.row
changeCustomerKey.DefaultsKeys.keyThree = selectedCell!.id!
let previous = selected.dropLast()
selected = indexPath
collectionView.reloadItems(at: [previous, selected])
}
If you want to tell your collection view to select a specific cell, you need to call the UICollectionView method selectItem(at:animated:scrollPosition:).
The "tricky bit" is that you can't call that until the collection view has finished populating itself (by calling your data source methods) and the first cell has been added to the collection view.
You might need to resort to something a bit hacky like adding an "initialDisplay" bool property who's value starts as true.
In your data source method that returns cells, check if initialDisplay==true and the requested indexPath is (0,0). If so, set initialDisplay=false, and fire a one-shot timer with a short delay. In the timer's closure, call selectItem(at:animated:scrollPosition:). The timer delay will return control to the event loop and give the system time to add the cell to the collection view.
There might be a better way to do this, but I can't think of it offhand, since you can't be sure when you will be asked to return your cell at IndexPath (0,0)

Deselcting a pre pupulated array swift

I have a filter operation which is done on a bottom sheet and this value is then converted to a dictionary, I have a collectionView embeded in a UIView to display filter parameters.
When an item is selected and a filter button is pressed, I want to save the selected value using UserDefault so I can persist the searched Item if the user decides to go to the filter page again until the user press rest then I clear everything.
Currently my filter works as expected but my issue now is the persistence. When I preselect the cells, and I try to deselect that field, I get a crash.
Here's what I do
fileprivate let data1 = UserDefaultsConfig.contributionTypeFilter["selectedPaymentIndex"] as? Data
var arrSelectedIndex = [IndexPath]() // This is selected cell Index array
var arrSelectedData = [String]()
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.contentView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
let strData = PaymentMethodFilter.allCases[indexPath.item].rawValue.capitalized
arrSelectedIndex.remove(at: indexPath.row)
arrSelectedData.remove(at: indexPath.row)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let strData = PaymentMethodFilter.allCases[indexPath.item].rawValue.capitalized
arrSelectedIndex.append(indexPath)
arrSelectedData.append(strData)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeue(dequeueableCell: PaymentMethodFilterCollectionCell.self, forIndexPath: indexPath)
cell.feedSubviews(with: PaymentMethodFilter.allCases[indexPath.row])
guard let selectedData = data1 else {
return cell
}
arrSelectedData = UserDefaultsConfig.contributionTypeFilter["payment_method_names"] as? [String] ?? []
arrSelectedIndex = NSKeyedUnarchiver.unarchiveObject(with: selectedData) as? [IndexPath] ?? []
arrSelectedIndex.forEach {
collectionView.selectItem(at: $0, animated: true, scrollPosition: .right)
}
return cell
}
Fatal error: Index out of range: file
/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/Array.swift,
line 1221 2020-08-16 15:36:26.359431+0100 Riby[40882:1056506] Fatal
error: Index out of range: file
/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/Array.swift,
line 1221
I want to save the selected value
To record what is selected, do not make an array of index paths, because index paths can change (and in your code, that's just what happens). Instead, make an array of unique identifiers for the data represented by the selected cells. That way, you can always find them again. What doesn't change is the data, so that's the way to identify things.
Basically, your underlying data model should consist of uniquely identifiable entries. (If you use a diffable data source, that rule will be enforced for you, which is just one many reasons why diffable data sources are great.) You can always convert between actual data objects and current cells in the collection view, in both directions.

Proper method to Iterate and Load Images and title from Array (image_url) into ViewController in Swift

I using a CollectionView with an ImageView in the prototype cell. I retrieve API result (title and image_url) and I would like to display an image and title in the collection view through array to retrieve the title and image_url.
Is it possible to retrieve image_url and display the image in the CollectionView?
I used SwiftyJSON to create the array and I can retrieve the API result (included title and image_url) in the array (can pint out the result). But I can't show it in the collection view.
I am posting an example code for your problem that might help you.
// Array that you retrieve from your API
let titleAndImageArray = []()
//CollectionView DataSourceMethod
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titleAndImageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:YourCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! YourCollectionViewCell
let labelTitle = titleAndImageArray[indexPath.row].title
let musicImageUrlString = titleAndImageArray[indexPath.row].image_url
cell.yourLabelOutletName.text = labelTitle
loadImage(indexPath: indexPath, cell: cell, urlString:musicImageUrlString)
}
*I have created a loadImage function to get image from URL which uses SDWebImage library which is Asynchronous image downloader SDWebImage *
func loadImage(indexPath:IndexPath,cell:YourCollectionViewCell,urlString:String){
cell.yourImageViewOutletName.sd_setImage(with: URL(string: urlString), placeholderImage: UIImage(named: "App-Default"),options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
})
}

Save Data always in the first Cell UICollectionView

I have a UICollectionView and i want to save the data (images) always in the first cell and the old data walks to the next cells. So when i save a new photo it´s comes in the first cell, i have tried with indexpath.row == 0 {} in cellForRowAtIndexPath but he saves only in the first cell in the other cells are nothing. Can i invert the save index or give it a another way ??
So have anyone a idea what i can make there ??
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picture.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("BookPicCell", forIndexPath: indexPath) as! BookPicCell
let cellcore = picture[indexPath.row]
if indexPath.row == 0 {
cell.BookImage.image = UIImage(contentsOfFile: cellcore.foto!)
}
return cell
}
Thanks for your Help.
You need to create an array of UIImages and append the new image to the front of that array:
pictures.insert(newImage, atIndex : 0)
Than after you inserted your new image you need to reload your UICollectionView using:
self.collectionView.reloadData()
You do not want to sort your data inside the cellForItemAtIndexPath

UICollectionView setting first cell to always be specific content

Hi so I'm using a side scrolling UICollectionView to display groups of people that the user makes. The groups are stored on my server and when the view loads, they load from the server. However I want the first cell to always be the same which is a cell which lets you create groups. This is the layout i need.
I know how to use multiple different custom cells, but how do I make it so the first cell is static and the cells after load content from my servers? Thanks :)
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return familyName.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
return cell
} else {
let cell : FriendGroupsCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! FriendGroupsCell
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
return cell
}
}
This is my code and it misses out the first person in the array because the index path skips over it. How can i modify this so it works
UICollectionViewCell is leveraging reusing techniques to improve performance. Remember this. Nothing can be static in a cell, because this cell later will be on another index.
You can use collectionView:cellForItemAtIndexPath: to make the first cell always load the same images/labels via indexPath.row == 0
You can use prepareReuse method to clean up the resources in the cell. So if cell No.2 is going to be the new No.1 cell, it get a chance to clean up old resources.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
if indexPath.row == 0 {
cell.groupImage.image = UIImage(named: "new")
cell.groupNameLabel.text = "new"
} else {
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
}
return cell
}