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()
gets called once from an Observer function?
I have also tried calling the NSMenu object function hideCustomizerMenu(self)
, but to no avail.
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
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)
}