Tiny Hints: a couple of Xcode 11.3 SwiftUI Preview tips

Xcode 11.3 & SwiftUI Previews: Dark Mode preview bug work-around, compact previews, multi-previews for ColorSchemes and Dynamic Text sizes.

Few small tips for previewing with SwiftUI that I just figured out.

Here’s the simple test ContentView.swift file to use in a standard single view iOS SwiftUI app template project in Xcode 11.3 that we’ll use to explore these tips:

import SwiftUI

struct ContentView: View {
  var body: some View {
    HStack {
      Image(systemName: "paperplane")
        .font(.largeTitle)
      VStack {
        Text("Title Text")
          .font(.title)
        Text("Subtitle goes here")
          .font(.subheadline)
      }
    }
  }
}

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Standard Preview looks like this:

So, the first thing is WOW that takes a lot of space for just a small horizontal cell I’m working on (like for list row or whatever). It’s especially annoying if you’re trying to make sure it works well in dark mode, at different dynamic type sizes, etc. First tip is how to make that work better.

1 – How to preview a horizontal or smaller view in less space in the Xcode Canvas

Context: you’re working on a custom horizontal control, or a row in a list and you want to do multiple previews for light & dark mode, or different text size classes.

By default each preview gets an entire iPhone worth of chrome around it which means you have to scroll to check all your variations out – tiresome! Avoid this by using the .previewLayout modifier with a size that’s sufficient for your view. For example:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

This produces a much more manageable preview like this:

The dimensions you use in .previewLayoutwill be specific to whatever you’re working on, of course, but this comes in really handy when you are previewing multiple versions of a horizontal view like this. Let’s demonstrate that with our next tip by making Dark Mode preview in Xcode 11.3 usable outside a NavigationView.

2 – Dark Mode Preview only seems to really work in NavigationViews

So the standard advice on how to preview in Dark Mode is to add .environment(.colorScheme, .dark) to the end of your view creation in PreviewProvider like so:

struct PreviewsDarkNotDark: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
      .border(Color.green)
      .environment(\.colorScheme, .dark)
  }
}

To make it clear what’s going on I’ve added a green border modifier to our ContentView so that we can see where it is when previewed like this because otherwise we’d wonder if it even exists!

Kind of useless :-/

Our views are using the colorScheme that was set and are rendering as if the device is in dark mode, but we can’t see it on a white background. So let’s fix that:

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ZStack {
      Color(.black)
      ContentView()
    }
    .previewLayout(.fixed(width: 400, height: 100))
    .environment(\.colorScheme, .dark)
  }
}

Which looks a lot better:

3 – Combining previews and reducing duplicate code

So now we have dark mode looking good so let’s combine the preceding two tips and look a both light and dark modes on the same screen and without having to scroll:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ZStack {
      Color(.black)
      ContentView()
    }
    .previewLayout(.fixed(width: 400, height: 100))
    .environment(\.colorScheme, .dark)
  }
}

Which looks like this:

Looking pretty good! While writing them in separate PreviewProviders like that helpfully uses their struct names to label them, it does involve a bit of duplicated code. Here’s an alternate way to do it with a ViewModifier to put shared modifiers in and a Group of views which Xcode makes each their own Preview:

struct MyPreviewModifer: ViewModifier {
  var displayMode: ColorScheme = .light
  var name: String = ""
  
  func body(content: Content) -> some View {
    ZStack {
      Color(displayMode == .light ? .clear : .black)
      content
    }
    .previewLayout(.fixed(width: 400, height: 120))
    .previewDisplayName(name)
    .environment(\.colorScheme, displayMode)
  }
}


struct PreviewsLightDarkDynamicText: PreviewProvider {
  static var previews: some View {
    Group {
      ContentView()
        .modifier(MyPreviewModifer())
      
      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark))

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark,
                        name: "dark extraSmall"))
        .environment(\.sizeCategory, .extraSmall)

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark, 
                        name: "dark extraExtraLarge"))
        .environment(\.sizeCategory, .extraExtraLarge)
    }
  }
}

This gives us four previews and adds the ability to name them since they’re all in the same PreviewProvider struct and so don’t get named individually any longer. Looks like this now:

Well, this “tiny tips” got a little long… but hopefully you found these small tips to be useful.

(Code in gist here for easier copy and paste)

Snippet: SwiftUI View printMessage extension

Just a simple utility to print a string in the middle of a view chain when doing SwiftUI (pardon narrow formatting for blog):

extension View {
  func printMessage(_ msg: Any...,
              separator: String = " ",
              terminator: String = "\n")
            -> some View {
    // Print them out as if not
    // converted to an array.
    for m in msg {
      print(m,
            separator,
            terminator: "")
    }
    print()
    return self
  }
}

Use like this:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
  var body: some View {
    VStack {
      TestView(label: "hello", idx: 22)
        .font(.title)
      TestView(label: "hello", idx: 24)
        .foregroundColor(Color.red)
      Spacer()
    }
  }
}

struct TestView: View {
  let label: String
  let idx: Int

  var body: some View {
    Text(label)
      .padding(10)
      .printMessage("\(self.label):", "\(self.idx)")
     // or: .printMessage(self.label, self.idx)
  }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
// In a Swift playground the output is:

hello: 22
hello: 24
hello: 22
hello: 24
hello: 22
hello: 24

Gist here.

Tiny Hints: Swift Playground – landscape size for SwiftUI preview

Assuming you have a ContentView defined and want to see it in the Swift playground preview with a landscape aspect ratio, this code snippet seems to work:

let view = UIHostingController(rootView: ContentView())
// pick whatever size you want (this is iPhone 8 div 2):
view.preferredContentSize = CGSize(width: 667, height: 375)
PlaygroundPage.current.liveView = view

Might be a better way, but when I didn’t find a menu item in Xcode 11.3 I just coded this in to move forward. Seems to work to check layout, though it’s not strictly “landscape orientation” from a device standpoint ¯\_(ツ)_/¯:

Improving on the common AnyCancellable .store(…) pattern.

If you’ve been reading any Combine documentation or code, you’ve probably seen code like this before:

let newPhotos = photos.selectedPhotos
newPhotos
  .map { [unowned self] newImage in
    return self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions)

Pretty standard: call something that returns a publisher, map the results to whatever type you need, assign that value to an instance variable, and finally, store the resulting AnyCancellable into a Set named subscriptions which is an instance variable or property in the enclosing scope. In this case we’re in a UIViewController subclass where it’s defined as:

private var subscriptions = Set<AnyCancellable>()

This code is from the popular book on Combine.

The problem with this pattern in this particular case, and other similar situations, is that the AnyCancellable is never removed from the subscriptions Set. Sometimes this doesn’t matter – when the enclosing scope is going to go away and the code is only called once relatively soon before exiting scope cleans up memory. But sometimes, as in the sample project this code is from, the user can do the action invoking this code (adding photos in this case) repeatedly. Each time this code is invoked the .store(...) call adds another AnyCancellable to the Set. Since this view controller is the root view controller of the app it is never closed and this memory is never cleaned up during the lifetime of the app.


An easy solution is to add an instance variable to your class (a UIViewController in this case):

private var newPhotosSubscription: AnyCancellable?

and then change the code above to something like this so the AnyCancellable is assigned to the instance variable and the .store(..) part is removed:

self.newPhotosSubscription = newPhotos
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)

When this assignment happens it frees the previous value, if any, and thus there will be at most a single AnyCancellable kept around.

Ok, that works. But I’m a perfectionist and that single AnyCancellable hanging around bugged me. That’s fixable, but as I went and fixed this issue in more places in the project there was a proliferation of instance variables as I had to add one for each place this came up.

So here’s a solution I came up with to avoid that.

First, add an extension on AnyCancellable:

extension AnyCancellable {
  func store(in dictionary: inout [UInt64: AnyCancellable], 
             for key: UInt64) {
    dictionary[key] = self
  }
}

Then change the subscriptions instance variable to be a matching Dictionary (and remove the individual instance variables you added for each case previously, if you did that already):

private var subscriptions = Dictionary<UInt64, AnyCancellable>()

In our original function which created the newPhotosSubscription and let the user choose photos, change the code to:

let key = DispatchTime.now().uptimeNanoseconds
 
newPhotos
  .handleEvents(receiveCompletion: { [unowned self] _ in
    self.subscriptions.removeValue(forKey: key)
  })
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions, for: key)

So above we created a Dictionary instead of a Set for our subscriptions instance variable that holds on to the AnyCancellable so it remains allocated while needed. The dictionary allows us to store the AnyCancellable under a unique key, which in this case is a UInt64. On the first line above we create that key and assign it the number of nanoseconds since the device rebooted.

Then we add a .handleEvents operator to the subscription pipeline. Once the publisher has sent the .finished or .failure completion event we no longer need to keep the AnyCancellable around. Our receiveCompletion closure code removes the AnyCancellable stored under the captured key from our subscriptions instance variable.

(Note: if you preferred, you could replace UInt64 with UUID in the dictionary declaration and the AnyCancellable extension. Then instead of setting the key value to DispatchTime.now().uptimeNanoseconds you could generate a new UUID with UUID(). All that matters is that the is a unique value conforming to Hashable and doesn’t cost too much to generate).


I’m relatively new to Combine, so if you know a better way to do this, I’d love to hear about it!


Addendum

1) Be aware that Swift closures capture variables by reference by default (unlike for Objective C). So don’t re-use the key variable like I did in my project 

2) I also ran into a case where the pattern above did odd things for a Combine pipeline subscribed to a Future publisher – the store seems to happen after the pipeline has finished executing (huh?!) and thus the clean up in the completion handler is called before the store. I haven’t dug into that to understand why, but thought I’d mention it in case you are seeing things not get cleaned up when you expect like I was.

-> I later learned that this is because in Combine Future is “eager” and will complete immediately. One option is to wrap it in a Deferred publisher and another is to capture the AnyCancellable in a local variable and only save it to the dictionary if the completion block hasn’t been called yet.

Swift 5 Encodable nested and hoisted JSON

Ran into an Encodable situation that puzzled me for a bit today.

For those who don’t want to read much, the key observation is that try foo.encode(to: container.superEncoder(forKey: key)) will DELETE any existing key node in the container and the encode foo into a new node with that key. However, nestedContainer(keyedBy: CodingKeys.self, forKey: key) will add encoded items to an existing key if it already exists.

Here’s the blow-by-blow of me figuring that out 🙂

(I have a lot of Swift yet to learn, so it’s likely there is a better way to do all this, but my searching wasn’t finding anything and this works and isn’t entirely horrible, so I thought I’d post it in case it helps someone else).

I have the following model which is already Codable (only Encodable shown for brevity):

public enum UserIdentifier: Encodable {
  case email(String)
  case phone(String)
  case guestID(String)
}

extension UserIdentifier {
  enum CodingKeys: String, CodingKey {
    case address, phone, gid
  }
    
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case let .email(value):
      try container.encode(value, forKey: CodingKeys.address)
    case let .phone(value): 
      try container.encode(value, forKey: CodingKeys.phone)
    case let .guestID(value): 
      try container.encode(value, forKey: CodingKeys.gid)
    }
  }
}

This then needed to be a field in a struct representing the body of a POST request to an http server:

struct PostBody: Encodable {
  let userID: User.UserIdentifier
  let name: String
  let password: String
}

and what the server expected in the body is JSON of the form:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

To see what the output of the default Encodable support will output I created a little test case (I often do this kind of thing in a playground, but this was already embedded in an iOS app so it was easiest to just make a test case):

class Tests: XCTestCase {
  func testCreateUserPostBodyToJSON() throws {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted

    let body = UserService.CreateUserPostBody(userID: User.UserIdentifier.email("bob@email.com"), 
        name: "Robert Tree", 
        password: "MyBadPassword!")

    let jsonData = try encoder.encode(body)
    print(String(data: jsonData, encoding: .utf8)!)
  }
}

and, of course we expect this won’t be right, but it’s our starting point.  Here’s what it outputs:

{
  "userID" : {
     "address" : "bob@email.com"
  },
  "name" : "Robert Tree",
  "password" : "MyBadPassword!"
}

So clearly we need a custom encode function to pull the userID enum contents up to the top level. Let’s try this:

func encode(to encoder: Encoder) throws {
  enum CodingKeys: String, CodingKey {
    case username, password
  }
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(name, forKey: .username)
  try container.encode(password, forKey: .password)
  try userID.encode(to: encoder)
}

The key is that last line: try userID.encode(to: encoder). Here’s the new output:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

Bingo! Just what the doctor ordered!

Except then I try the request to the server and it turns out the api documents for the server are out of date (🤦🏼‍♂️) and what the server really wants is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Hmmm. Ok, so this is a little more challenging, but it’s what nestedContainer is for, so we’ll try using that:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
  try userID.encode(to: container.superEncoder(forKey: .user))
}

Looks fancy. Let’s try it:

{
  "user" : {
    "address" : "bob@email.com"
  }
}

OH NO!!! what happened?!!

Hmmm… That looks like just the output of the last line, so comment out
try userID.encode(to: container.superEncoder(forKey: .user))
and see if the first part is working at least:

{
  "user" : {
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Ok. So the first part is creating things exactly how we want it to, it’s just that last line isn’t adding itself to the already existing "user" : { } node. Rats! Documentation isn’t shedding any light (it *does* in fact shed light, but it’s too subtle for me to catch on a too-quick read; as we’ll see below). Tried this on a whim:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  try userID.encode(to: container.superEncoder(forKey: .user))
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
}

And the output is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Success!

So, apparently, superEncoder(forKey: key) (Docs, emphasis mine: “Stores a new nested container for the given key and returns A new encoder instance for encoding super into that container.”) always makes the node for key and overwrites whatever happened to be there before (how rude!). Luckily, nestedContainer(keyedBy: Keys, forKey: key) (Docs: “Stores a keyed encoding container for the given key and returns it.”) doesn’t do that and will just add items to the node if it already exists. Sweet!

Follow up:

It occurs to me that the solution above only works because there’s only a single enum like UserIdentifier to include in the nested structure. They can’t both be first and so the second one would overwrite the first one.

Hopefully there’s another better way to do this that someone will point out in a comment which can handle multiple objects.

Making iOS 12 projects in Xcode 11

Some Xcode 11 iOS project templates aren’t iOS 12 compatible out of the box, but it’s relatively easy to make them work, here’s how:

  1. Select File > New… > Project…
  2. Select iOS and “Single View App” template, for example.
  3. Give the app a name, Swift, and be sure to select “Storyboard”, not “SwiftUI” (which needs some View and some requires iOS 13 according to the error message from Xcode), then select the “Next” to create the project.
  4. Select “AppDelegate.swift” and add @available(iOS 13, *) on the line above each of the func definitions in the “UISceneSession Lifecycle”
  5. Add an implementation of the window property inside the AppDelegate class like so: var window: UIWindow?
  6. Open “SceneDelegate.swift” and add @available(iOS 13, *) on the line above class SceneDelegate
  7. Select the project file in the Project Navigator to edit project settings and select the Project in the left pane of the editor
  8. In the “Info” tab, change the “iOS Deployment Target” at the top of the right pane to the appropriate 12.x deployment target for your project.
  9. Build and Run.  Good to go!

I’ve posted the two edited source files in a gist here

And thus he was gone…

With a harsh gesture at the ground ahead and loud but guttural roar of uttered spell, there was a bright flash, a sharp and loud “CRACK!”, and a deep groaning and tearing sound as the earth before them ripped open to reveal a vast chasm into which tumbled quantities of rock and debris as the edges collapsed inward. With a long and wrenching curse audible even over the furor of a great host of men trying to calm their startled beasts, the speaker dove forward into the depths of blackness and the earth closed up over him like the jaws of some huge monster closing over it’s prey. As the beasts were brought back under control by their riders, a silence spread over the assembled host with only the lesser grumbles of the earth settling back into place and the cries of the startled birds soaring overhead to be heard.

“I want my CFT”

I want my CFT

(sung to the tune of “Money for Nothing” by Dire Straits (1))

Now look at them LIGOs that’s the way you do it
Correct the errors on the CFT
That ain’t workin’ that’s the way you do it
Photons for nothin’ and your phase for free

Now that ain’t workin’ that’s the way you do it
Lemme tell ya them bits won’t flip
Maybe trap an ion in a planar crystal
Maybe trap an ion on a chip

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

I shoulda learned semiconductors
I shoulda learned them fermions
Look at those ions, they can’t find them on the camera
Man, we could have some fun

And he’s up there, what’s that? Rad-pressure noises?
Squeezin’ on the vacuum like a chimpanzee
Oh, that ain’t workin’ that’s the way you do it
Get your photons for nothin’ get your phase for free

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

Listen here, now that ain’t workin’ that’s the way to do it
Correct the errors on the CFT
That ain’t workin’ that’s the way you do it
Photons for nothin’ and your phase for free
Photons for nothin’ and the phase for free
Get your photons for nothin’ and phase for free
Photons for nothin’ and the phase for free (I want my, AdS/CFT)
Photons for nothin’ and the phase for free (I want my, AdS/CFT)


 

Geek is in a Quantum Physics PhD program at University of New Mexico, Albuquerque and his department organized this 21st annual workshop during February 2019 in Albuquerque: http://physics.unm.edu/SQuInT/2019/index.php

Geek attended parts of the event in-between his classes and job teaching undergraduate physics labs.  He sent dad this humorous song summary of the workshop along the lines of others we’ve done on the blog. 🙂

1. Money for Nothing (wikipedia)

 

Memorabilia box reveals another funny item and story…

I lived in rural Maui back when there was zero bus service and you had to have a car to get anywhere (though we did moderately well for local area access on bicycles via back roads and pineapple fields).  The fact that it is an island made out of the slopes of two mountains (one of which is 10,000′ tall!) meant a lot of steep rides though, so like any teenager in a rural location in those days, a car was a significant desire. 🙂

My first automobile was a Chevy Love truckthat I purchased when I was 14-1/2 – six months before I could get a permit and license to drive.  $250 and we had to tow it from a neighbor’s yard because it didn’t run.  It was in terrible shape. He’d stopped driving it several years prior because the back of the truck was so rusted out that his dogs’ legs were falling through and the water in the puddles in the unpaved road to our group of houses was splashing up into the cab through the giant rust holes.  Truly an amazing vehicle! 😛  The back tires were extra wide for some reason, but that just made it seem cooler 🙂  It looked something like this, but with a ton of rust holes:

CHEVY-LUV_Truck.jpg

Of course I was stoked to get it and spent the next 6 months getting it running, putting plywood down in the floor of the truck bed, and so on.  Had to get the rust ground off the brakes by a machine shop (raw metal sitting == rust build up fast in Hawaii), clean out the mildew, etc and so on.

The deal with my parents was that if I drove myself and my sister to school (no school buses to the schools we were in) then they’d pay the car insurance and just enough gas money to go those miles.  The truck maintenance and gas for any other trips was on me.

I got my permit at 15, and two weeks later my license (minimum required waiting time).  The muffler was pretty rusted out and so it sounded a bit like a crop duster when I pressed the gas pedal (i.e., loud and frappy).  It also wouldn’t idle so I had to keep tapping the gas pedal at stop lights and stop signs to keep the motor from dying; other teens thought I wanted to race (ha!).

During some periods it wouldn’t start reliably.  Had to push start it pretty regularly so I parked it up an embankment that let me roll it down towards the house and hopefully get it to start (yikes!).  Some periods I’d start it first and then run inside to take my shower before driving to school so that I wouldn’t have to take a second shower after getting all greasy getting it running.

Anyway, at one point I decided to make a stencil out of legal size file folders (only stiff card stock I had access to for free :)) and spray painta logo on the side of the doors to embrace the crappiness of the truck and so I made one that evoked the circular logo of the state that was on the side of government trucks.

I found the stencil in my mementoes box and without further ado or story telling, here it is:

IMG_8357 small for web.JPG

🙂 Apropos, for truly it was a rusted hulk that should have been scrapped, but got me around the island for maybe a year before I upgraded to a $750 car that burned almost a quart of oil a week and a went through about a quart of transmission fluid every two days until I took the transmission out and got it rebuilt, but that ran much more reliably and had much less rust 🙂  (So bad for the environment – we were so clueless back then!  sigh).

15th Birthday Poem from Dad’s grandmother

Looking through my memento boxes for things for my dad (Geek’s grandfather)’s memorial service and found this poem my grandmother wrote me for my 15th birthday.  She was a great grandmother.

15th birthday poem from Granny Bee.png

 

It references our relation to Daniel Boone, and the large ranch on the coast of California that our family owned for many generations and that my dad (Boone) grew up on.