removeFromSuperview() doesn't work - ios

I have the following class to add and remove an activity indicator view:
import Foundation
import UIKit
class ViewControllerUtils {
var container: UIView = UIView()
var loadingView: UIView = UIView()
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
func showActivityIndicator(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColorFromHex(rgbValue: 0xffffff, alpha: 0.3)
loadingView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColorFromHex(rgbValue: 0x444444, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 40, height: 40);
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
activityIndicator.center = CGPoint(x: loadingView.frame.size.width / 2, y: loadingView.frame.size.height / 2);
loadingView.addSubview(activityIndicator)
container.addSubview(loadingView)
uiView.addSubview(container)
activityIndicator.startAnimating()
}
func hideActivityIndicator(uiView: UIView) {
print("hideActivityIndicator called")
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
loadingView.removeFromSuperview()
container.removeFromSuperview()
}
func UIColorFromHex(rgbValue:UInt32, alpha:Double=1.0)->UIColor {
let red = CGFloat((rgbValue & 0xFF0000) >> 16)/256.0
let green = CGFloat((rgbValue & 0xFF00) >> 8)/256.0
let blue = CGFloat(rgbValue & 0xFF)/256.0
return UIColor(red:red, green:green, blue:blue, alpha:CGFloat(alpha))
}
}
I call the function in tableview and in view as well in the following way:
override func viewDidLoad() {
super.viewDidLoad()
ViewControllerUtils().showActivityIndicator(uiView: self.tableView)
invitedEvents()
}
After loading the data I try to remove the activity indicator, but it is not removing.
func invitedEvents() {
//calling firebase
DataService.ds.REF_USER_CURRENT.observe(.value, with: { (snap) in
ViewControllerUtils().hideActivityIndicator(uiView: self.tableView)
})
}
When the tableview is loading is printing that hideActivityIndicator was called, but it not removing from the view.
I call the functions in a view as well and the same result: showActivityIndicator is working, hideActivityIndicator doesn't work.
I call the functions in a view with the following parameters:
ViewControllerUtils().showActivityIndicator(uiView: self.view)
ViewControllerUtils().hideActivityIndicator(uiView: self.view)
Does anybody an idea what I'm missing?

Everytime if you write ViewControllerUtils(), you are creating the new instance of ViewControllerUtils class and hence once you show the activity indicator using one instance , you have to get the same instance of activity indicator to remove it with which you have added it. So you can make the ViewControllerUtils as singleton class and hence use it.
class ViewControllerUtils {
static let shared = ViewControllerUtils()
private override init() {
}
}
Now call your methods like this ..
ViewControllerUtils.shared.showActivityIndicator(uiView: self.tableView)
ViewControllerUtils.shared.hideActivityIndicator(uiView: self.tableView)

Related

How to isolate the loader screen code in swift?

I have created this custom code to create a loader in my project. The problem is that I have to copy and paste this function in all my classes. Is there any way I can declare this code in one global functions class and just use them wherever I want to use by calling.
import NVActivityIndicatorView
let activityIndicatorView = NVActivityIndicatorView(frame: CGRect(x: 80, y: 80, width: 60, height:60), type: .ballTrianglePath, color: .black)
let blurView = UIView()
func startLoader(){
DispatchQueue.main.async
{
self.blurView.isHidden = false
self.blurView.frame = self.view.frame
self.blurView.backgroundColor = UIColor.gray.withAlphaComponent(0.5)
self.view.addSubview(self.blurView)
self.activityIndicatorView.center = self.blurView.center
self.view.addSubview(self.activityIndicatorView)
self.activityIndicatorView.startAnimating()
}
}
func stopLoader(){
DispatchQueue.main.async {
self.activityIndicatorView.stopAnimating()
self.blurView.isHidden = true
}
}
First Create function to get activityIndicatorView and blurView. Because you don't need to repeat code in everywhere.And easily change entire loader views in one place
Class Helper {
static func getLoaderViews()->(UIView,NVActivityIndicatorView){
let activityIndicatorView = NVActivityIndicatorView(frame: CGRect(x: 80, y: 80, width: 60, height:60), type: .ballTrianglePath, color: .black)
let blurView = UIView()
// create your components,customise and return
return (blurView,activityIndicatorView)
}
}
now create a UIViewController Extension to start or stop loader
extension UIViewController {
func addLoaderToView(view:UIView,blurView:UIView ,activityIndicatorView:NVActivityIndicatorView) {
blurView.isHidden = false
blurView.frame = view.frame
blurView.backgroundColor = UIColor.gray.withAlphaComponent(0.5)
view.addSubview(blurView)
activityIndicatorView.center = blurView.center
view.addSubview(activityIndicatorView)
activityIndicatorView.startAnimating()
}
func removeLoader(activityIndicatorView:NVActivityIndicatorView,blurView:UIView) {
activityIndicatorView.stopAnimating()
blurView.isHidden = true
}
}
Now you can easily add or remove loader in any UIViewController
let (blurView,activityIndicatorView) = Helper.getLoaderViews() //In your class scope
//where you want to start
addLoaderToView(view:self.view,blurView:blurView ,activityIndicatorView:activityIndicatorView)
//where you want to stop
removeLoader(activityIndicatorView:activityIndicatorView,blurView:blurView)
You can create UIView extension
extension UIView {
func showActivity() {
let activityIndicatorView = NVActivityIndicatorView(frame: CGRect(x: 80, y: 80, width: 60, height:60), type: .ballTrianglePath, color: .black)
let blurView = UIView()
DispatchQueue.main.async {
self.blurView.isHidden = false
self.blurView.frame = self.frame
self.blurView.backgroundColor = UIColor.gray.withAlphaComponent(0.5)
self.addSubview(self.blurView)
self.activityIndicatorView.center = self.blurView.center
self.addSubview(self.activityIndicatorView)
self.activityIndicatorView.startAnimating()
}
}
}

Activity Indicator Will Not Stop or Hide

I am using a custom activity indicator (http://swiftonic.com/customized-activity-indicator-in-swift-3/) for my IOS webview application. It is working but the issue I am having is that the indicator does not hide or disappear when the page is loading. The animation just continues. Here is my code
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet var myWebView: WKWebView!
//#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// In order to Show activityIndicatorView
ViewControllerUtils().showActivityIndicator(uiView: self.view)
// In order to hide activityIndicatorView
ViewControllerUtils().hideActivityIndicator(uiView: self.view)
let url = URL(string: "https://oasis.geneseo.edu")!
myWebView.navigationDelegate = self as? WKNavigationDelegate
myWebView.load(URLRequest(url: url))
}
According to the directions ViewControllerUtils().showActivityIndicator(uiView: self.view) should show the indicator and ViewControllerUtils().showActivityIndicator(uiView: self.view). Here is the code from the seperate file that I store the UIActivityIndicatorView in.
import Foundation
import UIKit
class ViewControllerUtils {
var container: UIView = UIView()
var loadingView: UIView = UIView()
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
/*
Show customized activity indicator,
actually add activity indicator to passing view
#param uiView - add activity indicator to this view
*/
func showActivityIndicator(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColorFromHex(rgbValue: 0xffffff, alpha: 0.3)
loadingView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColorFromHex(rgbValue: 0x444444, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
activityIndicator.center = CGPoint(x: loadingView.frame.size.width / 2, y: loadingView.frame.size.height / 2);
loadingView.addSubview(activityIndicator)
container.addSubview(loadingView)
uiView.addSubview(container)
activityIndicator.startAnimating()
}
/*
Hide activity indicator
Actually remove activity indicator from its super view
#param uiView - remove activity indicator from this view
*/
func hideActivityIndicator(uiView: UIView) {
activityIndicator.stopAnimating()
container.removeFromSuperview()
}
/*
Define UIColor from hex value
#param rgbValue - hex color value
#param alpha - transparency level
*/
func UIColorFromHex(rgbValue:UInt32, alpha:Double=1.0)->UIColor {
let red = CGFloat((rgbValue & 0xFF0000) >> 16)/256.0
let green = CGFloat((rgbValue & 0xFF00) >> 8)/256.0
let blue = CGFloat(rgbValue & 0xFF)/256.0
return UIColor(red:red, green:green, blue:blue, alpha:CGFloat(alpha))
}
}
Thanks in advance.
In your code you are creating 2 different ViewControllerUtils objects. You are showing the first one, and hiding the second one.
You can solve it as follows:
let indicator = ViewControllerUtils()
indicator.showActivityIndicator(uiView: self.view)
indicator.hideActivityIndicator(uiView: self.view)

Swift: starting and stopping the animation of an activity indicator inside a custom class

I want to put an activity indicator inside a custom class so I can start/stop it from any view controller.
The below code works when starting the activity indicator but not stopping, how can I do this?
static func activityIndicatorFunction(view: UIView, targetVC: UIViewController, animate: Bool) {
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
if animate == false {
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
} else {
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 = targetVC.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents()
}
}
An example of starting the activity indicator, the animate parameter would be false if I wanted to stop it.
Utils.activityIndicatorFunction(view: view, targetVC: self, animate: true)
This is a perfect candidate for protocol extensions. I recently did this myself.
First create the protocol in a file, say ActivityIndicatorPresenter.swift
/// Used for ViewControllers that need to present an activity indicator when loading data.
public protocol ActivityIndicatorPresenter {
/// The activity indicator
var activityIndicator: UIActivityIndicatorView { get }
/// Show the activity indicator in the view
func showActivityIndicator()
/// Hide the activity indicator in the view
func hideActivityIndicator()
}
Create a protocol extension (in the same file...or a different one)
public extension ActivityIndicatorPresenter where Self: UIViewController {
func showActivityIndicator() {
DispatchQueue.main.async {
self.activityIndicator.activityIndicatorViewStyle = .whiteLarge
self.activityIndicator.frame = CGRect(x: 0, y: 0, width: 80, height: 80) //or whatever size you would like
self.activityIndicator.center = CGPoint(x: self.view.bounds.size.width / 2, y: self.view.bounds.height / 2)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
}
}
}
Any view controller can then conform to the protocol
class MyViewController: UIViewController, ActivityIndicatorPresenter {
/// Make sure to add the activity indicator
var activityIndicator = UIActivityIndicatorView()
//Suppose you want to load some data from the network in this view controller
override func viewDidLoad() {
super.viewDidLoad()
showActivityIndicator() //Wow you can use this here!!!
getSomeData { data in
//do stuff with data
self.hideActivityIndicator()
}
}
What I suggest is to implement them as two separated methods, also to add them into extension of UIViewController, as follows:
UIViewController extension:
extension UIViewController {
func showActivityIndicator() {
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 = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = .WhiteLarge
activityIndicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents()
activityIndicator.tag = 100 // 100 for example
// before adding it, you need to check if it is already has been added:
for subview in view.subviews {
if subview.tag == 100 {
print("already added")
return
}
}
view.addSubview(activityIndicator)
}
func hideActivityIndicator() {
let activityIndicator = view.viewWithTag(100) as? UIActivityIndicatorView
activityIndicator?.stopAnimating()
// I think you forgot to remove it?
activityIndicator?.removeFromSuperview()
//UIApplication.shared.endIgnoringInteractionEvents()
}
}
I assume that you want to always show/hide the activityIndicator it to ViewController.view, if it is not, you might need let it an activityIndicator of UIView instead of UIViewController.
Usage:
For example, consider that you have two IBActions, the first shows the activity indicator and the other one hides it, they should be like:
#IBAction func show(sender: AnyObject) {
showActivityIndicator()
}
#IBAction func hide(sender: AnyObject) {
hideActivityIndicator()
}
I found it best just to set a 'tag' to the activity indicator and then reference to that when stopping the animation, using one function to hide and one to show as Ahmad suggested. Ahmad F's answer and Guillermo's answer seem good as well.
Show/hide functions inside my Utils.swift file:
static func showActivityIndicator(view: UIView, targetVC: UIViewController) {
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
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 = targetVC.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
activityIndicator.tag = 1
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
static func hideActivityIndicator(view: UIView) {
let activityIndicator = view.viewWithTag(1) as? UIActivityIndicatorView
activityIndicator?.stopAnimating()
activityIndicator?.removeFromSuperview()
UIApplication.shared.endIgnoringInteractionEvents()
}
Calling show function:
Utils.showActivityIndicator(view: view, targetVC: self)
Calling hide function:
Utils.hideActivityIndicator(view: view)

Loading Screen from any Class in Swift

I am trying to implement a loading screen in swift, that I can reuse from any class in the project (I have a few classes that will handle 'long running' activities)
The public function is, from another answer:
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 = MyGlobalVariables.UICOLORGREEN
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()
}
}
Which works fine in FirstViewController.swift (Where it is located) by using:
LoadingOverlay.shared.showOverlay(self.view)
My question is, how do I use it in XYZ.swift? As self.view may not be referring to a view generated by that class. Is it possible to call and find the current super view, then add the loading screen on top of it?
You need to add your overlay to your main UIWindow class. In that case you will overlay all views.
public func showOverlay() {
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate,
let window = appDelegate.window {
overlayView.frame = CGRectMake(0, 0, 80, 80)
overlayView.center = CGPointMake(window.frame.width / 2.0, window.frame.height / 2.0)
overlayView.backgroundColor = MyGlobalVariables.UICOLORGREEN
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)
window.addSubview(overlayView)
activityIndicator.startAnimating()
}
}
PS: you can move most of overlayView customization code to init function, because you don't need to update backgroundColor or other things every time on display.
PPS: There is great libraries for loading overlay like
https://github.com/TransitApp/SVProgressHUD
https://github.com/jdg/MBProgressHUD
and others. Just google it

How to show activity indicator while tableView loads?

When I switch between my tabs it loads some seconds and I want to know that my data is loading. For that I decided to add an activity indicator.
I wrote a little function:
func showActivityIndicator() {
dispatch_async(dispatch_get_main_queue()) {
self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)
self.loadingView.addSubview(self.spinner)
self.view.addSubview(self.loadingView)
self.spinner.startAnimating()
}
}
that will show my indicator. And try to use it when I tapped from my infoController to button:
#IBAction func goToMainFromInfo(sender: AnyObject) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
I show it before segue perform and hide after. It doesn't help me. When I did try to use sync:
#IBAction func goToMainFromInfo(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue()) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
But it doesn't help too. When I press to tab it opacity becomes 0.5 and I wait while it loading. But I do not see my activity indicator.
What is the problem?
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
indicator.startAnimating()
indicator.backgroundColor = UIColor.whiteColor()
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Note: Avoid the calling of start and stop at the same time. Just give some conditions.
SWIFT : 4.2
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
activityIndicator()
indicator.startAnimating()
indicator.backgroundColor = .white
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Swift 3.0
// UIView Extension
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
setAssociatedObject(activityIndicatorView, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// NSObject Extension
public extension NSObject {
func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
if let valueAsAnyObject = value {
objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
}
}
func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
return nil
}
return valueAsType
}
}
start animation
tableView.activityIndicatorView.startAnimating()
stop animation
tableView.activityIndicatorView.stopAnimating()
You can find more code in Magic
Swift 2+
class ViewController: UITableViewController {
weak var activityIndicatorView: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
tableView.backgroundView = activityIndicatorView
self.activityIndicatorView = activityIndicatorView
activityIndicatorView.startAnimating()
}
...
}
I use two extension methods to add an UIActivityIndicatorView as the backgroundView of the tableview.
extension UITableView {
func showActivityIndicator() {
DispatchQueue.main.async {
let activityView = UIActivityIndicatorView(style: .medium)
self.backgroundView = activityView
activityView.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.backgroundView = nil
}
}
}
You can show/hide it like this.
tableView.showActivityIndicator()
tableView.hideActivityIndicator()
This code can help you :)
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
SWIFT
Place this below your class:
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
Place this in your loadView():
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
In my case, I am requesting json objects through a func request, so I placed this at the end of that function to remove the activity indicator once the data loads:
self.indicator.stopAnimating()
self.indicator.hidesWhenStopped = true
Another approach, In my code I added an extension for UITableView (Swift 2.3) :
extension UITableView {
func activityIndicator(center: CGPoint = CGPointZero, loadingInProgress: Bool) {
let tag = 12093
if loadingInProgress {
var indicator = UIActivityIndicatorView()
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.tag = tag
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
indicator.color = //Your color here
indicator.center = center
indicator.startAnimating()
indicator.hidesWhenStopped = true
self.superview?.addSubview(indicator)
}else {
if let indicator = self.superview?.viewWithTag(tag) as? UIActivityIndicatorView { {
indicator.stopAnimating()
indicator.removeFromSuperview()
}
}
}
}
Note : My tableview is embedded in a UIView (superview)
Update Swift 4.2:
1.call the activityIndicator function on viewDidLoad
eg:
var indicator = UIActivityIndicatorView()
override func viewDidLoad() {
//Initializing the Activity Indicator
activityIndicator()
//Starting the Activity Indicator
indicator.startAnimating()
//Call Your WebService or API
callAPI()
}
Here is the Code For Adding ActivityIndicator as Subview
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.whiteLarge
indicator.color = .red
indicator.center = self.view.center
self.view.addSubview(indicator)
}
2. Do UI related Operations or API Call and stop activity indicator
func callAPI() {
YourModelClass.fetchResult(someParams,
completionHandler: { (response) in
//Do necessary UIUpdates
//And stop Activity Indicator
self.indicator.stopAnimating()
})
}
func setupSpinner(){
spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height:40))
spinner.color = UIColor(Colors.Accent)
self.spinner.center = CGPoint(x:UIScreen.main.bounds.size.width / 2, y:UIScreen.main.bounds.size.height / 2)
self.view.addSubview(spinner)
spinner.hidesWhenStopped = true
}
Using "lazy var". It's better than function
fileprivate lazy var activityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicatorView.hidesWhenStopped = true
// Set Center
var center = self.view.center
if let navigationBarFrame = self.navigationController?.navigationBar.frame {
center.y -= (navigationBarFrame.origin.y + navigationBarFrame.size.height)
}
activityIndicatorView.center = center
self.view.addSubview(activityIndicatorView)
return activityIndicatorView
}()
Just start the spinner anywhere
like this
func requestData() {
// Request something
activityIndicatorView.startAnimating()
}
#brocolli's answer for swift 4.0. You have to use objc_ before getting or setting associated objects. According to the documentation, The APIs of getting and setting the associated object in Swift are:
func objc_getAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>
) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>,
value: AnyObject!,
policy: objc_AssociationPolicy)
Implementation:
import UIKit
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = objc_getAssociatedObject(self, &ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, activityIndicatorView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
In order to place the UIActivityIndicator in foreground, even over the eventual UITableViewController separators, I have adopted this solution.
I have add the UIActivityIndicator programmatically, and add it as a subview of my UINavigationController
var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Code
// .... omissis
// Set activity indicator
activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.color = UIColor.darkGray
activityIndicator.center = tableView.center
activityIndicator.hidesWhenStopped = true
activityIndicator.stopAnimating()
self.navigationController?.view.addSubview(activityIndicator)
}
I have just start & stop it when needed (in my case):
func animateActivityIndicator(_ sender: Any ) {
guard let vc = sender as? UIViewController else { return }
if let v = vc as? MyTableViewController {
if v.activityIndicator.isAnimating {
v.activityIndicator.stopAnimating()
} else {
v.activityIndicator.startAnimating()
}
}
// Others UIViewController or UITableViewController follows...
// all of them exhibits an activityIndicator variable
// implemented programmatically or with the storyboard
}
PS. My environment is Xcode 10.0, iOS 12.0

Resources