Regular Expressions for unicode string matching in Swift are pretty cool!

I had to do some validation on a username while coding an iOS app today and so dove into regular expression matching. The specification said “Usernames can include upper and lower case letters, numbers, periods, and underscores” and I’m figuring they’re thinking ASCII only. RegEx of [a-zA-Z0-9_.]+ kind of thing. Thinking of my twitter friend @krzyżanowskim and his posts about not being able to include the proper ż in so many places, I wanted to do better.

So I dove into the world of unicode regular expressions and found this very useful resource. I think I’ve figured out what might be a decent answer (please let me know here or on Twitter @GeekAndDad if you have advice to offer :)).

To include all letters, accented letters, symbols, numbers, an underscore or period (.), but exclude all other punctuation or spaces, I came up with this bit of Swift code:

func validateUsername( username: String) -> Bool { 
  // length tests go here and return false on failure 

  let expr = #"^[\p{L}\p{M}\p{N}_.\p{Me}\p{S}]+$"#
  let matchesRange = username.range(of: expr, options: .regularExpression)
  if matchesRange == nil {
    return false
  }

  // additional validation step here

  return true
}

Note that I don’t have to check matchesRange‘s lowerBound and upperBound against the string’s startIndex or endIndex because I’ve included the ^ and $ to indicate that everything in the string must match the expression.

Seems to work so far. I’m impressed and grateful that Swift’s regular expressions support these unicode specifiers (!!!).

Advertisement

SwiftUI tiny bits: Little view extension to log to the console

I wanted to log to the console in the middle of a SwiftUI chain but you can’t put “print” statements in SwiftUI code in most places (“Everything must be a View!” ;)). Since I was in a playground I couldn’t set a breakpoint that logged to the console (my standard strategy when in an application context). So I wrote this little thing:

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 it like so:

import Cocoa
import PlaygroundSupport
import SwiftUI

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
  }
}

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello")
      Text("World!")
        .padding()
        .background(
          GeometryReader { proxy in
            Color.clear
              .printMessage("Info: ", proxy.size)
        })
    }.padding()
  }
}


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

PlaygroundPage.current.liveView = NSHostingView(rootView: ContentView())

Output is:

Info:   (72.0, 48.0)
Info:   (72.0, 48.0)  
Info:   (72.0, 48.0)  

Could well be a better way or issues with this, but it’s just something I put together quickly to solve a problem (“what is the value of proxy.size?”) and it seems to work, so thought I’d share it. Improvements or better ways to get the same capabilities are welcome 🙂

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.

Updated for Xcode 13 thanks to a tip from a reader

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

As pointed out by a reader (thanks Shaps!), as of iOS 13 Apple has added a proper view modifier for this so we don’t have to set environment keys (doing so is fine as this is a documented environment key, but, as noted in the documentation, doing it this way doesn’t propagate to the presentation containing the view of the view it’s applied to and the new view modifier does). So instead of .environment(\.colorScheme, .dark) line the code below has was using and the work around for the presenting view not being in dark mode, I’ve changed the approach below to use this new view modifier:

.preferredColorScheme(.dark)

Here’s an example:

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

which shows our preview in dark mode as we want:

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 {
     ContentView()
       .previewLayout(.fixed(width: 400, height: 100))
       .preferredColorScheme(.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 {
      content
        .previewLayout(.fixed(width: 400, height: 120))
        .previewDisplayName(name)
        .preferredColorScheme(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.

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.