SwiftUI中的活动指示器

Joh*_*tty 24 swift swiftui

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

我可以.overlay(overlay: )View协议中使用功能。

这样,我可以进行任何视图叠加,但是找不到的UIActivityIndicatorView等效于iOS默认样式SwiftUI

如何使用制作默认样式微调框SwiftUI

注意:这与在UIKit框架中添加活动指示器无关。

Mat*_*ini 72

尚未显示很多视图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()
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以按以下方式使用它-这是加载叠加层的示例。

注意:我更喜欢使用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)

            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

要对其进行测试,可以使用以下示例代码:

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)
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

结果:

在此处输入图片说明

在Xcode 11.1上测试

  • @Alfi 在他的代码中写着“isShowing: .constant(true)”。这意味着该指示器始终显示。您需要做的是有一个“@State”变量,当您希望加载指示器出现时(当数据正在加载时),该变量为 true,然后当您希望加载指示器消失时(当数据加载时)将其更改为 false。已完成加载)。例如,如果变量名为“isDataLoading”,您将执行“isShowing: $isDataLoading”,而不是 Matteo 放置的“isShowing: .constant(true)”。 (6认同)
  • @MatteoPacini,您实际上不需要为此绑定,因为它没有在 ActivityIndi​​cator 或 LoadingView 中进行修改。只需一个常规的布尔变量就可以了。当您想要修改视图内的变量并将更改传递回父级时,绑定非常有用。 (4认同)
  • 但如何阻止呢? (2认同)

Moj*_*ini 65

iOS 14

这只是一个简单的看法。

ProgressView()
Run Code Online (Sandbox Code Playgroud)

目前,它是默认的,CircularProgressViewStyle但您可以通过添加以下修饰符来手动设置它的样式:

.progressViewStyle(CircularProgressViewStyle())
Run Code Online (Sandbox Code Playgroud)

此外,样式可以是任何符合 ProgressViewStyle


iOS 13 及以上

UIActivityIndicatorSwiftUI 中完全可定制的标准:(完全作为原生View):

您可以构建和配置它(尽可能多地在原始UIKit):

ActivityIndicator(isAnimating: loading)
    .configure { $0.color = .yellow } // Optional configurations ( bouns)
    .background(Color.blue)
Run Code Online (Sandbox Code Playgroud)

结果


只需实现这个基础struct,你就可以开始了:

struct ActivityIndicator: UIViewRepresentable {
    
    typealias UIView = UIActivityIndicatorView
    var isAnimating: Bool
    fileprivate var configuration = { (indicator: UIView) in }

    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIView { UIView() }
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<Self>) {
        isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
        configuration(uiView)
    }
}
Run Code Online (Sandbox Code Playgroud)

奖金扩展:

使用这个有用的扩展,您可以通过modifier类似其他 SwiftUIview的方式访问配置:

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

经典方法:

您也可以在经典初始化程序中配置视图:

ActivityIndicator(isAnimating: loading) { 
    $0.color = .red
    $0.hidesWhenStopped = false
    //Any other UIActivityIndicatorView property you like
}
Run Code Online (Sandbox Code Playgroud)

这种方法是完全适应的。例如,您可以在此处使用相同的方法查看如何使 TextField 成为第一响应者


Kit*_*Kit 63

如果你想要一个swift-ui 风格的解决方案,那么这就是魔法:

import SwiftUI

struct ActivityIndicator: View {

  @State private var isAnimating: Bool = false

  var body: some View {
    GeometryReader { (geometry: GeometryProxy) in
      ForEach(0..<5) { index in
        Group {
          Circle()
            .frame(width: geometry.size.width / 5, height: geometry.size.height / 5)
            .scaleEffect(!self.isAnimating ? 1 - CGFloat(index) / 5 : 0.2 + CGFloat(index) / 5)
            .offset(y: geometry.size.width / 10 - geometry.size.height / 2)
          }.frame(width: geometry.size.width, height: geometry.size.height)
            .rotationEffect(!self.isAnimating ? .degrees(0) : .degrees(360))
            .animation(Animation
              .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5)
              .repeatForever(autoreverses: false))
        }
      }
    .aspectRatio(1, contentMode: .fit)
    .onAppear {
        self.isAnimating = true
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

简单地使用:

ActivityIndicator()
.frame(width: 50, height: 50)
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!

示例用法:

ActivityIndicator()
.frame(size: CGSize(width: 200, height: 200))
    .foregroundColor(.orange)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

  • 最新 Xcode 和 Swift 中出现错误:“编译器无法在合理的时间内对该表达式进行类型检查;尝试将表达式分解为不同的子表达式” (3认同)
  • 喜欢这个解决方案! (2认同)

Moj*_*ini 17

自定义指标

尽管 Apple 现在从 SwiftUI 2.0 开始支持原生 Activity Indicator,但您可以简单地实现自己的动画。这些都在 SwiftUI 1.0 上得到支持。此外,它工作中的小部件。

弧线

struct Arcs: View {
    @Binding var isAnimating: Bool
    let count: UInt
    let width: CGFloat
    let spacing: CGFloat

    var body: some View {
        GeometryReader { geometry in
            ForEach(0..<Int(count)) { index in
                item(forIndex: index, in: geometry.size)
                    .rotationEffect(isAnimating ? .degrees(360) : .degrees(0))
                    .animation(
                        Animation.default
                            .speed(Double.random(in: 0.2...0.5))
                            .repeatCount(isAnimating ? .max : 1, autoreverses: false)
                    )
            }
        }
        .aspectRatio(contentMode: .fit)
    }

    private func item(forIndex index: Int, in geometrySize: CGSize) -> some View {
        Group { () -> Path in
            var p = Path()
            p.addArc(center: CGPoint(x: geometrySize.width/2, y: geometrySize.height/2),
                     radius: geometrySize.width/2 - width/2 - CGFloat(index) * (width + spacing),
                     startAngle: .degrees(0),
                     endAngle: .degrees(Double(Int.random(in: 120...300))),
                     clockwise: true)
            return p.strokedPath(.init(lineWidth: width))
        }
        .frame(width: geometrySize.width, height: geometrySize.height)
    }
}
Run Code Online (Sandbox Code Playgroud)

不同变体的演示 弧线


酒吧

struct Bars: View {
    @Binding var isAnimating: Bool
    let count: UInt
    let spacing: CGFloat
    let cornerRadius: CGFloat
    let scaleRange: ClosedRange<Double>
    let opacityRange: ClosedRange<Double>

    var body: some View {
        GeometryReader { geometry in
            ForEach(0..<Int(count)) { index in
                item(forIndex: index, in: geometry.size)
            }
        }
        .aspectRatio(contentMode: .fit)
    }

    private var scale: CGFloat { CGFloat(isAnimating ? scaleRange.lowerBound : scaleRange.upperBound) }
    private var opacity: Double { isAnimating ? opacityRange.lowerBound : opacityRange.upperBound }

    private func size(count: UInt, geometry: CGSize) -> CGFloat {
        (geometry.width/CGFloat(count)) - (spacing-2)
    }

    private func item(forIndex index: Int, in geometrySize: CGSize) -> some View {
        RoundedRectangle(cornerRadius: cornerRadius,  style: .continuous)
            .frame(width: size(count: count, geometry: geometrySize), height: geometrySize.height)
            .scaleEffect(x: 1, y: scale, anchor: .center)
            .opacity(opacity)
            .animation(
                Animation
                    .default
                    .repeatCount(isAnimating ? .max : 1, autoreverses: true)
                    .delay(Double(index) / Double(count) / 2)
            )
            .offset(x: CGFloat(index) * (size(count: count, geometry: geometrySize) + spacing))
    }
}
Run Code Online (Sandbox Code Playgroud)

不同变体的演示 酒吧


护目镜

struct Blinking: View {
    @Binding var isAnimating: Bool
    let count: UInt
    let size: CGFloat

    var body: some View {
        GeometryReader { geometry in
            ForEach(0..<Int(count)) { index in
                item(forIndex: index, in: geometry.size)
                    .frame(width: geometry.size.width, height: geometry.size.height)

            }
        }
        .aspectRatio(contentMode: .fit)
    }

    private func item(forIndex index: Int, in geometrySize: CGSize) -> some View {
        let angle = 2 * CGFloat.pi / CGFloat(count) * CGFloat(index)
        let x = (geometrySize.width/2 - size/2) * cos(angle)
        let y = (geometrySize.height/2 - size/2) * sin(angle)
        return Circle()
            .frame(width: size, height: size)
            .scaleEffect(isAnimating ? 0.5 : 1)
            .opacity(isAnimating ? 0.25 : 1)
            .animation(
                Animation
                    .default
                    .repeatCount(isAnimating ? .max : 1, autoreverses: true)
                    .delay(Double(index) / Double(count) / 2)
            )
            .offset(x: x, y: y)
    }
}
Run Code Online (Sandbox Code Playgroud)

不同变体的演示 护目镜


为了防止代码墙,您可以在托管在 git 上的这个 repo 中找到更优雅的指标。

注意,所有这些动画有一个BindingMUST切换到运行。


Arv*_*tel 13

struct ContentView: View {
    
    @State private var isCircleRotating = true
    @State private var animateStart = false
    @State private var animateEnd = true
    
    var body: some View {
        
        ZStack {
            Circle()
                .stroke(lineWidth: 10)
                .fill(Color.init(red: 0.96, green: 0.96, blue: 0.96))
                .frame(width: 150, height: 150)
            
            Circle()
                .trim(from: animateStart ? 1/3 : 1/9, to: animateEnd ? 2/5 : 1)
                .stroke(lineWidth: 10)
                .rotationEffect(.degrees(isCircleRotating ? 360 : 0))
                .frame(width: 150, height: 150)
                .foregroundColor(Color.blue)
                .onAppear() {
                    withAnimation(Animation
                                    .linear(duration: 1)
                                    .repeatForever(autoreverses: false)) {
                        self.isCircleRotating.toggle()
                    }
                    withAnimation(Animation
                                    .linear(duration: 1)
                                    .delay(0.5)
                                    .repeatForever(autoreverses: true)) {
                        self.animateStart.toggle()
                    }
                    withAnimation(Animation
                                    .linear(duration: 1)
                                    .delay(1)
                                    .repeatForever(autoreverses: true)) {
                        self.animateEnd.toggle()
                    }
                }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


Ras*_*tif 5

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()
    }
}

Run Code Online (Sandbox Code Playgroud)

SwiftUI 中的活动指示器