Introducing the “Partial” Option: A Dash in a Box

Integrating Three-State Checkboxes in a SwiftUI MacOS App

In this blog post, I’m going to walk you through how to implement a common user interface component in MacOS apps using SwiftUI—the three-state checkbox. This element is often utilized in scenarios where users are presented with a group of options under various categories. For example, consider a software installation process that allows users to select components to install. A three-state checkbox can represent three different states of selection: all items selected, some items selected, and none selected. Despite its frequent appearance in MacOS interfaces, SwiftUI does not provide a built-in three-state checkbox, prompting the need for a custom solution.

Understanding the Scenario

When dealing with grouped options in a UI, it can greatly enhance user experience to summarize the selection status at the group level with a three-state checkbox. Specifically:

  • A checked box ([✓]) indicates that all items in the category are selected.
  • A dash/indeterminate symbol ([-]) shows that some but not all items in the category are selected.
  • An empty box ([ ]) means that none of the items in the category are selected.

The challenge here is that SwiftUI, as of my last update in early 2023, doesn’t include a direct way to implement this. Therefore, I will demonstrate how we can create this functionality ourselves.

Building a Custom Three-State Checkbox in SwiftUI

Our task involves using SwiftUI views and some logic to replicate the behavior of a traditional three-state checkbox. We’ll utilize SF Symbols for the checkbox icons: checkmark.square, minus.square, and square.

Step 1: Define Model and Data Structure

First, let’s setup a basic model for our categories and options.

struct Category: Identifiable {
    let id: Int
    let name: String
    var items: [Option]
}

struct Option: Identifiable {
    let id: Int
    let name: String
    var isSelected: Bool
}

Step 2: Utility Functions

We need a function to determine the current state of the checkbox based on the selection of options in a category.

extension Category {
    var selectionState: SelectionState {
        let totalCount = items.count
        let selectedCount = items.filter { $0.isSelected }.count

        switch selectedCount {
        case 0:
            return .none
        case totalCount:
            return .all
        default:
            return .partial
        }
    }
}

enum SelectionState {
    case none, partial, all
}

Step 3: Building the SwiftUI View

Let’s design a view that uses these models and dynamically updates based on the selection.

struct CategoryView: View {
    @Binding var category: Category
    
    var body: some View {
        VStack {
            HStack {
                CheckboxView(state: $category.selectionState)
                Text(category.name).fontWeight(.bold)
            }
            ForEach($category.items) { $item in
                HStack {
                    Checkbox(isChecked: $item.isSelected)
                    Text(item.name)
                }
            }
        }
    }
}

Here, CheckboxView is a custom view that takes a binding to SelectionState and updates its appearance accordingly.

Step 4: The CheckboxView Component

This component will display different symbols based on the state.

struct CheckboxView: View {
    @Binding var state: SelectionState
    
    var body: some View {
        Button(action: toggle) {
            Image(systemName: iconName)
        }
        .buttonStyle(BorderlessButtonStyle())
    }
    
    private var iconName: String {
        switch state {
        case .all:
            return "checkmark.square"
        case .partial:
            return "minus.square"
        case .none:
            return "square"
        }
    }
    
    private func toggle() {
        switch state {
        case .all, .partial:
            state = .none
        case .none:
            state = .all
        }
        
        // Implement logic to update the actual options in the category
    }
}

Wrapping It Up

By implementing these steps, you create a three-state checkbox system in a SwiftUI MacOS app tailored towards better user interaction and state management. Remember that this is a foundational guideline; you might need to adjust components according to your specific app logic and SwiftUI updates.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *