Es wurde versucht, in SwiftUI eine Vollbild-Aktivitätsanzeige hinzuzufügen.
Ich kann die .overlay(overlay: )
Funktion im View
Protokoll verwenden.
Damit kann ich jede Ansicht Overlay machen, aber ich kann nicht die iOS Standardstil finden UIActivityIndicatorView
in gleichwertig SwiftUI
.
Wie kann ich einen Standard-Style-Spinner erstellen SwiftUI
?
HINWEIS: Hier geht es nicht um das Hinzufügen eines Aktivitätsindikators im UIKit-Framework.
Antworten:
Ab Xcode 12 Beta ( iOS 14 ) steht Entwicklern eine neue Ansicht mit dem Namen
ProgressView
zur Verfügung, die sowohl einen bestimmten als auch einen unbestimmten Fortschritt anzeigen kann.Der Stil ist standardmäßig
CircularProgressViewStyle
genau das, wonach wir suchen.var body: some View { VStack { ProgressView() // and if you want to be explicit / future-proof... // .progressViewStyle(CircularProgressViewStyle()) } }
Xcode 11.x.
Nicht wenige Ansichten sind noch nicht dargestellt
SwiftUI
, aber es ist einfach, sie in das System zu portieren. Sie müssen wickelnUIActivityIndicator
und es machenUIViewRepresentable
.(Mehr dazu finden Sie im ausgezeichneten WWDC 2019-Vortrag - Integration von 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() } }
Dann können Sie es wie folgt verwenden - hier ist ein Beispiel für eine Ladeüberlagerung.
Hinweis: Ich bevorzuge die Verwendung
ZStack
, anstattoverlay(:_)
genau zu wissen, was in meiner Implementierung vor sich geht.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) } } } }
Zum Testen können Sie diesen Beispielcode verwenden:
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) } } } }
Ergebnis:
quelle
isShowing: .constant(true)
. Das heißt, der Indikator wird immer angezeigt. Was Sie tun müssen, ist eine@State
Variable zu haben, die wahr ist, wenn der Ladeindikator angezeigt werden soll (wenn die Daten geladen werden), und diese dann in falsch zu ändern, wenn der Ladeindikator verschwinden soll (wenn die Daten geladen sind). . Wenn die VariableisDataLoading
zum Beispiel aufgerufen wird , würden SieisShowing: $isDataLoading
anstelle von Matteo setzenisShowing: .constant(true)
.tintColor
nur bei reinen Swift-UI-Ansichten funktioniert - nicht bei Bridged (UIViewRepresentable
).Wenn Sie eine Lösung im Swift-UI-Stil suchen, dann ist dies die Magie:
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 } } }
Einfach zu bedienen:
ActivityIndicator() .frame(width: 50, height: 50)
Ich hoffe es hilft!
Anwendungsbeispiel:
ActivityIndicator() .frame(size: CGSize(width: 200, height: 200)) .foregroundColor(.orange)
quelle
iOS 14 - Native
Es ist nur eine einfache Ansicht.
ProgressView()
Derzeit ist dies
CircularProgressViewStyle
standardmäßig aktiviert. Sie können den Stil jedoch manuell festlegen, indem Sie den folgenden Modifikator hinzufügen:.progressViewStyle(CircularProgressViewStyle())
Der Stil könnte auch alles sein, was passt
ProgressViewStyle
iOS 13 - Vollständig anpassbarer Standard
UIActivityIndicator
in SwiftUI: (Genau als nativeView
):Sie können es erstellen und konfigurieren (so viel wie im Original
UIKit
):ActivityIndicator(isAnimating: loading) .configure { $0.color = .yellow } // Optional configurations (🎁 bones) .background(Color.blue)
Implementieren
struct
Sie einfach diese Basis und Sie können loslegen: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) } }
🎁 Knochenverlängerung:
Mit dieser kleinen hilfreichen Erweiterung können Sie
modifier
wie bei anderen SwiftUIs auf die Konfiguration zugreifenview
:extension View where Self == ActivityIndicator { func configure(_ configuration: @escaping (Self.UIView)->Void) -> Self { Self.init(isAnimating: self.isAnimating, configuration: configuration) } }
Der klassische Weg:
Sie können die Ansicht auch in einem klassischen Initialisierer konfigurieren:
ActivityIndicator(isAnimating: loading) { $0.color = .red $0.hidesWhenStopped = false //Any other UIActivityIndicatorView property you like }
Diese Methode ist vollständig anpassbar. Zum Beispiel können Sie sehen , wie TextField- werden die Ersthelfer machen mit dem gleichen Verfahren hier
quelle
.progressViewStyle(CircularProgressViewStyle(tint: Color.red))
wird die Farbe ändernBenutzerdefinierte Indikatoren
Obwohl Apple jetzt den nativen Aktivitätsindikator von SwiftUI 2.0 unterstützt, können Sie einfach Ihre eigenen Animationen implementieren. Diese werden alle von SwiftUI 1.0 unterstützt. Es auch ist , arbeitet in Widgets.
Bögen
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) } }
Demo verschiedener Variationen
Riegel
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)) } }
Demo verschiedener Variationen
Scheuklappen
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) } }
Demo verschiedener Variationen
Aus Gründen der Verhinderung Wände des Codes , können Sie elegantere Indikatoren finden diese Repo auf dem git gehostet .
Beachten Sie, dass alle diese Animationen haben ,
Binding
dass MUST Toggle ausgeführt werden soll.quelle
iActivityIndicator(style: .rotatingShapes(count: 10, size: 15))
iActivityIndicator().style(.rotatingShapes(count: 10, size: 15))
dem übrigens? @ pawello2222?count
auf 5 oder weniger einstellen , sieht die Animation gut aus (ähnelt dieser Antwort ). Wenn Sie jedoch den Wertcount
auf 15 setzen, stoppt der führende Punkt nicht am oberen Rand des Kreises. Es beginnt einen weiteren Zyklus, kehrt dann nach oben zurück und startet den Zyklus erneut. Ich bin mir nicht sicher, ob es beabsichtigt ist. Nur auf dem Simulator getestet, Xcode 12.0.1.Ich habe den klassischen UIKit-Indikator mit SwiftUI implementiert. Sehen Sie hier den Aktivitätsindikator in Aktion
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) } }
quelle
Zusätzlich zu Mojatba Hosseinis Antwort ,
Ich habe ein paar Updates vorgenommen, damit dies in ein schnelles Paket gepackt werden kann :
Aktivitätsindikator:
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) }}
Erweiterung:
public extension View where Self == ActivityIndicator { func configure(_ configuration: @escaping (Self.UIView) -> Void) -> Self { Self.init(isAnimating: self.isAnimating, configuration: configuration) } }
quelle
Aktivitätsanzeige in 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() } }
quelle
Versuche dies:
import SwiftUI struct LoadingPlaceholder: View { var text = "Loading..." init(text:String ) { self.text = text } var body: some View { VStack(content: { ProgressView(self.text) }) } }
Weitere Informationen zu SwiftUI ProgressView
quelle
// 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) } }
quelle