为啥嵌入到 SwiftUI 表单中的按钮不起作用?

Posted

技术标签:

【中文标题】为啥嵌入到 SwiftUI 表单中的按钮不起作用?【英文标题】:Why buttons don't work when embedded in a SwiftUI Form?为什么嵌入到 SwiftUI 表单中的按钮不起作用? 【发布时间】:2019-09-15 19:34:00 【问题描述】:

我正在构建一个 SwitUI 表单。我需要绘制一些自定义滑块。为此,我制作了一个视图 (SliderView),我在表单中多次包含该视图。我花了一些时间来管理绑定以在父视图中获取滑块的值,但我能够做到:-) 我无法理解的最大谜团是为什么我放置在子视图中的按钮在嵌入表单时不起作用。它们在 VStack 中工作正常。代码如下:

//  SliderView.swift

import SwiftUI

struct SliderView: View 
    var name: String
    var min: Double
    var max: Double
    var step: Double
    var defaultValue: Double

    let numberFormatter: NumberFormatter = 
        let formatter = NumberFormatter()
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 2
        formatter.minimumIntegerDigits = 1
        return formatter
    ()


    @Binding var selectedValue: Double
    var body: some View 
        HStack
            Text("\(name)")
            Slider(value: $selectedValue, in: self.min...self.max, step: self.step)
            Button(action: 
                if (self.selectedValue - self.step) >= self.min
                    self.selectedValue = self.selectedValue - self.step
                
            ) 
                Image(systemName:"minus.circle")
            
            Text("\(numberFormatter.string(from: NSNumber(value: selectedValue)) ?? "")")
                .padding(5)
                .onAppear 
                    self.selectedValue = self.defaultValue
                
            Button(action: 
                if (self.selectedValue + self.step) <= self.max
                    self.selectedValue = self.selectedValue + self.step
                
            ) 
                Image(systemName:"plus.circle")
            


        

    


struct SliderView_Previews: PreviewProvider 
    @State static var myValue: Double = 1
    static var previews: some View 
        SliderView(name: "CPU", min: 1, max: 10, step: 0.5, defaultValue: 1, selectedValue: $myValue)
    

现在,我正在构建的表单的一部分。代码编译,滑块工作,每个滑块的值都可以在父视图中看到。但是,逐步更新滑块的按钮不起作用(因为它们嵌入在表单中)

//
//  ContentView.swift

import SwiftUI

struct ContentView: View 
    @State var selectedCPU: Double = 1
    @State var selectedRAM: Double = 1
    @State var selectedDisk: Double = 50
    var body: some View 

        NavigationView
            Form
                Section
                    SliderView(name: "CPU", min: 1, max: 16, step: 1, defaultValue: selectedCPU, selectedValue: $selectedCPU)
                    SliderView(name: "RAM", min: 0.5, max: 128, step: 0.5, defaultValue: selectedRAM, selectedValue: $selectedRAM)
                    SliderView(name: "SSD", min: 20, max: 500, step: 10, defaultValue: selectedDisk, selectedValue: $selectedDisk)
                
                Section
                    Text("CPU: \(selectedCPU)")
                    Text("RAM: \(selectedRAM)")
                    Text("SSD: \(selectedDisk)")
                
            
        


    


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

只需在上面的代码中用“VStack”替换“Form”,按钮就可以正常工作。任何人都可以解释一下吗?我认为是关于表单中不同层的东西。当我尝试单击按钮时,似乎单击了带有滑块的整行,但没有任何反应。 我在 MacOS 10.15 beta (19A558d) 中使用 Xcode 11 GM 提前致谢!

【问题讨论】:

在 Xcode 11.1 和 MacOS 10.15 中发生同样的情况 【参考方案1】:

据我所知,当表单行包含接受点击手势的视图(例如按钮)时,它们的行为会有所不同。当该行包含一个按钮时,当用户点击该按钮时,同一行中的所有其他内容也会收到一个点击手势。所以在你上面的例子中,并不是你的按钮不起作用,实际发生的是它们都在接收点击手势并执行它们的动作,但是由于你的代码中的逻辑有效地取消了另一个的效果,它看起来好像它们不工作。

为了帮助证明这一点,这是我编写的一些测试代码:

    @State var count1 = 0
    @State var count2 = 0

    func buttonTest() -> some View 
        HStack 
            Button(action: 
                self.count1 += 1
            ) 
                Text("Button 1: \(count1)")
            
            Spacer()
            Button(action: 
                self.count2 += 1
            ) 
                Text("Button 2: \(count2)")
            
        
    

    var body: some View 
        VStack 
            buttonTest()
            Form 
                Section(header: Text("Test")) 
                    buttonTest()
                
            
        
    

如果您点击表单上方的任一按钮,它们会按预期独立工作,只会增加它们自己的计数,但如果您点击表单中的任一按钮或行中的任何其他位置,则两个按钮的操作都会执行并且都计数递增。

我不确定 Apple 为什么要这样做,但我也在努力寻找解决这个问题的方法。

编辑

感谢Shauket Sheikh,有一个解决方案,但它使代码重用变得不那么优雅。

所以当 Button 在 Form 中时,需要将代码放入 .onTapGesture 处理程序中,但如果 Button 在 Form 外,则需要将代码放入 action: 处理程序中。

我真的希望这不是 Apple 在 Forms 上的长期立场:这与一次编写,随处使用的理念背道而驰。

【讨论】:

谢谢你们!!它工作正常,我同意这有点奇怪。我希望 Apple 允许这两种解决方案。 感谢您的完整性检查。截至 2020 年 4 月,这仍然是一个问题。 这仍然是 6/2021 的问题,也就是 WWDC 2021 的前几天。为了使用 onTapGesture,我将 Button 替换为 Image 本身。 “我真的希望这不是 Apple 在 Forms 上的长期立场:它违背了一次编写,到处使用的哲学”,公平地说,这种哲学根本站不住脚,因为ios、iPadOS、MacOS 和 WatchOS 之间的所有差异。归根结底,开发人员是可移植的,但没有那么多代码(在 UI 级别)。 有点,但这个问题出现在单个平台中。即使您使用的唯一平台是 iOS,您仍然需要从按钮中抽象出逻辑,并拥有一个从“action:”调用的版本和另一个从“onTapGesture:”调用的版本,然后根据具体情况使用正确的版本按钮是放置在窗体中还是外部。这仍然是程序员的责任,但它可以更优雅。【参考方案2】:

尝试将 buttonStyle 修饰符添加到表单中的按钮。这对我有用:

Button(action: , label: Text("Test"))
    ­­­­­.buttonStyle(PlainButtonStyle())

【讨论】:

这也对我有用,但我想了解原因。 这也为我修复了它。不知道为什么会出现这种行为! 现在(2020 年 9 月)在 SwiftUI 中似乎可以正常工作。刚刚尝试使用和不使用PlainButtonStyle() 并且两者都有效。事实上我不会使用PlainButtonStyle(),因为它会删除按钮的前景色 也适用于我(2020 年 12 月)——但如果没有这行代码,它(对我来说)仍然不起作用。并且:我仍然可以为基于图像的按钮使用前景色。 也为我工作 - 只需将以下内容添加到按钮闭包中即可恢复前景色... .buttonStyle(.plain).foregroundColor(.green)

以上是关于为啥嵌入到 SwiftUI 表单中的按钮不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SwiftUI“表单”会导致我的按钮转到屏幕底部?

SwiftUI:为啥 ObservedObject 在 AppDelegate 中不起作用?

Access 2010 VBA:为啥这个表单打开和关闭序列不起作用?

嵌入另一个视图时,SwiftUI 动画不起作用

为啥movieclip中的按钮不起作用(actionscript 2)

SwiftUI 为啥 Picker 上的输入不起作用?