我正在学习Swift,现在正在完成我的家庭任务。 我的一个按钮调用一个函数,我重新加载一个集合视图,这需要时间。我正在尝试实施一个活动指示器来显示该过程。 问题是,如果我启动指示器,加载集合视图,然后在按钮操作中停止指示器,则指示器不会出现。 在这种情况下,实施指标的正确方法是什么? 这是代码片段:
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)
}
答案 0 :(得分:11)
您面临的问题是,在您的代码返回并且应用程序为事件循环提供服务之前,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
}
}
}
同样,在您的代码RETURNS和应用程序为事件循环提供服务
之前,实际上不会发生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来停止活动指示器
答案 1 :(得分:1)
这是您可以尝试的一件事: -我创建了一个自定义加载视图,并在单击按钮时将其显示和隐藏。您可以根据自己的特定需求显示和隐藏。有关完整的演示示例,您可以在github-NKLoadingView in swift
上看到此仓库。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)
}
}
}
答案 2 :(得分:0)
Duncan C是对的,你应该异步进行耗时的活动。但是,实现它的最佳方法是将completionHandler添加到onCIFilterView.reloadData()
函数中。这样,您可以在数据重新加载完成时停止活动指示器。这里有一个关于completionHandlers的好教程:https://grokswift.com/completion-handlers-in-swift/
另外,我认为这里还有其他的东西。如果你看不到它,那是因为你的CIFilterView位于UI视图堆栈的顶部。你需要把它推向前进。要做到这一点,你需要调用类似的东西:
view.bringSubview(toFront: activityIndicator)
或
view.superclass?.bringSubview(toFront: activityIndicator)