【问题标题】:How to create a SwiftUI button that changes opacity and triggers button action when touched?如何创建一个改变不透明度并在触摸时触发按钮动作的 SwiftUI 按钮?
【发布时间】:2020-01-06 00:38:58
【问题描述】:

我想用 SwiftUI 创建一个按钮,它会在我的手指触摸它的那一刻触发(就像 UIKit 的触摸向下而不是内部向上触摸)。当我的手指按下按钮时,我还希望按钮的不透明度变为 0.7。只有当我的手指不再触摸按钮时,我希望按钮的不透明度变回 1。

我尝试了 2 种不同类型的按钮样式来创建这样的按钮,但都失败了:

    struct ContentView: View {  
        var body: some View {  
            Button(action: {  
                print("action triggered")  
            }){  
                Text("Button").padding()  
            }  
                .buttonStyle(SomeButtonStyle())  
        }  
    }  

    struct SomeButtonStyle: ButtonStyle {  
        func makeBody(configuration: Self.Configuration) -> some View {  
            configuration.label  
                .background(Color.green)  
                .opacity(configuration.isPressed ? 0.7 : 1)  
                .onLongPressGesture(  
                    minimumDuration: 0,  
                    perform: configuration.trigger//Value of type 'SomeButtonStyle.Configuration' (aka 'ButtonStyleConfiguration') has no member 'trigger'  
                )  
        }  
    }  

    struct SomePrimativeButtonStyle: PrimitiveButtonStyle {  
        func makeBody(configuration: Configuration) -> some View {  
          configuration.label  
            .background(Color.green)  
            .opacity(configuration.isPressed ? 0.7 : 1)//Value of type 'SomePrimativeButtonStyle.Configuration' (aka 'PrimitiveButtonStyleConfiguration') has no member 'isPressed'  
            .onLongPressGesture(  
              minimumDuration: 0,  
              perform: configuration.trigger  
            )  
        }  
    }

显然上面的按钮样式都不起作用,因为 ButtonStyle 和 PrimitiveButtonStyle 不共享相同的方法和属性,所以我不能同时使用 isPressed 属性(属于 ButtonStyle)和触发方法(属于 PrimitiveButtonStyle)按钮样式相同。

我应该如何配置我的按钮样式以使其工作?

【问题讨论】:

    标签: swiftui


    【解决方案1】:

    好的,我知道作者只想看到Button 的解决方案,所以我再挖一点。在Swift UI Lab 发现了一些有趣的东西。思路与in my first answer相同:使用@GestureState并创建LongPressGesture其中.updating($...)这个状态。但是在PrimitiveButtonStyle 中,您不需要将几个手势组合在一起。因此,我稍微简化了代码并在模拟器上对其进行了测试。我认为现在这正是作者所需要的:

    struct ComposingGestures: View {
    
        var body: some View {
    
            Button(action: {
                print("action triggered")
            }){
                Text("Button")
                    .padding()
            }
                .buttonStyle(MyPrimitiveButtonStyle())
    
        }
    
    }
    
    struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
    
        func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
            MyButton(configuration: configuration)
        }
    
        struct MyButton: View {
            @GestureState private var pressed = false
    
            let configuration: PrimitiveButtonStyle.Configuration
            let color: Color = .green
    
            @State private var didTriggered = false
    
            var body: some View {
                // you can set minimumDuration to Double.greatestFiniteMagnitude if you think that
                // user can hold button for such a long time
                let longPress = LongPressGesture(minimumDuration: 300, maximumDistance: 300.0)
                    .updating($pressed) { value, state, _ in
                        state = value
                        self.configuration.trigger()
                }
    
                return configuration.label
                    .background(Color.green)
                    .opacity(pressed ? 0.5 : 1.0)
                    .gesture(longPress)
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我没有使用ButtonStyle,但尝试使用Composing SwiftUI Gestures 解决它。我编写TapGestureLongPressGesture 并使用@GestureState 来控制“按钮”的.opacity(这只是Text)。结果如你所问:

      struct ComposingGestures: View {
      
          enum TapAndLongPress {
              case inactive
              case pressing
      
              var isPressing: Bool {
                  return self == .pressing
              }
      
          }
      
          @GestureState var gestureState = TapAndLongPress.inactive
          @State private var didPress = false
      
          var body: some View {
      
              let tapAndLongPressGesture = LongPressGesture(minimumDuration: 2) // if minimumDuration <= 1 gesture state returns to inactive in 1 second
                  .sequenced(before: TapGesture())
                  .updating($gestureState) { value, state, transaction in
                      switch value {
                      case .first(true), .second(true, nil):
                          self.didPress = true // simulation of firing action
                          state = .pressing
                      default:
                          state = .pressing
                      }
              }
      
              return VStack {
                  Text("action was fired!")
                      .opacity(didPress ? 1 : 0)
      
                  Text("Hello world!")
                      .gesture(tapAndLongPressGesture)
                      .background(Color.green)
                      .opacity(gestureState.isPressing ? 0.7 : 1)
              }
      
          }
      }
      

      P.S. 我只玩了@State var didPress 来展示如何触发动作。也许最好只在第一种情况下触发它,如下所示:

      // ...
                  .updating($gestureState) { value, state, transaction in
                      switch value {
                      case .first(true):
                          self.didPress = true // simulation of firing action
                          state = .pressing
                      case .second(true, nil):
                          state = .pressing
                      default:
                          state = .pressing
                      }
      

      更新 在模拟器上尝试了代码并修复了两个错误:

      // ...
          let tapAndLongPressGesture = LongPressGesture(minimumDuration: 300, maximumDistance: 300) // now button don't return opacity to 1 even if you move your finger
      // ...
          case .first(true), .second(true, nil):
              DispatchQueue.main.async { // now there are no purple mistakes
                  self.didPress = true // simulation of firing action
              }
              state = .pressing
      // ...
      

      【讨论】:

      • 在 XCode 中运行它,但它显示“在视图更新期间修改状态,这将导致未定义的行为。”当我按下“Hello world!”时,我也没有看到“动作被触发”文本标签。
      • 如果你按“Hello world”足够长的时间,它的不透明度实际上会在手指仍然按下按钮时回到 1。
      • @SmoothPoop69 Xcode 什么版本?新的东西,你是在画布上尝试还是在真实设备上尝试?
      • @SmoothPoop69 我尝试在模拟器中运行并且遇到了同样的问题。在编辑后的答案中检查 UPDATE:现在它可以工作了。
      • 非常感谢您的更新。对此,我真的非常感激。它不再显示紫色错误。但我想知道是否可以使用实际按钮而不是使用文本标签作为按钮。我想在点击按钮时触发一个方法,为了简单起见,一个简单的打印方法就可以了。我想知道目前 SwiftUI 是否可以实现这一点
      猜你喜欢
      • 2020-07-15
      • 2014-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多