UICollectionView dataSource and delegate cause termination of app [duplicate] - swift

This question already has answers here:
unrecognized selector sent to instance 0x8c9a6d0
(4 answers)
Closed 3 years ago.
I'm currently studying swift CollectionView, I followed each step of the tutorial but somehow my app ends up being terminated with this error
Disconnect the dateSource and delegate seems can stop it from crashing but it ends up having nothing in the collection view. Any hints would be appreciated:)
this is my ViewController:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
struct Item {
var title:String
var Image:String
var price:Double
}
let items:[Item] = [Item(title: "Mario Kart", Image: "Mario", price: 5), Item(title: "Car", Image: "Car", price: 6), Item(title: "Doll", Image: "Doll", price: 3)]
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.myImage.image = UIImage(named: items[indexPath.row].Image)
cell.myPrice.text = String(items[indexPath.row].price)
cell.myTitle.text = String(items[indexPath.row].title)
return cell
}
}
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myImage: UIImageView!
#IBOutlet weak var myPrice: UILabel!
#IBOutlet weak var myTitle: UILabel!
}

Please make an IBOutlet connection to collectionView.
Then add the
collectionView.delegate=self
collectionView.datasource=self
lines inside viewDidLoad method.

Related

Swift UITableView / UICollectionVIew passing data to cell in cellForRowAt returns nil

I am trying to pass a struct from collection view to its cells in cellForRowAt method. I also tried using table view but it produces the same bug.
Collection view class code:
class PaintingListCollectionViewController: UICollectionViewController {
var allPaintings: CategorySection!
override func viewDidLoad() {
super.viewDidLoad()
...
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allPaintings.paintings.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "paintingCell", for: indexPath) as? PaintingCollectionViewCell
// works perfectly
cell?.paintingTitleLabel.text = allPaintings.paintings[indexPath.item].title
cell?.paintingImageView.image = UIImage(named: allPaintings.paintings[indexPath.item].title)
cell?.paintingData = allPaintings!.paintings[indexPath.item]
// prints out two valid data
print(cell?.paintingData, allPaintings!.paintings[indexPath.item])
return cell!
}
}
Cell class code:
class PaintingTableViewCell: UITableViewCell {
#IBOutlet weak var paintingImageView: UIImageView!
#IBOutlet weak var paintingNameLabel: UILabel!
var paintingData: Painting?
override func awakeFromNib() {
super.awakeFromNib()
// always returns nil, despite printing out the exact same thing in cellForItemAt returns a valid result.
print(paintingData)
}
}
I am using following structs:
struct CategorySection {
var title: String
var image: String
var color: UIColor
var paintings: [Painting]
}
struct Painting {
var title: String
var size: String
var year: String
var text: String
}
What I found is that if I pass data directly to the cell's #IBOutlet UI properties, it works. But when I am passing data to an optional variable in UITableViewCell class, the result is always nil. This problem happens if I use UITableView too. Also, I need to mention that var allPaintings: CategorySection! in TableView class is also passed from another table view controller using the exact same way as described here. So the data passing occurring here is already the second time of the chain.
It is a very weird bug... I will be very thankful if anyone can help me!
EDITED: The original problem description uses UITable view instead of collection view, and sections instead of row for datasource. I made these changes for debugging purposes.
If you put a breakpoint on the print statements in awakeFromNib and in cellForRowAt, you will see that awakeFromNib gets called before you put the data into the collectionViewCell. In fact it gets called by collectionView.dequeueReusableCell(). You want to use UICollectionViewCell.didMoveToSuperview()
class PaintingCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var paintingImageView: UIImageView!
#IBOutlet weak var paintingNameLabel: UILabel!
var paintingData: Painting?
override func awakeFromNib() {
super.awakeFromNib()
// always returns nil, despite printing out the exact same thing in cellForItemAt returns a valid result.
print("Awake From Nib", paintingData)
}
override func didMoveToSuperview() {
print ("did Move To Superview", paintingData)
}
}
I added a guard to error check the cellForRowAt, if for some reason it fails to create the cell, the final line of the function will crash
return cell!
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "paintingCell", for: indexPath) as? PaintingCollectionViewCell else {
fatalError("unable to dequeue cell")
}
// works perfectly
cell.paintingNameLabel?.text = allPaintings.paintings[indexPath.item].title
cell.paintingImageView.image = UIImage(named: allPaintings.paintings[indexPath.item].title)
cell.paintingData = allPaintings!.paintings[indexPath.item]
// prints out two valid data
print("cellForItemAt ",cell.paintingData ?? "nothing to see here", allPaintings!.paintings[indexPath.item])
return cell
}

Cannot get CollectionViewCells to work in SWIFT

I have tried this numerous ways but I am unable to get it to work. I am trying to add a simple image and two text labels to a collection view cell and when the build completes the tableview is just blank. I am brand new too SWIFT but I am pretty sure I am missing something simple. What am I doing wrong?
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate{
let locationName = ["Hawaii Resort", "Mountain Expedition", "Scuba Diving"]
let locationImage = [UIImage(named: "img0"), UIImage(named: "img1"), UIImage(named: "img2")]
let locationDescription = ["Lorem Ipsum 0", "Lorem Ipsum 1", "Lorem Ipsum 2"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return locationName.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.locationName.text = locationName[indexPath.row]
cell.locationImage.image = locationImage[indexPath.row]
cell.locationDescription.text = locationDescription[indexPath.row]
return cell
}
}
CollectionViewCell
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var locationImage: UIImageView!
#IBOutlet weak var locationName: UILabel!
#IBOutlet weak var locationDescription: UILabel!
}
You need to connect the collectionView to the ViewController.
Beneath locationDescription add:
#IBOutlet weak var collectionView: UICollectionView!
Then connect it on the storyboard.
Then within the viewDidLoad,set the delegate and data source to self
self.collectionView.delegate = self
self.collectionView.datasource = self

Implement delegate method in Table View

IĀ“have a table view with a custom cell. In this cell I have two buttons that are used for counting. One pluss button, and one minus button. These buttons should increment and decrease a counterLabel with 1.
I have done this in a project I did a long time ago, but then I used extensions, witch I don't do in this project. My problem is that I cannot figure out how to implement the delegate method that I used in the previous project.
I will show the code I'm trying, as an extension. This gives the error "Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'".
How should I correctly implement the delegate?
Main View Controller
import UIKit
class MainViewController: UIViewController, UITableViewDelegate {
let staticList: [ListItem] =
[
ListItem(item1: "item1", item2: "item2", item3: "item3")]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return staticList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
cell = tableView.dequeueReusableCell(withIdentifier: "MenuCell", for: indexPath)
if let customCell = cell as? MenuCell
{
let itemIndex = indexPath.row
let listItem = staticList[itemIndex]
customCell.item1Label.text = listItem.item1
customCell.item2Label.text = listItem.item2
customCell.item3Label.text = listItem.item3
customCell.counterLabel.text = "\(listItem.count)"
}
return cell
}
}
#IBOutlet weak var mainTableView: UITableView!
}
extension MainViewController: CountDelegate {
func didAddItemCell(cell: MainCell) {
let indexPath = tableView.indexPath(for: cell)!
staticList[indexPath.row].count += 1
cell.countLabel.text = "\(staticList[indexPath.row].count)"
}
func didSubtractItemCell(cell: MenuCell) {
let indexPath = tableView.indexPath(for: cell)!
if staticList[indexPath.row].count > 0 {
staticList[indexPath.row].count -= 1
cell.countLabel.text = "\(staticList[indexPath.row].count)"
}
}
}
Table View Cell file
import Foundation
import UIKit
protocol CountDelegate: NSObjectProtocol {
func didAddItemCell(cell: MainCell)
func didSubtractItemCell(cell: MainCell)
}
class MenuCell : UITableViewCell
{
#IBOutlet weak var item1Label: UILabel!
#IBOutlet weak var item2Label: UILabel!
#IBOutlet weak var item3Label: UILabel!
#IBOutlet weak var counterLabel: UILabel!
#IBOutlet weak var minusButton: UIButton!
#IBOutlet weak var plusButton: UIButton!
weak var delegate: CountDelegate?
#IBAction func minusButton(_ sender: UIButton)
{
delegate?.didAddItemCell(cell: self)
}
#IBAction func plusButton(_ sender: UIButton)
{
delegate?.didSubtractItemCell(cell: self)
}
}
The numberOfRowsInSection and cellForRowAt methods are not part of the UITableViewDelegate protocol but the UITableViewDataSource protocol. You therefore need to change add that protocol like this:
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
You will also need to implement this as well:
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // Or whatever it should be
}

IBOutlet pageControl is invalid

I trying to add pageControl in viewController for display current page and total pages.
I have collectionView inside viewController. But if i add #IBOutlet in viewController i get the error:
"The pageControl outlet from the DetailViewController to the UIPageControl is invalid. Outlet cannot be connected to repeating content."
That means repeating content?
I understand that this question has been asked many times, but I tried what was suggested but nothing helped.
PageControl in main.storyboard have only one #IBOutlet and it throws an error...
If need my code, he is here:
class DetailViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITableViewDelegate, UITableViewDataSource {
// MARK: - IBOutlet's
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var pageControl: UIPageControl!
var hallImages: Hall?
var currentPage = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
tableView.estimatedRowHeight = 626
tableView.rowHeight = UITableViewAutomaticDimension
}
// MARK: - CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let imagesHall = hallImages?.ImagesHalls.count else {
return 0
}
return imagesHall
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! DetailCollectionViewCell
if let imagesHalls = hallImages?.ImagesHalls[indexPath.item] {
cell.imageView.sd_setImage(with: URL(string: imagesHalls))
}
return cell
}
}
How i can add pageControll in collectionView or collectionCell (i don't know how is better)?
I had the same problem. It is because your page control is a subview of the cell. You cannot do this.
take it out of your cell
put it in your main view/ safe area

Trouble added cell to UICollectionView

Trying to add a cell to my ViewController, and I'm having trouble. I'm wondering if I have all my delegates. Currently, I can change the color of the collection view's background, but can't add any cells.
I'm using an array of text from my Listing() object to drive the collection view cells. On viewDidLoad the length is 4. I envisioned using this count for the numberofItemsInSection, however, if I put the print statement there it is never printed. Which makes me think the wrong delegate is called and the method is never hit. Correct?
import UIKit
class ListingDetailViewController: UIViewController,
UICollectionViewDelegateFlowLayout {
var listing = Listing()
// #IBOutlet var image: UIImageView!
#IBOutlet var post: UITextView!
#IBOutlet var price: UILabel!
#IBOutlet var amenitiesCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationItem.title = listing.title
self.amenitiesCollection!.backgroundColor = UIColor.gray
print(listing.amenities.count)//printed 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
print(listing.amenities.count)
return listing.amenities.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let identifier = "UICollectionViewCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
return cell
}
}
Make sure you have linked the CollectionView delegate and datasource to your view controller.
In viewDidLoad() add:
self.amenitiesCollection.delegate = self
self.amenitiesCollection.dataSource = self
==================================
EDIT:
If you have already linked the delegate and dataSource, try reloading the collection view in viewDidAppear(_:)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.amenitiesCollection.reloadData()
}