SwiftUI View Preview Selecting Issue

If you have ever tried to select an element in the preview (Canvas) of your SwiftUI view, you may have noticed that sometimes it doesn't get selected. Instead, the whole simulator window gets selected, and the Attributes Inspector reads “Multiple Selection”. This can be a frustrating issue when you are trying to debug or modify your code.

A preview of a SwiftUI view when the user tried to select the internal Text view but it didn’t work

A preview of a SwiftUI view when the user tried to select the internal Text view but it didn’t work

Reason One

In some cases, the culprit may be creating a new return variable in the reduce (map?) closure. For example, the following code would cause the issue:

emotions.reduce(Text("")) { result, emotion in
    var result = result
    if emotion != emotions.first {
        result = result + Text(", ")
    }
    return result + Text(emotion)
}

To avoid this issue, we can modify the code to return just a modification of the initial result, without creating a new variable. The code for our sample case that doesn’t break the preview may look like this:

emotions.reduce(Text("")) { result, emotion in
    result
    + Text(emotion == emotions.first ? "" : ", ")
    + Text(emotion)
}

Another workaround is to move the code as-is into a separate function:

struct TestView: View {
    let emotions: [String]

    var body: some View {
        emotions.reduce(Text("")) { result, emotion in
            self.buildText(result: result, emotion: emotion, isFirst: emotion == emotions.first)
        }
    }

    private func buildText(result: Text, emotion: String, isFirst: Bool) -> Text {
        var result = result
        if !isFirst {
            result = result + Text(", ")
        }
        return result + Text(emotion)
    }
}

The full code of the troubling case:

import SwiftUI

struct TestView: View {
    let emotions: [String]

    var body: some View {
        emotions.reduce(Text("")) { result, emotion in
            var result = result
            if emotion != emotions.first {
                result = result + Text(", ")
            }
            return result + Text(emotion)
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView(emotions: ["antsy", "anxious"])
    }
}
Why so complex reduce with Texts?
This code reduces Texts, not Strings, to get advantage of the LocalizedStringKey type. All of the strings that goes to its constructor are automatically exported with the Product → Export Localizations menu. When dealing with the LocalizedString API, we need to manually add these string to the localization files. In the real-world code the emotion is not a string but rather an enum that returns a LocalizedStringKey in the localized computed property. We pass this localized property to the Text constructor so that it generates the correct localized string.

Reason Two

Another reason why this issue may occur is when using a NavigationView in the view. You can select the navigation view, but not any subview. To solve this, you can move all the subviews of the navigation view into a separate view.

For example, the following code would cause the issue:

import SwiftUI

struct TestView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("One")
                Text("Two")
                Text("Three")
            }
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

The only way to solve this issue so far is to move all the subviews of the navigation view into a separate view.