extiri blog

Blog - Extiri homepage



Some snippets of code for your Swift apps

Hi! While programming in Swift I have accumulated many code snippets. Some of them are mine and some of them come from the internet. Here I'm sharing 5 tiny quality-of-life snippets. Hopefully you will find them useful.

1. (Cocoa) Accessing NSWindow from NSViewController

extension NSViewController {
  var window: NSWindow? {
    for window in NSApp.windows {
      if window.contentViewController == self {
        return window
      }
    }
    return nil
  }
}

This snippet adds a window variable to NSViewController. It requires the window to exist (eg. be in NSApp.windows, in most cases that will be true) and that there is only one window with the specified view controller. It will return the window with the view controller on which it is called.

2. Checking if path leads to a directory

extension FileManager {
  func isDirectory(path: String) -> Bool {
    var isDirectory: ObjCBool = false
    self.fileExists(atPath: path, isDirectory: &isDirectory)
    return isDirectory.boolValue
  }
}

This is a simple wrapper around FileManager.fileExists() as an extension to FileManager that checks whether a path provided as a string leads to a directory.

3. Safely accessing an array

extension Array {
	subscript(safely index: Int) -> Element? {
		if index < self.endIndex && index >= 0  {
			return self[index]
		} else {
			return nil
		}
	}
}

This snippet adds a subscript to arrays. It checks whether index is within the array's range then returns an element or nil depending on the check. You can use it like this:

let array = ["a", "b", "c"]
let value = array[safely: 2] // returns Optional("c")

This might save you few lines of code for checking and force you to handle the edge case without crashing the app.

4. (SwiftUI) Web view wrapper

struct WebView: NSViewRepresentable {
	/// URL of current page. If changed, WebView will change it into the new URL.
	@Binding var url: URL
	
	private var onLoadStart: (WKWebView) -> ()
	private var onLoadFinish: (WKWebView) -> ()
	
  /// A WKWebView instance, which is used to render WebView, and which can be used to control it thanks to classes being passed by reference.
	private var wkWebView: WKWebView
	
  /// Accepts a binding to an URL, WKWebView instance, which can be used to control WebView and onLoadStart and onLoadFinish which will run when WebView starts or finishes loading a webpage.
	init(url: Binding<URL>, wkWebView: WKWebView = WKWebView(), onLoadStart: ((WKWebView) -> ())? = nil, onLoadFinish: ((WKWebView) -> ())? = nil) {
		self.wkWebView = wkWebView
		
		self._url = url
		
		if let onLoadStart = onLoadStart {
			self.onLoadStart = onLoadStart
		} else {
			self.onLoadStart = { _ in }
		}
		
		if let onLoadFinish = onLoadFinish {
			self.onLoadFinish = onLoadFinish
		} else {
			self.onLoadFinish = { _ in }
		}
	}
	
	func makeNSView(context: Context) -> WKWebView {
		wkWebView.navigationDelegate = context.coordinator
		wkWebView.load(URLRequest(url: url))
		
		return wkWebView
	}
	
	func updateNSView(_ nsView: WKWebView, context: Context) {
		nsView.load(URLRequest(url: url))
	}
	
	func makeCoordinator() -> Coordinator {
		Coordinator(onLoadStart: onLoadStart, onLoadFinish: onLoadFinish)
	}
	
	class Coordinator: NSObject, WKNavigationDelegate {
		let onLoadStart: (WKWebView) -> ()
		let onLoadFinish: (WKWebView) -> ()
		
		init(onLoadStart: @escaping (WKWebView) -> (), onLoadFinish: @escaping (WKWebView) -> ()) {
			self.onLoadStart = onLoadStart
			self.onLoadFinish = onLoadFinish
		}
		
		func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
			onLoadStart(webView)
		}
		
		func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
			onLoadFinish(webView)
		}
	}
}

This pretty lengthy snippet adds a simple SwiftUI wrapper around WKWebView (which needs to be imported). It only requires a binding to a url, which will be loaded. In more complex cases, you can pass onLoadStart and onLoadFinish handlers which will run when the web view starts and finishes loading and a WKWebView class instance. Since in Swift classes are passed by reference, you can use this instance in your view to control the web view (go back, forward, etc).

5. (Cocoa) Showing an alert

func showAlert(message: String, informative: String, buttons: [String] = [], completionHandler: @escaping (NSApplication.ModalResponse) -> () = { _ in }) {
	DispatchQueue.main.async {
		let alert = NSAlert()
		
		alert.messageText = message
		alert.informativeText = informative
		
		buttons.forEach { alert.addButton(withTitle: $0) }
		
		let result = alert.runModal()
		completionHandler(result)
	}
}

Once again, a wrapper. It wraps NSAlert in one function so that you don't need to rewrite this code. It accepts a message and informative text which will be used on the alert. It also optionally accepts a list of button titles which will be added to the alert. It also runs asynchronously on the main thread, so it works even when run in another thread. Here I'd like to add that you can use NSAlert.accessoryView to create alerts with custom views, for example I did something like this:

func showChoicePrompt(message: String, informativeText: String, buttons: [String], completionHandler: @escaping (String?) -> Void) {
  let alert = NSAlert()

  alert.addButton(withTitle: "OK")
  alert.addButton(withTitle: "Cancel")
  
  alert.messageText = message
  alert.informativeText = informativeText

  let popUp = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
  popUp.addItems(withTitles: buttons)

  alert.accessoryView = popUp

  let response: NSApplication.ModalResponse = alert.runModal()

  if response == NSApplication.ModalResponse.alertFirstButtonReturn {
    completionHandler(popUp.selectedItem?.title)
  } else {
    completionHandler(nil)
  }
}

which creates an alert with a pop up button for selecting a value.

As a sidenote, if you are dealing with many snippets of various types (like these ones), CodeMenu can help you organize, access and use their full potential.