【问题标题】:Loading an "overlay" when running long tasks in iOS在 iOS 中运行长任务时加载“覆盖”
【发布时间】:2024-05-21 23:50:02
【问题描述】:

在 Swift IOS 应用程序中执行长任务时加载覆盖的示例是什么。从远程服务器加载数据的示例。 我用谷歌搜索但没有找到任何答案。

更新:

感谢@Sebastian Dressler,这是一种简单的方法。我更新了我的代码,它运行得很酷

public class LoadingOverlay{

var overlayView = UIView()
var activityIndicator = UIActivityIndicatorView()

class var shared: LoadingOverlay {
    struct Static {
        static let instance: LoadingOverlay = LoadingOverlay()
    }
    return Static.instance
}

    public func showOverlay(view: UIView) {

        overlayView.frame = CGRectMake(0, 0, 80, 80)
        overlayView.center = view.center
        overlayView.backgroundColor = UIColor(hex: 0x444444, alpha: 0.7)
        overlayView.clipsToBounds = true
        overlayView.layer.cornerRadius = 10

        activityIndicator.frame = CGRectMake(0, 0, 40, 40)
        activityIndicator.activityIndicatorViewStyle = .WhiteLarge
        activityIndicator.center = CGPointMake(overlayView.bounds.width / 2, overlayView.bounds.height / 2)

        overlayView.addSubview(activityIndicator)
        view.addSubview(overlayView)

        activityIndicator.startAnimating()
    }

    public func hideOverlayView() {
        activityIndicator.stopAnimating()
        overlayView.removeFromSuperview()
    }
}

让我们使用:

LoadingOverlay.shared.showOverlay(self.view)
//To to long tasks
LoadingOverlay.shared.hideOverlayView()

【问题讨论】:

  • 您的请求几乎无法满足。而且,通过简单的“翻译”,您也一无所获。如果你谷歌例如与 Objective-C 叠加,你会发现一些条目如何在 iOS / OS X 上做到这一点。

标签: ios swift uiview overlay


【解决方案1】:

以上答案添加了一个加载视图,但它不会阻止屏幕上的点击事件,也不会为屏幕的其余部分提供覆盖。您可以按如下方式实现:

let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .Alert)

alert.view.tintColor = UIColor.blackColor()
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.startAnimating();

alert.view.addSubview(loadingIndicator)
presentViewController(alert, animated: true, completion: nil)

Swift 3.0

let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)

let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();

alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)

Swift 4.0 及更新版本

let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)

let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();

alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)

你可以按如下方式隐藏它:

dismiss(animated: false, completion: nil)

如下所示:

【讨论】:

  • dismissViewControllerAnimated(false, completion: nil) 在 XCode 8.2 中不存在。已改为dismiss(animated: false, completion: nil)
  • 更安全的解雇方式是:internal func dismissAlert() { if let vc = self.presentedViewController, vc is UIAlertController { dismiss(animated: false, completion: nil) } }
  • 对于 Swift 4.0 选项,我收到此警告:Warning: Attempt to present <UIAlertController: 0x7fc4f106e600> on <MyProj.MyViewController: 0x7fc4f3222710> whose view is not in the window hierarchy!
  • 工作正常,但解雇后**navigationController?.pushViewController(adminHomeVC, animated: true)** 不工作.. (swift 4)
  • 在 Swift 4 中将 loadingIndicator.style 更改为 loadingIndicator.activityIndicatorViewStyle
【解决方案2】:

只需为自己创建一个覆盖视图,将其添加到父视图并在任务完成后将其删除,例如添加它:

var overlay : UIView? // This should be a class variable

[ ... ]

overlay = UIView(frame: view.frame)
overlay!.backgroundColor = UIColor.blackColor()
overlay!.alpha = 0.8

view.addSubview(overlay!)

移除:

overlay?.removeFromSuperview()

【讨论】:

  • 感谢@Sebastian Dressler,我刚刚更新了我的问题。你能把上面的代码翻译成 Swift 吗?
  • 不,我不这么认为。我在这里提供的可以作为基本框架。剩下的自己加在上面。
  • 截至今天(2015 年 11 月),似乎需要覆盖!.removeFromSuperView() [注意感叹号而不是问号]
  • 可以在overlay...上确认!
  • “removeFromSuperview()”中的小“v”
【解决方案3】:

模糊背景 + 活动指示器,Swift 5 示例

extension UIView {
    func showBlurLoader() {
        let blurLoader = BlurLoader(frame: frame)
        self.addSubview(blurLoader)
    }

    func removeBluerLoader() {
        if let blurLoader = subviews.first(where: { $0 is BlurLoader }) {
            blurLoader.removeFromSuperview()
        }
    }
}


class BlurLoader: UIView {

    var blurEffectView: UIVisualEffectView?

    override init(frame: CGRect) {
        let blurEffect = UIBlurEffect(style: .dark)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = frame
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.blurEffectView = blurEffectView
        super.init(frame: frame)
        addSubview(blurEffectView)
        addLoader()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func addLoader() {
        guard let blurEffectView = blurEffectView else { return }
        let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
        activityIndicator.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
        blurEffectView.contentView.addSubview(activityIndicator)
        activityIndicator.center = blurEffectView.contentView.center
        activityIndicator.startAnimating()
    }
}

【讨论】:

  • 这个怎么用?
  • 啊谢谢,我真的明白了,在 uiviewcontroller 中添加为 self.view.addSubview(overlay)
  • 很好的解决方案!
  • 这个答案非常有帮助 - 如果其他使用 Xamarin.iOS 的人需要这样做,我已经用 C# here 重写了它。
  • 您可以在 viewController 中使用它来显示 self.view.showBlurLoader() 和删除 self.view.removeBluerLoader()
【解决方案4】:

对于像我这样迟到的人,我对@Sonrobby 代码进行了一些修改。据我了解,@Sonrobby 将活动添加到每个showOverlay 调用的覆盖中。并且一些配置可以传递给init函数,只让showOverlay方法上的放置。

我还将叠加层的背景更改为黑色,因为我的应用大部分是白色的。

这里是代码:

public class LoadingOverlay{

    var overlayView : UIView!
    var activityIndicator : UIActivityIndicatorView!

    class var shared: LoadingOverlay {
        struct Static {
            static let instance: LoadingOverlay = LoadingOverlay()
        }
        return Static.instance
    }

    init(){
        self.overlayView = UIView()
        self.activityIndicator = UIActivityIndicatorView()

        overlayView.frame = CGRectMake(0, 0, 80, 80)
        overlayView.backgroundColor = UIColor(white: 0, alpha: 0.7)
        overlayView.clipsToBounds = true
        overlayView.layer.cornerRadius = 10
        overlayView.layer.zPosition = 1

        activityIndicator.frame = CGRectMake(0, 0, 40, 40)
        activityIndicator.center = CGPointMake(overlayView.bounds.width / 2, overlayView.bounds.height / 2)
        activityIndicator.activityIndicatorViewStyle = .WhiteLarge
        overlayView.addSubview(activityIndicator)
    }

    public func showOverlay(view: UIView) {
        overlayView.center = view.center
        view.addSubview(overlayView)
        activityIndicator.startAnimating()
    }

    public func hideOverlayView() {
        activityIndicator.stopAnimating()
        overlayView.removeFromSuperview()
    }
}

【讨论】:

    【解决方案5】:

    要补充给出的答案,如果您有时尝试运行代码,可能会遇到问题。就个人而言,有一次没有正确调用 showOverlay(因为我试图进入场景,然后在 viewDidLoad 期间立即调用此函数)。

    如果您遇到与我类似的问题,我建议对代码进行一个修复并更改方法。

    修复:将两个代码块作为 dispatch_async 调用的闭包放置,如下所示:

    dispatch_async(dispatch_get_main_queue(),
     { //code });
    

    方法:调用您的代码时,对主队列执行 dispatch_after 调用,以将调用延迟几毫秒。

    推理?您只是在 viewDidLoad 期间要求 UI 做太多事情。

    如果解决方案的这个附录有帮助,我会很高兴。

    -乔尔·朗

    附:解决方案适用于 XCode v6.3.2

    【讨论】:

      【解决方案6】:

      使用 ATKit。

      参考: https://aurvan.github.io/atkit-ios-release/index.html

      ATProgressOverlay 类 https://aurvan.github.io/atkit-ios-release/helpbook/Classes/ATProgressOverlay.html

      代码:

      import ATKit
      
      ATProgressOverlay.sharedInstance.show() // Does not show network activity indicator on status bar.
      
      ATProgressOverlay.sharedInstance.show(isNetworkActivity: true) // Shows network activity indicator on status bar.
      

      截图:

      【讨论】:

        【解决方案7】:

        更新了@sonrobby 的答案,通过调整掩码大小添加了背景视图和方向处理...这可用于简单的东西

        public class LoadingOverlay{
        
            var overlayView = UIView()
            var activityIndicator = UIActivityIndicatorView()
            var bgView = UIView()
        
            class var shared: LoadingOverlay {
                struct Static {
                    static let instance: LoadingOverlay = LoadingOverlay()
                }
                return Static.instance
            }
        
            public func showOverlay(view: UIView) {
        
                bgView.frame = view.frame
                bgView.backgroundColor = UIColor.gray
                bgView.addSubview(overlayView)
                bgView.autoresizingMask = [.flexibleLeftMargin,.flexibleTopMargin,.flexibleRightMargin,.flexibleBottomMargin,.flexibleHeight, .flexibleWidth]
                overlayView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
                overlayView.center = view.center
                overlayView.autoresizingMask = [.flexibleLeftMargin,.flexibleTopMargin,.flexibleRightMargin,.flexibleBottomMargin]
                overlayView.backgroundColor = UIColor.black
                overlayView.clipsToBounds = true
                overlayView.layer.cornerRadius = 10
        
                activityIndicator.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
                activityIndicator.activityIndicatorViewStyle = .whiteLarge
                activityIndicator.center = CGPoint(x: overlayView.bounds.width / 2, y: overlayView.bounds.height / 2)
        
                overlayView.addSubview(activityIndicator)
                view.addSubview(bgView)
                self.activityIndicator.startAnimating()
        
            }
        
            public func hideOverlayView() {
                activityIndicator.stopAnimating()
                bgView.removeFromSuperview()
            }
        }
        

        如果你将它添加到 keywindow,它就可以越过你的导航栏和标签栏...类似这样的东西

        LoadingOverlay.shared.showOverlay(view: UIApplication.shared.keyWindow!)
        

        【讨论】:

        • 使用bgView.frame = view.bounds不是更安全吗?因为view.frame.origin 可能与 (0,0) 不同。
        • Overlay 的目标是覆盖提供给它的视图......它不必关心原点在哪里......如果您需要覆盖整个屏幕,请将其应用于键窗口。如果您遇到问题,请告诉我,以便我重新查看
        • 我的意思是视图的frame 属性总是在父视图的坐标系中。如果您希望叠加层完全覆盖它所应用的视图,则必须确保框架与目标(父)视图一样大,而且它从 (0,0) 开始。否则,如果父视图不在父视图的原点,则两者都不会覆盖。
        • 好的,让我尝试一些示例......并相应地更新代码
        • 没问题。它应该大部分时间都可以工作;这只是一个小技术问题。
        【解决方案8】:

        斯威夫特 5

        class func showUniversalLoadingView(_ show: Bool, loadingText : String = "") {
            let existingView = UIApplication.shared.windows[0].viewWithTag(1200)
            if show {
                if existingView != nil {
                    return
                }
                let loadingView = self.makeLoadingView(withFrame: UIScreen.main.bounds, loadingText: loadingText)
                loadingView?.tag = 1200
                UIApplication.shared.windows[0].addSubview(loadingView!)
            } else {
                existingView?.removeFromSuperview()
            }
        
        }
        
        
        
         class func makeLoadingView(withFrame frame: CGRect, loadingText text: String?) -> UIView? {
            let loadingView = UIView(frame: frame)
            loadingView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
            let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
            //activityIndicator.backgroundColor = UIColor(red:0.16, green:0.17, blue:0.21, alpha:1)
            activityIndicator.layer.cornerRadius = 6
            activityIndicator.center = loadingView.center
            activityIndicator.hidesWhenStopped = true
            activityIndicator.style = .white
            activityIndicator.startAnimating()
            activityIndicator.tag = 100 // 100 for example
        
            loadingView.addSubview(activityIndicator)
            if !text!.isEmpty {
                let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 30))
                let cpoint = CGPoint(x: activityIndicator.frame.origin.x + activityIndicator.frame.size.width / 2, y: activityIndicator.frame.origin.y + 80)
                lbl.center = cpoint
                lbl.textColor = UIColor.white
                lbl.textAlignment = .center
                lbl.text = text
                lbl.tag = 1234
                loadingView.addSubview(lbl)
            }
            return loadingView
        }
        

        用途

        showUniversalLoadingView(true, loadingText: "Downloading Data.......")
        

        showUniversalLoadingView(true)

        移除加载器

        showUniversalLoadingView(false)
        

        【讨论】:

          【解决方案9】:

          @Ajinkya Patil 回答作为参考。 Swift 4.0 及更新版本

          这是一个扩展解决方案,可以在所有 viewController 上使用而不会发生冲突。

          创建一个LoadingDialog+ViewContoller.swift

          import UIKit
          
          struct ProgressDialog {
              static var alert = UIAlertController()
              static var progressView = UIProgressView()
              static var progressPoint : Float = 0{
                  didSet{
                      if(progressPoint == 1){
                          ProgressDialog.alert.dismiss(animated: true, completion: nil)
                      }
                  }
              }
          }
          extension UIViewController{
             func LoadingStart(){
                  ProgressDialog.alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
              
              let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
              loadingIndicator.hidesWhenStopped = true
              loadingIndicator.style = UIActivityIndicatorView.Style.gray
              loadingIndicator.startAnimating();
          
              ProgressDialog.alert.view.addSubview(loadingIndicator)
              present(ProgressDialog.alert, animated: true, completion: nil)
            }
          
            func LoadingStop(){
              ProgressDialog.alert.dismiss(animated: true, completion: nil)
            }
          }
          

          在任何你喜欢的地方调用 ViewController 中的函数。像这样:

          self.LoadingStart()
          

          这是停止加载对话框的方法。

          self.LoadingStop()
          

          【讨论】:

            【解决方案10】:

            斯威夫特 3.

            我在下面的答案中使用了@Lucho 的代码,我将覆盖背景颜色更改为清除并添加了微调器颜色。

            public class LoadingOverlay {
            
                var overlayView : UIView!
                var activityIndicator : UIActivityIndicatorView!
            
                class var shared: LoadingOverlay {
                    struct Static {
                        static let instance: LoadingOverlay = LoadingOverlay()
                    }
                    return Static.instance
                }
            
                init(){
                    self.overlayView = UIView()
                    self.activityIndicator = UIActivityIndicatorView()
            
                    overlayView.frame = CGRect(0, 0, 80, 80)
                    overlayView.backgroundColor = .clear
                    overlayView.clipsToBounds = true
                    overlayView.layer.cornerRadius = 10
                    overlayView.layer.zPosition = 1
            
                    activityIndicator.frame = CGRect(0, 0, 40, 40)
                    activityIndicator.center = CGPoint(overlayView.bounds.width / 2, overlayView.bounds.height / 2)
                    activityIndicator.activityIndicatorViewStyle = .whiteLarge
                    activityIndicator.color = .gray
                    overlayView.addSubview(activityIndicator)
                }
            
                public func showOverlay(view: UIView) {
                    overlayView.center = view.center
                    view.addSubview(overlayView)
                    activityIndicator.startAnimating()
                }
            
                public func hideOverlayView() {
                    activityIndicator.stopAnimating()
                    overlayView.removeFromSuperview()
                }
            }
            

            【讨论】:

              【解决方案11】:

              我创建了一个协议,用于将您自己的视图控制器呈现为覆盖。用法很简单:

              class ViewController: UIViewController, OverlayHost {
                  @IBAction func showOverlayButtonPressed() {
                      showOverlay(type: YourOverlayViewController.self, 
                          fromStoryboardWithName: "Main")
                  }
              }
              

              结果:

              源码:https://github.com/agordeev/OverlayViewController

              相关文章:https://andreygordeev.com/2017/04/18/overlay-view-controller-protocols-swift/

              【讨论】:

                【解决方案12】:

                Xamarin.iOS 版本:

                var alert = UIAlertController.Create(string.Empty, "Please wait...", UIAlertControllerStyle.Alert);
                var alertIndicatorView = new UIActivityIndicatorView();
                alertIndicatorView.Frame = new CGRect(x: 10, y: 5, width: 50, height: 50);
                alertIndicatorView.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray;
                alertIndicatorView.HidesWhenStopped = true;
                alertIndicatorView.StartAnimating();
                alert.Add(alertIndicatorView);
                controller.PresentViewController(alert, true, null);
                

                【讨论】:

                  最近更新 更多