I am building an chat app and I have a problem with the collection view. In my app I just want to do something like whatsapp, in starting I load the first 50 messages and when we scroll down then want to fetch other messages bunch by bunch. But when I doing this and call the collection view reloadData method then it scroll to the top message. And I don't want they scrolling. Can you help me. Please..
I just simply want to make the collectionView similar to whatsapp's chat view.. i.e. load more messages when user scroll to the older messages.
What you are asking for is just feeding (inserting) cell into collection view without reloading it.
Check this appledoc, on inserting item.
determine the new indexPaths and then feed it to collection view using following
yourCollectionView.insertItems(at indexPaths: newIndexPaths)
Detect when you reach at the end and fetch more data
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
{
let lastSectionIndex = collectionView.numberOfSections - 1
let lastRowIndex = collectionView.numberOfItems(inSection: lastSectionIndex) - 1
if indexPath.section == lastSectionIndex && indexPath.row == lastRowIndex
{
//fetch more items
//add items to data array
//Reload collectionview
}
}
Try pagination concept
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.row + 1 == dataArray.count && dataArray.count < completeData.count {
fetchMoreData()
}
.....
}
func fetchMoreData() {
//Fetch and add more data to your dataArray.
//Reload collectionView
}
Related
I'm trying to create a view showing the dates for all weekdays in one single row with horizontal scrolling and pagination enabled. Similar to how it works in the iPhone's calendar app, one full week should be visible at once.
To do that, I created a collectionView and set it up to display those seven items. When the user scrolls to the left (to see the next week), the function collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) is called. Here I add the next items and reload the collectionView.:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.item > numberofitems - 10 {
numberofitems += 15
DispatchQueue.main.async {
self.myCollectionView.reloadData()
}
}
The collectionView is then reloaded:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! myCollectionViewCell
dayComponent.day = -3 * 7 - Calendar.current.component(.weekday, from: Date()) + indexPath.item + 1 + indecesadded
cell.setup(date: Calendar.current.date(byAdding: dayComponent, to: Date())!)
}
This seems to work pretty well.
However, what I also need, is being able to scroll in the other direction (backwards) and add new cells before the visible ones. I tried using the same method and adding the contentOffset:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.item < 10 {
numberofitems += 14
indecesadded += 14
DispatchQueue.main.async {
self.myCollectionView.reloadData()
self.myyCollectionView.setContentOffset(CGPoint(x: self.myCollectionView.contentOffset.x + 2 * self.view.bounds.width, y: 0), animated: false)
}
}
}
The problem is that after adding the new cells (mid-scrolling), the scrolling stops abruptly. When I manually beginn scrolling again, the paging seems to be off, meaning that the last day of one week does not correspond to the first day of the next - 1. (see images below).
before scrolling to previous week:
after scrolling:
after manually scrolling collectionView again:
So the question is: How do I insert items before the currently existing cells in a collectionView with paging enabled without messing up the scrolling and paging?
maybe you should try using scrollViewDidScroll like:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
lastContentOffset = offset //save the offset if u need to set it later
if offset.x > 30{
print("right")
scrollRight()
} else if offset.x < -30 {
scrollLeft() // here you can implement the code bellow preformBatchUpdates
print("left")
}
}
if you want to have a sort of jumpy experience, like when u scroll it turns the page but it doesn't show how the numbers smoothly go away like on built in IOS Calendar on IPhone, that's the code i've tried for that:
collectionView.performBatchUpdates(
{
collectionView.insertSections([0])
sectionIndex += 1
}, completion: nil)
collectionView.performBatchUpdates({
collectionView.deleteSections([sectionIndex - 1])
sectionIndex -= 1
}, completion: nil)
that is implied when i override scrollViewDidScroll and scroll left for example. same is done for the other direction, the idea is that you keep maintaining only 1 section, so the adding of the sections doesn't mess up the position of the items in collectionView. i am actually working on the same project so if someone could provide a sample code for how i can be done like on the IPhone calendar it would also help me, hope that snippet gives you any new ideas or solves your problem
the answer here here works but i get some weird results
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.
How can I have a UICollection View, with One cell in the first row, and three cells in the second row?
I want to display a picture in the first row, and below it 3 pictures.
You need to use collectionView's Datasource method number of section to specify how many section you required in your collectionview. in that section you can pass number of cell you want to display. and in cell for item you can set data according to section and Item value.
try like this.
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3 // you can return as per your requirement
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 2 // number of cell you want to display in each section
}else if section == 1 {
return 3 // number of cell you want to display in each section
}else {
return 4 // number of cell you want to display in each section
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierCollection, for: indexPath) as! HomeCollectionViewCell
return cell
}
You need to implement your own custom layout. By default the collection view has UICollectionViewFlowLayout, that fills the cells in a horizontal/vertical grid.
You can change this behaviour with your own layout, i.e. a class implementing UICollectionViewLayout.
To get started, I would recommend checking this and this tutorials.
new to swift. I have a nested CollectionView from one viewcontroller. The main viewcontroller has 7 collectionviewcell ("Level1Cell" in the code below). Each time I click a button or trigger an event, I want the collectionView to reload with the new data.
func eventHandler() {
// updates data
myCollectionView.reloadData()
}
Then, after it calls reload, it will call the reload again on each of the the nested CollectionViewCell.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Level1Cell", for: indexPath) as! Level1Cell
cell.appsCollectionView.reloadData()
return cell
}
The problem is, let say I want to, for the first cell, set a particular row some text.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(self.index == 0 && indexPath.row == 30){
rightCell.textLabel.text = "asdasd"
}
The fourth "Level1Cell" cell somehow has its label set also at the 30th row, but not the second and third. After stepping through the debugger, I realize that the cells, after reloading, the fourth cell "Level1Cell" is set to have the the same memory address as the first cell ( why does reload do this - shouldn't it allocate a new memory for each "Level1Cell"? - how can I get around this). Also, should I not use reload to update the data in the view and nested view of those from the view controller?
Thanks!
UIcollectionview will reuse cells. You must provide needed data for row in method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
Or just clear previous data at cell.
have a collectionView that potentially loads a ton of cells depending on the data. Currently my architecture is viewDidLoad (get first 25 from dataSource), then in willDisplayCell (get rest of datasource in sets of 25 until I hit a rate limit). The problem is that it appears that its pulling down all the data at once. I would like to not pull down more data for the cells unless the user is scrolling (i.e. unless those cells are necessary to be displayed). How can I do this?
I know that I need to work with the cell and indexPath inside willDisplayCell but I can't quite piece it together?
override func viewDidLoad() {
super.viewDidLoad()
getFirstPinsAndImages()
}
override func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
if maxExceeded == false {
getRestOfPinsAndImages()
}
}
What I needed to do was limit the data call to only when the indexPath.row of willDisplayCell (i.e. the row that is about to be displayed) was greater than the number of items in section (cells loaded) less some arbitrary number (to give a buffer for loading time):
override func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
if indexPath.row > (self.collectionView(collectionView, numberOfItemsInSection: 0) - 10) {
if maxExceeded == false {
getRestOfPinsAndImages()
}
}
}