Комментарии 2
Спасибо, было интересно. Не хотите выложить в open source? Я так вижу, что весь код не соберется, если его скопирую в playground.
Спасибо за статью! Я как раз делал похожий сегмент контрол, где взял за основу ваш подход со слоями.
В качестве альтернативы смешиванию слоёв, можно накладывать маску на верхний слой, которая будет вырезать его в форме слайдера. Пример реализации ниже.
public struct HolaSegmentControl<Content: View, Value: Equatable, ID: Hashable>: View {
@Binding var selection: Value?
let values: [Value]
let id: KeyPath<Value, ID>
let itemContent: (Value) -> Content
var selectedIndex: Int? {
guard let selection else { return nil }
return values.firstIndex(of: selection)
}
public var body: some View {
ZStack {
// The layer with normal color
content(
backgroundColor: .secondary.opacity(0.3),
foregroundColor: .black,
cell: { value in
itemContent(value: value)
}
)
// The layer with selected colors trimmed by mask for the selected value
// It allows to blend text colors while selected segment changing
content(
backgroundColor: .white,
foregroundColor: .black,
cell: { value in
itemContent(value: value).onTapGesture {
selection = value
}
}
)
.mask { mask }
}
.frame(height: 44)
.clipShape(Capsule())
.animation(.spring(duration: 0.2), value: selection)
}
private var mask: some View {
GeometryReader { proxy in
let calculations = calculations(totalWidth: proxy.size.width)
if let selectedIndex {
Capsule()
.padding(2)
.frame(width: calculations.segmentWidth)
.offset(x: calculations.segmentOffset(for: selectedIndex))
}
}
}
private func content<Cell: View>(
backgroundColor: Color,
foregroundColor: Color,
cell: @escaping (Value) -> Cell
) -> some View {
HStack(spacing: .zero) {
ForEach(values, id: id) { value in
cell(value)
}
}
.background(backgroundColor)
.foregroundStyle(foregroundColor)
}
private func itemContent(value: Value) -> some View {
itemContent(value)
.padding(.horizontal, 16)
.frame(maxHeight: .infinity)
.contentShape(Rectangle())
}
private func calculations(totalWidth: CGFloat) -> HolaSegmentControlPickerCalculations {
HolaSegmentControlPickerCalculations(
totalWidth: totalWidth,
segmentCount: values.count
)
}
}
struct HolaSegmentControlPickerCalculations {
let totalWidth: CGFloat
let segmentCount: Int
var segmentWidth: CGFloat {
guard segmentCount > 0 else { return .zero }
return totalWidth / CGFloat(segmentCount)
}
func segmentOffset(for index: Int) -> CGFloat {
segmentWidth * CGFloat(index)
}
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Собственный Segmented Control на SwiftUI. Часть 1. Смешиваем цвета