0

I have a LottieAnimationView along with some other components inside ScrollView, animation is only supposed to play once.

@State var messageBannerVisisbility: Bool = false

var body: some View {
    VStack(alignment: .center) {
        TrackableScrollView {
            VStack(alignment: .center) {
                headerView(components: header)
                contentView(components: body)
            }
        } onScrollingStarted: {
            hideMessageBanner()
        } onScrollingFinished: {
            showMessageBanner()
        }
        .animation(nil)
        
        footerView(footer: content.footer)
        
    }
    .onAppear {
        showMessageBanner()
    }
}

@ViewBuilder private func footerView(footer: SignupPageV2Footer) -> some View {
    VStack(alignment: .leading, spacing: 0) {
        if let message = footer.message, messageBannerVisisbility {
            footerMessageView(from: message)
        }
        
        HStack(alignment: .center, spacing: 8) {
            Label(footer.signupAction.info.stripHTMLTags)
                .textColor(.secondary)
                .frame(width: 115, alignment: .leading)
            
            LozengeButton(title: footer.signupAction.label, isLoading: $viewModel.isPaymentInProgress) {
                viewModel.startPayment()
            }
            .accessibility(identifier: "subscribe_button")
        }
        .padding([.horizontal, .top], 16)
        .padding(.bottom, 8)
        .background(Color.white)
    }
    .background(Color.white.edgesIgnoringSafeArea(.bottom).elevation(.LightBackground.small))
}

@ViewBuilder private func footerMessageView(from message: SignupPageV2FooterMessage) -> some View {
    message.build { deeplink in
        viewModel.handleDeeplink(deeplink)
    } processEvent: { event in
        viewModel.handleEvent(event)
    }
    .transition(.move(edge: .bottom).combined(with: .opacity))
}

private func showMessageBanner() {
    if messageBannerVisisbility == true { return }
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
        withAnimation(.easeIn(duration: 0.3)) {
            messageBannerVisisbility = true
        }
    }
}

private func hideMessageBanner() {
    if messageBannerVisisbility == false { return }
    withAnimation(.easeIn(duration: 0.3)) {
        messageBannerVisisbility = false
    }
}

TrackableScrollView is my custom implementation to have scroll Start and End callbacks to show/hide footerMessageView, which is inside footerView, while scrolling.

The issue I am facing is, whenever I scroll LottieAnimationView seems to be resetting and therefore it plays the animation again everytime I scroll.

How do I just update footerView so that animation is only played once after the screen loads?

Paras Gorasiya
  • 1,295
  • 2
  • 13
  • 33

2 Answers2

0

I have seen similar problems and I have been able to solve them by using an id on the view that is supposed to animate only once. The best way is to add the id as something that is unique to that instance of the view, normally the id would be based on the data you give the view.

.id(item.id)

This will prevent the view from being re-drawn when there are changes in other components.

DevB2F
  • 4,674
  • 4
  • 36
  • 60
  • Thanks for your answer. I tried this on the LottieAnimationView component but it doesnt seem to work. I set a static id like 9999 to the component because I dont have any other data, based on which I can set the id, that I pass to that component. – Paras Gorasiya Feb 07 '23 at 14:05
0

Try this:

(I cannot check this code will work or not)

struct SomeView1: View {
    @State var messageBannerVisisbility: Bool = false

    var body: some View {
        VStack(alignment: .center) {
            ScrollViewTest(messageBannerVisisbility: messageBannerVisisbility)
        
            footerView(footer: content.footer)
        }
        .onAppear {
            showMessageBanner()
        }
    }
}

struct ScrollViewTest: View {
    @Binding var messageBannerVisisbility: Bool
    
    var body: some View {
        TrackableScrollView {
            VStack(alignment: .center) {
                headerView(components: header)
                contentView(components: body)
            }
        } onScrollingStarted: {
            withAnimation(.easeIn(duration: 0.3)) {
                messageBannerVisisbility = true
            }
        } onScrollingFinished: {
            withAnimation(.easeIn(duration: 0.3)) {
                messageBannerVisisbility = false
            }
        }
        .animation(nil)
    }
}

@ViewBuilder private func footerView(footer: SignupPageV2Footer) -> some View {
    VStack(alignment: .leading, spacing: 0) {
        if let message = footer.message, messageBannerVisisbility {
            footerMessageView(from: message)
        }
        
        HStack(alignment: .center, spacing: 8) {
            Label(footer.signupAction.info.stripHTMLTags)
                .textColor(.secondary)
                .frame(width: 115, alignment: .leading)
            
            LozengeButton(title: footer.signupAction.label, isLoading: $viewModel.isPaymentInProgress) {
                viewModel.startPayment()
            }
            .accessibility(identifier: "subscribe_button")
        }
        .padding([.horizontal, .top], 16)
        .padding(.bottom, 8)
        .background(Color.white)
    }
    .background(Color.white.edgesIgnoringSafeArea(.bottom).elevation(.LightBackground.small))
}

@ViewBuilder
private func footerMessageView(from message: SignupPageV2FooterMessage) -> some View {
    message.build { deeplink in
        viewModel.handleDeeplink(deeplink)
    } processEvent: { event in
        viewModel.handleEvent(event)
    }
    .transition(.move(edge: .bottom).combined(with: .opacity))
}

Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101