Как стать автором
Обновить

Комментарии 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)
    }
    
}

Зарегистрируйтесь на Хабре, чтобы оставить комментарий