SwiftUIでフルスクリーンアクティビティインジケーターを追加しようとしています。
View
プロトコルで.overlay(overlay: )
関数を使用できます。
これにより、ビューオーバーレイを作成できますが、UIActivityIndicatorView
に相当するiOSのデフォルトスタイルSwiftUI
が見つかりません。
SwiftUI
を使用してデフォルトスタイルのスピナーを作成するにはどうすればよいですか?
注:これは、UIKitフレームワークにアクティビティインジケーターを追加することではありません。
かなりの数のビューが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()
}
}
_
その後、次のように使用できます。ここでは、読み込みオーバーレイの例を示します。
注:overlay(:_)
ではなくZStack
を使用することをお勧めします。そのため、実装で何が行われているのかを正確に把握しています。
_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)
}
}
}
}
_
結果:
Xcode 11.1でテスト済み
UIActivityIndicator
(完全にネイティブView
として):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)
}
}
この小さな便利な拡張機能を使用すると、他のSwiftUI modifier
sのようにview
を介して構成にアクセスできます。
extension View where Self == ActivityIndicator {
func configure(_ configuration: @escaping (Self.UIView)->Void) -> Self {
Self.init(isAnimating: self.isAnimating, configuration: configuration)
}
}
元のUIKit
で可能な限り設定できます。
ActivityIndicator(isAnimating: loading)
.configure { $0.color = .yellow }
.background(Color.blue)
また、クラシックな初期化子でビューを構成することもできます。
ActivityIndicator(isAnimating: loading) { (indicator: UIActivityIndicatorView) in
indicator.color = .red
indicator.hidesWhenStopped = false
//Any other UIActivityIndicatorView property you like
}
この方法は完全に適応可能です。たとえば、同じ方法で TextFieldをファーストレスポンダーにする方法 を確認できます ここ
Swift-ui-styleソリューションが必要な場合、これが魔法です。
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
}
}
}
単に使用する:
ActivityIndicator()
.frame(width: 50, height: 50)
それが役に立てば幸い!
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()
}
}
Mojatba Hosseiniへの応答:
これをSwiftパッケージに入れられるように、いくつかの更新を行いました。
アクティビティインジケータ:
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)
}
}
// 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)
}
}
ネットワークインジケーターをオンまたはオフにする場合は、Bindingを渡す必要があります。
例:顧客がボタンをクリックするとネットワークインジケーターが表示されるSwiftUI構造体。
struct SettingView: View {
struct SettingView: View {
@State var networkIndicator = false
var body: some View {
ZStack {
NavigationView() {
List() {
Text("Item 1")
Text("Item 2")
}
Button(action: {
self.purchaseManagerViewModel.restorePurchase(networkIndicator: self.$networkIndicator)
}) {
Text("Restore Purchase")
}
}
NetworkIndicatorSwiftView(isAnimating: $networkIndicator, style: .large)
}.disabled(networkIndicator)
.blur(radius: networkIndicator ? 1.0 : 0.0)
}
MatteoのNetworkIndicatorSwiftView。UIKitからネットワークインジケーターを作成できます。
import Foundation
import SwiftUI
import UIKit
struct NetworkIndicatorSwiftView: UIViewRepresentable {
@Binding var isAnimating: Bool
let style: UIActivityIndicatorView.Style
private static var loadingCount = 0
func makeUIView(context: UIViewRepresentableContext<NetworkIndicatorSwiftView>) -> UIActivityIndicatorView {
return UIActivityIndicatorView(style: style)
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<NetworkIndicatorSwiftView>) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
}
オン/オフにしたいときは、Bindingを使用しています。 SwiftUI構造体Binding <>からBindingを渡し、値を変更するには、networkIndicator.wrappedValue = trueを使用しています
func restorePurchase(networkIndicator: Binding<Bool>) {
networkIndicator.wrappedValue = true
// Network Indicator will start
SwiftyStoreKit.restorePurchases(atomically: true) { results in
networkIndicator.wrappedValue = false
// Network Indicator will stop