Search code examples
swiftaddsubview

My subview can't be added to a UIView that is a subView of a UIView.xib file, it returns nil


I have two Swift files, one is called FileView.swift and the other is a FileViewController.swift. Using MVC Architecture, I am trying to add a subview from FileViewController.swift to FileView.swift's webViewContainer: UIView! which is an IBOutlet from a XIB File.

However, I am getting a nil result when it is called from FileViewController.swift.

class FileView: UIView {

    @IBOutlet weak var webViewContainerHeight: NSLayoutConstraint!
    @IBOutlet weak var webViewContainerWidth: NSLayoutConstraint!
    @IBOutlet weak var closeBttnWidth: NSLayoutConstraint!
    @IBOutlet weak var closeBttnHeight: NSLayoutConstraint!
    @IBOutlet weak var webViewContainer: UIView!
    @IBOutlet weak var closeButton: UIButton!

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

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

    private func setup() {
        webViewContainer = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 350))
    }
class FileViewController: UIViewController, WKNavigationDelegate {

    var webView = WKWebView()
    var campaignUrl = ""
    var finalCampaignUrl = ""

    lazy var customView: FileView = {
        let customView = FileView()
        return customView
    }()

    override func loadView() {
        self.view = self.customView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        webViewModal()
    }

func webViewModal () {
        if UIDevice.current.userInterfaceIdiom == .pad {
            let deviceWidth = UIScreen.main.bounds.width/3
            let deviceHeight = UIScreen.main.bounds.height/3
            customView.webViewContainerWidth.constant = 290 + deviceWidth
            customView.webViewContainerHeight.constant = 475 + deviceHeight
            customView.closeBttnWidth.constant = 55
            customView.closeBttnHeight.constant = 55
            customView.closeButton.layoutIfNeeded()
            customView.webViewContainer.layoutIfNeeded()
        }
        webView = Global.setWKWebViewInitWithConfig(frame: .zero)
        customView.webViewContainer.addSubview(webView)
        customView.webViewContainer.showSpinner(CGSize(width: 30 , height: 30), tintColor: UIColor.lightGray)
        webView.translatesAutoresizingMaskIntoConstraints = false
        let webViewHeightConstraint = NSLayoutConstraint(
            item: webView,
            attribute: .height,
            relatedBy: .equal,
            toItem: customView.webViewContainer,
            attribute: .height,
            multiplier: 1,
            constant: 0
        )
        let webViewWidthConstraint = NSLayoutConstraint(
            item: webView,
            attribute: .width,
            relatedBy: .equal,
            toItem: customView.webViewContainer,
            attribute: .width,
            multiplier: 1,
            constant: 0
        )
        let webViewLeftMarginConstraint = NSLayoutConstraint(
            item: webView,
            attribute: .leftMargin,
            relatedBy: .equal,
            toItem: customView.webViewContainer,
            attribute: .leftMargin,
            multiplier: 1,
            constant: 0
        )
        let webViewRightMarginConstraint = NSLayoutConstraint(
            item: webView,
            attribute: .rightMargin,
            relatedBy: .equal,
            toItem: customView.webViewContainer,
            attribute: .rightMargin,
            multiplier: 1,
            constant: 0
        )
        let webViewBottomMarginContraint = NSLayoutConstraint(
            item: webView,
            attribute: .bottomMargin,
            relatedBy: .equal,
            toItem: customView.webViewContainer,
            attribute: .bottomMargin,
            multiplier: 1,
            constant: 0
        )
        NSLayoutConstraint.activate([webViewHeightConstraint, webViewWidthConstraint, webViewLeftMarginConstraint, webViewRightMarginConstraint, webViewBottomMarginContraint])

        webView.navigationDelegate = self

        Global.initialLoadWithParam(ofWebView: webView, withURL: NSURL(string: campaignUrl)!)
        customView.closeButton.layer.cornerRadius = 0.9 * customView.closeButton.bounds.size.width
        customView.closeButton.backgroundColor = #colorLiteral(red: 0.003921568627, green: 0.1647058824, blue: 0.2666666667, alpha: 1)
        customView.closeButton.tintColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
        customView.webViewContainer.layer.cornerRadius = 15
        customView.webViewContainer.layer.masksToBounds = true
    }


I expected that this should add

webView = Global.setWKWebViewInitWithConfig(frame: .zero)

to

customView.webViewContainer.addSubview(webView)

but it returns nil instead on customView.webViewContainer's line:

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

Should'nt webViewContainer already be instantiated when customView was loaded on override func loadView()?


Solution

  • The problem is that your webViewContainer is a weak reference:

    class FileView: UIView {
        // ...
        @IBOutlet weak var webViewContainer: UIView!
        // ...
    }
    

    This works fine if

    • you are using XIB files
    • someone (e.g.the view controller) keeps a strong reference to the top view
    • The outlets are part of the view hierarchy.

    But in your case, in setup you create an instance of the view, store it in the weak reference, and then ARC (the reference counting mechanism) will find out that there are no strong references any more to the UIView and therfore clears the memory, resulting in a nil reference.

    So you need to somehow keep a strong reference; in the simplest case, just skip the weak reference specifier in the outlet.

    But keep in mind that you might have to do additional stuff to prevent strong reference cycles. In your case, if you do not work with xib files, you could just remove all the @IBOutlet stuff and make the NSCoder initializer just call fatalError, to prevent any creation from a xib file.