Swift Snippets 2: Interface, NSDocument, Files

Interface

A miscellany of human interface items.

Display a modal sheet alert with a message
let alert: NSAlert = NSAlert()
alert.messageText = "An error occurred in your last action:"
alert.informativeText = message
alert.alertStyle = NSAlertStyle.critical
alert.addButton(withTitle: "OK")
alert.beginSheetModal(for: window, completionHandler: nil)

Display a modal window alert with a message
let alert: NSAlert = NSAlert()
alert.messageText = "An error occurred in your last action:"
alert.informativeText = message
alert.alertStyle = NSAlertStyle.critical
alert.addButton(withTitle: "OK")
alert.runModal()

Open a file
let FO = NSOpenPanel()
FO.canChooseFiles = true
FO.canChooseDirectories = false
FO.allowsMultipleSelection = false
FO.begin { result in
if result == NSFileHandlingPanelOKButton {
guard let theUrl = FO.url else { return }
do { }
}
}

or as a sheet
let FO = NSOpenPanel()
FO.allowsMultipleSelection = false
FO.canChooseDirectories = false
FO.allowedFileTypes = ["text"]
FO.beginSheetModal(for: self.view.window!) { result in
if result == NSFileHandlingPanelOKButton {
guard let url = FO.url else { return }
do {}
}
}

Display a save dialog and write out a string to the file
var fileContentToWrite = theOutputString
let FS = NSSavePanel()
FS.canCreateDirectories = true
FS.allowedFileTypes = ["text", "txt"]
FS.begin { result in
if result == NSFileHandlingPanelOKButton {
guard let url = FS.url else { return }
do {
try fileContentToWrite.write(to: url, atomically: false, encoding: String.Encoding.utf8)
} catch {
doErrorAlertSheet(message: error.localizedDescription, window: self.window)
}
}
}

Set plain text in a text box
textBox.stringValue = theString
Take a plain NSString and append it as attributed text into a text box
let attr = NSAttributedString(string: theString as String)
textBox.textStorage?.append(attr)

Radio button groups

To group, Control-drag to make an Action for the first button in the group. Strongly type this to an NSButton sender, with a function name for the group as a whole. Then Control-drag others in the group to the same Action.
For each button in the group, edit its Tag attribute to a different number (1, 2, …) so that each has a tag which is unique within that group.
Edit group Action to the likes of
@IBAction func filterRadioButton(_ sender: NSButton) {
selectedFilterButton = sender.tag
}

where
var selectedFilterButton = 1
to contain the tag number of the currently selected button in the group.
Test which of the buttons in a group is selected using
if (selectedFilterButton == 1)
Add each button as an individual Outlet, and you can then set the selected button using
filterTMbutton.state = NSOnState

Set a popup menu up from a list of strings, in viewDidLoad()
let menuItemList = ["sec", "min", "hour", "day"]
override func viewDidLoad() {
super.viewDidLoad()
popupMenu.removeAllItems()
popupMenu.addItems(withTitles: menuItemList)
popupMenu.selectItem(at: 0)
}

Get currently-selected item
selectedItemTitle = (popupMenu.selectedItem?.title)!
selectedItemTitle = popupMenu.titleOfSelectedItem!

Set the background colour of a text box to red
textFixed1.backgroundColor = NSColor(red:1.00, green:0.00, blue:0.00, alpha:0.5)

Clip (upper and lower limits) numeric value in a text box and sync with stepper periodStep
@IBAction func setThePeriodText(_ sender: Any) {
theTimePeriod = periodText.integerValue
if (theTimePeriod < 1) {
theTimePeriod = 1
periodText.integerValue = theTimePeriod
} else if (theTimePeriod > 24) {
theTimePeriod = 24
periodText.integerValue = theTimePeriod
}
periodStep.integerValue = theTimePeriod
}

Stepper

Clip numeric value (separate snippet above) and use action function
@IBAction func stepPeriodText(_ sender: Any) {
theTimePeriod = periodStep.integerValue
periodText.integerValue = theTimePeriod
}

Set the colour of an NSColorWell
trafficLight.color = NSColor(red:0.00, green:0.80, blue:0.00, alpha:1.0)
for green
trafficLight.color = NSColor(red:1.00, green:0.80, blue:0.00, alpha:1.0)
for amber
trafficLight.color = NSColor(red:0.90, green:0.00, blue:0.00, alpha:1.0)
for red

NSDocument

Useful code for NSDocuments.

NSDocument function to write out View Controller variable as string
override func data(ofType typeName: String) throws -> Data {
if let theVC = self.windowControllers[0].contentViewController as? ViewController {
let fileContentToWrite = theVC.theResTxt.string!
return fileContentToWrite.data(using: String.Encoding.utf8) ?? Data()
}
else {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

Open new, empty file or existing file of text
var thePath = ""
var theData = Data.init()

override func makeWindowControllers() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
if let theVC = self.windowControllers[0].contentViewController as? ViewController {
theVC.theInPath = thePath
if !theData.isEmpty {
var theDict: NSDictionary? = nil
do {
let attr = try NSAttributedString(data: theData, options: [:], documentAttributes: &theDict)
if (attr.length != 0) {
theVC.textViewBot.textStorage?.setAttributedString(attr)
}
} catch {
_ = NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
}
}

override func read(from data: Data, ofType typeName: String) throws {
if !data.isEmpty {
theData = data
thePath = (fileURL?.path)!
}
else {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

Set the default filename for a document, using date and time string
override func defaultDraftName() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd_HH_mm_ss"
let theDate: Date = Date()
let theDateString = dateFormatter.string(from: theDate)
return "T2M2_" + theDateString
}

Set app to use traditional Save, Save As commands rather than new Rename, Duplicate, etc.
override class func autosavesInPlace() -> Bool {
return false
}

Files

Code referring to NSFileManager won’t work: use FileManager instead.

Initialise FileManager
let fileManager = FileManager.default

Get the path to the current user’s Home folder
fileManager.homeDirectoryForCurrentUser.path

Get an NSURL to the /Library folder
chosenPath = NSURL(fileURLWithPath: theMainLibPrefs)

Get an absolute path for a relative path item and the parent directory theDir
let path = NSURL(fileURLWithPath: item as! String, relativeTo: theDir as URL).path

Use try and catch to protect from errors
let fileManager = FileManager.default
do {
try fileManager.attributesOfItem(atPath: path)
} catch let error as NSError {
print("Error: \(error.domain)")
}

Compare files
contentsEqual(atPath path1: String, andPath path2: String)
returns Boolean to indicate whether two files have the same contents.

Move a file using URLs
moveItem(at: URL, to: URL)
or using POSIX paths
moveItem(atPath: String, toPath: String)

Perform a deep traversal of a folder URL theDir, processing each item within its hierarchy
let fm = FileManager.default
let thePath = theDir.path
if let theDirEnumerator = fm.enumerator(atPath: thePath) {
for item in theDirEnumerator {
// process item, which is a relative path
}
}

Test whether an item with an absolute path of path is writable or readable
fm.isWritableFile(atPath: path)
fm.isReadableFile(atPath: path)

Returns true if aliasUrl is not an alias
func isNotFinderAlias(aliasUrl: NSURL) -> Bool? {
var isAlias:AnyObject? = nil
do {
try aliasUrl.getResourceValue(&isAlias, forKey: kCFURLIsSymbolicLinkKey as URLResourceKey)
} catch _ {}
return !((isAlias as! Bool?)!)
}

Return long list of attributes
attributesOfItem(atPath: path)

Swift Snippets 0: Introduction and Contents