在 iOS 中运行长任务时加载“覆盖”

当执行长任务时,在 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()
107510 次浏览

Just create yourself an overlay view, which you add to your parent view and remove it once your task is done, e.g. to add it:

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!)

For removal:

overlay?.removeFromSuperview()

To add on to the answers given, you might run into issues if you are attempting to run the code sometimes. Personally, there was an occasion where showOverlay was not being properly called (because I was trying to segue into a scene, then immediately call this function during viewDidLoad).

If you run into an issue similar to mine, there is one fix to the code and a change in approach I recommend.

FIX: Place both blocks of code as closures to a dispatch_async call, like so:

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

APPROACH: When calling your code, do a dispatch_after call onto the main queue to delay the call by a few milliseconds.

The reasoning? You're simply asking the UI to do too much during viewDidLoad.

If this appendix to the solution helped, I'd be glad.

-Joel Long

P.S. Solution worked for XCode v6.3.2

For anyone late like me, I made some modifications to @Sonrobby code. As i understand, @Sonrobby adds the activity to the overlay on every showOverlay call. And some of the configuration can be passed to the init function, letting only the placement on the showOverlay method.

I also change the overlay's background to black, since my app it is mostly white.

here is the code :

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

The above answers add a loading view but it doesn't block click events on the screen also it does not provides overlay for rest of screen. You can achieve it as follows:

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 and newer

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)

and you can hide it as follows:

dismiss(animated: false, completion: nil)

It will be shown as follows: enter image description here

Swift 3.

I used @Lucho's code in his answer below and I changed the overlay background color to clear and added a spinner color.

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

I've created a protocol for presenting your own view controller as an overlay. The usage is very simple:

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

Result:

Swift OverlayViewController

Source code: https://github.com/agordeev/OverlayViewController

Related article: https://andreygordeev.com/2017/04/18/overlay-view-controller-protocols-swift/

Updated @sonrobby answer, added a background view and orientation handling via resizing mask... this can be used for simple stuffs

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

if you add it to keywindow, it can then go over your nav and tab bars also... something like this

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

Blur background + Activity Indicator, Swift 5 example

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

demo

Use ATKit.

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

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

Code:

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.

Screenshot:

Loading OverlayScreenshot

Swift 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
}

Uses

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

enter image description here

showUniversalLoadingView(true) enter image description here

Remove loader

showUniversalLoadingView(false)

@Ajinkya Patil answer as a reference. Swift 4.0 and newer

This is an Extension Solution to use on all viewController without clashing.

Create a 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)
}
}

call the function inside ViewController anywhere you like. like so:

self.LoadingStart()

and here's how to stop the loading dialog.

self.LoadingStop()

Xamarin.iOS version:

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);

If there's someone looking for a Lottie implementation for loading view here's a working solution I made using @Shourob Datta solution:

import Foundation
import UIKit
import Lottie


public class LogoLoadingAnimation{
    

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 gradienView = GradientBackgroundView(frame: frame)
gradienView.startColor = UIColor(red: 255, green: 255, blue: 255, alpha: 1)
gradienView.endColor = UIColor(red: 238, green: 238, blue: 238, alpha: 1)
gradienView.startColor = UIColor(named: "dark") ?? .blue
gradienView.startColor = UIColor(named: "purpuleGrey") ?? .gray
let loadingAnimationView = AnimationView()
gradienView.addSubview(loadingAnimationView)
loadingAnimationView.translatesAutoresizingMaskIntoConstraints = false
loadingAnimationView.centerXAnchor.constraint(equalTo: gradienView.centerXAnchor).isActive = true
loadingAnimationView.centerYAnchor.constraint(equalTo: gradienView.centerYAnchor).isActive = true
loadingAnimationView.animation = UITraitCollection.current.userInterfaceStyle == .dark ? Animation.named("logoLoadingWhite") : Animation.named("logoLoadingBlue")
loadingAnimationView.backgroundBehavior = .pauseAndRestore
loadingAnimationView.contentMode = .scaleAspectFit
loadingAnimationView.loopMode = .loop
loadingAnimationView.play()
return gradienView
}
    

}