The weird cases of text ellipsis

While working on a SwiftUI app I had to design views that hold more than a few lines of text inside a VStack. As I had almost no issues with layouting so far I expected the Text to grow as expected, but in some cases (but not others) the text was cut off at the end:

The view used to render this part of the UI is written like this:

struct MissionView: View {
  var mission: Mission
  var body: some View {
    VStack(alignment: .leading) {
      Text(mission.name)				
        .font(.system(.headline, design: .rounded))
        .foregroundColor(.label)
        .padding(.bottom, 8)
	.lineLimit(2)
	
      Text(mission.description)
        .font(.subheadline)
        .foregroundColor(
          .placeholderText)
    }
}

At first glance it seems the Text element has an implicit lineLimit. So I added a .lineLimit(.max). But that didn’t change the outcome.

After some digging I found out about .fixedSize(horizontal: Bool, vertical: Bool). This allows us to fix the size of the Text element in only one direction – vertical in our case.
After adding .fixedSize(horizontal: false, vertical: true) the text is not cut off and behaves as intended.

Image showing fixed version with now cut off

Bringing the simplicity of NavigationLink to popovers

In this article we explore the possibility to extend SwiftUIs APIs to make the common use case of presenting a popover easier and in line of NavigationLink.

I love the simple design of the NavigationLink view in SwiftUI. Only specifying the destination view and a label gets you fully functional navigation in the hosting NavigationView.

NavigationLink(destination: Text("This is the subview")) {
    Text("Go to subview")
}

But pushing a new view to the navigation stack is not the only interesting way to present new views. Modal sheets or popovers also have their use cases! Unfortunately the API for sheets and popovers is a little less straightforward to use, as it involves introducing a state variable and updating it from e.g. a button:

struct ContentView: View {
    @State var isShowingPopover = false
    
    var body: some View {
        Button {
            isShowingPopover = true
        } label: {
            Text("Go to subview")
        }
        .popover(isPresented: $isShowingPopover) {
            Text("Go to subview")
        }
    }
}

While this is still relatively concise and exposes access to dismissing the popover, it still requires introducing state even though this might not be needed.

So I asked myself: Is it possible to achieve the same API as NavigationLink but for popovers?
It turns out the answer is yes, and the solution is remarkably clean and easy to implement.

We start by implementing our own view that leans on the API of NavigationLink:

struct PopoverLink<Label, Destination> : View where Label : View, Destination : View {
    /// Creates an instance that presents `destination`.
    public init(destination: Destination, @ViewBuilder label: () -> Label)

    /// Creates an instance that presents `destination` when active.
    public init(destination: Destination, isActive: Binding<Bool>, @ViewBuilder label: () -> Label)
}

This interface is adapted from NavigationLink (but leaves out one initializer that I never used). Now to implementing this interface.
First we have to introduce variables for destination, label and isActive and assign these in the initializers. We also have to introduce an internal state variable that is used if the initializer without specific binding is used.

struct PopoverLink<Label, Destination> : View where Label : View, Destination : View {
    private let destination: Destination
    private let label: Label
    private var isActive: Binding<Bool>?
    @State private var internalIsActive = false

	/// Creates an instance that presents `destination`.
	public init(destination: Destination, @ViewBuilder label: () -> Label) {
        self.destination = destination
        self.label = label()
    }

    /// Creates an instance that presents `destination` when active.
    public init(destination: Destination, isActive: Binding<Bool>, @ViewBuilder label: () -> Label) {
        self.destination = destination
        self.label = label()
        self.isActive = isActive
    }
}

Last but not least we have to implement the body of our view. Here we can lean on our previous implementation of a popover and essentially do the same thing in the body of our view:

var body: some View {
    Button {
        (isActive ?? _internalIsActive.projectedValue).wrappedValue = true
    } label: {
        label
    }.popover(isPresented: (isActive ?? _internalIsActive.projectedValue)) {
        destination
    }
}

Note the use of optional chaining to pick the right binding depending on which initializer was used!

Finally we can fix the popover animation bug and present a sheet instead on compact layouts:

@Environment(\.horizontalSizeClass) var horizontalSizeClass

private func popoverButton() -> some View {
    Button {
        (isActive ?? _internalIsActive.projectedValue).wrappedValue = true
    } label: {
        label
    }
}

public var body: some View {
    if horizontalSizeClass == .compact {
        popoverButton().sheet(isPresented: (isActive ?? _internalIsActive.projectedValue)) {
            destination
        }
    } else {
        popoverButton().popover(isPresented: (isActive ?? _internalIsActive.projectedValue)) {
            destination
        }
    }
}

With just a little code we introduced our own PopoverLink implementation that allows us easily present modal popovers in SwiftUI using easy and familiar syntax from NavigationLink:

PopoverLink(destination: Text("This is the subview")) {
    Text("Go to subview")
}

This article shows how easy it is to introduce own views to SwiftUI to simplify common UI use cases and to improve on the existing SwiftUI library.

Below you can find the full source code for PopoverLink.

Fix internet sharing on OS X Mavericks

It seems that OS X Mavericks has issues resuming internet sharing over WiFi when waking up from sleep. This is especially annoying, as only restarting seems to “fix” this issue. After searching on Google for a while, I stumbled upon this. The forum post suggests to kill bootps to free the port opened by internet sharing, even though it is not running anymore. For me this seems like a reliable solution, even though I wish Apple would fix it.

Running the following script will allow restarting internet sharing:

sudo lsof -i udp:67 | grep bootpd |awk ‘{ print $2 }’| while read pid; do echo killing $pid; sudo kill -9 $pid&&echo “successfull…”; done

SpaceFlightNow Launch Schedule Calendar

I love the website Spaceflight Now. They provide detailed coverage on most rocket launches and have a nice worldwide rocket launch schedule. Unfortunately you have to look at it to not miss any launches. I would have loved to have the ability to import this schedule into my calendar, but searching the web did not find anything useful.

So I wrote my own parser for the Spaceflight Now launch schedule and provide it as a ics file.

http://manuel.weiel.eu/SpaceFlightNow.ics

If you want to keep getting updates just subscribe to the webcal.

Subscribe here

In addition to providing a up to date launch schedule, this calendar also features recent launches.

 

Update: Due to recent changes, the current implementation does not work. In the meantime I recommend the webcal feed from Launch Library. Subscribe to it using this link.

 

As for the technical side of things: due to the way the Spaceflight Now website is coded and the not always consistent format of the launch events, some may be off either an hour or a whole day. So please be sure to check back on Spaceflight Now to have confirmation on the actual launch time.