web-dev-qa-db-ja.com

アクティビティインジケーターSwift3の開始と停止

私はSwiftを学習していて、現在ホームタスクに取り組んでいます。ボタンの1つがコレクションビューをリロードする関数を呼び出し、時間がかかります。アクティビティインジケーターを実装して、プロセス。インジケーターを開始し、コレクションビューを読み込み、ボタンアクション内でインジケーターを停止すると、インジケーターが表示されないという問題があります。この場合、インジケーターを実装する正しい方法は何ですか?コードの一部:

let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge)
@IBOutlet weak var onCIFilterView: UICollectionView!
@IBOutlet var cifilterMenu: UIView!

@IBAction func onCIFiltersButton(_ sender: UIButton) {
    if (sender.isSelected) {
        hideCIFilters()
        sender.isSelected = false
    }
    else {
        activityIndicator.startAnimating()
        showCIFilters()  //the menu with UIView collection
        activityIndicator.stopAnimating()
        sender.isSelected = true
    }
}

func showCIFilters () {
    onCIFilterView.reloadData() // reloading collection view
    view.addSubview (cifilterMenu)
    let bottomConstraint = cifilterMenu.bottomAnchor.constraint(equalTo: mainMenu.topAnchor)
    let leftConstraint = cifilterMenu.leftAnchor.constraint(equalTo: view.leftAnchor)
    let rightConstraint = cifilterMenu.rightAnchor.constraint(equalTo: view.rightAnchor)
    let heightConstraint = cifilterMenu.heightAnchor.constraint(equalToConstant: 80)
    NSLayoutConstraint.activate([bottomConstraint, leftConstraint, rightConstraint, heightConstraint])
    view.layoutIfNeeded()
}

func hideCIFilters () {
    currentImage = myImage.image
    cifilterMenu.removeFromSuperview()

}

override func viewDidLoad() {
    super.viewDidLoad()
    //.....
    activityIndicator.center = CGPoint(x: view.bounds.size.width/2, y: view.bounds.size.height/2)
    activityIndicator.color = UIColor.lightGray
    view.addSubview(activityIndicator)
}
8
Ana

直面している問題は、アクティビティインジケーターの開始などのUIの変更は、コードが返され、アプリがイベントループを提供するまで行われないことです。

コードは同期しているため、アクティビティインジケーターが呼び出されることはありません。

アクティビティインジケーターの回転を開始して戻り、時間のかかるアクティビティを遅延後に実行する必要があります。これには_dispatch_after_を使用できます(またはSwift 3)の_DispatchQueue.main.asyncAfter_)。

次に、ボタンをタップした後に2秒間アクティビティインジケーターを実行するIBActionの例を示します。

_@IBAction func handleButton(_ sender: UIButton) {
  activityIndicator.startAnimating()
  sender.isEnabled = false
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {

    // your time-consuming code here
    sleep(2) //Do NOT use sleep on the main thread in real code!!!
    self.activityIndicator.stopAnimating()
    sender.isEnabled = true
  }

}
_

編集:

コードは次のようになります。

_@IBAction func onCIFiltersButton(_ sender: UIButton) {
    if (sender.isSelected) {
        hideCIFilters()
        sender.isSelected = false
    }
    else {
        activityIndicator.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) {
            showCIFilters()  //the menu with UIView collection
            activityIndicator.stopAnimating()
            sender.isSelected = true
        }
    }
}
_

#2を編集:

繰り返しますが、UIの変更は、コードが返され、アプリがイベントループを処理するまで実際には行われません

次のようなコードがある場合:

_activityIndicator.startAnimating()
sleep(2)  //This is in place of your time-consuming synchronous code.
activityIndicator.stopAnimating()
_

次に何が起こるか:

次にコードが戻ったときに、アクティビティインジケーターを開始する呼び出しをキューに入れます。 (つまり、まだ発生していません。)

いくつかの時間のかかるタスクでメインスレッドをブロックします。この間、アクティビティインジケーターは回転していません。

最後に、時間のかかるタスクが完了します。コードがactivityIndicator.stopAnimating()を呼び出します。最後に、コードが返され、アプリがイベントループを処理し、アクティビティインジケーターを開始してすぐに停止するための呼び出しが呼び出されます。その結果、アクティビティインジケータは実際には回転しません。

このコードはそれを別様に行います:

_//Queue up a call to start the activity indicator
activityIndicator.startAnimating()

//Queue up the code in the braces to be called after a delay.
//This call returns immediately, before the code in the braces is run.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
    showCIFilters()  //the menu with UIView collection
    activityIndicator.stopAnimating()
    sender.isSelected = true
}
_

_DispatchQueue.main.asyncAfter_の呼び出しに渡されたクロージャはすぐには実行されないため、その呼び出しはすぐに返されます。

アプリがイベントループのサービスに戻り、アクティビティインジケーターが回転を開始します。その後すぐに、Grand Central Dispatchは、渡されたコードのブロックを呼び出します。これにより、(メインスレッドで)時間のかかる操作の実行が開始され、時間のかかるタスクが完了すると、calがアクティビティインジケーターを停止します。 。

11
Duncan C

ここであなたが試すことができる一つのことです:-私はカスタムロードビューを作成し、ボタンクリックでそれを表示したり隠したりしました。特定のニーズに基づいて、表示と非表示を切り替えることができます。完全に機能するデモについては、githubでこのリポジトリを確認できます- SwiftのNKLoadingView

import UIKit

class NKLoadingView: UIView {

let width: CGFloat = 150
let height: CGFloat = 60
let cornerRadius: CGFloat = 15
let spacing: CGFloat = 20
var label = UILabel()
var fontSize: CGFloat = 20
var isVisible: Bool = false


override init(frame: CGRect) {
    let window = UIApplication.shared.keyWindow!
    let viewFrame = CGRect(x: 50, y: window.frame.height / 2 - height / 2, width: window.frame.width - 2 * 50, height: height)
    super.init(frame: viewFrame)
    setupView()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

func setupView(){
    self.layer.cornerRadius = cornerRadius
    self.layer.backgroundColor = UIColor.black.withAlphaComponent(0.65).cgColor
    let spinner = UIActivityIndicatorView(activityIndicatorStyle: .white)
    spinner.startAnimating()
    spinner.frame = CGRect(x: CGFloat(20), y: CGFloat(10), width: 40, height: 40)

    label = UILabel(frame: CGRect(x: spinner.frame.Origin.x + spinner.frame.width + spacing, y:  spinner.frame.Origin.y, width: self.frame.width - spacing - spinner.frame.width - 20, height: spinner.frame.height))
    label.text  = "Please Wait..."
    label.textColor = UIColor.white
    label.font = UIFont.systemFont(ofSize: fontSize)

    self.addSubview(spinner)
    self.addSubview(label)
}

func stopAnimating(){
    isVisible = !isVisible
    self.removeFromSuperview()
}

func startAnimating(message: String){
    label.text = message
    isVisible = !isVisible
    UIApplication.shared.keyWindow!.addSubview(self);

 }
}

//View Controller code
class ViewController: UIViewController {

var loading: NKLoadingView?
override func viewDidLoad() {
    super.viewDidLoad()

    loading = NKLoadingView()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()

}

@IBAction func startEndAnimation(_ sender: Any) {
    if !loading!.isVisible {
        loading?.startAnimating(message: "Please Wait....")
        (sender as! UIButton).setTitle("Stop Animation", for: .normal)
    }else{
        loading?.stopAnimating()
        (sender as! UIButton).setTitle("Start Animation", for: .normal)
    }
}

}
1
Nikesh Jha

Duncan Cは正解です。時間のかかるアクティビティを非同期で実行する必要があります。ただし、それを実装する最良の方法は、completementHandlerをonCIFilterView.reloadData()関数に追加することです。このようにして、データのリロードが完了したときにアクティビティインジケーターを停止できます。ここにcompletionHandlersに関する優れたチュートリアルがあります: https://grokswift.com/completion-handlers-in-Swift/

また、他にも何か起こっていると思います。あなたがそれを見ることができない場合、それはあなたのCIFilterViewがUIビュースタックでそれの上にあるからです。あなたはそれを前に進める必要があるでしょう。これを行うには、次のようなものを呼び出す必要があります。

view.bringSubview(toFront: activityIndicator)

または

view.superclass?.bringSubview(toFront: activityIndicator)
0