Реализация списка тегов в SwiftUI - PullRequest
3 голосов
/ 01 декабря 2019

Я пытаюсь реализовать список тегов в SwiftUI, но я не уверен, как заставить его обернуть теги в дополнительные строки, если список переполняется по горизонтали. Я начал с массива строк, называемого тегами, и в SwiftUI я перебираю этот массив и создаю кнопки следующим образом:

HStack{
    ForEach(tags, id: \.self){tag in
        Button(action: {}) {
            HStack {
                Text(tag)
                Image(systemName: "xmark.circle")
            }
        }
        .padding()
        .foregroundColor(.white)
        .background(Color.orange)
        .cornerRadius(.infinity)
        .lineLimit(1)
    }
}

Если массив тегов маленький, он выглядит следующим образом: enter image description here

Однако, если массив имеет больше значений, он делает это:

enter image description here

Поведение, которое я ищу, является последнимтег (желтый) для переноса на вторую строку. Я понимаю, что это в HStack, я надеялся, что смогу добавить вызов lineLimit со значением больше единицы, но это, похоже, не меняет поведение. Если я изменяю внешний HStack на VStack, он помещает каждую кнопку в отдельную строку, поэтому поведение, которое я пытаюсь создать, все равно не совсем. Любое руководство будет с благодарностью.

Ответы [ 2 ]

1 голос
/ 14 декабря 2019

Хорошо, это мой первый ответ на этом сайте, так что потерпите меня, если я совершу какое-то поддельное переполнение стека.

Я опубликую свое решение, которое работает для модели, в которой теги либо присутствуют в наборе selectedTags, либо нет, а все доступные теги присутствуют в наборе allTags. В моем решении они устанавливаются как привязки, поэтому их можно вводить из любого места в приложении. Кроме того, мое решение имеет теги, упорядоченные в алфавитном порядке, потому что это было проще всего. Если вы хотите, чтобы они были заказаны другим способом, вам, вероятно, потребуется использовать другую модель, чем два независимых набора.

Это определенно не будет работать для всех пользователей, но так как я не мог найти свой собственный ответ на этот вопрос, и ваш вопрос был единственным местом, где я мог найти упоминание об этой идее, я решил, что будупопытаться создать что-то, что будет работать для меня, и поделиться им с вами. Надеюсь, это поможет:

struct TagList: View {

    @Binding var allTags: Set<String>
    @Binding var selectedTags: Set<String>

    private var orderedTags: [String] { allTags.sorted() }

    private func rowCounts(_ geometry: GeometryProxy) -> [Int] { TagList.rowCounts(tags: orderedTags, padding: 26, parentWidth: geometry.size.width) }

    private func tag(rowCounts: [Int], rowIndex: Int, itemIndex: Int) -> String {
        let sumOfPreviousRows = rowCounts.enumerated().reduce(0) { total, next in
            if next.offset < rowIndex {
                return total + next.element
            } else {
                return total
            }
        }
        let orderedTagsIndex = sumOfPreviousRows + itemIndex
        guard orderedTags.count > orderedTagsIndex else { return "[Unknown]" }
        return orderedTags[orderedTagsIndex]
    }

    var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .leading) {
                ForEach(0 ..< self.rowCounts(geometry).count, id: \.self) { rowIndex in
                    HStack {
                        ForEach(0 ..< self.rowCounts(geometry)[rowIndex], id: \.self) { itemIndex in
                            TagButton(title: self.tag(rowCounts: self.rowCounts(geometry), rowIndex: rowIndex, itemIndex: itemIndex), selectedTags: self.$selectedTags)
                        }
                        Spacer()
                    }.padding(.vertical, 4)
                }
                Spacer()
            }
        }
    }
}

struct TagList_Previews: PreviewProvider {
    static var previews: some View {
        TagList(allTags: .constant(["one", "two", "three"]), selectedTags: .constant(["two"]))
    }
}

extension String {

    func widthOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }

}

extension TagList {
    static func rowCounts(tags: [String], padding: CGFloat, parentWidth: CGFloat) -> [Int] {
        let tagWidths = tags.map{$0.widthOfString(usingFont: UIFont.preferredFont(forTextStyle: .headline))}

        var currentLineTotal: CGFloat = 0
        var currentRowCount: Int = 0
        var result: [Int] = []

        for tagWidth in tagWidths {
            let effectiveWidth = tagWidth + (2 * padding)
            if currentLineTotal + effectiveWidth <= parentWidth {
                currentLineTotal += effectiveWidth
                currentRowCount += 1
                guard result.count != 0 else { result.append(1); continue }
                result[result.count - 1] = currentRowCount
            } else {
                currentLineTotal = effectiveWidth
                currentRowCount = 1
                result.append(1)
            }
        }

        return result
    }
}

struct TagButton: View {

    let title: String
    @Binding var selectedTags: Set<String>

    private let vPad: CGFloat = 13
    private let hPad: CGFloat = 22
    private let radius: CGFloat = 24

    var body: some View {
        Button(action: {
            if self.selectedTags.contains(self.title) {
                self.selectedTags.remove(self.title)
            } else {
                self.selectedTags.insert(self.title)
            }
        }) {
            if self.selectedTags.contains(self.title) {
                HStack {
                    Text(title)
                        .font(.headline)
                }
                .padding(.vertical, vPad)
                .padding(.horizontal, hPad)
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(radius)
                .overlay(
                    RoundedRectangle(cornerRadius: radius)
                        .stroke(Color(UIColor.systemBackground), lineWidth: 1)
                )

            } else {
                HStack {
                    Text(title)
                        .font(.headline)
                        .fontWeight(.light)
                }
                .padding(.vertical, vPad)
                .padding(.horizontal, hPad)
                .foregroundColor(.gray)
                .overlay(
                    RoundedRectangle(cornerRadius: radius)
                        .stroke(Color.gray, lineWidth: 1)
                )
            }
        }
    }
}
0 голосов
/ 02 декабря 2019

Вы можете попробовать использовать ScrollView с горизонтальной осью.

ScrollView(.horizontal, showsIndicators: true) {
        HStack{
            ForEach(tags, id: \.self){tag in
                Button(action: {}) {
                    HStack {
                        Text(tag)
                        Image(systemName: "xmark.circle")
                    }
                }
                .padding()
                .foregroundColor(.white)
                .background(Color.orange)
                .cornerRadius(.infinity)
                .lineLimit(1)
            }
        }
    }

Надеюсь, это вам поможет.

Вы можете попробовать использовать showsIndicators: false также, если вы не хотите показывать прокруткуиндикатор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...