From 0c945c40c4928f477f95a291c5e2c61576271304 Mon Sep 17 00:00:00 2001 From: Claire Date: Sat, 1 Oct 2022 20:06:48 -0700 Subject: [PATCH] added section on user prefs --- Home.md | 5 +- Swift-Windows.md | 1 + User-Prefs.md | 243 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 User-Prefs.md diff --git a/Home.md b/Home.md index e98053a..4b45929 100644 --- a/Home.md +++ b/Home.md @@ -3,6 +3,7 @@ ## Sections * The main application (this page) * [Windows with Swift](/wiki/Swift-Windows) +* [Setting (and retrieving) user preferences](/wiki/User-Prefs) ## Introduction I recently picked up a very cheap MacBook Air circa 2015. I'm disinterested in running any version of MacOS beyond 10.14, and as time goes on and more and more popular apps drop support for Mojave, I figured I might try my hand at writing apps for my Mac. @@ -50,7 +51,9 @@ To double-check the results of this command, you can run: xattr -lr . ``` -If all went well, you *should* get no results. If something went wrong, try again, and maybe try with `sudo`, to circumvent any permissions issues. These extended attributes will prevent codesigning entirely, which is a *mandatory* step to packaging a MacOS application for release. If you get an error like `resource fork, Finder information, or similar detritus not allowed` during archiving your project for release, try using `xattr` to strip your project of any leftover attributes. +If all went well, you *should* get no results. If something went wrong, try again, and maybe try with `sudo`, to circumvent any permissions issues. These extended attributes will prevent codesigning entirely, which is a *mandatory* step to packaging a MacOS application for release. + +If you get an error like `resource fork, Finder information, or similar detritus not allowed` during archiving your project for release, try using `xattr` to strip your project of any leftover attributes. Make sure to **clean your build folder** (under the **Product** menu) after running `xattr`, or you'll continue to get build errors. ### Application icon diff --git a/Swift-Windows.md b/Swift-Windows.md index 9573eb8..c421d9f 100644 --- a/Swift-Windows.md +++ b/Swift-Windows.md @@ -4,6 +4,7 @@ * [The main application](/wiki/Home) * Windows with Swift (this page) +* [Setting (and retrieving) user preferences](/wiki/User-Prefs) ## Introduction diff --git a/User-Prefs.md b/User-Prefs.md new file mode 100644 index 0000000..588e9db --- /dev/null +++ b/User-Prefs.md @@ -0,0 +1,243 @@ +# Setting (and retrieving) user preferences + +## Sections +* [The main application](/wiki/Home) +* [Windows with Swift](/wiki/Swift-Windows) +* Setting (and retrieving) user preferences (this page) + +## Introduction + +Version 1.4 of LunaMac introduces a user preference, which defines the preferred icon style - default, alternate dark, or emoji. After I finished and released this project, I decided I didn't like the dark mode icons as much, because the white part of the icon is actually the dark area of the moon. I also wanted to add back support for the emoji icons, in case some people prefer that to the monochrome icons. + +## Setting a user preference + +Handling all this one of the easiest parts of this project, so I can at least give Apple credit for that much. Apple provides a common interface for application preferences, which in MacOS are managed using special XML **plist** files. Instead of having to write these files directly, you can just use [`UserDefaults.standard`](https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard). + +```swift +class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { + // user settings + let defaults = UserDefaults.standard +} +``` + +We're going to set a single preference named `iconPref`, which holds a string value. Each value represents an icon theme: `default`, `darkalt`, and `emoji`. + +Before we make a submenu for setting the user preference, we're going to make a function that actually handles setting the value in `defaults`. + +This function takes a `sender` argument, which is the [`NSMenuItem`](https://developer.apple.com/documentation/appkit/nsmenuitem) the user selects. We can set the preference based on the contents of [`NSMenuItem.title`](https://developer.apple.com/documentation/appkit/nsmenuitem/1514805-title). + +Once we set our user preference, we'll run `updateIcon()`, which will rebuild the icon and menu with the new preferred icon set. + +```swift +@objc func setPref(sender: NSMenuItem) { + // sender.title tells us what to do + switch (sender.title) { + case "Default" : + defaults.set("default", forKey: "iconPref") + break + case "Alternate Dark" : + defaults.set("darkalt", forKey: "iconPref") + break + case "Emoji" : + defaults.set("emoji", forKey: "iconPref") + break + default: + break + } + + // rebuild the menu + updateIcon() +} +``` + +## Adding new icons + + + +Next, we need to add icons for these new icon sets. As you'll see below, the alternate dark mode icons can be reused in a different order for half of the phases. For the crescent and gibbous phases, I created new icons with better light/dark proportion. These icons use the same filename prefix, with a suffix of `-alt-dark`. The emoji icons, which are PNG representations of Apple's system emoji font, use a suffix of `-emoji`. + +Remember to run `xattr -cr .` after adding new images to your project. +
+ +## Creating the submenu + + + +Now we need a menu interface, so the user can select their preferred icon set. + +Previously, in `buildMenu()`, we built our menu by directly invoking [NSMenu.addItem](https://developer.apple.com/documentation/appkit/nsmenu/1518176-additem). This works, but it doesn't give us access to the more advanced capabilities of [`NSMenu`](https://developer.apple.com/documentation/appkit/nsmenu), like adding submenus. We're going to add a user setting menu item, which points to a submenu of options. + +First, we're going to create a new instance of [`NSMenu`](https://developer.apple.com/documentation/appkit/nsmenu), which will serve as a submenu for the **Icon Style** menu item. +
+ +```swift +@objc func buildMenu(key: String = "default") { + ... + // create menu item to hold submenu + let prefMenu = NSMenu(title: "Prefs") + ... +} +``` + +Next, we're going to create the three menu items in our submenu using [`NSMenuItem`](https://developer.apple.com/documentation/appkit/nsmenuitem). All three will call our `setPref()` function when clicked. + +```swift +@objc func buildMenu(key: String = "default") { + ... + // default + let optDefault = NSMenuItem( + title: "Default", + action: #selector(setPref), + keyEquivalent: "" + ) + + // alternate dark + let optDarkAlt = NSMenuItem( + title: "Alternate Dark", + action: #selector(setPref), + keyEquivalent: "" + ) + + // emoji + let optEmoji = NSMenuItem( + title: "Emoji", + action: #selector(setPref), + keyEquivalent: "" + ) + ... +} +``` + +You may have noticed in my menu screenshot that the selected theme is indicated with a checkmark. This is done by setting [`NSMenuItem.state`](https://developer.apple.com/documentation/appkit/nsmenuitem/1514804-state) to [`NSControl.StateValue.on`](https://developer.apple.com/documentation/appkit/nscontrol/statevalue/2876355-on). + +This is accomplished with a basic `switch case`, which sets the `state` value based on the user's icon preference. If no preference exists, the icon preference is set to `default`. + +```swift +@objc func buildMenu(key: String = "default") { + ... + // make sure the right menu item is checked + switch (defaults.string(forKey: "iconPref")) { + case "default" : + // standard icons + optDefault.state = NSControl.StateValue.on + break + case "darkalt" : + // inverted dark mode icons + optDarkAlt.state = NSControl.StateValue.on + break + case "emoji" : + // emoji icons + optEmoji.state = NSControl.StateValue.on + break + default : + // default to standard icons + optDefault.state = NSControl.StateValue.on + break + } + ... +} +``` + +Now that we have our menu items all built and set up, we can add them to our submenu object. + +```swift +@objc func buildMenu(key: String = "default") { + ... + prefMenu.addItem(optDefault) + prefMenu.addItem(optDarkAlt) + prefMenu.addItem(optEmoji) + ... +} +``` + +Now we need to create the [`NSMenuItem`](https://developer.apple.com/documentation/appkit/nsmenuitem) instance for the **Icon Style** menu item, and add it to our main menu object. + +```swift +@objc func buildMenu(key: String = "default") { + ... + let prefMenuItem = NSMenuItem( + title: "Icon Style", + action: nil, + keyEquivalent: "" + ) + + statusBarMenu.addItem(prefMenuItem) + ... +} +``` + +The last thing we need to do is add our submenu, `prefMenu`, to the menu item we just created. + +```swift +@objc func buildMenu(key: String = "default") { + ... + statusBarMenu.setSubmenu(prefMenu, for: prefMenuItem) + ... +} +``` + +You can run your application now, and test the user setting. Change the setting, close the app, relaunch, and your selected icon set should be identified with a checkmark. + +Now we can move on to actually displaying different icons based on the user preference. + +## Displaying the user's preferred icon set + +At the top of our `buildMenu()` function, we're going to add some code to the `if` block used to set the correct icon mode (dark or light). + +First, if the user preference is set to `emoji`, we just need to append `-emoji` to the filename. + +```swift +@objc func buildMenu(key: String = "default") { + ... + // if iconPref is set to emoji + if (defaults.string(forKey: "iconPref") == "emoji") { + suffix += "-emoji" + } + ... +} +``` + +For the monochrome icons, we just need to make a few small adjustments. Since we want the white parts of the icon to represent the illuminated area of the moon, we can just swap the `new` and `full` icons, and also swap the `firstq` and `lastq` icons. + +For the other four phases (waxing and waning, crescent and gibbous), we're going to append `-alt` to the filename. + +```swift +@objc func buildMenu(key: String = "default") { + ... + // otherwise use monochrome icons + else { + // check for dark mode and set the right image file + if #available(OSX 10.14, *) { + let darkMode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") + + if darkMode == "Dark" { + // if theme is set to dark alt make adjustments + if (defaults.string(forKey: "iconPref") == "darkalt") { + switch (key2) { + case "new" : + key2 = "full" + break + case "full" : + key2 = "new" + break + case "firstq" : + key2 = "lastq" + break + case "lastq" : + key2 = "firstq" + break + case "wanc", "wang", "waxc", "waxg" : + suffix += "-alt" + break + default : + break + } + } + + suffix += "-dark" + } + } + } + ... +} +``` \ No newline at end of file