class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var liveModel = [LiveModel]()
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return liveModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = myCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
cell.imageViewer.image = UIImage(named: "\(liveModel[indexPath.row].image[0].src)")
return cell
}
}
struct LiveModel: Codable {
var id: Int
var name: String
var image: [Image]
}
struct Image: Codable {
var id: Int
var name: Int
var src: String
}
I have this model. I want to access "src" value from "Image" for collection view using indexpath. How can I do that?
I am fetching the images from API. I use model for that. I successfully access the value "id" & "name" from "LiveModel" but I can't understand how access the "src" value from "Image" model
Assuming you only want to show the first image, and assuming there is always at least 1 image, then your line:
cell.imageViewer.image = UIImage(named: "\(liveModel[indexPath.row].image[0].src)")
is close. Just remove the unnecessary string interpolation.
cell.imageViewer.image = UIImage(named: liveModel[indexPath.row].image[0].src)
This code will crash if the given liveModel record has no images. So if that is a possibility you should code accordingly.
Also note that with a UICollectionView you should use the item property of IndexPath. Use row for UITableView. Though effectively they are the same.
Here's the updated method with all of the changes.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = myCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
if let imageSrc = liveMode[indexPath.item].image.first?.src {
cell.imageViewer.image = UIImage(named: imageSrc)
}
return cell
}
Related
I am using collectionview in tableview cell,
i need to pass selected collectionview cells value to next viewcontroller, how?
code: here is the code for tableview and collectionview cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryNewTableCell", for: indexPath) as! CategoryNewTableCell
let indexData = self.activeCategories?[indexPath.row]
cell.selectionStyle = .none
cell.catNameLbl.text = indexData?.details?.first?.title
cell.subCategories = indexData?.sub_categories
cell.clcSeller.reloadData()
}
class CategoryNewTableCell: UITableViewCell,UICollectionViewDelegate,UICollectionViewDataSource{
#IBOutlet weak var clcSeller: UICollectionView!
public var subCategories : Array<Sub_categories>?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return subCategories?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubCatCollectionCell", for: indexPath) as! SubCatCollectionCell
let subCategory = self.subCategories?[indexPath.item]
cell.lblTitle.text = langType == .en ? subCategory?.details?.first?.title : subCategory?.details?[1].title
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = StoryBoard.main.instantiateViewController(withIdentifier: "SearchResultVC") as! SearchResultVC
vc.subCatId = sub_categories?[indexPath.row].slug ?? ""
self.navigationController?.pushViewController(vc, animated: true)
}
}
here if i use didSelectItemAt for collectionview to send its selected cell value to next view controller
error:
Value of type 'CategoryNewTableCell' has no member 'navigationController'
if i give button action in main class then able to push but value is not going
class CategoryNewVC: UIViewController {
#IBAction func didselectcollectionviewBTn(_ sender: UIButton) {
let vc = StoryBoard.main.instantiateViewController(withIdentifier: "SearchResultVC") as! SearchResultVC
vc.subCatId = //here how to pass value
self.navigationController?.pushViewController(vc, animated: true)
}
}
here how to pass selected collectionview cells value to SearchResultVC please do help
EDIT
according to below answer i have added: still didSelectItemAt not called, why plz do help
class CategoryNewTableCell: UITableViewCell,UICollectionViewDelegate,UICollectionViewDataSource{
override func awakeFromNib() {
super.awakeFromNib()
self.clcSeller.delegate = self
self.clcSeller.dataSource = self
//
You can use protocol to send data to your nextviewcontroller.
protocol CategorySelectionDelegate {
func get(category: Sub_categories)
}
Declare the delegate in your CategoryNewTableCell and use it in didSelectItemAt method of your collectionviewcell like below:
class CategoryNewTableCell:
UITableViewCell,UICollectionViewDelegate,UICollectionViewDataSource{
#IBOutlet weak var clcSeller: UICollectionView!
public var subCategories : Array<Sub_categories>?
var delegate: CategorySelectionDelegate?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return subCategories?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubCatCollectionCell", for: indexPath) as! SubCatCollectionCell
let subCategory = self.subCategories?[indexPath.item]
cell.lblTitle.text = langType == .en ? subCategory?.details?.first?.title : subCategory?.details?[1].title
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.get(category: sub_categories?[indexPath.row])
}
}
Adopt the protocol in the receiving class
class CategoryNewVC: UIViewController, CategorySelectionDelegate {
func get(category: Sub_categories) {
let vc = StoryBoard.main.instantiateViewController(withIdentifier: "SearchResultVC") as! SearchResultVC
vc.subCatId = category.slug ?? ""
self.navigationController?.pushViewController(vc, animated: true)
}
}
Declare a callback in the tableViewCell subclass.
class CategoryNewTableCell: UITableViewCell {
var onSelectSubcategory: ((_ subcategoryID: String) -> Void)?
}
Assign this callback in your cellForRow like this.
cell.subCategories = indexData?.sub_categories
cell.onSelectSubcategory = { [weak self] (subcategoryID) in
let vc = StoryBoard.main.instantiateViewController(withIdentifier: "SearchResultVC") as! SearchResultVC
vc.subCatId = subcategoryID
self?.navigationController?.pushViewController(vc, animated: true)
}
Invoke this callback from collectionView didSelectItem like this.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let subcategoryID = sub_categories?[indexPath.item].slug ?? ""
self.onSelectSubcategory?(subcategoryID)
}
this is screenshot(image) of my viewcontrollerI'm using collectionview and placed label in header, header and label created in storyboard
I want to change label text at runtime.
I know I can do it in viewForSupplementaryElementOfKind of collectionview but I want it in viewdidload method
my code is as below
Controller code
class DeleteViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! TestCollectionReusableView
headerView.labelText.text = "dummy" // this line shows dummy
return headerView
}
let testCollectionReusableView = TestCollectionReusableView()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(TestCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerId")
testCollectionReusableView.labelText.text = "Test"
// above line Xcode 12.4 shows error - **Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value**
}
}
Header Class File
class TestCollectionReusableView: UICollectionReusableView {
#IBOutlet weak var labelText: UILabel!
}
Probably the best place to do this is in your DeleteViewController assuming that this class holds your collection view and is a data source for it.
You can simply add a new property such as headerText: String which may then be user in your data source method. Whenever you change the text you should reload your collection view (or just the headers) for change to take effect.
So for instance
class DeleteViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet private var collectionView: UICollectionView?
private var currentHeaderText: String = "Dummy"
override func viewDidLoad() {
super.viewDidLoad()
changeHeaderText(to: "Some other text")
}
private func changeHeaderText(to text: String) {
currentHeaderText = text
collectionView?.reloadData()
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! TestCollectionReusableView
headerView.labelText.text = currentHeaderText
return headerView
}
}
A more optimized approach may be to detect which index paths need reloading and only reload those index paths. It would even be possible to use methods like collectionView?.visibleSupplementaryViews(ofKind: <#T##String#>) and loop through all visible views that correspond to your class to apply the change. But this is all up to you.
My CollectionViewCell imageView is nil
My Cell File is here
class MyTicketsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imgTicket: UIImageView!
}
My Controller File Code is here
class MyTicketsCollectionViewController: UICollectionViewController {
var photos: [String] = ["ticket1","ticket2","ticket3","ticket4"]
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.register(MyTicketsCollectionViewCell.self, forCellWithReuseIdentifier: "MyTicketsCollectionViewCell")
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyTicketsCollectionViewCell", for: indexPath) as! MyTicketsCollectionViewCell
cell.imgTicket.image = UIImage(named: photos[indexPath.row])
return cell
}
}
Error is "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
When I debug it then
cell.imgTicket is nil
How solve this issue?
Try changing your code to
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyTicketsCollectionViewCell", for: indexPath) as! MyTicketsCollectionViewCell
cell.imgTicket.image = UIImage(named: photos[indexPath.row])
return cell
}
You are dequeuing ActiveTicketsCollectionViewCell but the cell is registered under MyTicketsCollectionViewCell.
There is 2 possible solutions here...
1. Might be ActiveTicketsCollectionViewCell have not the property imgTicket or you mention a wrong cell class here as u have mentioned a different cell class name MyTicketsCollectionViewCell so try to write this code.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyTicketsCollectionViewCell", for: indexPath) as! MyTicketsCollectionViewCell
cell.imgTicket.image = UIImage(named: photos[indexPath.row])
return cell
2. Might be you have missed to connect your property (#IBOutlet weak var imgTicket: UIImageView!) with the Outlet (xib or storyboard).
I have a UICollection view of things. these things can have 3 states:
- Active
- Neutral
- Inactive
Now, here is the code for the UICollectionViewCell:
class NGSelectStashCell: UICollectionViewCell {
var status: String = "Active"
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var bgImage: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
func changeStatus()
{
switch status {
case "Active":
status = "Neutral"
//change bgImage
case "Neutral":
status = "Inactive"
//change bgImage
case "Inactive":
status = "Active"
//change bgImage
default:
print("No Status")
}
}
}
Now, when I declare the UICollection View, I want to make it so that when the user "clicks" on the UICell it will call out the changeStatus() function. How can I do this in the Delegate/DataSource code?. Also, how do I save the "status" of the each cell (so that if I refresh the UICollectionView they don't all return to "active" ?
/*
////////// UICOLLECTIONVIEW FUNCTIONS ///////////
*/
extension NewOnlineGameVC: UICollectionViewDelegate, UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return availableStashes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
stashCell.titleLabel.text = availableStashes[indexPath.row]
// stashCell.bgImage make image file with the same name as the name and change bg image to it.
return stashCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// code to toggle between active/neutral/inactive
// do I re-declare stashCell as! NGSelectStashCell? or what do I do?
}
}
Unfortunately the solution is a bit more complicated then you think. Collection views may queue and reuse their cells for performance gains. That means that a single cell may and will be used for multiple objects when scrolling. What will happen is that when you will change the state on first cell and will scroll so it is reused then this cell will preserve its state and will look as if another cell has this changed state...
So your source of truth must always be your data source. Whatever availableStashes contains it needs to also contain its state. So for instance if you currently have var availableStashes: [MyObject] = [] you can change it like this:
typealias MySource = (status: String, object: MyObject)
var availableStashes: [MySource] = []
func setNewObjects(objects: [MyObject]) {
availableStashes = objects.map { ("Neutral", $0) }
}
Now on press you need to update the object in your data source for instance:
func changeStatusOfObjectAtIndex(_ index: Int, to newStatus: String) {
availableStashes[index] = (newStatus, availableStashes[index].object)
}
So on press you do:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
changeStatusOfObjectAtIndex(indexPath.row, to: <#Your new status here#>)
UICollectionView().reloadItems(at: [indexPath])
}
This will now trigger a reload for this specific cell which you can now update like
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
stashCell.dataObject = availableStashes[indexPath.row]
return stashCell
}
And inside the cell:
var dataObject: NewOnlineGameVC.MySource {
didSet {
titleLabel.text = dataObject.object
switch dataObject.status {
case "Active":
//change bgImage
case "Neutral":
//change bgImage
case "Inactive":
//change bgImage
default:
print("No Status")
}
}
}
I hope this clears your issue.
You can change the status of the cell once you get the reference of the selected cell.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? NGSelectStashCell else {return}
cell.status = "Active"
cell.changeStatus()
}
If you want to save the status of the cell then it must be model driven i.e anything happens to the cell must be saved to the model and the same model have to be reflecte in the cell when collection view tries to reuse the previously instantiated cells.
You already have a model AvailableStash, lets use it in proper way.
struct AvailableStash {
var statusImage: UIImage?
var backgroundImage: UIImage?
var title: String?
var status: String
//Initilize properties properly
init(with status: String) {
self.status = status
}
}
Your collection view must be model driven. For eg:
class DemoCollectionView: UICollectionViewController {
var availableStashes: [AvailableStash]?
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return availableStashes?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let stashCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ngStashCell", for: indexPath) as! NGSelectStashCell
let item = availableStashes[indexPath.row]
stashCell.titleLabel.text = item
// stashCell.bgImage make image file with the same name as the name and change bg image to it.
stashCell.statusImage = item.statusImage
return stashCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? NGSelectStashCell else {return}
cell.status = availableStashes[indexPath.row].status
cell.changeStatus()
availableStashes[indexPath.row].status = cell.status
}
}
import UIKit
let reuseIdentifier = "MyCell"
class ViewController: UIViewController, UICollectionViewDataSource {
var myimage = UIImage(named: "1433584709_clock_ios7_ios_7")
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
cell.imageView.image = myimage
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.collectionViewLayout = CollectionViewLayout()
self.collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
// Do any additional setup after loading the view, typically from a nib.
}
}
I have this code to add so many cells with same image.
However, I want to make cells to have different pictures.
Please tell me how to do this.
Use array of images instead of var myImage because collection view or tableview need some form of list or array to store all items. The indexPath.row will return the index of the images the collectionview is looking for. i.e.
var images: [String] = ["image1", "image2"]
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
cell.imageView.image = UIImage(named: self.images[indexPath.row])
return cell
}
Also in function collectionView:numberOfItemsInSection:
return self.images.count
Here is the complete code assuming you have a custom CollectionViewCell class
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var images : [String] = ["image1", "image2"]
private let reuseIdentifier = "MyCell"
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
cell.imageView.image = UIImage(named: self.images[indexPath.row])
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.images.count
}
}
Where are your images coming from?
Suppose I have a 2D array of images like myImageArray[][], then change line:
cell.imageView.image = myImage
to
cell.imageView.image = myImageArray[indexPath.section][indexPath.row]