Archive for the ‘iOS’ Category

Good Things: SwiftUI on Mojave in iOS Playgrounds

Yes, you can upgrade to the Catalina Beta. Or, you can keep getting work done on Mojave and still enjoy the glory of SwiftUI exploration.

  1. Install Xcode 11 beta along side your 10.2.x Xcode
  2. Using the beta, create an iOS playground. (This won’t work with macOS, which attempts to use the native frameworks, which don’t  yet support SwiftUI)
  3. Import both SwiftUI and PlaygroundSupport.
  4. Set the live view to a UIHostingController instance whose rootView conforms to View.

Here’s an outline of the basic “Hello World” setup:

From there, you can create pages for each of your SwiftUI experiments, allowing you to build short, targeted applets to expand your exploration and understanding of the new technology.

Betas, Damned Betas, and 11.3

After spending an eternity trying to connect to WiFi and my Apple ID after updating to the latest beta, I googled and discovered I wasn’t alone. A short while reading posts later, I downgraded to 11.2.5 and removed my beta profile from my iPad.

Here’s why: I could sign into my Apple ID in Safari but not Settings, and I was in an eternal loop of “you need to accept terms and conditions”-sign in-invalid password. I wasted an entire afternoon on this nonsense.

To save you (some) trouble:

  • Enter DFU Mode (hold power & home forever until the connect to iTunes appears)
  • Option-click Upgrade/Update (whatever it is) in iTunes rather than restore.
  • Navigate to the actual ipsw download (sorry, no easy “preferred” update), select it, wait for it to download a really really big file.
  • Go through the endless set-up again, cursing the day you chose  really secure long involved passwords for all your services.
  • ???
  • Profit

I’m going to hold off on new betas until June. What a pain. I hope this helps someone. Let me know.

Carrying user-sourced code forward in Swift Playgrounds for iOS

Had a really neat challenge today, as a Slack-buddy attempted to work with Apple’s exquisitely insufficient Playground Book documentation. His goal was simple: he wanted to be able to incrementally grow and test code from page to page, copying the user’s work as they moved on.

In theory, Swift Playgrounds for iOS enables you to build books where your reader/student incrementally builds code. Each page introduces a new concept, a new tweak, or a new approach. It’s a great way to layer each lesson on a previous take, or to take one lesson and branch it out to multiple endpoints.

You can either build, build, build to one big story or take one core concept and show many different ways to apply it. Either way, you want to be able to bring code — whether from the most recently edited page or from a shared core page — forward, so the reader/student can further engage with it, edit it, and make it fit with each page’s challenge.

Implementing “code forwarding” (I just made up that term) proved trickier than expected. Playground book workflow is often “understood” (that is, you have a deep understanding of what’s required because you’ve worked with it a lot or you’ve poked around at Apple’s examples or reversed engineered to see how things work) rather than explained step by step in the official docs. Because of this, he ran into several roadblocks along the way.

  • First among these, is that Apple does not provide a Playgrounds Book Author tool for Mac. You have to build your books by hand, going through the specs and hoping that each iteration works. Most of the time it does. Sometimes, maddeningly, it does not.
  • Second, you have to transfer the book to the iPad for each test (I use AirDrop™), and guess at what went wrong if it doesn’t work. When testing a series of book-based exercises, you have to either hard-code each “success” sequence (and there’s no way to set a “I am debugging/developing this book” flag) or actually do the coding, which can take a lot of time, especially if you’re debugging page 7 and you have to work through the exercises on page 1 – 6 for each test of page 7.
  • Finally, if your book is even a little out of spec with what Swift Playgrounds for iOS expects, it’s going to die without much feedback or explanation, leaving you scratching your head, cursing Dev Tools (but we really love you guys, we do), or otherwise venting frustration.

I wasted a bunch of hours because I wanted to make this work. And finally, I managed to get things working to my satisfaction. I thought I’d take the time to write things up to save you some bother. Here are a few things I learned while deep diving into today’s experiments.

The Zen of User Code

Code-forwarding allows you to propagate user-sourced/user-edited code from an earlier page in a playground book to a later page in a playground book. When you code-forward from a “source” to a “destination”, Swift Playgrounds for iOS makes a copy of the earlier code and places onto the current page.

That code is copied once and you aren’t given the option to re-copy unless you reset the page. Every page in Swift Playgrounds offers a reset option in the ellipsis menu, but its discoverability is low. Apple expects each reader/student to work through exercises linearly, progressing only when each previous problem is solved. This means that you don’t get “live” updates by popping back and making new changes to the source page. The destination copies once.

That also means you cannot apply code-forwarding until your page is set as complete. By “complete”, I mean that the book’s source code and Swift Playgrounds accept that the reader/student has done sufficient work to move forward and progress to the next page/exercise.

This usually happens by executing a page epilogue. The epilogue tests the state of the page’s data, determines if the problem was solved (for example, whether the robot reached the end square and the code progressed to a hidden portion containing this test), and then updates a user assessment. Unless a page’s assessment status is “passed” (that is, done), the reader/student is not offered code copying on the following page.

This is built into Swift Playgrounds for iOS, and is an underlying assumption on how progressive learning plans operate. It’s a critical pathway for building page-by-page progress and enabling code-forwarding. This is why the following snippet includes its bit of hidden code. This code allows a user to pass, that is receive a passing grade/assessment for the page, without doing any more work than running the current page:

//#-copy-source(id1)
//#-editable-code 
func foo() {
    // ... starter code here
}
//#-end-editable-code
//#-end-copy-source

//#-hidden-code
import PlaygroundSupport
PlaygroundPage.current.assessmentStatus = 
    .pass(message: "Great job!")
//#-end-hidden-code

Building Code-Forwarding

The preceding code incorporates two essential parts of using a copy-source markup area.

  • First, is the actual tagged copy-source code. This delimits the code that gets copied forward to one or more other pages. Make sure to mark it editable when you want to present a challenge requiring end-user-reader modification. You can omit editable tags when you want the next step or branches to start with code you source yourself. It’s an unusual approach but it’s not illegal to do so.
  • Second, is the hidden assessment update. Normally you’d use more sophisticated logic to determine whether a reader/student has met those challenges laid out in the current page before allowing them to .pass or .fail. When you just want to demonstrate core functionality, make it clear in your marked-up write-up that the user must run the code before continuing. Use the approach in this code to “pass on first run” for demonstration. You’ll probably want to update the message to something along the lines of “Great! You’ve seen this work, move to the next page to start making changes.”

Building The Destination

Crafting a destination page is trickier than laying out acopy-source area: You must update your page’s manifest as well as its content source. The manifest will expect properly internationalized source strings. That means at a minimum, a code receiving page will need a Contents.swift file, a Manifest.plist file, and a PrivateResources folder with at least one localized lproj folder (in my case, en.lproj), which in turn holds the ManifestPlist.strings file.

Here’s what a simple manifest looks like for a copying destination. Keep in mind that each value entry for the CodeCopySetup keys is actually a placeholder for localization.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CodeCopySetup</key>
	<dict>
		<key>CopyCommandButtonTitle</key>
		<string>CopyCommandButtonTitle</string>
		<key>DefaultCommandButtonTitle</key>
		<string>DefaultCommandButtonTitle</string>
		<key>NavigateCommandButtonTitle</key>
		<string>NavigateCommandButtonTitle</string>
		<key>NotReadyToCopyInstructions</key>
		<string>NotReadyToCopyInstructions</string>
		<key>ReadyToCopyInstructions</key>
		<string>ReadyToCopyInstructions</string>
	</dict>
	<key>Description</key>
	<string>Description</string>
	<key>LiveViewEdgeToEdge</key>
	<true/>
	<key>LiveViewMode</key>
	<string>VisibleByDefault</string>
	<key>MaximumSupportedExecutionSpeed</key>
	<string>Fastest</string>
	<key>Name</key>
	<string>Name</string>
	<key>PlaygroundLoggingMode</key>
	<string>Off</string>
	<key>Version</key>
	<string>1.0</string>
</dict>
</plist>

I followed Apple’s example in my ManifestPlist.strings file, so the English expressions aren’t terribly exciting. The Name field used in the manifest is spelled out in addition to the button text:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CopyCommandButtonTitle</key>
	<string>Copy My Code From the Last Page</string>
	<key>DefaultCommandButtonTitle</key>
	<string>Start Coding on This Page</string>
	<key>Name</key>
	<string>Copying Text from the Previous Page</string>
	<key>NavigateCommandButtonTitle</key>
	<string>Return to Previous Page</string>
	<key>NotReadyToCopyInstructions</key>
	<string>Be sure to complete the previous page before you move on to solving the next step.</string>
	<key>ReadyToCopyInstructions</key>
	<string>You can bring over your algorithm from the previous page to continue improving it.</string>
</dict>
</plist>

Here, each possible assessment state and action is given a human-readable form. I was unable to make the system “default” to the items mentioned in the Playground Page Manifest documentation (such as “Copy my code” and “Start with provided code”). I’m sure if I tried hard enough, I could have gotten this working per the docs but I didn’t have the time to push.

In building the code-destination contents, link each identifier you used in the source (it’s id1 in this example but it can be any key you want to use) and the page to copy from (Page1). This page names comes from the name of the playgroundpage file hosting the user-edited or user-sourced content.

You must mention the page because you may keep enhancing the same progression of code from page to page, while using a single identifier. If you start on page 1, update on page 2, when you get to page 3, you want to copy from the updated source on page 2, not page 1. Mentioning which source you want to copy helps keep you and the user on track.

//#-editable-code
//#-copy-destination("Page1", id1)

//#-end-copy-destination
//#-end-editable-code

If you’re using a branching storyline (for example, you might explore variations on a sort or showcase different blending modes for merging images), you can place this destination code on each branch page.

More often, you’ll want to progressively modify code through a series of exercises. To carry the code further, add copy-source tags around the destination as in the following code, using the same id1 identifier, and refer to #-copy-destination("Page2", id1) for the next copy on Page 3 and so forth. Read this directive as this is the copy destination for the code tagged with id1 sourced from page 2.

Here’s what an edit-and-carry approach looks like for a second page, referring back to ("Page1", id1). In my imagination, this is the first time code has been copied and this markup sets up a user-editable progression that can be carried to the third page and beyond.

//#-copy-source(id1)
//#-editable-code
//#-copy-destination("Page1", id1)

//#-end-copy-destination
//#-end-editable-code
//#-end-copy-source

That’s pretty much all you need: proper tags, proper localized strings, and proper id/page references. If you’d like to try out a copy of my playground, you can grab a copy from here or email me for a copy if that doesn’t work.

Beta 3 iPad Control Center Updates

The screenshot facility is still broken, retaining the orientation (including sideways and upside down, or blank if launching) of the last application use. However, those tiny “x” buttons for closing an app are gone. It’s slide-up again. Thank you, Apple.

Incidentally, battery life has been good (it was good in Beta 2 for me but I hear that iPhone users weren’t as lucky as iPad installs). And responsiveness is pretty good.

Lessons Learned: iOS Extensions and Keychain Accessibility

Today’s lesson is courtesy of Rizwan Sattar, who writes:

If you’re building a notification service or content extension, keep in mind that these extensions will run even after a device restart, before the first unlock. Normally Apple recommends to set your Keychain items as kSecAttrAccessibleAfterFirstUnlock for “items that need to be accessed by background applications”. When you’re accessing the keychain for things in your notification extensions, you’ll want to set it to kSecAttrAccessibleAlways or kSecAttrAccessibleAlwaysThisDeviceOnly, etc instead.

Thanks Rizwan!

Dropbox’s very bad not okay UX on iOS

Consider this question for a second: why do people own Dropbox? So they can put stuff into it and grab stuff out. Sure, there are other features but that mission statement pretty much the bottom line for iOS use. So why is Dropbox’s iOS client so horrible?

Start with the main menu bar. After selecting an item that you want to open in another app, which of these icons do you tap? The garbage one is clearly wrong, but the ellipsis is a very muddy choice. This should obviously use the (user-facing) share/(developer-facing) actions icon instead.

Apple’s design team agrees.

Once tapped, what’s the next step for “Open in”? Move and copy make sense but you have to tap “Export”, which finally uses the actions icon to give the hint that you want to open the selected file in another application. Bad naming, although it does appear properly as the first action.

Once tapped, you next have to find “Open In”. I have a couple of blockers installed (which shouldn’t even appear in this action sheet), so you have to scroll right to find the right icon.

It’s only after I scroll right that I finally locate “Open In” and can start the progress of opening my file in an appropriate application.

To summarize:

  • The ellipsis is a bad choice. It should be the Action/Share icon and it should directly open the Action Sheet.
  • Rename and Move should be placed into a separate “File actions” menu, along with discard (the trash can). They don’t belong grouped with the Action/Share sheet, which has a specific and conventional meaning in iOS.
  • The app should use care, especially with known file extensions, to limit the options presented in the sheet. “Open In” should be about the only choice for an epub extension file, and the other actions should be constrained to the invoked context.
  • Dropbox made me sad.

iOS 11 Screenshots

The new iOS 11 screenshot / edit feature has got to be my very favorite thing of the upcoming firmware update. Saving a screenshot creates a thumbnail in the bottom corner of the screen. Tap it to enter an edit mode that lets you crop and mark up the image.

  • Scroll left and right to move between screenshots if you’ve snapped more than one.
  • Use the blue handles on the sides and corners of the screenshot to crop the shot.
  • Select an editing tool (pen, highlight, pencil, eraser, lasso, and an assortment colors) then draw with your finger.
  • You undo-and-redo using buttons at the bottom left of the editor window (not shown here, because I hadn’t made any edits yet).
  • Tap Done at the top left to leave. You can save your shot to the photos album.
  • Otherwise, tap the action button (top right), to print, air drop, email, or otherwise work with your now-edited image.  I love the air drop option, which lets me snap, edit, and then throw the image into a blog post or a slack channel.

No wires, no fuss, just great convenience.

Holy War: Why I utterly loathe the new app switcher in iOS 11

Beta 3 Update: iOS 11 Beta 3 now supports swipe-up-to-close instead of the tiny little “x” buttons.

Under iOS 10, you could switch between apps or remove an app from the “recent list” by following these simple steps:

  1. Double-click the Home button to see recently used apps.
  2. Swipe left or right to find the app that you want to use.
  3. Tap the app or swipe upwards to quit/remove it from the list.

  • Each page was  clear and easy to identify. The app name and icon appeared on top.
  • The nearly full size rolodex presentation ensured that users with poor eyesight could easily identify each app.
  • The swipe area to select or remove an app was large, supporting users with a wide range of dexterity skills and motor limitations.

It was a great system that worked well.

In iOS 11, Apple redesigned. It decided to combine this recently used apps list with the control center, so that you could put as much information on-screen at once as possible. This produced an interface with teeny tiny images, and lots of user confusion overload.

In other words, in the current beta and the presentation at WWDC, they espoused a system that is best used by nimble millenials who unlike most every fidget-spinner-ing millenial I have ever met, would not be overwhelmed by sensory overload when presented with far too much information on a single screen.

In my opinion, this new design doesn’t work for the young, the old, the millenial, the seasoned pro, the able, the dis, the hawkeyed, or the near blind. Apple basically disregarded every rule of human usability and thrown it all together into a jumbled disorganized mess:

Compare this screen, with its dock, its windows, and its nearly two dozen control affordances to the iOS 10 version.  It’s a big jumbled insane mishmash of a UI design mess.

The controls in particular are unlabeled. Consider the timer, alarm, and stopwatch icons. They’re nearly identical and randomly scattered. And I can’t for the life of me remember what the dot is to the right of the camera.

As I constantly harp on about, a key factor in enhancing usability is to prioritize recognition over recall. As much as the Apple engineers have aimed to make all the buttons recognizable, they really aren’t.

You have to remember what a lot of these items do: the man in a circle, the magnifying glass, the three different clock faces, etc. You must further remember what happens when you tap and hold these as several of these items have secondary panels with embedded controls beneath them.

This 3-in-1 design breaks George A. Miller’s basic rule of working memory. Miller’s rule argues that you should not present the user with an overwhelming number of interactive items at once. The user cannot effectively remember and strategize interactions when shown more than seven or nine items at a single time. It’s as if Apple didn’t bother passing this screen through a usability evaluation process.

Working memory is not all that’s wrong with this design. In iOS 10, you just tapped or swiped up to manage apps. Those are both relatively large motor functions that require little fine control. That makes them an excellent match for a wide range of user ages and abilities. (Plus the pictures are all big and easy to recognize!)

Compare with iOS 11. To remove an app from the recents list, here’s what you have to do:

  1. Enter the control center. (In an improvement over iOS 10, this can be done with double tap, even if you’ve disabled swipe-up for the control center.)
  2. Swipe left and right to locate the app of interest.
  3. Tap and hold any app screen until the “X” buttons appear (and, maddeningly, the icons and app names disappear). This wait is short but frustrating.
  4. Tap exactly on the “X” to dismiss any app. This usually takes me upwards of three or four tries because the “X” is so small, my coordination is bad, and apparently I may need to re-align my touches with the OS.

In the end, the new control center a big giant mess trying to do too much in a massive design mishmash. I wish Apple would go back to its original design, although I wouldn’t mind some way to access the control center from the App Switcher.

Update: Oh you have to be kidding me:

If you like my posts and you want to say thanks for helping to prevent the meltdown of global civilization (“But if I’m right, and we can swap out that screen shot… Lenny, you will have saved the lives of millions of registered voters”), consider buying a book.  Save the Turtle, save the World. Choose the form of your Constructor. Thanks, all!

The Unexpected Joy of Vector Images in iOS 11

A few years ago, I griped about That Vector Thing, which was the way that Xcode 6 handled PDF vector assets and could not extend them to arbitrary use in UIImages.

Enter WWDC 2017. In Session 201 “What’s New in Cocoa Touch”, Apple described Asset Catalogs with PDF-backed vector images. All you have to do is tick the “Preserve Vector Data” checkbox.

After chatting with some colleagues about whether this would actually work as promised, I dragged up a 29×29 vector PDF image.

And I added it as a 1x image in an asset catalog. Notice the Resizing checkbox to the right. It is normally unchecked by default.

I then built a super-simple single view testbed to test things out.

override func viewDidLoad() {
    super.viewDidLoad()
    let imageView = UIImageView()
    imageView
        .translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    view.addSubview(imageView)
    ["H:|[v]|", "V:|[v]|"].forEach { format in
        NSLayoutConstraint
            .constraints(withVisualFormat: format, 
                options: .init(rawValue:0), metrics: nil, 
                views: ["v": imageView]).forEach { 
            $0.isActive = true 
        }
    }
    let image = UIImage(named: "Biff")
    imageView.image = image
}

Here’s what that 29×29 image looks like running on an iPhone 7+ in the simulator. The 1x image is being rendered on a 3x destination, at a greatly magnified size. Its vector data ensures the image renders without losing detail or clarity. Compare it to the same asset that does not preserve vector data and my original test from 2014:

  

Click above to see full size screenshot originals. Below is a comparison shot from the simulator at the largest size.

I expect there are minor performance hits in scaling and rendering the vector image compared to loading a standard PNG or JPEG, but I didn’t get around to measuring the costs.

If you like my write-ups, please consider buying a book.

Solving Mathieu’s Phone: The mystery of disappearing gigs

The other day, Mathieu’s 16 GB phone suddenly had no space. Even after rebooting, even after reformatting (and not restoring from backup), all his spare bytes were being sucked into a black hole.

He had no songs, few apps, a modest number of photos, and under a gigabyte of space available, making him unable to compile, load, and tests his apps.

cvgmmpaxeaeooco

Each time he deleted one of his apps, the space would mysteriously fill up within a few minutes, adding to the ever increasing “other” bar in iTunes:

unspecified

This delete-then-lose-space behavior made me think that iCloud was trying to store files locally on his phone to reduce cloud access. I suggested that he disable iCloud and sync just the bare essentials like contacts, calendars, and notes. (Mathieu has a paid 300GB iCloud plan.) Sure enough once he logged out and rebooted, over 7GB of space was freed up and he was able to use his phone again.

I’m not super-familiar with iCloud so if anyone can further explain how this works, and how to set up the phone to limit it from glomming space, I’d sure appreciate being able to pass that along. Thanks!