How to set customView in UITableViewCell with Swift - ios

I have UITableView, UITablaViewCell, CustomView
and UITableViewCell includes customView.
I'm trying to put Product's data to cell with my function not cellForRowAt.
Cell shows just origin view ProductView.xib with empty data
Please help.
ViewController.swift
struct Product {
let brand: String
let product: String
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as! ProductTableViewCell
// this line is not work
// productView is not update
cell.productView = createProductView(product: product)
return cell
}
func createProductView(product: Product) -> ProductView {
let productView = ProductView()
productView.brandLabel.text = product.brand
productView.productLabel.text = product.product
return productView
}
UITableViewCell.swift
class ProductTableViewCell: UITableViewCell {
#IBOutlet var productView: ProductView!
}
ProductView.swift
class ProductView: UIView {
#IBOutlet weak var productView: UIView!
#IBOutlet weak var brandLabel: UILabel!
#IBOutlet weak var productLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
let bundle = Bundle(for: ProductView.self)
bundle.loadNibNamed("ProductView", owner: self, options: nil)
addSubview(productView)
productView.frame = self.bounds
}

There are multiple issues with your code
Issue 1: cellForRowAt IndexPath gets called multiple times, with your code you will end up creating a new ProductView every time tableView is scrolled (cell is reused). instead you can create product view only once and update its label every time cell is reused
Issue 2: In ProductView's commonInit you set the frame using productView.frame = self.bounds self.bounds will always be(0,0,0,0). Because you have instantiated ProductView as ProductView()
Issue 3: createProductView is supposed to return an instance of ProductView hence the method signature is invalid so you should change it from func createProductView(product: Product) -> ProductView() to func createProductView(product: Product) -> ProductView as already suggested in answer above
What can be better solution?
class ProductTableViewCell: UITableViewCell {
var productView: ProductView!
func updateProductView(product: Product) {
productView.brandLabel.text = product.brand
productView.productLabel.text = product.product
}
override func prepareForReuse() {
super.prepareForReuse()
productView.brandLabel.text = nil
productView.productLabel.text = nil
}
override func awakeFromNib() {
super.awakeFromNib()
self.productView = createProductView()
self.addSubview(self.productView)
self.productView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.productView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.productView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.productView.topAnchor.constraint(equalTo: self.topAnchor),
self.productView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
func createProductView() -> ProductView {
return ProductView()
}
}
Finally in your cellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as! ProductTableViewCell
//assuming you already have a product for index path
cell.updateProductView(product: product)
return cell
}
EDIT:
As OP is facing issue with loading nib from bundle updating the answer here.
In your ProductView common init change the way you access bundle from
Bundle(for: ProductView.self) to Bundle.main as shown below
class ProductView: UIView {
#IBOutlet weak var productView: UIView!
#IBOutlet weak var brandLabel: UILabel!
#IBOutlet weak var productLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("ProductView", owner: self, options: nil)
addSubview(productView)
}
Few things to take care
Ensure you have an XIB named ProductView in your bundle
Ensure you have set its file owner to ProductView

Related

TableView Inside of Xib File

I want to add table view inside my xib file but i got error. My controller is
class ViewController: UIViewControllerUITextViewDelegate,UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell:UITableViewCell? = yerImleriView.tableView.dequeueReusableCell(withIdentifier: identifier)
if cell == nil {
_ = YerImleri().loadNib() as UIView
tableView.register(UINib(nibName: "YerImleri", bundle: nil), forCellReuseIdentifier: identifier)
cell = yerImleriView.tableView.dequeueReusableCell(withIdentifier: identifier)
cell?.textLabel?.textColor = UIColorFromRGB(rgbValue: 0xEFEFEF)
cell?.textLabel?.text = items[indexPath.row]
cell?.backgroundColor = UIColorFromRGB(rgbValue: 0x3A4858)
cell?.textLabel?.font = UIFont (name: "Proxima Nova", size: 16)
}
return cell!
}
var items: [String] = ["Katmanlar", "Yer Imleri", "GPS Cihazlari", "Kategorik Arama","Cevrimdisi Haritalar"]
override func viewDidLoad() {
super.viewDidLoad()
yerImleriView.tableView.delegate = self
yerImleriView.tableView.dataSource = self
}
}
and my Xib file is
class YerImleri: UIView{
#IBOutlet var contentView: UIView!
#IBOutlet weak var tableView: UITableView!
override init(frame: CGRect){
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("YerImleri", owner:self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
and the error is
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key contentView.'
As per the understanding : You are assigning wrong class as file owner to xib check whether correct classes are assigned and all outlets are connected and circle of ib outlet is filled and it will work fine

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 :)

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

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.

Having trouble passing property into drawRect in UIView

Not understanding why my property resets to the original value assigned (0.1). I pass in a fillHeight of 0.5 from outside method. The property is set in the convenience init, but does not carry over to the drawRect. What am I missing?
import UIKit
class MyView: UIView {
var fillHeight: CGFloat = 0.1
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(fillHeight: CGFloat) {
self.init()
self.fillHeight = fillHeight
print("self.fillHeight: \(self.fillHeight) and fillHeight: \(fillHeight)")
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func drawRect(rect: CGRect) {
print("drawRect self.fillHeight: \(self.fillHeight)")
// custom stuff
}
}
Output on the console:
outsideAmount:Optional(0.5)
self.fillHeight: 0.5 and fillHeight: 0.5
drawRect self.fillHeight: 0.1
EDIT:
The outside call comes from UITableViewController with a custom UITableViewCell. The image is for the cell.
func configureCell(cell: CustomTableViewCell, atIndexPath indexPath: NSIndexPath) {
let myObject = self.fetchedResultsController.objectAtIndexPath(indexPath) as! MyObject
cell.nameLabel.text = myObject.name
cell.strengthLabel.text = myObject.strength
cell.myView = MyView(fillHeight: CGFloat(myObject.fillAmount!))
...
MORE EDIT:
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var strengthLabel: UILabel!
#IBOutlet weak var myView: MyView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The problem is that you assign a new MyView instance whenever you configure your cell. You don't have to do that because the view is already there (because you added it in the nib).
So just set fillHeight on the cell's myView. That fixes the problem:
func configureCell(cell: CustomTableViewCell, atIndexPath indexPath: NSIndexPath) {
let myObject = self.fetchedResultsController.objectAtIndexPath(indexPath) as! MyObject
cell.nameLabel.text = myObject.name
cell.strengthLabel.text = myObject.strength
cell.myView.fillHeight = CGFloat(myObject.fillAmount!)
....
}

TableViewController with custom cells in swift

I'm having an issue with custom TableViewCell in Swift. I'm trying to display a custome TableViewCell in a TableView but that doesn't work well. I got a grey square on the screen... I found some answers on the Internet that recommend to disable Auto-Layout. I did it but now, my cell is displayed but there is a mess with the height of the cells. As you can see, my cell that is supposed to have red background is displayed on the height of 3 cells while the background is on only 1 cell. The height of the cell and the Row Height are both set to 150px.
With Auto-Layout: Without Auto-Layout:
Here is the Cell code:
class CharacterDescription : UITableViewCell {
#IBOutlet var imageProfile: UIImageView!
#IBOutlet var name: UILabel!
#IBOutlet var hp: UILabel!
#IBOutlet var damages: UILabel!
#IBOutlet var range: UILabel!
#IBOutlet var mp: UILabel!
#IBOutlet var critChances: UILabel!
#IBOutlet var critModifier: UILabel!
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: UITableViewCellStyle.Value1, reuseIdentifier: reuseIdentifier)
}
}
And the TableViewController code:
class CharacterDescriptionsViewController : UITableViewController, UITableViewDataSource, UITableViewDelegate {
var userProfile: UserProfile!
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override init() {
super.init()
}
override func viewDidLoad() {
var nibName = UINib(nibName: "CharacterDescription", bundle:nil)
self.tableView.registerNib(nibName, forCellReuseIdentifier: "CharacterDescription")
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return userProfile.characters!.count
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
var cell = self.tableView.dequeueReusableCellWithIdentifier("CharacterDescription", forIndexPath: indexPath) as CharacterDescription
//!TODO: Init of cell
return cell
}
}
You have to override TableView.heightForRowAtIndexPath to return the height of your custom cell.

Resources