尝试在SwiftUI中添加一个全屏活动指示器。

我可以在视图协议中使用.overlay(overlay:)函数。

有了这个,我可以做任何视图叠加,但我找不到iOS默认风格UIActivityIndicatorView等效的SwiftUI。

如何使用SwiftUI创建默认样式微调器?

注意:这不是关于在UIKit框架中添加活动指示器。


当前回答

除了Mojatba Hosseini的回答,

我做了一些更新,这样就可以把它放在一个快速包中:

活动指标:

import Foundation
import SwiftUI
import UIKit

public struct ActivityIndicator: UIViewRepresentable {

  public typealias UIView = UIActivityIndicatorView
  public var isAnimating: Bool = true
  public var configuration = { (indicator: UIView) in }

 public init(isAnimating: Bool, configuration: ((UIView) -> Void)? = nil) {
    self.isAnimating = isAnimating
    if let configuration = configuration {
        self.configuration = configuration
    }
 }

 public func makeUIView(context: UIViewRepresentableContext<Self>) -> UIView {
    UIView()
 }

 public func updateUIView(_ uiView: UIView, context: 
    UIViewRepresentableContext<Self>) {
     isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
     configuration(uiView)
}}

扩展:

public extension View where Self == ActivityIndicator {
func configure(_ configuration: @escaping (Self.UIView) -> Void) -> Self {
    Self.init(isAnimating: self.isAnimating, configuration: configuration)
 }
}

其他回答

试试这个:

import SwiftUI

struct LoadingPlaceholder: View {
    var text = "Loading..."
    init(text:String ) {
        self.text = text
    }
    var body: some View {
        VStack(content: {
            ProgressView(self.text)
        })
    }
}

更多关于SwiftUI ProgressView的信息

SwiftUI中的活动指标


import SwiftUI

struct Indicator: View {

    @State var animateTrimPath = false
    @State var rotaeInfinity = false

    var body: some View {

        ZStack {
            Color.black
                .edgesIgnoringSafeArea(.all)
            ZStack {
                Path { path in
                    path.addLines([
                        .init(x: 2, y: 1),
                        .init(x: 1, y: 0),
                        .init(x: 0, y: 1),
                        .init(x: 1, y: 2),
                        .init(x: 3, y: 0),
                        .init(x: 4, y: 1),
                        .init(x: 3, y: 2),
                        .init(x: 2, y: 1)
                    ])
                }
                .trim(from: animateTrimPath ? 1/0.99 : 0, to: animateTrimPath ? 1/0.99 : 1)
                .scale(50, anchor: .topLeading)
                .stroke(Color.yellow, lineWidth: 20)
                .offset(x: 110, y: 350)
                .animation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: true))
                .onAppear() {
                    self.animateTrimPath.toggle()
                }
            }
            .rotationEffect(.degrees(rotaeInfinity ? 0 : -360))
            .scaleEffect(0.3, anchor: .center)
            .animation(Animation.easeInOut(duration: 1.5)
            .repeatForever(autoreverses: false))
            .onAppear(){
                self.rotaeInfinity.toggle()
            }
        }
    }
}

struct Indicator_Previews: PreviewProvider {
    static var previews: some View {
        Indicator()
    }
}

从Xcode 12 beta版(iOS 14)开始,开发者可以使用一个名为ProgressView的新视图,它可以显示确定进度和不确定进度。

它的样式默认为CircularProgressViewStyle,这正是我们正在寻找的。

var body: some View {
    VStack {
        ProgressView()
           // and if you want to be explicit / future-proof...
           // .progressViewStyle(CircularProgressViewStyle())
    }
}

Xcode 11.倍

相当多的视图还没有在SwiftUI中表示,但很容易将它们移植到系统中。 你需要包装UIActivityIndicator并使其UIViewRepresentable。

(关于这方面的更多信息,可以在WWDC 2019的精彩演讲-集成SwiftUI中找到)

struct ActivityIndicator: UIViewRepresentable {

    @Binding var isAnimating: Bool
    let style: UIActivityIndicatorView.Style

    func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
        return UIActivityIndicatorView(style: style)
    }

    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
        isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
    }
}

然后你可以使用它如下-这是一个加载叠加的例子。

注意:我更喜欢使用ZStack,而不是overlay(:_),所以我确切地知道在我的实现中发生了什么。

struct LoadingView<Content>: View where Content: View {

    @Binding var isShowing: Bool
    var content: () -> Content

    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .center) {

                self.content()
                    .disabled(self.isShowing)
                    .blur(radius: self.isShowing ? 3 : 0)

                VStack {
                    Text("Loading...")
                    ActivityIndicator(isAnimating: .constant(true), style: .large)
                }
                .frame(width: geometry.size.width / 2,
                       height: geometry.size.height / 5)
                .background(Color.secondary.colorInvert())
                .foregroundColor(Color.primary)
                .cornerRadius(20)
                .opacity(self.isShowing ? 1 : 0)

            }
        }
    }

}

要测试它,你可以使用下面的示例代码:

struct ContentView: View {

    var body: some View {
        LoadingView(isShowing: .constant(true)) {
            NavigationView {
                List(["1", "2", "3", "4", "5"], id: \.self) { row in
                    Text(row)
                }.navigationBarTitle(Text("A List"), displayMode: .large)
            }
        }
    }

}

结果:

我使用SwiftUI实现了经典的UIKit指示器。 点击这里查看活动指示器的运行情况

struct ActivityIndicator: View {
  @State private var currentIndex: Int = 0

  func incrementIndex() {
    currentIndex += 1
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50), execute: {
      self.incrementIndex()
    })
  }

  var body: some View {
    GeometryReader { (geometry: GeometryProxy) in
      ForEach(0..<12) { index in
        Group {
          Rectangle()
            .cornerRadius(geometry.size.width / 5)
            .frame(width: geometry.size.width / 8, height: geometry.size.height / 3)
            .offset(y: geometry.size.width / 2.25)
            .rotationEffect(.degrees(Double(-360 * index / 12)))
            .opacity(self.setOpacity(for: index))
        }.frame(width: geometry.size.width, height: geometry.size.height)
      }
    }
    .aspectRatio(1, contentMode: .fit)
    .onAppear {
      self.incrementIndex()
    }
  }

  func setOpacity(for index: Int) -> Double {
    let opacityOffset = Double((index + currentIndex - 1) % 11 ) / 12 * 0.9
    return 0.1 + opacityOffset
  }
}

struct ActivityIndicator_Previews: PreviewProvider {
  static var previews: some View {
    ActivityIndicator()
      .frame(width: 50, height: 50)
      .foregroundColor(.blue)
  }
}

// Activity View

struct ActivityIndicator: UIViewRepresentable {

    let style: UIActivityIndicatorView.Style
    @Binding var animate: Bool

    private let spinner: UIActivityIndicatorView = {
        $0.hidesWhenStopped = true
        return $0
    }(UIActivityIndicatorView(style: .medium))

    func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
        spinner.style = style
        return spinner
    }

    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
        animate ? uiView.startAnimating() : uiView.stopAnimating()
    }

    func configure(_ indicator: (UIActivityIndicatorView) -> Void) -> some View {
        indicator(spinner)
        return self
    }   
}

// Usage
struct ContentView: View {

    @State var animate = false

    var body: some View {
            ActivityIndicator(style: .large, animate: $animate)
                .configure {
                    $0.color = .red
            }
            .background(Color.blue)
    }
}