Dynamically changing NSWindow title while the app is running

443 Views Asked by At

I want to create an app where the NSWindow's title reflects the WKWebView's page title value. However, I've been having trouble with setting the window's title outside of initializer methods such as viewDidLoad, viewWillAppear, etc. Here's what I have thus far:

ViewController.swift

import Cocoa
import WebKit

class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate, NSWindowDelegate {

    @IBOutlet var webView: WKWebView!
    var webViewTitleObserver: NSKeyValueObservation?
    let windowController: WindowController = WindowController()

    override func viewDidLoad() {
        super.viewDidLoad()
        webView.navigationDelegate = self
        webView.uiDelegate = self

        let preferences = WKPreferences()
        preferences.javaScriptEnabled = true
        preferences.javaScriptCanOpenWindowsAutomatically = true
        let configuration = WKWebViewConfiguration()
        configuration.preferences = preferences

        webView.load("https://google.com")

        // WebView Title Observer
        webViewTitleObserver = self.observe(\.webView.title, options: .new) { webView, change in
            let title = "\(String(describing: change.newValue))"
            ViewController().titleChange(pageTitle: title)
        }
    }

    override func viewWillAppear() {
        self.view.window?.delegate = self
    }

    func titleChange(pageTitle: String) {
        //self.view.window?.delegate = self
        print("Start Title:", pageTitle)
        // Fix Optional URL String
        var title = pageTitle.replacingOccurrences(of: "Optional", with: "")
        let brackets: Set<Character> = ["(", ")"]
        title.removeAll(where: { brackets.contains($0) })
        print("Clean Title:", title)

        self.view.window?.title = title
        self.view.window?.update()
    }
}


// MARK: Extensions

// WKWebView Extension
extension WKWebView {
    func load(_ urlString: String) {
        if let url = URL(string: urlString) {
            let request = URLRequest(url: url)
            load(request)
        }
    }
}

This code currently prints to the console whenever the title is changed, so the page titles are coming through as such:

Start Title: Optional(Optional("Google"))
Clean Title: "Google"

However, the window's title refuses to change in the titleChange function. Why is this?

WindowController.swift (if needed)

import Cocoa

class WindowController: NSWindowController, NSWindowDelegate {

    override func windowDidLoad() {
        super.windowDidLoad()

        window!.delegate = self
        window!.titlebarAppearsTransparent = true
        window!.isMovableByWindowBackground  = true

    }
}

What am I doing wrong?

1

There are 1 best solutions below

0
dive On

You are creating a new view controller in your observer:

ViewController().titleChange(pageTitle: title)

And call titleChange() on it. So, because this is a new controller, it has no windows and has nothing to do with the currently presented view hierarchy.

To fix it, update your observer to something like this:

// WebView Title Observer
webViewTitleObserver = self.observe(\.webView.title, options: .new) { [weak self] webView, change in
    let title = "\(String(describing: change.newValue))"
    self?.titleChange(pageTitle: title)
}

I have changed the arguments and capture weak reference for the self and then call your titleChange() function on it. In this case, self.view.window?.title = title will be called on the controller you see on the screen.