My xcopen adventures: playground workspaces

Now that Xcode 12 supports Swift Packages for playgrounds, I thought it was time to expand xcopen to build not only playgrounds but also allow you to embed them in workspaces.

xcopen in a nutshell

If you’re not familiar with xcopen (I’ve only mentioned it briefly on this website), it’s my answer to xed. It does what xed does more or less and adds more features that I use a lot.

I built xcopen to handle command-line activities that I regularly perform during development. If you run it without arguments, it looks for a workspace and then opens that. If no workspace is found, it looks for xcode projects and playgrounds. If you pass it file names and paths, it opens those instead.

OVERVIEW: 
xcopen ...        Open files in Xcode.
xcopen docs              Open .md and .txt files.
xcopen new               Create new files (if they don't exist), open in Xcode.
xcopen xc|ws|pg(w)       Open xcodeproj, workspace, or playground.
                           * Add ios|mac|tvos to create playground.
                           * Add w (pgw) to create playground in workspace.
xcopen pkg|xpkg          Open Package.swift in TextEdit or Xcode.

USAGE: xcopen [ ...] [--background] [--folder] [--open] [--no-open]

ARGUMENTS:
                   Files to open. If blank, opens xcworkspace or,if not
                          found, searches for xcodeproj. 

OPTIONS:
  -b, -g, --background    Open Xcode in the background 
  -f, -e, --folder        Enclose new items in folder 
  --open/--no-open        Open newly created playgrounds/workspaces (default:
                          true)
  -h, --help              Show help information.

Shortcuts let you gather up your docs (like README.md, CHANGELOG.md, and LICENSE.txt) and open them together for edits.

You control whether Xcode opens in the foreground or background, enabling you to keep working without Xcode taking up your immediate attention.

Recently, I added support for playground creation. Need a Mac playground? xcopen pg mac. It emulates Finder naming  so there won’t be naming conflicts. Instead, it builds macOS, macOS 2, macOS 3, etc as your root playground names. Based on feedback from my Twitterati pals (waves hi!), I added a flag that lets you group them together in a subfolder if you don’t want multiple playgrounds cluttering your working directory.

Adding Workspaces

Today, I decided to start working with Swift packages, so I added workspace creation:

xcopen pgw mac --folder

Using pgw builds both a playground and an associated workspace. Adding --folder embeds them both into a new folder. Otherwise they are created in the working directory.

Using Swift Package Support

Add any folder containing a Swift Package to your workspace:

  • Files > Add files to workspace name (may be greyed out); or
  • Project navigator contextual pop-up > Add files to workspace name; or
  • Or just drag the folder above your playground entry in the Project navigator to ensure you’re not adding it directly to your playground.

If your package has dependencies, they’re listed in the Project navigator.

Next, try importing the new package. If it doesn’t autocomplete, quit and restart Xcode and re-open your workspace. For some reason, in this early beta it doesn’t seem to get picked up immediately.

Then test out the functionality you’ve imported. In the following example, I’m using a custom exponentiation operator:

Wrap up

I’m using xcopen a lot these days, tooling it to make my workday easier. If you find a feature you think I should include please open an issue at github. And if you like the utility, do let me know. Thanks!

The easiest way to install xcopen is via mint, which you can install with brew. Once you have mint, all you have to say is mint install erica/xcopen.

Pronouncing all the things: Jumping into my bin folder

After dutifully training people to pronounce simctl correctly (“sim control”, not cuddle or kittle), I stopped by my binary folder (/bin, also pronounced “bin”, like the thing you throw stuff into) this morning to see how I pronounce all sorts of things. I’m curious as to how my pronunciations compare to yours.

Here is a list of how I say things from my highly unscientific survey of /bin:

  • cat: Meow. Does anyone not say “cat”? (From “concatenate”)
  • csh: See shell. See shell run. Run shell run. Who’s a good shell? Yes you are!
  • df: Dee Eff or occasionally disk free. Mostly “Run df to see how much free disk there is” (notice the inversion from “disk free” to “free disk”).
  • chmod, chown: Change mod but I mostly say “make it executable” or “add read permission”. I almost never say it out loud. Similarly, chown is just “change owner” because saying “change own” is just weird.
  • cp, rm, mv: See Pee (no 30) or copy. Are Em or remove. And “move”. Don’t let anyone convince you it’s “mivv”.
  • ls: Ell ess. I almost never say “list” but I do say “list the contents of the working directory with ell ess”.
  • rmdir, mkdir: Arr emm dear or remove “dear”, and “make dear” (which, you know, is really sweet when you think about it.)
  • pwd: I only ever say “print the working directory”.
  • ln: Going against the trend, it’s always “ell enn”, not “link”. Probably because I never use this and always use lns instead.

I’m not sure this post has much of a point other than to amuse you by how I waste time in the morning before the caffeine kicks in. Let me know how wrong I am and what the correct pronunciation of some of these items are.

Cleaning up SPM builds and other SwiftPM thoughts

If you’re short on space and want to clean up your local Swift Package Manger repos, you can easily remove build products by issuing:

UsefulModifiers% swift package clean
UsefulModifiers%

This is particularly helpful for people, like me, who develop in Xcode where it’s easy to clean your product’s build folder but forget that there’s also mess with SPM. GrandPerspective or any of the other file space visualizers is great for seeing where your clutter builds.

Also, while I’m chatting about SPM builds, I find that a lot of people forget that swift package init offers separate initializers for library and executable. Just pass --type library for example. I’m doing a lot of library and executable work these days so it helps to have that set up for you.

I’m not crazy about everything that SwiftPM sets up, so I’m finding myself more often creating my manifests by hand.

I use a global git ignore file located in my home directory, so one of my first steps is to always dump the .gitignore that SwiftPM creates for me.

[core]
	editor = vi
	excludesfile = ~/.gitignore_global

I populate my git ignore file with gitignore.io. It’s a great resource for building savvy collections. Mine includes, among others, ignore groups for Xcode, Swift, Objective-C, macOS, Emacs, CocoaPods, Carthage, SwiftPM, and more.

I automatically add CHANGELOG.md and LICENSE.txt files, which I think SwiftPM should consider doing as well.

When developing in Xcode native, make sure you are using the right source and test folders. Notice that the preceding screenshot has both Sources and UsefulModifiers. I’m currently leaning towards reconfiguring my Xcode project to use the default SwiftPM folders but I’ve also gone the other direction. For example, you can set your target path: to specify where to look for your source material to compile:

    targets: [
        .target(
            name: "UsefulModifiers",
            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")],
            path: "UsefulModifiers/"
            ),
    ],

Always confirm that the default Swift version in the comment at the top of the file is the one you want to work with. I recently spent time updating my Swifts back to 5.1 so they’d run on Mojave systems:

// swift-tools-version:5.2

Of course, if you’re building new libraries of modifiers and views for SwiftUI, make sure you’re using the latest tools.

Speaking of “latest”, it’s important to think about version drift when it comes to your dependencies. I’ve been leaning towards always freezing my dependencies for any distribution to ensure that my code will keep compiling until I’m ready to move those dependencies forward.

Setting an exact dependency avoids the unnecessary pain of your dependency updating (SAP at the moment is 0.2.0) and your code dying as a result:

    dependencies: [
      .package(
        url:"https://github.com/apple/swift-argument-parser",
        .exact("0.1.0")),
    ],

At the same time, I think one of the most exciting things this week for me was Xcode’s automatic search and support of views and modifiers. Building a smart package that can be added to normal app development and updated over time, and whose bounty automatically appears in the resource library is just marvelous.

I thought I’d share some SwiftPM thoughts as I put together exactly that. Are you building your own View and Modifer libraries? Anything public? I’m curious as to what everyone else is working on!

Removing trailing white space

Your linter might find it, but did you know there’s an easy regex approach to removing trailing whitespaces from lines?

Andrew Wagner reminds me that there’s also a built-in setting:

I have this set but for whatever reason, pasting or re-indenting, I always seem to end up with a few scattered around.

If you like, you can start by visualizing the spaces by enabling Invisibles. This switches Xcode’s editor display mode to show all characters including whitespaces. It’s also a great way to track down invisible extra characters you may have entered accidentally while coding. This happens to me enough on a regular basis that I reach for this mode when it happens.

If you don’t like Invisibles mode or you want to go back once you don’t need it anymore, just switch it off in the menu. This menu is also helpful for getting rid of the minimap and listing commit authors.

Next, do a Search/Replace. Make sure to set the match to Regular Expression. The pop-up is towards the right:

Replace one or more (+) horizontal-only spaces (\h) that extend to the end of the line ($) with a blank/nothing replacement. Horizontal spaces won’t gobble up empty lines within your code as well as the trailing spaces.

Reader Rob adds: “I use ‘;$’ to remove the semis from swift code that a lifetime of C and Objective C (and other langs) have caused me to insert unconsciously.”

Helpful? Or did I mess something up? Let me know.

Coloring SVG assets in SwiftUI

Update: Huge thanks to Justin.

Retain the same code as the system image but use the asset inspector to change the SVG resource to a template image! So much easier and better. Thank you, Justin!


Coloring a SFIcon is simple. Here’s the default rendering:

struct ContentView: View {
  var body: some View {
    Image(systemName: "bandage")
      .resizable()
      .aspectRatio(contentMode: .fit)
      .padding()
  }
}

And here’s the same using a red tint:

Image(systemName: "bandage")
  .resizable()
  .aspectRatio(contentMode: .fit)
  .foregroundColor(.red)
  .padding()

But what about the new SVG image support? (The seal image is by mungang kim, the Noun project):

SVGs carry their own color information. I edited the seal in Adobe Illustrator CS4 (I have a computer dedicated to Mojave) to add intrinsic colors:

Again, the foregroundColor(.red) modifier is ignored and the native colors are shown. From my developer’s point of view, what I want is to be able to modify the SVG asset to use normal SwiftUI coloring. So the first thing I did was to create a ZStack and use blending modes to set my foreground color.

I finally got my first hint of victory by using a content blend with .colorDodge:

I discovered, though, that dodge wasn’t a great choice for non B&W assets. I needed a better blending mode.

When I tried to layer images in a ZStack, I discovered the color mode would bleed through:

I needed to:

  • Use a better blend mode that wouldn’t be affected by the SVG Image source colors and whether they were native Color s (like Color.red or system ones (like Color(.red), which uses UIColor/NSColor).
  • Isolate the blend mode so it wouldn’t affect other Views.
  • Move that functionality to a simple modifier, allowing a SVG Image to blend with a color.

I soon discovered that the sourceAtop blend mode got me the coloring I needed, whether I used the B&W or colorized asset:

ZStack {
  content
  color.blendMode(.sourceAtop)
}

Then, I needed to isolate the blend. I first turned to .drawingGroup(opaque:false) but it kept failing to provide the result I was aiming for until I discovered that isolating that into its own VStack bypassed any blends with ZStack elements at the same level:

VStack {
  ZStack {
    content
    color.blendMode(.sourceAtop)
  }
  .drawingGroup(opaque: false)
}

I then moved this into a custom View modifier:

public struct ColorBlended: ViewModifier {
  fileprivate var color: Color
  
  public func body(content: Content) -> some View {
    VStack {
      ZStack {
        content
        color.blendMode(.sourceAtop)
      }
      .drawingGroup(opaque: false)
    }
  }
}

extension View {
  public func blending(color: Color) -> some View {
    modifier(ColorBlended(color: color))
  }
}

This allowed me to create a standard SwiftUI ZStack that used the modifier in a normal cascade:

struct ContentView: View {
  var body: some View {
    ZStack {
      Image(systemName: "bandage.fill").resizable()
        .aspectRatio(contentMode: .fit)
      
      Image("ColorizedSeal")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .padding(100)
        .blending(color: Color(.red))
    }
  }
}

Here’s how that renders:

You’ll want to make sure the blending happens after the image resizable and aspectRatio calls but other than that it can appear before or after the padding.

What I got out of this was a way to use Xcode 12’s new SVG asset support, standard SwiftUI layout, and flexibility when applying my color blend to assets that might not just be black and white.

I hope this helps others. If you have thoughts, corrections, or suggestions, let me know.

App Clips: when is an app an app and when should it be a webpage

Apple’s new App Clip technology lets people load transient mini-apps without installing through the App Store. Users don’t have to authenticate or authorize the mini-app. It just downloads and works. Whether scanning a code (think QR code) or detecting an NFC tag, iOS users can download and run these pre-vetted packages that represent a light, typically transactional, view of a larger app experience. I went through some writeups and video today and thought I’d share a mental dump of my thoughts.

All App Clips are accessed via URLs and limited to 10MB or less in size.  Their job is to move a user through a quick transaction and then either return control to the user or solicit the user to download the full application. So if you’re selling cupcakes, you can “upsell” the experience from a single purchase to a loyalty program app.

App Clips are designed for transaction. As an app, rather than a web page, they integrate seamlessly with the store ecosystem, allowing users to purchase goods and services from an instant menu and integrate with features like Apple Pay.

You can also use App Clips to decorate a museum or a city with points of interests or a bus stop with upcoming travel information. You can also do this with the phone’s built-in QR recognition for URLs to land on a web page instead but you’d miss the charm of the Apple “concierge” guiding you through the process.

Honestly, there’s nothing an App Clip can do (maybe other than something like Apple Pay) that a reasonably designed web page cannot but it’s that charm and an eye towards lowering the transaction barrier that makes the Clips so compelling. With Clips, end-users can point, pick, and pay with an absolute minimum of effort. If this works as promised, many of the typical web hurdles (I speak as someone who has ordered a lot of MadGreens pick-up food over the last few days) disappear.

A Clip’s transient data only is transient if it is not transferred to a client app. Since the Clip must be developed in tandem with that app, an impulse purchase can be applied directly to loyalty points, putting the user on the path of a redemption reward. The side-by-side development and the tandem review (which takes place together) plus the ability to share assets and re-use code makes this a promising area for developing commercial transaction apps that don’t stand between the user’s desire and impatience and the need for a more traditional app.

I use the Safeway app regularly and I utterly loathe it. Safeway has three tiers of user prices: general rip-off, loyalty-program, and expensive-but-you-can-live-with-it Just4U. Swiping a card only gets you to the middle tier. To get the Just4U prices, you must spend a half-hour before each visit entering all the coupons you think you might need or try to scan the teeny-tiny, often folded, ripped, or dirty QR codes on the shelves and then hope that the store’s remarkably bad WiFi and cell service doesn’t give you dozens of error alerts (which must each be manually dismissed) keeping you from actually being awarded those better prices (and the occasional $5 or $10 off a $100 purchase).

If the App Clip experience shows how to smooth the pathway from desire to acquisition, then Safeway’s approach shows how to tick off its customers and actively convince them not to purchase certain items as the coupons are not working.

However, I’m not only excited about the transactional nature of App Clip, I can easily see how a well-backed municipal or organizational effort can provide “more information”, “deeper information”, “found facts”, and “inspiration” using the same technology. Again, the key lies in reducing turbulence, steering the user, and completing the goal in the shortest period of time.

In terms of designing your own apps for App Clips, remember that simple is always better. The more choices, options, and features in your Clip you present, the more work the user must do. Instead, consider pushing your top sellers as “Quick Buys”. That doesn’t mean you can’t offer the deeper choices but if you do, remember the people queuing up in line, impatiently waiting for your customer to pick their deep-menthe chocolate-macchiato with half-skim/half-soy, two-and-three-quarters squirts of Pumpkin Spice, with low-fat whipped cream.

I’d imagine that the best transactional clips will not only capitalize on desire but also on the flow and customer-to-customer dynamics that might also exist within the store experience.

For App Clips out of the sphere of purchase, keep the same kind of locational awareness. A visitor who has just discovered the fascinating history of your city’s belltower probably shouldn’t step back into traffic to better look up and view the little dancing people on the clockface.

When I first saw this feature, I wasn’t all that excited. Now that I’ve dived in a little more I’m much more impressed by the thought care and clever delivery mechanism Apple. has put together.

What do you think? Too clever for its own good or a tech we’ll be seeing for years to come?

Swift Packages and the need for metadata

Right now, a Swift Package defines the sources and dependencies for successful compilation. The PackageDescription specifies items like the supported Swift version, linker settings, and so forth.

What it does not do is offer metadata. You won’t find email for the active project manager, a list of major authors, descriptive tags, an abstract or discussion of the package, a link to documentation, deprecation information or links to superceding packages upon deprecation.

For me, tags are especially important as they can drive discoverability on aggregators such as SwiftPackageIndex.com or SwiftPackageRegistry.com, among others, as they do in the various App Stores. However, all the other information I’ve mentioned can be equally valuable. The question is how this information should be stored and travel.

Extending SwiftPM’s PackageDescription is the most obvious way but the one with the greatest hurdles. Extending a specification means review, bikeshedding, and approval but is one that would produce the most rigorous and widely-applicable outcome:

let package = Package(
    name: "now",
    platforms: [
      .macOS(.v10_12)
    ],
    metadata: [
      .tags(["dates", "calendar", "scheduling", "time", "appointments"]),
      .maintainer("erica@ericasadun.com"),
    ],
...

Freeform tags are, as Mattt of three t’s pointed out to me, a folksonomy: a user-specified list that can be organized or freeform, sensible or not. Anyone familiar with the App Store will recall how its tags have both benefited developers and how its tags can be abused to drive traffic.

Of course, updating the PackageDefinition spec is not the only approach. The same information could be packaged into a second file cohosted with Package.swift. Perhaps it could be called Package.metadata (if stored as JSON, for example) or PackageMetadata.swift (if the information is SwiftPM-like with its own Metadata type and supporting package to better support automated validation and consumption).

Plain JSON has many advantages. It requires no secondary code development as PackageMetadata.swift would. It has an obvious place to live and can be just as easily omitted. The standard for contents could be community-sourced and Decodable developed specifically for it, plus the JSON could be validated according to that standard.

I am least enthused by PackageMetadata.swift with its high overhead and mimicking of Package.swift but it would certainly fit with the design and approach and lower the overhead for consumption.

What do you think? How would you design metadata delivery for Swift packages? The one file to rule them all expansion of SwiftPM itself, the simplicity of Metadata.json, or something else? Let me know.

The silly delight of the Xcode document opening `xed`

Julian Kahnert recently introduced me to xed, a command-line built-in that opens individual documents in Xcode. You give xed the name of a file (or files) and presto, it launches in my favorite IDE.

There’s some intriguing flexibility to xed. You can request that Xcode remains in the --background or specify a --line number to select once open. The line number request affects the last file in the invocation. In addition to passing it files to open, you can --create new files with the contents of stdin: promising for scripting and code-gen utilities.

If you pass --launch, xed promises to create a “new empty unsaved file”,  without involving stdin. Unfortunately, in my experiments with it over the past week, I’ve found that while it’s very good at opening a single file, complex commands can sometimes confuse the poor geriatric utility. xed was introduce way back in Xcode 3.0 and it kind of shows its age at times.

Even just limiting my xed use to opening a file for editing, it’s a neat little discovery for me, so thank you Julian!

Our discussion arose out of my own little utility xcopen, which I put up on Github. Mine looks in the working directory for an xcproj and then opens it. (I got tired of either working through the file completion, when there’s always a subdirectory with the same name as the xcproj or having to open Finder and then launch.)

I really like the idea behind xed and I’m half tempted to extend the feature set in xcopen using AppleScripting to get the promise of xed with a more reliable delivery platform.

Swift Process and osascript: so much easier than the command line

I use osascript a lot to automate things that need automating and until yesterday, I did so almost exclusively from shell scripts. Then,  while developing some sample code the other day, I discovered how much easier it is to call osascript from a command-line utility, even for the most trivial of scripts.

let script = """
  set appName to "\(appName)"
  tell application "System Events"
	if visible of application process appName is true then
		set visible of application process appName to false
	else
		set visible of application process appName to true
	end if
  end tell
  """

With Swift’s triple-quoted multiline string, you need little if any escaping and your script can basically be copy/pasted from the Script Editor app. Plus string interpolation enables you programmatically customize your AppleScript code.

For your Process, set your launchPath to "/usr/bin/osascript" and your arguments to ["-e", script]. Then launch and wait until exit.

if #available(macOS 10.13, *) {
    guard (try? process.run()) != nil
        else { throw RuntimeError.operationError }
} else {
    process.launch()
}
process.waitUntilExit()

Why is it worth creating an entire Xcode project to deal with a little osascript work? For me, it’s a big win when it is part of a larger utility. With the Swift Argument Parser, I’ve been porting a lot of my scripts — both shell scripts and swift scripts — to compiled utilities and I find that I tend to lean on osascript a lot more than I thought I did to automate my system.