Looking for the exploded view of an electric saw I need to take apart and found some design sketches for a desk I made myself – has dimensions for a Macintosh IIci for the computer area 🙂
The other funny thing is the sketches are on the back of ImageWriter II (dot-matrix) printout of Pascal code from 1988 for an iChing app I did for a client 🙂
The Dissolve(…) routine call in it is calling out to a 68k asm routine I wrote to do a fast dissolve from an offscreen grafport to the screen – likely the first assembly language code I shipped in a commercial product 🙂
Anyway, likely only amusing to me as a reminder of a time in the past, but they were some good times so posting it here to save the smile they brought to my face.
PSA: For a great macOS app experience, the details matter. As an example, if you implement a custom macOS window titlebar for a document based app* don’t forget to implement the feature whereby you display a menu with the folder hierarchy for the document if the user right clicks, control+clicks or command+clicks the document title in the window titlebar.
Just ran across an app that failed to do this and it was quite annoying. I needed to open the folder containing the document in the Finder so I could duplicate the file and selecting the folder name in this menu from the document title is the easiest way to do that. Bug report filed with the app maker, but please save me the time of filing one against your app by getting this right from the beginning 😉
* Note that the Finder implements this for Finder windows, so I’d follow their lead and interpret “document” broadly as ‘anything with a location in the file system’.
AppleScript to eject mounted attached volumes/disks.
I have a MBP which I use with a Thunderbolt dock to connect to various USB and thunderbolt hard drives, SSDs, and optical drives. This script lets me eject and unmount anything physically connected before disconnecting my Thunderbolt dock.
Yah, sure, OWC makes a utility you can install that’ll do this, but it installs a kernel extension which I wasn’t keen on installing.
-- AppleScript I wrote to eject mounted USB disks before I disconnect my USB-C hub.
-- Notes:
-- this will halt a time machine backup to a locally mounted
-- drive when it tries to eject it.
-- will also halt a time machine backup to a network destination.
-- tested in macOS 11.6 (2021.11.19)
-- License: MIT
tell application "System Events"
set ds to get disks
-- for debugging
if false then
log "list of disks: "
repeat with d in ds
log (get the URL of d)
log (get the name of d)
end repeat
end if
log ("Ejecting local ejectable volumes")
repeat with d in ds
-- before we try to do anything with this disk
-- try to access the disk and catch any errors
-- as a way to test if the disk is still present.
-- set a flag that we can test below.
set diskAvailable to true
try
set trashme to (get the name of d)
on error errormsg number errorno
if errorno is -1728 then set diskAvailable to false
end try
-- Notes:
-- ejectable excludes sparse volume mounts for time
-- machine, but also means other ejectables aren't unmounted…
-- ...decided to remove this in the if clause below and let it halt
-- time machine backups even for network volumes also
-- since I'm generally calling this script when I'm disconnecting
-- the laptop and heading out.
-- d is not ejectable and ¬
-- starts with "Data@" excludes time machine local snapshot
-- mounts during backups
-- starts with file:///Volumes excludes file:///System/Volumes/...
-- system volumes.
-- local volume excludes network mounts
if diskAvailable and ¬
d is local volume and ¬
the URL of d starts with "file:///Volumes/" and ¬
the displayed name of d does not start with "Data@" then
log ("........ ejecting: " & (get the name of d))
tell application "Finder" to eject (get the name of d)
else if diskAvailable then
log (" Skipping: " & (the URL of d))
else
log (" Skipping disk that is no longer available")
end if
end repeat
log (" all ejectable local volumes ejected")
end tell
Posted as a gist here for easier copying and using.
AppleScript to move Safari windows up as much as the window closest to the top of the screen will allow.
There’s a bug in Safari 15.0 where it sometimes re-positions open windows when waking from sleep by moving all the windows down an amount such that the bottom of the lowest one is at the bottom of the monitor (or something like that? They all move down a lot).
If happen to place your Safari windows up enough so that another app’s window can be visible on-screen under them (chat in my case), and you have a few too many windows open in Safari (ahem!), then you might be very annoyed to have to over a hundred windows all back up again.
Now, you could go through your heap of open Safari windows, but if you’re a programmer than you might (likely incorrectly ;)) think it’d be faster to write an AppleScript to move them all back up again. Not that I’d do such a thing, you understand, but, purely for educational purposes, here’s the kind of script one might write (or the second version if the first one took 6 minutes process the > 100 Safari windows you had open) :). This one moves all Safari windows up the same amount to keep their relative positioning and moves them up as much as possible while still keeping the window positioned highest on the screen below the menu bar.
Disclaimer: I write zero to two AppleScripts a year and never really learned AppleScript. There’s almost certainly better ways to do this. But it works for me and fixes my windows after Safari munges them again. So GoodEnough™ 🙂
-- Put in the public domain with zero warranty of any kind
--
-- only tested on macOS 11.6, with Safari 13.0; ymmv
use scripting additions
tell application "Safari"
set windlist to windows
log "Examining " & length of windlist & " windows..."
set lowestYWindow to first item of windlist
set lowestYValue to get second item of (get bounds of lowestYWindow)
repeat with wind in windlist
set curValue to second item of (get bounds of wind)
if curValue < lowestYValue then
copy wind to lowestYWindow
set lowestYValue to curValue
end if
end repeat
-- subtract out the title bar height (hard coded - bad dog!)
set yOffset to lowestYValue - 25
if yOffset > 0 then
log "moving " & length of windlist & " windows up " & yOffset & " pixels"
repeat with aWind in windlist
set aBounds to (get the bounds of aWind)
set the second item of aBounds to ((get the second item of aBounds as integer) - yOffset)
set the fourth item of aBounds to ((get the fourth item of aBounds as integer) - yOffset)
set the bounds of aWind to aBounds
end repeat
else
display alert ("The highest window already at the top! Not moving any windows!")
end if
log "done"
end tell
I had to do some validation on a username while coding an iOS app today and so dove into regular expression matching. The specification said “Usernames can include upper and lower case letters, numbers, periods, and underscores” and I’m figuring they’re thinking ASCII only. RegEx of [a-zA-Z0-9_.]+ kind of thing. Thinking of my twitter friend @krzyżanowskim and his posts about not being able to include the proper ż in so many places, I wanted to do better.
So I dove into the world of unicode regular expressions and found this very useful resource. I think I’ve figured out what might be a decent answer (please let me know here or on Twitter @GeekAndDad if you have advice to offer :)).
To include all letters, accented letters, symbols, numbers, an underscore or period (.), but exclude all other punctuation or spaces, I came up with this bit of Swift code:
func validateUsername( username: String) -> Bool {
// length tests go here and return false on failure
let expr = #"^[\p{L}\p{M}\p{N}_.\p{Me}\p{S}]+$"#
let matchesRange = username.range(of: expr, options: .regularExpression)
if matchesRange == nil {
return false
}
// additional validation step here
return true
}
Note that I don’t have to check matchesRange‘s lowerBound and upperBound against the string’s startIndex or endIndex because I’ve included the ^ and $ to indicate that everything in the string must match the expression.
Seems to work so far. I’m impressed and grateful that Swift’s regular expressions support these unicode specifiers (!!!).
UPDATE: iOS 14.5.1 and macOS 11.3.1 have added the ability to print a list! Hallelujah!
––––––––––––––––––––––––––––––––––––
Damn cannot print a Reminders list for the store. Who doesn’t think to add printing?
@steveriggins
I need this capability for the same reason (shared food shopping list is in a shared Reminders list and because of COVID-19 will eventually go shopping without wanting to touch my phone while there). So I hacked together a quick AppleScript to grab all the not-yet-completed items from a list in Reminders and put the name of each reminder prefixed with “[ ] ” as a single line in a new TextEdit document (adding the notes for a reminder is left as an easy exercise for the reader 😉 ).
Here it is as a gist for easy use. And here’s a Feedback issue number if you’d like to report it and add a vote for Apple to fix this: FB7646521
tellapplication "Reminders"
set listNames to {}
repeatwith aList inlistscopy name of aList toendof listNames
endrepeatset listName tochoose from list listNames with prompt "Select the list to print:"
-- now find list object for the choosen list
set listToPrint to ""
repeatwith aList inlistsif name of aList asstringisequal to listName asstringthenset listToPrint to aList
exitrepeatendifendrepeat
-- log name of listToPrint as string
-- get a list of the names of all the reminders
set listItems toremindersof listToPrint
set reminderStrings to {}
repeatwith aReminder in listItems
if aReminder is not completed thenset reminderText to name of aReminder asstringcopy ("[ ] " & reminderText) toendof reminderStrings
endifendrepeat
-- make a single string out of the list of reminders
set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to "
"
set listText to reminderStrings astextset AppleScript's text item delimiters to TID
log listText
set listText to ((name of listToPrint asstring) & return & return & listText)
-- make a new text edit document to print
tellapplication "TextEdit"
make new documentsettextoffrontdocumentto listText
-- prints to your default printer
-- commented out since you may want to set formatting first.
-- print front document
endtellendtell
I often end up with a Safari window in front that I don’t want to close or minimize, but that I do want to send behind all other windows. Alas, Safari doesn’t have this command in the Window menu. I’ve requested that it be added (FB7642150) and you can also 😉
In the meantime, I don’t much like AppleScript so I don’t know it well, but this need finally hit the level where I was willing to suffer through the pain of AppleScript and hack something together (there’s probably a better way to write this). I put this into a simple Alfred workflow and it works great (yay!):
on alfred_script(q)
tell application "Safari"
local lastVisibleWindow
repeat with n from 1 to count of windows
if window n is visible then
set lastVisibleWindow to window n
else
exit repeat
end if
end repeat
set index of front window to index of lastVisibleWindow
end tell
end alfred_script
You can also use this script from a scripts menu, or other utility able to invoke applescripts, by removing the first and last lines that make it a handler for Alfred.
Tested and works under Safari 13.0.5 on macOS 10.15.3
This script handles the case where you have minimized windows. The “set index of the frontmost window to (count of windows)” type script you’ll find elsewhere online doesn’t work if you have minimized windows.
I wanted to log to the console in the middle of a SwiftUI chain but you can’t put “print” statements in SwiftUI code in most places (“Everything must be a View!” ;)). Since I was in a playground I couldn’t set a breakpoint that logged to the console (my standard strategy when in an application context). So I wrote this little thing:
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
Use it like so:
import Cocoa
import PlaygroundSupport
import SwiftUI
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello")
Text("World!")
.padding()
.background(
GeometryReader { proxy in
Color.clear
.printMessage("Info: ", proxy.size)
})
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PlaygroundPage.current.liveView = NSHostingView(rootView: ContentView())
Could well be a better way or issues with this, but it’s just something I put together quickly to solve a problem (“what is the value of proxy.size?”) and it seems to work, so thought I’d share it. Improvements or better ways to get the same capabilities are welcome 🙂
Xcode 11.3 & SwiftUI Previews: Dark Mode preview bug work-around, compact previews, multi-previews for ColorSchemes and Dynamic Text sizes.
Few small tips for previewing with SwiftUI that I just figured out.
Updated for Xcode 13 thanks to a tip from a reader
Here’s the simple test ContentView.swift file to use in a standard single view iOS SwiftUI app template project in Xcode 11.3 that we’ll use to explore these tips:
So, the first thing is WOW that takes a lot of space for just a small horizontal cell I’m working on (like for list row or whatever). It’s especially annoying if you’re trying to make sure it works well in dark mode, at different dynamic type sizes, etc. First tip is how to make that work better.
1 – How to preview a horizontal or smaller view in less space in the Xcode Canvas
Context: you’re working on a custom horizontal control, or a row in a list and you want to do multiple previews for light & dark mode, or different text size classes.
By default each preview gets an entire iPhone worth of chrome around it which means you have to scroll to check all your variations out – tiresome! Avoid this by using the .previewLayout modifier with a size that’s sufficient for your view. For example:
struct Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
}
}
This produces a much more manageable preview like this:
The dimensions you use in .previewLayoutwill be specific to whatever you’re working on, of course, but this comes in really handy when you are previewing multiple versions of a horizontal view like this. Let’s demonstrate that with our next tip by making Dark Mode preview in Xcode 11.3 usable outside a NavigationView.
2 – Dark Mode Preview only seems to really work in NavigationViews
As pointed out by a reader (thanks Shaps!), as of iOS 13 Apple has added a proper view modifier for this so we don’t have to set environment keys (doing so is fine as this is a documented environment key, but, as noted in the documentation, doing it this way doesn’t propagate to the presentation containing the view of the view it’s applied to and the new view modifier does). So instead of .environment(\.colorScheme, .dark) line the code below has was using and the work around for the presenting view not being in dark mode, I’ve changed the approach below to use this new view modifier:
.preferredColorScheme(.dark)
Here’s an example:
struct PreviewsActuallyDarkMode: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
.preferredColorScheme(.dark)
}
}
which shows our preview in dark mode as we want:
3 – Combining previews and reducing duplicate code
So now we have dark mode looking good so let’s combine the preceding two tips and look a both light and dark modes on the same screen and without having to scroll:
struct Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
}
}
struct PreviewsActuallyDarkMode: PreviewProvider {
static var previews: some View {
ZStack {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
.preferredColorScheme(.dark)
}
}
Which looks like this:
Looking pretty good! While writing them in separate PreviewProviders like that helpfully uses their struct names to label them, it does involve a bit of duplicated code. Here’s an alternate way to do it with a ViewModifier to put shared modifiers in and a Group of views which Xcode makes each their own Preview:
This gives us four previews and adds the ability to name them since they’re all in the same PreviewProvider struct and so don’t get named individually any longer. Looks like this now:
Well, this “tiny tips” got a little long… but hopefully you found these small tips to be useful.
Just a simple utility to print a string in the middle of a view chain when doing SwiftUI (pardon narrow formatting for blog):
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
Use like this:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack {
TestView(label: "hello", idx: 22)
.font(.title)
TestView(label: "hello", idx: 24)
.foregroundColor(Color.red)
Spacer()
}
}
}
struct TestView: View {
let label: String
let idx: Int
var body: some View {
Text(label)
.padding(10)
.printMessage("\(self.label):", "\(self.idx)")
// or: .printMessage(self.label, self.idx)
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
// In a Swift playground the output is:
hello: 22
hello: 24
hello: 22
hello: 24
hello: 22
hello: 24