How to do zoom in UICollectionView - ios

I have working uicollectionview codes with CustomCollectionViewLayout , and inside have a lot of small cells but user cannot see them without zoom. Also all cells selectable.
I want to add my collection view inside zoom feature !
My clear codes under below.
class CustomCollectionViewController: UICollectionViewController {
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
customCollectionViewLayout.delegate = self
getDataFromServer()
}
func getDataFromServer() {
HttpManager.getRequest(url, parameter: .None) { [weak self] (responseData, errorMessage) -> () in
guard let strongSelf = self else { return }
guard let responseData = responseData else {
print("Get request error \(errorMessage)")
return
}
guard let customCollectionViewLayout = strongSelf.collectionView?.collectionViewLayout as? CustomCollectionViewLayout else { return }
strongSelf.items = responseData
customCollectionViewLayout.dataSourceDidUpdate = true
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
strongSelf.collectionView!.reloadData()
})
}
}
}
extension CustomCollectionViewController {
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return items.count
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].services.count + 1
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CustomCollectionViewCell
cell.label.text = items[indexPath.section].base
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath cellForItemAtIndexPath: NSIndexPath) {
print(items[cellForItemAtIndexPath.section].base)
}
}
Also my UICollectionView layout properties under below you can see there i selected maxZoom 4 but doesnt have any action !
Thank you !

You don't zoom a collection like you'd zoom a simple scroll view. Instead you should add a pinch gesture (or some other zoom mechanism) and use it to change the layout so your grid displays a different number of items in the visible part of the collection. This is basically changing the number of columns and thus the item size (cell size). When you update the layout the collection can animate between the different sizes, though it's highly unlikely you want a smooth zoom, you want it to go direct from N columns to N-1 columns in a step.

I think what you're asking for looks like what is done in the WWDC1012 video entitled Advanced Collection Views and Building Custom Layouts (demo starts at 20:20) https://www.youtube.com/watch?v=8vB2TMS2uhE
You basically have to add pinchGesture to you UICollectionView, then pass the pinch properties (scale, center) to the UICollectionViewLayout (which is a subclass of UICollectionViewFlowLayout), your layout will then perform the transformations needed to zoom on the desired cell.

Related

Image on CollectionView cell changes when scrolling

I have a collectionView that has custom cells. Each custom cell has an image in it and other components. The problem I'm having is that when I scroll, the images changes to another images from other cells on the collectionView. I'm gonna put the code that I'm using so it's more clear to see how I'm implementing the collectionView and it's cell.
func registerCell() {
collectionView.register(PageCell.self, forCellWithReuseIdentifier: cellId)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageNumber = Int(targetContentOffset.pointee.x / view.frame.width)
pageControl.currentPage = pageNumber
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return feedTitles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PageCell
cell.titleLabel.text = self.feedTitles[indexPath.item].uppercased()
cell.textView.text = self.feedDescription[indexPath.item]
guard let imageURL = URL(string: self.feedImages[indexPath.item]) else {return cell}
cell.imageView.load(url: imageURL)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
This is the code for getting the images from a server:
var feeds: [Feed]?
func getFeed(){
feedService.getFeed() { [weak self](feedRes) in
switch feedRes {
case .success(let successResponse):
print("Success Feed")
if successResponse.data.count < 1 {
} else{
self?.feeds = successResponse.data
self?.feedTitles = self?.feeds?.compactMap({ $0.title
}) ?? ["Titulo Noticia"]
self?.feedImages = self?.feeds?.compactMap({ $0.image
}) ?? ["Imagen"]
self?.feedDescription = self?.feeds?.compactMap({ $0.content
}) ?? ["Descripcion Imagen"]
self?.collectionView.reloadData()
self?.pageControl.numberOfPages = (self?.feedTitles.count)!
self?.pageControl.isHidden = false
}
case .failure(let feedError):
print("Fail Feed \(feedError)")
print(feedError.localizedDescription)
self?.pageControl.isHidden = true
}
}
}
This is the "load" function:
func load(url: URL) {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
The issue is inside your cell.imageView.load(url: imageURL) implementation.
Downloading an image is an asynchronous operation. When you create a cell - you start an operation and you don't know when it's going to complete. Whenever it completes, it sets the image on your UIImageView instance.
The cell on the other hand is a reuseable component for collectionView and when you scroll, same cell instance that disappeared by going out of screen from top will also reappear by coming in from bottom of the screen. This means that one cell instance can trigger multiple image download requests and it doesn't cancel it's previous in-flight image download request. It also does not know which image download call completed - the last one (or the one before that etc.)
What you need to do is - add some additional protection in the imageView load method so that when a cell (and hence imageView) instance is reused - it only sets the image for last request (not the ones done prior to the last one).
Copy paste following code into your project.
import UIKit
import ObjectiveC.runtime
private var keyTagIdentifier: String?
public extension UIView {
var tagIdentifier: String? {
get { return objc_getAssociatedObject(self, &keyTagIdentifier) as? String }
set { objc_setAssociatedObject(self, &keyTagIdentifier, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
Update your image load extension like this
public extension UIImageView {
func load(url: URL) {
// 1. Assign the current request identifier on this instance
let lastRequestIdentifier = url.absoluteString
self.tagIdentifier = lastRequestIdentifier
// 2. Download the image as you are doing today
// 3. After the image has been downloaded and you set the image
// Please check whether we are still interested in same image or not
// This will be written inside your image download completion block
if self.tagIdentifier == lastRequestIdentifier {
self.image = // the image you downloaded
}
}
}
I believe the issue is due to UICollectionView reusing cells. Meaning the cell that disappears from the top, is rendered at the bottom, usually with the same state as the cell that disappeared from the top and vice-versa for the cells at the bottom. You can avoid this behaviour by adding the following to you code to your PageCell,
override func prepareForReuse() {
super.prepareForReuse()
imageView?.image = nil
}

CollectionView in TableView displays false Data swift

I'm trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)

Realm/iOS: Bumpy scrolling performance for UICollectionView

I am using Realm as the alternative for coredata for the first time.
Sadly, I had this bumpy scrolling issue(It is not too bad, but quite obvious) for collectionView when I try Realm out. No data were downloaded blocking the main thread, I use local stored image instead.
Another issue is when I push to another collectionVC, if the current VC will pass data to the other one, the segue is also quite bumpy.
I am guessing it is because of the way I write this children property in the Realm Model. But I do not know what might be the good way to compute this array of array value (merging different types of list into one)
A big thank you in advance!!
Here is the main model I use for the collectionView
class STInstitution: STHierarchy, STContainer {
let boxes = List<STBox>()
let collections = List<STCollection>()
let volumes = List<STVolume>()
override dynamic var _type: ReamlEnum {
return ReamlEnum(value: ["rawValue": STHierarchyType.institution.rawValue])
}
var children: [[AnyObject]] {
var result = [[AnyObject]]()
var tempArr = [AnyObject]()
boxes.forEach{ tempArr.append($0) }
result.append(tempArr)
tempArr.removeAll()
collections.forEach{ tempArr.append($0) }
result.append(tempArr)
tempArr.removeAll()
volumes.forEach{ tempArr.append($0) }
result.append(tempArr)
return result
}
var hierarchyProperties: [String] {
return ["boxes", "collections", "volumes"]
}
}
Here is how I implement the UICollectionViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.alwaysBounceVertical = true
dataSource = STRealmDB.query(fromRealm: realm, ofType: STInstitution.self, query: "ownerId = '\(STUser.currentUserId)'")
}
// MARK: - datasource:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
guard let dataSource = dataSource else { return 0 }
return dataSource.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! STArchiveCollectionViewCell
guard let dataSource = dataSource,
dataSource.count > indexPath.row else {
return cell
}
let item = dataSource[indexPath.row]
DispatchQueue.main.async {
cell.configureUI(withHierarchy: item)
}
return cell
}
// MARK: - Open Item
func pushToDetailView(dataSource: [[AnyObject]], titles: [String]) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: STStoryboardIds.archiveDetailVC.rawValue) as? STArchiveDetailVC
else { return }
vc.dataSource = dataSource
vc.sectionTitles = titles
self.navigationController?.pushViewController(vc, animated: true)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let dataSource = self.dataSource,
dataSource.count > indexPath.row else {
return
}
let item = dataSource[indexPath.row]
self.pushToDetailView(dataSource: item.children, titles: item.hierarchyProperties)
}
Modification(more codes on configureUI):
// configureUI
// data.type is an enum type
func configureUI<T: STHierarchy>(withHierarchy data: T) {
print("data", kHierarchyCoverImage + "\(data.type)")
titleLabel.text = data.title
let image = data.type.toUIImage()
self.imageView.image = image
}
// toUIImage of enum data.type
func toUIImage() -> UIImage {
let key = kHierarchyCoverImage + "\(self.rawValue)" as NSString
if let image = STCache.imageCache.object(forKey: key) {
return image
}else{
print("toUIImage")
let defaultImage = UIImage(named: "institution")
let image = UIImage(named: "\(self)") ?? defaultImage!
STCache.imageCache.setObject(image, forKey: key)
return image
}
}
If your UI is bumpy when you're scrolling, it simply means the operations you're performing in collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell are too heavy.
Realm itself is structured in such a way that reading data from objects is very fast, so you shouldn't be seeing substantial dropped frames if all you're doing is populating a cell with values from Realm.
A couple of considerations:
If you're calling item.children inside the cellForItem block method, since you're manually looping through and paging in every Realm object doing that, that will cause frame drops. If you are, it'd be best to either do that ahead of time, or re-desing the logic to only access those arrays when absolutely needed.
You mentioned you're including images. Even if the images are on disk, unless you force image decompression ahead of time, Core Animation will lazily decompress the image at draw time on the main thread which can severely kill scroll performance. See this question for more info.
The cellForItemAt method call should already be on the main thread, so configuring your cell in a DispatchQueue.main.async closure seems un-necessary, and given that it's not synchronous, may be causing additional issues by running out of order.
Collection views are notoriously hard for performance since entire rows of cells used to be created and configured in one run loop iteration. This behavior was changed in iOS 10 to spread cell creation out across multiple run loop iterations. See this WWDC video for tips on optimizing your collection view code to take advantage of this.
If you're still having trouble, please post up more of your sample code; most importantly, the contents of configureUI. Thanks!
Turned out I was focusing on the wrong side. My lack of experience with Realm made me feel that there must be something wrong I did with Realm. However, the true culprit was I forgot to define the path for shadow of my customed cell, which is really expensive to draw repeatedly. I did not find this until I used the time profile to check which methods are taking the most CPU, and I should have done it in the first place.

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
}

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