NSCollectionViewItem has nil outlets in itemForRepresentedObjectAtIndexPath - swift

I have an NSCollectionView that displays a bunch of items that are called "ImageCollectionViewItem"s. I have an ImageCollectionViewItem.xib file that solely has an NSView that covers the item. I want to be able to dynamically change what is in that view, but when I try to reference it, it is nil. I already checked questions like: outlets in UIViewController nil in viewdidload , that is not my problem, I linked it and I have the filled circle
Here is the viewcontroller extension:
extension ViewController : NSCollectionViewDataSource {
func collectionView(collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return titles.count
}
func collectionView(collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItemWithIdentifier("ImageCollectionViewItem", forIndexPath: indexPath) as! ImageCollectionViewItem
//This is what is nil:
let thisView = item.iconView
return item
}
}
And then here is the ImageViewCollectionItem code
class ImageCollectionViewItem: NSCollectionViewItem {
#IBOutlet weak var iconView: NSView!
var numItem: Int?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.whiteColor().CGColor
}
}

That is some odd stuff for sure in Cocoa world. But here is some flow which should help. Open your ImageCollectionViewItem.xib in Interface Builder and drag "Collection View Item" from Object Library, then select it and set your custom class name to ImageCollectionViewItem, then makeItemWithIdentifier should create non-nil item.
In my example I have Tile class:
class Tile: NSCollectionViewItem {
And here is how I create the tiles:
func collectionView(collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(collectionView: NSCollectionView,
itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItemWithIdentifier("Tile", forIndexPath: indexPath) as! Tile
return item
}
Here is the picture of IB:
If you follow these steps, your outlets will be connected upon creation. In my case I have 2 outlets:
class Tile: NSCollectionViewItem {
#IBOutlet weak var bigScrollView: ItemScrollView!
#IBOutlet weak var smallScrollView: ItemScrollView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func viewDidAppear() {
//
}
}
And they are both get connected as you can see on the picture:

Related

accessing struct massive in UITableViewCell

I've created an collection view with carName's there are 5 of them, after clicking for example Mercedes(one of the collection view's cell) I want to set label text its own values:carModel ( carName and carModel are both same struct properties ) in Table view which is already created by me, but I cant access carModel array
I tried for loop but it returns an error
for i in cars.carModel {
lbl.text = cars.carModel[i]
}
any solution will be appericated
// data source file
struct Cars {
let carName:String
let carModel:[String]
}
let cars:[Cars] = [
Cars(carName: "Mercedes", carModel: ["S Class","A Class", "B Class"]),
Cars(carName: "BMW", carModel: ["X5","X6","X7"]),
Cars(carName: "Ford", carModel: ["Fuison","Focus","Mustang"]),
Cars(carName: "Toyota", carModel: ["Camry", "Corolla"]),
Cars(carName: "Hyundai", carModel: ["Elantra"])
]
// table view cell file
class TableViewCell: UITableViewCell {
#IBOutlet var lbl: UILabel!
func configure(with cars:Cars){
lbl.text = cars.carName
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
// mainviewcontroller
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
//#IBOutlet weak var tableView
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cars.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell
cell?.setup(with: cars[indexPath.row])
return cell!
}
}
extension ViewController:UICollectionViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(identifier: "TableViewController") as? TableViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.configure(with: cars[indexPath.row])
return cell
}
}
Here's my simulator:
img1
img2
what I want:expected result img
When you defined your UITableViewDataSource you set the number of rows in function method to return a constant (5). In cellForRowAt function you setup the cell with a Cars object.
I think you are mixing what data should be use for table view's datasource.
For collection view data source you have to:
Number of rows in section function should return: cars.count
Cell for item at function must configure CollectionViewCell with a Cars instance
I think all you are missing is saving which cell is being selected in collection view to use this to supply correct data to table view.
In collection view delegate when user taps on a cell you can either save the selected indexPath or Cars instance, this will be use by table view delegate. Let's call it selectedCar.
Then for table view data source you have to:
Number of rows in section function should return: selectedCar.carModel.count
Cell for item at function must configure TableViewCell with a CarModel instance (which is a String)
I created a small project in Github as a sample, is working as expected. Check it out. Hope this helps you. Sample video in Youtube.
Note: Setting datasource and delegate for tableView or collectionView in ViewController is not the best way to organize code but for sample code is ok.

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
}

How can I get an array of Strings i.e.. [String] to show up in the custom collection view cell using storyboard

In the Storyboard:
The datasource and delegate are connected from the LFCollectionView to the LFCollectionViewController. The reuseIdentifier is set to LFCell in the CollectionViewCell. The label inside the CollectionViewCell is named cellLabel and has an outlet to my custom LFCollectionViewCell. I want the outlet cellLabel to display the elements in my array of strings lFContainerLabel within each cell container in the collection. I am missing something, or going about this all wrong. I do not have a nib. I don't know how to use those. Is a nib a must in this scenario?
import UIKit
var lFContainerLabel: [String] = []
private let reuseIdentifier = "LFCell"
class LFCollectionView: UICollectionView { }
class LFNavigationController: UINavigationController { }
class LFCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellLabel: UILabel!
}
class LFCollectionViewController: UICollectionViewController,UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(LFCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in LFCollectionViewController: UICollectionView) -> Int {
return 1
}
override func collectionView(_ LFCollectionViewController: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return lFContainerLabel.count
}
override func collectionView(_ LFCollectionViewController: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let lFCell = LFCollectionViewController.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as UICollectionViewCell!
var myIndex: Int = 0
lFCell?.backgroundColor = UIColor.black
lFCell?.accessibilityElements = lFContainerLabel
myIndex = indexPath.item
lFCell?.cellLabel?.text = lFContainerLabel[myIndex]
return lFCell!
}
}

collectionView's didSelectItemAtIndexPath not working [Swift]

I've been having this problem recently, and I couldn't seem to find a fix on any other similar questions. I have a collectionView which just display's colours (see below image) and I would like to store the cell's background colour in a variable to pass through an unwind segue, in order to set a background colour of another element. However, I can't seem to get this function to work as it has on previous projects and versions of this application and I was wondering if anyone knows what the problem might be ?
After pressing the 'add' button at the bottom, it unwinds and appends a new item to another collectionView, seen here:
I am able to get the name that the user has put in, however I have to use a default colour as I can't seem to get the didSelectItemAtIndexPath function to work.
Here's my code for the addViewController (first image)
import UIKit
import ChameleonFramework
class mondayViewController: UIViewController {
// collectionView
#IBOutlet var dayView: UICollectionView!
// data to display
var data = Day.mondayData()
// data from segues
var receivedName: String?
var receivedColour: UIColor?
override func viewDidLoad() {
super.viewDidLoad()
// set title
self.navigationController!.navigationBar.topItem!.title = "Monday"
// set datasource for collectionView
// datasource is self as the collectionViewDataSource methods are in the extension class below
dayView.dataSource = self
// make the navigationBar flat to flow onto the options tab
self.navigationController?.hidesNavigationBarHairline = true
}
#IBAction func unwindToMonday(segue: UIStoryboardSegue) {
if segue.identifier == "mondayUnwind" {
let subjectAdd = segue.sourceViewController as! mondayAddSubjectView
receivedName = subjectAdd.nameField.text
appendStuff()
}
}
func appendStuff() {
data.append(Day(name: receivedName, color: UIColor.flatRedColor()))
dayView.reloadData()
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
// data.append(Day(name: receivedName, color: receivedColour))
// dayView.reloadData()
// currently used for testing
#IBAction func mondayAddButton(sender: AnyObject) {}
}
// mandatory functions for UICollectionView to work
// this includes assigning the number of sections, how manycells are meant to be in the section(s) and what cell to use (mondayCell in this case)
extension mondayViewController: UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! mondayCell
cell.data = self.data[indexPath.item]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
}
// custom style the size of the collectionView. Changes the width to fill the screen and the height to 70
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let kWhateverHeightYouWant = 80
return CGSizeMake(collectionView.bounds.size.width, CGFloat(kWhateverHeightYouWant))
}
}
if anyone could let me know if I am doing something wrong, or if there is a solution to this issue, that would be awesome.
Cheers,
Rowan
UPDATE:
I have added the collectionViewDataSource and Delegate Methods to the main class (code below):
import UIKit
import ChameleonFramework
class mondayAddSubjectView: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var pageView: PageViewController = PageViewController()
#IBOutlet var selectColourLabel: UILabel!
// would be a public variable, however it is an internal class
internal var cellColour: UIColor!
// collectionView
#IBOutlet var colourPicker: UICollectionView!
// textfield for the user to input their subject name
#IBOutlet var nameField: UITextField!
// source of colours to use in the colour collectionView
private var data = collectionViewColors.createColor()
override func viewDidLoad() {
super.viewDidLoad()
self.colourPicker.delegate = self
colourPicker.dataSource = self
// gesture recognizer to determine when the user has tapped anywhere but the text field, which cancells the keyboard
let tap: UIGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mondayAddSubjectView.dismissKeyboard))
view.addGestureRecognizer(tap)
}
// dismiss keyboard when the user taps away from the text field (called above with #selector)
func dismissKeyboard() {
view.endEditing(true)
}
// MARK: - CollectionView Delegate and Data source methods
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
print("it works")
}
// choose how many sections are in the collectionView. as no content break is required, this will be 1
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
// choose how many items are in each section. as there is only 1 section, it will be the number of stored items
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
// set re-use identifier and the data that will go in each cell
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! colourCell
cell.data = self.data[indexPath.item]
return cell
}
}

Not able to display anything on table view Swift

This is my history view, I have a tableview inside view controller. But for some reason i can't output anything, I am trying to hardcode it and its not displaying anything at all. One thing i know is i need to make tableView functions override and if i do that i get error.
import UIKit
class History: UIViewController, UITableViewDataSource, UITableViewDelegate
{
#IBOutlet var tableView: UITableView!
override func viewDidLoad()
{
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "newBackground.jpg")!)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("historyCell", forIndexPath: indexPath) as! historyCell
cell.durationLabel.text = "98"
return cell
}
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
}
}
heres my class for the cell prototype, and i made sure that the identifier is also "historyCell"
public class historyCell: UITableViewCell{
#IBOutlet var durationLabel: UILabel!
#IBOutlet var kicksLabel: UILabel!
}
How did you connect your prototype cell's controls to the IBOutlets in you historyCell class ? I didn't know that was possible when you're not using a UITableviewController. I've been using tags all this time.
Unless there's something new that I wasn't aware of, your assignment to cell.durationLabel.text should cause unwrapping of a nil (is that the error you're getting ?)