1

So I'm a newbie and trying some reusability. I've a class called SingleButtonFooterView which subclasses UIView and UI is done in an .xib.

Now I want to use this in a UITableViewCell. I've tried almost all possible solutions nothing is working for me.

Code for the class:

class SingleButtonFooterView: UIView {

@IBOutlet weak var button: Button50!

override init(frame: CGRect) {
    super.init(frame: frame)
    commonInit()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    commonInit()
}

private func commonInit(){
    let view = UINib(nibName: "SingleButtonFooterView", bundle: nil).instantiate(withOwner: self, options: nil).first as! UIView
    view.frame = self.bounds
    self.addSubview(view)
}

override func awakeFromNib() {
    super.awakeFromNib()
    button.backgroundColor = .clear
    button.layer.cornerRadius = 5
    button.layer.masksToBounds = true

    button.titleLabel?.font = UIFont.futura(with: .medium, size: 16)
    button.setTitleColor(.white, for: .normal)
    self.contentMode = .scaleAspectFit
    self.layer.masksToBounds = true
}
}

Now for cellForRowAt:

let cellId = "SingleButtonFooterView"
            var cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)

            if cell == nil {
                cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: cellId)
                let subView = SingleButtonFooterView(frame: cell.frame)

                cell.contentView.attachViewWithConstraints(subView)
                let _ = subView.viewLoadedFromNibAttached(name: cellId)
            }

            return cell

and in viewDidLoad() of my VC class.

tableView.register(UINib(nibName: "SingleButtonFooterView", bundle: nil), forCellReuseIdentifier: "SingleButtonFooterView")

In short -> I want to use a UIView class (UI done using interface builder -> .xib) in a UITableViewCell

Yash Bedi
  • 1,323
  • 17
  • 25
  • what is it in `attachViewWithConstraints` – wootage Aug 05 '19 at 09:05
  • yep, I forgot to add that snippet infact I'll share the SO answer from where I've been taking help, it basically has all the snippets, https://stackoverflow.com/questions/41851892/nib-file-loaded-uiview-in-uitableviewcell-does-not-stretch – Yash Bedi Aug 05 '19 at 09:09
  • https://stackoverflow.com/questions/57015324/using-a-uiview-xib-as-a-table-view-cell/57020305#57020305 – El Tomato Aug 05 '19 at 09:19
  • Thanks for the comment @ElTomato but in my case `tableview` rendering is done all the way programmatically. Also the post shared by you, doesn't really goes with the question asked by me. =) – Yash Bedi Aug 05 '19 at 09:20

3 Answers3

1

If I can suggest you something just do it "by the book".

Create a custom cell and .xib. Then you can do whatever you want with the UIViews (remember that you can create your own class and put it into xib by changing the class here: Where you change class of the UIView

Having awakeFromNib and Init in one class it's somehow a code smell because either you use .xib or code to create a view.

Remember that adding subviews from the VC it's always risky because you need to take care of recycle, what means that this subview may stay and be not wanted unless you handle this situation.

Remember about prepareForReuse() to handle cells' recycle.

Łukasz Szpyrka
  • 687
  • 1
  • 6
  • 24
  • Actually I've already designed the UI in the `.xib` which inherits from `UIView`. Now as per the UI of the product, The UI in the `.xib` is basically used in some other screen as well. Now obviously why would I be re-designing it since I've already Designed it.? and the other screen has a `UITableView`... – Yash Bedi Aug 05 '19 at 09:19
  • @YashBedi could you rephrase your comment? It is hardly understandable – Łukasz Szpyrka Aug 05 '19 at 11:44
  • ok, Let me put it this way. I want to use a UIView class (UI done using interface builder -> `.xib`) in a UITableViewCell. – Yash Bedi Aug 05 '19 at 12:20
  • @YashBedi my post above answers your question. Again, make a simple UIView in the `.xib` and attach it to cell. Next, change class of this UIView which you attached to the cell and it is ready to go. Just don't use awake from nib method. It is going to be used from `required init?(coder aDecoder: NSCoder)` Here is followup: https://stackoverflow.com/questions/38386339/what-exactly-is-init-coder-adecoder – Łukasz Szpyrka Aug 06 '19 at 08:27
  • I really appreciate you taking out time to write the answer. But it doesn't answer my question at all. And on top of it your explanation is really vague, for e.g.-> `Then you can do whatever you want with the...` ? **If you can write a working solution or if you think your code will work or if you really know the Answer to a question asked on SO** please then you should post your answer, else always prefer comments. – Yash Bedi Aug 06 '19 at 10:32
0

You have two issues here:

1) Bundle.main.loadNibNamed(name, owner: self, options: nil) in viewLoadedFromNibAttached (if you use the exact same code from the other question) and you have the same nib load in commonInit. You have to decide where to put it, IMO you can get rid of

    let subView = SingleButtonFooterView(frame: cell.frame)
    cell.contentView.attachViewWithConstraints(subView)
    let _ = subView.viewLoadedFromNibAttached(name: cellId)

and put that part in the cell (this way you can easily maintain the livecycle of the SingleButtonFooterView

2) Guess: the File owner of the xib is empty or using wrong class, that's why commonInit and required init?(coder aDecoder: NSCoder) are causing infinite loop

EDIT:

Step 1: subclass UITableViewCell, lets call it SingleButtonCell

Step 2: custom view (SingleButtonFooterView in your case). NOTE! File's owner should be the class itself -> SingleButtonFooterView

 @IBOutlet var contentView: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)

        loadXib()
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        loadXib()
    }

    private func loadXib() {
        Bundle.main.loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)

        addSubview(contentView)
        contentView.frame = bounds
    }

This is what I use to load views from .xib. contentView is the outlet of the main view in the .xib file

Step 3: Two options here:

  • Option 3.1: (easier to maintain IMO)

    With the SingleButtonCell you create it's own .xib file

  • 3.1.1: Add view in the cell's xib (SingleButtonCell.xib) and change it's class to SingleButtonFooterView

  • Option 3.2: Do not create cell xib. Instead instantiate the view (SingleButtonFooterView) inside the cell and add it as subview (add constraints if you want). Here you have to be careful where to instantiate the view, because there is a chance to add it multiple times

wootage
  • 936
  • 6
  • 14
0

What I can see is that dequeueReusableCell(withIdentifier:for:) never returns nil. So the code in your nil-check will never be called. You may be thinking of the "old" dequeueReusableCell(withIdentifier:) which can return nil and can be used similar to how you do.

Since it's never nil, you need an additional parameter, say "hasBeenInitialized" to keep track of if you have added your custom UIView yet or not.

// In loading viewDidLoad, register cellId for type for tableView

let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)

if !hasBeenInitialized {
    // Load view from xib
    // Add view as subview to cell contentview
    // Add surrounding constraints
    hasBeenInitialized = true
}
return cell
Sunkas
  • 9,542
  • 6
  • 62
  • 102