Loading indicator while transitioning from view controllers within tabbarcontroller - ios

I have tabbarcontroller and within that I have 5 tabs I want to display an overlay containing an activity indicator when tab bar is pressed and the user is waiting for the view to load. The view controllers contain functions that get data from servers so I need to show a loading activity indicator. But when I put it in viewWillDisappear or viewWillAppear it does not come. So I am trying to do it in tabbarcontroller itself, when an item is selected. But it is not happening. The activityindicator loads after the view has transitioned to the new view. The loading overlay should be immediate.
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem){
dispatch_async(dispatch_get_global_queue((QOS_CLASS_UTILITY), 0)) {
dispatch_async(dispatch_get_main_queue()) {
LoadingOverlay.shared.showOverlay(self.view)
}
}}
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(white: 0.5, 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()
} } `

Please use the IJProgressView library to show HUD
Usage:
To show the progress view,
IJProgressView.shared.showProgressView(view)
To hide the progress view,
IJProgressView.shared.hideProgressView()

Related

Managing Custom UITabBar View

Following this repo, I created a custom "tabBar" via a separate uiview that sits behind the native uitabbarcontroller tabBar. The custom tabBar has rounded corners and a shadow.
Everything works great except in the instances I push/pop a new uiviewcontroller onto/from the embedded uinavigationcontroller stack, where I hide the tabBar.
My issue is smoothly toggling the custom tabBar to hide/show during these instances.
I've set .isHidden = true/false for the custom tabBar when a uiviewcontroller is pushed/popped, and either the custom tabBar disappears too early or appears too late relative to the native uitabbarcontroller tabBar.
Any guidance would be appreciated.
class TabBarViewController: UITabBarController {
let customTabBarView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = Constants.style.offWhite
view.layer.cornerRadius = 20
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: -8.0)
view.layer.shadowOpacity = 0.12
view.layer.shadowRadius = 10.0
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.isHidden = true
self.tabBar.backgroundColor = .clear
self.tabBar.barStyle = .default
self.tabBar.isTranslucent = true
addCustomTabBarView()
hideTabBarBorder()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
customTabBarView.frame = tabBar.frame
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var newSafeArea = UIEdgeInsets()
newSafeArea.bottom += customTabBarView.bounds.size.height
self.children.forEach({$0.additionalSafeAreaInsets = newSafeArea})
}
//Function invoked when pushing/popping viewControllers onto/from the embedded navigation stack
func toggleCustomTabBarView(shouldHide: Bool) {
self.customTabBarView.isHidden = shouldHide
DispatchQueue.main.async {
UIView.transition(with: self.view, duration: TimeInterval(UINavigationController.hideShowBarDuration), options: .transitionCrossDissolve, animations: {
})
}
}
func addCustomTabBarView() {
customTabBarView.frame = tabBar.frame
view.addSubview(customTabBarView)
view.bringSubviewToFront(self.tabBar)
}
func hideTabBarBorder() {
let tabBar = self.tabBar
tabBar.backgroundImage = UIImage.from(color: .clear)
tabBar.shadowImage = UIImage()
tabBar.clipsToBounds = true
}
}
extension UIImage {
static func from(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
https://stackoverflow.com/a/65285566/9466631
Couldn't find anything as a solve for the dummy view behind the tabBar, but this new solution worked for me that avoids using the dummy view altogether and adds a CAShapeLayer instead.

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)

Adding & Removing A View Overlay in Swift

Following from this question: Loading Screen from any Class in Swift
Issue: The loading overlay view will display but will not hide when hideOverlayView() is called. Oddly however, the overlay disappears after some time (15 to 30 seconds after it appears)
Code: Contained in FirstController.swift
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() {
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()
}
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
overlayView.removeFromSuperview()
}
}
And called in functions in DataManager.swift as:
LoadingOverlay.shared.showOverlay()
Solution:
I was calling on a background thread. As per the answers below, call as:
dispatch_async(dispatch_get_main_queue(), { // This makes the code run on the main thread
LoadingOverlay.shared.hideOverlayView()
})
Swift 2
Are you calling hideOverlayView() from a background thread? If you are, you should make sure it runs on the main thread:
dispatch_async(dispatch_get_main_queue(), { // This makes the code run on the main thread
LoadingOverlay.shared.hideOverlayView()
})
Swift 3+
DispatchQueue.main.async {
LoadingOverlay.shared.hideOverlayView()
}

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

Resources