SwiftUI: how to handle BOTH tap & long press of button?

前端 未结 8 1202
刺人心
刺人心 2020-12-15 06:59

I have a button in SwiftUI and I would like to be able to have a different action for \"tap button\" (normal click/tap) and \"long press\".

Is that possible in Swift

相关标签:
8条回答
  • 2020-12-15 07:37

    Here is my implementation using a modifier:

    struct TapAndLongPressModifier: ViewModifier {
      @State private var isLongPressing = false
      let tapAction: (()->())
      let longPressAction: (()->())
      func body(content: Content) -> some View {
        content
          .scaleEffect(isLongPressing ? 0.95 : 1.0)
          .onLongPressGesture(minimumDuration: 1.0, pressing: { (isPressing) in
            withAnimation {
              isLongPressing = isPressing
              print(isPressing)
            }
          }, perform: {
            longPressAction()
          })
          .simultaneousGesture(
            TapGesture()
              .onEnded { _ in
                tapAction()
              }
          )
      }
    }
    

    Use it like this on any view:

    .modifier(TapAndLongPressModifier(tapAction: { <tap action> },
                                      longPressAction: { <long press action> }))
    

    It just mimics the look a button by scaling the view down a bit. You can put any other effect you want after scaleEffect to make it look how you want when pressed.

    0 讨论(0)
  • 2020-12-15 07:41

    Try this :)

    Handles isInactive, isPressing, isLongPress and Tap(Click)

    based on this

    I tried to make this as a viewmodifier without success. I would like to see an example with @GestureState variable wrapper used in same manner as @State/@Published are bound to @Binding in view components.

    Tested: Xcode 12.0 beta, macOS Big Sur 11.0 beta

    import SwiftUI
    
    enum PressState {
    
        case inactive
        case pressing
        case longPress
        
        var isPressing: Bool {
            switch self {
            case .inactive:
                return false
            case .pressing, .longPress:
                return true
            }
        }
        
        var isLongPress: Bool {
            switch self {
            case .inactive, .pressing:
                return false
            case .longPress:
                return true
            }
        }
        
        var isInactive : Bool {
            switch self {
            case .inactive:
                return true
            case .pressing, .longPress:
                return false
            }
        }
    }
    
    
    struct ContentView: View {
        
        @GestureState private var pressState: PressState = PressState.inactive
        @State var showClick: Bool = false
        
        var press: some Gesture {
            LongPressGesture(minimumDuration: 0.8, maximumDistance: 50.0)
                .sequenced(before: LongPressGesture(minimumDuration: .infinity, maximumDistance: 50.0))
                .updating($pressState) { value, state, transaction in
                    switch value {
                    case .first(true): // first gesture starts
                        state = PressState.pressing
                    case .second(true, nil): // first ends, second starts
                            state = PressState.longPress
                        default: break
                    }
                }
        }
        
        var body: some View {
            ZStack{
                
                Group {
                Text("Click")
                    .offset(x: 0, y: pressState.isPressing ? (pressState.isLongPress ? -120 : -100) : -40)
                    .animation(Animation.linear(duration: 0.5))
                    .opacity(showClick ? 1 : 0 )
                    .animation(Animation.linear(duration: 0.3))
                    
                Text("Pressing")
                    .opacity(pressState.isPressing ? 1 : 0 )
                    .offset(x: 0, y: pressState.isPressing ? (pressState.isLongPress ? -100 : -80) : -20)
                    .animation(Animation.linear(duration: 0.5))
                
                Text("Long press")
                    .opacity(pressState.isLongPress ? 1 : 0 )
                    .offset(x: 0, y: pressState.isLongPress ? -80 : 0)
                    .animation(Animation.linear(duration: 0.5))
                }
                
                Group{
                Image(systemName: pressState.isLongPress ? "face.smiling.fill" : (pressState.isPressing ? "circle.fill" : "circle"))
                    .offset(x: 0, y: -100)
                    .font(.system(size: 60))
                    .opacity(pressState.isLongPress ? 1 : (pressState.isPressing ? 0.6 : 0.2))
                    .foregroundColor(pressState.isLongPress ? .orange : (pressState.isPressing ? .yellow : .white))
                    .rotationEffect(.degrees(pressState.isLongPress ? 360 : 0), anchor: .center)
                    .animation(Animation.linear(duration: 1))
                
                Button(action: {
                    showClick = true
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
                        self.showClick = false
                    })
                }, label: {
                    ZStack {
                        Circle()
                            .fill(self.pressState.isPressing ? Color.blue : Color.orange)
                            .frame(width: 100, height: 100, alignment: .center)
                        Text("touch me")
                    }}).simultaneousGesture(press)
                }.offset(x: 0, y: 110)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    
    0 讨论(0)
  • 2020-12-15 07:49

    I just discovered that the effect depends on the order of the implementation. Implementing the detection of gestures in the following order it seems to be possible to detect and identify all three gestures:

    1. handle a double tap gesture
    2. handle a longPressGesture
    3. handle a single tap gesture

    Tested on Xcode Version 11.3.1 (11C504)

        fileprivate func myView(_ height: CGFloat, _ width: CGFloat) -> some View {
        return self.textLabel(height: height, width: width)
            .frame(width: width, height: height)
            .onTapGesture(count: 2) {
                self.action(2)
            }
            .onLongPressGesture {
                self.action(3)
            }
            .onTapGesture(count: 1) {
                self.action(1)
            }
    }
    
    0 讨论(0)
  • 2020-12-15 07:53

    As a follow up, I had the same issue and I tried all of these answers but didn't like how they all worked. I ended up using a .contextMenu it was way easier and produces pretty much the same effect.

    Check link here

    and here is an example

    0 讨论(0)
  • 2020-12-15 07:57

    This isn't tested, but you can try to add a LongPressGesture to your button.

    It'll presumably look something like this.

    struct ContentView: View {
        @GestureState var isLongPressed = false
    
        var body: some View {
            let longPress = LongPressGesture()
                .updating($isLongPressed) { value, state, transaction in
                    state = value
                }
    
            return Button(/*...*/)
                .gesture(longPress)
        }
    }
    
    0 讨论(0)
  • 2020-12-15 07:58

    I tried many things but finally I did something like this:

        Button(action: {
        }) {
            VStack {
                Image(self.imageName)
                    .resizable()
                    .onTapGesture {
                        self.action(false)
                    }
                    .onLongPressGesture(minimumDuration: 0.1) {
                        self.action(true)
                    }
            }
        }
    

    It is still a button with effects but short and long press are different.

    0 讨论(0)
提交回复
热议问题