Archive for the ‘Development’ Category

Pasting quoted code perfectly

You have some code you need to incorporate into a multi-line string. What’s the quickest and best way to handle it? Although I see people do this all the time, manually adding spaces to each line isn’t the best solution.

Here’s a quick Xcode tip:

  • First, paste your material into scope. Retain the indentation by using Edit > Paste and Preserve Formatting.
  • Next, if you haven’t placed them already, add the assignment and triple-quotes above and below the pasted material.
  • Select your material and use Editor > Structure > Shift-Right (Command-]) to line up the left edge of the text with the closing triple-quote. This command moves all selected material n spaces to the right, depending on how you’ve set up your tabbing. There’s a matching Shift-Left if you indent a little too much.

Hope this helps someone.

Executing command-line directly from Xcode

I got pulled into one of those conversations where I end up saying, “Fine, I’ll put up a post about it” and this is the post. Yes, you can test and run command-line apps directly from Xcode but I pretty much never do. It’s a pain with few benefits. That said, here’s how you do it.

Arguments

Let’s say you need arguments. Open your scheme (⌘<) and select the Run > Arguments tab. Add the arguments you want to pass on launch one at a time. Double-click to edit any argument:

The arguments are vended byCommandLine.arguments. Either count the array or use CommandLine.argc to find out how many arguments you’re dealing with.

print(CommandLine.arguments)
print(CommandLine.argc)

Counter-intuitively, Xcode does not automatically quote the arguments for you. This produces five arguments, not three, or six if you include the command itself:

["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "second", "third", "fourth", "fifth"]
6

And what do you expect from the following?

You get this if you run directly in Xcode’s console:

["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "several items at once", "third"]
4
Program ended with exit code: 0

But if you set your code to execute using Terminal:

Launching: '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test'
Working directory: '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug'
3 arguments:
argv[0] = '/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test'
argv[1] = 'first'
argv[2] = 'several'
["/Users/ericasadun/Library/Developer/Xcode/DerivedData/Test-gwehknnihlcsiucsovtbnlrdtfun/Build/Products/Debug/Test", "first", "several"]
3

Xcode’s Crazy Terminal Option

If you’re running anything with direct key input (using POSIX termios/raw mode) or curses, running in the console doesn’t work. So Xcode provides a way to run those utilities in the terminal. Visit Run > Options and scroll all the way down.

This feature is buggy as hell, produces ridiculous amounts of excess text (see this), can take a significant time to launch, and even more time for Xcode to realize the process has finished. It is impossible to use with paths that use spaces (“warning: working directory doesn't exist: '/Volumes/Kiku/Xcode/Derived'“).

I don’t like it. I don’t use it. But it exists.

Sane Command-Line Execution

Unless you’re dealing with things like automation and such, you can try out your compiled command-line apps by dragging your executable from the Products group onto the terminal. This places the path to your build at the prompt. Type out your arguments and press return:

However, I prefer to use a Copy File build phase. Select your Target > Build Phases, click plus (+) and add the executable. (I use absolute path and disable “only when installing”.) This lets you install directly to  standard locations like /usr/local/bin or ~/bin, or if you don’t want to place it there until it is stable and ready for deployment, you can use a development folder:

Assuming your destination is in your shell’s path, start a new shell for the executable to be picked up the first time. After that, you can compile and run as you like.

A different way to develop SwiftPM Packages inside Xcode projects

WWDC gave us many reasons to both migrate libraries to SwiftPM and to develop new ones to support our work. The integration between Xcode development and SwiftPM dependencies keeps growing stronger and more important.

Apple’s Editing a Package Dependency as a Local Package assumes you’ll drag in your package to an Xcode project as a local package overrides one that’s imported through a normal package dependency.

In Developing a Swift Package in Tandem with an App, Apple writes, “To develop a Swift package in tandem with an app, you can leverage the behavior whereby a local package overrides a package dependency with the same name…if you release a new version of your Swift package or want to stop using the local package, remove it from the project to use the package dependency again.”

I don’t use this approach. It’s not bad or wrong, it just doesn’t fit my style.

On the other hand, opening the Package.swift file directly to develop has drawbacks in that it doesn’t fully offer Xcode’s suite of IDE support features yet.

So I’ve been working on a personal solution that best works for me. I want my package development and its tests to live separately from any specific client app outside a testbed. I need to ensure that my code will swift build and swift test properly but I also want to use Xcode’s built-in compilation and unit testing with my happy green checks.

I set out to figure out how best, at least for me, to develop Swift packages under the xcodeproj umbrella.

I first explored  swift package generate-xcodeproj. This builds an Xcode library project complete with tests and a package target. You can use the --type flag to set the package to executable, system-module, or manifest instead of the default (library) during swift package init:

Generate% swift package init
Creating library package: Generate
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/Generate/Generate.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/GenerateTests/
Creating Tests/GenerateTests/GenerateTests.swift
Creating Tests/GenerateTests/XCTestManifests.swift
Generate% swift package generate-xcodeproj
generated: ./Generate.xcodeproj

Although SwiftPM creates a .gitignore file for you as you see, it does not initialize a git repository. Also, I always end up deleting the .gitignore as I use a customized global ignore file. This is what the resulting project looks like:

As you see, the generated Xcode project has everything but a testbed for you. I really like having an on-hand testbed, whether a simple SwiftUI app or a command line utility to play with ideas. I looked into using a playground but let’s face it: too slow, too glitchy, too unreliable.

It’s a pain to add a testbed to this set-up, so I came up with a different way to build my base package environment. It’s hacky but I much prefer the outcome. Instead of generating the project, I start with a testbed project and then create my package. This approach naturally packs a sample with the package but none of that sample leaks into the package itself:

I end up with three targets: the sample app, a library built from my Sources, and my tests. The library folder you see here contains only an Info.plist and a bridging header. It otherwise builds from whatever Sources I’ve added.

I much prefer this set-up to the generate-xcodeproj approach, although it takes slightly longer to set-up. The reason for this is that SwiftPM and Xcode use different philosophies for how a project folder is structured. SwiftPM has its Sources and Tests. Xcode uses a source folder named after the project.

So I remove that folder, add a Sources group to the project, and ensure that my build phases sees and compiles those files. The Tests need similar tweaks, plus I have to add a symbolic link from Xcode’s tests name (e.g. “ProjectNameTests”) to my SwiftPM Tests folder at the top level of my project to get it to all hang together. Once I’ve done so my green checks are ready and waiting just as if I had opened the Package.swift file directly. But this time, I have all the right tools at hand.

Since I’m talking about set-up, let me add that my tasks also include setting up the README, adding a license and creating the initial change log. These are SwiftPM setup tasks that swift package init doesn’t cover the way I like. I trash .gitignore but since I have Xcode set-up to automatically initialize version control, I don’t have to git init by hand.

I suspect this is a short-term workaround as I expect the integration of SwiftPM and Xcode to continue growing over the next couple of years. Since WWDC, I’ve been particularly excited about developing, deploying, and integrating SwiftPM packages. I thought I’d share this in case it might help others. Let me know.

The (Switch) Case of the Missing Binding

Here’s a cool little challenge brought up this morning by a friend. Consider the following code:

switch foo {
  case .a: return "a"
  case .b(let str) where str.hasPrefix("c"), .c: return "c"
  case .b: return "b"
}

It won’t compile.

When you bind a symbol for one pattern, you must bind that symbol for every pattern in a case. This prevents you, for example, from binding str in one pattern and then attempting to use str in the shared case body. For example, consider this case. What would you expect to happen when foo is .c?

func switchTheFallthroughOrder(foo: Foo) -> String {
    switch foo {
    case .a: return "a"
    case .b(let str) where str.hasPrefix("c"), .c:
        // Using `str` here is bad!
        print(str)
        return "c"
    case .b: return "b"
    }
}

Despite my first knee-jerk refactoring, moving out the .c case to use fallthrough doesn’t work. Again, this is because str is not bound for .c and could be used in the successive case body:

However, as Greg Titus pointed out, if you switch the order to use the binding case first with fallthrough, Swift knows at compile time that the binding won’t carry on beyond that scope. This resolves the error, since str is only used in the where clause to narrow the pattern matching:

Further, when using bindings in case tests, a waterfall approach where the bound items are used before fallthrough can extend through multiple steps with the blessing of the compiler:

case .widest(let first, let second) where first.satisfiesACondition():
    // can use `first`, `second` here
    fallthrough
case .medium(let second) where second.satisfiesAnotherCondition():
    // can use `second` here even if it was bound 
    // via `widest` above via fallthrough
    fallthrough
case .narrowest: return someValue

My thanks to Greg Titus for figuring this all out!

Importing Web-based SwiftPM packages to your Xcode Playground

I’ve been kicking the wheels on Xcode 12 and its ability to use frameworks and packages with playgrounds. Up until now, I’ve only been able to import packages that are either downloaded or developed locally on my home system. However, a lot of the packages I want to work with are hosted from GitHub.

I decided to follow a hunch and see if I could import my dependency through a local Forwarding package and then use that code. Long story short: I could.

Here’s my playground, successfully running.

The RuntimeImplementation is declared in a GitHub-hosted package called Swift-General-Utility:

What I did to make this work was that I created what I called a Forwarding Utility, whose sole job is to create a shell package that depends on the remote package and forwards it to the playground. It looks like this. It is a single file called “Forwarding.swift” (no, the name is not at all magic.) in Sources/. I use @_exported to forward the import.

/*
 
 Use this to forward web-based dependencies to Swift Pkg
 
 */

@_exported import GeneralUtility

Its Package.swift installs the dependency:

    dependencies: [ .package(url: "https://github.com/erica/Swift-General-Utility", .exact("0.0.4")), ],
    targets: [
        .target(
            name: "ForwardingUtility",
            dependencies: [ .product(name: "GeneralUtility"), ],
            path: "Sources/"
        ),
    ],

And that’s pretty much all that there is to it, other than (as I mentioned in my other post about how to use SwiftPM packages in playground workspaces) that you may have to quit and re-open the first beta before you can import the forwarding.

Let me know anything that I messed up. But also let me know if this was helpful to you!

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.

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?