SwiftUI中可扩展的自定义分段选取器

mznpcxlj  于 2023-01-16  发布在  Swift
关注(0)|答案(2)|浏览(156)

我尝试在SwiftUI中创建一个可扩展的分段选取器,到目前为止我已经完成了以下操作:

struct CustomSegmentedPicker: View {
    
    @Binding var preselectedIndex: Int
    
    @State var isExpanded = false
    
    var options: [String]
    let color = Color.orange

    var body: some View {
        HStack {
            ScrollView(.horizontal) {
                HStack(spacing: 4) {
                    ForEach(options.indices, id:\.self) { index in
                        let isSelected = preselectedIndex == index
                        ZStack {
                            Rectangle()
                                .fill(isSelected ? color : .white)
                                .cornerRadius(30)
                                .padding(5)
                                .onTapGesture {
                                    preselectedIndex = index
                                    withAnimation(.easeInOut(duration: 0.5)) {
                                        isExpanded.toggle()
                                    }
                                }
                        }
                        .shadow(color: Color(UIColor.lightGray), radius: 2)
                        .overlay(
                            Text(options[index])
                                .fontWeight(isSelected ? .bold : .regular)
                                .foregroundColor(isSelected ? .white : .black)
                        )
                        .frame(width: 80)
                    }
                }
            }
            .transition(.move(edge: .trailing))
            .frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
            .background(Color(UIColor.cyan))
            .cornerRadius(30)
            .clipped()
            Spacer()
        }
    }
}

这给出了以下结果:

现在,当它收缩时,我如何保持显示选中的项目并隐藏其他项目?(目前,左侧的项目在未展开时总是显示)

pb3skfrl

pb3skfrl1#

做得好,你可以在ScollView的内容中添加一个.offset(),它会根据选择向左移动:

HStack {
            ScrollView(.horizontal) {
                HStack(spacing: 4) {
                    ForEach(options.indices, id:\.self) { index in
                        let isSelected = preselectedIndex == index
                        ZStack {
                            Rectangle()
                                .fill(isSelected ? color : .white)
                                .cornerRadius(30)
                                .padding(5)
                                .onTapGesture {
                                    preselectedIndex = index
                                    withAnimation(.easeInOut(duration: 0.5)) {
                                        isExpanded.toggle()
                                    }
                                }
                        }
                        .shadow(color: Color(UIColor.lightGray), radius: 2)
                        .overlay(
                            Text(options[index])
                                .fontWeight(isSelected ? .bold : .regular)
                                .foregroundColor(isSelected ? .white : .black)
                        )
                        .frame(width: 80)
                    }
                }
                .offset(x: isExpanded ? CGFloat(-84 * preselectedIndex) : 0) // <<< here
            }
            .transition(.move(edge: .trailing))
            .frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
            .background(Color(UIColor.cyan))
            .cornerRadius(30)
            .clipped()
            Spacer()
        }
pod7payv

pod7payv2#

下面是使用.matchedGeometryEffect的另一种方法,它可以处理不同的标签宽度,而不必退回到GeometryReader
根据expansionState,它可以只绘制选定的项目或绘制所有项目,.matchedGeometryEffect确保动画流畅。

struct CustomSegmentedPicker: View {
    
    @Binding var preselectedIndex: Int
    
    @State var isExpanded = false
    
    var options: [String]
    let color = Color.orange
    
    @Namespace var nspace

    var body: some View {
        HStack {
            
            HStack(spacing: 8) {
                
                if isExpanded == false { // show only selected option
                    optionLabel(index: preselectedIndex)
                        .id(preselectedIndex)
                        .matchedGeometryEffect(id: preselectedIndex, in: nspace, isSource: true)
                    
                } else { // show all options
                    ForEach(options.indices, id:\.self) { index in
                        optionLabel(index: index)
                            .id(index)
                            .matchedGeometryEffect(id: index, in: nspace, isSource: true)
                    }
                }
            }
            .padding(5)
            .background(Color(UIColor.cyan))
            .cornerRadius(30)
            
            Spacer()
        }
    }
    
    func optionLabel(index: Int) -> some View {
        
        let isSelected = preselectedIndex == index
        
        return Text(options[index])
            .fontWeight(isSelected ? .bold : .regular)
            .foregroundColor(isSelected ? .white : .black)
            .padding(8)
        
            .background {
                Rectangle()
                    .fill(isSelected ? color : .white)
                    .cornerRadius(30)
            }
        
            .onTapGesture {
                preselectedIndex = index
                withAnimation(.easeInOut(duration: 0.5)) {
                    isExpanded.toggle()
                }
            }
    }
    
}

相关问题