The STRANGEST error in Swift Collection View - ios

Im facing the strangest problem i have experienced using Swift, Im developing a game and im using a collection view cell to show the different add ons that ill have, like clothes and stuff, however, im loading the image names that every collectionviewcell is going to have within an String List, The problem appears when I want to get a check image inside the selected "thing" when i click in one cell, and if i click in other cell, the check goes to that by first deselecting all the cells, everything works fine! but when i put more than 8 items in the String list the following error appears "fatal error: unexpectedly found nil while unwrapping an Optional value" its weird because the items are shown in the collection view fine but the problem appears when i click in one of them, with more than 8 items I repeat.
This is my view controller:
//
// WeaponSelectorViewController.swift
// swiftSpriteAwesomeProject
//
// Created by Mauri C. on 3/2/15.
// Copyright (c) 2015 WANT. All rights reserved.
//
import UIKit
class WeaponSelectorViewController: UIViewController {
var images = [NSString]()
let defaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
images = ["chancla", "chancle", "chancle2", "chancle3", "chancle4", "chancle5", "chancle7"]
images.append("chancla")
images.append("chancla")
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as WeaponCollectionViewCell
cell.checkImageView.hidden = true
if(defaults.stringForKey("chancla") != nil){
if(defaults.stringForKey("chancla")! == images[indexPath.row]){
cell.checkImageView.hidden = false
cell.checkImageView.image = UIImage(named: "check")
}
}
cell.imageView.image = UIImage(named:images[indexPath.row])
cell.imageView.contentMode = UIViewContentMode.ScaleAspectFit
return cell
}
// Uncomment this method to specify if the specified item should be selected
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
NSLog("\(indexPath)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as WeaponCollectionViewCell
NSLog("\(self.images.count)")
for var i = 0; i < self.images.count; i++ {
var celli = collectionView.cellForItemAtIndexPath(NSIndexPath(forItem: i, inSection: 0)) as WeaponCollectionViewCell
celli.checkImageView.hidden = true
}
if(cell.checkImageView.hidden){
cell.checkImageView.hidden = false
cell.checkImageView.image = UIImage(named: "check")
defaults.setObject(images[indexPath.row], forKey: "chancla")
} else {
cell.checkImageView.hidden = true
}
return true
}
}

Related

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

Swift: Update CollectionView Cell Image

Im making a game that has a collectionView character select at the bottom of the GameScene. Many of the characters are locked, so i display a "Locked" image as the collection view cell image. However, when the user unlocks the character and they load the GameScene again(move to gameOver scene, restart the game back to present GameScene), i need to update the collectionView cells images and swap out the "Locked" image with the regular image. Ive tried so many different things but it isn't working. It won't swap out/reload the new images. I know its just something small I'm missing but heres all my code associated with the collection view.
class CharactersCollectionViewCell: UICollectionViewCell {
//MARK : Public API
var characters : NACharacters!{
didSet{
updateUI()
}
}
//MARK : Private
#IBOutlet weak var featuredImageView: UIImageView!
private func updateUI(){
self.layer.borderColor = UIColor.whiteColor().CGColor
self.layer.borderWidth = 2.5
self.layer.masksToBounds = true
self.layer.cornerRadius = CGFloat(roundf(Float(self.frame.size.width/2.0)))
featuredImageView.image = characters.featuredImage
}
}
class NACharacters {
var featuredImage : UIImage!
static var characterLineUp = [NACharacters]()
init(featuredImage: UIImage){
self.featuredImage = featuredImage
}
static func createCharacters() -> [NACharacters]{
print("Inside create character")
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "DiploSquadCollectionView")!), atIndex: 0)
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "LockedNBAPlayersCollectionView")!), atIndex: 1)
if(NSUserDefaults().unlockNBAPlayers == true){
characterLineUp[1].featuredImage = UIImage(named: "NBAPlayersCollectionView")!
}
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "LockedLeanSquadCollectionView")!), atIndex: 2)
if(NSUserDefaults().totalDabs >= 10000){
print("setting up lean to be unlocked")
characterLineUp[2].featuredImage = UIImage(named: "LeanSquadCollectionView")!
}
return characterLineUp
}
}
//Inside Game View Controller
#IBOutlet var collectionView: UICollectionView!
//MARK : Data Source
internal var characters = NACharacters.createCharacters()
extension GameViewController : UICollectionViewDataSource, UICollectionViewDelegate{
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return characters.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Storyboard.CellIdentifier, forIndexPath: indexPath) as! CharactersCollectionViewCell
cell.characters = self.characters[indexPath.item]
return cell
}
//Inside Game Scene
var collectionView: UICollectionView? {
return self.view?.subviews.filter{ $0 is UICollectionView }.first as? UICollectionView
}
override func didMoveToView(view: SKView) {
//INSIDE HERE I NEED TO UPDATE THE COLLECTION VIEW CELL IMAGES
}
I know this looks like a lot of code but most of it is pretty standard. I show you all of this so that we can pin point exactly whats wrong or what needs to be done to get this to work. This is all of my code associated with the collectionView.
Have you try this in same place ::
imageView.image = nil
then set the image you want to update as ..
imageView.image = UIImage(named : "YourImage")
When you need to 'refresh' a Collection View, tell it to reload its data, then all the images should update as needed.
collectionView.reloadData()

UICollectionView attempts to dequeue cells beyond data source bounds while reordering is in progress

I have implemented collection view with cell reordering
class SecondViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private var numbers: [[Int]] = [[], [], []]
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
for j in 0...2 {
for i in 0...5 {
numbers[j].append(i)
}
}
longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
self.collectionView.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .Began:
guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case .Changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case .Ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
extension SecondViewController: UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return numbers.count
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let number = numbers[section].count
print("numberOfItemsInSection \(section): \(number)")
return number
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
print("cellForItemAtIndexPath: {\(indexPath.section)-\(indexPath.item)}")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! TextCollectionViewCell
cell.textLabel.text = "\(numbers[indexPath.section][indexPath.item])"
return cell
}
func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let temp = numbers[sourceIndexPath.section].removeAtIndex(sourceIndexPath.item)
numbers[destinationIndexPath.section].insert(temp, atIndex: destinationIndexPath.item)
}
}
It works fine until I try to drag an item from section 0 to section 2 (which is off screen). When I drag the item to the bottom of collection view, it slowly starts scrolling down. At some point (when it scrolls past section 1) application crashes with fatal error: Index out of range because it attempts to request cell at index 6 in section 1 while there are only 6 items in this section. If I try to drag an item from section 1 to section 2, everything works fine.
Here's an example project reproducing this problem (credit to NSHint):
https://github.com/deville/uicollectionview-reordering
Is this a bug in framework or am I missing something? If it is the former, what would be the workaround?
I actually ran into this issue today. Ultimately the problem was in itemAtIndexPath - I was referencing the datasource to grab some properties for my cell : thus the source of the out of bounds crash. The fix for me was to keep a reference to the currently dragging cell and in itemAtIndexPath; check the section's datasource length versus the passed NSIndexPath and if out of bounds; refer to the dragging cell's property.
Likesuchas (in Obj-C ... haven't moved to Swift yet) :
NSArray* sectionData = [collectionViewData objectAtIndex:indexPath.section];
MyObject* object;
if (indexPath.row < [sectionData count]) {
object = [dataObject objectAtIndex:indexPath.row];
} else {
object = draggingCell.object;
}
[cell configureCell:object];
Not sure if this is similar to your exact issue; but it was mine as everything is working as expected now. :)

Showing Cells in UICollectionViewController

I have tried to make a UICollectionViewController where I can show a image for each cell. When I want to open this ViewController it shows me an error
import UIKit
private let reuseIdentifier = "Cell"
class RodelCollectionViewController: UICollectionViewController {
var personService: PersonService!
override func viewDidLoad() {
super.viewDidLoad()
assert(personService != nil, "Person Service has to be set, otherwise this class can't do anything useful.")
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personService.allPersons().count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
return cell
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let PersonDetailViewController = segue.destinationViewController as? PersonDetailViewController,
let person = (sender as? RodelCollectionViewCell)?.personView?.person {
PersonDetailViewController.person = person
}
}
This is the error
I have tried a lot to fix it but it allways shows me the same error. I don't know where I have to solve this
Did you assign the cell identifier ("PersonCollectionCell") to the cell in the xib file or in the storyboard?
I noticed you declared private let reuseIdentifier = "Cell" that you use to register the cell. But you are using a different reuseIdentifier "PersonCollectionCell" when dequeuing the cell.
Also,
I wouldn't recommend using a function personService.allPersons() inside:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
This method gets called every time a cell will be reuse/dequeued and could bring performance issues in the future. Instead I would save the result inside an array and update it every time something change and can affect what personService.allPersons() returns.
I would declared a lazy variable like this:
private lazy var allPersons: [WhateverTheTypeIs] = {
let allPersons = self.personService.allPersons()
return allPersons
}
and in the collectionView datasource methods use allPersons instead of the method itself.
Hope this helps.
Another problem which is found with your code is in the
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Here you are trying to register a default UICollectionViewCell and in the cellForItemAtIndexPath you are trying to check for the
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
Here in this code you are checking for your custom cell how this cell become custom cell
if you want to register and create your custom cell your should be like this:
At viewDidLoad()
self.collectionView!.registerClass(RodelCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
At cellForItemAtIndexPath
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! RodelCollectionViewCell
Default cell
If you want to keep the default cell your code will remain same as it's currently but it will not go inside the condition of custom cell the cell may be show empty if you don't do anything else in the cellforrow
Update
Put both of the code in the cellForItemAtIndexPath
To change cell background color
cell.contentView.backgroundColor = UIColor.redColor()
As person view is coming nil for now as testing purpose we can add a sample view
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.row]
}
cell.contentView.backgroundColor = UIColor.redColor()
let lbl = UILabel(frame:CGRectMake(0,0,100,21))
lbl.text = "\(indexPath.row)" //replace this value with your original value if it displays for the first time
cell.contentView.addSubview(lbl)
return cell
}

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