Have an issue where:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
is called when an item is selected, but I cannot change any cell properties from this method.
I created a new project with a stripped down UICollectionViewController to change the background color of a cell when selected. It also doesn't work. Here it is:
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.blue
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = self.collectionView(self.collectionView, cellForItemAt: indexPath)
cell.backgroundColor = UIColor.green
}
}
The only thing I did in Storyboard is delete the standard View Controller and replace it with a UICollectionViewController, create a subclass of UICollectionViewController and set the controller in the storyboard as that class.
Also, I can confirm that the cell's index path is returned from this method when called from inside the didSelectItemAt method:
self.collectionView.indexPathsForSelectedItems
You are using the wrong API. Never call the delegate method collectionView(_ cellForItemAt:, use cellForItem(at:
if let cell = collectionView.cellForItem(at: indexPath) {
cell.backgroundColor = UIColor.green
}
But be aware that this change is not persistent. When the user scrolls the color will change back to blue.
You can achieve that as below,
ViewController
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColoursViewCell", for: indexPath) as! ColoursViewCell
return cell
}
UICollectionViewCell
class ColoursViewCell: UICollectionViewCell {
#IBOutlet var photoImageView: UIImageView?
override var bounds: CGRect {
didSet {
self.layoutIfNeeded()
}
}
override var isSelected: Bool{
didSet{
if self.isSelected{
self.photoImageView?.backgroundColor = UIColor.random
}else{
self.photoImageView?.backgroundColor = UIColor.lightGray
}
}
}
}
you can have sample projects from this link I have in GitHub
https://github.com/hadanischal/RandomColors/tree/develop
Related
I am new to iOS and I want to implement a UICollectionView inside a UITableView which can have multiple select/unselect in section 1 of the UITableview. And in section 2, only a single selection is allowed. And save the selection sate event if I dismiss the Viewcontroller and when I open it again, it must show the last selected cell as highlight.
I searched for tutorials but all of them don't mention to select/unselect the state of collection cell or saving state after dismissing view controller.
Can someone help to implement it?
Thank in advance!
Here is my code I do till now:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
}
you guys can also check my project here:
https://mega.nz/#!xRs0EQyQ
try this and let me know if you have any problem or if this solved your problem.
var arrSelectedIndex:[IndexPath] = []// store this array either in database by api or in local
var clvData:[String] = []// your data array
//get the arrSelectedIndex from default in viewDidLoad before reloading the table and collection.
override func viewDidLoad() {
super.viewDidLoad()
if let myArray = UserDefaults.standard.array(forKey: "selectedArray") as? [IndexPath] {
arrSelectedIndex = myArray
} else {
arrSelectedIndex = []
}
}
// and save the arrSelectedIndex in viewWillDisappear method
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UserDefaults.standard.set(arrSelectedIndex, forKey: "selectedArray")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
if arrSelectedIndex.contains(indexPath) { // You need to check wether selected index array contain current index if yes then change the color
cell.backgroundColor = UIColor.red
}
else {
cell.backgroundColor = UIColor.white
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
if !arrSelectedIndex.contains(indexPath) {// if it does not contains the index then add it
arrSelectedIndex.append(indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
if let currIndex = arrSelectedIndex.firstIndex(of: indexPath) {// if it contains the index then delete from array
arrSelectedIndex.remove(at: currIndex)
}
}
In my match app Xcode project I have an error stating:
Use of unresolved identifier 'cell'.
My Code:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
#IBOutlet weak var collectionView: UICollectionView!
var model = CardModel()
var cardArray:[Card] = []
override func viewDidLoad() {
super.viewDidLoad()
//call the getCards method of the card model
cardArray = model.getCards()
collectionView.delegate = self
collectionView.dataSource = self
}
//mark: -UICollectionView Protocol Methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cardArray.count
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//get a cardCollectionViewcell object
let cell =
collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as! CardCollectionViewCell
//get card that
let card = cardArray[indexPath.row]
cell.setCard(card)
return
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
_ = collectionView.cellForItem(at: indexPath) as!CardCollectionViewCell
//flip the card
cell.flip() <--------- THIS IS THE ERROR
Once the error is fixed I am supposed to be able run a match app example on the fake iPhone. It would allow me to flip the cards on a click.
I think you forgot to set reusable identifier
I hope this will be resolve your issue.
To access that cell method you have to declare variable
let cell = collectionView.cellForItem(at: indexPath) as!CardCollectionViewCell
First of all you are misusing collectionView:willDisplay:forItemAt. Move everything into collectionView:cellForItemAt, replace return with return cell and delete willDisplay:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as! CardCollectionViewCell
//get card that
let card = cardArray[indexPath.row]
cell.setCard(card)
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { ... }
The error is pretty clear: You never declared cell is the scope of didSelectItemAt. Change
let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
so I want to set my button tittle like this with my array, but the result the button title didn't show my array
//viewcontroller
let menus = ["Topokki","Sundubu","Galbitang"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menus.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.menusButton.titleLabel?.text = menus[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
}
//collectionviewcell class
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var menusButton: UIButton!
}
Try:
cell.menusButton.setTitle(menus[indexPath.item], forState: .normal)
From docs about titleLabel of UIButton
To set the actual text of the label, use setTitle(_:for:) (button.titleLabel.text does not let you set the text)
So... you need to set title of your button by calling setTitle(_:for:)
cell.menusButton.setTitle(menus[indexPath.item], for: .normal)
I am using Swift4 and Xcode9.
I create my collectionView cells by using the delegate method below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
return cell
}
Then, I want to replace the image of selected cell with a "stop" image. To achieve this, I have tried to use didSelectItemAt method;
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (mediaPlayer?.isPlaying == true) {
// stop the player
mediaPlayer?.stopPlayingMedia()
} else {
// change the image and start playing
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell // CRASH
cell.mediaItemCellImageView.image = UIImage(named: "stop.png")
...
...
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
// stop playing
mediaPlayer?.stopPlayingMedia()
}
Now, when I select the cell which is already selected (hence the original image has already been replaced with "stop.png") the code above works; i.e. the original image is restored, which is done by stopPlayingMedia() method.
When I select a cell different than the currently selected one, the App crashes. I tried to move the didSelectItemAt call out of the delegate method, but it did not work.
Surprisingly, the logical check if collectionView.cellForItem(at: indexPath) is MediaItemCollectionViewCell succeeds when I select the currently selected cell, and fails when I select another cell.
I want to change the image of the cell, hence need to cast the variable type but it fails. Why does to cast fail when I select a different cell than the currently selected one?
Thanks
Edit:
I use collectionView.reloadData() to restore all the images to originals (I mentioned this in the OP - when stopPlayingMedia() is invoked). It appears that, if I add a private var index:IndexItem? to my ViewController class and update its value within didSelectItemAt (to save the last selected cell) and use the code below, the code does not crash
extension ViewController:MediaPlayerControllerDelegate {
// this delegate method is invoked when stopPlayingMedia() is called
func mediaPlayerControllerDidFinishPlaying() {
// restore the UI
// collectionView.reloadData() -> this causes the crash, instead, I only restore one cell
let cell = collectionView.cellForItem(at: index!) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: (index?.row)!)?.image
timeLabel.text = "00:00"
progressBar.progress = 0.0
}
}
I tried the following to create cell and changing the image inside cell on selection. Not crashing at all. Please check the implementation, It might help you.
import UIKit
class ViewController: UIViewController {
private var isPlayingModel = [false, false, false, false, false] // initially all are stop, not playing
#IBOutlet weak var collectionView: UICollectionView! {
didSet{
collectionView.delegate = self
collectionView.dataSource = self
}
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return isPlayingModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
isPlayingModel[indexPath.row] = !isPlayingModel[indexPath.row]
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: 60)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
}
class MediaItemCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageVw: UIImageView!
}
Check the implementation here.
You are not passing indexPath correctly to load a cell, that's why it is crashing.Edit your didSelectItemAt: method as follows:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = IndexPath(item: indexPath.item, section: indexPath.section)
let cell = collectionView.cellForItem(at: selectedCell) as! ACollectionViewCell
cell.imgView.image = UIImage(named: "3.jpg")
}
Collection view indexPath has element item, and not row
Try this and see: (let me know what does it print)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
if let cell = collectionView.cellForItem(at: indexPath) as? MediaItemCollectionViewCell {
if let cellImage = mediaPlayer?.item(at: indexPath.item)?.image { // Collection view indexPath has element `item`, and not `row`
cell.mediaItemCellImageView.image = cellImage
} else {
print("cellImage not found at indexPath - \(indexPath)")
}
} else {
print("collection view cell `MediaItemCollectionViewCell` not found at indexPath -\(indexPath) ")
}
// stop playing
mediaPlayer?.stopPlayingMedia()
}
I am using LBTAComponents pod
this is a pod that makes easier to work with UICollectionView
without any need to register anything and provides a super simple anchoring system ... and in the first project I've got two days ago I decided to use this framework
but now I have a problem in one of uicollectionviewCells I need another collectionview that I can fill with items then I need it to be scrollable horizontally
import LBTAComponents
import UIKit
class ProductCell: DatasourceCell {
let cellId = "cellid"
let collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .black
return cv
}()
override func setupViews() {
super.setupViews()
ProductCell.addSubview(collectionView)
collectionView.frame = frame
collectionView.register(UICollectionView.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
}
}
extension ProductCell : UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .white
return cell
}
}
datasourceCell is equal to UICollectionViewCell in this pod.
and now I am getting this ERROR :
instance member 'addsubview' cannot be used on type uiview did you use the value of this type instead?
could you please help me?
I tried to use self.addSubview(collectionView)
but I got another error enter image description here
You can simply use a UITableView with custom UITableViewCells containing a UICollectionView.
Example:
1. View Hierarchy
2. UIViewController containing UITableView
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
return tableView.dequeueReusableCell(withIdentifier: "tcell", for: indexPath) as! TableCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if indexPath.row == 0
{
return 120
}
else
{
return 150
}
}
}
3. Custom UITableViewCell containing UICollectionView
class TableCell: UITableViewCell, UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
return collectionView.dequeueReusableCell(withReuseIdentifier: "ccell", for: indexPath)
}
}
4. Output Screenshot
addsubview is an instance method, not a class method. So you should use:
self.addSubview(collectionView)
instead of:
ProductCell.addSubview(collectionView)