Loading an "overlay" when running long tasks in iOS

IosSwiftUiviewOverlay

Ios Problem Overview


What is example for loading overlay in Swift IOS application when do a long tasks. Example for loading data from remote server. I googled but not found any answer.

Updated:

Thanks for @Sebastian Dressler this is simple way. I updated my code and it run cool

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

let using:

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

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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

Solution 3 - Ios

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

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

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

Solution 7 - Ios

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

Solution 8 - Ios

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)

Solution 9 - Ios

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

Solution 10 - Ios

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

Solution 11 - Ios

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/

Solution 12 - Ios

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

Solution 13 - Ios

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSonrobbyView Question on Stackoverflow
Solution 1 - IosAjinkya PatilView Answer on Stackoverflow
Solution 2 - IosSebastian DresslerView Answer on Stackoverflow
Solution 3 - IosMaorView Answer on Stackoverflow
Solution 4 - IosLuchoView Answer on Stackoverflow
Solution 5 - IosMuhammad AsyrafView Answer on Stackoverflow
Solution 6 - IosJoel LongView Answer on Stackoverflow
Solution 7 - IosRupendraView Answer on Stackoverflow
Solution 8 - IosShourob DattaView Answer on Stackoverflow
Solution 9 - Iosanoop4realView Answer on Stackoverflow
Solution 10 - IosmarkhorrocksView Answer on Stackoverflow
Solution 11 - IosAndrey GordeevView Answer on Stackoverflow
Solution 12 - IosDivyesh_08View Answer on Stackoverflow
Solution 13 - IosMatanView Answer on Stackoverflow