【问题标题】:Disable animation when a view appears in SwiftUI当视图出现在 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)。 ?它不起作用

标签: swift swiftui swiftui-list


【解决方案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()
    }
}

【讨论】:

【解决方案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) {}
}

结果

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-11
    相关资源
    最近更新 更多