1

I have two WebViews: webView and customizerWebView. Both of these WKWebViews are attached by a trailing constraint. Essentially, when I go to the menu and click "Show Customizer" showCustomizer() or "Hide Customizer" hideCustomizer(), it calls the respective function and either shows or hides all the things related to customizerWebView.

To clarify, everything works and animates as expected when calling these functions from their attached NSMenuItems. However, when show/hideCustomizer() gets called from an Observer that essentially detects a URL - ie. url.contains("#close") - the app crashes on the first line of animator() code with the error: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

ViewController.swift

import Cocoa
import WebKit

class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate {
    var customizerURLObserver: NSKeyValueObservation?

    @IBOutlet var webView: WKWebView!
    @IBOutlet var customizerWebView: WKWebView!
    @IBOutlet var rightConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad
        ...
        customizerURLObserver = customizerWebView.observe(\.url, options: .new) { webView, change in
            let url = "\(String(describing: change.newValue))"
            ViewController().urlDidChange(urlString: url) }
    }

    func urlDidChange(urlString: String) {
        let url = cleanURL(urlString)
        if url.contains("#close") { hideCustomizer() }  // Observer call to hide function
    }

    @IBAction func showCustomizerMenu(_ sender: Any) { showCustomizer() }  // These work flawlessly
    @IBAction func hideCustomizerMenu(_ sender: Any) { hideCustomizer() }  // These work flawlessly

    func showCustomizer() {
        let customTimeFunction = CAMediaTimingFunction(controlPoints: 5/6, 0.2, 2/6, 0.9)
        NSAnimationContext.runAnimationGroup({(_ context: NSAnimationContext) -> Void in
            context.timingFunction = customTimeFunction
            context.duration = 0.3
            rightConstraint.animator().constant = 280
            customizerWebView.animator().isHidden = false
            webView.animator().alphaValue = 0.6
        }, completionHandler: {() -> Void in
        })
    }

    func hideCustomizer() {
        let customTimeFunction = CAMediaTimingFunction(controlPoints: 5/6, 0.2, 2/6, 0.9)
        NSAnimationContext.runAnimationGroup({(_ context: NSAnimationContext) -> Void in
            context.timingFunction = customTimeFunction
            context.duration = 0.3
            webView.animator().alphaValue = 1     // Found nil crash highlights this line
            rightConstraint.animator().constant = 0
        }, completionHandler: {() -> Void in
            self.customizerWebView.isHidden = true
        })
    }
}

Could someone please enlighten me as to why this animation looks and works flawlessly 100 times when called from the NSMenu, but crashes when hideCustomizer()正规365体育投注 gets called once from an Observer function?

I have also tried calling the NSMenu object function hideCustomizerMenu(self), but to no avail.

1

On the line:

ViewController().urlDidChange(urlString: url)

you are mistakenly creating a new instance of your view controller class and calling urlDidChange on that instance. Since this new instance is not created from a storyboard/xib, all of its outlets are nil, and thus when you try to call the animator method on its webView in hideCustomizer, it crashes because it's nil.

Instead, call urlDidChange on self (actually a weakified self正规365体育投注 so that you don't create a retain cycle):

customizerURLObserver = customizerWebView.observe(\.url, options: .new) { [weak self] webView, change in
    let url = "\(String(describing: change.newValue))"
    self?.urlDidChange(urlString: url)
}
|improve this answer|||||
  • 1
    Now I feel foolish. Thank you very much – for both the corrected code and explanation! Now I know not to make the same mistake in the future. Just a quick followup if you have the time: Why choose a weak self in instances like these? How would I go about knowing when to choose a weak self upon coding a new project or function? – Matt Swift Mar 27 at 0:35
  • No prob! In this particular case, I knew to use [weak self] because self owns a strong reference to customizerURLObserver, which owns a strong reference to the change handler closure specified in the call to observe(_:options:), which would own a strong reference to self if I had not made self weak – so there would have been a retain cycle going from self->customizerURLObserver->closure->self, which would create a memory leak because none of those things would ever deallocate. In general, though, you pretty much just have to be aware of what holds strong references to what. – TylerTheCompiler Mar 27 at 2:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.