From 04941a84c6b6a4c0288086bec66336866950d076 Mon Sep 17 00:00:00 2001 From: Claire Davis Date: Thu, 13 Oct 2022 12:37:09 -0700 Subject: [PATCH] added outline to section headers --- Home.md | 40 ++++++++++++++++++++-------------------- Swift-Windows.md | 22 +++++++++++----------- User-Prefs.md | 10 +++++----- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Home.md b/Home.md index 57d14a2..0976b27 100644 --- a/Home.md +++ b/Home.md @@ -5,7 +5,7 @@ * [Windows with Swift](/wiki/Swift-Windows) * [Setting (and retrieving) user preferences](/wiki/User-Prefs) -## Introduction +## I. 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. In 2014, Apple introduced a new programming language called [Swift](https://www.apple.com/swift/). This is a high-level language using *managed code*, meaning the language runs on top of a framework, very similar to Microsoft's .NET. This also makes it possible to use C libraries (including C++ and Objective-C) in your Swift projects, which can be pretty useful depending on what you're doing. @@ -16,7 +16,7 @@ Swift does things very differently from .NET. It's strongly-typed to an extreme, And with that, let's look at how the code for *this* project works. -## The Plan +## II. The Plan I'm one of those people who cares about the moon phases. I like to know what phase the moon is in at a glance. I found [this project on GitHub](https://github.com/ColdBio/Moonu-Phases), but it requires Python - and enough knowledge of Python to actually get it to work. I fiddled with it for a day or so, and decided to instead try my hand at writing my own tool. My needs are minimal: @@ -28,12 +28,12 @@ My needs are minimal: * About the app * Quit the app -## Setting up Xcode project +## III. Setting up Xcode project I'll be using Xcode 10.3 and Swift 5.0.1, on a Mac running MacOS 10.14.6. We'll start with a blank Swift project. In Xcode, create a new project, and use the **Cocoa App** project template under **MacOS**. Give your project a name and publisher details, and make sure **Language** is set to **Swift**, and **Use Storyboards** is ***unchecked***. Storyboarding is a rather complex Apple-designed means of creating UIs and attaching behavior (backend code) to those UI elements. Since the main application is a status bar icon, we're building the initial UI programmatically, so we don't need to use this feature. Before we get into the code, let's get the application's icons out of the way, since it's not exactly a straightforward procedure. -### Menu bar icons +### A. Menu bar icons LunaMac v1 used emoji for the menu bar icon. v2 introduced handmade icons, with support for both light and dark MacOS themes. This is where we run into one of Apple's fun quirks: you can't package an app for distribution if any of its files contain extended MacOS file attributes, but Xcode doesn't strip these attributes when you import files with MacOS attributes. @@ -55,7 +55,7 @@ If all went well, you *should* get no results. If something went wrong, try agai 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 +### B. Application icon Apple loves its ultra-high-res icons, so you're going to need a set of icon images (generally transparent PNGs or TIFFs). [This StackOverflow thread](https://stackoverflow.com/questions/12281309/where-is-the-iconset-command-line-tool-iconutil-located) goes into some detail regarding icon dimensions and PPI. If you want pixel-perfect icons of all dimensions and densities, you'll want to manually create the following files: @@ -89,14 +89,14 @@ If you plan on releasing your app in Apple's App Store, you also need to set up To configure your project to use the `Icon.icns` file you imported, edit the existing `Info.plist` file. Add an `Icon file` key if one doesn't already exist, and set the value to Icon (no file extension required).
-### Removing extraneous files +### C. Removing extraneous files This project runs out of the systemwide menu bar, which means we don't need the default window and main menu that are part of `MainMenu.xib`. Open this file, and before we do anything else, drag the lefthand pane of the view so you see the full tree, rather than a handful of nondescript icons. Now you can see both `Main Menu` and `Window` in the list of objects. Select each and delete it by hitting backspace (or delete).
-### Removing references to deleted content +### D. Removing references to deleted content You'll find a default line at the beginning of your `AppDelegate` class: @@ -113,7 +113,7 @@ Delete this line - this is for loading the default application window, which we Whew. Now that's out of the way, let's get into the fun(ish) stuff. -## Creating a menu bar icon +## IV. Creating a menu bar icon I started with [this tutorial](https://8thlight.com/blog/casey-brant/2019/05/21/macos-menu-bar-extras.html) ([archive](https://archive.ph/bNVdG)). We need to start with kicking off the creation of the menu when the application launches. @@ -201,7 +201,7 @@ Now we can actually set the icon image (using [NSImage](https://developer.apple. We have an icon! If you want to test your application now, we have enough code for it to do something. Hit the play button in Xcode, and in a moment, you'll see your menu bar icon, as well as the name of your application in the menu bar. Since nothing is set up to actually *do* anything yet, hit the stop button in Xcode to terminate your running application. -## Hiding the dock icon and application menu +## V. Hiding the dock icon and application menu We deleted the regular application menu bar, but the app still loaded the menu bar. You might have noticed it also appears in the dock when running. Since this application is just going to run from the menu bar's status area, we can disable the icon and regular menu bar. @@ -209,7 +209,7 @@ Open `Info.plist` and click the plus sign next to **Information Property List** ![](https://abettergeek.com/_media/wiki/git/lunamac/serviceplist.png) -## Building a menu for the icon +## VI. Building a menu for the icon Since we have no regular application menu in this project, we need to add a menu to our menu bar icon. To get started, we'll just add a "Quit" menu item. @@ -257,7 +257,7 @@ Run your application, and you should now have a menu that works! You might be wondering how I have a dark mode-compatible menu bar icon. It's a perfect segue into the more advanced aspects of this project.
-## Automatically setting the dark mode icon +## VIII. Automatically setting the dark mode icon To automatically choose the correct icon for light or dark themes, we need to look at a system-wide variable under [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults). This doesn't exist prior to MacOS 10.14, so we'll wrap it in an OS version check just to be safe. @@ -308,9 +308,9 @@ func themeListener() { } ``` -## Calculating the right lunar phase +## VIII. Calculating the right lunar phase -### Overview +### A. Overview I can't take credit for figuring out the math on this. I was lucky to find [this excellent PHP implementation](https://minkukel.com/en/various/calculating-moon-phase/) ([archive](https://archive.ph/hjXlv)), which lays out the math in detail. @@ -326,7 +326,7 @@ Here's the overview: This is all just basic arithmetic, so let's get into the Swift implementation. -### Implementation +### B. Implementation Since we're going to want this app to stay updated automatically, the math is going into a function named `updateIcon()`. This is the `key` we're going to pass to the `buildMenu()` function we already built. @@ -389,7 +389,7 @@ Apple does at least provide a useful function here to do the date math for us, a } ``` -## Using the moon phase to build the menu +## IX. Using the moon phase to build the menu Now that we have `currDays`, we need to find the right moon phase. I created a [`Dictionary`](https://developer.apple.com/documentation/swift/dictionary) (an unordered array of key-value pairs) named `phaseArray` in my `AppDelegate` class containing all the information I need to calculate the correct phase and display the right icon. @@ -466,7 +466,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } ``` -### Searching the dictionary +### A. Searching the dictionary To find the current phase, we're going to use the [`Dictionary.filter()`](https://developer.apple.com/documentation/swift/dictionary/filter(_:)) method of `phaseArray`. This method can take a function to filter for the correct entry, and will return a dictionary of the same type as the original one. @@ -485,7 +485,7 @@ Swift is strongly-typed, so we need to use the same number type for every argume } ``` -## Building the menu +## X. Building the menu Back in our `updateIcon()` function, we're first going to create a new dictionary to hold our filter results. Then we're going to populate this dictionary by calling [`Dictionary.filter()`](https://developer.apple.com/documentation/swift/dictionary/filter(_:)) with our filter function, `findPhase()`. @@ -581,7 +581,7 @@ Next, we're going to add a menu item for manually updating the moon phase, just } ``` -## Updating `applicationDidFinishLaunching()` +## XI. Updating `applicationDidFinishLaunching()` Now that `updateIcon()` calls `buildMenu()`, we need to take a quick detour back to `applicationDidFinishLaunching()` to change our `buildMenu()` call to `updateIcon()`. Your launch function should look like this now: @@ -596,7 +596,7 @@ func applicationDidFinishLaunching(_ aNotification: Notification) { } ``` -## Updating the lunar phase in the background +## XII. Updating the lunar phase in the background This application is meant to run silently in the background, with just one unobtrusive menu bar icon. For the icon to stay updated, we need to add a timer that reruns `updateIcon()` regularly. @@ -637,7 +637,7 @@ func applicationDidFinishLaunching(_ aNotification: Notification) { } ``` -## Preventing duplicate menu bar icons +## XIII. Preventing duplicate menu bar icons Version 1.3 of LunaMac includes a small, but annoying bug - if you are using the icon's menu when the timer turns over and reruns the menu script, a second icon will appear in the status area, until you click outside the menu to close it. This is because our [`Timer`](https://developer.apple.com/documentation/foundation/timer) is still running when the menu is open, and if it turns over when the status icon is in use, a second icon will appear, and when you close the menu, the first icon disappears from the status area. diff --git a/Swift-Windows.md b/Swift-Windows.md index c421d9f..f19fee8 100644 --- a/Swift-Windows.md +++ b/Swift-Windows.md @@ -6,17 +6,17 @@ * Windows with Swift (this page) * [Setting (and retrieving) user preferences](/wiki/User-Prefs) -## Introduction +## I. Introduction I'm just going to preface this by saying: holy hell, making windows work in Xcode and Swift is no small feat. This project features two windows: one that shows some basic information about the application, and another that shows the application's changelog, which will be loaded from a text file packaged with the application. -## The "About" window +## II. The "About" window -### Building the window +### A. Building the window Hit **⌘+N** or right-click in the project navigator to add a new file to your project. Select **Window** in the **User Interface** section. Click **Next**, give your new file a name, and click **Create**. My file is named `InfoBox.xib`. @@ -28,13 +28,13 @@ Make sure you've selected the **Window** (not the **View** under the window), an Under **Title**, enter a display title (mine is "About LunaMac"), and under **Controls**, uncheck the boxes next to **Minimize** and **Resize**. Finally, make sure **Visible at launch** is checked. This refers the launch of the *window*, not the *application*. -### Adding the controls +### B. Adding the controls To add content to the window, we need to bring up the **Objects Library** - you can find this under **View** > **Libraries** > **Show Library** from the application menu, hit **Shift+⌘+L**, or click the **Libraries** icon in the toolbar of your Xcode window (it looks like a square inside a circle). Search for the word **label**, and drag a **Label** to the window. Change the **Title** attribute to your application name. Drag a **Multi-line Label** to the window, and position it below the label we just created. Change the **Title** attribute to a short summary (or other information) about your application. The application icon in this window also serves as a button to open the changelog window. Open the **Objects Library** again, and search for and add an **Image Button**. Change the **Image** attribute of this button to the name of your application icon (in my case, `LunaMac`). Resize and position this button to your liking. -### Activating the window +### C. Activating the window We're going to go back to our main `AppDelegate.swift` file. In your `buildMenu()` function, add a new menu item above the **Quit application** menu item. This will activate a new `showAbout()` function when clicked. @@ -74,7 +74,7 @@ Launch your application, show the "About" window from your icon's menu, and bask This took awhile to figure out, so maybe I can save you the headache of blindly searching for answers and details. -### Preventing window duplication +### D. Preventing window duplication Swift follows the MVC model, in which models, views, and controls are separate components of your application. Because of this, we need to use an instance [`NSViewController`](https://developer.apple.com/documentation/appkit/nsviewcontroller), which will give us access to window triggers that fire when the window is opened, closed, changed, etc. @@ -170,7 +170,7 @@ If you look at the source code (XML) view for `InfoBox.xib`, there's now a ` ``` -### Tracking the window status +### A. Tracking the window status Displaying the Changelog window also requires a [`Bool`](https://developer.apple.com/documentation/swift/bool) value to track whether it's already open, so add one to your `AppDelegate` class definition in `AppDelegate.swift`. @@ -256,7 +256,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } ``` -### Handling the view controller +### B. Handling the view controller Then, add the code to `toggleWindow()` in `ViewController.swift` to handle showing the changelog window, similar to how we handled the info window. @@ -274,7 +274,7 @@ func toggleWindow(boolShow: Bool = false) { Now you can launch your application and access both the info and changelog windows. We're in the home stretch! -### Loading the changelog file +### C. Loading the changelog file In our `ViewController` class, we're going to override [`NSViewController.viewDidAppear()`](https://developer.apple.com/documentation/appkit/nsviewcontroller/1434455-viewdidappear) to load and display the contents of `changelog.txt` in our changelog window. diff --git a/User-Prefs.md b/User-Prefs.md index ede69a6..cb6712b 100644 --- a/User-Prefs.md +++ b/User-Prefs.md @@ -5,11 +5,11 @@ * [Windows with Swift](/wiki/Swift-Windows) * Setting (and retrieving) user preferences (this page) -## Introduction +## I. 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 +## II. 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). @@ -50,7 +50,7 @@ Once we set our user preference, we'll run `updateIcon()`, which will rebuild th } ``` -## Adding new icons +## III. Adding new icons @@ -59,7 +59,7 @@ Next, we need to add icons for these new icon sets. As you'll see below, the alt Remember to run `xattr -cr .` after adding new images to your project.
-## Creating the submenu +## IV. Creating the submenu @@ -180,7 +180,7 @@ You can run your application now, and test the user setting. Change the setting, Now we can move on to actually displaying different icons based on the user preference. -## Displaying the user's preferred icon set +## V. 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).