I am using the viewForSupplementaryElementOfKind function to apply the header section from the uicollectionview controller. However, before the asynchronous parsing of the viewDidAppear API, the row index is loaded into the viewForSupplementaryElementOfKind function and becomes out of range. What should I do?
Here is my code...
override func viewDidAppear(_ animated: Bool) {
callVideo3API()
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let row1 = self.list[0]
let row2 = self.list[1]
let row3 = self.list[2]
let headerSection = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! HeaderSection
headerSection.nameLabel01.text = row1.nickname
headerSection.nameLabel02.text = row2.nickname
headerSection.nameLabel03.text = row3.nickname
return headerSection
default:
assert(false, "Unexpected element kind")
}
}
You must wait until the callVideo3API() get completed. After successful completion of callVideo3API() you can reload the collection view to get the output. Please follow below steps
Call method callVideo3API()
Make CollectionView empty by returning zero through CollectionView
DataSource [func numberOfSections(in collectionView:
UICollectionView) -> Int, func collectionView(_ collectionView:
UICollectionView, numberOfItemsInSection section: Int) -> Int ]
(Optional) On the time of callVideo3API() execution you can a show an activity indicator on place of your CollectionView
After successful completion of callVideo3API() you can reload
CollectionView with corresponding DataSource Value. This time it
will work without any fault :-) (If you put activity indicator don't forget to remove after successful api call)
Related
I have a collection view, and you can select the items in it and toggle them on and off by changing the background colour. The cells are toggled on/off thanks to a boolean I have in an arrow I made for all of the cells. I have saved the bool value but when I try to write them back into the array and use collectionView.reloadData()the app crashes. My collectionViewcode is:
extension OLLViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { //set the amount of items in the CollectionView to the amount of items in the OLLData dictionary
return OLLData.OLLCasesList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { //set each cell to a different mamber of the dict.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OLLCell", for: indexPath) as! OLLCell
cell.imageView.backgroundColor = OLLData.OLLCasesList[indexPath.item]._isSelected ? UIColor.orange : UIColor.clear //change colour if selected
let image = OLLData.OLLCasesList[indexPath.item]._imageName
cell.label.text = image
cell.imageView.image = UIImage(named: image)
let savedIsSelected = defaults.bool(forKey: Key.isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = savedIsSelected
//collectionView.reloadData() //when uncommented it crashes the app
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { //detect if case selected and reload CollectionView
let caseName = OLLData.OLLCasesList[indexPath.item]._imageName
print(caseName, OLLData.OLLCasesList[indexPath.item]._isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = !OLLData.OLLCasesList[indexPath.item]._isSelected
defaults.set(OLLData.OLLCasesList[indexPath.item]._isSelected, forKey: Key.isSelected)
collectionView.reloadItems(at:[indexPath])
collectionView.reloadData()
if OLLData.OLLCasesList[indexPath.item]._isSelected == true { //if the item is selected, add to selectedCases array
selectedCases.append(OLLData.OLLCasesList[indexPath.item]._id)
selectedCaseNames.append(OLLData.OLLCasesList[indexPath.item]._imageName)
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
else if OLLData.OLLCasesList[indexPath.item]._isSelected == false { //remove from selectedCases array
selectedCases.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._id })
selectedCaseNames.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._imageName })
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
}
._isSelectedis the boolean that says whether the cell is 'toggled'.
Any ideas would be greatly appreciated.
First of all, uncommenting that line will produce an infinite loop. cellForRowAt happens because the collection view is reloading, so calling a refresh while the collection view is refreshing is no good.
So your issue is that you don't know how to display selected cells in your collection view, right?
Here's a function that fires right before the collection view is about to display a cell:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
<#code#>
}
Inside this function, you should:
Cast cell into your OLLCell (safely if you want to be thorough)
Look at your data and see if the cell should be selected OLLData.OLLCasesList[indexPath.item]._isSelected
Ask your casted cell to change its colors/UI/appearance according to your ._isSelected boolean
Step 3 has a VERY important caveat. You should be changing the UI when ._isSelected is false AND when it's true. Because the collection view reuses cells, old UI state will randomly recur. So setting it every time is a good way to ensure the behavior you want.
Here's an example:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
//Cast the vanilla cell into your custom cell so you have access
//to OLLCell's specific functions and properties.
//Also make sure the indexPath falls in the indices of your data
if let myCastedCell = cell as? OLLCell,
0 ..< OLLData.OLLCasesList.count ~= indexPath.item
{
myCastedCell.imageView.backgroundColor = OLLData
.OLLCasesList[indexPath.item]._isSelected
? UIColor.orange
: UIColor.clear
}
}
Having UICollectionView as a child of UITableView row. UICollectionView contains images, but whenever I scroll tableview down and up the collection view images got vanished randomly. I am attaching images for my problem reference. Please suggest me how to stop this.
I want my tableview to be like this. And its items should not change on scrolling.
----------------------------------------------------------------------------------------------------------------------------------------------
The collectionview images got vanish on scrolling tableview. It looks like this after scrolling up.
Code Is as follow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell:PartOfLookTableViewCell = self.looksListTable.dequeueReusableCell(withIdentifier: "cell") as! PartOfLookTableViewCell
let oneRecord = looksArray[indexPath.row]
cell.myCollectionView.loadInitial(_dataArray: oneRecord.imagesArray, isLooks: 1)
return cell
}
Code for loading data to CollectionView:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: looksReuseIdentifier, for: indexPath) as! CustomCollectionViewCell
let oneRecord = inputArray[indexPath.row]
cell.productImage.sd_setImage(with: URL.init(string: oneRecord.thumb_url)){ (image, error, cacheType, url) in
DispatchQueue.main.async {
cell.productImage.image = image
}
}
}
}
}
#Sourabh Bissa :
UITableView reuses the cell using method CellForRowAtIndexPath whenever your new cell gets visible your this method reuse the data source.
The very important thing here is to maintain the data source:
In your case cell for the row at index path giving the updated value to the collection view method but you are not reloading in main Queue. Try to do it immediately after you get the data source.
Your Cell for the row at index path will look like this :
guard let cell = self.tableview.dequeueReusableCell(withIdentifier: "cell") as! PartOfLookTableViewCell else {
return UITableViewCell()
}
let oneRecord = looksArray[indexPath.row]
cell.configureCell(for record : oneRecord with looks : 1)
return cell
and Now in the cell, you will have collection view outlet, where you will implement a collection view data source method and there you download your images asynchronously.
Cell Class will look like this :
class PartOfLookTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
collectionView.delegate = self
collectionView.dataSource = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configureCell(for record : Record , with looks : Int) {
// Here reload your collection view
// This collection view will be specific to the cell.
collectionView.reloadData()
}
}
extension PartOfLookTableViewCell : UICollectionViewDelegate , UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//return array
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Asyncronously download images
}
}
This is how you can achieve your requirements without using any tags. Please let me know if you have any Queries in it.
I need to get the next cell inside cellForItem within a collection view so that I can update a view object. When I try the following below it doesn't work. I've also tried indexPathForVisibleItems passing in indexPath.row + 1 and the produces an index out of range error.
let index = IndexPath(row: indexPath.row + 1, section: indexPath.section)
if let nextCell = collectionView.cellForItem(at: index) as! MKRCell {
nextCell.setupWaitView(time: timeToWait)
nextCell.waitViewHeightConstraint.constant = 80
nextCell.waitView.alpha = 1
nextCell.waitView.isHidden = false
}
Is this possible to achieve or will I need to do this via another way?
Thanks
No, it is not possible to get the cell object before initialization in cellForItemAt but here
you can receive the call before displaying the cell from UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView,willDisplay cell: UICollectionViewCell,forItemAt indexPath: IndexPath) {
if let myCell = cell as? MKRCell {
}
}
AND
If you want to set up the cell you have to setup view in the UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
You should update the cell in:
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Remember to modify only the cell you'll be returning from this method. Other cells might not have exist at that moment.
Alternatively you can keep a weak reference to the cell and update it when needed.
I have created a collection view that pulls from an array of images (8 right now, but user can add more). I originally was using a scrollview, but found it easier with a collection, and thanks to this great community, went to a collection view. I need to find the indexPath to delete an item at a given point. So here is some code I have so far, but I am new to this specifically. Here is some code I currently have.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UserFeedCollectionViewCell
cell.myImage.image = UIImage(named: imageArray[indexPath.row])
cell.myImage.layer.cornerRadius = 12.0
cell.myImage.clipsToBounds = true
return cell
}
//delete item at current item - 2
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row > 2 {
myCollectionView.deleteItems(at: [])
}
}
Hope this will help and feel free to ask in the comments if you have a question.
Edit: Paging is enabled, and it is horizontal scrolling, and each image takes up the whole cell.
How about keeping an array of tapped on image indices?
Define a variable for the indexes at the top:
var selected = [IndexPath]()
Then implement didSelectItemAt as:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selected.append(indexPath)
if selected.count > 2 {
let ndx = selecte.count - 3
let twoBack = selected[ndx]
myCollectionView.deleteItems(at:[twoBack])
}
}
The above would work fine for the first selection, but at that point, you would need to figure out how you handle the next selection - whether you wipe the selected array and start over, or need to keep track of further selections to handle the next input.
Depending on how you want to proceed, the selected array would either need to be wiped or to be modified to remove the item that was deleted.
I have a collection view that I want to display hourly weather in. I seem to have a problem with loading the cell, and for some reason scrolling forwards and then back loads the cell fully. Before I scroll the collection view, all of the constraints do not work and one label doesn't show it's info.
Before scrolling
After scrolling (this is how I want the cells to look like)
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return newhourlyWeather.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! hourlyWeatherCell
// Configure the cell
let hWeather = newhourlyWeather[indexPath.row]
if let HourlyTemp = hWeather.temperatureh {
cell.temperatureHLabel.text = "\(HourlyTemp)ยบ"
}
if let HourlyTime = hWeather.convertedTimeH {
cell.timeHLabel.text = "\(HourlyTime)"
}
if let HourlyRain = hWeather.precipProbabilityh {
cell.rainChanceHLabel.text = "\(HourlyRain)%"
}
cell.iconhView.image = hWeather.iconh
return cell
self.collectionView.reloadData()
}
Seems like you populate your cells asynchronously, if so then add a mycollectionview.reloadData() at the end.
I fixed the problem by adding cell.layoutIfNeeded() before the return cell. Everything loaded as expected without any scrolling!
I had the same issue and I solved calling cell.layoutIfNeeded() inside collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath).
Also if you are using UICollectionViewDiffableDataSource and you are applying the snapshot inside the viewWillAppear, you need to add some delay to make it work correctly like this:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
// apply here the snapshot
}