Activity indicator with custom image

2019-03-09 12:40发布

问题:

I am loading a UIWebView and in the meantime I wan't to show a blank page with this activity indicator spinning (siri activity indicator). From what I have understand you can not change the image, but can't I use that image and create an animation with it rotating 360° and looping? Or will that drain the battery?

something like this?:

- (void)webViewDidStartLoad:(UIWebView *)webView {
    //set up animation        
    [self.view addSubview:self.loadingImage];
    //start animation
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{   
    //stop animation
    [self.loadingImage removeFromSuperview];
}

What should I do?

Thanks in advance!

回答1:

Most of this is found in Stack Overflow. Let me summarize:

Create an UIImageView which will serve as an activity indicator (inside storyboard scene, NIB, code ... wherever you wish). Let's call it _activityIndicatorImage

Load your image: _activityIndicatorImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"activity_indicator"]];

You need to use animation to rotate it. Here is the method I use:

+ (void)rotateLayerInfinite:(CALayer *)layer
{
    CABasicAnimation *rotation;
    rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotation.fromValue = [NSNumber numberWithFloat:0];
    rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    rotation.duration = 0.7f; // Speed
    rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number.
    [layer removeAllAnimations];
    [layer addAnimation:rotation forKey:@"Spin"];
}

Inside my layoutSubviews method I initiate rotation. You could place this in your webViewDidStartLoad and webViewDidFinishLoad if this is better for your case:

- (void)layoutSubviews
{
    [super layoutSubviews];

    // some other code 

    [Utils rotateLayerInfinite:_activityIndicatorImage.layer];
}

You could always always stop rotation using [_activityIndicatorImage.layer removeAllAnimations];



回答2:

You may use this beautiful loader inspired from Tumblr app:
Asich/AMTumblrHud



回答3:

SWIFT 4 Sweet And Simply just put extension UIView{}

Modified answer of @gandhi Mena

if you want to create your own custom Loading indicator

Create a UIView extension which create and customize your brand logo as a custom indicator put this code in you global declaration file.

extension UIView{
func customActivityIndicator(view: UIView, widthView: CGFloat?,backgroundColor: UIColor?, textColor:UIColor?, message: String?) -> UIView{

    //Config UIView
    self.backgroundColor = backgroundColor //Background color of your view which you want to set

    var selfWidth = view.frame.width
    if widthView != nil{
        selfWidth = widthView ?? selfWidth
    }

    let selfHeigh = view.frame.height
    let loopImages = UIImageView()

    let imageListArray = ["image1", "image2"] // Put your desired array of images in a specific order the way you want to display animation.

    loopImages.animationImages = imageListArray
    loopImages.animationDuration = TimeInterval(0.8)
    loopImages.startAnimating()

    let imageFrameX = (selfWidth / 2) - 30
    let imageFrameY = (selfHeigh / 2) - 60
    var imageWidth = CGFloat(60)
    var imageHeight = CGFloat(60)

    if widthView != nil{
        imageWidth = widthView ?? imageWidth
        imageHeight = widthView ?? imageHeight
    }

    //ConfigureLabel
    let label = UILabel()
    label.textAlignment = .center
    label.textColor = .gray
    label.font = UIFont(name: "SFUIDisplay-Regular", size: 17.0)! // Your Desired UIFont Style and Size
    label.numberOfLines = 0
    label.text = message ?? ""
    label.textColor = textColor ?? UIColor.clear

    //Config frame of label
    let labelFrameX = (selfWidth / 2) - 100
    let labelFrameY = (selfHeigh / 2) - 10
    let labelWidth = CGFloat(200)
    let labelHeight = CGFloat(70)

    // Define UIView frame
    self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height)


    //ImageFrame
    loopImages.frame = CGRect(x: imageFrameX, y: imageFrameY, width: imageWidth, height: imageHeight)

    //LabelFrame
    label.frame = CGRect(x: labelFrameX, y: labelFrameY, width: labelWidth, height: labelHeight)

    //add loading and label to customView
    self.addSubview(loopImages)
    self.addSubview(label)
    return self }}

Hide an indicator something like this you can remove subview at the top from the subview stack. put this code in the same globally declared swift file.

func hideLoader(removeFrom : UIView){
removeFrom.subviews.last?.removeFromSuperview()
}

Now you can shoot at the mark by this code To display activity indicator in your view controller put this code when you want to display.

 self.view.addSubview(UIView().customActivityIndicator(view: self.view, widthView: nil, backgroundColor:"Desired color", textColor: "Desired color", message: "Loading something"))

To hide animating loader you can user above function you defined in the globally. In your ViewController.swift where you want to hide put this line of code.

hideLoader(removeFrom: self.view)

imageListArray looks like this.



回答4:

Without Image , you can use third party library

for objective C (also support in iOS 6) https://github.com/shebinkoshy/UIControllsRepo

for swift https://github.com/shebinkoshy/Activity-Indicator-Swift

Advantages

-> Able to set colors for spinner

-> Available in different sizes like tiny, small, medium, large, very large

-> Able to set Title (center and bottom) for medium, large, very large sizes



回答5:

You can set an images to your activityIndicator. I created a function for add custom image to activityIndicator. Here is what I created.

public func showProgressView(view: UIView) -> UIImageView {
    let containerView = UIView()
    let progressView = UIView()
    var activityIndicatorImageView = UIImageView()

    if let statusImage = UIImage(named: Constants.ActivityIndicatorImageName1) {
        let activityImageView = UIImageView(image: statusImage)
        containerView.frame = view.frame
        containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.3)
        progressView.frame = CGRectMake(0, 0, 80, 80)
        progressView.center = CGPointMake(view.bounds.width / 2, view.bounds.height / 2)
        progressView.backgroundColor = UIColor(hex: 0x18bda3, alpha: 0.7)
        progressView.clipsToBounds = true
        progressView.layer.cornerRadius = 10
        activityImageView.animationImages = [UIImage(named: Constants.ActivityIndicatorImageName1)!,
            UIImage(named: Constants.ActivityIndicatorImageName2)!,
            UIImage(named: Constants.ActivityIndicatorImageName3)!,
            UIImage(named: Constants.ActivityIndicatorImageName4)!,
            UIImage(named: Constants.ActivityIndicatorImageName5)!]
        activityImageView.animationDuration = 0.8;
        activityImageView.frame = CGRectMake(view.frame.size.width / 2 - statusImage.size.width / 2, view.frame.size.height / 2 - statusImage.size.height / 2, 40.0, 48.0)
        activityImageView.center = CGPointMake(progressView.bounds.width / 2, progressView.bounds.height / 2)
        dispatch_async(dispatch_get_main_queue()) {
            progressView.addSubview(activityImageView)
            containerView.addSubview(progressView)
            view.addSubview(containerView)
            activityIndicatorImageView = activityImageView
        }
    }
    return activityIndicatorImageView
}

You can call this method everywhere in your code. And just call the startAnimating method. If you want to hide just call the stopAnimating method.



回答6:

it works in both SWITF 3 and 4

var activityIndicator = UIActivityIndicatorView()
var myView : UIView = UIView()

func viewDidLoad() {
    spinnerCreation()
}

func spinnerCreation() {

    activityIndicator.activityIndicatorViewStyle =  .whiteLarge

    let label = UILabel.init(frame: CGRect(x: 5, y: 60, width: 90, height: 20))
    label.textColor = UIColor.white
    label.font = UIFont.boldSystemFont(ofSize: 14.0)
    label.textAlignment = NSTextAlignment.center
    label.text = "Please wait...."

    myView.frame = CGRect(x: (UIScreen.main.bounds.size.width - 100)/2, y: (UIScreen.main.bounds.size.height - 100)/2, width: 100, height: 100)

    myView.backgroundColor = UIColor.init(white: 0.0, alpha: 0.7)
    myView.layer.cornerRadius = 5
    activityIndicator.center = CGPoint(x: myView.frame.size.width/2, y:  myView.frame.size.height/2 - 10)
    myView.addSubview(activityIndicator)
    myView.addSubview(label)

    myView.isHidden = true
    self.window?.addSubview(myView)
}

@IBAction func activityIndicatorStart(_ sender: Any) {
    myView.isHidden = false
    self.activityIndicator.startAnimating()
    self.view.isUserInteractionEnabled = false
    self.view.bringSubview(toFront: myView)
}

@IBAction func activityIndicatorStop(_ sender: Any)() {
    myView.isHidden = true
    self.activityIndicator.stopAnimating()
    self.view.isUserInteractionEnabled = true
}


回答7:

You can create your custom activity Indicator with this in Swift 3 & 4:

Create a new file with name: UIViewExtension.Swift and copy this code and paste in your new file file:

import UIkit

extension UIView{
   func customActivityIndicator(view: UIView, widthView: CGFloat? = nil,backgroundColor: UIColor? = nil, message: String? = nil,colorMessage:UIColor? = nil ) -> UIView{

    //Config UIView
    self.backgroundColor = backgroundColor ?? UIColor.clear
    self.layer.cornerRadius = 10


    var selfWidth = view.frame.width - 100
    if widthView != nil{
        selfWidth = widthView ?? selfWidth
    }

    let selfHeigh = CGFloat(100)
    let selfFrameX = (view.frame.width / 2) - (selfWidth / 2)
    let selfFrameY = (view.frame.height / 2) - (selfHeigh / 2)
    let loopImages = UIImageView()

    //ConfigCustomLoading with secuence images
    let imageListArray = [UIImage(named:""),UIImage(named:""), UIImage(named:"")]
    loopImages.animationImages = imageListArray
    loopImages.animationDuration = TimeInterval(1.3)
    loopImages.startAnimating()
    let imageFrameX = (selfWidth / 2) - 17
    let imageFrameY = (selfHeigh / 2) - 35
    var imageWidth = CGFloat(35)
    var imageHeight = CGFloat(35)

    if widthView != nil{
        imageWidth = widthView ?? imageWidth
        imageHeight = widthView ?? imageHeight
    }

    //ConfigureLabel
    let label = UILabel()
    label.textAlignment = .center
    label.textColor = .gray
    label.font = UIFont.boldSystemFont(ofSize: 17)
    label.numberOfLines = 0
    label.text = message ?? ""
    label.textColor = colorMessage ?? UIColor.clear

    //Config frame of label
    let labelFrameX = (selfWidth / 2) - 100
    let labelFrameY = (selfHeigh / 2) - 10
    let labelWidth = CGFloat(200)
    let labelHeight = CGFloat(70)

    //add loading and label to customView
    self.addSubview(loopImages)
    self.addSubview(label)

    //Define frames
    //UIViewFrame
    self.frame = CGRect(x: selfFrameX, y: selfFrameY, width: selfWidth , height: selfHeigh)

    //ImageFrame
    loopImages.frame = CGRect(x: imageFrameX, y: imageFrameY, width: imageWidth, height: imageHeight)

    //LabelFrame
    label.frame = CGRect(x: labelFrameX, y: labelFrameY, width: labelWidth, height: labelHeight)

    return self

}

}

And then you can use it in your ViewController like this:

import UIKit


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

        self.view.addSubview(UIView().customActivityIndicator(view: self.view,backgroundColor: UIColor.green))

    }

   //function for stop and desappear loading
   func deseappearLoading(){
      self.view.subviews.last?.removeFromSuperview()
   }
}

Don't forget replace [UIImage(named:" "),UIImage(named:" "), UIImage(named:" ")] with your names of images and adjust the TimeInterval(1.3). Enjoy it.



回答8:

I've faced a similar issue lately. And this is my solution. Basically, it's what topic starter initially wanted: blank page with custom activity indicator on it.
I have partly used @Azharhussain Shaikh answer but I've implemented auto-layout instead of using frames and added a few other refinements with the intention to make usage as simple as possible.

So, it's an extension for UIView with two methods: addActivityIndicator() and removeActivityIndicator()

extension UIView {

func addActivityIndicator() {
    //    creating a view (let's call it "loading" view) which will be added on top of the view you want to have activity indicator on (parent view)
    let view = UIView()
    //    setting up a background for a view so it would make content under it look like not active
    view.backgroundColor = UIColor.white.withAlphaComponent(0.7)

    //    adding "loading" view to a parent view
    //    setting up auto-layout anchors so it would cover whole parent view
    self.addSubview(view)
    view.translatesAutoresizingMaskIntoConstraints = false
    view.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
    view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true

    //    creating array with images, which will be animated
    //    in my case I have 30 images with names activity0.png ... activity29.png
    var imagesArray = [UIImage(named: "activity\(0)")!]
    for i in 1..<30 {
        imagesArray.append(UIImage(named: "activity\(i)")!)
    }

    //    creating UIImageView with array of images
    //    setting up animation duration and starting animation
    let activityImage = UIImageView()
    activityImage.animationImages = imagesArray
    activityImage.animationDuration = TimeInterval(0.7)
    activityImage.startAnimating()

    //    adding UIImageView on "loading" view
    //    setting up auto-layout anchors so it would be in center of "loading" view with 30x30 size
    view.addSubview(activityImage)
    activityImage.translatesAutoresizingMaskIntoConstraints = false
    activityImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    activityImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    activityImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
    activityImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
}

func removeActivityIndicator() {
    //    checking if a view has subviews on it
    guard let lastSubView = self.subviews.last else { return }
    //    removing last subview with an assumption that last view is a "loading" view
    lastSubView.removeFromSuperview()
} }

"Rotating" effect is achieved by those 30 images you've put in imagesArray. Each image is a new frame of a rotating indicator like this.

Usage. In your view controller for showing an activity indicator simply put:

    view.addActivityIndicator()

For removing an activity indicator:

    view.removeActivityIndicator()

For example, in case of using it with table view (like I do) it can be used like this:

func setLoadingScreen() {
    view.addActivityIndicator()
    tableView.isScrollEnabled = false
}

func removeLoadingScreen() {
    view.removeActivityIndicator()
    tableView.isScrollEnabled = true
}

It works in Swift 4.