Search code examples
swiftuiviewuiscrollviewxibnib

Thread 1 EXC_BAD_ACCESS when loading custom UIView from .xib file


I am trying to load a number of custom UIViews from a .xib file into a UIScrollView and I keep getting "Thread 1: EXC_BAD_ACCESS".

Since it's the first time when I'm actually trying to load a custom UIView (done this many times for UICollectionView and UITableView cells), I've been following this tutorial.

class PreSignupDataQuestionView : NibView
{
    @IBOutlet weak var vMainContainer: UIView!
    @IBOutlet weak var vQuestionViewContainer: UIView!
    @IBOutlet weak var ivQuestionViewImage: UIImageView!
    @IBOutlet weak var lblQuestion: UILabel!
}

class NibView : UIView
{
    var view: UIView!

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

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

private extension NibView
{
    func xibSetup()
    {
        backgroundColor = UIColor.clear
        view = loadNib()
        view.frame = bounds
        addSubview(view)

        view.translatesAutoresizingMaskIntoConstraints = false
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[childView]|",
                                                      options: [],
                                                      metrics: nil,
                                                      views: ["childView": view!]))
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[childView]|",
                                                      options: [],
                                                      metrics: nil,
                                                      views: ["childView": view!]))
    }
}

extension UIView
{
    func loadNib() -> UIView
    {
        let bundle = Bundle(for: type(of: self))
        let nibName = type(of: self).description().components(separatedBy: ".").last!
        let nib = UINib(nibName: nibName, bundle: bundle)

        // Error pops here
        return nib.instantiate(withOwner: self, options: nil).first as! UIView
    }
}

Inside my UIViewController I have a function that sets up the UIScrollView and it works fine until I try to load the custom UIView.

for index in 0..<self.screenData.count
{
    let xCoordinate = (CGFloat(index) * (width)) + firstSpace
    let viewFrame = CGRect(x: xCoordinate, y: 0, width: width - spaceBetweenView, height: height)

    let childView = PreSignupDataQuestionView().loadNib() as! PreSignupDataQuestionView

    childView.frame = viewFrame
    childView.tag = index
    childView.isUserInteractionEnabled = true

    childView.ivQuestionViewImage.image = UIImage(named: "PlaceholderImage")
    childView.lblQuestion.text = "Lorep Ipsum"
    self.svQuestions.addSubview(childView)
}

However, I have no problem loading a UIView if there is no .xib involved:

let xCoordinate = (CGFloat(index) * (width)) + firstSpace + lastSpace
let viewFrame = CGRect(x: xCoordinate, y: 0, width: width - spaceBetweenView, height: height)
let childView = UIView()
childView.tag = index
childView.isUserInteractionEnabled = true

if index % 2 == 0 { childView.backgroundColor = .red }
else { childView.backgroundColor = .green }

childView.frame = viewFrame
self.scrollView.addSubview(childView)

Any ideas? I've been trying to load a custom UIView into a UIScrollView for some time now and can't seem to figure this out. I know this is a common issue but so far nothing worked for me.


Solution

  • That tutorial isn't quite right...

    When loading the custom view - your PreSignupDataQuestionView - via code, change this line:

    let childView = PreSignupDataQuestionView().loadNib() as! PreSignupDataQuestionView
    

    to simply:

    let childView = PreSignupDataQuestionView()
    

    That should take care of it.


    EDIT:

    Try this in a new view controller, to rule out any other possible issues:

    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let childView = PreSignupDataQuestionView()
    
            childView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(childView)
    
            NSLayoutConstraint.activate([
                childView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
                childView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50),
                childView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50),
    
                childView.heightAnchor.constraint(equalToConstant: 300),
    
                ])
    
        }
    
    }
    

    EDIT 2:

    I posted a complete sample project at https://github.com/DonMag/XIBLoadExtension

    See if that works for you. If so, then compare to what you've got to see what's different.