How to Re-use an xib subclass of UIView in UITable

2019-08-23 08:49发布

问题:

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

回答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:

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.



回答2:

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



回答3:

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