UICollectionViewCell populate with wrong data - ios

I have simple UICollectionViewCell that contains one product. My product have image(UIImageView), name(UILabel), description(UILabel), price(UILabel), rating(UIStackView) shown with 0 to 5 stars and buy button.
I fetch my products from JSON and save it to Array of products.
After app launch my UICollectionView populate with no problems. I use:
var product: Product? {
didSet {
populateProduct()
}
}
to populate data in Cell..
private func populateProduct() {
guard let product = product else { return }
let viewModel = ProductViewModel(product: product)
productImageView.loadImage(image: product.imageString)
productBrand.text = product.brand
productName.text = product.name
productDescription.text = product.description
productPrice.text = viewModel.price
if productRating.arrangedSubviews.count != 5 { // Check if there is rating already
for star in 1...5 {
let stars = product.score
if stars != 0 { // Doesn't show rating if there is none
var starImage = UIImageView(image: UIImage(named: "star-pink"))
if star > stars {
starImage = UIImageView(image: UIImage(named: "star-grey"))
}
starImage.contentMode = .scaleAspectFit
productRating.addArrangedSubview(starImage)
}
}
}
}
Problem is when I scroll collection view and some of my cells are dequed and reused. It doesn't happen every time, but often happens, that my cells swaps a data of products. Specifically the rating and the picture - so for example my first product have his own data, but rating of second product.
I think I fixed the image problem by this code in Cell:
override func prepareForReuse() {
super.prepareForReuse()
productImageView.image = nil
}
But I don't found how to reload arrangedSubviews in rating StackView or reload whole UIStackView same as just image in UIImageView. Or is there another solution to avoid it?
CellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ProductCell
cell.product = products[indexPath.row]
return cell
}
And why cells swaps just ratingView?

Remove all subview from stackView
override func prepareForReuse() {
super.prepareForReuse()
productImageView.image = nil
_ = productRating.arrangedSubviews.map { $0.removeFromSuperview() }
// or
productRating.arrangedSubviews.forEach { subview in
subview.removeFromSuperview()
}
}

Related

Tableview need to be reloaded twice to update the data from textfield?

I have question about the tableView.
Here is my tableView code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tierCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.interestRateTextField.delegate = self
cell.rowLabel.text = "\(indexPath.row + 1)."
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
interestRateArray[indexPath.row] = interest
} else {
interestRateArray[indexPath.row] = nil
}
} else {
interestRateArray[indexPath.row] = nil
}
return cell
}
As you can see, I have the cellForRowAt method to get the value from the textfields in the cell, and assign to my arrays. (I actually have 2 textfields per cell.)
Basically, I let the users input and edit the textfield until they are happy then click this calculate button, which will call the calculation method. In the calculation method I call the "tableView.reloadData()" first to gather data from the textfields before proceed with the actual calculation.
The problem was when I ran the app. I typed values in all the textfields then clicked "calculate", but it showed error like the textfields were still empty. I clicked again, and it worked. It's like I had to reload twice to get things going.
Can anyone help me out?
By the way, please excuse my English. I'm not from the country that speak English.
edited: It may be useful to post the calculate button code here as someone suggested. So, here is the code of calculate button
#IBAction func calculateRepayment(_ sender: UIButton) {
//Reload data to get the lastest interest rate and duration values
DispatchQueue.main.async {
self.interestRateTableView.reloadData()
}
//Get the loan value from the text field
if let loanText = loanTextField.text {
if let loanValue = Double(loanText) {
loan = loanValue
} else {
print("Can not convert loan value to type Double.")
return
}
} else {
print("Loan value is nil")
return
}
tiers = []
var index = 0
var tier: Tier
for _ in 0..<tierCount {
if let interestRateValue = interestRateArray[index] {
if let durationValue = durationArrayInMonth[index] {
tier = Tier(interestRateInYear: interestRateValue, tierInMonth: durationValue)
tiers.append(tier)
index += 1
} else {
print("Duration array contain nil")
return
}
} else {
print("Interest rate array contain nil")
return
}
}
let calculator = Calculator()
repayment = calculator.calculateRepayment(tiers: tiers, loan: loan!)
if let repaymentValue = repayment {
repaymentLabel.text = "\(repaymentValue)"
totalRepaymentLabel.text = "\(repaymentValue * Double(termInYear!) * 12)"
} else {
repaymentLabel.text = "Error Calculating"
totalRepaymentLabel.text = ""
}
}
cellForRowAt is used for initially creating and configuring each cell, so the textfields are empty when this method is called.
UITableView.reloadData() documentation:
// Reloads everything from scratch. Redisplays visible rows. Note that this will cause any existing drop placeholder rows to be removed.
open func reloadData()
As it says in Apple's comment above, UITableView.reloadData() will reload everything from scratch. That includes your text fields.
There are a number of ways to fix your issue, but it's hard to say the best way without more context. Here's an example that would fit the current context of your code fairly closely:
class MyCustomTableViewCell: UITableViewCell {
#IBOutlet weak var interestRateTextField: UITextField!
var interestRateChangedHandler: (() -> ()) = nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
interestRateTextField.addTarget(self, action: #selector(interestRateChanged), for: UIControlEvents.editingChanged)
}
#objc
func interestRateChanged() {
interestRateChangedHandler?()
}
}
and cellForRowAtIndex:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.rowLabel.text = "\(indexPath.row + 1)."
cell.interestRateChangedHandler = { [weak self] in
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
self?.interestRateArray[indexPath.row] = interest
} else {
self?.interestRateArray[indexPath.row] = nil
}
} else {
self?.interestRateArray[indexPath.row] = nil
}
}
return cell
}

UICollectionView fast scrolling displays wrong images in the cell when new items are appended asynchronously

I have a UICollectionView which displays images in a grid but as I scroll rapidly it displays the wrong image in the cell momentarily until the image is downloaded from the S3 storage and then the correct image is displayed.
I have seen questions and answers relating to this problem on SO before but none of the solutions are working for me. The dictionary let items = [[String: Any]]() is filled after an API call. I need the cell to discard the image from the recycled cell. Right now there is an unpleasant image "dancing" effect.
Here is my code:
var searchResults = [[String: Any]]()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
let item = searchResults[indexPath.item]
cell.backgroundColor = .white
cell.itemLabel.text = item["title"] as? String
let imageUrl = item["img_url"] as! String
let url = URL(string: imageUrl)
let request = Request(url: url!)
cell.itemImageView.image = nil
Nuke.loadImage(with: request, into: cell.itemImageView)
return cell
}
class MyCollectionViewCell: UICollectionViewCell {
var itemImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.layer.cornerRadius = 0
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .white
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
---------
}
override func prepareForReuse() {
super.prepareForReuse()
itemImageView.image = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I changed my cell image loading to the following code:
cell.itemImageView.image = nil
APIManager.sharedInstance.myImageQuery(url: imageUrl) { (image) in
guard let cell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell
else { return }
cell.itemImageView.image = image
cell.activityIndicator.stopAnimating()
}
Here is my API manager.
struct APIManager {
static let sharedInstance = APIManager()
func myImageQuery(url: String, completionHandler: #escaping (UIImage?) -> ()) {
if let url = URL(string: url) {
Manager.shared.loadImage(with: url, token: nil) { // internal to Nuke
guard let image = $0.value as UIImage? else {
return completionHandler(nil)
}
completionHandler(image)
}
}
}
If the user scrolls past the content limit my collection view will load more items. This seems to be the root of the problem where cell reuse is reusing images. Other fields in the cell such as item title are also swapped as new items are loaded.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y + view.frame.size.height) > (scrollView.contentSize.height * 0.8) {
loadItems()
}
}
Here is my data loading function
func loadItems() {
if loadingMoreItems {
return
}
if noMoreData {
return
}
if(!Utility.isConnectedToNetwork()){
return
}
loadingMoreItems = true
currentPage = currentPage + 1
LoadingOverlay.shared.showOverlay(view: self.view)
APIManager.sharedInstance.getItems(itemURL, page: currentPage) { (result, error) -> () in
if error != nil {
}
else {
self.parseData(jsonData: result!)
}
self.loadingMoreItems = false
LoadingOverlay.shared.hideOverlayView()
}
}
func parseData(jsonData: [String: Any]) {
guard let items = jsonData["items"] as? [[String: Any]] else {
return
}
if items.count == 0 {
noMoreData = true
return
}
for item in items {
searchResults.append(item)
}
for index in 0..<self.searchResults.count {
let url = URL(string: self.searchResults[index]["img_url"] as! String)
let request = Request(url: url!)
self.preHeatedImages.append(request)
}
self.preheater.startPreheating(with: self.preHeatedImages)
DispatchQueue.main.async {
// appendCollectionView(numberOfItems: items.count)
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
When the collection view scrolls a cell out of bounds, the collection view may reuse the cell to display a different item. This is why you get cells from a method named dequeueReusableCell(withReuseIdentifier:for:).
You need to make sure, when the image is ready, that the image view is still supposed to display that item's image.
I recommend you change your Nuke.loadImage(with:into:) method to take a closure instead of an image view:
struct Nuke {
static func loadImage(with request: URLRequest, body: #escaping (UIImage) -> ()) {
// ...
}
}
That way, in collectionView(_:cellForItemAt:), you can load the image like this:
Nuke.loadImage(with: request) { [weak collectionView] (image) in
guard let cell = collectionView?.cellForItem(at: indexPath) as? MyCollectionViewCell
else { return }
cell.itemImageView.image = image
}
If the collection view is no longer displaying the item in any cell, the image will be discarded. If the collection view is displaying the item in any cell (even in a different cell), you'll store the image in the correct image view.
Try this:
Whenever the cell is reused, its prepareForReuse method is called. You can reset your cell here. In your case, you can set a default image in the image view here till the original image is downloaded.
override func prepareForReuse()
{
super.prepareForReuse()
self.imageView.image = UIImage(named: "DefaultImage"
}
#discardableResult
public func loadImage(with url: URL,
options: ImageLoadingOptions = ImageLoadingOptions.shared,
into view: ImageDisplayingView,
progress: ImageTask.ProgressHandler? = nil,
completion: ImageTask.Completion? = nil) -> ImageTask? {
return loadImage(with: ImageRequest(url: url), options: options, into: view, progress: progress, completion: completion)
}
all Nuke API's return an ImageTask when requesting unless the image was in the cache. Hold reference to this ImageTask if there is one. In the prepareForReuse function. call ImageTask.cancel() in it and set the imageTask to nil.
From the Nuke project page:
Nuke.loadImage(with:into:) method cancels previous outstanding request associated with the target. Nuke holds a weak reference to a target, when the target is deallocated the associated request gets cancelled automatically.
And as far as I can tell you are using their example code correctly:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.imageView.image = nil
Nuke.loadImage(with: url, into: cell.imageView)
...
}
You are explicitly setting the UIImageViews image to nil so there should actually be no way it could display an image until Nuke loads a new image into the target.
Also Nuke will cancel a previous request. I'm using Nuke myself and don't have these issues. Are you sure your "changed code" isn't the faulty piece here? For me everything seems to be correct and I don't see an issue why it shouldn't work.
One possible issue could be the reload of the whole collection view though. We should always try to provide the best user experience so if you alter the datasource of the collection view, try to use performBatchUpdates to animate the inserts/updates/moves/deletes.
A nice library which automatically can take care of this is Dwifft for example.

While fast scrolling collectionview the first row data duplicate in the last row

i wanted to make an application with a design that looks like the app store so i followed let's build that app tutorial on how to build it and the result was excellent, when i tested it on my device it seem that when i fast scroll the collection-view the data that's in the first row take place on the last row and when scroll fast up again, the data that supposed to be in the last row i found it in the first row and when i scroll left and right in the row when the cell go off the screen it re-update to the right data but when fast scroll up and down fast again the data between the first row and last row go crazy.
i added image to the case in the end of the code.i spent 5 days trying to fix this but no luck at all i tried alot of solution like reset the data in the cell to nil before reseting it and alot other u will find it in the code but no luck , I really appreciate any help you can provide, Thanks
*update
after #Joe Daniels answer all the data are staple and work fine except the images it still go crazy when fast scrolling but it return after 7/8 sec of stopping scrolling to the right image
first class that contain the vertical collectionview
i made the width of the cell equal the width of the view , i tried the cell.tag == index-path.row solution but it didn't work
class HomePageVC: UIViewController , UICollectionViewDataSource , UICollectionViewDelegate , UICollectionViewDelegateFlowLayout ,SWRevealViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mainProductsRow.delegate = self
mainProductsRow.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.count {
return count + 1
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LargeHomeCategoriesCell.identifier, for: indexPath) as! LargeHomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.productCategories = productCategory
cell.seeMore.addTarget(self, action: #selector(self.seeMoreCat(_:)), for: UIControlEvents.touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RowCell", for: indexPath) as! HomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.seeMore.tag = (indexPath.row - 1 )
cell.seeMore.addTarget(self, action: #selector(self.seeMore(_:)), for: UIControlEvents.touchUpInside)
// cell.tag = indexPath.row - 1
cell.productCategory = nil
//if cell.tag == indexPath.row - 1{
cell.productCategory = productCategory?[indexPath.row - 1 ]
// }
return cell
}
**Second Class that contain the horizontal collectionview that is in the first collectionview cell **
in the cell-for-item-At-index-Path i printed the index-path.row when the view-load it printed 0 , 1 ,2 and didn't print the last index '3' and the same thing happen again when i scroll to the end of the collection view
class HomeCategoriesCell: UICollectionViewCell , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout , UICollectionViewDelegate{
#IBOutlet weak var seeMore: UIButton!
#IBOutlet weak var categorytitle: UILabel!
#IBOutlet weak var productsCollectionView: UICollectionView!
let favFuncsClass = FavItemsFunctionality()
let onCartFuncsClass = OnCartFunctionality()
var productCategory : ProductCategories? {
didSet {
if let categoryTitle = productCategory?.name {
categorytitle.text = categoryTitle
}
}
override func awakeFromNib() {
recivedNotification()
productsCollectionView.delegate = self
productsCollectionView.dataSource = self
productsCollectionView.backgroundColor = UIColor.clear
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.products?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(indexPath.row)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HProductCell", for: indexPath) as! HomeProductCell
cell.tag = indexPath.row
cell.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
cell.productTitle.text = nil
cell.productPrice.text = nil
cell.discountLabel.text = nil
cell.preDiscountedPrice.text = nil
cell.favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
cell.addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
cell.configCell(products: nil)
if cell.tag == indexPath.row {
cell.configCell(products: productCategory?.products?[indexPath.item])
}
cell.catNum = indexPath.row
return cell
}
// Thanks to Joe Daniels i added this code and my nightmare become way less scary :P
override func prepareForReuse() {
super.prepareForReuse()
productsCollectionView.reloadData()
}
}
Product Cell
i tried the prepare-For-Reuse() and reseted all the data to nil but still didn't work
class HomeProductCell: UICollectionViewCell {
func configCell(products :productDetails?){
if let title = products?.name {
self.productTitle.text = title
}else { self.productTitle.text = nil}
if let price = products?.price {
self.productPrice.text = "\(price)"
}else { self.productPrice.text = nil }
}
override func prepareForReuse() {
super.prepareForReuse()
self.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
productTitle.text = nil
productPrice.text = nil
discountLabel.text = nil
preDiscountedPrice.text = nil
favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
}
i took a screen shoots of the case you can find it here
i Found the Perfect Solution that worked for me as magic after 15 days of suffering xD i used this library (https://github.com/rs/SDWebImage).
To use it in swift put this line in your bridging Header
#import <SDWebImage/UIImageView+WebCache.h>
And in the collectionViewCell i used this line
self.productImage.sd_setImage(with: myUrl , placeholderImage: UIImage(named:"yourPlaceHolderImage")
The inner collectionView has no way of knowing that it went off screen. do a reload in prepareForReuse

Show number of items selected in UICollectionView

I am using UICollectionView to show the images. From that collectionView users can choose multiple images. Right now I am able to change view of selected images by overriding selected variable of UICollectionViewCell.
override var selected: Bool {
didSet {
if self.selected {
imageView.layer.borderWidth = 5.0
} else {
imageView.layer.borderWidth = 0.0
}
}
}
I put a UILabel at the top of UIImageView of cell to show the count. But I am not able to update the number of selected images. The count should be updated when user deSelect the image. How can i achieve this? It should be similar to Facebook image picker.I do not want to reload collectionView.
As you clarified in your comments, you want the number in the cell to show the order of the selected image.
Add a singleton class to hold an array of selected image IDs. The characteristic of a singleton is that its instance is accessible from everywhere in your app:
class ImageSelectionManager {
/// The singleton instance
static let instance = ImageSelectionManager()
/// This is the array that holds the order of selected image IDs
var selectedImageIds: [String] = []
}
In your UICollectionViewController implement the following delegate functions:
class YourViewController: UICollectionViewController {
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// Get the selected cell
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! YourCell
// Get the selected image ID
let imageId = cell.imageId
// Add ID
ImageSelectionManager.instance.selectedImageIds.append(imageId)
// Notify cells that image array has changed
NSNotificationCenter.defaultCenter().postNotificationName(
"ImageSelectionChangedNotification",
object: self)
}
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
// Get the deselected cell
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! YourCell
// Get the deselected image ID
let imageId = cell.imageId
// Remove ID
ImageSelectionManager.instance.selectedImageIds = ImageSelectionManager.instance.selectedImageIds.filter({ $0 != imageId })
// Notify cells that image array has changed
NSNotificationCenter.defaultCenter().postNotificationName(
"ImageSelectionChangedNotification",
object: self)
}
}
In your UICollectionViewCell add an observer to notifications and a function to update the counter label:
class YourCell: UICollectionViewCell {
/// The ID of your image that the cell is showing
var imageId: String!
/// The counter label
var counterLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Start listening to notifications
registerForNotifications()
}
override func prepareForReuse() {
super.prepareForReuse()
counterLabel.text = nil
}
deinit {
unregisterFromNotifications()
}
func registerForNotifications() {
// Make the cell listen to changes in the selected images array
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(self.handleNotification(_:)),
name: "ImageSelectionChangedNotification",
object: nil)
}
func unregisterFromNotifications() {
// Stop listening to notifications
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func handleNotification(notification: NSNotification) {
// If the notification is the one we are listening for
if notification.name == "ImageSelectionChangedNotification" {
// Execute on main thread because you are changing a UI element
dispatch_async(dispatch_get_main_queue()) {
// Get the position in the selected images array
if let position = ImageSelectionManager.instance.selectedImageIds.indexOf({ $0 == self.imageId }) {
// Image position was found so set the label text
self.counterLabel.text = String(position)
} else {
// Image was not found no remove the label text
self.counterLabel.text = nil
}
}
}
}
}
It works like this:
A cell is selected / deselected.
The cell's image ID is added / removed from the singleton's array.
A notification is sent to the cells.
Each cell receives a notification and checks the position of its image ID in the singleton's array.
If the image ID has a position in the array, the label text is updated with the position. If the image ID is not found that means the cell is not selected so the label text is removed.
First you will need a model class to store the selection status and the other attributes.
For example you are populating data for an Image in your collectionView.
class MyImage : NSObject {
var name : String?
var selectionNumber : Int? // Using for selection update on UI
/*other properties below*/
}
Now in your Collection View Cell make a MyImage property with hooked didSet
var image : MyImage? {
didSet {
imageView.image = UIImage(named:image.name)
if image.selectionNumber != nil {
imageView.layer.borderWidth = 5.0
lblCount.text = String(selectionNumber + 1)
}
else {
imageView.layer.borderWidth = 0.0
lblCount.text = ""
}
}
Now In your view controller class or the class where you are implementing collectionView's delegate methods.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(kYour_Cell_ID, forIndexPath: indexPath) as! YourCustomCell
let myImage = self.dataSource[indexPath.row]
myImage.selectionNumber = self.selectedIndexes.contains(indexPath)
cell.image = myImage
return cell
}
Where your dataSource will be the array containing MyImage objects and create an array for storing selected indexPaths.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
let idx = self.selectedIndexes.indexOf(image)
if (idx != nil) {
self.selectedIndexes.remove(indexPath.row)
}
else
{
self.selectedIndexes.append(indexPath.row)
}
self.collectionView.reloadItemsAtIndexPaths([self.selectedIndexes])
}

Swift / Array of Images in UIImage only last Image is displayed multiple times

I have the following code:
var posts = [EventPosts]() {
didSet {
eventsCollectionView.reloadData()
}
}
//MARK:PLACEHOLDER IMAGES
var eventImagesPlaceholder: [UIImage] = [
UIImage(named: "wildstyle.jpg")!,
UIImage(named: "geilesleben.jpg")!]
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
}
return cell
}
If I turn the internet on, four images from the desired source will be parsed and correctly displayed. If I turn the internet off, just the last placeholder image will be displayed 4 times. If I set wildstyle.jpg last, it is displayed four times. If I set geilesleben.jpg last, only that one is.
How can I display BOTH placeholder images. optimally only one time each.
Help is very appreciated.
You could use this, the two placeholder images are displayed in all cells alternately.
If you add more placeholder images to the array, the number of items will be considered automatically.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
cell.eventsImageView.image = eventImagesPlaceholder[indexPath.row % eventImagesPlaceholder.count]
}
return cell
}
In your code always the last image is displayed because of the repeat loop which assigns all images to the same image view and keeps the last image.
Add
override func prepareForReuse() {
self.eventsImageView.image = nil
}
in your cell's class
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
change this loop statment to like this
let element = eventImagesPlaceholder.objectAtIndex(indexPath.row) as! UIImage
cell.eventsImageView.image = element
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
This iterates for every cell you have. Basically every cell gets every image and keeps the last. pretty straight forward
What you want to do is sth like this:
cell.eventsImageView.image = eventImagesPlaceholder[someKindOfIndex]
Instead of that for loop.
Like when you know you have 4 cells and 4 images in your placeholder variable, just take indexPath.row as "someKindOfIndex"

Resources