UIcollectionview lagging while scrolling cells - ios

I am creating 7 cells using UICollectionview. When i scroll, the application works fine, but if i continue to scroll, it start to lag and the shadow(behind every cell) become more dark.
I think that the cell that disappear from the screen is not deleted and when i return back the program recreate a new one in the same position of the oldest one. is there any solution?
Screen before the scroll
https://ibb.co/dNQJ5k
Scree after the scroll
https://ibb.co/kjDAJ5
here's the code
class menuController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var coll_view: UICollectionView!
var array = [String]()
override func viewDidLoad() {
array = ["segue_menu_map", "segue_menu_camera"]
coll_view.scrollToItem(at: IndexPath(item: 2, section: 0), at: .left, animated: true)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)
button.layer.cornerRadius = 0.2*button.frame.width
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2
button.tag=indexPath.row
button.layer.shadowColor = UIColor.lightGray.cgColor
button.layer.shadowOpacity = 1.0
button.layer.shadowOffset = CGSize(width: 0.4, height: 1.8)
cell.clipsToBounds = false
button.addTarget(self, action: #selector(collectionAction(sender:)), for: .touchUpInside)
print([indexPath.row])
//cell.backgroundColor = UIColor.gray
button.setTitle(String(indexPath.row), for: .normal)
button.setTitleColor(.black, for: .normal)
cell.addSubview(button)
return cell
}
func collectionAction( sender: UIButton) {
if sender.tag < 2{
self.performSegue(withIdentifier: array[sender.tag], sender: nil)
}
}
}
thanks in advance

collectionView.dequeueReusableCell does not create a new cell every time. It reuses already created cells to improve performance. You are adding a button to the cell every time it's reused, this could mean that a single cell could have dozens/hundreds of buttons on it.
The solution is to make a subclass of UICollectionViewCell and put your set-up code there.

I faced the same problem when i am scrolling the collectionView.
I had found two major problem and fixed this.
I moved the code like create a button or something to the collectionviewcell .m file explicitly. This bring me smooth scrolling.
I delete shadow property of the cell.. trust me it removes the lag immediately.
Now i dont know how to fix this problem with shadow offset. but upper case was fixed the lag.
And add this code in custom cell or cellforItem in main class:
for main class
cell.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:cell.contentView.layer.cornerRadius].CGPath;
for custom cell
self.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:cell.contentView.layer.cornerRadius].CGPath;

Related

UICollectionViewCell created from XIB will cause flickering during drag and drop

I implement a simple drag and drop sample.
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView?
var colors: [UIColor] = [
.link,
.systemGreen,
.systemBlue,
.red,
.systemOrange,
.black,
.systemPurple,
.systemYellow,
.systemPink,
.link,
.systemGreen,
.systemBlue,
.red,
.systemOrange,
.black,
.systemPurple,
.systemYellow,
.systemPink
]
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: view.frame.size.width/3.2, height: view.frame.size.width/3.2)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
//collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
let customCollectionViewCellNib = CustomCollectionViewCell.getUINib()
collectionView?.register(customCollectionViewCellNib, forCellWithReuseIdentifier: "cell")
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .white
view.addSubview(collectionView!)
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture))
collectionView?.addGestureRecognizer(gesture)
}
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard let collectionView = collectionView else {
return
}
switch gesture.state {
case .began:
guard let targetIndexPath = collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
return
}
collectionView.beginInteractiveMovementForItem(at: targetIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: collectionView))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = view.bounds
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = colors[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width/3.2, height: view.frame.size.width/3.2)
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = colors.remove(at: sourceIndexPath.row)
colors.insert(item, at: destinationIndexPath.row)
}
}
However, I notice that, if my UICollectionViewCell is created with XIB, it will randomly exhibit flickering behaviour, during drag and drop.
The CustomCollectionViewCell is a pretty straightforward code.
CustomCollectionViewCell.swift
import UIKit
extension UIView {
static func instanceFromNib() -> Self {
return getUINib().instantiate(withOwner: self, options: nil)[0] as! Self
}
static func getUINib() -> UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
class CustomCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Flickering
By using the following code
let customCollectionViewCellNib = CustomCollectionViewCell.getUINib()
collectionView?.register(customCollectionViewCellNib, forCellWithReuseIdentifier: "cell")
It will have the following random flickering behaviour - https://youtu.be/CbcUAHlRJKI
No flickering
However, if the following code is used instead
collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
Things work fine. There are no flickering behaviour - https://youtu.be/QkV2HlIrXK8
May I know why it is so? How can I avoid the flickering behaviour, when my custom UICollectionView is created from XIB?
Please note that, the flickering behaviour doesn't happen all the time. It happens randomly. It is easier to reproduce the problem using real iPhone device, than simulator.
Here's the complete sample code - https://github.com/yccheok/xib-view-cell-cause-flickering
While we are rearranging cells in UICollectionView (gesture is active), it handles all of the cell movements for us (without having us to worry about changing dataSource while the rearrange is in flight).
At the end of this rearrange gesture, UICollectionView rightfully expects that we will reflect the change in our dataSource as well which you are doing correctly here.
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = colors.remove(at: sourceIndexPath.row)
colors.insert(item, at: destinationIndexPath.row)
}
Since UICollectionView expects a dataSource update from our side, it performs following steps -
Call our collectionView(_:, moveItemAt:, to:) implementation to provide us a chance to reflect the changes in dataSource.
Call our collectionView(_:, cellForItemAt:) implementation for the destinationIndexPath value from call #1, to re-create a new cell at that indexPath from scratch.
Okay, but why would it perform step 2 even if this is the correct cell to be at that indexPath?
It's because UICollectionView doesn't know for sure whether you actually made those dataSource changes or not. What happens if you don't make those changes? - now your dataSource & UI are out of sync.
In order to make sure that your dataSource changes are correctly reflected in the UI, it has to do this step.
Now when the cell is being re-created, you sometimes see the flicker. Let the UI reload the first time, put a breakpoint in the cellForItemAt: implementation at the first line and rearrange a cell. Right after rearrange completes, your program will pause at that breakpoint and you can see following on the screen.
Why does it not happen with UICollectionViewCell class (not XIB)?
It does (as noted by others) - it's less frequent. Using the above steps by putting a breakpoint, you can catch it in that state.
How to solve this?
Get a reference to the cell that's currently being dragged.
Return this instance from cellForItemAt: implementation.
var currentlyBeingDraggedCell: UICollectionViewCell?
var willRecreateCellAtDraggedIndexPath: Bool = false
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard let cv = collectionView else { return }
let location = gesture.location(in: cv)
switch gesture.state {
case .began:
guard let targetIndexPath = cv.indexPathForItem(at: location) else { return }
currentlyBeingDraggedCell = cv.cellForItem(at: targetIndexPath)
cv.beginInteractiveMovementForItem(at: targetIndexPath)
case .changed:
cv.updateInteractiveMovementTargetPosition(location)
case .ended:
willRecreateCellAtDraggedIndexPath = true
cv.endInteractiveMovement()
default:
cv.cancelInteractiveMovement()
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if willRecreateCellAtDraggedIndexPath,
let currentlyBeingDraggedCell = currentlyBeingDraggedCell {
self.willRecreateCellAtDraggedIndexPath = false
self.currentlyBeingDraggedCell = nil
return currentlyBeingDraggedCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.backgroundColor = colors[indexPath.item]
return cell
}
Will this solve the problem 100%?
NO. UICollectionView will still remove the cell from it's view hierarchy and ask us for a new cell - we are just providing it with an existing cell instance (that we know is going to be correct according to our own implementation).
You can still catch it in the state where it disappears from UI before appearing again. However this time there's almost no work to be done, so it will be significantly faster and you will see the flickering less often.
BONUS
iOS 15 seems to be working on similar problems via UICollectionView.reconfigureItems APIs. See an explanation in following Twitter thread.
Whether these improvements will land in rearrange or not, we will have to see.
Other Observations
Your UICollectionViewCell subclass' XIB looks like following
However it should look like following (1st one is missing contentView wrapper, you get this by default when you drag a Collection View Cell to the XIB from the View library OR create a UICollectionViewCell subclass with XIB).
And your implementation uses -
cell.backgroundColor = colors[indexPath.row]
You should use contentView to do all the UI customization, also note the indexPath.item(vs row) that better fits with cellForItemAt: terminology (There are no differences in these values though). cellForRowAt: & indexPath.row are more suited for UITableView instances.
cell.contentView.backgroundColor = colors[indexPath.item]
UPDATE
Should I use this workaround for my app in production?
NO.
As noted by OP in the comments below -
The proposed workaround has 2 shortcomings.
(1) Missing cell
(2) Wrong content cell.
This is clearly visible in https://www.youtube.com/watch?v=uDRgo0Jczuw Even if you perform explicit currentlyBeingDraggedCell.backgroundColor = colors[indexPath.item] within if block, wrong content cell issue is still there.
The flickering is caused by the cell being recreated at its new position. You can try holding to the cell.
(only the relevant code is shown)
// keeps a reference to the cell being dragged
private weak var draggedCell: UICollectionViewCell?
// the flag is set when the dragging completes
private var didInteractiveMovementEnd = false
#objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
// keep cell reference
draggedCell = collectionView.cellForItem(at: targetIndexPath)
collectionView.beginInteractiveMovementForItem(at: targetIndexPath)
case .ended:
// reuse the cell in `cellForItem`
didInteractiveMovementEnd = true
collectionView.performBatchUpdates {
collectionView.endInteractiveMovement()
} completion: { completed in
self.draggedCell = nil
self.didInteractiveMovementEnd = false
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// reuse the dragged cell
if didInteractiveMovementEnd, let draggedCell = draggedCell {
return draggedCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
...
}

UITableView's Items Change Color After Scrolling Down, Then Back Up Swift

I'm making an iOS app with swift and Xcode 11. Inside my app, there is a scrollable table view controller, consisting of buttons on the left and right side, like this:
These are 2 of the many UITableViewCells that I've made. When the user presses the red button, the once red button becomes green. But there's a glitch: If I press a red button(button goes green) and then I scroll down inside the UITableView(and scroll back up), the button that was once green(and still should be green) isn't green anymore. I have no idea why this is happening and I've scrounged StackOverflow's other similar questions like this one, but did not manage to find any.
Here's my UITableViewController:
import UIKit
#objcMembers class CustomViewController: UITableViewController {
var tag = 0
override func viewDidLoad() {
super.viewDidLoad()
tag = 0
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
tag = 0
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return SingletonViewController.themes.count
}
// 3
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tag = tag + 1
let cell = tableView.dequeueReusableCell(withIdentifier: "themeCell", for: indexPath) as! ThemeCell
let cellButton = UIButton(frame: CGRect(x: 0, y: 5, width: 88, height: 119.5))
cellButton.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(cellButton)
cell.accessoryView = cellButton
cellButton.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 10).isActive = true
cellButton.topAnchor.constraint(equalTo: cell.topAnchor, constant: 10).isActive = true
cellButton.widthAnchor.constraint(equalToConstant: 88).isActive = true
cellButton.heightAnchor.constraint(equalToConstant: 119.5).isActive = true
cellButton.setImage(UIImage(named: SingletonViewController.themes[indexPath.row]), for: UIControl.State.normal)
cellButton.addTarget(self, action: #selector(CustomViewController.backBTN(sender:)), for: .touchUpInside)
cellButton.tag = tag
var cellyi: UIButton!
//red/green button's declaration^
cellyi = UIButton(frame: CGRect(x: 5, y: 5, width: 50, height: 30))
cell.addSubview(cellyi)
cell.accessoryView = cellyi
cellyi.backgroundColor = UIColor.red
cellyi.addTarget(self, action: #selector(CustomViewController.backBTN(sender:)), for: .touchUpInside)
cellyi.tag = tag
print(cellyi.tag)
if UserDefaults.standard.integer(forKey: "like") == 0{
UserDefaults.standard.set(1, forKey: "like")
}
if UserDefaults.standard.integer(forKey: "like") == tag{
cellyi.backgroundColor = UIColor.green
}
tableView.allowsSelection = false
return cell
}
#objc func backBTN(sender: UIButton){
UserDefaults.standard.set(sender.tag, forKey: "like")
tag = 0
tableView.reloadData()
}
}
The cellForRowAt method is not a for loop!
I see that you are using a tag property to control what gets shown in each cell. From the fact that you increment the tag very time cellForRowAt is called, you seem to assume that cellForRowAt will be called once for each row, in order. This is not the case, and you should not implement cellForRowAt like this.
cellForRowAt essentially asks a question: "What should the cell at this index path be?", and you provide the answer. The index path that the table view is asking about is the indexPath parameter. You should make use of this parameter instead of your own tag property, because the table view is not asking about that.
The reason why why your code doesn't work is because table view cells are reused. When cells are scrolled out of view, they are not put aside, so that when new table view cells need to be shown, they can be reconfigured to "appear as if they are new cells". Essentially what this means is that when you scroll up, cellForRowAt is called for the rows that are about to come into view. You didn't expect that, did you?
All that code that sets up each cell should be moved to the initialiser of ThemeCell. Alternatively, design the cell in a storyboard. cellForRowAt should only configure a cell specifically for an index path. ThemeCell should have the properties cellButton and cellyi so that the buttons can be accessed.
Now, cellForRowAt can be written like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "themeCell", for: indexPath) as! ThemeCell
cell.cellButton.setImage(UIImage(named: SingletonViewController.themes[indexPath.row]), for: UIControl.State.normal)
cell.cellButton.addTarget(self, action: #selector(CustomViewController.backBTN(sender:)), for: .touchUpInside)
cell.cellyi.addTarget(self, action: #selector(CustomViewController.backBTN(sender:)), for: .touchUpInside)
if UserDefaults.standard.integer(forKey: "like") == indexPath.row {
cell.cellyi.backgroundColor = UIColor.green
} else {
cell.cellyi.backgroundColor = UIColor.red
}
// this line should be moved to viewDidLoad
// tableView.allowsSelection = false
return cell
}

How can I access a button added in a collection view cell?

I added buttons in my collection view cells by the code below, where 'myButton' refers to the buttons I try to make access to.
When I click some button outside the collectionView, I want one of my buttons to have its background image changed to have the background of the button clicked, which I tried with 'sendToBox' function below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! ItemCollectionViewCell
cell.myButton.setTitle(self.items[indexPath.item], for: .normal)
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 0.5
return cell
}
...
#IBAction func keyClick(_ sender: UIButton) {
...
sendToBox(object: sender)
...
}
...
func sendToBox(object sentObject: UIButton) {
let imageToSend = sentObject.backgroundImage(for: .normal)
let imageIdentity = sentObject.restorationIdentifier
let cell = self.collectionView(ItemCollectionSet, cellForItemAt: IndexPath(item: 0, section: 0))
cell.myButton ## blah blah not working!
}
So I want to make a direct access to one of the cells I created, and then change the button inside it to have a different appearance. I'm stuck in this matter for a whole day, please help me out.

Weird behavior when scrolling and selecting in UICollectionView

I am having issues with displaying a checkmark on the a custom cell in a UICollectionView. For the first few taps everything works as expected, but when I begin scrolling or tapping repeatedly or click on the already selected cell, the behavior becomes odd as shown in the gif. Perhaps I am going about this in an incorrect way? The .addCheck() and .removeCheck() are methods inside the custom UICollectionViewCell class I made and all they do is add a checkmark image or remove one from the cell view. The odd behavior shown here
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ColorUICollectionViewCell
// Configure the cell
let color = colorList[(indexPath as NSIndexPath).row]
cell.delegate = self
cell.textLabel.text = color.name
cell.backgroundColor = color.color
if color.selected {
cell.addCheck()
}
else {
cell.removeCheck()
}
return cell
}
// user selects item
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for color in colorList {
color.selected = false
}
// set selected color to true for selection
let color = colorList[indexPath.row]
color.selected = true
settings.backgroundColor = color.color
//userDefaults.set(selectedIndex, forKey: "selectedIndex")
collectionView.reloadData()
}
Below is what the addCheck() and removeCheck() functions in my custom cell look like.
func addCheck() {
// create check image
let checkImage = UIImage(named: "checkmark")
checkImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.size.height / 4, height: bounds.size.height / 4))
checkImageView.image = checkImage!.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
checkImageView.tintColor = UIColor.white()
// add the views
addSubview(checkImageView)
}
func removeCheck() {
if checkImageView != nil {
checkImageView.removeFromSuperview()
}
}
first off, you can simplify your didSelect a bit:
override func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for (index, color) in colorList.enumerate() {
if index == indexPath.row {
color.selected = false
settings.backgroundColor = color.color
}
else {
color.selected = false
}
}
collectionView.reloadData()
}
Based on the language in your cellForItemAt method, I'm guessing you're adding a second check mark image when you tap on the same cell twice, and it's not being tracked properly so that cell just keeps getting rotated around overtime the collectionView's reloaded
Post your cell class, or at least the logic for addCheck and removeCheck and we might find the problem.
What I would recommend is permanently having an imageView with the check mark over the cell, when simple show/hide it based on the selection. This should speed up the collectionView as well.

Retaining button content in UICollectionView after scrolling with swift

I'm trying to make a UICollectionView that has infinite scrolling of buttons and the button's background is populated base on the result of http request to a server.
let reuseIdentifier = "Cell"
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let categoryApiUrl = "url"
let categoryImageField = "field"
class BrowseViewController: UICollectionViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var categoryImgUrl:[String] = []
var buttonList:[UIButton] = []
func setupView(){
self.title = "Browse"
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: screenWidth/2-15, height: screenHeight/3.5)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView!.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView!)
}
func setupButton(cell: UICollectionViewCell, cellNumber: Int){
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.frame = CGRectMake(0, 0, screenWidth/2-15, screenHeight/3.5)
button.backgroundColor = UIColor.orangeColor()
button.setTitle("Category", forState: UIControlState.Normal)
button.addTarget(self, action: "btnClicked:", forControlEvents: UIControlEvents.TouchUpInside)
buttonList.append(button)
cell.addSubview(button)
}
override func viewDidLoad(){
super.viewDidLoad()
let url = NSURL(string: categoryApiUrl)
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, dataValue, error) in
let json = JSON(data: dataValue)
for(var i = 0; i < json.count; i++){
self.categoryImgUrl.append(json[i]["CATEGORY_IMAGE"].stringValue)
let imageUrl = self.categoryImgUrl[i]
let url = NSURL(string: imageUrl)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
self.buttonList[i].setBackgroundImage(image, forState: .Normal)
}
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return 10;
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell
let cellNumber = indexPath.row as Int
setupButton(cell, cellNumber: cellNumber)
// Configure the cell
return cell
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
numberOfItemsPerSection += 6
self.collectionView!.reloadData()
}
}
}
Currently, the code is able to pull the image from the server and populate it as the button's background image.
However, since I made this collection view scrollable. When I scroll the view down and then back up, the background image of the previous buttons disappear.
I did some research but couldn't find a solution to it. The reason that the button disappears is because IOS only loads the cell that is visible on screen. So when I scroll down and then scroll back up, the previous cells are consider as "New Cells". Therefore the background image that was in it are now gone.
Questions:
Does anyone have an idea on how to retain the previous buttons even if we scroll down and then scroll back up? In addition, with my current code, I added the image onto the button inside the http request because the http request is always the last execution that finishes. Is there anyway to change the code so then the http request will be finish before the cells get loaded?
I would suggest to create uicollectionview in interface builder, subclass uicollectionviewcell, add one dynamic cell to collectionview, change its class to collectionviewcell you subclassed, drop uibutton on it, and everytime cell is being created, you would download the image.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as myCollectionViewCell
let cellNumber = indexPath.row as Int
//downloadimghere
cell.myButton.setBackgroundImage(downloadedImg, forState: .Normal)
return cell
}
This would download image everytime cell is being created. For more info you should checkout "lazy image loading". I think this is a better approach to your problem.
Now to your code, first of all you are not using your buttonList array, everytime cell is being created you create a new button and place it there, so you are not reusing already created buttons. If you fixed this, it might work like you wanted.
Here is another problem, since collectionview is reusing cells, everytime you create a button and place it on cell, it stays there, so basically now you are creating button on button. So if you want this to work correctly and have only one button on your cell, you need to remove previous button from the cell before you create it, you can do this in cellForItemAtIndexPath.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as myCollectionViewCell
//something like this
for view in cell.subviews(){
if view == <UIButton>{
view.removeFromSuperview()
}
}
return cell
}
There might be some syntax errors in my code, I didnt test it, but you get the idea how to do it.

Resources