Skip to content

Instantly share code, notes, and snippets.

@eldaroid
Last active October 13, 2024 04:03
Show Gist options
  • Save eldaroid/c8b3793e1c627d5efcbdf95f1d225150 to your computer and use it in GitHub Desktop.
Save eldaroid/c8b3793e1c627d5efcbdf95f1d225150 to your computer and use it in GitHub Desktop.
RU: Реализация макета Selectable Aligned Tags с поддержкой переносов строк и выравнивания. EN: Implementation Selectable Aligned Tags layout that supports line breaks and alignment
public struct TagsModel: Identifiable, Hashable, Equatable {
public let id: String
public var title: String
public var tooltip: String?
public var isSelected: Bool
public init(
id: String = UUID().uuidString,
title: String,
isSelected: Bool = false,
tooltip: String? = nil
) {
self.id = id
self.title = title
self.isSelected = isSelected
self.tooltip = tooltip
}
}
public struct WrappableTagsView<Content: View>: View {
private let tags: [TagsModel]
private let color: Color
private let item: (TagsModel, Color) -> Content
@State private var totalHeight = CGFloat.zero
public init(
tags: [TagsModel],
color: Color,
item: @escaping (TagsModel, Color) -> Content
) {
self.tags = tags
self.color = color
self.item = item
}
public var body: some View {
GeometryReader { geometry in
generateContent(in: geometry, for: tags, color: color)
.background(viewHeightReader($totalHeight))
}
.frame(height: totalHeight)
}
private func generateContent(in geo: GeometryProxy, for tags: [TagsModel], color: Color) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
/// moving tags to a new line when they do not fit on the screen
return ZStack(alignment: .topLeading) {
ForEach(tags, id: \.id) { tag in
self.item(tag, color)
.padding([.horizontal, .vertical], 4)
.fixedSize(horizontal: false, vertical: true)
.linelimit(1)
.alignmentGuide(.leading) { dimension in
if abs(width - dimension.width) > geo.size.width {
width = 0
height -= dimension.height
}
let result = width
if tag.id == tags.last?.id {
width = 0
} else {
width -= dimension.width
}
return result
}
.alignmentGuide(.top) { _ in
let result = height
if tag.id == tags.last?.id {
height = 0
}
return result
}
}
}
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
}
@eldaroid
Copy link
Author

eldaroid commented May 13, 2024

Description

The WrapTagsView component is designed to display a series of tags that wrap onto new lines when they exceed the width of their container. This component is particularly useful in user interfaces where a list of tags, labels, or categories needs to be presented in a space-efficient and visually appealing manner.

Снимок экрана 2024-05-13 в 13 36 09

Usage

How to use the WrappableTagsView component in a SwiftUI application:

struct ContentView: View {
    let tags = [
        TagsModel(title: "\"Well"),
        TagsModel(title: "done"),
        TagsModel(title: "is better"),
        TagsModel(title: "than"),
        TagsModel(title: "said"),
        TagsModel(title: "well"),
        TagsModel(title: "said\""),
        TagsModel(title: "-"),
        TagsModel(title: "Benjamin"),
        TagsModel(title: "Franklin")
    ]
    
    var body: some View {
        AlignedTagsView(
            tags: tags,
            color: .cyan
        ) { tag, color in
            Text(tag.title)
                .padding()
                .background(color)
                .frame(height: 36)
                .cornerRadius(10)
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.indigo, lineWidth: 2))
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment