Blog

Latest Post

SwiftUI: Paths vs. Shapes

In SwiftUI, there are two similar ways to draw shapes:

  • A Path is a struct that contains the outline of a shape. It’s very similar to the CGPath/CGMutablePath classes from Core Graphics.
  • A Shape is a protocol with a single requirement: a method path(in rect: CGRect) -> Path.

A path is a list of lines, curves and other segments that all have absolute positions. A shape doesn’t know its size up front: the framework calls path(in:) once the final size is known. While a path is absolute, a shape can choose to adjust its path to the given rect.

To draw a resizable vector asset in code, we can create a Shape, and then use the given rect to draw an absolute path. Let’s start with the code for an absolutely-sized balloon-like path:

let balloon = Path { p in
    p.move(to: CGPoint(x: 50, y: 0))
    p.addQuadCurve(to: CGPoint(x: 0, y: 50),
                   control: CGPoint(x: 0, y: 0))
    p.addCurve(to: CGPoint(x: 50, y: 150),
               control1: CGPoint(x: 0, y: 100),
               control2: CGPoint(x: 50, y: 100))
    p.addCurve(to: CGPoint(x: 100, y: 50),
               control1: p1,
               control2: p2)
    p.addQuadCurve(to: CGPoint(x: 50, y: 0),
                   control: CGPoint(x: 100, y: 0))
}

balloon.boundingRect // (0, 0, 100, 150)

Interestingly, because Path conforms to View, we can directly draw this in a SwiftUI application:

struct ContentView: View {
    var body: some View {
        balloon
    }
}

2019 08 20 01

By default, the path is filled with the current foreground color. Like a CGPath, we can also apply an affine transform to the path, giving us a new Path:

let balloon2x: Path = balloon.applying(CGAffineTransform(scaleX: 2, y: 2))
balloon2x.boundingRect // (0, 0, 200, 300)

2019 08 20 02

By using the Shape protocol, we can have much more control over paths. For example, we could make a Balloon shape that automatically fills the entire rect (for simplicity, we ignore the rect’s origin):

struct Balloon: Shape {
    func path(in rect: CGRect) -> Path {
        let bounds = balloon.boundingRect
        let scaleX = rect.size.width/bounds.size.width
        let scaleY = rect.size.height/bounds.size.height
        return balloon.applying(CGAffineTransform(scaleX: scaleX, y: scaleY))
    }
}

2019 08 20 03

You can easily turn this into a more generic Fit struct that takes the path as a parameter.

The other APIs are very similar between Path and Shape. For example, we can stroke a Path or a Shape, which gives us back either a new Path or a new Shape, respectively. The moment we apply a fill to the path or shape, the resulting type is some View, which means that we “lose” the fact that we were dealing with a path or shape. For example, it’s not possible to apply a fill and then a stroke, as stroke is only available on paths and shapes.

2019 08 20 04

In conclusion: paths and shapes are almost the same. When you need an absolute drawing, you can use either one, but when you want to adjust the path to the available size, use a Shape.

In Swift Talk Episode 164 we explore paths and shapes in more depth, by building a custom loading animation for our SwiftUI app. This week, we’ll be refining the animation, and adding a geometry effect.

You can see all our episodes in the SwiftUI Collection — the first episode is free to watch.

Previous Posts

Conforming Tuples to Protocols

Tuples in Swift are a great way to quickly combine two or more pieces of data. For example, when working on an SVG parsing library, we had two doubles that we wanted to combine. In some cases it’s very helpful to create a nominal type, such as a Point:

struct Point {
    var x: Double
    var y: Double
}

In other cases, we can’t always come up with a good name. Perhaps we’re working on something more abstract, or the two values could be interpreted in different ways: in one case they might be points, in another they’re interpreted as sizes. Instead of a nominal type, we’d use a structural type such as a tuple:

struct MyModel {
    var pairs: [(Double, Double)]
}

The major disadvantage of using a tuple to model data — as we do above with (Double, Double) — is that a tuple can’t conform to a protocol. This means we can’t just add Equatable, Hashable, or Codable conformance using the implementation we’d otherwise get for free. If we had written our pairs property as [Point] instead, this would be much easier.

However, all is not lost. We can write a type Pair<A, B> that works in the same way as a tuple, but also adds conditional conformance. Here’s one possible implementation:

struct Pair<A, B> {
    var one: A
    var two: B
    init(_ one: A, _ two: B) {
        self.one = one
        self.two = two
    }
}

Now we can conditionally conform Pair to all kinds of protocols:

extension Pair: Encodable where A: Encodable, B: Encodable { }
extension Pair: Decodable where A: Decodable, B: Decodable { }
extension Pair: Equatable where A: Equatable, B: Equatable { }
extension Pair: Hashable where A: Hashable, B: Hashable { }

We can even conform Pair to SwiftUI’s VectorArithmetic protocol, so that it can be used with animatableData.

Of course, a Pair doesn’t have all the same syntactic sugar as a tuple, but it can be a quick alternative in case we want a structural type that can conform to Codable (or any other protocol):

struct MyModel {
    var pairs: [Pair<Double, Double>]
}

Until Swift adds conditional conformance to tuples, this is a workaround that has served us well in a few projects.

Many of our projects feature in Swift Talk, our weekly pair programming video series. The most recent collection explores SwiftUI, as we build a client app for our Swift Talk backend.

To learn with us, subscribe here.

Removing Dependencies

Over the last few weeks, we’ve been cleaning up the dependencies in our Swift Talk backend. Now that we’re done, we finally feel like we’re on top of the code base again. No more dark, dusty corners!

In this post, we’ll outline how the code base has developed over time, and the changes we’ve made to clean it up.

We started developing the Swift Talk backend almost three and a half years ago. To begin with, we hired a contractor to build the site using Ruby on Rails. After we launched, we continued to make small changes ourselves, and hire other contractors for larger things.

When we decided to rewrite the backend in Swift last fall, our goal was to get the new backend up and running as quickly as possible. For the most part, we replicated what the Rails backend was doing before. To focus on building the most interesting parts of the site, we included third party libraries for dealing with PostgreSQL, XML, and crypto functions. We also kept all the CSS and Javascript infrastructure around, serving the same .css and .js files as before, just from our Swift server.

A few things bothered us about the state of this code base:

  1. Some of the Swift dependencies felt unnecessary, given the little code it would take us to directly interface with the underlying libraries.

  2. We didn’t understand the Javascript build stack (lots of node modules, browserify, and babel), and the deprecation warnings in the JS build process were starting to accumulate.

  3. We ended up shipping huge CSS (2 MB uncompressed) and Javascript (7.3 MB uncompressed) files, although they were doing very little to enhance the functionality of the site. For example, the subscription form was built using React, which adds a massive amount of code for a small subsection of the site.

With regard to the Swift dependencies, we ended up writing our own lightweight wrapper around libpq, similar to the wrapper we demonstrated on Swift Talk. Our wrapper is approximately 300 lines of code, almost half of which are straightforward mechanical conversions between Swift types and their representations in PostgreSQL. We’re happy to maintain this small piece of code against a mature library like libpq ourselves. The benefit is that we can easily fix warnings that arise with new Swift versions, fix bugs as we encounter them, or make changes to the API so that it better suits our needs.

We also removed our dependency on BlueCryptor, a Swift wrapper around CommonCrypto on macOS and libcrypto on Linux. Since we were only using it to create md5 hashes for our assets, we wrote our own md5 wrapper around the native crypto libraries, with approximately 30 lines of code.

Before, we used Perfect-XML to parse XML on Linux, as Foundation’s XML parsing gave us strange crashes. In recent releases, these issues have been fixed, and we were able to drop the dependency and use Foundation instead.

On the client side, we slowly dropped more and more Javascript dependencies, until we were able to remove the entire Javascript build stack. There’s no single application.js anymore, instead, we have a few inline scripts for pages that need it. For example, we ended up re-writing the payment form without React, which took about 150 lines of vanilla Javascript code. We maintained the spirit of the React-based code: our custom Javascript also keeps track of a state object and re-renders the potentially affected DOM nodes when the state changes.

We took a long hard look at the CSS, and also managed to remove almost all dependencies.

Taking stock after the refactoring, we cut down the CSS and Javascript sizes to a fraction of what they were before: the uncompressed CSS is 199KB, and there are only a few bits of inline Javascript. This makes for a faster site and — at least in our case — much happier developers 😀.

If you’d like to check out the backend, the source code is on GitHub.

Swift Quiz in Review

We recently tweeted our 20th Swift Quiz, so we thought it would be a good time to take stock and review what we’ve learned so far. You can find the full collection here.

The quiz is compiled every week by Ahmet Yalcinkaya, our “language design fuzzer”, who does a great job exploring the edges of Swift. We look for puzzles that strike the right balance between obscurity and realism: they should illustrate a problem that might happen in everyday use, but in a way that makes you think. And of course, they must fit in a tweet!

Twitter’s compactness of expression can force an extra layer of obscurity, but this isn’t our aim. It also doesn’t prioritize context, so it might be worth stating here: the puzzles are meant as quizzes, not whiteboard interviews questions. They’re a stimulus to thought, not a test of competency.

That said, let’s take a look at your answers!

The results

2019 07 30 quiz review graph

This graph shows the percentage of correct votes for each poll. For the most part, the success cases fluctuate around fifty percent. Which is to say that, in general, just over half of you are disposed to choose the right answer — the British might call this an “overwhelming majority”.

There are some outliers, and it might be instructive to look at them more closely.

The outliers

Three quizzes left our voters stumped, despite a high turnout:

Quiz 3: Protocol Dispatch

protocol Drawing {
    func render()
}

extension Drawing {
    func circle() { print("protocol") }
    func render() { circle() }
}

class SVG: Drawing {
    func circle() { print("class") }
}

SVG().render()

// What's the output?

/// Answers: protocol / class

This quiz had a 43 percent success rate, with the highest vote count.

When building an app, every once in a while you might wonder why a method doesn’t get called, even though you added a specific override (like we do above, with circle). Unless the method is part of the protocol, it won’t get called, because only those methods will be dynamically dispatched. Note that you can add the method to the protocol and provide a default implementation in an extension. This still allows you to override the method in a conforming type.

Quiz 20: Dictionary with Nil Values

var dictWithNils: [String: Int?] = [
    "one": 1,
    "two": 2,
    "none": nil
]

dictWithNils["two"] = nil
dictWithNils["none"] = nil
let result = dictWithNils.count

// What’s the result?

/// Answers: 1 / 3 / Compiler Error

This quiz had a 30 percent success rate, with the third highest vote count.

We usually avoid dictionaries with optional values, because the behavior is confusing. The subscript used above takes an optional value, and nil means: delete this key. However, to a casual reader, this might not be obvious.

Quiz 21: Pattern Matching with Variable Values

var x = 42

let result: Result<Int, Error> = .success(x)
x = 21

switch result {
case .success(x):
    print(x)
case .failure(_):
    print("Error")
default:
    print("Default")
}

// What’s the output?

/// Answers: 42 / 21 / Error / Default

This quiz had a 15 percent success rate, with the fourth highest vote count.

It’s quite tricky, because it’s not at all obvious what happens. The pattern match on .success(x) does not bind to the variable x, but uses the value of x to match on. In other words, it matches when result is equal to .success(21). In general, it’s wise to avoid variable shadowing: don’t reuse a variable name from an outer scope. Yet, there are times when matching like above is exactly what you want. To be more clear about the intent, you could rewrite it as following: case .success(let y) where y == x:. This is more verbose, but easier to spot when reading the code.

It’s the taking part that counts!

Twenty two questions later, one thing becomes very clear: choosing the right answer is a lot less important than thinking through the solutions.

Perhaps the best way to learn from quizzes like this is to reflect and make your best guess, then afterwards, try the puzzle out in a playground. We tweet the solution a day after, and it can be instructive to compare our explanation with the range of analyses offered in the replies. Learning how others approach a problem can illuminate unexpected difficulties, and tell you a lot about how language users expect a language to behave.

We don’t always get the answers right either; for us, that’s what makes a question interesting. Indeed, sometimes it’s a “we didn’t know this!” moment that gives rise to a question in the first place.

If you’d like to join in with future quizzes, follow our Twitter. We try to publish every Wednesday around 4 PM Berlin.

Enjoy! 😊

Swift Tip: Sharing Code between Backend and Frontend

Over at Swift Talk, we’ve been busy building a new Swift Talk client app in SwiftUI. Because our backend is already written in Swift, we can now share a bunch of code between the client and server.

As a first step, we extracted our Tiny Networking library into its own Github project. It’s a Swift package, and with Xcode 11, it’s now even easier to use across projects.

Then, we went for the low hanging fruit: sharing view helper functions. This gives us a consistent output between our backend and client apps. For example, we share custom formatters to display an episode’s duration, and custom date formatters.

Finally, one of the most interesting cases is sharing the JSON models we use for the API. The backend provides a JSON list of episodes and collections, and we generate that using the Codable infrastructure. To keep things flexible, we have the following setup:

In our backend, our model struct for an episode is called Episode. Rather than directly rendering this to JSON, we created a struct EpisodeView. An EpisodeView is so called because we use it as a (JSON) view on the original data. In our backend, we can then populate it from an episode:

extension EpisodeView {
    init(_ e: Episode) {
        self = EpisodeView(
            id: e.id.rawValue,
            number: e.number,
            title: e.title,
            synopsis: e.synopsis,
            url: Route.episode(e.id, .view(playPosition: nil)).url,
            small_poster_url: e.posterURL(width: 590, height: 270),
            // ...
        )
    }
}

Separating the two types allows us to change the original Episode struct, while maintaining the same API. Likewise, our Episode struct shouldn’t know about URLs for the episode itself, those are added when we create the view.

In our shared code, we also expose a JSON encoder and decoder. By putting those into the shared library, it’s much easier to keep them in sync. For example, we need to ensure that we encode and decode our dates using the same strategy. In our server, we use the shared encoder, and in our client, we use the shared decoder.

In the future, it would be nice to also share a typed specification of routes between the server and the client, in a manner similar to Haskell’s servant library.

To follow our experiments with SwiftUI, subscribe to Swift Talk.