UICollectionViewCells not showing after reloadData() - ios

I want to make an infinite scroll collection view, so in this code I add 10 to the number of cells every time the index path is equals to the last cell item, and then reload the data in the collection. The function works; I can scroll infinitely, but if I stop, and then scroll a little up or down, everything is blank. The cells aren't showing.
My theory is that it has something to do with dequeueReusableCellWithReuseIdentifier, and it decides to only show the cells that are currently on the screen.
View when scrolling (I added the numbers to the cells outside of Xcode)
View with the missing cells above when scrolling a bit after stopping
private let reuseIdentifier = "Cell"
private var numberOfCells = 20
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
if indexPath.row == numberOfCells - 1 {
numberOfCells += 10
self.collectionView?.reloadData()
}
cell.contentView.backgroundColor = UIColor.blueColor()
return cell
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberOfCells
}
}

Calling reloadData from cellForItemAtIndexPath is bad. Use other delegate method for that, for example, scrollViewDidScroll.

var numberOfCells: Int = 20 {
didSet {
if numberOfCells != oldValue {
self.collectionView?.reloadData()
}
}
}

I am doing similar things in Table View and it is working for me. The only difference is that if indexPath.row is last cell then I am calling another function which do some stuff (required for me) and calling reloadData() into that function only. Try this way, I am not sure, but it may solve your problem.

Swift 4.2
reload it like this.
DispatchQueue.main.async(execute: collectionView.reloadData)

Related

Can I add a collection view to a collection view section header?

So I'm trying to figure out how to add two collection views on the name view controller. Right now the current collection view I have is a vertical scrolling collection view that displays users posts on the feed. I would like to add a "people to follow" section that scrolls horizontally on the top. Please note I would like the horizontal collection view to scroll down with the whole view.
Something that will look like this
I thought about adding a section header, then trying to add a collection view in that but I'm not sure if that is an illegal configuration.
I'm also not sure if I need to add 2 sections in the number of sections line.
Here is the collection view code currently.
#IBOutlet weak var collectionView: UICollectionView!
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if posts.count > 4 {
if indexPath.item == posts.count - 1 {
fetchPosts()
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if viewSinglePost {
return 1
} else {
if posts.count == 0 {
self.collectionView.setEmptyMessage("You haven't followed anyone yet.")
} else {
self.collectionView.restore()
}
return posts.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostsCell", for: indexPath) as! FollowingCell
cell.delegate = self
if viewSinglePost {
if let post = self.post {
cell.post = post
}
} else {
cell.post = posts[indexPath.item]
}
handleUsernameLabelTapped(forCell: cell)
handleMentionTapped(forCell: cell)
handleHashtagTapped(forCell: cell)
return cell
}
Right now its just a basic feed that will fetch posts users upload. I thought it would be great user experience to include a people to follow section for new uses. What is the best way I should go about this?
Seems like you want to add two collection views, one is for upper that is horizontal and the lower is vertical
Add two collection views in the storyboard, give the first one a
static height, take outlet from the storyboard and give outlet name something like UpperCollectionView and LowerCollectionView
set delegate and data source to the collectionViews
upperCollectionView.delegate = self
upperCollectionView.dataSource = self
lowerCollectionView.delegate = self
lowerCollectionView.dataSource = self
in case of all delegate methods , implement the collectionViews using if - else method
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == upperCollectionView {
//do code for upperCollectionView
} else {
//do code for lowerCollectionView
}
}
if look like complex, then I will suggest you to split, the upper part in the parent viewController and lower part in a container view.
If you want to scroll up the whole thing like a tableView scrolling, add all of this on a UIScrollView , this will handle the scrolling.
if you ask me i would suggest a table view with 2 cells, fist cell is for collection view and second cell is your normal cell. in that way you can scroll in both directions. But if you want to use collectionview only then you need to add tag for collection view and configure it according to your tag i.e
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView.tag == 1 {
// your horizantal collection view
} else {
//vertical one
}
You could define the first index as another collection view.

TableView Items get refresh on scrolling

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.

UICollectionView not loading fully until I scroll

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
}

Collection View Cells not appearing in Collection View?

I created a Collection View using purely the storyboard interface builder. This is what the storyboard looks like:
My collection view's properties are default as well. I haven't written anything into my ViewController.swift yet.
For some reason, when I run on my phone / emulator, none of the buttons are showing.
UICollectionView does not support static cells like UITableView. You will have to set its dataSource,delegate and configure your cells in code.
Just configure the collectionView properly see below code and image:
Implement the delegate methods of collectionView:
class yourClassController: UICollectionViewDataSource, UICollectionViewDelegate {
override func numberOfSectionsInCollectionView(collectionView:
UICollectionView!) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView!,
numberOfItemsInSection section: Int) -> Int {
return yourArray.count
}
override func collectionView(collectionView: UICollectionView!,
cellForItemAtIndexPath indexPath: NSIndexPath!) ->
UICollectionViewCell! {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.blackColor()
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
cell.imageView?.image = UIImage(named: "circle")
return cell
}
Then from your storyboard set the delegate and datasource by drag and drop see image:
Note: collectionView appears when you do complete above formality with its relevant class.

Display cells of tableview gradually using autolayout?

I have a tableview inside my UIViewController to display comments. The height of this tableview depends on the number and the size of comments. The cells are dynamic.
I use autolayout, so my tableview has a height constraint. I set this constraint programmatically :
override func viewDidLayoutSubviews() {
self.heightCommentsTableView.constant = self.commentsTableView.contentSize.height + 50
}
It works if I display all the comments at once.
BUT, I would like display the comments 5 per 5, using this method : load more for UITableView in swift because of performance issue to display my view
(all my comments are loaded before pushing the view)
I noticed when I set my constraint, it calls cellForRowAtIndexPath for all the comments, not only the first 5.
I don't know how to do.
EDIT
var allCommentsArray: NSMutableArray = []
var elements: NSMutableArray = []
var range = 5
var currentPage = 0
var nextpage = 0
override func viewDidLoad() {
super.viewDidLoad()
allCommentsArray = NSMutableArray(array: comments)
elements.addObjectsFromArray(allCommentsArray.subarrayWithRange(NSMakeRange(0, range)))
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
self.nextpage = self.elements.count - 5
if indexPath.row == nextpage {
self.currentPage++
self.nextpage = self.elements.count - 5
self.elements.addObjectsFromArray(self.allCommentsArray.subarrayWithRange(NSMakeRange(self.currentPage, self.range)))
tableView.reloadData()
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CommentCustomTableViewCell", forIndexPath: indexPath) as! CommentCustomTableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
The table view will call tableView(_:cellForRowAtIndexPath:) for every visible cell it is about to create, the amount of it will determine based on how many sections and how many cells in each section. You let the table vie know these amounts by implementing the tableView(_:numberOfRowsInSection:) and numberOfSectionsInTableView(_:) methods of UITableViewDataSource. So, if you want to control how many cells could possibly be created and visible at a given time, you'd have to manage that state in your data source by adding and removing according to whatever logic you desire. In the answer that you linked, you can see that he is called elements.addObjectsFromArray to progressively add more elements in batches.

Resources