How to Make an UIButton Shrink Into an UIActivityIndicator When Pressed? - ios

I'm trying to create an UIButton that will shrink down to an Activity Indicator when tapped on. The UIButton I'm referring to is named Request Ride. I have most of the code already set, but for some reason the button won't shrink and the indicator won't show up? I'll attach pictures of the app and my code.
// My app
// My end result I'm needing
// Main Storyboard
// HomeVC
#IBAction func actionBtnWasPressed(_ sender: Any) {
actionBtn.animateButton(shouldLoad: true, withMessage: nil)
}
// RoundedShadowButton
class RoundedShadowButton: UIButton {
// Variables
var originalSize: CGRect?
func setupView() {
originalSize = self.frame
self.layer.cornerRadius = 5.0
self.layer.shadowRadius = 10.0
self.layer.shadowColor = UIColor.darkGray.cgColor
self.layer.shadowOpacity = 0.3
self.layer.shadowOffset = CGSize.zero
}
override func awakeFromNib() {
setupView()
}
func animateButton(shouldLoad: Bool, withMessage message: String?) {
let spinner = UIActivityIndicatorView()
spinner.style = .large
spinner.color = UIColor.darkGray
spinner.alpha = 0.0
spinner.hidesWhenStopped = true
spinner.tag = 21
if shouldLoad {
self.addSubview(spinner)
self.setTitle("", for: .normal)
UIView.animate(withDuration: 0.2, animations: {
self.layer.cornerRadius = self.frame.height / 2
self.frame = CGRect(x: self.frame.midX - (self.frame.height / 2), y: self.frame.origin.y, width: self.frame.height, height: self.frame.height)
}) { (finished) in
if finished == true {
spinner.startAnimating()
spinner.center = CGPoint(x: self.bounds.width / 2 + 1, y: self.bounds.width / 2 + 1)
UIView.animate(withDuration: 0.2) {
spinner.alpha = 1.0
}
}
}
self.isUserInteractionEnabled = false
} else {
self.isUserInteractionEnabled = true
for subview in self.subviews {
if subview.tag == 21 {
subview.removeFromSuperview()
}
}
UIView.animate(withDuration: 0.2) {
self.layer.cornerRadius = 5.0
self.frame = self.originalSize!
self.setTitle(message, for: .normal)
}
}
}
}

One obvious thing is that this line is totally wrong:
spinner.center = CGPoint(x: self.frame.width / 2 + 1, y: self.frame.width / 2 + 1)
You are placing the spinner incorrectly. If the activity view is a subview of the button, then it needs to be centered at the middle of the button’s bounds, not its frame.
I was able to get an effect somewhat like what you are describing:
So this ought to be possible if you get your values right.

Related

IOS Step menu using swift

I would like to create the stepper menu in IOS using swift, But I'm facing some issues. Here are the issues.
1) Portrait and landscape stepper menu is not propper.
2) How to set default step position with the method below method, It's working when button clicked. But I want to set when menu loads the first time.
self.stepView.setSelectedPosition(index: 2)
3) If it reached the position last, I would like to change the color for complete path parentPathRect.
4) Progress animation CABasicAnimation is not like the progress bar, I want to show the animation.
5) It should not remove the selected position color when changing the orientation.
As per my organization rules should not use third-party frameworks.
Can anyone help me with the solution? Or is there any alternative solution for this?
Here is my code:
import UIKit
class ViewController: UIViewController, StepMenuDelegate {
#IBOutlet weak var stepView: StepView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.stepView.delegate = self;
self.stepView.titles = ["1", "2", "3"]
self.stepView.lineWidth = 8
self.stepView.offSet = 8
self.stepView.setSelectedPosition(index: 2)
}
func didSelectItem(atIndex index: NSInteger) {
print(index)
}
}
protocol StepMenuDelegate {
func didSelectItem(atIndex index: NSInteger)
}
class StepView: UIView {
var delegate : StepMenuDelegate!
var titles: [String] = [] {
didSet(values) {
setup()
setupItems()
}
}
var lineWidth: CGFloat = 8 {
didSet(values) {
setup()
}
}
var offSet: CGFloat = 8 {
didSet(values) {
self.itemOffset = offSet * 4
setup()
}
}
private var selectedIndex : NSInteger!
private var itemOffset : CGFloat = 8 {
didSet (value) {
setup()
setupItems()
}
}
private var path : UIBezierPath!
var selectedLayer : CAShapeLayer!
private var parentPathRect : CGRect!
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
self.setup()
setupItems()
}
func setup() {
self.removeAllButtonsAndLayes()
let layer = CAShapeLayer()
self.parentPathRect = CGRect(origin: CGPoint(x: offSet, y: self.bounds.midY - (self.lineWidth/2) ), size: CGSize(width: self.bounds.width - (offSet * 2), height: lineWidth))
path = UIBezierPath(roundedRect: self.parentPathRect, cornerRadius: 2)
layer.path = path.cgPath
layer.fillColor = UIColor.orange.cgColor
layer.lineCap = .butt
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOffset = CGSize(width: 1, height: 2)
layer.shadowOpacity = 0.1
layer.shadowRadius = 2
self.layer.addSublayer(layer)
}
func setupItems() {
removeAllButtonsAndLayes()
let itemRect = CGRect(x: self.itemOffset, y: 0, width: 34, height: 34)
let totalWidth = self.bounds.width
let itemWidth = totalWidth / CGFloat(self.titles.count);
for i in 0..<self.titles.count {
let button = UIButton()
var xPos: CGFloat = itemOffset
self.addSubview(button)
xPos += (CGFloat(i) * itemWidth);
xPos += itemOffset/3
button.translatesAutoresizingMaskIntoConstraints = false
button.leftAnchor.constraint(equalTo: self.leftAnchor, constant: xPos).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
button.heightAnchor.constraint(equalToConstant: itemRect.height).isActive = true
button.widthAnchor.constraint(equalToConstant: itemRect.width).isActive = true
button.backgroundColor = UIColor.red
button.layer.zPosition = 1
button.layer.cornerRadius = itemRect.height/2
let name : String = self.titles[i]
button.tag = i
button.setTitle(name, for: .normal)
button.addTarget(self, action: #selector(selectedItemEvent(sender:)), for: .touchUpInside)
if self.selectedIndex != nil {
if button.tag == self.selectedIndex {
selectedItemEvent(sender: button)
}
}
}
}
#objc func selectedItemEvent(sender:UIButton) {
if self.selectedLayer != nil {
selectedLayer.removeFromSuperlayer()
}
self.delegate.didSelectItem(atIndex: sender.tag)
let fromRect = self.parentPathRect.origin
self.selectedLayer = CAShapeLayer()
let rect = CGRect(origin: fromRect, size: CGSize(width:sender.frame.origin.x - 4, height: 8))
let path = UIBezierPath(roundedRect: rect, cornerRadius: 4)
self.selectedLayer.path = path.cgPath
self.selectedLayer.lineCap = .round
self.selectedLayer.fillColor = UIColor.orange.cgColor
let animation = CABasicAnimation(keyPath: "fillColor")
animation.toValue = UIColor.blue.cgColor
animation.duration = 0.2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
self.selectedLayer.add(animation, forKey: "fillColor")
self.layer.addSublayer(self.selectedLayer)
}
func removeAllButtonsAndLayes() {
for button in self.subviews {
if button is UIButton {
button.removeFromSuperview()
}
}
}
func setSelectedPosition(index:NSInteger) {
self.selectedIndex = index
}
}
Here I found One way to achieve the solution.!!
https://gist.github.com/DamodarGit/7f0f484708f60c996772ae28e5e1c615
Welcome to suggestions or code changes.!!

Right margin of custom clear button in iOS

I create a custom clear button on my text field with a background image set.
I position it on the right view but it always positions with a default margin. What I want is to have a right margin, for example, of 8.
extension UITextField {
func clearButtonWithImage(_ image: UIImage) {
let clearButton = UIButton()
clearButton.setBackgroundImage(image, for: .normal)
clearButton.alpha = 0.25
clearButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
clearButton.contentMode = .scaleAspectFit
clearButton.addTarget(self, action: #selector(self.clear(sender:)), for: .touchUpInside)
self.rightViewMode = .always
self.clearButtonMode = .never
self.rightView = clearButton
}
#objc func clear(sender: AnyObject) {
self.text = ""
}
}
I use a custom textfield class for changing the style of my textfield on selecting.
My custom Textfield Class:
class customSearchTextField: customTextField {
override var isSelected: Bool {
didSet {
setSelected(isSelected)
}
}
private func setSelected(_ selected: Bool) {
//UIView.animate(withDuration: 0.15, animations: {
self.transform = selected ? CGAffineTransform(scaleX: 1.05, y: 1.05) : CGAffineTransform.identity
self.cornerRadius = selected ? 2.0 : 0.0
if selected {
self.clearButtonWithImage(UIImage())
} else {
self.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
}
self.layer.shadowColor = UIColor .customDark.cgColor
self.layer.shadowOpacity = selected ? 0.19 : 0.0
//})
}
}
Tried positioning the image frame but it always stays the same.
You should take a look at rightViewRect(forBounds:) method. Create a subclass of UITextField and custom position and size of clear button inside this method.
For example
class customSearchTextField: customTextField {
override var isSelected: Bool {
didSet {
setSelected(isSelected)
}
}
private func setSelected(_ selected: Bool) {
//UIView.animate(withDuration: 0.15, animations: {
self.transform = selected ? CGAffineTransform(scaleX: 1.05, y: 1.05) : CGAffineTransform.identity
self.cornerRadius = selected ? 2.0 : 0.0
if selected {
self.clearButtonWithImage(UIImage())
} else {
self.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
}
self.layer.shadowColor = UIColor .customDark.cgColor
self.layer.shadowOpacity = selected ? 0.19 : 0.0
//})
}
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.rightViewRect(forBounds: bounds)
rect.origin.x -= 30 // Assume your right margin is 30
return rect
}
}

UIPanGestureRecognizer on subclass UIView

I've been trying to lean more about subclassing certain objects.
Now I've subclassed a UIView which has a PanGestureRecognizer for swiping left and right.
Can't seem to find the problem. It won't even move the UIView. I've tried looking the lifecycle of an UIView to set the isUserInteractionEnabled to true, but with no result. See the code below:
VC
import UIKit
class SwipeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addNewProfile()
}
private func addNewProfile() {
let swipeView = SwiperView(frame: CGRect(x: self.view.bounds.width / 2 - 150, y: self.view.bounds.height / 2 - 75, width: 300, height: 150))
swipeView.parentView = self.view
swipeView.delegate = self
swipeView.shadow = true
swipeView.isUserInteractionEnabled = true
swipeView.backgroundColor = UIColor.white
swipeView.alpha = 0.0
view.addSubview(swipeView)
UIView.animate(withDuration: 0.3, animations: {
swipeView.alpha = 1.0
}, completion: { (succeed) in
swipeView.isUserInteractionEnabled = true
})
}
}
//MARK: - ChosenSwipeResultDelegate
extension SwipeViewController: ChosenSwipeResultDelegate {
func pickedLeftSide() {
}
func pickedRightSide() {
}
}
SwiperView
import UIKit
protocol ChosenSwipeResultDelegate {
func pickedLeftSide()
func pickedRightSide()
}
#IBDesignable class SwiperView: UIView {
private var _shadow: Bool!
private var _parentView: UIView!
var delegate: ChosenSwipeResultDelegate?
var parentView: UIView {
set {
_parentView = newValue
}
get {
return _parentView
}
}
#IBInspectable var shadow: Bool {
get {
return layer.shadowOpacity > 0.0
}
set {
if newValue == true {
addShadow()
}
}
}
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
if shadow == false {
layer.masksToBounds = true
}
}
}
override func setNeedsLayout() {
super.setNeedsLayout()
isUserInteractionEnabled = true
}
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(SwiperView.dragging(gesture:)))
addGestureRecognizer(dragGesture)
}
func dragging(gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: parentView)
let tinderView = gesture.view!
tinderView.center = CGPoint(x: parentView.bounds.width / 2 + translation.x, y: parentView.bounds.height / 2 + translation.y)
let xFromCenter = tinderView.center.x - parentView.bounds.width / 2
let scale = min(100 / abs(xFromCenter), 1)
var rotation = CGAffineTransform(rotationAngle: xFromCenter / 200)
let stretch = rotation.scaledBy(x: scale, y: scale)
tinderView.transform = stretch
if gesture.state == .ended {
if tinderView.center.x < 100 {
print("left")
UIView.animate(withDuration: 0.3, animations: {
tinderView.alpha = 0.0
}, completion: { (succeed) in
self.delegate?.pickedLeftSide()
})
} else if tinderView.center.x > parentView.bounds.width - 100 {
print("right")
UIView.animate(withDuration: 0.3, animations: {
tinderView.alpha = 0.0
}, completion: { (succeed) in
self.delegate?.pickedRightSide()
})
} else {
print("Not chosen")
rotation = CGAffineTransform(rotationAngle: 0)
let stretch = rotation.scaledBy(x: 1, y: 1)
tinderView.transform = stretch
tinderView.center = CGPoint(x: parentView.bounds.width / 2, y: parentView.bounds.height / 2)
}
}
}
private func addShadow(shadowColor: CGColor = UIColor.black.cgColor, shadowOffset: CGSize = CGSize(width: 1.0, height: 2.0), shadowOpacity: Float = 0.4, shadowRadius: CGFloat = 3.0) {
layer.shadowColor = shadowColor
layer.shadowOffset = shadowOffset
layer.shadowOpacity = shadowOpacity
layer.shadowRadius = shadowRadius
}
}
add these code to your SwiperView see if it solves the problem
override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(SwiperView.dragging(gesture:)))
addGestureRecognizer(dragGesture)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(SwiperView.dragging(gesture:)))
addGestureRecognizer(dragGesture)
}

UIView changing its position in swift

How do I make a UIView slide up with a touch of a button from its original position and them bring it back down with a touch of a button? Using Swift and Xcode 6.
I have currently tried this:
#IBOutlet weak var DynView: UIView!
#IBAction func btnUp(sender: AnyObject) {
}
You have to implement an animation changing the DynView position on click. Here's an example:
#IBAction func btnUp(sender: AnyObject) {
let xPosition = DynView.frame.origin.x
let yPosition = DynView.frame.origin.y - 20 // Slide Up - 20px
let width = DynView.frame.size.width
let height = DynView.frame.size.height
UIView.animateWithDuration(1.0, animations: {
dynView.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
}
Hi create this extends if you want. For Swift
Create File Extends.Swift and add this code
/**
Extension UIView
by DaRk-_-D0G
*/
extension UIView {
/**
Set x Position
:param: x CGFloat
by DaRk-_-D0G
*/
func setX(#x:CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
by DaRk-_-D0G
*/
func setY(#y:CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
by DaRk-_-D0G
*/
func setWidth(#width:CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
by DaRk-_-D0G
*/
func setHeight(#height:CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
}
For Use (inherits Of UIView)
inheritsOfUIView.setX(x: 100)
button.setX(x: 100)
view.setY(y: 100)
I kinda combined the two most voted answers into one and updated to Swift 3. So basically created an extension that animates a view moving to a different position:
extension UIView {
func slideX(x:CGFloat) {
let yPosition = self.frame.origin.y
let height = self.frame.height
let width = self.frame.width
UIView.animate(withDuration: 1.0, animations: {
self.frame = CGRect(x: x, y: yPosition, width: width, height: height)
})
}
}
// MARK: - Properties
var bottomViewHeight: CGFloat = 200
var isViewHide = false
private let bottomView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let showHideButton: UIButton = {
let button = UIButton()
button.setTitle("Show / Hide", for: .normal)
button.setTitleColor(.black, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(showHideButtonTapped(_:)), for: .touchUpInside)
return button
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
view.addSubview(bottomView)
NSLayoutConstraint.activate([
bottomView.heightAnchor.constraint(equalToConstant: bottomViewHeight),
bottomView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
bottomView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
bottomView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
view.addSubview(showHideButton)
NSLayoutConstraint.activate([
showHideButton.widthAnchor.constraint(equalToConstant: 200),
showHideButton.heightAnchor.constraint(equalToConstant: 50),
showHideButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
showHideButton.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
showHideView(isShow: isViewHide)
}
// MARK: - Selectors
#objc func showHideButtonTapped(_ sender: UIButton) {
print("👆 HIDE / SHOW BUTTON")
showHideView(isShow: isViewHide)
}
// MARK: - Functions
private func showHideView(isShow: Bool) {
if isShow {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: self.bottomViewHeight)
}
} else {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
isViewHide = !isViewHide
}

Activity indicator with custom image

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!
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];
You may use this beautiful loader inspired from Tumblr app:
Asich/AMTumblrHud
Swift 5
Another answer working perfect
Step 1.
Create swift file "CustomLoader.swift" and put this code in that file
import UIKit
import CoreGraphics
import QuartzCore
class CustomLoader: UIView
{
//MARK:- NOT ACCESSABLE OUT SIDE
fileprivate var duration : CFTimeInterval! = 1
fileprivate var isAnimating :Bool = false
fileprivate var backgroundView : UIView!
//MARK:- ACCESS INSTANCE ONLY AND CHANGE ACCORDING TO YOUR NEEDS *******
let colors : [UIColor] = [.red, .blue, .orange, .purple]
var defaultColor : UIColor = UIColor.red
var isUsrInteractionEnable : Bool = false
var defaultbgColor: UIColor = UIColor.white
var loaderSize : CGFloat = 80.0
/// **************** ****************** ////////// **************
//MARK:- MAKE SHARED INSTANCE
private static var Instance : CustomLoader!
static let sharedInstance : CustomLoader = {
if Instance == nil
{
Instance = CustomLoader()
}
return Instance
}()
//MARK:- DESTROY TO SHARED INSTANCE
#objc fileprivate func destroyShardInstance()
{
CustomLoader.Instance = nil
}
//MARK:- SET YOUR LOADER INITIALIZER FRAME ELSE DEFAULT IS CENTER
func startAnimation()
{
let win = UIApplication.shared.keyWindow
backgroundView = UIView()
backgroundView.frame = (UIApplication.shared.keyWindow?.frame)!
backgroundView.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
win?.addSubview(backgroundView)
self.frame = CGRect.init(x: ((UIScreen.main.bounds.width) - loaderSize)/2, y: ((UIScreen.main.bounds.height) - loaderSize)/2, width: loaderSize, height: loaderSize)
self.addCenterImage()
self.isHidden = false
self.backgroundView.addSubview(self)
self.layer.cornerRadius = loaderSize/2
self.layer.masksToBounds = true
backgroundView.accessibilityIdentifier = "CustomLoader"
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSExtensionHostDidBecomeActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(CustomLoader.ResumeLoader), name: NSNotification.Name.NSExtensionHostDidBecomeActive, object: nil)
self.layoutSubviews()
}
//MARK:- AVOID STUCKING LOADER WHEN CAME BACK FROM BACKGROUND
#objc fileprivate func ResumeLoader()
{
if isAnimating
{
self.stopAnimation()
self.AnimationStart()
}
}
override func layoutSubviews()
{
super.layoutSubviews()
self.backgroundColor = defaultbgColor
UIApplication.shared.keyWindow?.isUserInteractionEnabled = isUsrInteractionEnable
self.AnimationStart()
}
#objc fileprivate func addCenterImage()
{
/// add image in center
let centerImage = UIImage(named: "Logo")
let imageSize = loaderSize/2.5
let centerImgView = UIImageView(image: centerImage)
centerImgView.frame = CGRect(
x: (self.bounds.width - imageSize) / 2 ,
y: (self.bounds.height - imageSize) / 2,
width: imageSize,
height: imageSize
)
centerImgView.contentMode = .scaleAspectFit
centerImgView.layer.cornerRadius = imageSize/2
centerImgView.clipsToBounds = true
self.addSubview(centerImgView)
}
//MARK:- CALL IT TO START THE LOADER , AFTER INITIALIZE THE LOADER
#objc fileprivate func AnimationStart()
{
if isAnimating
{
return
}
let size = CGSize.init(width: loaderSize , height: loaderSize)
let dotNum: CGFloat = 10
let diameter: CGFloat = size.width / 5.5 //10
let dot = CALayer()
let frame = CGRect(
x: (layer.bounds.width - diameter) / 2 + diameter * 2,
y: (layer.bounds.height - diameter) / 2,
width: diameter/1.3,
height: diameter/1.3
)
dot.backgroundColor = colors[0].cgColor
dot.cornerRadius = frame.width / 2
dot.frame = frame
let replicatorLayer = CAReplicatorLayer()
replicatorLayer.frame = layer.bounds
replicatorLayer.instanceCount = Int(dotNum)
replicatorLayer.instanceDelay = 0.1
let angle = (2.0 * M_PI) / Double(replicatorLayer.instanceCount)
replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
layer.addSublayer(replicatorLayer)
replicatorLayer.addSublayer(dot)
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.toValue = 0.4
scaleAnimation.duration = 0.5
scaleAnimation.autoreverses = true
scaleAnimation.repeatCount = .infinity
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
dot.add(scaleAnimation, forKey: "scaleAnimation")
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.toValue = -2.0 * Double.pi
rotationAnimation.duration = 6.0
rotationAnimation.repeatCount = .infinity
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
replicatorLayer.add(rotationAnimation, forKey: "rotationAnimation")
if colors.count > 1 {
var cgColors : [CGColor] = []
for color in colors {
cgColors.append(color.cgColor)
}
let colorAnimation = CAKeyframeAnimation(keyPath: "backgroundColor")
colorAnimation.values = cgColors
colorAnimation.duration = 2
colorAnimation.repeatCount = .infinity
colorAnimation.autoreverses = true
dot.add(colorAnimation, forKey: "colorAnimation")
}
self.isAnimating = true
self.isHidden = false
}
//MARK:- CALL IT TO STOP THE LOADER
func stopAnimation()
{
if !isAnimating
{
return
}
UIApplication.shared.keyWindow?.isUserInteractionEnabled = true
let winSubviews = UIApplication.shared.keyWindow?.subviews
if (winSubviews?.count)! > 0
{
for viw in winSubviews!
{
if viw.accessibilityIdentifier == "CustomLoader"
{
viw.removeFromSuperview()
// break
}
}
}
layer.sublayers = nil
isAnimating = false
self.isHidden = true
self.destroyShardInstance()
}
//MARK:- GETTING RANDOM COLOR , AND MANAGE YOUR OWN COLORS
#objc fileprivate func randomColor()->UIColor
{
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
override func draw(_ rect: CGRect)
{
}
}
find the func name and "addCenterImage" and replace the image name with your custom image.
Step 2
Create the AppDelegate class instance out side of the AppDelegate class like this.
var AppInstance: AppDelegate!
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
AppInstance = self
}
Step 3.
put these two func in your AppDelegate
//MARK: - Activity Indicator -
func showLoader()
{
CustomLoader.sharedInstance.startAnimation()
}
func hideLoader()
{
CustomLoader.sharedInstance.stopAnimation()
}
Step 4. Use the functions like this whenever you want to animate your loader and stop.
AppInstance.showLoader()
AppInstance.hideLoader()
HAPPY LOADING...
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.
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.
Swift 5.0 version of accepted Answer
public extension UIImageView {
func spin(duration: Float) {
let rotation = CABasicAnimation(keyPath: "transform.rotation")
rotation.fromValue = 0
rotation.toValue = 2 * Double.pi
rotation.duration = 0.7
rotation.repeatCount = duration
layer.add(rotation, forKey: "spin")
}
func stopSpinning() {
layer.removeAllAnimations()
}
}
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
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.
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
}
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.

Resources