What is a JSON feed? Learn more

JSON Feed Viewer

Browse through the showcased feeds, or enter a feed URL below.

Now supporting RSS and Atom feeds thanks to Andrew Chilton's feed2json.org service


And now it’s all this

I just said what I said and it was wrong. Or was taken wrong.


Message Image to Family

Permalink - Posted on 2018-08-19 14:42

Over the past several weeks, I’ve been cleaning up my older Pythonista scripts, TextExpander snippets, and Workflow macros.1 Some of this is in anticipation of iOS 12 and Shortcuts; some is to revive old scripts that don’t work in Pythonista 2/Python 3; and some is just because months of use has shown me that things could be done better. It’s probably time to start posting them.

Let’s start with the simplest one, a variation on a Messages workflow I wrote about last year. In this one, instead of quickly messaging URLs to my family, I message images to them.

It works like this: I take a photo or a screenshot that I want to send to some or all of my family. In the Photos app, I select the image, bring up the share sheet, tap Run Workflow, and choose Message Image to Family from the list of my action extension workflows. Up pops a list of my family, and I choose which ones to send the image to.

Choose from list

This choosing step could be skipped if I always wanted to send images to everyone in my family, but I don’t. Some go just to my wife, some to just my wife and daughter, some to just my sons, etc. The choosing step gives me flexibility.

After I tap Done, I’m switched to Messages, and the image is there in a new message in the proper thread, waiting for me to add text (if I want to) and hit the send button. The biggest advantage of sharing images this way is that I don’t have to hunt through Messages for the right thread.

The workflow itself is pretty simple:

Message Image to Family

The workflow starts by taking the image it’s invoked on and saving it to the variable TheImage. We’ll bring this back later.

The list of recipients is constructed from the Contacts action. When building the workflow, I added family members to the list one at a time by tapping the plus button and selecting them.

This short list of family members is the input to the Choose from List action, which is what lets me select who gets the message. Enabling the Select Multiple option is what gives this action so much flexibility. The set of chosen recipients is then saved in the TextRecipients variable.

Finally, we construct the message by referencing the TheImage variable saved at the top of the workflow and feeding it to the Send Message action. The action’s Recipients parameter is set to the TextRecipients variable.

This workflow is a little clumsy in that it doesn’t really “flow.” The image has to be taken out of the flow to get the list of recipients and then reinserted just before the Send Message action. It would be cleaner if the image could flow “around” the Contacts block directly into the Send Message action, but I don’t think the straight line paradigm of Workflow allows that.

  1. I will not call them “Workflow workflows.” 

[If the formatting looks odd in your feed reader, visit the original article]

That doesn’t count: we didn’t believe you

Permalink - Posted on 2018-08-18 20:52

Inspired by this week’s obviously untrue explanation by the White House that John Brennan’s security clearance was revoked because of his erratic behavior, I’ve added a new entry to my Copied list of frequently used images:

Maltese Falcon lie

It’s from The Maltese Falcon, both the film and the book, in the scene where Spade visits Brigid Wonderly/Leblanc/O’Shaughnessy after Miles Archer is murdered. (“When a man’s partner is killed he’s supposed to do something about it.”)

I should have added it long ago, after James Comey was fired, which was said to be because he was unfair to Hillary Clinton during the campaign. Or when we were told the Don Jr./Jared/Manafort meeting with the Russians was about adoption policies. Many missed opportunities, but the great thing about the Trump administration is that there will always be more. The only question is who’s taking the fall.

[If the formatting looks odd in your feed reader, visit the original article]

A small thing

Permalink - Posted on 2018-08-17 01:02

So often it’s the little things. A few months ago, I made a small change in how I pack for overnight trips, and it’s eliminated a nagging doubt I used to have.

I use the Clear Care system to disinfect my contact lenses. Every night, I put the lenses in a plastic holder which then goes into a cup of hydrogen peroxide solution.

Lens holder and cup

The holder is integral with the screw-on cap. The left lens goes in the holder on the white side, the right lens on the blue side. Before putting the holder into the cup and screwing it tight, I fill the cup with solution up to the embossed line. And that’s where the doubt came in.

Clear Care’s travel bottles contain 3 ounces of solution, the maximum allowed by the TSA. The problem is that the bottles are opaque, so after I’d used one a few times, I wouldn’t be sure how much was left in the bottle. I could shake the bottle and kind of tell if there was more or less than half left, but beyond that I had no idea. So I’d often find myself standing in my bathroom while packing for a trip, trying to decide whether there was likely to be enough in the bottle to make it through however many nights I’d be away. More often than not, I’d pack a new bottle just to be sure. The solution wouldn’t go to waste—I’d use the travel bottle at home until it was empty—the waste was of my time and attention.

Eventually, I realized that this was both stupid and easily solved. One day, when I had just replaced an old cup and holder with a new one, I took the old cup into work with me to do some measuring.

One of the advantages of working in a company with a chemist is that she keeps a variety of graduated cylinders in the lab cabinets. I didn’t take long to learn that the contacts cup is 9.6 ml up to the embossed line. As I said, the travel bottle is 3 oz or 90 ml, so in theory, I could get 9 nights of use from the travel bottle. But because I sometimes fill the cup beyond the line—and I really don’t want to find myself at a hotel late at night without enough cleaner—I limit myself to 8 nights per bottle.

What sophisticated technological system do I use to keep track of how much solution I’ve used? I write “8” in on the side of each travel bottle and make a tally mark every time I use it. I keep a blue Sharpie in my shaving kit for this.

Clear Care travel bottle

As long as I can subtract, I’ll have peace of mind.

[If the formatting looks odd in your feed reader, visit the original article]

Apple quarterly sales

Permalink - Posted on 2018-08-02 02:55

I was traveling for business yesterday, so I’m even later than usual with my take on Apple’s sales. By now, I’m sure you’ve already seen the many charts at MacStories and Six Colors, but I still like to post my own. It gives me a chance to try out new ways of showing the data.

I started out plotting only the four-quarter moving averages because I wanted to show how trends in jumpy data can be better shown through smoothing. Later, I included the raw sales figures but tried to keep them from overwhelming the moving averages. This time, I’ve added a faint dashed line to point out the year-over-year changes in the raw sales data. I’m trying to squeeze more useful information into the graphs without making them so cluttered that the main point is lost.

Here are the unit sales for the devices Apple breaks out individually:

Apple sales

The iPhone dominates this graph, especially since 2013, when the iPad peaked and started its three-year slide. To better see what’s going on with the iPad and the Mac, we need to plot them by themselves.

iPad sales

The iPad is still showing modest gains after bottoming out a little over a year ago. As for the Mac…

Mac sales

Ugh. Everyone has pointed out that this quarter had the worst Mac unit sales since 2010. This is true, but as you can see, the June quarter of 2013 was about as bad—3.754 million units compared to the most recent quarter’s 3.740 million. “Worst quarter since waaay back in 2010” makes for a better story.

I don’t think there’s much mystery to the Mac’s poor sales. As Marco Arment said in a series of tweets today, Apple let its notebook line (which drives Mac sales) go fallow for too long, and it’s caught up with them. I see it as being similar to, albeit less dramatic than, what happened to the iPad during its slide. With the iPad, Apple’s neglect was primarily on the software side; with Mac notebooks, there’s been neglect on both the software and hardware sides.

Even if the quality of the Mac is now improving, and there are good reasons to think it is, sales are unlikely to bounce back for few quarters. These are big investments and it takes time for word to spread. Recall that the iPad sales continued to decline for about a year even after the big improvements that came with the iPad Pro and iOS 9. Sales tend to be a trailing indicator of product quality. This is especially true after your indifference to a product line has trained your customers to be skeptical.

[If the formatting looks odd in your feed reader, visit the original article]

Python and platforms

Permalink - Posted on 2018-07-30 18:26

After my last post on the scripts that generate my Apple sales graphs (which I’ll need again when the new quarterly results are posted tomorrow), I was shamed by Rosemary Orchard and Nathan Grigg into rewriting them to run on both macOS and iOS (the latter via Pythonista). It turned out to be pretty easy, with only a couple of additions needed.

I won’t bore you with the entire script, I’ll just bore you with the new stuff that lets the script run on both platforms. There were two changes.

First, since there is no OptiPNG for iOS, this line in the original script,

subprocess.run(['optipng', pngFile])

which processed the just-saved file on my Mac, had to go. The fix, though, wasn’t too much different: just run OptiPNG on the server after the file was uploaded. I already had an SSH connection to the server, so all I had to do was add this line,

sshIn, sshOut, sshErr = ssh.exec_command("optipng '{}'".format(pngDest))

before closing the connection. The exec_command function is part of the paramiko SSH library, and pngDest is a variable that contains the full path on the server to the newly uploaded PNG file.

The second change was really more of an addition than a change. I decided I wanted the script to put the uploaded PNG’s URL on the clipboard to make it easy to paste into a blog post. This is easy to do on the Mac, where you just use subprocess to run the pbcopy command;1 and it’s even easier to do on iOS, where Pythonista includes the handy clipboard library. The problem comes in figuring out which device the script is running on.

(It’s possible this problem has already been solved and is sitting in GitHub repository, but I didn’t find it. Al Sweigart’s pyperclip is a cross-platform library for moving text to and from the clipboard, but the platforms it crosses are the traditional Windows, Mac, and Linux—no iOS.)

At first, I thought calling os.name would give me what I needed, but it returns posix on both macOS and iOS. After a bit of searching, I hit upon the platform library, which I’d never used before. In particular, the platform.platform() function returns the string


on my 2012 iMac at home,


on my 2017 iMac at work,


on my iPad Pro, and


on my iPhone 6s. I don’t understand why the minor Darwin number is one less on the home iMac than it is everywhere else, but it doesn’t matter for what I need here. The key is that Macs have “x86” in the string (at least for now) and it’s a safe bet that iOS devices never will.

Thus, the following section of code at the end of my script:

if 'x86' in platform.platform():
  import subprocess
  subprocess.Popen('pbcopy', stdin=subprocess.PIPE).communicate(pngURL.encode())
  import clipboard

As its name implies, pngURL is a string that contains the URL of the just-uploaded and optimized PNG file. Applying encode to it before sending it to communicate is necessary because communicate takes only bytes.

Oh, there is one more thing. I used to keep the Apple sales scripts and data files in a Dropbox folder, but now I have them in a folder on iCloud Drive. Pythonista’s external files feature can link to individual Dropbox files, but I couldn’t get it to link to a folder, and I kept getting errors whenever a script on Dropbox tried to save a file. Both of these problems disappeared when I moved everything to iCloud.2

So thanks to Rosemary and Nathan for the nudge. I didn’t think I wanted an iOS-resident solution, but now that I have it, it seems pretty neat.

  1. That link should go to Apple’s online man page for the pbcopy command, but Apple has decided either to delete its man pages or move them where neither I nor Google can find them. Have I complained about this before? Yes, and I’m complaining about it again. 

  2. I’ve been thinking about switching entirely from Dropbox to iCloud, but that’s a topic for another post. 

[If the formatting looks odd in your feed reader, visit the original article]

Plotting my Apple sales plots

Permalink - Posted on 2018-07-26 03:03

Last week, Jason Snell wrote a nice post on how he automated his system for producing and uploading the many charts he makes whenever Apple posts its quarterly results. It’s a Mac-centric system, based on Numbers, Image Magick, SCP1, and Automator. A few days later, in a heroic Twitter thread, Jason took up the challenge of creating an iOS workflow that did the same thing. I’ve been expecting a blog post with full writeup, but there’s been nothing so far. Maybe he’s cleaning it up around the edges.2

I am not heroic. The creation of my charts is automated automatically (so to speak) because they come from Python scripts. And I can generate them from iOS through the simple expedient of using Prompt to log into my Mac from my iPad or iPhone and then typing the name of the necessary script at the command line.

What’s that? You don’t think logging onto a Mac and running a Terminal command is “generat[ing] them from iOS”? Well, I’ve already told you I’m not heroic; you shouldn’t be surprised that I’m also a cheater.

Cheater or not, I was inspired by Jason to add a few things to my Apple chart-making scripts. Two of them, reducing the size of the graphic via OptiPNG and uploading the resulting file through STFP, were things I used to do at the command line after making a chart. Another was analytic: to the raw sales and four-quarter moving average, I added a companion line that tracks the year-over-year sales associated with the current quarter. People seem to like year-over-year data, and it was easy to include. Finally, I thought the branding joke of putting my giant snowman head in a prominent place of every graph had worn thin, so I made the head much smaller and tucked it into a less conspicuous spot.

The result, still using the results reported three months ago, looks like this:

Apple sales

The year-over-year tracking is done by making those raw sales dots slightly bigger than the others and connecting them with a thin, faint, and dashed line. My goal was to maintain the prominence of the moving average while making easier to see the year-over-year changes. One thing I’d never noticed before was that the Jan-Mar quarter used to have above average iPhone sales but hasn’t since 2015.

The plot above was made to compare the three product lines. Because the iPhone sets the plot’s scale, it also serves as a decent plot of the iPhone itself. But it’s terrible at showing the evolution of iPad and Mac sales, so I also make individual plots for them.

iPad sales

Mac sales

The data is kept in files that look like this,

2016-Q1 2015-12-26  74.779
2016-Q2 2016-03-26  51.193
2016-Q3 2016-06-25  40.399
2016-Q4 2016-09-24  45.513
2017-Q1 2016-12-31  78.290
2017-Q2 2017-04-01  50.763
2017-Q3 2017-07-01  41.026
2017-Q4 2017-09-30  46.677
2018-Q1 2017-12-30  77.316
2018-Q2 2018-03-31  52.217

with one line per quarter, with the quarter’s name, end date, and sales separated by whitespace. There’s a file like this for each of the devices.

The script I use to make the first plot is this:

  1:  #!/usr/bin/env python
  3:  from datetime import date, datetime
  4:  from sys import stdin, argv, exit
  5:  import numpy as np
  6:  import matplotlib.pyplot as plt
  7:  import matplotlib.dates as mdates
  8:  from matplotlib.ticker import MultipleLocator
  9:  from PIL import Image
 10:  import paramiko
 11:  import subprocess
 13:  # Initialize
 14:  phoneFile = 'iphone-sales.txt'
 15:  padFile = 'ipad-sales.txt'
 16:  macFile = 'mac-sales.txt'
 17:  firstYear = 2010
 18:  today = date.today()
 19:  baseFile = today.strftime('%Y%m%d-Apple sales')
 20:  pngFile = baseFile + '.png'
 21:  pdfFile = baseFile + '.pdf'
 22:  dest = '/path/to/images{}/{}'.format(today.strftime('%Y'), pngFile)
 24:  # Read the given data file and return the series.
 25:  def getSeries(fname):  
 26:    global lastYear, lastMonth
 27:    dates = []
 28:    sales = []
 29:    for line in open(fname):
 30:      if line[0] == '#':
 31:        continue
 32:      quarter, edate, units = line.strip().split('\t')
 33:      units = float(units)
 34:      qend = datetime.strptime(edate, '%Y-%m-%d')
 35:      dates.append(qend)
 36:      sales.append(units)
 37:    ma = [0]*len(sales)
 38:    for i in range(len(sales)):
 39:      lower = max(0, i-3)
 40:      chunk = sales[lower:i+1]
 41:      ma[i] = sum(chunk)/len(chunk)
 42:    return dates, sales, ma
 44:  # Make new series with just the latest quarter for every year.
 45:  def getYoY(d, s):
 46:    dyoy = list(reversed(d[::-4]))
 47:    syoy = list(reversed(s[::-4]))
 48:    return dyoy, syoy
 50:  # Read in the data
 51:  phoneDates, phoneRaw, phoneMA = getSeries(phoneFile)
 52:  padDates, padRaw, padMA = getSeries(padFile)
 53:  macDates, macRaw, macMA = getSeries(macFile)
 54:  phoneDatesYoY, phoneRawYoY = getYoY(phoneDates, phoneRaw)
 55:  padDatesYoY, padRawYoY = getYoY(padDates, padRaw)
 56:  macDatesYoY, macRawYoY = getYoY(macDates, macRaw)
 58:  # Tick marks and tick labels
 59:  y = mdates.YearLocator()
 60:  m = mdates.MonthLocator(bymonth=[1,
 61:   4, 7, 10])
 62:  yFmt = mdates.DateFormatter('                %Y')
 63:  ymajor = MultipleLocator(10)
 64:  yminor = MultipleLocator(2)
 66:  # Plot the raw sales data and moving averages.
 67:  # Connect the year-over-year raw data.
 68:  fig, ax = plt.subplots(figsize=(8,6))
 69:  ax.plot(phoneDates, phoneMA, '-', color='#7570b3', linewidth=3, label='iPhone')
 70:  ax.plot(phoneDates, phoneRaw, '.', color='#7570b3')
 71:  ax.plot(phoneDatesYoY, phoneRawYoY, '.', color='#7570b3', markersize=8)
 72:  ax.plot(phoneDatesYoY, phoneRawYoY, '--', color='#7570b3', linewidth=1, alpha=.25)
 73:  ax.plot(padDates, padMA, '-', color='#d95f02', linewidth=3, label='iPad')
 74:  ax.plot(padDates, padRaw, '.', color='#d95f02')
 75:  ax.plot(padDatesYoY, padRawYoY, '.', color='#d95f02', markersize=8)
 76:  ax.plot(padDatesYoY, padRawYoY, '--', color='#d95f02', linewidth=1, alpha=.25)
 77:  ax.plot(macDates, macMA, '-', color='#1b9e77', linewidth=3, label='Mac')
 78:  ax.plot(macDates, macRaw, '.', color='#1b9e77')
 79:  ax.plot(macDatesYoY, macRawYoY, '.', color='#1b9e77', markersize=8)
 80:  ax.plot(macDatesYoY, macRawYoY, '--', color='#1b9e77', linewidth=1, alpha=.25)
 82:  # Add a grid.
 83:  ax.grid(linewidth=1, which='major', color='#dddddd', linestyle='-')
 85:  # Set the upper and lower limits to show all of the last year in the data set.
 86:  # Add a year if the sales are for the last calendar quarter.
 87:  lastYear = macDates[-1].year
 88:  lastMonth = macDates[-1].month
 89:  if lastMonth == 12:
 90:    lastYear += 1
 91:  plt.xlim(xmin=date(firstYear, 1, 1), xmax=date(lastYear, 12, 31))
 92:  plt.ylim(ymin=0, ymax=80)
 94:  # Set the labels
 95:  plt.ylabel('Unit sales (millions)')
 96:  plt.xlabel('Calendar year')
 97:  t = plt.title('Raw sales and four-quarter moving averages')
 98:  t.set_y(1.03)
 99:  ax.xaxis.set_major_locator(y)
100:  ax.xaxis.set_minor_locator(m)
101:  ax.xaxis.set_major_formatter(yFmt)
102:  ax.yaxis.set_minor_locator(yminor)
103:  ax.yaxis.set_major_locator(ymajor)
104:  ax.set_axisbelow(True)
105:  plt.legend(loc=(.08, .72), borderpad=.8, fontsize=12)
106:  fig.set_tight_layout({'pad': 1.5})
108:  # Save the plot file as a PNG and as a PDF.
109:  plt.savefig(pngFile, format='png', dpi=200)
110:  plt.savefig(pdfFile, format='pdf')
112:  # Add the logo to the PNG and optimize it.
113:  plot = Image.open(pngFile)
114:  head = Image.open('snowman-head.jpg')
115:  smallhead = head.resize((60, 60), Image.ANTIALIAS)
116:  plot.paste(smallhead, (1496, 26))
117:  plot.save(pngFile)
118:  subprocess.run(['optipng', pngFile])
120:  # Upload the PNG
121:  ssh = paramiko.SSHClient()
122:  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
123:  ssh.connect(hostname='host.com', username='user', password='password', port=6789)
124:  sftp = ssh.open_sftp()
125:  sftp.put(pngFile, dest)

Much of the script has been explained in that earlier post. Here, I’ll just discuss the new stuff.

The getYoY function in Lines 45–48 uses slices to create the lists of dates and sales for the year-over-year subset of the full data. The slice itself, [::-4], works backward from the end of the list, so I included the reversed function to put the year-over-year lists in chronological order. This isn’t necessary for plotting, but I figured future me would expect all of the lists to be in the same order if he was going to do something else with them.

Lines 71, 75, and 79 plot the year-over-year quarters with a slightly larger dot. This dot goes over and hides the raw data dots that are plotted in Lines 70, 74, and 78. Lines 72, 76, and 80 plot thin (linewidth=1), faint (alpha=.25), dashed ('--') lines connecting the year-over-year dots.

After the plot is saved as a PNG (Line 101), I use the Python Imaging Library to add my head near the upper right corner (Lines 113–117). Then I run the file through OptiPNG via the subprocess library.

Finally, in Lines 121–125, I use the paramiko library to establish an SSH connection to the server and upload the file via SFTP. The file name uses the current date as a prefix (Line 19), and the destination directory on the server is named according to the year: imagesyyyy. This path is set in Line 22.

You’ll note that in addition to the PNG file, the script also generates a PDF. I almost never use the PDF, but if I want to annotate the plot before publishing it, I get better results if I annotate the PDF in OmniGraffle and then export the OG file as a PNG.

As I said, this script produces the plot with sales of all three devices. The individual iPad and Mac plots are produced with similar scripts that import only one device’s sales. I should also mention that this script has evolved over the past three years, and it’s likely to contain lines of code that no longer do anything but haven’t yet been deleted. In addition to being a coward and a cheat, I’m also lazy.

The results for the Apr-Jun quarter are going to be posted next Tuesday. I’ll be traveling that day and probably won’t be posting updated plots until the day after. Until then, you’ll just have to get by with charts from Jason and the other usual suspects.

  1. If you’re wondering why I’m linking to a generic Unix man page site instead of to Apple, it’s because Apple has either taken its man pages offline or changed their fucking URLs again

  2. Sneakily, Jason didn’t write a new post, he updated the original with the iOS stuff. Scroll down to the bottom to see it. 

[If the formatting looks odd in your feed reader, visit the original article]


Permalink - Posted on 2018-07-15 03:47

If you pay any attention at all to Apple-related websites,1 you know this past week was the tenth anniversary of the opening of the App Store. And in the dozens of blog posts marking the occasion, you no doubt saw many references to Steve Jobs’s much-derided “sweet solution” of web apps only for iPhone developers in that initial App Storeless year. But some good things came out of that year.

The first I remember fondly was a hangman game that I played with my older son every night before he went to bed. Sadly, I deleted the bookmark for it long ago, and I never mentioned it here on the blog, so I can’t find even an outdated link to it. Too bad. It was a simple game, but it worked exactly as you’d expect, and it fit nicely on that original iPhone screen.

The other app I used so much in the pre-App Store days was Hahlo, a web-based Twitter client that was so much better designed for the iPhone screen than the Twitter website was.

Hahlo screenshot

The Hahlo website still exists, but it redirects to this nicely written farewell page from Hahlo’s developer, Dean Robinson. On it you’ll find several screenshots of Hahlo in action, one of which you see above.

I learned of Hahlo from Andy Ihnatko. I don’t remember if he wrote about it on his blog or mentioned it on MacBreak Weekly, but he thought highly of it, and so did I. I used it as my main mobile Twitter client until Tweetie came along.

No one wants to go back to the days before the App Store, but it is worth remembering that clever developers gave us useful things even when they were stuck in the molasses of the sweet solution.

  1. Especially the increasingly inaccurately named MacStories

[If the formatting looks odd in your feed reader, visit the original article]

Some OmniGraffle automation

Permalink - Posted on 2018-07-04 20:26

As if I weren’t already subscribed to more podcasts than I could possibly listen to, along come David Sparks (of Mac Power Users and countless instructional videos and books) and Rosemary Orchard (of Automation Orchard and countless posts in the MPU and Drafts forums) with a new podcast about automation on Apple devices. I’m not sure what I’ll have to squeeze out to fit this one in, but I will.

As a sort of inverse tribute to David and Rosemary, who provide automations with broad appeal across all sorts of Mac and iOS users, this post is about a pair of recent automations that appeal basically to me. With luck, though, they may trigger ideas for you to create similar automations that solve your own specific problems.

Let’s start with the simpler one. I deal with architectural and engineering drawings quite a bit at work. They usually come to me as PDFs, whether scanned or generated directly from CAD. When I need to have a discussion about them, I drop the PDF into an OmniGraffle document and use OG’s tools to mark up the parts I have questions or comments about. Then I export the annotated document out as a PDF so my client can view it.

Exporting an OmniGraffle document as a PDF is a straightforward and quick process, one that I’d never thought about automating until recently, when I had about two dozen marked-up drawings that I needed to export as PDFs. I wasn’t concerned with the time it would take, but I was pretty sure I’d screw something up during the mindlessly repetitive clicking and tapping I’d need to do. So I made a Keyboard Maestro macro to do it for me. It didn’t do the job faster than I could have by hand, but it didn’t make any mistakes or forget to export any of the drawings.

Here’s the macro:

OmniGraffle to PDF macro

This started out as a macro that exported just one file. After I got that working, I wrapped those steps in the action that loops through all the selected files.

There are only a few tricky things in this macro:

  • The pauses at several points in the macro are needed to allow OmniGraffle to catch up. It takes time to open files, bring up the export sheet, and so on, and a macro without pauses wouldn’t work. I could probably cut down on some of these delays, but it didn’t seem worth the effort to do so.
  • The target image for the first click in the export sheet, telling OmniGraffle to export to PDF, is cropped very tightly to make sure it’s recognized. As you can see below, the icon for the selected format has a darker background than the others. If I included one of those backgrounds in the target image, the target wouldn’t be recognized if it had the other background.
  • Similarly, the target image for the export area selection has to be the descriptive text next to the popup menu, not the popup menu itself, because the text in the popup menu changes depending on which export area was last selected. One of OmniGraffle’s clever features is the ability to direct the click some distance away from the target image; I took advantage of that here.
  • After clicking the export area popup menu, the menu item is chosen by typing the first letter (or first few letters, if necessary) of the menu item and the Return key.

OmniGraffle export sheet

Because of all the pauses, this macro takes a while to run when there are a lot of files to convert. But it does the job more accurately than I can. And now that I’ve made it, I’ll use it even when I don’t have dozens of documents to convert.

The second piece of automation is an AppleScript and is also needed because of the way I use OmniGraffle to mark up drawings.

Quite often, I need to go through the drawings for a building and identify where certain features or components are located. After that, I need to provide a count of all these items. I do the identifying and locating by marking the items with circles or boxes, as in this drawing.

Annotated plumbing drawing

Here I’m marking up a plumbing drawing, showing where the valves are. The different sizes of valve have been given different colors and the circles that identify them are kept in separate layers. This is a convenient way of organizing things while I’m working on the markup, and it makes the process of counting easier.

But when I have dozens of drawings and dozens of items on each drawing, I don’t trust myself to do the counting without making mistakes. Luckily, computers are really good at counting. Here’s the AppleScript I use to count all the different parts on all the drawings:

 1:  -- Create the file for the results
 2:  tell application "Finder" to set theFolder to target of front Finder window as alias
 4:  set fileRef to choose file name with prompt ¬
 5:    "Valve count file:" default name ¬
 6:    "valve-count.txt" default location theFolder
 7:  open for access fileRef with write permission
 9:  -- Initialize the output string
10:  set output to ""
12:  -- Get a list of all the files of interest.
13:  tell application "Finder"
14:    set FPFiles to the selection as alias list
15:  end tell
17:  -- Count the circles in each layer of each drawing.
18:  repeat with f in FPFiles
19:    tell application "OmniGraffle"
20:      open f
21:      delay 3
22:      tell front document
23:        -- Start with the name of the drawing.
24:        set output to output & name & linefeed
25:        tell front canvas
26:          -- The bottom layer has the highest number and contains
27:          -- only the drawing, no circles.
28:          repeat with i from 1 to ((count of layers) - 1)
29:            tell layer i
30:              set sCount to count of graphics
31:              -- Add the layer name and count. Skip empty layers.
32:              if sCount > 0 then
33:                set output to output & name & ": " & sCount & linefeed
34:              end if
35:            end tell
36:          end repeat
37:        end tell
38:        close
39:        set output to output & linefeed
40:      end tell
41:    end tell
42:  end repeat
44:  -- Write and close the file.
45:  write output to fileRef
46:  close access fileRef

Line 2 gets the folder that contains all the selected drawings. Lines 4–6 ask the user for a file in that folder in which to save the counts. Line 7 then opens that file for writing.

Lines 13–15 create a list of all the selected files. This will be the list we loop through starting on Line 18.

For each file in the list, we open the file and loop through all the layers, getting the count of the graphic items in each layer. The output string, which was initialized to the empty string in Line 10, gets updated with each new file name and item count as we work our way through the two loops. When the outer loop is done, Line 45 writes the output string to the file we opened in Line 7, and Line 46 closes the file.

OmniGraffle numbers the layers from top to bottom. Since the bottom layer is the drawing, we don’t need to include its item count, which is why the upper limit on the repeat loop in Line 28 is one less than the number of layers.

I sometimes make layers that end up not getting used. Because I have no interest in reporting counts of zero, the condition in Line 32 filters those out.

The output of the script looks like this:

.75 in: 1
1.5 inch: 4
2 inch: 6
4 inch: 12
6 inch: 2

.75 in: 3
1.5 inch: 2
2 inch: 12
4 inch: 16
6 inch: 4

If I need to do further processing of the counts, this format is easy to parse.

We all know the XKCD comic about the false efficiency of automation, but automation is at least as much about accuracy and repeatability as it is about efficiency. No one thinks it’s wrong to put in more time to get more accurate and trustworthy results.

[If the formatting looks odd in your feed reader, visit the original article]

Apple, batteries, and customer service

Permalink - Posted on 2018-06-24 15:43

I got the battery in my iPhone replaced last Sunday, and after a week of use I can say without reservation that it was a big success. Before the replacement, a normal day of use at the office would put the battery well below 50% and I’d usually have to recharge sometime during the evening; now it’s typically over 80% when I get home, and I don’t even think about recharging. This success is tempered, though, by the poor advice Apple gave me before doing the replacement.

I tweeted about this on Monday, but I think it’s worth discussing in more detail. Let’s start with a bit of history.

My phone is an iPhone 6s bought phone shortly after it was released, probably in October of 2015. About a year later, Apple announced a [free battery replacement program][2] for a certain set of 6s phones made in September and October of 2015. My phone fit in that window, so I got the replacement, even though I hadn’t had the sudden shutdown problem the recall was meant to address.

I was pretty happy about getting the new battery. The original was still giving me plenty of life, but I’d decided I wasn’t going to get an iPhone 7, so I figured a new battery would keep my phone going strong at least until the next phone was released in Fall 2017.

When September 2017 rolled around, I decided to take a pass on the iPhone 8 and iPhone X. After all, I had a phone that worked fine with a battery that was still less than a year old. I could wait until the next generation.

Then came the blowup about sudden shutdowns on many phone models and, what was more controversial, Apple’s throttling of performance to avoid those shutdowns without telling its customers it was doing it. At the time, I didn’t think my phone was being throttled, and I was still getting a full day of use with capacity to spare. But that didn’t last much longer, so I decided to take advantage of the $29 replacement program.

With stories of long waits at the Apple Store as everyone rushed in to get their batteries replaced, I put off getting my own replacement until I couldn’t stand it anymore. I made an appointment for last Sunday.

The Apple Store employee (not sure if he was technically a Genius or not—are all Store employees called Geniuses?) looked at the phone’s Battery Health in Settings, saw that the Maximum Capacity was 81%, and told me the battery replacement wouldn’t do me any good. “You won’t see any difference,” he said.

I was sure he was wrong but didn’t say it in so many words. I told him I wanted the replacement anyway because I could no longer get through a day without recharging. Maybe the replacement wouldn’t fix that, but I wanted to give it a try.

He said he’d certainly do the replacement if that’s what I wanted; he just wanted to make sure I knew it wasn’t likely to help. Apple didn’t want me spending money for no reason. Background processes were the more likely culprit. Did I mind if he looked through my settings to see if there were some things that could be changed to improve battery life?

Of course I let him and of course he found nothing. I’d been through that exercise myself several times. He did say—and this was the one time I was tempted to laugh in his face—that my Auto-Lock setting of 5 minutes was too long and would really kill the battery. I told him I’d used that Auto-Lock setting on every iPhone I’d owned over the past ten years and it had never been a problem. I still wanted the battery replaced.

An hour and a half later I had my phone back with a new battery—with, I noted with some disappointment, only a 17% charge—and it’s now behaving exactly as I expected and exactly the opposite of what Apple told me to expect.

I say “what Apple told me to expect” rather than “what an Apple Store employee told me to expect” because I don’t think he was making up his own advice to pass along to customers. I think this is Apple’s official advice. And it’s wrong. I ignored Apple’s advice because I knew how I used my phone, knew that I didn’t have many background processes running, and knew that the battery life had decreased significantly over the course of a few months even though my usage hadn’t changed. I wonder if other customers are living with short battery lives because they listened to Apple.

I should point out that anyone with an eligible phone who wants a new battery will get one for $29. That’s Apple’s policy. But the policy is being undercut by poor advice based on a single number that comes out of the Battery Health setting. Which is beta software. The mixed message is not good customer relations.

[If the formatting looks odd in your feed reader, visit the original article]

A couple of Amazon things

Permalink - Posted on 2018-06-14 01:23

Two recent Amazon purchases got me thinking about design and manufacturing.

Let’s start with design. On the recommendation of a couple of friends, I bought this LitraTorch LED light cube.


It’s a nice compact little light that generates fairly uniform coverage at three intensity levels (and there’s a strobe mode, which I find useless). Also, it starts at the lowest intensity so you don’t get immediately blinded when you turn it on in a dark room. It’s not cheap, but it’s a professional tool for the inspections I do at work. Overall, I’m happy with it.


It has a permanent battery that recharges through a micro USB port. I always struggle getting the orientation of the plug and port to match, and working with this one is going to be even more trouble than I’m used to.

Litra micro USB port

Every port I’ve run across before is shaped like the plug in sort of a curved trapezoid. Not this guy. The only way to know which end is up is to see the black connector part inside the black rectangle on the back of a black cube. I assume the decision to go with a rectangular port was driven by cost, but it’s an unfortunate choice, especially in an $80 flashlight.

Now let’s move on to a manufacturing problem. I also bought this LIHIT LAB pen case to hold my pens, pencils, erasers, and tweezers.

LIHIT LAB pen case

It’s a simple case and not expensive, but the first time I tried to unzip the main compartment, the zipper pull came off in my hands.


The little hook on the slider that holds the pull had snapped off.

Broken hook from slider

Since I was at work, I took the broken hook into the lab to have a look at it under the microscope. Please forgive the fuzziness; it’s hard to get good depth of field in optical microscopy.

Zipper hook fracture surface

So many problems in how this was made. The immediately obvious flaw is the bubble in the center. This is a cheap casting made of cheap pot metal and the casting left a huge (relative to the size of the part) bubble in the center of the hook.

But I doubt that was the main cause of the failure. The hook is loaded mainly in bending, and the bending strength of a hollow part isn’t usually significantly different from that of a solid part. Many parts are deliberately made hollow to save material without reducing strength much.

You may also notice the crack extending inward from the edge on the right side. That, too, is a flaw, but didn’t lead to the failure. That crack is perpendicular to the final fracture surface and would not be affected by the bending stresses that broke the hook.

Now look at the black areas at the upper right and upper left corners of the cross-section. This is paint, and because it’s on the fracture surface, we know that cracks in those areas were already present when the hook was painted. And because they’re on the outer edges of the cross-section, where the stresses are high during bending, they are the flaws that were the most direct cause of the failure. I’m guessing the casting cracked during cooling but was not weeded out during whatever quality control process the manufacturer used.

[If the formatting looks odd in your feed reader, visit the original article]

A TaskPaper/Drafts improvement

Permalink - Posted on 2018-05-27 15:57

While looking through the Drafts scripting documentation for help with another idea for an action, I found a couple of Editor functions that I hadn’t noticed before. One of them led to a big improvement to my TaskPaper action group.

As explained in this post, I use Drafts now for handling TaskPaper task lists on my iPhone and iPad. Drafts has some nice built-in features for TaskPaper lists, and its extensibility through actions allows more features to be added. I wrote a handful of actions geared toward my use of TaskPaper, and added them to the Drafts Action Directory, a repository of community-created actions. You can download them from here.

One of the actions in my TaskPaper group worked as a toggle to add/remove blank lines between tasks. I keep my lists in a format that looks like this,

TaskPaper list in Drafts

with no blank lines between projects except for the Archive section at the end. This is great for keeping the list tight but is a problem when I want to rearrange projects to bring the more important ones to the top. Drafts has an Arrange Mode which is perfect for this sort of thing, but it requires blank lines delimiters to define text groupings that can be moved as blocks.

Originally, I made an Un/Block action, which added or removed blank lines between projects, depending on the list’s current state. My rearranging workflow was this:

  1. Tap Un/Block to add the blank lines.
  2. Tap the Arrange Mode button at the bottom of the screen to enter Arrange Mode.
  3. Rearrange the projects.
  4. Tap the Un/Block button to remove the blank lines.

This worked well but was a little clumsy. Then I saw the showArrange and arrange functions in the Drafts Editor library. The showArrange function wasn’t quite what I needed, but the arrange function was perfect. It takes text as an argument, stops the flow of the script while the user rearranges it, then returns the rearranged text.

By incorporating arrange, I was able to change the script of the Un/Block action into this:

 1:  // Allow user to rearrange projects by temporarily adding
 2:  // blank lines above all projects
 4:  // Regexes for project headers.
 5:  var projREtight = /([^\n])(\n[^:\n]+:)/g;
 6:  var projREloose = /(\n\n)([^:\n]+:)/g;
 7:  var archiveRE = /^(Archive:)$/m;
 9:  // Get the text of the current draft
10:  var d = draft.content;
12:  // Make new text with blank lines above all project
13:  // headers that didn't have them.
14:  var e = d.replace(projREtight, '$1\n$2');
16:  // Go into arrange mode with this new text
17:  // and wait until the user is done rearranging.
18:  e = editor.arrange(e);
20:  // Take blank lines away except above the Archive section.
21:  e = e.replace(projREloose, '\n$2');
22:  e = e.replace(archiveRE, '\n$1');
23:  editor.setText(e);

It adds the blank lines in Line 14, lets the user rearrange the blocks in Line 18, and then removes the blank lines in Line 20 (Line 21 restores the blank line above the Archive section). Now the rearranging happens within the execution of the script, so I’ve renamed the action Rearrange. The workflow is reduced to two steps:

  1. Tap Rearrange.
  2. Rearrange the projects.

Rearranging projects

This is not just faster, it’s more natural because the action is named for what I really want to do and all the necessary-but-incidental format fiddling happens behind the scenes.

So, yeah, reading the documentation can be helpful. I have a sense the activate, deactivate, and dictate functions, which are also things I missed when I first looked in the Editor section, will be finding their way into my scripts soon.

[If the formatting looks odd in your feed reader, visit the original article]


Permalink - Posted on 2018-05-26 12:38

About a month ago, I started having trouble charging my iPhone 6S. I’m not talking about the need to charge my phone more often because the battery isn’t what it used to be, although that’s definitely happening and I need to cough up the $30 to get the battery replaced. No, I’m talking about the Lightning plug on the charger cable not seating well in the port on the bottom of the phone. The plug would wiggle and often lose contact, leaving me with a phone that was still draining when I thought it was charging.1

My first thought was that lint had built up in the port and needed to be cleaned out. I was at home without good lighting or good magnification, but I got a toothpick and dug around in the port, figuring that if anything was in there, that would loosen it and pull it out. When nothing emerged, I started thinking there was a problem with either the port itself or with the third-party cables I was using.

Yesterday afternoon I learned the truth. On the cusp of a long weekend, I didn’t feel like working and neither did my clients, as the usual stream of phone calls and emails dried up. I decided to go back in the lab at work and take a serious look at the Lightning port though a stereo microscope.

Lightning port before cleaning

Depth-of-field is terrible in optical microscopy, and I didn’t adjust the white balance before capturing the image, but you get the idea. The lint had been compressed by the plug until it was almost as solid as felt.

For whatever reason—most likely because the lint was too tightly packed—the toothpick hadn’t done its job a month ago and led me to think the port was clean when it obviously wasn’t. I went at it with a pair of fine-tipped tweezers and, after several minutes of digging, had a little pile of lint and a clean Lightning port.

Tweezers and lint

Lightning port after cleaning

The Lightning port is so narrow that even these tweezers couldn’t reach in and pluck out the lint. I had to use one side of the tweezers as a pick to poke at the lint and draw it out. And it took several minutes to clean because even the sharp pointy ends of the tweezers had trouble digging in and getting under the lint.

The two lessons here are don’t assume you’ve done something right without checking and don’t wait over 2½ years before cleaning out your Lightning port.

  1. I keep my phone on Mute, so there’s no sound when contact is lost. The phone does still vibrate, but I don’t always notice that because the phone isn’t in my hands. 

[If the formatting looks odd in your feed reader, visit the original article]