禁用 SwiftUI SegmentedPickerStyle Picker 中的段?

Posted

技术标签:

【中文标题】禁用 SwiftUI SegmentedPickerStyle Picker 中的段?【英文标题】:Disable a segment in a SwiftUI SegmentedPickerStyle Picker? 【发布时间】:2020-03-04 15:03:06 【问题描述】:

我的 SwiftUI 应用有一个分段选取器,我希望能够根据从网络调用中检索到的选项的可用性禁用一个或多个选项。查看代码类似于:

    @State private var profileMetricSelection: Int = 0
    private var profileMetrics: [RVStreamMetric] = [.speed, .heartRate, .cadence, .power, .altitude]
    @State private var metricDisabled = [true, true, true, true, true]

    var body: some View 
        VStack(alignment: .leading, spacing: 2.0) 
            ...(some views)...
            Picker(selection: $profileMetricSelection, label: Text("")) 
                ForEach(0 ..< profileMetrics.count)  index in
                    Text(self.profileMetrics[index].shortName).tag(index)
                
            .pickerStyle(SegmentedPickerStyle())
            ...(some more views)...
        
    

我想要做的是根据网络数据修改metricDisabled 数组,以便视图重绘启用相关段。在 UIKit 中,这可以通过在 UISegmentedControl 上调用 setEnabled(_:forSegmentAt:) 来完成,但我找不到使用 SwiftUI Picker 的方法

我知道我可以将 UISegmentedControl 包装在 UIViewRepresentable 中,但在此之前我只是想检查一下我没有遗漏什么...

【问题讨论】:

现在不行。使用UISegmentedControl 你最好基于布尔标志的文件管理器 profileMetrics(所以不要显示该选项,如果它不可用) @Asperi 你可以模仿,唯一的麻烦是视觉风格:-( 【参考方案1】:

你可以使用这个简单的技巧

import SwiftUI

struct ContentView: View 
    @State var selection = 0
    let data = [1, 2, 3, 4, 5]
    let disabled = [2, 3] // at index 2, 3
    var body: some View 

        let binding = Binding<Int>(get: 
            self.selection
        )  (i) in
            if self.disabled.contains(i)  else 
                self.selection = i
            
        

        return VStack 
            Picker(selection: binding, label: Text("label")) 
                ForEach(0 ..< data.count)  (i) in
                    Text("\(self.data[i])")
                
            .pickerStyle(SegmentedPickerStyle())
            Spacer()
        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

也许像

ForEach(0 ..< data.count)  (i) in
    if !self.disabled.contains(i) 
        Text("\(self.data[i])")
     else 
        Spacer()
    

有助于更好地可视化它

注释(基于讨论)

从用户的角度来看,Picker 是一个控件,可以处于禁用/启用状态。

从 Picker 中选择的选项不是控件,它是一些值。如果您制作一个呈现给用户的控件列表,其中一些可能会被禁用,只是为了通知用户,与之关联的操作当前不可用(如菜单、某些按钮集合等)

我建议您在 Picker 中仅显示可以选择的值。这个值集合可以随时更新。

更新

你喜欢这样的吗?

完全没问题...(复制-粘贴-尝试-修改...)

import SwiftUI

struct Data: Identifiable 
    let id: Int
    let value: Int
    var disabled: Bool


struct ContentView: View 
    @State var selection = -1
    @State var data = [Data(id: 0, value: 10, disabled: true), Data(id: 1, value: 20, disabled: true), Data(id: 2, value: 3, disabled: true), Data(id: 3, value: 4, disabled: true), Data(id: 4, value: 5, disabled: true)]
    var filteredData: [Data] 
        data.filter( (item) -> Bool in
            item.disabled == false
        )
    
    var body: some View 
        VStack 
            VStack(alignment: .leading, spacing: 0) 
                Text("Select from avaialable")
                    .padding(.horizontal)
                    .padding(.top)
                HStack 
                    GeometryReader  proxy in
                        Picker(selection: self.$selection, label: Text("label")) 
                            ForEach(self.filteredData)  (item) in
                                Text("\(item.value.description)").tag(item.id)
                            
                        
                        .pickerStyle(SegmentedPickerStyle())
                        .frame(width: CGFloat(self.filteredData.count) * proxy.size.width / CGFloat(self.data.count), alignment: .topLeading)

                        Spacer()
                    .frame(height: 40)
                .padding()
            .background(Color.yellow.opacity(0.2)).cornerRadius(20)
            Button(action: 
                (0 ..< self.data.count).forEach  (i) in
                    self.data[i].disabled = false
                
            ) 
                Text("Enable all")
            
            Button(action: 
                self.data[self.selection].disabled = true
                self.selection = -1
            ) 
                Text("Disable selected")
            .disabled(selection < 0)
            Spacer()
        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

【讨论】:

这违反了 Apple 人机界面指南并且行为令人困惑。这就是为什么我不建议这样的事情(尽管,事实上,我尝试了类似的方法)。因此,如果需要段禁用功能,现在UISegmentedControl 更可取。 @Asperi 是的,我们需要公开所有这些 xxxStyle 协议。或者你可以在 ForEach 中使用 spacer 作为临时替换 感谢两位的想法,还有一些富有想象力的想法。感觉需要对 SwiftUI 进行一次重大更新,以使功能更接近 UIKit 的水平,但我不确定我们是否或何时会得到它。与此同时,我认为我需要恢复到 UISegmentedControl 才能让它看起来和表现得恰到好处 @FleetPhil 我个人会从选择器中过滤掉所有“禁用”选项。我喜欢看到某些东西处于“禁用状态”,只有当用户可以通过一些交互来启用它时,比如在填写凭据之前临时禁用登录按钮等。选择器中的禁用选项对我来说似乎是错误的想法。 @user3441734 可以看出这种做法的逻辑了的完整类型列表可供选择。也许更好的方法是显示选择器的占位符,直到选项可用,然后仅填充选择器中有效的段。我试试看……

以上是关于禁用 SwiftUI SegmentedPickerStyle Picker 中的段?的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUi 中禁用默认按钮单击动画

SwiftUI ActionSheet 禁用按钮

SwiftUI 2.0 在 macOS 上禁用窗口的缩放按钮

当视图出现在 SwiftUI 中时禁用动画

SwiftUI:基于 WebView 状态禁用/重新启用按钮

如何禁用 SwiftUI 的默认行为?