Blog

Latest Post

SwiftUI: Running a Mac App Without an Xcode Project

In 2018, we wrote a post about using AppKit from the command line. We can use exactly the same technique to run a SwiftUI app directly from a Swift file.

When you’re writing a quick tool, perhaps to visualize something, or to get user feedback, an Xcode project can feel like overkill. It’s often very useful to have a single file that you just run with swift myfile.swift from Terminal. Likewise, when you’re inside a Swift Package Manager project, you might not want to create an additional Xcode project when you’re just testing things out.

What if you could just do the following?

NSApplication.shared.run {
    VStack {
        Text("Hello, World")
            .padding()
            .background(Capsule().fill(Color.blue))
            .padding()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
}

Fortunately, you can, by pasting in the boilerplate code from this gist. Simply run swift boilerplate.swift in Terminal and you’ll have a macOS app up and running.

If you’re building a complex app, you’ll almost certainly want to move to a “real” application, but for one-off tools and prototyping we’ve found this shortcut very useful.

Screenshot

In Swift Talk Episode #145, which is free to watch, we use the same technique to create an AppKit app using Swift Package Manager.

Previous Posts

SwiftUI: Showing an Alert with a Text Field

In Swift Talk Episode 198, we made a wrapper around UIKit alerts.

SwiftUI provides a built-in API just for this: the alert method on View allows us to present an alert to the user, and under the hood it almost certainly uses a UIAlertController.

For our purposes, we wanted an alert controller with a text field. UIAlertController supports this, but so far, SwiftUI’s built-in Alert struct does not. To route around this limitation, we’ve found a hack that seems to work — for us at least! (we haven’t tested this in a project other than our own).

We’ll start by defining an API similar to SwiftUI’s builtin alert API. It will have the usual properties, and an action parameter that gets called whenever the user presses either the OK or Cancel button.

public struct TextAlert {
    public var title: String
    public var placeholder: String = ""
    public var accept: String = "OK"
    public var cancel: String = "Cancel"
    public var action: (String?) -> ()
}

extension View {
    public func alert(isPresented: Binding<Bool>, _ alert: TextAlert) -> some View {
        // ...
    }
}

Now for the hacky part. When we call alert on a view, we wrap that view inside a custom UIViewControllerRepresentable. Inside, we create a UIHostingController for the view, so that we can call .present on it.

struct AlertWrapper<Content: View>: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    let alert: TextAlert
    let content: Content

    func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIHostingController<Content> {
        UIHostingController(rootView: content)
    }

    // ...
}

To store the current alert controller we need to create a coordinator:

struct AlertWrapper<Content: View>: UIViewControllerRepresentable {
    // ...

    final class Coordinator {
        var alertController: UIAlertController?
        init(_ controller: UIAlertController? = nil) {
            self.alertController = controller
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
}

Finally, we need to show and hide the alert whenever our binding changes. SwiftUI will automatically observe the binding and call updateUIViewController(_:context:) each time that happens. Inside, there are two code paths: when the binding’s value is true but we’re not yet presenting the alert controller, we need to present it; when the binding’s value is false but we are presenting the alert controller, we need to dismiss it.

func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<AlertWrapper>) {
    uiViewController.rootView = content
    if isPresented && uiViewController.presentedViewController == nil {
        var alert = self.alert
        alert.action = {
            self.isPresented = false
            self.alert.action($0)
        }
        context.coordinator.alertController = UIAlertController(alert: alert)
        uiViewController.present(context.coordinator.alertController!, animated: true)
    }
    if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController {
        uiViewController.dismiss(animated: true)
    }
}

Note that we’re setting the hosting controller’s root view in the first line of the updateViewController method. This method will be called if our isPresented binding for the alert changes, but also when something changes that affects the content of the alert wrapper. If we omit this first line, SwiftUI will no longer update the view inside the hosting controller.

Now we can use our text alert:

struct ContentView: View {
    @State var showsAlert = false
    var body: some View {
        VStack {
            Text("Hello, World!")
            Button("alert") {
                self.showsAlert = true               
            }
        }
        .alert(isPresented: $showsAlert, TextAlert(title: "Title", action: {
            print("Callback \($0 ?? "<cancel>")")
        }))
    }
}

We hope that SwiftUI will catch up with UIAlertController, making these kinds of hacks unnecessary. In the meantime, if anyone knows of a simpler way to do this, do let us know and we’ll update this post!

Here’s the full code.

Our SwiftUI Collection has 33 episodes and growing, with 8 public episodes. In our latest series, we port App Architecture‘s sample app from MVC to SwiftUI — the first episode is free to watch.

Our New Book: Thinking in SwiftUI

We’re very happy to announce that our latest book, Thinking in SwiftUI, is complete and available today — our thanks to all the early access readers!

Thinking in swiftui hero original

The book

When SwiftUI came out, we immediately started exploring the framework. It quickly became clear that SwiftUI was a radically different pattern from any we had encountered before. When you’re coming from UIKit, you really do have to rethink how to structure your applications.

While Apple provided tutorials and WWDC talks, many things remained undocumented or unclear. Rather than attempting to document fast-changing APIs, our new book focuses on clarity of thought. We take you through the fundamental concepts behind SwiftUI, and emphasise the differences, for experienced UIKit programmers who already know Swift.

In short, you will learn to think in SwiftUI.

We start by explaining the view-update cycle: SwiftUI is a declarative system which doesn’t let you update views in place; instead, you change the state, and let the framework re-render the necessary views. We look into the environment and preference system, which is an essential part of SwiftUI. We also look at how the layout system works, and how to build advanced layouts using geometry readers, overlays, alignment guides and preferences. The final chapter is about animations, both the built-in animations, and completely custom animations.

Our book is filled with examples, but we believe that the best way to learn is to build things yourself. We are including exercises at the end of each chapter so that you can practice the techniques on real problems. Of course, at the end of the book, we include all solutions.

Here’s what one of our reviewers had to say about them:

The exercises are a lot of fun! I love when a book includes the solutions to the exercise. After looking at your solution, it was immediately clear what I had forgotten.

Ole Begemann

With video

The book also comes with an optional video package. In the videos, we use pair programming to implement the solutions to the exercises. We also build an angle dial control (you may recognise this from the built-in angle adjustment in Photos for Mac), and a view to display and animate trees — we use the same code to generate the trees in our book:

This edition won’t be covering (many) platform-specific views, such as scroll views and navigation views. At the time of writing, these still require significant workarounds to perform well. However, once you have worked through the chapters, you’ll be very prepared to start writing a SwiftUI app today.

We’d like to thank all the people that helped us during the writing: Ole, Javier, Matt, Natalye, Morgan, and everyone who sent feedback during the pre-release phase.

Enjoy the book! 😊

Best from Berlin, Florian and Chris.

SwiftUI Line Graph Animation

In the sixth of our Thinking in SwiftUI challenges, we asked you to animate a line graph.

As a first step, we’ll create a shape for the line graph. This shape takes an array of data points (normalized to 0...1). Any points that are outside this range are drawn outside of the proposed rectangle rect. For clarity, we pulled out the function that computes a CGPoint for a given data point.

struct LineGraph: Shape {
    var dataPoints: [CGFloat]

    func path(in rect: CGRect) -> Path {
        func point(at ix: Int) -> CGPoint {
            let point = dataPoints[ix]
            let x = rect.width * CGFloat(ix) / CGFloat(dataPoints.count - 1)
            let y = (1-point) * rect.height
            return CGPoint(x: x, y: y)
        }

        return Path { p in
            guard dataPoints.count > 1 else { return }
            let start = dataPoints[0]
            p.move(to: CGPoint(x: 0, y: (1-start) * rect.height))
            for idx in dataPoints.indices {
                p.addLine(to: point(at: idx))
            }
        }
    }
}

SwiftUI has built-in support for trimming shapes using the .trim modifier: this trims the path of the shape to the specified start and end points. We keep a constant starting point, 0, but vary the end point during the animation (from 0 to 1). Since the trim modifier has built-in support for animating the start and end points, we can animate the graph simply by animating between a to value of 0 and 1. Because the graph is defined as a Shape, we can use the built in modifiers to style it: stroke it with a red color, fix it to an aspect ratio, add a border, and apply some padding:

struct ContentView: View {
    @State var on = true

    var body: some View {
        VStack {
            LineGraph(dataPoints: sampleData)
                .trim(to: on ? 1 : 0)
                .stroke(Color.red, lineWidth: 2)
                .aspectRatio(16/9, contentMode: .fit)
                .border(Color.gray, width: 1)
                .padding()
            Button("Animate") {
                withAnimation(.easeInOut(duration: 2)) {
                    self.on.toggle()
                }
            }
        }
    }
}

That’s all we need for our line graph — or any shape, really — to animate:

Our new book, Thinking in SwiftUI, discusses the animation system in more detail in chapter six. You can buy it here.

SwiftUI Path Animations

In the fifth of our Thinking in SwiftUI challenges, we asked you to animate a path.

We started the challenge with code that draws a line. The starting point changes based on a boolean property, but despite the withAnimation, the path itself doesn’t animate:

let p1 = CGPoint(x: 50, y: 50)
let p2 = CGPoint(x: 100, y: 25)
let p3 = CGPoint(x: 100, y: 100)

struct ContentView: View {
    @State var toggle = true
    var body: some View {
        VStack {
            Button("Toggle") {
                withAnimation { self.toggle.toggle() }
            }
            Path { p in
                p.move(to: toggle ? p1 : p2)
                p.addLine(to: p3)
            }.stroke(lineWidth: 2)          
        }
    }
}

To animate a path, we need to tell SwiftUI which properties are animatable. One way to do this is by wrapping the path in a custom Shape. We create a Line shape with properties for both the line’s start and end points:

struct Line: Shape {
    var start, end: CGPoint

    func path(in rect: CGRect) -> Path {
        Path { p in
            p.move(to: start)
            p.addLine(to: end)
        }
    }
}

To animate the start and end properties, we need to expose them via animatableData, and the type of animatableData needs to conform to the VectorArithmetic protocol. Unfortunately, CGPoint does not conform to VectorArithmetic, and it’s bad practice to add this conformance ourselves: you’re not supposed to conform types you don’t own to protocols you don’t own, even though in this case, there wouldn’t be much harm in it.

Luckily, CGPoint does conform to Animatable, so we can use its animatableData. We can now make both points of the Line animatable by creating an animatable pair out of the two CGPoint.Animatable values:

extension Line {
    var animatableData: AnimatablePair<CGPoint.AnimatableData, CGPoint.AnimatableData> {
        get { AnimatablePair(start.animatableData, end.animatableData) }
        set { (start.animatableData, end.animatableData) = (newValue.first, newValue.second) }
    }
}

Our new book, Thinking in SwiftUI, discusses the animation system in more detail in chapter six. You can join the early access here.