web-dev-qa-db-ja.com

SwiftUIのアクティビティインジケーター

SwiftUIでフルスクリーンアクティビティインジケーターを追加しようとしています。

Viewプロトコルで.overlay(overlay: )関数を使用できます。

これにより、ビューオーバーレイを作成できますが、UIActivityIndicatorViewに相当するiOSのデフォルトスタイルSwiftUIが見つかりません。

SwiftUIを使用してデフォルトスタイルのスピナーを作成するにはどうすればよいですか?

注:これは、UIKitフレームワークにアクティビティインジケーターを追加することではありません。

46
Johnykutty

かなりの数のビューが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)
            }
        }
    }

}
_

結果:

enter image description here

Xcode 11.1でテスト済み

125
Matteo Pacini

SwiftUIの完全にカスタマイズ可能な標準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 modifiersのようにviewを介して構成にアクセスできます。

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

カスタマイズ:

元のUIKitで可能な限り設定できます。

SwiftUIとまったく同じです。

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
}

結果:

Result


この方法は完全に適応可能です。たとえば、同じ方法で TextFieldをファーストレスポンダーにする方法 を確認できます ここ

13

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)

それが役に立てば幸い!

7
KitKit

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

Activity indicator in SwiftUI

2
Rashid Latif

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)
 }
}
0
moyoteg
// 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)
    }
}
0
Manish

ネットワークインジケーターをオンまたはオフにする場合は、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
0