当视图出现在 SwiftUI 中时禁用动画

Posted

技术标签:

【中文标题】当视图出现在 SwiftUI 中时禁用动画【英文标题】:Disable animation when a view appears in SwiftUI 【发布时间】:2020-12-16 02:25:01 【问题描述】:

尝试在 SwiftUI 中显示自定义加载视图时遇到问题。 我创建了一个自定义结构视图 OrangeActivityIndi​​cator:

struct OrangeActivityIndicator: View 
    var style = StrokeStyle(lineWidth: 6, lineCap: .round)
    @State var animate = false
    let orangeColor = Color.orOrangeColor
    let orangeColorOpaque = Color.orOrangeColor.opacity(0.5)
    
    init(lineWidth: CGFloat = 6) 
        style.lineWidth = lineWidth
    
    
    var body: some View 
        ZStack 
            Circle()
                .trim(from: 0, to: 0.7)
                .stroke(
                    AngularGradient(gradient: .init(colors: [orangeColor, orangeColorOpaque]), center: .center), style: style
                )
                .rotationEffect(Angle(degrees: animate ? 360 : 0))
                .animation(Animation.linear(duration: 0.7).repeatForever(autoreverses: false))
        .onAppear() 
            self.animate.toggle()
        
    

我在不同的屏幕或视图中使用它,我的问题是它看起来很奇怪,例如在应用程序的 CampaignsView 中我在服务器调用正在进行时显示它。

struct CampaignsView: View 
    
    @ObservedObject var viewModel: CampaignsViewModel
    
    var body: some View 
        NavigationView 
            ZStack 
                VStack(spacing: 0) 
                    CustomNavigationBar(campaignsNumber: viewModel.cardCampaigns.count)
                        .padding([.leading, .trailing], 24)
                        .frame(height: 25)
                    CarouselView(x: $viewModel.x, screen: viewModel.screen, op: $viewModel.op, count: $viewModel.index, cardCampaigns: $viewModel.cardCampaigns).frame(height: 240)
                    CampaignDescriptionView(idx: viewModel.index, cardCampaigns: viewModel.cardCampaigns)
                        .padding([.leading, .trailing], 24)
                    Spacer()
                
                .onAppear 
                    self.viewModel.getCombineCampaigns()
                
                if viewModel.isLoading 
                    OrangeActivityIndicator()
                        .frame(width: 40, height: 40)
                
            
            .padding(.top, 34)
            .background(Color.orBackgroundGrayColor.edgesIgnoringSafeArea(.all))
            .navigationBarHidden(true)
        
    

指示器本身是正确旋转的,问题是当它出现时,它显示为从屏幕底部到中间的平移动画。这是我的带有服务器调用和 isLoading 属性的 viewModel:

class CampaignsViewModel: ObservableObject 
    
    @Published var index: Int = 0
    @Published var cardCampaigns: [CardCampaign] =  [CardCampaign]()
    @Published var isLoading: Bool = false

    var cancellable: AnyCancellable?


    
    func getCombineCampaigns() 
        self.isLoading = true
        let campaignLoader = CampaignLoader()
        cancellable = campaignLoader.getCampaigns()
            .receive(on: DispatchQueue.main)
            //Handle Events operator is used for debugging.
            .handleEvents(receiveSubscription:  print("Receive subscription: \($0)") ,
                          receiveOutput:  print("Receive output: \($0)") ,
                          receiveCompletion:  print("Receive completion: \($0)") ,
                          receiveCancel:  print("Receive cancel") ,
                          receiveRequest:  print("Receive request: \($0)") )
            .sink  completion in
                switch completion 
                case .finished:
                    break
                case .failure(let error):
                    print(error)
                
             receiveValue:  campaignResult in
                self.isLoading = false
                guard let campaignsList = campaignResult.content else 
                    return
                
                self.cardCampaigns = campaignsList.map  campaign in
                    return CardCampaign(campaign: campaign)
                
                self.moveToFirstCard()
            
    

【问题讨论】:

您是否尝试过在视图中使用 .transition(.identity) 来提供有线转换。 你的意思是 OrangeActivityIndi​​cator() .frame(width: 40, height: 40) .transition(.identity)。 ?它不起作用 【参考方案1】:

使用关联状态的动画

var body: some View 
    ZStack 
        Circle()
            .trim(from: 0, to: 0.7)
            .stroke(
                AngularGradient(gradient: .init(colors: [orangeColor, orangeColorOpaque]), center: .center), style: style
            )
            .rotationEffect(Angle(degrees: animate ? 360 : 0))
            .animation(Animation.linear(duration: 0.7)
                                .repeatForever(autoreverses: false), 
                       value: animate)                              // << here !!
    .onAppear() 
        self.animate.toggle()
    

【讨论】:

它没有解决问题。这是问题发生的视频:vimeo.com/491538782【参考方案2】:

SwiftUI 3 (ios 15)

使用新的 API

.animation(.default, value: isAppeared)

SwiftUI 1、2

如下所示,当我第一次加载视图时,按钮是动画的。

所以,我使用了 GeometryReader。

当假人的坐标发生变化时,动画被设置为 nil。

RecordView.swift

struct RecordView: View 
    var body: some View 
        Button(action:  ) 
            Color(.red)
                .frame(width: 38, height: 38)
                .cornerRadius(6)
                .padding(34)
                .overlay(Circle().stroke(Color(.red), lineWidth: 8))
        
        .buttonStyle(ButtonStyle1()) // ? customize the button.
    

ButtonStyle.swift

import SwiftUI

struct ButtonStyle1: ButtonStyle 

    // In my case, the @State isn't worked, so I used class.
    @ObservedObject private var data = ButtonStyle1Data()


    func makeBody(configuration: Self.Configuration) -> some View 
        ZStack 
            // Dummy for tracking the view status
            GeometryReader 
                Color.clear
                    .preference(key: FramePreferenceKey.self, value: $0.frame(in: .global))
            
            .frame(width: 0, height: 0)
            .onPreferenceChange(FramePreferenceKey.self)  frame in
                guard !data.isAppeared else  return 

                // ⬇️ This is the key
                data.isLoaded = 0 <= frame.origin.x
            
        
            // Content View
            configuration.label
                .opacity(configuration.isPressed ? 0.5 : 1)
                .scaleEffect(configuration.isPressed ? 0.92 : 1)
                .animation(data.isAppeared ? .easeInOut(duration: 0.18) : nil)
        
    

ButtonStyleData.swift

final class ButtonStyle1Data: ObservableObject 

    @Published var isAppeared = false


FramePreferenceKey.swift

struct FramePreferenceKey: PreferenceKey 

    static var defaultValue: CGRect = .zero

    static func reduce(value: inout CGRect, nextValue: () -> CGRect) 

结果

【讨论】:

以上是关于当视图出现在 SwiftUI 中时禁用动画的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中禁用位置动画?

如何在 SwiftUI 中禁用子视图上的特定状态更改动画

如何阻止视图在 SwiftUI 中出现动画?

当内容滚动到视图中时激活 CSS3 动画

SwiftUI:输入字段键盘动画导致选项卡视图中的其他视图动画

SwiftUI onAppear:动画持续时间被忽略