So you’re going to teach remotely…

Remote meetings and teaching are an unfortunate necessity of the times. For those of us already using these platforms in positive ways, perhaps we can share some lessons on how to enhance your toolset.I thought I’d put together a quick post to address some of the ways things the classroom experience changes and suggest some technology support  for online classes.

I apologize for the stream of thought and incompleteness of this post. I wanted to put something out there right away. I’ll try to add, edit, and revise when time allows.

All Cameras On

I found the biggest struggle was my inability to wander through the classroom, peek over shoulders, and talk quietly to individual participants. I’m a walker and the online classroom is very much one of staying still.

My first rule is all cameras on with a further all cameras on faces. People are often uncomfortable with this, either switching their cameras off or pointing them up to the ceiling, essentially isolating themselves as students in a way they cannot do in a normal classroom, even by sitting at the very back with a phone in their lap. Push back and be insistent.

This rule goes a long way towards bringing the classroom together and making up for the fact that so much of the teaching experience is limited to a single viewpoint. The teleconferencing Zoom site offers a camera grid feature, which displays all participants at once (although not necessarily on the same screen, you will have to page through for large gatherings). This feature is especially important when you give in-class exercises, allowing you to keep track of the emotional temperature and find students who may be struggling.

Unfortunately, Zoom does provide an equivalent “screen peek”, so you cannot look over shoulders. Each screen must be shared and viewed individually.

As you teach, make sure you encourage participation, even more than you would in a normal classroom as there is always a danger of getting lost in your monotone without the immediate ability to “read the room” as you teach. Be extra conversational with give and take, much more than you would in a traditional class or seminar. This helps offset the technology and creates a warmth and inclusivity that otherwise would be missing.

Take Breaks

If you’re teaching extended classes, workshops, or labs, make sure to offer regular breaks to stretch and hit the bathroom. Put a timer onscreen. Most search engines allow you to enter “5 minute timer” and produce that timer for you automatically. Teaching, even remotely, is much more physically active a task than learning so remember your student’s endurance will be less than yours.

Prepare

Create a preflight list to ensure you’ve hit all the setup points before you go live teaching, including testing your camera and microphone and cleaning up your teaching platform. Don’t overshare. Depersonalize your desktop and your workspace.

Where possible, use a second unshared screen for teaching notes, outlines, and coordination. It’s far easier to lose track of time and go on tangents in a remote classroom because of the immediacy of the face-to-face conversation. Make sure you know what time it is, and what you have to cover in your lesson plan.

Have on hand a list of teaching objectives, scripts for live coding demos, working and starting code configurations. Don’t forget wrap-ups, next steps, and so forth. If you can, rehearse before you teach.

  • GitHub Place shareable starting source on GitHub or another publicly available site so students can follow along with minimal setup time. I use GitHub’s gist a lot.
  • Snippetty: Minimize typing with Snippetty. This menu-bar-sourced utility collects, orders, and offers the code or text snippets you’ll need during your session. Snippetty also lets you add presenter notes to those snippets. The less you type directly, the fewer typos you’ll encounter directly, and you limit long pauses during your lesson.
  • Snippety is end-of-life’d, sadly. I love this tool.

Display Tools

When displaying slides, whether from a second screen or a tablet, use the presenter notes feature to remind yourself of important topics.

A highlighting tool such as ScreenBrush (ScreenBrush for Mac and ScreenBrush for iOS) is great for circling, underlining, and drawing arrows to onscreen information.

I’m particularly fond of LargeType from Gold Mountain Software, which enables you to emphasize text or code at a readable huge size. This is a great way to call out key information. I’ve customized System Preferences > Keyboard > Shortcuts > Services > Text > LargeType to add a custom keyboard combination to perform selection enlargement without using menus. I currently have it set to Command-Control-Option-L, but you can pick whatever keyboard shortcut best works for you.

Don’t forget that your screen will be hard to read. macOS’s accessibility features enable you to use the mouse, trackpad, and keyboard to zoom parts of the screen. This allows you to zoom in on code and other important details that could be missed by showing an entire large screen. The zoom centers around your cursor by default, so it tracks your interest point and allows that field of vision to be shared with others. Visit System Preferences > Accessibility > Zoom to set your preferences

Demoing

Reflector 3 wirelessly mirrors an iOS screen, enabling you to load, launch, and demo apps from your deployment device. I also use Reflector 3 as a wireless screen-mirroring app from my iOS device.

If you don’t mind being tethered, QuickTime allows you to present your device screen for a connected iOS device.

Whiteboarding

On my iPad, I use Linea Sketchto draw. It has a present mode that hides all the normal iOS app details and just shows a plain whiteboard drawing screen. I recently upgraded to a lastest-gen iPad mini with 1st gen pencil and that has been fantastic. I also got good results before my upgrade with the rubber tipped Heiyo stylus. Use a firm stylus for writing. Most stylii are designed for drawing and to simulate brushes.  The Heiyo doesn’t scratch the screen like the metal-tipped ones, is easy to charge (microUSB), and works with older iPads.

Zoom

Zoom offers a number of great built-in features for questions, messaging, raising hands, and so forth. I particularly like using it to know when students are done with a particular task: clear the participant list and have them use a check mark or thumbs up when they have finished. This way you can continue the class without having to wait a full number of minutes.

The same tool is great for “Who thinks that X is Y” style questions. Always encourage the student to answer before looking at what others have voted.

If you can, supplement Zoom with Slack. It’s much better for creating a record of q&a (start a thread specifically for that), and for when you need to distribute text/code from teacher to students. In terms of q&a, a great approach is to allow 3-5 minutes where everyone can add their questions and then you review the questions and choose which ones you wish to answer. I got this technique from Andrew Madsen and it’s terrific. He also recommends using sli.do for audience participation.

How to contribute to the iOS Developer Survey

Dave Verwer, bless his heart, put together an amazing collection of developer statistics in this year’s iOS developer survey. From careers to conferences, dev tools to frameworks, he profiled the community to discover what is happening on our platforms and in our jobs. If you haven’t already been over to the survey site, go and click the link. Don’t worry, this post will still be here when you get back.

The raw data only goes so far. What the survey needs now is you. Dave is looking for community members to help analyze the results so we can better understand what all this data means. For example, we’ve come to love and cherish our dev tools in a way that was unthinkable just a few years ago. Are the high marks for Xcode universal or are  coders with, say, five or more years of development experience particularly grateful for this advancement?

If you have an interest in deeper analysis, Dave has tools to help you slice, dice, and Julian fry the results. Right now there are just four articles available that evaluate the core information (“Is WWDC already a virtual conference?”, “How popular is Swift?”), and they’ve been written by Dave himself.

This data is crying out for deeper investigation, more voices, and diverse insights. If you have time and curiosity, I urge you to reach out to Dave on Twitter and get involved.

This is a fantastic opportunity to give a little back to the community that nurtures our professional lives.

How to fix: Ooops, I lost track of those beta upgrades

What happens when you put off beta upgrades and put them off and off and off and suddenly the release version has outpaced the beta? This happens:

And this happens:

So there you are, stuck in beta and complaining to your friends about what is, ultimate, my your fault. If you don’t update on a timely basis, you can waste a few hours (as I just did) getting your system back on track so you can mess with (for example, just to pull something out of thin air) Swift Playgrounds for iOS for Mac (which requires the 10.15.4 beta10.15.3).

Just so you don’t waste time, don’t try installing the 10.15.3 combo updater. (Image above) You need to:

  • Unenroll from the beta program,
  • Install the latest macos from the App Store,
  • Re-enroll into the beta program, and
  • Upgrade to the latest beta.

Prepare to waste your entire morning on this, hopefully less the time I spent figuring it out courtesy of friends. Once you get the App Store blessed macos install going, remember that once it reboots, there’s another hour of wait time just on the standard install:

Those 13 minutes are a lie.

Expect at least another hour once you get to the beta access installer:

Hopefully this post will help anyone doing a websearch to get out of exactly this issue at some point in the future.

A final note: Once you’re up to date, swear to yourself that your rule of “never be the first to install a new beta” doesn’t mean “never be the last to install the new beta“. Hopefully I’ve learned my lesson.

Adjusting HDMI volume on Catalina

Normally when you output audio through HDMI, you cannot control its volume from your Mac. You adjust it on the output device instead, whether it is a monitor or TV.

I’m working on my Catalina laptop while my mini is in the shop and using my newish DELL monitor without my normal speakers, which plug into the headphone jacks. With this setup, my music is loud, even when I have the monitor settings fairly low.

As it is a pain to adjust volume through the monitor menu, I decided to give Soundflower/SoundflowerBed a try. I was quite sure that Catalina would have killed it by now.

I was wrong.

In SoundflowerBed, I chose HDMI for the 2-channel output.

In System Preferences, I selected Soundflower (2ch) as my sound output device.

Boom. My volume control came back to life and my ears are recovering from the onslaught.

Seeing an old friend keep plugging along and doing what I need is remarkably satisfying. Thank you to everyone who has worked on Soundflower through the years, from Alexander Hudek and RogueAmoeba to MattIngalls and Maciej Wilczyński.

Circles within circles: custom types and extensions

So the other day Paul and I were shooting the breeze about spirographs. He was saying I needed to get back to blogging now that I have the time. Of course, the second you set out to try to write a post, so many ideas flow forth at once, it actually is hard to pick just one and focus on it. Since he had enjoyed my drawing work, I thought I’d start out with a little push at circles.

That quickly went out of control because I ended up with enough material for a few chapters rather than a single blog post. (And, no, my chapter for his book is about property wrappers, which I love, rather than drawing)

To give a little context, before I decided to try my hand at full time work, I had had a contract with Pragmatic to update my Drawing book. I had to cancel that because commuting full time to another city left me with zero free time for my self, my family, let alone writing. (Now that I am back at my own desk, I may see about returning to that project because geometry is a core passion.)

Anyway, let me start at the end of all this and show you some pictures and then I’ll (pardon me) circle back and start telling the story of the code and how all this came to pass.

I started my day by opening a playground, deciding to just flex my coding and see where it would take me. I wanted to work with circles, so I plugged a range into a map to get me there.

// Get some points around a circle
let points = (0 ..< nPoints).map({ idx -> CGPoint in
  let theta = CGFloat(idx) * (CGFloat.pi * 2 / CGFloat(nPoints))
  return CGPoint(x: r * cos(theta), y: r * sin(theta))
})

// Create a path
let path = UIBezierPath()

// Get the last point before the circle starts again
let lastIndex = points.index(before: points.endIndex)

// Draw something interesting
for (point, index) in zip(points, points.indices) {
  let p0 =  index == lastIndex
    ? points.startIndex
    : points.index(after: index)
  let p1 = index == points.startIndex
    ? lastIndex
    : points.index(before: index)
  path.move(to: .zero)
  path.addCurve(to: point, controlPoint1: points[p0], controlPoint2: points[p1])
  
}

It’s not the worst code in the world, but it’s not great. I was rewarded with a pretty path, so whee. Still, the code was screaming out that it wanted to be better.

I started by attacking the index work. Why not have an array that supported circular indexing by offering the next and previous items? So I built this:

public extension Array {
  /// Return the next element in a circular array
  subscript(progressing idx: Index) -> Element {
    idx == indices.index(before: indices.endIndex)
      ? self[indices.startIndex]
      : self[indices.index(after: idx)]
  }
  
  /// Return the previous element in a circular array
  subscript(regressing idx: Index) -> Element {
    idx == indices.startIndex
      ? self[indices.index(before: indices.endIndex)]
      : self[indices.index(before: idx)]
  }
}

Before you start yelling at me, I quickly realized that this was pretty pointless. Arrays use Int indexing and I had already written wrapping indices a billion times before. Just because I has the concept of a circle in my head didn’t mean that my array needed to. I pulled back and rewrote this to a variation of my wrap:

public extension Array {
  /// Return an offset element in a circular array
  subscript(offsetting idx: Index, by offset: Int) -> Element {
    return self[(((idx + offset) % count) + count) % count]
  }
}

Much shorter, a bit wordy, but you can offset in either direction for as far as you need to go. I did run into a few out of bounds errors before remembering that modulo can return negative values. I decided not to add in any assertions about whether idx was a valid index as arrays already trap on that, so a precondition or assertion was just overkill.

Next, I decided to design a PointCircle, a type made up of points in a circle. Although I initially assumed I’d want a struct, I soon realized that I preferred to be able to tweak a circle’s radius or center, so I moved it to a class instead:

/// A circle of points with a known center and radius
public class PointCircle {
  
  /// Points representing the circle
  public private(set) var points: [CGPoint] = []
  
  /// The number of points along the edge of the circle
  public var count: Int {
    didSet { setPoints() }
  }
  
  /// The circle radius
  public var radius: CGFloat {
    didSet { setPoints() }
  }
  
  /// The circle's center point
  public var center: CGPoint {
    didSet { setPoints() }
  }
  
  public init(count: Int, radius: CGFloat, center: CGPoint = .zero) {
    (self.count, self.radius, self.center) = (count, radius, center)
    setPoints()
  }

  /// Calculate the points based on the center, radius, and point count
  private func setPoints() {
    points = (0 ..< count).map({ idx -> CGPoint in
      let theta = CGFloat(idx) * (2 * CGFloat.pi / CGFloat(count))
      return CGPoint(x: center.x + radius * cos(theta), y: center.y + radius * sin(theta))
    })
  }
  
}

I added observers to each of the tweakable properties (the radius, point count, and center), so the circle points would update whenever these were changed. By the way, this is a great example of when not to use property wrappers. Wrappers establish behavioral declarations, which this is not. My circle uses the observers instead to maintain coherence of its internal state, not to modify, limit, or expand type side effects for any of these properties.

Now I could easily create a circle and play with its points:

let circle = PointCircle(count: 20, radius: 100)
let path = UIBezierPath()
for (point, index) in zip(circle.points, circle.points.indices) {
  path.move(to: circle.center)
  path.addCurve(to: point,
                controlPoint1: circle.points[offsetting: index, by: 1],
                controlPoint2: circle.points[offsetting: index, by: -1])
}

I decided not to add some kind of API for passing a 2-argument (point, index) closure to my circle instance. It doesn’t really add anything or make much sense to do that here. It wouldn’t produce much beyond a simple for-loop, and the point here is to build a Bezier path, not to “process” the circle points.

My goal was to build enough support to allow me to iterate through the points and reference adjacent (or further offset) points to build curves. This does the job quite nicely.

It didn’t feel quite finished though. I wanted to add a little extra because one of the things I often do with this kind of drawing is create stars and points and loops using offsets from the main radius, often interpolated between the main points on the circle’s edge. Because I had built such a simple class, adding an extension for this was a snap:

extension PointCircle {
  /// Returns an interpolated point after a given index.
  ///
  /// - Note: Cannot pass the existing radius as a default, which is why the greatest finite magnitude is used
  public func interpolatedPoint(after idx: Int, offsetBy offset: CGFloat = 0.5, radius r: CGFloat = .greatestFiniteMagnitude) -> CGPoint {
    let r = r == .greatestFiniteMagnitude ? self.radius : r
    let theta = (CGFloat(idx) + offset) * (2 * CGFloat.pi / CGFloat(count))
    return CGPoint(x: center.x + r * cos(theta), y: center.y + r * sin(theta))
  }
}

The most interesting thing about this is that you cannot use a type member to default a method argument, which is why I ended up defaulting it to `greatestFiniteMagnitude`. It seems to me that it would be a natural fit for the Swift language to allow that kind of defaulting. In this case, it would say: “If the caller doesn’t specify a radius, just use the one already defined by the instance.” What do you think? Is that too weird an ask?

To wrap up, here’s an example using the interpolation:

let path2 = UIBezierPath()
for (point, index) in zip(circle.points, circle.points.indices) {
  path2.move(to: point)
  path2.addCurve(to: circle.points[offsetting: index, by: 1],
                 controlPoint1: circle.interpolatedPoint(after: index, offsetBy: 0.33, radius: circle.radius * -2),
                 controlPoint2: circle.interpolatedPoint(after: index, offsetBy: 0.67, radius: circle.radius * -2))
}

All the shapes at the start of this post are created from this and the preceding loop by tweaking the curve parameters. What pretty results will you come up with? If you build something particularly lovely, please share. I’d love to see them!

Reading stdin on the fly

Shai Mishali asks, “Is there something like readLine that lets me read stdin as the user types instead of waiting for an entire line?” The readLine function waits until the user presses enter or otherwise recognizes EOF to process input.

A little web search led me to this SO post, which is  based on this example, using termios, the Unix API for terminal I/O. After looking through both posts, I built a RawMode type to support direct input based on that code because nothing makes me happier than messing with solutions to make them a little swiftier.

To use this, you fetch bytes until you catch an EOF. It’s interesting to test both direct keyboard entries as well as pasting complex emojis. As I was working on this on both 10.14 and 10.15, I was mildly startled when my initial test code stopped working when I moved to 10.14. UnicodeScalar does not offer a utf8 view until 10.15:

extension Unicode.Scalar {
  @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  @frozen
  public struct UTF8View {
    @inlinable
    internal init(value: Unicode.Scalar) {
      self.value = value
    }
    @usableFromInline
    internal var value: Unicode.Scalar
  }

  @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  @inlinable
  public var utf8: UTF8View { return UTF8View(value: self) }
}

So thank you, Swift team, for giving us that. It’s a nice addition to the language!

Anyway, if you have a different approach or you can see ways to improve my take further, drop a comment and let me know.

Black Cyber Friday Monday and the art of the outdated iPhone

I decided to replace my aging iPhone 6+ a few months ago with a refurbished one- or two-years old iPhone model. The iPhone XR seems like a solid choice and I looked forward to some Black Friday/Cyber Monday deals. I assumed I’d purchase a “renewed” model rather than pay full price from Apple.

Then reality hit.

The “renewed” unlocked 128GB XR went from about $560 a few weeks back to $600 on Black Friday and soared even higher over the weekend. Today, it’s back “down” to $630 although it exceeded that price at its height..

Apparently, secondary markets plus high consumer demand create different pricing outcomes than shiny loss leaders.

This lead me to a sea-change in my planning and expectations.

An unlocked brand new SIM-free 128GB XR retails for $649 from Apple plus they throw in a $50 gift card today. If I hand over my existing iPhone 6+, my price goes down $80 (although I’m tempted to hand it down to a child). Not having to deal with Gazelle or the vagaries of Amazon vendors seems like a happy outcome.

Oddly, this is the last major purchase from my yearly “Pick up on Black Friday/Cyber Monday” list. The rest all went fairly smoothly, especially the $129 Apple Watch 3 for my middle child (Thank you David Ashman!) and the $649 MBA for my eldest.

I find year-by-year my purchase requirements are shrinking and changing. I’m spending more on services like iCloud and Backblaze, VPNs and NoIP, and less on hardware. I didn’t bother buying any external backup hard drives this year as I still have a couple in boxes from last year.

For those keeping track, I didn’t replace or upgrade my 2012 Mac mini although I am seriously considering the dual drive upgrade several of you recommended. I now run Mojave and Catalina installs on separate machines because I don’t want to lose access to thousands of dollars of software from the 32-bitpocalypse while I need access to the latest dev tools and OS. It’s a fairly frustrating situation.

So where did I spend my BF/CM money? This year, it was mostly about  gifts (carbon fiber PLA filament for the boy, Switch games for the girls), education (the watch and the laptop), and prosaic things-we-simply-needed (replacing a broken space heater).

It was also a good lesson on what not to buy during the rush.

Review: In praise of Ring Fit Adventure

I bought my son Ring Fit Adventure as a reward for bringing home good grades. Little did I know that I would soon monopolize the game. Yes, he uses and loves it too, but only after school. For me, it is full of 2-10 minute exercise breaks perfect for pausing work and getting myself moving.

I recently left a position with a massive commute to a nearby city, ridiculous hours, and lots and lots of sitting. It completely broke up my routine, which had previously included a well considered mix of resistance, strength, flexibility, and aerobics (mixed with a bit of Restorative and Yin yoga, which are the bomb).

My fitness pre-job was hard earned. Lots of its early ramp-up was due to a mix of physical therapy and Wii Fit (or as I like to call it Wii Fiit). Despite the harsh criticism Wii Fit gets, it is an amazing tool for convalescence and several of its activities (most specifically boxing) can get a good heart rate and sweat going. I particularly love its quirky “yoga”, which opened me up to a much wider world of non-scare-quote yoga.

Wii Fit eventually evolved into Wii Fit Plus, which I have never played, mostly due to the fact that we own a bunch of Wii Fits and I never saw enough value to upgrade to the newer system. When the Switch debuted, I hoped they’d continue Nintendo’s fit tradition, and Nintendo finally delivered with Ring Fit Adventure. (Also, it tickles me silly that the Wii Fit trainers are now in Smash.)

Ring Fit Adventure is basically an interval training system based on jogging in place mixed with resistance training using a Pilates Ring and some minimal stretching. It doesn’t sound like much but it works brilliantly. If you regularly move a lot of iron around in your life, this isn’t going to do much for you but if you’re coming off six months of nearly complete inactivity and want to get back in shape to take advantage of more physicality, it’s perfect.

I find it’s never hard to build a sweat and get my heart moving for a good 20-60 minutes. In addition the game has incentivized many of my most hated activities (I’m looking at you, Mr. Squats) and turned them into something I am determined to conquer. Yes, being short means the travel of the thigh sensor may be occasionally tricky to detect (meaning deeper squats for me) but the pilates-style ring is deservedly the star of this show.

From what I can guess, it has some interior material that changes resistance as it bends to allow the system to detect interaction both pushing in and pulling out. There’s a larger-than-expected vocabulary of fitness activities you can incorporate using those two actions including overhead presses and pulls, forward and rear, and the star of the show, the engaged “ab-guard”, which when done correctly allows you to engage your gluts, abs, and shoulders, or basically all the things my PT regularly tells me to engage.

Technically this is a game and there’s some kind of irritating storyline about a picked-on pilates-ring and his steroid abuser ex, but you can ignore most of that and enjoy the beautiful tracks and vistas. There’s a bit of repetition from world to world and a “cheat” system that involves drinking lots of smoothies (because pure sugar is always what people trying to control insulin metabolisms want in their system, right?) but these, too, are minor issues.

Mostly, the game is great because it is, at least for me, hard and it ramps up its difficulty level to level. I’m savoring each “world” and I am in no hurry to finish.  Most recently, I spent time pushing and pulling blocks along a running track with my arms so I could run without falling into holes and by the end of the course, I felt it. For me, finding that physical edge and training to that point of exhaustion just before it’s too much is what makes a great workout.

No, the biggest issue is that we have the one Switch and occasionally my child wants to use his toy without his mom demanding 100% access through the day.

This isn’t a cheap hobby. A basic fully fitted Switch with dock will set you back $300. Add in a second pair of joycons, and that’s another $70-$80. The game alone is $80. And if you end up destroying the pilates ring (the “ring controller” or “ring con”) that’s $45 + shipping direct from Nintendo. (And if you do break that, chances are good you’ll have banged up a joycon as well.) Fortunately, our ring is doing well despite the abuse we have put it through. And we have, as yet, not flung any joycons across the room or into the TV.

Despite the price, most of the cost involves things we would have bought anyway or already own. They’re expensive but have proved to be a delight for the family.

As for Ring Fit Adventure, you shouldn’t have to wait for your report card to treat yourself or your kids to this. I suspect the adults will be playing it longer than the kids unless you treat some of its minigames as pass-and-play so you can enjoy it together.

I hope there will be follow-up fitness products but based on Wii Fit/Wii Fit Plus, Nintendo has a respectable heritage of a single, focused fitness title.

I’ve been waiting for great training and fitness innovation products that combines the Apple Watch and Apple TV but so far, nothing has grabbed me in the way that Wii Fit and Ring Fit Adventure have.

What about you? Any amazing finds?

Taking charge of those xips

Apple adopted the digitally-signed xip format for Xcode downloads a few years ago. It’s basically a signed version of zip archives. Most commonly, you download a xip and double-click. Archive Utility will open the file, verify its signature, and expand its contents.

In its default settings, Archive Utility always expands files to the same folder you download to. With Xcode, this is a big pain as moving the app with its thousands and thousands of tiny subfiles and embedded executables takes forever. Alternatively, moving the xip file from one location on your system to another can be painfully slow.

Fortunately, Archive Utility does allow you to specify where to unpack. Launch the Application using spotlight (or /System/Library/CoreServices/Applications/Archive Utility.app) and open preferences.

Although there’s no “Ask” option for “Save expanded files”, you can select where you want items to be stored using “into” from the pop-up:

Once set, you have to unset it for general use, because the location persists between launches. This is, needless to say, a big pain when you use archives for non-Xcode purposes on a regular basis:

Fortunately, you can unxip more effectively by using the command-line xip utility located in /usr/bin/xip without having to mess with Archive Utility or its preferences:

% xip
Usage: xip [options] --sign <identity> <input-file> [ <input-file> ... ] <output-xip-file>

Usage: xip --expand <input-file>

99.9% of everything you do with xip is that last “Usage” example. Still, as xip doesn’t offer a --help option, if you want to know what those interesting [options] are, you’ll need to read the man page (man xip). I prefer to open man pages in Preview instead of the command line, using this little trick:

man -t xip | open -f -a /System/Applications/Preview.app

Notice two things here:

  • First, the -t flag tells man to use the Groff typesetter (no relation) to format the page to postscript. This presents as a PDF in Preview. (Specifically, it uses /usr/bin/groff -Tps -mandoc -c if that kind of detail intrigues you.)
  • Second, the path for Preview has changed in Catalina to /System/Applications. If you want to do this on Mojave or earlier, adjust the path accordingly.

(Isn’t that a neat way to view man pages?)

While the man page suggests you can sign your own xip archives and provide your own identities, don’t bother. This format is exclusive to Apple, starting  from macOS Sierra. Only xip archives signed by Apple can be expanded in modern macOS releases. (See Tech note TN 2206 for details.)

Since --expand cannot be used with any other arguments, hop over to /Applications, and expand from there:

% cd /Applications/
% time xip --expand /Volumes/Kiku/xips/Xcode_11.2.1.xip 

Adding the time command at the start of the line lets you know how long it took to unxip, which is deeply satisfying to those of pedantic bent like myself. For those playing along, it was

xip: expanded items from "/Volumes/Kiku/xips/Xcode_11.2.1.xip"
1109.625u 275.408s 10:58.85 210.2%	0+0k 0+0io 167pf+0w

Update:

Whisky tango foxtrot: Xcode allows ObjC switch unindenting

This happened.

You might think I’m about to go off on some Swift rant (and trust me there is a Swift rant inside me waiting to emerge) but it’s the second checkbox that made my mind explode.

From:

To:

Who thought this was a good idea? I’ve never been a fan of left-aligned case in Swift although I embrace it as the standard. But in Objective-Freaking-C? As a standard Apple-blessed toggle in Xcode? No! Thrice no! The option enabling the choice is bad for Swift and worse for Objective-C.

Why is this option in there and why is it available for both languages? It would be best to, as Joe Groff put it, “let sloth naturally lead everyone to pick the default” given that the feature has been expressed in Xcode. Or better yet, file some bug reports for the broken feature.

Each language default reflects years (and decades) of language style consensus:

  • Swift: keyword-aligned.
  • Objective-C: “scope”-aligned.

This new choice in preferences is madness.

Talk me down from here, friends.