Skip to content

Instantly share code, notes, and snippets.

@gromwel
Created July 7, 2022 13:40
Show Gist options
  • Save gromwel/97d754b19ed485484c0e1894394b6a81 to your computer and use it in GitHub Desktop.
Save gromwel/97d754b19ed485484c0e1894394b6a81 to your computer and use it in GitHub Desktop.
Вьюха с расстановкой контента построчно с переносами
import SwiftUI
import Combine
extension Int {
static func random() -> Int {
let lenght = Int.random(in: 1...15)
return Array(0...lenght).reduce(into: 0) { result, x in
let random = Array(0...9).randomElement() ?? 0
result *= 10
result += random
}
}
}
// MARK: - Пример
struct ContentView: View {
let tags1: [String] = ["hello", "world", "ios", "wwdc", "one more thing", "macos"]
@State var tags2: [Int] = []
var body: some View {
VStack {
ScrollView {
TagsView(tags: tags1) { AnyView(TagStringView(text: $0)) }
TagsView(tags: tags2) { AnyView(TagIntView(number: $0)) }
}
Button {
tags2.append(Int.random())
} label: {
Text("add one")
.foregroundColor(.white)
.padding()
.background(.blue)
.cornerRadius(16)
}
}
}
}
struct TagStringView: View {
let text: String
var body: some View {
Text(text)
.font(.body)
.lineLimit(1)
.padding(8)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color(UIColor.systemGray6))
)
.padding(4)
}
}
struct TagIntView: View {
let number: Int
var body: some View {
Button {
print(String(number))
} label: {
Text(String(number))
.font(.caption2)
.lineLimit(1)
.padding(4)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(Color(UIColor.systemGray6))
)
.padding(2)
}
}
}
// MARK: -
// MARK: Вьюха расстановки "тегов"
struct TagsView<T>: View {
var tags: [T]
var content: (T) -> AnyView
@State private var sizesDict: [Int: CGSize] = [:]
@State private var totalHeight: CGFloat = 0
var body: some View {
// Топ ридер который даст ширину для вычислений
GeometryReader { topProxy in
// ZStack в который и будем класть все теги, они должны быть на разныех слоях так как положение регулируем отступами
ZStack(alignment: .topLeading) {
ForEach(Array(0..<tags.count), id: \.self) { index in
// Берем ui по значению
content(tags[index])
.background(
// Ставим бэкграундом вычислитель размера конкретной сабвьюшки
SizeCalculator<GPTagK> {
// Мы сами определили ключ по которому передавать значение и тут маппим это значение
[GPKModel(id: index, size: $0.size)]
}
)
// Рассчитаные отспупы для каждой вьюшки
.padding(.top, sizesDict[index]?.height)
.padding(.leading, sizesDict[index]?.width)
}
}
//.background(.purple)
.background(
// Ставим бекграундом вычислитель размера всего стека с вьюшками
SizeCalculator<GPReaderK> { [GPKModel(id: NSNotFound, size: $0.size)] }
)
// Следим за изменением значения по ключу каждой вьюхи
.onPreferenceChange(GPTagK.self) { value in
var x: CGFloat = 0
var y: CGFloat = 0
// Все рассчеты для расстановки тегов тут
value.forEach { model in
var currentX = x
var currentY = y
if x + model.size.width > topProxy.size.width {
x = model.size.width
y += model.size.height
currentX = 0
currentY = y
} else {
x += model.size.width
}
// рассчитанные отступы для каждой вьюхи кладем в словарь
self.sizesDict[model.id] = CGSize(width: currentX, height: currentY)
}
}
}
// Установка общей высоты для тор ридера, иначе он займер все пространство
.frame(height: totalHeight)
// Следим за изменением значения по ключу общей высоты
.onPreferenceChange(GPReaderK.self) { value in
// надо проверить не пустое ли значение, может крашнуть
totalHeight = value[0].size.height
}
}
}
// MARK: Подложка которая посчитает размеры вьюхи и передаст "наверх"
// можно передать разные ключи и обработав proxy передать необходимые параметры
struct SizeCalculator<Key: PreferenceKey>: View {
let value: (GeometryProxy) -> Key.Value
var body: some View {
GeometryReader { proxy in
Color.clear
.preference(key: Key.self,value: value(proxy))
}
}
}
// MARK: Preference key
struct GPKModel: Equatable {
let id: Int
let size: CGSize
}
struct GPTagK: PreferenceKey {
static var defaultValue: [GPKModel] = []
static func reduce(value: inout [GPKModel], nextValue: () -> [GPKModel]) {
value.append(contentsOf: nextValue())
}
}
struct GPReaderK: PreferenceKey {
static var defaultValue: [GPKModel] = []
static func reduce(value: inout [GPKModel], nextValue: () -> [GPKModel]) {
value.append(contentsOf: nextValue())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment