I am not using UIViewController or UITableViewController, would rather use only a UITableView to display contents fetched from url. Contents are mostly images so it takes a while to load.
You can solved this by following steps :
You need to prepare two function :
1) For start activity indicator and 2) for stop indicator
you can start activity indicator before calling API function or code whatever you use.
Then you can call stop indicator function after getting API response or before reloading tableview.
Include the following into a class and then call accordingly
// Usage:
//
// # Show Overlay
// LoadingOverlay.shared.showOverlay(self.navigationController?.view)
//
// # Hide Overlay
// LoadingOverlay.shared.hideOverlayView()
import UIKit
import Foundation
public class LoadingOverlay: NSObject {
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 = UIView(frame: UIScreen.mainScreen().bounds)
overlayView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.WhiteLarge)
activityIndicator.center = overlayView.center
overlayView.addSubview(activityIndicator)
activityIndicator.startAnimating()
view.addSubview(overlayView)
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
overlayView.removeFromSuperview()
}
}
If you want to wait until all pictures are loaded, take a look at SVProgressHUD.
Then call before content load
[SVProgressHUD show];
and when the process is finished
[SVProgressHUD dismiss];
But this approach has significant drawback - it'll block your UI until all data is retrieved. So instead I'd recommend to add UIActivityIndicator to UITableViewCell and show loading indicator for each image.
Related
Is it possible to put a loading animation over the VNDocumentViewController? As in, when the user presses the Save button, is there a way for me to somehow indicate that the Vision is processing the image and hasn't frozen? Right now, in my app, there is a long pause between the user pressing Save and the actual image being processed.Here is an example from another post of what I'm trying to create
Here is one example of adding a loading indicator using UIActivityIndicatorView().
startAnimating() to start the animation and stopAnimation() to stop the animation.
iOS - Display a progress indicator at the center of the screen rather than the view
guard let topWindow = UIApplication.shared.windows.last else {return}
let overlayView = UIView(frame: topWindow.bounds)
overlayView.backgroundColor = UIColor.clear
topWindow.addSubview(overlayView)
let hudView = UIActivityIndicatorView()
hudView.bounds = CGRect(x: 0, y: 0, width: 20, height: 20)
overlayView.addSubview(hudView)
hudView.center = overlayView.center
hudView.startAnimating()
Alternatively, you could look into using Cocoapod MBProgressHud
https://cocoapods.org/pods/MBProgressHUD
There's a way you can extend a class in Swift that captures this problem well. The idea is you want a UIActivityIndicator in your VNDocumentCameraViewController. But we'd like that to be a part of every version of this we use. We could simply embed the DocumentVC's view into our current view and superimpose a UIActivityIndicator above it in the view stack, but that's pretty hacky. Here's a quick way we can extend any class and solve this problem
import VisionKit
import UIKit
extension VNDocumentCameraViewController {
private struct LoadingContainer {
static var loadingIndicator = UIActivityIndicatorView()
}
var loadingIndicator: UIActivityIndicatorView {
return LoadingContainer.loadingIndicator
}
func animateLoadingIndicator() {
if loadingIndicator.superview == nil {
view.addSubview(loadingIndicator)
//Setup your constraints through your favorite method
//This constrains it to the very center of the controller
loadingIndicator.frame = CGRect(
x: view.frame.width / 2.0,
y: view.frame.height / 2.0,
width: 20,
height: 20)
//Setup additional state like color/etc here
loadingIndicator.color = .white
}
loadingIndicator.startAnimating()
}
func stopAnimatingLoadingIndicator() {
loadingIndicator.stopAnimating()
}
}
The place we can call these functions are in the delegate methods for VNDocumentCameraViewController that you implement in your presenting ViewController:
func documentCameraViewController(
_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan
) {
controller.animateLoadingIndicator()
}
Writing an app with some network activity I find myself writing the same code for multiple view controllers over and over just to display an activity indicator.
class SomeViewController: UIViewController {
let indicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
override func viewDidLoad() {
super.viewDidLoad()
// customize indicator
self.indicator.layer.cornerRadius = 10
self.indicator.center = self.view.center
self.indicator.hidesWhenStopped = true
self.indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
self.indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
}
// MARK: - Acitivity Indicator
func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
}
}
func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
}
}
}
Within the same SomeViewController class I can then use it as follows:
#IBAction func startHeavyNetworkStuffButtonPressed(_ sender: UIButton) {
startIndicatingActivity()
doHeavyNetworkStuff() { success in
// heavy networking has finished
stopIndicatingActivity()
}
}
This works fine as long as I only need to display the activity indicator in a single view controller. However, it's tedious to do it over and over for every view controller that needs this functionality. As I hate writing the same code over and over, I am in search of a solution where I can simply call
startIndicatingActivity() (and stopIndicatingActivity() respectively) in any view controller.
0th idea - Extension
My obvious first thought was to write an extension for the UIViewController class. As I need to store an instance of the UIActivityIndicatorView, however, I got the Extensions may not contain stored properties error.
1st idea - Subclassing
Next up: subclassing UIViewController. This would work fine for any simple view controller. However, if I needed the same functionality for a MyCustomTableViewController, I would again need to first subclass from UITableViewController and copy/paste existing code.
My question
Is there an elegant way to call startIndicatingActivity() / stopIndicatingActivity() in any view controller while avoiding to copy/paste large amounts of code? I'm assuming an elegant solution would involve an extension, protocol, or some kind of multiple-inheritance approach.
This SO thread is the solution! Turns out there is a way to solve this with an extension and simulated properties, after all.
Posting the complete solution for the interested reader:
Extending UIViewController
extension UIViewController {
// see ObjectAssociation<T> class below
private static let association = ObjectAssociation<UIActivityIndicatorView>()
var indicator: UIActivityIndicatorView {
set { UIViewController.association[self] = newValue }
get {
if let indicator = UIViewController.association[self] {
return indicator
} else {
UIViewController.association[self] = UIActivityIndicatorView.customIndicator(at: self.view.center)
return UIViewController.association[self]!
}
}
}
// MARK: - Acitivity Indicator
public func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents() // if desired
}
}
public func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
//UIApplication.shared.endIgnoringInteractionEvents()
}
}
}
Borrowing code from said SO thread
// source: https://stackoverflow.com/questions/25426780/how-to-have-stored-properties-in-swift-the-same-way-i-had-on-objective-c
public final class ObjectAssociation<T: AnyObject> {
private let policy: objc_AssociationPolicy
/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
self.policy = policy
}
/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {
get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}
For the sake of completeness
I also extended the UIActivityIndicatorView class with a static function with my customizations.
extension UIActivityIndicatorView {
public static func customIndicator(at center: CGPoint) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
indicator.layer.cornerRadius = 10
indicator.center = center
indicator.hidesWhenStopped = true
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
return indicator
}
}
Sample show full loading in any View
extension UIView{
/**
ShowLoader: loading view ..
- parameter Color: ActivityIndicator and view loading color .
*/
func showLoader(_ color:UIColor?){
let LoaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
LoaderView.tag = -888754
LoaderView.backgroundColor = color
let Loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
Loader.center = LoaderView.center
Loader.activityIndicatorViewStyle = .whiteLarge
Loader.color = Color.primaryColor
Loader.startAnimating()
LoaderView.addSubview(Loader)
self.addSubview(LoaderView)
}
/**
dismissLoader: hidden loading view ..
*/
func dismissLoader(){
self.viewWithTag(-888754)?.removeFromSuperview()
}
}
call this func
in UIViewController
self.view.showLoader(nil) //you can set background color nil or any color
// dismiss
self.view.dismissLoader()
I prefer this method because you can use it any view in button ,
tableview , cell ...etc
I have been trying to create a quick method that will allow me to replace any UIView with a spinner right before a process starts and do the re-show the view once my process is done. For some reason, the UIView does disappear but the spinner never shows. These are the methods in question:
func showLoader(view: UIView, controller: UIViewController) -> UIActivityIndicatorView {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.color = AC_BLUE
spinner.center = view.center
spinner.startAnimating()
view.alpha = 0
controller.view.addSubview(spinner)
return spinner
}
func hideLoader(view: UIView, spinner: UIActivityIndicatorView) {
view.alpha = 1
spinner.removeFromSuperview()
}
..which I call with something like this:
let spinner = Extensions().showLoader(view: signInBtn, controller: self)
APICalls().getUser(email: usernameTextField.text!, pass: passwordTextField.text!) { success, user in
//..CODE..//
Extensions().hideLoader(view: self.signInBtn, spinner: spinner)
}
Also, I tried centering on the main VC view, and that does work. So I'm guessing it must be related to the view's position.
Thanks!
Try setting this before adding the spinner to the controllers view (instead of the old spinner.center = view.center):
spinner.center = view.superview!.convert(view.center, to: controller.view)
You need to convert the view's center to the coordinates of the controller.view.
I was wondering if it's possible to not show the UIActivityIndicatorView when the loading of your data is fast enough (for example less than a second).
In my current app, my collection view gets reloaded at every new search, but sometimes the search is very fast, and the indicator view blinks and disappears instantly, giving an ugly overall feel to the app.
So basically my question is: can I "disable" the UIActivityIndicatorView when my loading is fast enough?
Using this class, I do not show activity indicator if callback is happening less than 1 sec.
final class DelayableActivityIndicatorView: UIActivityIndicatorView {
static let kDefaultDelay: TimeInterval = 1
private var inProgress = false
func startAnimatingWithDelay(_ delay: TimeInterval = kDefaultDelay) {
inProgress = true
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
if self.inProgress == true {
self.startAnimating()
}
self.inProgress = false
}
}
func stopAnimatingWithDelay() {
inProgress = false
stopAnimating()
}
}
To use it in the view controller,
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
var spinner: DelayableActivityIndicatorView = {
let view = DelayableActivityIndicatorView(style: .gray)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(spinner)
spinner.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
spinner.startAnimatingWithDelay()
// replace this code with your network api
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.spinner.stopAnimatingWithDelay()
}
}
}
set a timer once you put a response to the server.if u get a reponse with in seconds you can hide the uiactivity indicator hidden by
activityindicator.hidden =false
Sub class the UIActivityIndicatorView and override the setter for showing the view. Set a timer in here to only show after 2 seconds. If the activity indicator is dismissed before this then nothing will be shown.
You may simple delay start your indicator animate:
var inUpdate = false
let indicatorView = UIActivityIndicatorView()
func startUpdate()
{
// go to update request
// do not forget to set inUpdate property to false in update completeon
inUpdate = true
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC))), dispatch_get_main_queue())
{
[weak self] in
if self?.inUpdate == true
{
self!.indicatorView.startAnimating()
}
}
}
If it's impossible to know how many time your data employ to be taken, you can delay the showing of your UIActivityIndicator by using a timer or a delay func:
func delay(seconds seconds: Double, completion:()->()) {
let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds ))
dispatch_after(popTime, dispatch_get_main_queue()) {
completion()
}
}
Use:
self.delay(seconds: 0.5, completion: {
// show my activityindicator
}
If you work with Alamofire framework to handle your network flow, you can also using the good AlamofireNetworkActivityIndicator here with:
NetworkActivityIndicatorManager.sharedManager.startDelay = 1.0
I'm developping application in Swift.
This application has many view and I would like to put a UIProgressView on all views
Can we get an array of all storyboard views ?
for exemple :
self.progressBar = UIProgressView(progressViewStyle: .Bar)
self.progressBar?.center = view.center
self.progressBar?.frame = CGRect(x: 0, y: 20, width: view.frame.width, height: CGFloat(1))
self.progressBar?.progress = 1/2
self.progressBar?.trackTintColor = UIColor.lightGrayColor();
self.progressBar?.tintColor = UIColor.redColor();
var arrayViewController : [UIViewController] = [...,...,...]
for controller in arrayViewController {
controller.view.addSubview(self.progressBar)
}
Thank you
Ysée
I assume that what you really want is to have the progress displayed on every view IF there is an operation in progress.
There are many ways to do that (using delegation, NSNotificationCenter, …) but the easiest I can think of would be to rely on viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Check if there's an operation in progress and add progressView if relevant
}
For the user, it will effectively look like you added the progress view to all views.
Why not create a base class that has a lazy stored property of type UIProgressView ? Optionally you can have two methods setProgressViewHidden(hidden : Bool) in order to easily show and hide the progress view and setProgress(progress : Float) to update the progress. Then all your view controllers can subclass this base class and conveniently interact with the progress view.
class ProgressViewController : UIViewController {
lazy var progressView : UIProgressView = {
[unowned self] in
var view = UIProgressView(frame: CGRectMake(0, 20, self.view.frame.size.width, 3))
view.progress = 0.5
view.trackTintColor = UIColor.lightGrayColor()
view.tintColor = UIColor.redColor()
self.view.addSubview(view)
return view
}()
}
To read more about lazy stored properties, check: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html