Skip to content

Crash on organizeDeclarations #2446

@BastiOSDev

Description

@BastiOSDev

Hey Nick,

thanks for your work on this project! I implemented SwiftFormat to an old project of mine and registered an crash on the organizeDeclarations rule with the following code.

Do you mind to take a look on this crash?

struct MultiSelectMenu<Choice: Identifiable & Hashable>: View {

    let title: String
    let systemImage: String

    let all: [Choice]
    @Binding var selected: [Choice]

    let titleProvider: (Choice) -> String
    let colorProvider: (Choice) -> Color

    init(
        title: String,
        systemImage: String = "person.circle.fill",
        all: [Choice],
        selected: Binding<[Choice]>,
        titleProvider: @escaping (Choice) -> String,
        colorProvider: @escaping (Choice) -> Color
    ) {
        self.title = title
        self.systemImage = systemImage
        self.all = all
        self._selected = selected
        self.titleProvider = titleProvider
        self.colorProvider = colorProvider
    }

    var body: some View {
        Menu {
            ForEach(all) { item in
                let isOn = selected.contains(where: { $0.id == item.id })
                Button {
                    toggle(item)
                } label: {
                    HStack {
                        Text(titleProvider(item))
                        Spacer()
                        if isOn {
                            Image(systemName: "checkmark")
                        }
                    }
                }
            }

            if !selected.isEmpty {
                Divider()
                Button("Clear Selection", role: .destructive) {
                    selected.removeAll()
                }
            }
        } label: {
            HStack(spacing: 8) {
                Image(systemName: systemImage)
                    .imageScale(.medium)

                Text(title)
                Spacer()
                if !selected.isEmpty {
                    SelectedChips(
                        selected: selected,
                        titleProvider: titleProvider,
                        colorProvider: colorProvider
                    )
                    .padding(.trailing, 2)
                }
            }
            .foregroundStyle(.primary)
            .tint(.primary)
        }
        .frame(minHeight: 22)
    }

    private func toggle(_ item: Choice) {
        if let idx = selected.firstIndex(where: { $0.id == item.id }) {
            selected.remove(at: idx)
        } else {
            selected.append(item)
        }
    }

    // MARK: - Chips View

    private struct SelectedChips: View {
        let selected: [Choice]
        let titleProvider: (Choice) -> String
        let colorProvider: (Choice) -> Color

        var body: some View {
            let maxChips = 3
            let head = Array(selected.prefix(maxChips))
            let restCount = max(0, selected.count - maxChips)

            HStack(spacing: 6) {
                ForEach(head, id: \.id) { item in
                    let text = initials(for: titleProvider(item))
                    let color = colorProvider(item)
                    UserView(
                        title: text,
                        backgroundColor: color,
                        style: .iconOnly,
                        frame: 22,
                        iconFont: .caption2.bold()
                    )
                }

                if restCount > 0 {
                    Text("+ \(restCount)")
                        .font(.caption).bold()
                        .padding(.horizontal, 6).padding(.vertical, 2)
                        .background(
                            RoundedRectangle(cornerRadius: 6)
                                .stroke(.secondary, lineWidth: 1)
                        )
                }
            }
            .accessibilityLabel("\(selected.count) selected")
        }

        private func initials(for name: String) -> String {
            let parts = name.split(separator: " ")
            let first = parts.first?.first.map(String.init) ?? ""
            let last = parts.dropFirst().first?.first.map(String.init) ?? ""
            return (first + last).uppercased()
        }
    }
}

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions