setting nested collectionview number of cells equal to array - ios

I have a collectionview nested inside a collectionview cell and I need the number of cells in that collectionview to be equal to a dictionary value that the super cell carries.
So to reiterate, each cell is a room object and contains a title, a description and an array of members. I want the members.count to equal the number of cells inside the interior collection view for that specified index.
It originally thought that it would be best to set this in the the super cells cellForItemAtIndexPath function as seen below,
if section == 0 {
cell = liveCell
// these work fine
let room = rooms[indexPath.row]
liveCell.liveStreamNameLabel.text = room.groupChatName
liveCell.descriptionLabel.text = room.groupChatDescription
// this does not
liveCell.profileCV.numberOfItems(inSection: 0) = room.members?.count
return cell
}
but it appears that numberOfItems(inSection: ) is a get method.
I tried accessing the array count at the actual interior collectionviews numberOfItemsInSection function however it returns zero each time.
lazy var homeController: HomeController = {
let hc = HomeController()
hc.liveCell = self
return hc
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// does not work
// let room = homeController.rooms[indexPath.row]
// return homeController.room.count
// doesn't crash but returns zero everytime
return homeController.rooms.count
}
Any suggestions?

Related

Displaying two different cells in a collection view - Swift 2.0 iOS

I'm working on a "trading" application where I would like to have a static number of cells.
On load, users will see 5 cells, each displaying a label that says "Add."
When a "player" is added, that cell displays the players information, the other 4 cells still display the "Add" label. Another is added, 2 cells have player information, 3 have the "Add"
I'm having a hell of a time with this. Can anyone point my in the right direction? I have custom labels setup, I think my logic may just be off on how to perform this correctly.
You need to subclass the UICollectionViewDelegate and UICollectionViewDataSource protocols in your viewController, then you need to implement the numberOfItemsInSection and cellForItemAtIndexPath functions.
Additionally to that you need to create two type of cells in your storyboard and subclass them, in the following code i will suppose that you call AddedPlayerCell and DefaultCell your cells, i will suppose that each cell has a label called labelText too.
let players = ["Player1","Player2"] //players added till now
let numberOfCells = 5
//Here you set the number of cell in your collectionView
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return max(players.count,numberOfCells);
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if((indexPath.row + 1) < self.players.count){ //If index of cell is less than the number of players then display the player
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("yourIdentifierForAddedPlayerCell", forIndexPath: indexPath) as! AddedPlayerCell
cell.labelText.text = self.players[indexPath.row] //Display player
return cell;
}else{//Else display DefaultCell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("yourIdentifierForDefaultCell", forIndexPath: indexPath) as! DefaultCell
cell.labelText.text = "Add"
return cell;
}
}
In order to manage two different cell types, you can:
Create 2 prototype cells for your collection view. Give one the identifier "Add" and the other "Info". The "Add" cell prototype will contain the label "Add", and the "Info" cell prototype will contain fields to display the player info.
Add an array property to your class which keeps track of which cells are displaying "Add".
var showingAdd = [true, true, true, true, true]
In cellForItemAtIndexPath, check the showingAdd array to determine which identifier to use when dequeueing the cell:
let identifier = showingAdd[indexPath.row] ? "Add" : "Info"
let cell = dequeueReusableCellWithIdentifer(identifier...)
if !showingAdd[indexPath.row] {
// configure the cell with the proper player info
// retrieve info from info property array item created in
// step 4.
let player = playerInfo[indexPath.row]
cell.playerName = player.name
...
}
When a cell is selected in didSelectItemAtIndexPath, check if it is showing add and then process it accordingly:
if showingAdd[indexPath.row] {
// query user to get player info
// store the info in a property array indexed by `indexPath.row`
playerInfo[indexPath.row] = PlayerInfo(name: name, ...)
showingAdd[indexPath.row] = false
// trigger a reload for this item
collectionView.reloadItemsAtIndexPaths([indexPath])
}

How to implement horizontally infinite scrolling UICollectionView?

I want to implement UICollectionView that scrolls horizontally and infinitely?
If your data is static and you want a kind of circular behavior, you can do something like this:
var dataSource = ["item 0", "item 1", "item 2"]
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int.max // instead of returnin dataSource.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let itemToShow = dataSource[indexPath.row % dataSource.count]
let cell = UICollectionViewCell() // setup cell with your item and return
return cell
}
Basically you say to your collection view that you have a huge number of cells (Int.max won't be infinite, but might do the trick), and you access your data source using the % operator. In my example we'll end up with "item 0", "item 1", "item 2", "item 0", "item 1", "item 2" ....
I hope this helps :)
Apparently the closest to good solution was proposed by the Manikanta Adimulam. The cleanest solution would be to add the last element at the beginning of the data list, and the first one to the last data list position (ex: [4] [0] [1] [2] [3] [4] [0]), so we scroll to the first array item when we are triggering the last list item and vice versa. This will work for collection views with one visible item:
Subclass UICollectionView.
Override UICollectionViewDelegate and override the following methods:
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let numberOfCells = items.count
let page = Int(scrollView.contentOffset.x) / Int(cellWidth)
if page == 0 { // we are within the fake last, so delegate real last
currentPage = numberOfCells - 1
} else if page == numberOfCells - 1 { // we are within the fake first, so delegate the real first
currentPage = 0
} else { // real page is always fake minus one
currentPage = page - 1
}
// if you need to know changed position, you can delegate it
customDelegate?.pageChanged(currentPage)
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let numberOfCells = items.count
if numberOfCells == 1 {
return
}
let regularContentOffset = cellWidth * CGFloat(numberOfCells - 2)
if (scrollView.contentOffset.x >= cellWidth * CGFloat(numberOfCells - 1)) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x - regularContentOffset, y: 0.0)
} else if (scrollView.contentOffset.x < cellWidth) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0.0)
}
}
Override layoutSubviews() method inside your UICollectionView in order to always to make a correct offset for the first item:
override func layoutSubviews() {
super.layoutSubviews()
let numberOfCells = items.count
if numberOfCells > 1 {
if contentOffset.x == 0.0 {
contentOffset = CGPoint(x: cellWidth, y: 0.0)
}
}
}
Override init method and calculate your cell dimensions:
let layout = self.collectionViewLayout as! UICollectionViewFlowLayout
cellPadding = layout.minimumInteritemSpacing
cellWidth = layout.itemSize.width
Works like a charm!
If you want to achieve this effect with collection view having multiple visible items, then use solution posted here.
I have implemented infinite scrolling in UICollectionView. Made the code available in github. You can give it a try. Its in swift 3.0.
InfiniteScrolling
You can add it using pod. Usage is pretty simple. Just intialise the InfiniteScrollingBehaviour as below.
infiniteScrollingBehaviour = InfiniteScrollingBehaviour(withCollectionView: collectionView, andData: Card.dummyCards, delegate: self)
and implement required delegate method to return a configured UICollectionViewCell. An example implementation will look like:
func configuredCell(forItemAtIndexPath indexPath: IndexPath, originalIndex: Int, andData data: InfiniteScollingData, forInfiniteScrollingBehaviour behaviour: InfiniteScrollingBehaviour) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellID", for: indexPath)
if let collectionCell = cell as? CollectionViewCell,
let card = data as? Card {
collectionCell.titleLabel.text = card.name
}
return cell
}
It will add appropriate leading and trailing boundary elements in your original data set and will adjust collectionView's contentOffset.
In the callback methods, it will give you index of an item in the original data set.
Tested code
I achieved this by simply repeating cell for x amount of times. As following,
Declare how many loops would you like to have
let x = 50
Implement numberOfItems
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myArray.count*x // large scrolling: lets see who can reach the end :p
}
Add this utility function to calculate arrayIndex given an indexPath row
func arrayIndexForRow(_ row : Int)-> Int {
return row % myArray.count
}
Implement cellForItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myIdentifier", for: indexPath) as! MyCustomCell
let arrayIndex = arrayIndexForRow(indexPath.row)
let modelObject = myArray[arrayIndex]
// configure cell
return cell
}
Add utility function to scroll to middle of collectionView at given index
func scrollToMiddle(atIndex: Int, animated: Bool = true) {
let middleIndex = atIndex + x*yourArray.count/2
collectionView.scrollToItem(at: IndexPath(item: middleIndex, section: 0), at: .centeredHorizontally, animated: animated)
}
Also implying that your data is static and that all your UICollectionView cells should have the same size, I found this promising solution.
You could download the example project over at github and run the project yourself. The code in the ViewController that creates the UICollectionView is pretty straight forward.
You basically follow these steps:
Create a InfiniteCollectionView in Storyboard
Set infiniteDataSource and infiniteDelegate
Implement the necessary functions that create your infinitely scrolling cells
For those who are looking for infinitely and horizontally scrolling collection views whose data sources are appended to at the end--append to your data source in scrollViewDidScroll and call reloadData() on your collection view. It will maintain the scroll offset.
Sample code below. I use my collection view for a paginated date picker, where I load more pages (of entire months) when the user is towards the right end (second to the last):
func scrollViewDidScroll(scrollView: UIScrollView) {
let currentPage = self.customView.collectionView.contentOffset.x / self.customView.collectionView.bounds.size.width
if currentPage > CGFloat(self.months.count - 2) {
let nextMonths = self.generateMonthsFromDate(self.months[self.months.count - 1], forPageDirection: .Next)
self.months.appendContentsOf(nextMonths)
self.customView.collectionView.reloadData()
}
// DOESN'T WORK - adding more months to the left
// if currentPage < 2 {
// let previousMonths = self.generateMonthsFromDate(self.months[0], forPageDirection: .Previous)
// self.months.insertContentsOf(previousMonths, at: 0)
// self.customView.collectionView.reloadData()
// }
}
EDIT: - This doesn't seem to work when you are inserting at the beginning of the data source.
in case the cell.width == collectionView.width, this solution has worked for me:
first, you need your items * 2:
func set(items colors: [UIColor]) {
items = colors + colors
}
Then add these two computed variables to determine the indices:
var firstCellIndex: Int {
var targetItem = items.count / 2 + 1
if !isFirstCellSeen {
targetItem -= 1
isFirstCellSeen = true
}
return targetItem
}
var lastCellIndex: Int {
items.count / 2 - 2
}
as you can see, the firstCellIndex has a flag isFirstCellSeen. this flag is needed when the CV appears for the first time, otherwise, it will display items[1] instead of items[0]. So do not forget to add that flag into your code.
The main logic happens here:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.item == 0 {
scroll(to: firstCellIndex)
} else if indexPath.item == items.count - 1 {
scroll(to: lastCellIndex)
}
}
private func scroll(to row: Int) {
DispatchQueue.main.async {
self.collectionView.scrollToItem(
at: IndexPath(row: row, section: 0),
at: .centeredHorizontally,
animated: false
)
}
}
That was it. The collection view scroll should now be infinite. I liked this solution because it does not require any additional pods and is very easy to understand: you just multiply your cv items by 2 and then always scroll to the middle when the indexPath == 0 or indexPath == lastItem
To apply this infinite loop functionality You should have proper collectionView layout
You need to add the first element of the array at last and last element of the array at first
ex:- array = [1,2,3,4]
presenting array = [4,1,2,3,4,1]
func infinateLoop(scrollView: UIScrollView) {
var index = Int((scrollView.contentOffset.x)/(scrollView.frame.width))
guard currentIndex != index else {
return
}
currentIndex = index
if index <= 0 {
index = images.count - 1
scrollView.setContentOffset(CGPoint(x: (scrollView.frame.width+60) * CGFloat(images.count), y: 0), animated: false)
} else if index >= images.count + 1 {
index = 0
scrollView.setContentOffset(CGPoint(x: (scrollView.frame.width), y: 0), animated: false)
} else {
index -= 1
}
pageController.currentPage = index
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
infinateLoop(scrollView: scrollView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
infinateLoop(scrollView: scrollView)
}
The answers provided here are good to implement the feature. But in my opinion they contain some low level updates (setting content offset, manipulating the data source ...) which can be avoided. If you're still not satisfied and looking for a different approach here's what I've done.
The main idea is to update the number of cells whenever you reach the cell before the last one. Each time you increase the number of items by 1 so it gives the illusion of infinite scrolling. To do that we can utilize scrollViewDidEndDecelerating(_ scrollView: UIScrollView) function to detect when the user has finished scrolling, and then update the number of items in the collection view. Here's a code snippet to achieve that:
class InfiniteCarouselView: UICollectionView {
var data: [Any] = []
private var currentIndex: Int?
private var currentMaxItemsCount: Int = 0
// Set up data source and delegate
}
extension InfiniteCarouselView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Set the current maximum to a number above the maximum count by 1
currentMaxItemsCount = max(((currentIndex ?? 0) + 1), data.count) + 1
return currentMaxItemsCount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
let row = indexPath.row % data.count
let item = data[row]
// Setup cell
return cell
}
}
extension InfiniteCarouselView: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
// Detect when the collection view has finished scrolling to increase the number of items in the collection view
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// Get the current index. Note that the current index calculation will keep changing because the collection view is expanding its content size based on the number of items (currentMaxItemsCount)
currentIndex = Int(scrollView.contentOffset.x/scrollView.contentSize.width * CGFloat(currentMaxItemsCount))
// Reload the collection view to get the new number of items
reloadData()
}
}
Pros
Straightforward implementation
No use of Int.max (Which in my own opinion is not a good idea)
No use of an arbitrary number (Like 50 or something else)
No change or manipulation of the data
No manual update of the content offset or any other scroll view attributes
Cons
Paging should be enabled (Although the logic can be updated to support no paging)
Need to maintain a reference for some attributes (current index, current maximum count)
Need to reload the collection view on each scroll end (Not a big deal if the visible cells are minimal). This might affect you drastically if you're loading something asynchronously without caching (Which is a bad practice and data should be cached outside the cells)
Doesn't work if you want infinite scroll in both directions

swift collectionview cell didSelectItemAtIndexPath shows wrong cell if you scroll

Let's say you set up a bunch of image views inside a UICollectionView's cells (from an array of image names) and make their alpha 0.5 by default when you set up the items.
Then you make the image view's alpha to 1.0 in the didSelectItemAtIndexPath func, so it becomes alpha 1 when the user taps.
This works when the user taps a cell, but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level.
The result is another cell farther down the way (when scrolling) becomes alpha 1.0 and the original cell you selected reverts back to its previous alpha 0.5 appearance.
I understand that this is all done to make things more efficient on the device, but I still have not figured out how to make it work properly where the selected item persists.
ANSWER
Apple does provide a selectedBackgroundView for cells that you can use to change the background color, shadow effect, or outline etc. They also allow you to use an image inside the cell with a "default" and "highlighted" state.
Both of those methods will persist with the selection properly.
However, if you wish to use attributes or different elements than one of those provided for indicating your selected state, then you must use a separate data model element that includes a reference to the currently selected item. Then you must reload the viewcontroller data when the user selects an item, resulting in the cells all being redrawn with your selected state applied to one of the cells.
Below is the jist of the code I used to solve my problem, with thanks to Matt for his patience and help.
All of this can be located inside your main UICollectionView Controller class file, or the data array and struct can be located inside their own swift file if you need to use it elsewhere in the project.
Data and data model:
let imagesArray=["image1", "image2", "image3", ...]
struct Model {
var imageName : String
var selectedState : Bool
init(imageName : String, selectedState : Bool = false){
self.imageName = imageName
self.selectedState = selectedState
}
}
Code for the UICollectionView Controller
// create an instance of the data model for images and their status
var model = [Model]()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// build out a data model instance based on the images array
for i in 0..<imagesArray.count {
model.append(Model(imageName: imagesArray[i]))
// the initial selectedState for all items is false unless otherwise set
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imagesArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// when the collectionview is loaded or reloaded...
let cell:myCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! myCollectionViewCell
// populate cells inside the collectionview with images
cell.imageView.image = UIImage(named: model[indexPath.item].imageName)
// set the currently selected cell (if one exists) to show its indicator styling
if(model[indexPath.item].selectedState == true){
cell.imageView.alpha = 1.0
} else {
cell.imageView.alpha = 0.5
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// when a cell is tapped...
// reset all the selectedStates to false in the data model
for i in 0..<imagesArray.count {
model[i].selectedState = false
}
// set the selectedState for the tapped item to true in the data model
model[indexPath.item].selectedState = true
// refresh the collectionView (triggering cellForItemAtIndexPath above)
self.collectionView.reloadData()
}
but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level
Because you're doing it wrong. In didSelect, make no change to any cells. Instead, make a change to the underlying data model, and reload the collection view. It's all about your data model and your implementation of cellForItemAtIndexPath:; that is where cells and slots (item and section) meet.
Here's a simple example. We have just one section, so our model can be an array of model objects. I will assume 100 rows. Our model object consists of just an image name to go into this item, along with the knowledge of whether to fade this image view or not:
struct Model {
var imageName : String
var fade : Bool
}
var model = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<100 {
// ... configure a Model object and append it to the array
}
}
override func collectionView(
collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 100
}
Now, what should happen when an item is selected? I will assume single selection. So that item and no others should be marked for fading in our model. Then we reload the data:
override func collectionView(cv: UICollectionView,
didSelectItemAtIndexPath indexPath: NSIndexPath) {
for i in 0..<100 {model[i].fade = false}
model[indexPath.item].fade = true
cv.reloadData()
}
All the actual work is done in cellForItemAtIndexPath:. And that work is based on the model:
override func collectionView(cv: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let c = self.collectionView!.dequeueReusableCellWithReuseIdentifier(
"Cell", forIndexPath: indexPath) as! MyCell
let model = self.model[indexPath.item]
c.iv.image = UIImage(named:model.imageName)
c.iv.alpha = model.fade ? 0.5 : 1.0
return c
}
You logic is incorrect. didSelectItemAtIndexPath is used to trigger something when a cell is selected. All this function should contain is this:
let cell:stkCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! stkCollectionViewCell
cell.imageView.alpha = 1.0
selectedIndex = indexPath.item
Then in your cellForItemAtIndexPath function you should have the logic to set the cell because this is where the cells are reused. So this logic should be in there:
if (indexPath.item == selectedIndex){
print(selectedIndex)
cell.imageView.alpha = 1.0
}
else {
cell.imageView.alpha = 0.5
}

Set numberOfItemsInSection at Runtime - UICollectionView

I would like to set the numberOfItemsInSection of my collectionView at runtime. I will be changing the value programmatically at runtime quite often and would like to know how.
I have an array of images to display in my UICollectionView (1 image per UICollectionViewCell), and the user can change the category of the images to display, which will also change the number of images to display. When the view loads, the numberOfItemsInSection is set to the count of the allClothingStickers array. But number this is too high. The array that does get displayed is the clothingStickersToDisplay array which is a subset of the allClothingStickers array.
There is this error after some scrolling:
fatal error: Array index out of range
This is because the number of items has become smaller, but the UICollectionView numberOfItemsInSectionproperty has not changed to be a smaller number.
I have this function that sets the number of cells in the UICollectionView before runtime.
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems
}
This function to set the stickersToDisplay array (and I want to update the numberOfItemsInSection property here):
func setStickersToDisplay(category: String) {
clothingStickersToDisplay.removeAll()
for item in self.allClothingStickers {
let itemCategory = item.object["category"] as! String
if itemCategory == category {
clothingStickersToDisplay.append(item)
}
}
self.numberOfItems = clothingStickersToDisplay.count
}
This is the function that returns the cell to display:
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let sticker = clothingStickersToDisplay[indexPath.row]
let name = sticker.object["category"] as! String
var imageView: MMImageView =
createIconImageView(sticker.image, name: name)
cell.setImageV(imageView)
return cell
}
EDIT: Oh yeah, and I need to reload the UICollectionView with the new clothingStickersToDisplay at the same place that I update it's numberOfItemsInSection
I think what you should do is to clothingStickersToDisplay a global array declaration.
Instead of using that self.numberOfItems = clothingStickersToDisplay.count
Change this function to
func setStickersToDisplay(category: String) {
clothingStickersToDisplay.removeAll()
for item in self.allClothingStickers {
let itemCategory = item.object["category"] as! String
if itemCategory == category {
clothingStickersToDisplay.append(item) //<---- this is good you have the data into the data Structure
// ----> just reload the collectionView
}
}
}
In the numberOfItemsInSection()
return self.clothingStickersToDisplay.count

Cells insight UICollectionView do not load until there are visible?

I have a UITableView with UICollectionView insight every table view cell. I use the UICollectionView view as a gallery (collection view with paging). My logic is like this:
Insight the method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This is a dictionary with an index (for the table view row),
// and an array with the url's of the images
self.allImagesSlideshow[indexPath.row] = allImages
// Calling reloadData so all the collection view cells insight
// this table view cell start downloading there images
myCell.collectionView.reloadData()
}
I call collectionView.reloadData() and in the
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// This method is called from the cellForRowAtIndexPath of the Table
// view but only once for the visible cell, not for the all cells,
// so I cannot start downloading the images
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoCollectionCell
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
if indexPath.item < arr.count {
var imageName:String? = arr[indexPath.item]
if let imageName = imageName {
var escapedAddress:String? = imageName.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let escapedAddress = escapedAddress {
var url:NSURL? = NSURL(string: escapedAddress)
if let url = url {
cell.imageOutlet.contentMode = UIViewContentMode.ScaleAspectFill
cell.imageOutlet.hnk_setImageFromURL(url, placeholder: UIImage(named: "placeholderImage.png"), format: nil, failure: nil, success: nil)
}
}
}
}
}
}
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
println("collection row: \(collectionView.tag), items:\(arr.count)")
return arr.count
}
}
return 0
}
I set the right image for the cell. The problem is that the above method is called only for the first collection view cell. So when the user swipe to the next collection view cell the above method is called again but and there is a delay while the image is downloaded. I would like all the collection view cells to be loaded insight every visible table view cell, not only the first one.
Using the image I have posted, "Collection View Cell (number 0)" is loaded every time but "Collection View Cell (number 1)" is loaded only when the user swipe to it. How I can force calling the above method for every cell of the collection view, not only for the visible one? I would like to start the downloading process before swiping of the user.
Thank you!
you're right. the function func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell will be called only when cell start to appear. that's a solution of apple called "Lazy loading". imagine your table / collection view have thousand of row, and all of those init at the same time, that's very terrible with both memory and processor. so apple decide to init only view need to be displayed.
and for loading image, you can use some asynchronous loader like
https://github.com/rs/SDWebImage
it's powerful and useful too :D

Resources