Skip to content

Instantly share code, notes, and snippets.

@KaneBuckthorpe
Last active October 18, 2022 07:15
Show Gist options
  • Save KaneBuckthorpe/63e719cb96e644d31bf027194bf4ccdb to your computer and use it in GitHub Desktop.
Save KaneBuckthorpe/63e719cb96e644d31bf027194bf4ccdb to your computer and use it in GitHub Desktop.
SwiftUI - WrappedHStack + example TagsView
//
// TagItem.swift
// ChalkUp
//
// Created by kane buckthorpe on 13/03/2021.
//
import Foundation
class TagItem: ObservableObject {
let title: String
@Published var isSelected: Bool
init(title: String, isSelected: Bool = false) {
self.title = title
self.isSelected = isSelected
}
convenience init(_ title: String) {
self.init(title: title)
}
}
//
// TagsView.swift
// Test
//
// Created by kane buckthorpe on 11/03/2021.
//
import SwiftUI
struct TagsView: View {
var tags: [TagItem]
var body: some View {
GeometryReader { geometry in
WrappedHStack(geometry: geometry) {
tags.map(TagView.init)
}
}
}
}
struct TagsView_Previews: PreviewProvider {
static var previews: some View {
let tagItems = ["tags", "For", "Testing"].map(TagItem.init)
return TagsView(tags: tagItems)
}
}
//
// TagView.swift
// Test
//
// Created by kane buckthorpe on 13/03/2021.
//
import SwiftUI
struct TagView: View {
@ObservedObject var tagItem: TagItem
init(_ tagItem: TagItem) {
self.tagItem = tagItem
}
var body: some View {
Text(tagItem.title)
.font(.subheadline)
.foregroundColor(.white)
.padding(4)
.border(Color.black)
.background(tagItem.isSelected ? Color.black : Color.purple)
.onTapGesture {
self.tagItem.isSelected.toggle()
}
}
}
//
// View+.swift
// Test
//
// Created by kane buckthorpe on 13/03/2021.
//
import SwiftUI
extension View {
func erasedToAnyView() -> AnyView {
AnyView(self)
}
func getSize() -> CGSize {
UIHostingController(rootView: self).view.intrinsicContentSize
}
}
//
// WrappedHStack.swift
// Test
//
// Created by kane buckthorpe on 13/03/2021.
//
import SwiftUI
struct WrappedHStack<Content: View>: View {
private let content: [Content]
private let spacing: CGFloat = 8
private let geometry: GeometryProxy
init(geometry: GeometryProxy, content: [Content]) {
self.content = content
self.geometry = geometry
}
var body: some View {
let rowBuilder = RowBuilder(spacing: spacing,
containerWidth: geometry.size.width)
let rowViews = rowBuilder.generateRows(views: content)
let finalView = ForEach(rowViews.indices) { rowViews[$0] }
VStack(alignment: .center, spacing: 8) {
finalView
}.frame(width: geometry.size.width)
}
}
extension WrappedHStack {
init<Data, ID: Hashable>(geometry: GeometryProxy, @ViewBuilder content: () -> ForEach<Data, ID, Content>) {
let views = content()
self.geometry = geometry
self.content = views.data.map(views.content)
}
init(geometry: GeometryProxy, content: () -> [Content]) {
self.geometry = geometry
self.content = content()
}
}
extension WrappedHStack {
struct RowBuilder {
private var spacing: CGFloat
private var containerWidth: CGFloat
init(spacing: CGFloat, containerWidth: CGFloat) {
self.spacing = spacing
self.containerWidth = containerWidth
}
func generateRows<Content: View>(views: [Content]) -> [AnyView] {
var rows = [AnyView]()
var currentRowViews = [AnyView]()
var currentRowWidth: CGFloat = 0
for (view) in views {
let viewWidth = view.getSize().width
if currentRowWidth + viewWidth > containerWidth {
rows.append(createRow(for: currentRowViews))
currentRowViews = []
currentRowWidth = 0
}
currentRowViews.append(view.erasedToAnyView())
currentRowWidth += viewWidth + spacing
}
rows.append(createRow(for: currentRowViews))
return rows
}
private func createRow(for views: [AnyView]) -> AnyView {
HStack(alignment: .center, spacing: spacing) {
ForEach(views.indices) { views[$0] }
}
.erasedToAnyView()
}
}
}
extension WrappedHStack_Previews {
struct TestView: View {
let dummyText: String
let words: [String.SubSequence]
init() {
dummyText =
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
Ut enim ad minim veniam
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur
Excepteur sint occaecat cupidatat non proident
sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
Ut enim ad minim veniam
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur
Excepteur sint occaecat cupidatat non proident
sunt in culpa qui officia deserunt mollit anim id est laborum.
"""
self.words = dummyText.split { !$0.isLetter }
}
var body: some View {
GeometryReader { geometry in
ScrollView {
WrappedHStack(geometry: geometry) {
ForEach(words.indices) { (idx) -> AnyView in
Text(words[idx])
.padding(4)
.background(Color.purple)
.foregroundColor(.white)
.font(.caption)
.erasedToAnyView()
}
}
}
}
}
}
}
struct WrappedHStack_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment