91

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()
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Sonrobby
  • 3,112
  • 8
  • 30
  • 38
  • 2
    Your request is nearly impossible to fulfill. And you also gain nothing by getting a plain "translation". If you Google for e.g. overlay with Objective-C, you'll find some entries how to do it wrt iOS / OS X. – Sebastian Jan 15 '15 at 10:00

13 Answers13

236

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ajinkya Patil
  • 5,518
  • 4
  • 20
  • 22
  • 4
    dismissViewControllerAnimated(false, completion: nil) does not exist in XCode 8.2. It has been changed to dismiss(animated: false, completion: nil) – Olanrewaju O. Joseph Sep 22 '17 at 22:12
  • 5
    A safer way to dismiss would be: `internal func dismissAlert() { if let vc = self.presentedViewController, vc is UIAlertController { dismiss(animated: false, completion: nil) } }` – Derek Soike Jan 07 '19 at 23:51
  • For Swift 4.0 option, i get this warning: `Warning: Attempt to present on whose view is not in the window hierarchy!` – zaitsman Feb 06 '19 at 02:41
  • 1
    working fine but after dismiss `**navigationController?.pushViewController(adminHomeVC, animated: true)**` is not working.. (swift 4) – Manivel Gopi Nov 25 '19 at 04:58
  • small change `loadingIndicator.style` to `loadingIndicator.activityIndicatorViewStyle` in Swift 4 – Developer Dec 03 '19 at 12:56
  • Another change in iOS 13. 'gray' was deprecated in iOS 13.0: renamed to 'UIActivityIndicatorView.Style.medium' – ShadeToD Mar 20 '20 at 09:12
35

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()
Sebastian
  • 8,046
  • 2
  • 34
  • 58
33

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

Maor
  • 3,340
  • 3
  • 29
  • 38
9

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()
    }
}
Lucho
  • 1,024
  • 1
  • 15
  • 24
7

@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()
Muhammad Asyraf
  • 1,748
  • 15
  • 20
6

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

Uma Madhavi
  • 4,851
  • 5
  • 38
  • 73
Joel Long
  • 61
  • 1
  • 1
6

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

Rupendra
  • 89
  • 1
  • 2
6

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)
Shourob Datta
  • 1,886
  • 22
  • 30
5

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!)
anoop4real
  • 7,598
  • 4
  • 53
  • 56
  • Isn't it safer to use `bgView.frame = view.bounds`? Since `view.frame.origin` might be different from (0,0). – Nicolas Miari Mar 20 '18 at 08:50
  • Overlay's objective is to cover the view which is supplied to it....it doesnt have to care where the origin is.... in-case you need to cover the whole screen apply it to keywindow. let me know if you have faced an issue so that I can relook at it – anoop4real Mar 22 '18 at 03:44
  • What I was saying is that a view's `frame` property is always in the parent view's coordinate system. If you want the overlay to fully cover the view it is applied to, you must make sure the frame is as big as the target (parent) view, but also that it starts at (0,0). otherwise, if the parent view is not at the origin ot _its_ parent view, neither will overlay. – Nicolas Miari Mar 22 '18 at 04:06
  • Ok, let me try some samples... and accordingly will update the code – anoop4real Mar 22 '18 at 05:41
  • No problem. It should work most of the time; it's just a minor technicality. – Nicolas Miari Mar 22 '18 at 05:50
3

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()
    }
}
markhorrocks
  • 1,199
  • 19
  • 82
  • 151
3

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/

Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
1

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);
Divyesh
  • 2,085
  • 20
  • 35
0

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
    }
    
}
Matan
  • 685
  • 7
  • 18