1

I have a few requests to my API that, while it's making the request, I want to show a fullscreen modal with a spinner in the middle that goes overtop the current scene.

I have set up a scene in my storyboard and I've given that scene a controller:

class LoadingViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

In my other controller, I have the following:

class SignInViewController: UIViewController {

    // MARK: Properties

    @IBOutlet weak var submitButton: UIButton!

    // MARK: Actions

    @IBAction func onSubmit(_ sender: Any) {
        // How do I show LoadingViewController as a modal here?
        // Later, how do I hide it?
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

As I commented above, when the user clicks on the Submit button, I want to show the fullscreen modal over that controller. Then, when the API returns data, I want to hide the modal.

How can I show and hide the LoadingViewController as a modal overtop the SignInViewController?

user9815123
  • 63
  • 1
  • 10
  • 1
    Design your own LoadingViewController, present it while with luck because as far as i know, iOS will make the background black of the background so you won't be able to make the LoadingViewController background transparent. Take a reference of the modal you're showing, and dismiss it while network call recieved. – Ratul Sharker Nov 17 '18 at 16:32
  • Have a look at: https://stackoverflow.com/questions/27669699/transparent-background-for-modally-presented-viewcontroller – messeb Nov 17 '18 at 16:58

2 Answers2

0

Here is a solution that will create a simple loader view on top of the presented view,

First, we need to create a class that is responsible for adding/removing an activityIndicator

class AppLoaderView : UIView{

    fileprivate
    lazy var circularProgressIndicator : UIActivityIndicatorView = {
        let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
        activityIndicator.center = self.center
        activityIndicator.style = .gray
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        activityIndicator.hidesWhenStopped = true
        self.addSubview(activityIndicator)
        return activityIndicator
    }()


    func showSpinning() {
        setupConstraints()
        circularProgressIndicator.startAnimating()
    }

    private
    func setupConstraints() {
        let xCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerX,
                                                   relatedBy: .equal, toItem: circularProgressIndicator,
                                                   attribute: .centerX,
                                                   multiplier: 1,
                                                   constant: 0)
        self.addConstraint(xCenterConstraint)

        let yCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerY,
                                                   relatedBy: .equal, toItem: circularProgressIndicator,
                                                   attribute: .centerY,
                                                   multiplier: 1,
                                                   constant: 0)
        self.addConstraint(yCenterConstraint)
    }


    func removeLoading() {
        circularProgressIndicator.stopAnimating()
    }

}

Now we will create an extension on viewController, that has two methods one for showing the LoaderView and one for removing it,

extension UIViewController{

    private var loaderView : AppLoaderView?{

        if let view =  self.view.subviews.first(where: { $0 is AppLoaderView}) as? AppLoaderView { return view }
        let view = AppLoaderView(frame: self.view.frame)
        view.backgroundColor = .white
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        view.isHidden = true
        return view
    }


    func showLoaderView(){
        if let view = loaderView  {

            self.view.addSubview(view)
            self.view.bringSubviewToFront(view)
            view.isHidden = false
            view.clipsToBounds = true
            view.layoutIfNeeded()
            view.showSpinning()

        }

    }

    func removeLoaderView(){
        if let view = loaderView{
            view.removeLoading()
            view.removeFromSuperview()
            self.view.layoutIfNeeded()
        }
    }
}

That's it, now with your example you can call self.showLoaderView() when the button clicked, and once the API call returns you will call self.removeLoaderView().

Add these somewhere in your project, and on any viewController you can call these methods to show/hide your loaderView.

mojtaba al moussawi
  • 1,360
  • 1
  • 13
  • 21
0

To use a transparent background with the presented view controller, set modalPresentationStyle = .custom in the initializer of your LoadingViewController class.

To use a custom transition with the presented view controller, set the transitioningDelegate property of your LoadingViewController class and implement the UIViewControllerAnimatedTransitioning and UIViewControllerAnimatedTransitioning.

For example:

class LoadingViewController: UIViewController {

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

            transitioningDelegate = self        // Required for a custom transition.
            modalPresentationStyle = .custom    // Required for a transparent background.
    }

    // Other setup in your class...
}

extension LoadingViewController: UIViewControllerTransitioningDelegate {

    public func animationController(forPresented presented: UIViewController,
                                    presenting: UIViewController,
                                    source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }

    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }
}

extension LoadingViewController: UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        if isBeingPresented {

            let toView = transitionContext.view(forKey: .to)!
            toView.alpha = 0

            transitionContext.containerView.addSubview(toView)
            UIView.animate(withDuration: 0.25,
                           animations: {
                toView.alpha = 1
            }, completion: { (_) in
                transitionContext.completeTransition(true)
            })

        } else {

            let fromView = transitionContext.view(forKey: .from)!

            UIView.animate(withDuration: 0.25,
                           animations: {
                fromView.alpha = 0
            }, completion: { (_) in
                fromView.removeFromSuperview()
                transitionContext.completeTransition(true)
            })
        }
    }
}

Then you can just present and dismiss your custom LoadingViewController normally:

@IBAction func onSubmit(_ sender: Any) {

    // Present loading view controller.
    self.present(LoadingViewController(), animated: true)

    // Dismiss later in the callback of your API call...
    // API.request {
    //     self.dismiss(animated: true)
    // }
}

This can achieve the effect you're looking for:
custom transition demo

ryanecrist
  • 328
  • 2
  • 9