How to load custom cell ( xib) in UICollectionView cell using swift - ios

I have created a small sample project using Swift. I have created an "MyCustomView" as xib which contains label, button and imageView as shown in below code:
import UIKit
#IBDesignable class MyCustomView: UIView {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var btnClick: UIButton!
#IBOutlet weak var myImageView: UIImageView!
var view:UIView!
#IBInspectable
var mytitleLabelText: String? {
get {
return lblName.text
}
set(mytitleLabelText) {
lblName.text = mytitleLabelText
}
}
#IBInspectable
var myCustomImage:UIImage? {
get {
return myImageView.image
}
set(myCustomImage) {
myImageView.image = myCustomImage
}
}
override init(frame : CGRect)
{
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup()
{
view = loadViewFromNib()
view.frame = self.bounds
// not sure about this ?
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyCustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Attached the image of xib for the reference.
In StoryBoard -> ViewController added UIViewCollection which as shown in the below image. In this viewcollection, I need that orange color cell to contain my custom xib to be loaded at runtime.
How do I achieve this?
New Modified code as suggested by Sandeep
// 1
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(UINib(nibName: "MyCustomView", bundle: nil), forCellWithReuseIdentifier: "myCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : MyCustomView = collectionView.dequeueReusableCellWithReuseIdentifier("your_reusable_identifier", forIndexPath: indexPath) as! MyCustomView
cell.lblName.text = "MyNewName"
return cell
}
}
// 2
import UIKit
#IBDesignable class MyCustomView: UICollectionViewCell {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var btnClick: UIButton!
#IBOutlet weak var myImageView: UIImageView!
var view:UIView!
#IBInspectable
var mytitleLabelText: String? {
get {
return lblName.text
}
set(mytitleLabelText) {
lblName.text = mytitleLabelText
}
}
#IBInspectable
var myCustomImage:UIImage? {
get {
return myImageView.image
}
set(myCustomImage) {
myImageView.image = myCustomImage
}
}
}

Here is what you can do,
Change your MyCustomView class to be a subclass of UICollectionViewCell and not UIView.
Remove override init(frame : CGRect),required init?(coder aDecoder: NSCoder),func xibSetup(),func loadViewFromNib() -> UIView from MyCustomView
I seriously could not understand how are you using your setter and getter for mytitleLabelText and myCustomImage. If its of no use get rid of it as well.
Finally you will be left with just IBOutlets in MyCustomView.
For better coding practice change the name from MyCustomView to MyCustomCell (optional)
Go to your xib, select the xib and set its class as MyCustomView.
In the same screen change file owner to yourView controller hosting collectionView
In ViewDidLoad of your viewController register your nib.
self.collectionView.registerNib(UINib(nibName: "your_xib_name", bundle: nil), forCellWithReuseIdentifier: "your_reusable_identifier")
In cellForItemAtIndexPath,
let cell : MyCustomView = collectionView.dequeueReusableCellWithReuseIdentifier("your_reusable_identifier", forIndexPath: indexPath) as! MyCustomView
cell.lblName.text = "bla bla" //access your Cell's IBOutlets
return cell
Finally in order to control the size of cell either override the delegate of collectionView or simply go to your collectionView select the collectionCell in it and drag it to match your dimension :) Thats it :)
Happy coding. Search tutorials for better understanding. I can't explain all delegates as I'll end up writing a blog here.

For Swift 4.0
in viewDidLoad:
//custom collectionViewCell
mainCollectionView.register(UINib(nibName: "your_customcell_name", bundle: nil), forCellWithReuseIdentifier: "your_customcell_identifier")
in cellForItemAt indexPath:
let cell : <your_customcell_name> = mainCollectionView.dequeueReusableCell(withReuseIdentifier: "your_customcell_identifier", for: indexPath) as! <your_customcell_name>
And dont forget to set identifier for your custom cell in xib section.

One line approach if you have to register multiple cells.
extension UICollectionViewCell {
static func register(for collectionView: UICollectionView) {
let cellName = String(describing: self)
let cellIdentifier = cellName + "Identifier"
let cellNib = UINib(nibName: String(describing: self), bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: cellIdentifier)
}
}
Steps on how to use
Name your Cell identifier as "YourcellName" + "Identifier" eg:
CustomCellIdentifier if your cell name is CustomCell.
CustomCell.register(for: collectionView)

For Swift 4.2
in viewDidLoad
self.collectionView.register(UINib(nibName: "your_xib_name", bundle: nil), forCellWithReuseIdentifier: "your_reusable_identifier")
in cellForItemAt indexPath:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "your_reusable_identifier", for: indexPath) as! MyCustomView
And of course as #Raj Aryan said:
don't forget to set identifier for your custom cell in xib section.

Related

NSUnknownKeyException when using an IBOutlet in UICollectionViewCell in .xib

I've been trying to add a label using an IBOutlet from an xib to a custom UICollectionViewCell class that is meant to be used as the cells for a UICollectionView that I have in another xib but when I add the outlet I get an NSUnknownKeyException on the label reference I've created, without the outlet reference the contents of the cell load properly but I want to be able to manipulate the label within the cell.
Heres what I have in my parent xib class:
class Calendar : UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
let nibName: String = "Calendar"
let calendarDayNibName: String = "CalendarDay"
let calendarReusableCellName: String = "CalendarCell"
let calendarDaysLimit: Int = 35
var contentView: UIView?
#IBOutlet var sundayLabel: UILabel!
#IBOutlet var mondayLabel: UILabel!
#IBOutlet var tuesdayLabel: UILabel!
#IBOutlet var wednesdayLabel: UILabel!
#IBOutlet var thursdayLabel: UILabel!
#IBOutlet var fridayLabel: UILabel!
#IBOutlet var saturdayLabel: UILabel!
#IBOutlet var calendarDays: UICollectionView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = self.loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
contentView?.isUserInteractionEnabled = true
}
override func awakeFromNib()
{
super.awakeFromNib()
calendarDays.register(UINib(nibName: calendarDayNibName, bundle: nil), forCellWithReuseIdentifier:calendarReusableCellName)
calendarDays.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return calendarDaysLimit;
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: calendarReusableCellName, for: indexPath)
configureCell(cell: cell)
return cell
}
private func configureCell(cell: UICollectionViewCell)
{
//does nothing right now, placeholder for if configuring cell on
//collection view load
}
private func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
self.isUserInteractionEnabled = true
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func loadCalendarDays(month: Int)
{
//todo: write day loading logic here
}
}
Here is the child xib class (UICollectionViewCell):
class CalendarDay: UICollectionViewCell
{
#IBOutlet weak var dayLabel: UILabel!
}
Here is my overall project if it helps to look at: https://github.com/CharlemagneVI/practice-calendar
You've set the classes and IBOutlet connections wrong... well, not-quite-right...
In CalendarDay.xib, the class of File's Owner should be the default NSObject:
and it should not have any connections:
The class of the cell object itself should be CalendarDay:
and that is where you make your connection:
That should do it :)

Use custom view with XIB in IB uitableviewcell w/dynamic height

I have a cell that has a an area where the content can vary from other cells, (while other cell properties are the same) - I thought a custom view backed by a nib would work well in this case but am running into an issue using the custom view with cells where height is dynamic.
Storyboard:
Here is a UIViewController with a UITableView and a custom UITableViewCell, MessageTableViewCell, implemented like this:
class MessageTableViewCell: UITableViewCell {
#IBOutlet weak var avatar: UIImageView!
#IBOutlet weak var initials: UILabel!
#IBOutlet weak var timestamp: UILabel!
#IBOutlet weak var messageView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In the view controller's cellForRowAtIndexPath method, I attempt to use the frame from the messageView (the white box in the cell on the view controller) to instantiate a UIView backed by a xib.
The custom view:
It's implementation:
class MessageView: NibLoadingView {
#IBOutlet weak var message: UILabel!
}
The NibLoadingView implementation:
(credit: https://stackoverflow.com/a/40051928/4096655)
class NibLoadingView: UIView {
weak var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
backgroundColor = .clear
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
addSubview(view)
}
private func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
return nibView
}
}
The cell for row implementation:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as? MessageTableViewCell else {
return UITableViewCell()
}
let frame = cell.messageView.frame
let messageView = MessageView(frame: frame)
messageView.message.text = messages[indexPath.row]
cell.addSubview(messageView)
return cell
}
Results in:
The short story is I'd like to share some common properties between what I would normally consider different cells and am trying to create custom views for the part of the cells that are different from each other, some of them have dynamic height. Thanks for reading.
You do not need to create a custom view MessageView, instead you need to add the contents of MessageView in messageView which is the view that you added in your prototype cell MessageTableViewCell on the viewController and don't forget the outlets, and give the label inside the messageView constraints leading, top, trailing and bottom,
lastly in your viewController instead of calling tableView(_:heightForRowAt:) call tableView(_:estimatedHeightForRowAt:) UITableViewDelegate method.

objc custom view with nib not visible on tableviewcell

I have an UITableViewCell contain a custom view, the custom view contains two label. The view hierarchical likes that:
|---Label 1
XIB---MyTableViewCell---MyView---|
|---Label 2
But run application, just shown 'MyView', Label 1 and Label 2 not visible! If I wrote code on viewDidLoad, take 'MyView' as viewController.view's subview, the label 1 and 2 is appeared. Hope you can help me.
Have you tried this?
Create MyView like this:
class MyView: UIView {
#IBOutlet weak var _label1: UILabel!
#IBOutlet weak var _label2: UILabel!
// Our custom view from the XIB file
var view: UIView!
//MARK: - Setup -
private func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
addSubview(view)
}
private func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
override func awakeFromNib() {
super.awakeFromNib()
}
func setLabeles(lbl1: String, lbl2: String) {
_label1!.text = lbl1
_label2!.text = lbl2
}
}
Make Label1 and Label2 outlet to respective ones.
Then add it to custom tableViewCell as:
(Pink colour is for highlighting!)
Make My View outlet.
Custom cell code:
#IBOutlet weak var _myView: MyView!
class func identifier() -> String {
return "CustomTableViewCellId"
}
class func nib() -> UINib {
let nib = UINib(nibName: "CustomTableViewCell", bundle: NSBundle.mainBundle())
return nib
}
//MARK: Public Methods
func setCellIndexLabel(index: Int) {
_myView!.setLabeles("lbl1: \(index)", lbl2: "lbl2: \(index)")
}
Then there is no need to do extra anything in table view, just do:
In viewDidLoad()-
tableView!.registerNib(CustomTableViewCell.nib(), forCellReuseIdentifier: CustomTableViewCell.identifier())
//then
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell = tableView.dequeueReusableCellWithIdentifier(CustomTableViewCell.identifier(), forIndexPath: indexPath) as! CustomTableViewCell
cell.setCellIndexLabel(indexPath.row)
return cell
}
And you will get this:
Is this what you are looking for?
Forgive me for providing swift code, but it's not much different that objective-c!

UITableView in xCode 7.3 without UITableViewController

I'm trying to load an UITableView from a xib file. I created an UIView object which I use in order to load the xib file. Here I added an UITableView but with the last update of Xcode, I can't figure out how to include a prototype cell inside this table view.
Here there is my code of the UIView which loads the xib file:
class TopChartView: UIView, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableViewChart: UITableView!
var view : UIView!
var prova = [String]()
override init(frame: CGRect) {
super.init(frame: frame)
prova.append("hi")
prova.append("bye")
self.tableViewChart = UITableView()
self.tableViewChart.delegate = self
self.tableViewChart.dataSource = self
self.tableViewChart.registerClass(TopChartTVCell.self, forCellReuseIdentifier: "TopChart_IDcell")
//carico il file xib
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func xibSetup() {
Global.log.action()
view = loadViewFromNib()
// Make the view stretch with containing view
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
self.addSubview(view)
Global.log.action(methodEnded: true)
}
override func layoutSubviews() {
super.layoutSubviews()
Global.log.action()
let screenSize: CGRect = UIScreen.mainScreen().bounds
view.frame.size.width = screenSize.width
view.frame.size.height = screenSize.height
view.frame.origin.x = 0
view.frame.origin.y = 0
Global.log.action(methodEnded: true)
}
func loadViewFromNib() -> UIView {
Global.log.action()
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: Constants.topChartVC.xibFileName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
Global.log.action(methodEnded: true)
return view
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.prova.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TopChart_IDcell", forIndexPath: indexPath) as! TopChartTVCell
let elem = self.prova[indexPath.row]
cell.label.text = elem
return cell
}
}
And here there is my simple xib which is the problem because I'm not able to add an UITableViewCell inside the UITableView:

SWIFT TableView not responding

I have a problem, i have a controller, and in viewdidload, i try to load a subview created from a xib file.
My "custom" subview is well added to my first controller but the tableview isn't responding... i mean it doesn't scroll, and when i click it, nothing happens... (i haven't implemented yet the method to trigger an action when a cell is clicked, but the celle isn't highlighted when clicked).
Here the code :
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let v1 = MyViewTest(frame: CGRectZero)
v1.tableView.dataSource = self
v1.tableView.delegate = self
self.view.addSubview(v1)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//datasource method returning the what cell contains
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
//reusing the previous scrolled cells from table view(if any)
//cell.textLabel?.text = array[indexPath.row]
cell.textLabel?.text = "test"
//passing each elements of the array to cell
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//datasource method returning no. of rows
return 14
}
}
here the code in MyViewTest
class MyViewTest: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var view:UIView!
let nibName:String = "MyViewTest"
override init(frame: CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder: NSCoder) {
//fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
setup()
}
func setup() {
view = loadViewFromNib()
let screenSize: CGRect = UIScreen.mainScreen().bounds
let sWidth = screenSize.width
let sHeight = screenSize.height
let xPos = sWidth/2-(view.bounds.width/2)
let yPos = sHeight/2-(view.bounds.height/2)
view.frame = CGRectMake(xPos, yPos, view.bounds.width, view.bounds.height)
addSubview(view)
}
func loadViewFromNib () -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let mview = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return mview
}
}
in the xib file, i only have a view with a label and my tableview in. The xib file is associated with its class (in identifier).
If you know why my table view isn't working it would be great thx !
You seem to set the frame of the MyTestView to CGRectZero.
You then add your table as a subview of this view with the frame size set up. As MyTestView has 0 width and height and the default for a view is to not clip subviews I imagine you can see the table but not click it.
Try setting the frame of your MyTestView to the screen size?
The problem happens because I instantiate my MyViewTest with CGRectZero. If i use a CGRectMake for example with actual width and height it works great.
See rory mckinnel post just above for more details :-)

Resources