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 :)
Related
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
I am new to Swift. Unable to find solution for below problem.
Below is a ViewController with CollectionView and When you click on Cell in CollectionView, data from cell(even this who isn't in label and image view, but are in Book array row) must be send to TabBarCollection, than from TabBarCollection I need to send this data to all of child's, like in this image.
Later in childs of TabBar I will set value of Labels in View Controllers from data from choosed Cell.
Book.swift
import UIKit
struct Book {
let title: String
let image: UIImage?
//Here soon will be more "let", and this data will also have to be send to TabBar but it don't will be show I CollectionViewCell
}
extension Book {
static let books: [Book] = [
Book(title: "Antygona", image: UIImage(named: "imgantygona")!),
//etc
]
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bookImageView: UIImageView!
#IBOutlet weak var bookTitle: UILabel!
func setup(with book: Book) {
bookTitle.text = book.title
bookImageView.image = book.image
}
}
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let books = Book.books
override func viewDidLoad() {
super.viewDidLoad()
let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0)]
UITabBarItem.appearance().setTitleTextAttributes(fontAttributes, for: .normal)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return books.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "bookCell", for: indexPath) as! CollectionViewCell
let book = books[indexPath.item]
cell.setup(with: book)
return cell
}
}
I saw many solutions but I can't perfectly adapt it to my problem. :(
Thanks for help !
BookInsideViewController.swift
import UIKit
class BookInsideViewController: UIViewController {
#IBOutlet weak var testImageView: UIImageView!
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
You can use collection view DidSelectItemAt function
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourtabbarIdentifier") as! UITabBarController
// You should access the `imageController` through your `tabBarController`,
// not instantiate it from storyboard.
if let viewControllers = tabBarController.viewControllers,
let imageController = viewControllers.first as? ImageController {
BookInsideViewController.recivedData1 = Books[indexPath.row]
}
navigationController?.pushViewController(tabBarController, animated: true)
}
I created a Xib File and call in ViewController.Also I create a CollectionView into Xib File.And now I want to reach CollectionViewCell for showing cells.
class ProductVC: UIViewController {
var collection:UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let productView : ProductDetailView = UIView.fromNib()
productView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(productView)
collection = productView.collectionView
collection.register(UINib(nibName: "TagCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagCollectionViewCell")
productView.topAnchor.constraint(equalTo: view.safeTopAnchor).isActive = true
productView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
productView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
}
}
class ProductDetailView: UIView {
#IBOutlet var productTitle: UILabel!
#IBOutlet var dateLabel: UILabel!
#IBOutlet var productImage: UIImageView!
#IBOutlet var productDescriptionLabel: UILabel!
#IBOutlet var collectionView: UICollectionView!
}
class TagCollectionViewCell: UICollectionViewCell {
#IBOutlet var tagName: UILabel!
}
Also I added some code like below . But has no sense!. Where is my mistake?
extension ProductVC : UICollectionViewDelegate , UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: TagCollectionViewCell! = collectionView.dequeueReusableCell(withReuseIdentifier: "TagCollectionViewCell", for: indexPath) as? TagCollectionViewCell
if cell == nil {
collectionView.register(UINib(nibName: "TagCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagCollectionViewCell")
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TagCollectionViewCell", for: indexPath) as? TagCollectionViewCell
}
cell.tagName.text = "aa"
return cell
}
}
You did not conform to delegate and dataSource protocols. I think this the problem. Put below lines after collection.register
collection.delegate = self
collection.dataSource = self
Hope this will work.
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!
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.