Created
December 29, 2023 21:14
-
-
Save shtnkgm/1b78a25bbd071631fb3f148655c9f0cf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct TagListView<Data: Collection, Content: View>: View where Data.Element: Hashable { | |
let items: Data | |
let itemSpacing: CGFloat | |
let lineSpacing: CGFloat | |
let alignment: HorizontalAlignment | |
let content: (Data.Element) -> Content | |
@State private var width: CGFloat = 0 | |
var body: some View { | |
ZStack(alignment: Alignment(horizontal: alignment, vertical: .center)) { | |
Color.clear | |
.frame(height: 1) | |
.readSize { size in | |
width = size.width | |
} | |
_TagListView( | |
width: width, | |
items: items, | |
itemSpacing: itemSpacing, | |
lineSpacing: lineSpacing, | |
alignment: alignment, | |
content: content | |
) | |
} | |
} | |
} | |
struct _TagListView<Data: Collection, Content: View>: View where Data.Element: Hashable { | |
let width: CGFloat | |
let items: Data | |
let itemSpacing: CGFloat | |
let lineSpacing: CGFloat | |
let alignment: HorizontalAlignment | |
let content: (Data.Element) -> Content | |
@State var itemsSize: [Data.Element: CGSize] = [:] | |
var body: some View { | |
VStack(alignment: alignment, spacing: lineSpacing) { | |
ForEach(rows, id: \.self) { rowItems in | |
HStack(spacing: itemSpacing) { | |
ForEach(rowItems, id: \.self) { item in | |
content(item) | |
.fixedSize() | |
.readSize { size in | |
itemsSize[item] = size | |
} | |
} | |
} | |
} | |
} | |
} | |
var rows: [[Data.Element]] { | |
var result: [[Data.Element]] = [[]] | |
var currentRow = 0 | |
var remainingWidth = width | |
items.forEach { item in | |
let itemWidth = (itemsSize[item] ?? CGSize(width: width, height: 1)).width | |
if remainingWidth >= itemWidth { | |
result[currentRow].append(item) | |
} else { | |
currentRow += 1 | |
result.append([item]) | |
remainingWidth = width | |
} | |
remainingWidth -= itemWidth + itemSpacing | |
} | |
return result | |
} | |
} | |
extension View { | |
func readSize(onChange: @escaping (CGSize) -> Void) -> some View { | |
background( | |
GeometryReader { geometryProxy in | |
Color.clear | |
.preference(key: SizePreferenceKey.self, value: geometryProxy.size) | |
} | |
) | |
.onPreferenceChange(SizePreferenceKey.self, perform: onChange) | |
} | |
func readFrame(in coordinateSpace: CoordinateSpace, onChange: @escaping (CGRect) -> Void) -> some View { | |
background( | |
GeometryReader { geometryProxy in | |
Color.clear | |
.preference(key: RectPreferenceKey.self, value: geometryProxy.frame(in: coordinateSpace)) | |
} | |
) | |
.onPreferenceChange(RectPreferenceKey.self, perform: onChange) | |
} | |
} | |
private struct SizePreferenceKey: PreferenceKey { | |
static var defaultValue: CGSize = .zero | |
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} | |
} | |
private struct RectPreferenceKey: PreferenceKey { | |
static var defaultValue: CGRect = .zero | |
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment