Mencoba menambahkan indikator aktivitas layar penuh di SwiftUI.
Saya dapat menggunakan .overlay(overlay: )
fungsi dalam View
Protokol.
Dengan ini, saya dapat membuat hamparan tampilan apa pun, tetapi saya tidak dapat menemukan gaya default iOS yang UIActivityIndicatorView
setara SwiftUI
.
Bagaimana cara membuat pemintal gaya default SwiftUI
?
CATATAN: Ini bukan tentang menambahkan indikator aktivitas dalam kerangka UIKit.
Jawaban:
Pada Xcode 12 beta ( iOS 14 ), pandangan baru yang disebut
ProgressView
adalah tersedia untuk pengembang , dan yang dapat menampilkan baik determinate dan kemajuan tak tentu.Gayanya ditetapkan secara default
CircularProgressViewStyle
, persis seperti yang kita cari.var body: some View { VStack { ProgressView() // and if you want to be explicit / future-proof... // .progressViewStyle(CircularProgressViewStyle()) } }
Xcode 11.x
Cukup banyak tampilan yang belum terwakili
SwiftUI
, tetapi mudah untuk memasukkannya ke dalam sistem. Anda perlu membungkusUIActivityIndicator
dan membuatnyaUIViewRepresentable
.(Lebih lanjut tentang ini dapat ditemukan dalam pembicaraan WWDC 2019 yang luar biasa - Mengintegrasikan 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() } }
Kemudian Anda dapat menggunakannya sebagai berikut - berikut adalah contoh overlay pemuatan.
Catatan: Saya lebih suka menggunakan
ZStack
, daripadaoverlay(:_)
, jadi saya tahu persis apa yang terjadi dalam implementasi saya.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) } } } }
Untuk mengujinya, Anda dapat menggunakan kode contoh ini:
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) } } } }
Hasil:
sumber
isShowing: .constant(true)
. Artinya, indikator selalu muncul. Yang perlu Anda lakukan adalah memiliki@State
variabel yang benar ketika Anda ingin indikator pemuatan muncul (ketika data sedang dimuat), dan kemudian mengubahnya menjadi salah ketika Anda ingin indikator pemuatan menghilang (ketika data selesai dimuat) . Jika variabel dipanggilisDataLoading
misalnya, Anda akan melakukanisShowing: $isDataLoading
alih - alih tempat Matteo meletakkanisShowing: .constant(true)
.tintColor
hanya berfungsi pada tampilan Swift UI murni - bukan pada yang dijembatani (UIViewRepresentable
).Jika Anda menginginkan solusi gaya ui-cepat , inilah keajaibannya:
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 } } }
Cukup untuk digunakan:
ActivityIndicator() .frame(width: 50, height: 50)
Semoga membantu!
Contoh Penggunaan:
ActivityIndicator() .frame(size: CGSize(width: 200, height: 200)) .foregroundColor(.orange)
sumber
iOS 14 - Asli
itu hanya tampilan sederhana.
ProgressView()
Saat ini, defaultnya
CircularProgressViewStyle
tetapi Anda dapat mengatur gayanya secara manual dengan menambahkan modifer berikut:.progressViewStyle(CircularProgressViewStyle())
Juga, gayanya bisa apa saja yang sesuai
ProgressViewStyle
iOS 13 - Standar yang dapat disesuaikan sepenuhnya
UIActivityIndicator
di SwiftUI: (Persis seperti aslinyaView
):Anda dapat membangun dan mengkonfigurasinya (sebanyak yang Anda bisa dalam bahasa aslinya
UIKit
):ActivityIndicator(isAnimating: loading) .configure { $0.color = .yellow } // Optional configurations (🎁 bones) .background(Color.blue)
Cukup terapkan basis ini
struct
dan Anda akan baik-baik saja: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) } }
🎁 Ekstensi Tulang:
Dengan ekstensi kecil yang bermanfaat ini, Anda dapat mengakses konfigurasi melalui
modifier
SwiftUI seperti lainnyaview
:extension View where Self == ActivityIndicator { func configure(_ configuration: @escaping (Self.UIView)->Void) -> Self { Self.init(isAnimating: self.isAnimating, configuration: configuration) } }
Cara klasik:
Anda juga dapat mengonfigurasi tampilan di penginisialisasi klasik:
ActivityIndicator(isAnimating: loading) { $0.color = .red $0.hidesWhenStopped = false //Any other UIActivityIndicatorView property you like }
Metode ini dapat disesuaikan sepenuhnya. Misalnya, Anda dapat melihat Cara membuat TextField menjadi responden pertama dengan metode yang sama di sini
sumber
.progressViewStyle(CircularProgressViewStyle(tint: Color.red))
akan mengubah warnaIndikator Kustom
Meskipun Apple mendukung Indikator Aktivitas asli sekarang dari SwiftUI 2.0, Anda cukup mengimplementasikan animasi Anda sendiri. Ini semua didukung di SwiftUI 1.0. Juga yang bekerja di widget.
Busur
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 berbagai variasi
Bar
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 berbagai variasi
Penutup mata
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 berbagai variasi
Demi mencegah dinding kode , Anda dapat menemukan indikator yang lebih elegan di repo ini yang dihosting di git .
Perhatikan bahwa semua animasi ini memiliki tombol
Binding
yang HARUS dijalankan.sumber
iActivityIndicator(style: .rotatingShapes(count: 10, size: 15))
iActivityIndicator().style(.rotatingShapes(count: 10, size: 15))
by the way? @ purnama04?count
ke 5 atau kurang, animasi terlihat bagus (terlihat mirip dengan jawaban ini ). Namun, jika Anda menyetelcount
ke 15, titik awal tidak berhenti di atas lingkaran. Ini mulai melakukan siklus lain, kemudian kembali ke atas dan kemudian memulai siklus lagi. Saya tidak yakin apakah itu dimaksudkan. Diuji hanya di simulator, Xcode 12.0.1.Saya menerapkan indikator UIKit klasik menggunakan SwiftUI. Lihat indikator aktivitas beraksi di sini
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) } }
sumber
Selain Mojatba Hosseini 's jawaban ,
Saya telah membuat beberapa pembaruan sehingga ini dapat dimasukkan ke dalam paket yang cepat :
Indikator aktivitas:
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) }}
Perpanjangan:
public extension View where Self == ActivityIndicator { func configure(_ configuration: @escaping (Self.UIView) -> Void) -> Self { Self.init(isAnimating: self.isAnimating, configuration: configuration) } }
sumber
Indikator aktivitas di 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() } }
sumber
Coba ini:
import SwiftUI struct LoadingPlaceholder: View { var text = "Loading..." init(text:String ) { self.text = text } var body: some View { VStack(content: { ProgressView(self.text) }) } }
Informasi lebih lanjut tentang di SwiftUI ProgressView
sumber
// 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) } }
sumber