Search code examples
iosswiftswiftuicore-data

How to trigger button in one view and animate other object in another view in SwiftUI?


While creating animation logic so that when the button is pressed, the Card would swipe away, however, with the current approach by using State and Binding nothing happens. I suppose the trick here is that I'm not letting the card view know when the button is triggered. Here's button where @State private var isFLipped = false :

Button(action: {
                // Handle button action
              //  isTapped.toggle()
                isFLipped.toggle()
            }) {
                Text("👍")
                    .frame(width: 70, height: 50)
                    .background(Color("Easy"))
                    .clipShape(RoundedRectangle(cornerRadius: 8))
            }

Here's SingleCardView:

ZStack {
         RoundedRectangle(cornerRadius: 25, style: .continuous)
             .fill(Color.white)
             
             
             .overlay(RoundedRectangle(cornerRadius: 25).stroke(getColor(), lineWidth: 2))// Here we change the border color based on the swipe direction
             .shadow(radius: 3)

         VStack {
             NavigationStack {
                         
                 Text(card.term ?? "Unnamed Card")
                 Divider()
                                     if isTapped {
                                         Text(card.definition ?? "Unnamed Card")
                                     }
                        }

         }
     }
     .frame(width: 300, height: 500)
     
     .rotation3DEffect(
                             Angle(degrees: isFLipped ? 360: 0),
                             axis: (x: 0, y: isFLipped ? 360 : 0, z: 0)
                             
                 )

     .onTapGesture {
         isTapped.toggle()
     }

 }

Solution

  • The SwiftUI way/most efficient of doing this would be to have a value type in an @State and sharing isFlipped as an @Binding

    import SwiftUI
    
    struct SampleSharedManagerView: View {
        @State private var isFlipped: Bool = false
        var body: some View {
            NavigationStack{
                List {
                    Section("first") {
                        FirstView(isFlipped: $isFlipped)
                    }
                    Section("second") {
                        SecondView(isFlipped: $isFlipped)
                    }
                }
            }
        }
    }
    
    struct FirstView: View {
        @Binding var isFlipped: Bool
        var body: some View {
            Image(systemName: "person")
                .rotation3DEffect(Angle(degrees: isFlipped ? 360: 0), axis: (x: 0, y: isFlipped ? 360 : 0, z: 0))
            
                .animation(.easeInOut(duration: 2), value: isFlipped)
        }
    }
    struct SecondView: View {
        @Binding var isFlipped: Bool
        var body: some View {
            Button("Toggle") {
                isFlipped.toggle()
            }.buttonStyle(.borderedProminent)
            
        }
    }
    

    But if we assume that the 2 Views are separated by various layers we can tap into Combine and have a shared source of truth.

    import SwiftUI
    import Combine
    
    class AppEventsManager {
        static let shared = AppEventsManager()
        var isFlipped : CurrentValueSubject<Bool, Never> = .init(true)
        
        private init() { }
    }
    

    Then you can subscribe to changes in the Views that need them.

    //Observes changes
    struct FirstView: View {
        @State private var isFlipped: Bool = false
        @State private var cancellables: Set<AnyCancellable> = []
        
        var body: some View {
            Image(systemName: "person")
                .rotation3DEffect(Angle(degrees: isFlipped ? 360: 0), axis: (x: 0, y: isFlipped ? 360 : 0, z: 0))
            
                .animation(.easeInOut(duration: 2), value: isFlipped)
                .task {
                    AppEventsManager.shared.isFlipped
                        .receive(on: DispatchQueue.main)
                        .sink { isFlipped in
                            self.isFlipped = isFlipped
                        }
                        .store(in: &cancellables)
                }.onDisappear {
                    self.cancellables = .init()
                }
        }
    }
    //Makes Changes
    struct SecondView: View {
        let manager = AppEventsManager.shared
        var body: some View {
            Button("Toggle") {
                manager.isFlipped.send(!manager.isFlipped.value)
            }.buttonStyle(.borderedProminent)
            
        }
    }