如何创建一个SwiftUI按钮,在触摸时改变不透明度并触发按钮动作?

5
我想使用SwiftUI创建一个按钮,当我的手指接触它时立即触发(类似于UIKit的touch down而不是touch up inside)。我还希望当我按住按钮时,按钮的不透明度变为0.7。并且我希望按钮的不透明度仅在我不再触摸按钮时才恢复为1。
我尝试了两种不同的按钮样式来创建这样一个按钮,但它们都失败了。
    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) 和 trigger 方法 (属于 PrimitiveButtonStyle)。

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

2个回答

4

好的,我理解作者只想使用Button来解决问题,所以我进行了更深入的挖掘。我在Swift UI实验室发现了一些有趣的东西。这个想法与我的第一个答案相同:使用@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)
        }
    }
}

0
我没有使用过 ButtonStyle,但是尝试通过 Composing SwiftUI Gestures 来解决它。我组合了 TapGestureLongPressGesture 并使用 @GestureState 来控制 "button"(只是一个 Text)的 .opacity。结果就像你要求的一样:
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)
        }

    }
}

附言:我只使用了@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!”时,我也没有看到“action was fired”文本标签。 - SmoothPoop69
如果您长按“Hello world”按钮足够长的时间,它的不透明度实际上会在手指仍然按住按钮的情况下恢复为1。 - SmoothPoop69
@SmoothPoop69,你用的是哪个版本的Xcode?有什么新东西吗?你是在Canvas上试还是在真实设备上试? - Hrabovskyi Oleksandr
@SmoothPoop69 我尝试在模拟器中运行,出现了相同的问题。请检查编辑后答案中的更新:现在可以正常工作了。 - Hrabovskyi Oleksandr
嘿,非常感谢您的更新。我真的很感激。它不再显示紫色错误了。但是我想知道是否可以使用实际按钮而不是使用文本标签作为按钮。当我点击按钮时,我想要实际触发一个方法,一个简单的打印方法就足够了。我想知道在当前SwiftUI中是否可能实现这一点。 - SmoothPoop69

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接