Swift: How can i avoid view overlapping in this situation - ios

I'm a noob in Swift and XCode and it may be a silly question, but I can't figure it out on my own :(
This is how my LoginViewController looks like. Image1
And I have the following function that triggers when I press the submit button.
#objc func loginButtontapped(){
if emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
customAlert.showAlert(with: "Error", message: "Fill in the fields.", on: self)
} else {
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Auth.auth().signIn(withEmail: email, password: password) { (result, err) in
if err != nil {
self.customAlert.showAlert(with: "Error", message: "Invalid email or password.", on: self)
}else{
let homepageVC = HomepageViewController()
homepageVC.modalPresentationStyle = .fullScreen
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
self.view.window!.layer.add(transition, forKey: kCATransition)
self.present(homepageVC, animated: false, completion: nil)
}}}}
Basically it verifies if the TextFields are empty and after that it shows a view with a message.
The first time after I run the project and I click Submit with the empty fields, I get the desired result, as can be seen here. Screenshot here
The problem appears the second time i press the submit button, after i complete the fields with some text as it shows the views overlapped.
Screenshot here
What I'm trying to do is to show one "alert" at a time, depending on the situation.
Below is how my CustomAlert is build.
class CustomAlert{
struct Constants {
static let backgroundAlphaTo: CGFloat = 0.6
}
private let backgroundView: UIView = {
let backgroundView = UIView()
backgroundView.backgroundColor = .black
backgroundView.alpha = 0
return backgroundView
}()
private let alertView: UIView = {
let alertView = UIView()
alertView.backgroundColor = UIColor(rgb: 0x363636, alphaVal: 1)
alertView.layer.masksToBounds = true
alertView.layer.cornerRadius = 12
return alertView
}()
private var myTargetView: UIView?
func showAlert(with title: String, message: String, on ViewController: UIViewController){
guard let targetView = ViewController.view else{
return
}
myTargetView = targetView
backgroundView.frame = targetView.bounds
targetView.addSubview(backgroundView)
targetView.addSubview(alertView)
alertView.frame = CGRect(x: 40,
y: -300,
width: targetView.frame.size.width-80,
height: 180)
let titleLabel = UILabel(frame: CGRect(x: 20,
y: 10,
width: alertView.frame.size.width,
height: 40))
titleLabel.text = title
titleLabel.textAlignment = .left
titleLabel.font = UIFont(name: "Roboto-Regular", size: 20)
titleLabel.textColor = .white
alertView.addSubview(titleLabel)
let messageLabel = UILabel(frame: CGRect(x: 20,
y: 40,
width: alertView.frame.size.width-30,
height: 60))
messageLabel.text = message
messageLabel.font = UIFont(name: "Roboto-Thin", size: 18)
messageLabel.textColor = .white
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .left
alertView.addSubview(messageLabel)
let button = UIButton (frame: CGRect(x: alertView.frame.size.width-130,
y: alertView.frame.size.height-70,
width: (alertView.frame.midX)/2,
height: 50))
button.setTitle("Okay", for: .normal)
button.applyGradient(colors: [Helper.UIColorFromRGB(0x694BCE).cgColor,Helper.UIColorFromRGB(0x7D3297).cgColor])
button.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
alertView.addSubview(button)
UIView.animate(withDuration: 0.25, animations: {
self.backgroundView.alpha = Constants.backgroundAlphaTo},
completion: {done in
if done{
UIView.animate(withDuration: 0.25, animations: {
self.alertView.center = targetView.center
})
}
})
}
#objc func dismissAlert(){
guard let targetView = myTargetView else{
return
}
UIView.animate(withDuration: 0.25, animations: {
self.alertView.frame = CGRect(x: 40,
y: targetView.frame.size.height,
width: targetView.frame.size.width-80,
height: 300)
},
completion: { [weak self] done in
if done{
UIView.animate(withDuration: 0.25, animations: {
self?.backgroundView.alpha = 0
}, completion: { done in
if done {
self?.alertView.removeFromSuperview()
self?.backgroundView.removeFromSuperview()
}
})
}
})
}
}

It looks like you're adding a new UILabel each time you call showAlert function. You should add this label as child of alertView and just change it's text.

i managed to fix it by adding in the dismissAlert() func, the following 2 lines of code:
myTitleLabel?.text = ""
myMessageLabel?.text = ""
I also made those 2 global.

Related

How to Send Data as Parameter to UIButton Selector Method

So I'm building an app similar to Tinder where there is a card "deck" (referred to in this code as cardsDeckView) filled with UIViews (referred to in this code as cardView). Each of these "cards" display user information such as profile images (which you can cycle through), name, age, and profession. They also have a button on them that, when pressed, go to a user info screen where more information about that user is shown. Here is where I'm having trouble. I figured I could pass each user's id to each respective "card" when users are loaded on the deck, and pass this data through the button target when pressed, but I haven't found anything on Stack Overflow regarding passing parameters into button selectors in Swift. Here is my code, that essentially loads existing users with some filters, and creates cardViews with each user's info:
import UIKit
import SDWebImage
import SLCarouselView
import JGProgressHUD
class DeckVC: UIViewController {
let headerView = UIView()
let cardsDeckView = SLCarouselView(coder: NSCoder.empty())
let menuView = BottomNavigationStackView()
var users: [User] = []
var userId: String?
let hud = JGProgressHUD(style: .extraLight)
override func viewDidLoad() {
super.viewDidLoad()
hud.textLabel.text = "Loading nearby users..."
hud.layer.zPosition = 50
hud.show(in: view)
headerView.heightAnchor.constraint(equalToConstant: 70).isActive = true
menuView.heightAnchor.constraint(equalToConstant: 70).isActive = true
let stackView = UIStackView(arrangedSubviews: [headerView, cardsDeckView!, menuView])
stackView.axis = .vertical
view.addSubview(stackView)
stackView.frame = .init(x: 0, y: 0, width: 300, height: 200)
stackView.fillSuperview()
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 12, bottom: 0, right: 12)
stackView.bringSubviewToFront(cardsDeckView!)
menuView.settingsButton.addTarget(self, action: #selector(handleSettings), for: .touchUpInside)
menuView.messagesButton.addTarget(self, action: #selector(handleMessages), for: .touchUpInside)
setupUI()
}
func setupUI() {
observeUsers { (user) in
API.User.observeCurrentUser(completion: { (currentUser) in
if (user.id != API.User.CURRENT_USER?.uid) && (currentUser.preferedGender == user.gender) && (currentUser.minAge!...currentUser.maxAge! ~= user.age!) {
self.users.append(user)
DispatchQueue.main.async {
self.setupCards()
}
} else if (user.id != API.User.CURRENT_USER?.uid) && (currentUser.preferedGender == "Both") && (currentUser.minAge!...currentUser.maxAge! ~= user.age!) {
self.users.append(user)
DispatchQueue.main.async {
self.setupCards()
}
}
})
}
}
func observeUsers(completion: #escaping (User) -> Void) {
API.User.REF_USERS.observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String : Any] {
let user = User.transformUser(dict: dict, key: snapshot.key)
completion(user)
}
}
}
#objc func handleSettings() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileVC")
self.present(profileVC, animated: true, completion: nil)
}
#objc func handleMessages() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let messagesVC = storyboard.instantiateViewController(withIdentifier: "MessagesVC")
self.present(messagesVC, animated: true, completion: nil)
}
#objc func moreInfoTapped() {
let userDetailsController = UserDetailsVC()
userDetailsController.userId = userId
present(userDetailsController, animated: true, completion: nil)
}
#objc func messageUserTapped() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let messagesVC = storyboard.instantiateViewController(withIdentifier: "MessagesVC")
let m = MessagesVC()
m.userId = userId
self.present(messagesVC, animated: true, completion: nil)
// go to specific user chat after this transition
}
func setupCards() {
for user in users {
let gradientView = GlympsGradientView()
let barsStackView = UIStackView()
let moreInfoButton = UIButton(type: .system)
moreInfoButton.setImage(#imageLiteral(resourceName: "info_icon").withRenderingMode(.alwaysOriginal), for: .normal)
moreInfoButton.isUserInteractionEnabled = true
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped), for: .touchUpInside)
let messageUserButton = UIButton(type: .system)
messageUserButton.setImage(#imageLiteral(resourceName: "message-icon2").withRenderingMode(.alwaysOriginal), for: .normal)
messageUserButton.isUserInteractionEnabled = true
messageUserButton.addTarget(self, action: #selector(messageUserTapped), for: .touchUpInside)
gradientView.layer.opacity = 0.5
let cardView = CardView(frame: .zero)
cardView.userId = user.id
userId = user.id
cardView.images = user.profileImages
if let photoUrlString = user.profileImages {
let photoUrl = URL(string: photoUrlString[0])
cardView.imageView.sd_setImage(with: photoUrl)
}
(0..<user.profileImages!.count).forEach { (_) in
let barView = UIView()
barView.backgroundColor = UIColor(white: 0, alpha: 0.1)
barView.layer.cornerRadius = barView.frame.size.height / 2
barsStackView.addArrangedSubview(barView)
barsStackView.arrangedSubviews.first?.backgroundColor = .white
}
let nametraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]
var nameFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
nameFontDescriptor = nameFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: nametraits])
let agetraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.light]
var ageFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
ageFontDescriptor = ageFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: agetraits])
let jobtraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.light]
var jobFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
jobFontDescriptor = jobFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: jobtraits])
let attributedText = NSMutableAttributedString(string: user.name!, attributes: [.font: UIFont(descriptor: nameFontDescriptor, size: 30)])
attributedText.append(NSAttributedString(string: " \(user.age!)", attributes: [.font: UIFont(descriptor: ageFontDescriptor, size: 24)]))
if user.profession != "" && user.company != "" {
attributedText.append(NSAttributedString(string: "\n\(user.profession!) # \(user.company!)", attributes: [.font: UIFont(descriptor: jobFontDescriptor, size: 20)]))
}
cardView.informationLabel.attributedText = attributedText
// cardsDeckView.addSubview(cardView)
cardView.addSubview(gradientView)
cardView.addSubview(barsStackView)
cardView.addSubview(moreInfoButton)
cardView.addSubview(messageUserButton)
cardView.moreInfoButton = moreInfoButton
cardView.messageUserButton = messageUserButton
cardView.stackView = barsStackView
moreInfoButton.anchor(top: nil, leading: nil, bottom: cardView.bottomAnchor, trailing: cardView.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 20, right: 20), size: .init(width: 50, height: 50))
messageUserButton.anchor(top: cardView.topAnchor, leading: nil, bottom: nil, trailing: cardView.trailingAnchor, padding: .init(top: 25, left: 0, bottom: 0, right: 25), size: .init(width: 44, height: 44))
barsStackView.anchor(top: cardView.topAnchor, leading: cardView.leadingAnchor, bottom: nil, trailing: cardView.trailingAnchor, padding: .init(top: 8, left: 8, bottom: 0, right: 8), size: .init(width: 0, height: 4))
barsStackView.spacing = 4
barsStackView.distribution = .fillEqually
cardView.fillSuperview()
gradientView.fillSuperview()
hud.textLabel.text = "All done! \u{1F389}"
hud.dismiss(afterDelay: 0.0)
self.cardsDeckView?.appendContent(view: cardView)
}
}
}
extension NSCoder {
class func empty() -> NSCoder {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.finishEncoding()
return NSKeyedUnarchiver(forReadingWith: data as Data)
}
}
extension Array {
public mutating func appendDistinct<S>(contentsOf newElements: S, where condition:#escaping (Element, Element) -> Bool) where S : Sequence, Element == S.Element {
newElements.forEach { (item) in
if !(self.contains(where: { (selfItem) -> Bool in
return !condition(selfItem, item)
})) {
self.append(item)
}
}
}
}
See setupUsers(), and see how the cardView is being created with buttons. How do I get these userIds from the cardViews and pass them to the UserDetails ViewController once the moreInfo button is pressed? Could I add target/selector to these buttons in the cardView? Any suggestions would help! Thanks!
This will definitely works!
In setupCards() function, use below code as follows:
Button layer's name is of type String which will contain your user id and catch it further using it's layer's name.
moreInfoButton.layer.name = user.id
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped(_:)), for: .touchUpInside)
In moreInfoTapped selector method, add parameters as mentioned below and pass it further to desired controller.
#objc func moreInfoTapped(_ sender: UIButton) {
let userDetailsController = UserDetailsVC()
userDetailsController.userId = sender.layer.name
present(userDetailsController, animated: true, completion: nil)
}
There are two problems.
First, the thing you want to do is impossible. You cannot pass extra data into an target/action button event call, because it is not your call. It is Cocoa's call.
Second, your action method signature is defective:
#objc func moreInfoTapped() {
The correct signature is:
#objc func moreInfoTapped(_ sender:Any) {
If you write the action method that way, you can retrieve the sender — the particular button that was tapped. Now you can work out what button this is and where it is, and so forth, and figure out what data you want to pass in response.
What you can do is Create a Custom class of UIButton. Create your required variables in that custom button class.
Pass your userId, add addTarget.
moreInfoButton.userId = user.id
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped(_:)), for: .touchUpInside)
class CustomButton: UIButton {
var userId: String?
}
#objc func moreInfoTapped(_ sender: CustomButton) {
print(sender.userId)
}
You don't send parameters to button selectors. There is a fixed method signature for IBAction methods.
The IBAction is a method of the target (usually the view controller). The target should hold the additional state data you need to decide what to do.
You posted a lot of code without much explanation and I don't have time to go through that code and figure it out.
I gather you have a button action on one view controller that needs to link to another view controller. The first view controller should know the userIDs that it need to send to the other view controller. The first view controller should have instance variables that give it access to that information. Your IBAction methods have access to the instance variables of the object that implements those IBActions.

UIView gesture recognizer not working when added in topViewController

I'm trying to add a view on the top most ViewController. And it is actually working, but when I tried to add a tap gesture recognizer, the action doesn't work. Here is my code, and I call this class using the shared instance. Could it be because the object is not being retained?
class QFNotificationView: NSObject {
static let shared = QFNotificationView()
internal let keyWindow = UIApplication.shared.keyWindow!
private var titleLabel: UILabel = {
let windowFrame = UIApplication.shared.keyWindow!.bounds
let label = UILabel(frame: CGRect(x: 12, y: 15, width: windowFrame.width - 24, height: 20))
label.font = UIFont(name: "WorkSans-SemiBold", size: 18)
label.textColor = .darkishPurple
return label
}()
private var descriptionLabel: UILabel = {
let windowFrame = UIApplication.shared.keyWindow!.bounds
let label = UILabel(frame: CGRect(x: 12, y: 35, width: windowFrame.width - 24, height: 40))
label.font = UIFont(name: "WorkSans-Regular", size: 14)
label.numberOfLines = 2
label.textColor = .darkLavender
return label
}()
private var notificationView: UIView = {
let windowFrame = UIApplication.shared.keyWindow!.bounds
let view = UIView(frame: CGRect(x: 0, y: -82, width: windowFrame.width, height: 82))
view.backgroundColor = .white
view.isUserInteractionEnabled = true
return view
}()
var message: Message?
func show(message: Message) {
self.message = message
titleLabel.text = "Message"
descriptionLabel.text = message.messageText
notificationView.addSubview(titleLabel)
notificationView.addSubview(descriptionLabel)
if let topVC = UIApplication.getTopMostViewController() {
topVC.view.addSubview(notificationView)
notificationView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap)))
animateNotification()
}
}
private func animateNotification() {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
self.notificationView.frame = CGRect(x: 0, y: 0, width: self.keyWindow.bounds.width, height: 82)
}) { (_) in
UIView.animate(withDuration: 0.5, delay: 4, options: .curveEaseInOut, animations: {
self.notificationView.frame = CGRect(x: 0, y: -82, width: self.keyWindow.bounds.width, height: 82)
}) { (_) in
self.notificationView.removeFromSuperview()
}
}
}
#objc func didTap() {
print("CLICKEDD")
if let topVC = UIApplication.getTopMostViewController() {
let dest = ChatViewController.instantiateFromAppStoryboard(appStoryboard: .Main)
dest.serviceRequestID = message?.serviceRequest
topVC.navigationController?.pushViewController(dest, animated: true)
}
}
}
I've just found the problem. You can't interact with a view when it is being animate, and that was the case. So I just created a container view to hold the notification card and placed the gesture recognizer on the container instead of the view that was being animated.

how to put badge on UIBarButtonItem in swift 4?

I want put badge on UIBarButtonItem. for that I use the following reference
Add badge alert in right bar button item in swift
in this I create the 'UIBarButtonItem+Badge.swift' file and put that code in it. In my viewcontroller I take the outlet of the UIBarButtonItem. And call the function but it didn't work for me. my viewcontroller file is this
My UIBarButtonItem+Badge.swift file is
extension CAShapeLayer {
func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
private var handle: UInt8 = 0;
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func setBadge(text: String?, withOffsetFromTopRight offset: CGPoint = CGPoint.zero, andColor color:UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
{
badgeLayer?.removeFromSuperlayer()
if (text == nil || text == "") {
return
}
addBadge(text: text!, withOffset: offset, andColor: color, andFilled: filled)
}
func addBadge(text: String, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true, andFontSize fontSize: CGFloat = 11)
{
guard let view = self.value(forKey: "view") as? UIView else { return }
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) { font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: UIFont.Weight.regular) }
let badgeSize = text.size(withAttributes: [NSAttributedString.Key.font: font])
// Initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height;
var width = badgeSize.width + 2 /* padding */
//make sure we have at least a circle
if (width < height) {
width = height
}
//x position is offset from right-hand side
let x = view.frame.width - width + offset.x
let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// Initialiaze Badge's label
let label = CATextLayer()
label.string = text
label.alignmentMode = CATextLayerAlignmentMode.center
label.font = font
label.fontSize = font.pointSize
label.frame = badgeFrame
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
private func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
my viewcontroller file is this
import UIKit
#IBOutlet weak var notificationLabel: UIBarButtonItem!
in view didload function
notificationLabel.addBadge(text: "4")
Here is a swift 4 solution of #VishalPethani with small convenient changes.
Add this UIBarButtonItem to you code:
class BadgedButtonItem: UIBarButtonItem {
public func setBadge(with value: Int) {
self.badgeValue = value
}
private var badgeValue: Int? {
didSet {
if let value = badgeValue,
value > 0 {
lblBadge.isHidden = false
lblBadge.text = "\(value)"
} else {
lblBadge.isHidden = true
}
}
}
var tapAction: (() -> Void)?
private let filterBtn = UIButton()
private let lblBadge = UILabel()
override init() {
super.init()
setup()
}
init(with image: UIImage?) {
super.init()
setup(image: image)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup(image: UIImage? = nil) {
self.filterBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.filterBtn.adjustsImageWhenHighlighted = false
self.filterBtn.setImage(image, for: .normal)
self.filterBtn.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
self.lblBadge.frame = CGRect(x: 20, y: 0, width: 15, height: 15)
self.lblBadge.backgroundColor = .red
self.lblBadge.clipsToBounds = true
self.lblBadge.layer.cornerRadius = 7
self.lblBadge.textColor = UIColor.white
self.lblBadge.font = UIFont.systemFont(ofSize: 10)
self.lblBadge.textAlignment = .center
self.lblBadge.isHidden = true
self.lblBadge.minimumScaleFactor = 0.1
self.lblBadge.adjustsFontSizeToFitWidth = true
self.filterBtn.addSubview(lblBadge)
self.customView = filterBtn
}
#objc func buttonPressed() {
if let action = tapAction {
action()
}
}
}
And then you can use it like that:
class ViewController: UIViewController {
let btn = BadgedButtonItem(with: UIImage(named: "your_image"))
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = btn
btn.tapAction = {
self.btn.setBadge(with: 1)
}
}
}
Here is a repository for that with some customisation
https://github.com/Syngmaster/BadgedBarButtonItem
Here it is a simple solution for putting the badge on a navigation bar
let filterBtn = UIButton.init(frame: CGRect.init(x: 0, y: 0, width: 30, height: 30))
filterBtn.setImage(UIImage.fontAwesomeIcon(name: .filter, style: .solid,
textColor: UIColor.white,
size: CGSize(width: 25, height: 25)), for: .normal)
filterBtn.addTarget(self, action: #selector(filterTapped), for: .touchUpInside)
let lblBadge = UILabel.init(frame: CGRect.init(x: 20, y: 0, width: 15, height: 15))
self.lblBadge.backgroundColor = COLOR_GREEN
self.lblBadge.clipsToBounds = true
self.lblBadge.layer.cornerRadius = 7
self.lblBadge.textColor = UIColor.white
self.lblBadge.font = FontLatoRegular(s: 10)
self.lblBadge.textAlignment = .center
filterBtn.addSubview(self.lblBadge)
self.navigationItem.rightBarButtonItems = [UIBarButtonItem.init(customView: filterBtn)]
In your case
self.navigationItem.rightBarButtonItems = [notificationLabel.init(customView: filterBtn)]
import Foundation
import UIKit
extension UIBarButtonItem {
convenience init(icon: UIImage, badge: String, _ badgeBackgroundColor: UIColor = #colorLiteral(red: 0.9156965613, green: 0.380413115, blue: 0.2803866267, alpha: 1), target: Any? = self, action: Selector? = nil) {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
imageView.image = icon
let label = UILabel(frame: CGRect(x: -8, y: -5, width: 18, height: 18))
label.text = badge
label.backgroundColor = badgeBackgroundColor
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 10)
label.clipsToBounds = true
label.layer.cornerRadius = 18 / 2
label.textColor = .white
let buttonView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
buttonView.addSubview(imageView)
buttonView.addSubview(label)
buttonView.addGestureRecognizer(UITapGestureRecognizer.init(target: target, action: action))
self.init(customView: buttonView)
}
}
Use:
item = UIBarButtonItem(icon: UIImage(), badge: "\(Test)", target: self, action: nil)
self.navigationItem.rightBarButtonItems = [item]
extension UIBarButtonItem {
func setBadge(with value: Int) {
guard let lblBadge = customView?.viewWithTag(100) as? UILabel else { return }
if value > 0 {
lblBadge.isHidden = false
lblBadge.text = "\(value)"
} else {
lblBadge.isHidden = true
}
}
func setup(image: UIImage? = nil) {
customView?.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let lblBadge = UILabel()
lblBadge.frame = CGRect(x: 20, y: 0, width: 15, height: 15)
lblBadge.backgroundColor = .red
lblBadge.tag = 100
lblBadge.clipsToBounds = true
lblBadge.layer.cornerRadius = 7
lblBadge.textColor = UIColor.white
lblBadge.font = UIFont.systemFont(ofSize: 10)
lblBadge.textAlignment = .center
lblBadge.isHidden = true
lblBadge.minimumScaleFactor = 0.1
lblBadge.adjustsFontSizeToFitWidth = true
customView?.addSubview(lblBadge)
}
}
Steps:
Drag an drop an UIButton in navigation/toolbar or add it programmatically using customView initializer.
Call setup method in view didload:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
itemsButton.setup(image: UIImage(named: "image_name"))
}
Connect an #IBOutlet/or programmatically and call setBadge method everywhere you need:
badgeButton.setBadge(with: 10)

Start activity indicator view in iOS and Swift [duplicate]

I wanna show, programmatically, an activity indicator with text, like the one in the Photos app (after editing and saving a picture). How can I do this?
Xcode 9.0 • Swift 4.0
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var filterButton: UIButton!
#IBOutlet weak var saveButton: UIButton!
let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
.appendingPathComponent("filteredImage.png")
let imagePicker = UIImagePickerController()
let messageFrame = UIView()
var activityIndicator = UIActivityIndicatorView()
var strLabel = UILabel()
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
func activityIndicator(_ title: String) {
strLabel.removeFromSuperview()
activityIndicator.removeFromSuperview()
effectView.removeFromSuperview()
strLabel = UILabel(frame: CGRect(x: 50, y: 0, width: 160, height: 46))
strLabel.text = title
strLabel.font = .systemFont(ofSize: 14, weight: .medium)
strLabel.textColor = UIColor(white: 0.9, alpha: 0.7)
effectView.frame = CGRect(x: view.frame.midX - strLabel.frame.width/2, y: view.frame.midY - strLabel.frame.height/2 , width: 160, height: 46)
effectView.layer.cornerRadius = 15
effectView.layer.masksToBounds = true
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .white)
activityIndicator.frame = CGRect(x: 0, y: 0, width: 46, height: 46)
activityIndicator.startAnimating()
effectView.contentView.addSubview(activityIndicator)
effectView.contentView.addSubview(strLabel)
view.addSubview(effectView)
}
func saveImage() {
do {
try imageView.image?.data?.write(to: destinationUrl, options: .atomic)
print("file saved")
} catch {
print(error)
}
}
func applyFilterToImage() {
imageView.image = imageView.image?.applying(contrast: 1.5)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/a/a8/VST_images_the_Lagoon_Nebula.jpg"), let data = try? Data(contentsOf: url), let image = UIImage(data: data) else { return }
view.backgroundColor = UIColor(white: 0, alpha: 1)
imageView.image = image
}
#IBAction func startSavingImage(_ sender: AnyObject) {
saveButton.isEnabled = false
filterButton.isEnabled = false
activityIndicator("Saving Image")
DispatchQueue.main.async {
self.saveImage()
DispatchQueue.main.async {
self.effectView.removeFromSuperview()
self.saveButton.isEnabled = true
self.filterButton.isEnabled = true
}
}
}
#IBAction func filterAction(_ sender: AnyObject) {
filterButton.isEnabled = false
saveButton.isEnabled = false
activityIndicator("Applying Filter")
DispatchQueue.main.async {
self.applyFilterToImage()
DispatchQueue.main.async {
self.effectView.removeFromSuperview()
self.filterButton.isEnabled = true
self.saveButton.isEnabled = true
}
}
}
#IBAction func cameraAction(_ sender: AnyObject) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
imagePicker.delegate = self
imagePicker.sourceType = .camera
present(imagePicker, animated: true)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [AnyHashable: Any]!) {
dismiss(animated: true, completion: nil)
imageView.image = image
}
}
extension Data {
var image: UIImage? { return UIImage(data: self) }
}
extension UIImage {
var data: Data? { return UIImagePNGRepresentation(self) }
func applying(contrast value: NSNumber) -> UIImage? {
guard let ciImage = CIImage(image: self)?.applyingFilter("CIColorControls", withInputParameters: [kCIInputContrastKey: value]) else { return nil }
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
UIImage(ciImage: ciImage).draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
While Esq's answer works, I've added my own implementation which is more in line with good component architecture by separating the view into it's own class. It also uses dynamic blurring introduced in iOS 8.
Here is how mine looks with an image background:
The code for this is encapsulated in it's own UIView class which means you can reuse it whenever you desire.
Updated for Swift 3
Usage
func viewDidLoad() {
super.viewDidLoad()
// Create and add the view to the screen.
let progressHUD = ProgressHUD(text: "Saving Photo")
self.view.addSubview(progressHUD)
// All done!
self.view.backgroundColor = UIColor.black
}
UIView Code
import UIKit
class ProgressHUD: UIVisualEffectView {
var text: String? {
didSet {
label.text = text
}
}
let activityIndictor: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
let label: UILabel = UILabel()
let blurEffect = UIBlurEffect(style: .light)
let vibrancyView: UIVisualEffectView
init(text: String) {
self.text = text
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
super.init(effect: blurEffect)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.text = ""
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
super.init(coder: aDecoder)
self.setup()
}
func setup() {
contentView.addSubview(vibrancyView)
contentView.addSubview(activityIndictor)
contentView.addSubview(label)
activityIndictor.startAnimating()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
let width = superview.frame.size.width / 2.3
let height: CGFloat = 50.0
self.frame = CGRect(x: superview.frame.size.width / 2 - width / 2,
y: superview.frame.height / 2 - height / 2,
width: width,
height: height)
vibrancyView.frame = self.bounds
let activityIndicatorSize: CGFloat = 40
activityIndictor.frame = CGRect(x: 5,
y: height / 2 - activityIndicatorSize / 2,
width: activityIndicatorSize,
height: activityIndicatorSize)
layer.cornerRadius = 8.0
layer.masksToBounds = true
label.text = text
label.textAlignment = NSTextAlignment.center
label.frame = CGRect(x: activityIndicatorSize + 5,
y: 0,
width: width - activityIndicatorSize - 15,
height: height)
label.textColor = UIColor.gray
label.font = UIFont.boldSystemFont(ofSize: 16)
}
}
func show() {
self.isHidden = false
}
func hide() {
self.isHidden = true
}
}
Swift 2
An example on how to use it is like this:
override func viewDidLoad() {
super.viewDidLoad()
// Create and add the view to the screen.
let progressHUD = ProgressHUD(text: "Saving Photo")
self.view.addSubview(progressHUD)
// All done!
self.view.backgroundColor = UIColor.blackColor()
}
Here is the UIView code:
import UIKit
class ProgressHUD: UIVisualEffectView {
var text: String? {
didSet {
label.text = text
}
}
let activityIndictor: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.White)
let label: UILabel = UILabel()
let blurEffect = UIBlurEffect(style: .Light)
let vibrancyView: UIVisualEffectView
init(text: String) {
self.text = text
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blurEffect))
super.init(effect: blurEffect)
self.setup()
}
required init(coder aDecoder: NSCoder) {
self.text = ""
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blurEffect))
super.init(coder: aDecoder)
self.setup()
}
func setup() {
contentView.addSubview(vibrancyView)
vibrancyView.contentView.addSubview(activityIndictor)
vibrancyView.contentView.addSubview(label)
activityIndictor.startAnimating()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
let width = superview.frame.size.width / 2.3
let height: CGFloat = 50.0
self.frame = CGRectMake(superview.frame.size.width / 2 - width / 2,
superview.frame.height / 2 - height / 2,
width,
height)
vibrancyView.frame = self.bounds
let activityIndicatorSize: CGFloat = 40
activityIndictor.frame = CGRectMake(5, height / 2 - activityIndicatorSize / 2,
activityIndicatorSize,
activityIndicatorSize)
layer.cornerRadius = 8.0
layer.masksToBounds = true
label.text = text
label.textAlignment = NSTextAlignment.Center
label.frame = CGRectMake(activityIndicatorSize + 5, 0, width - activityIndicatorSize - 15, height)
label.textColor = UIColor.grayColor()
label.font = UIFont.boldSystemFontOfSize(16)
}
}
func show() {
self.hidden = false
}
func hide() {
self.hidden = true
}
}
Heres how this code looks:
Heres my drag and drop code:
var boxView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.blackColor()
addSavingPhotoView()
//Custom button to test this app
var button = UIButton(frame: CGRect(x: 20, y: 20, width: 20, height: 20))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(button)
}
func addSavingPhotoView() {
// You only need to adjust this frame to move it anywhere you want
boxView = UIView(frame: CGRect(x: view.frame.midX - 90, y: view.frame.midY - 25, width: 180, height: 50))
boxView.backgroundColor = UIColor.whiteColor()
boxView.alpha = 0.8
boxView.layer.cornerRadius = 10
//Here the spinnier is initialized
var activityView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
activityView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
activityView.startAnimating()
var textLabel = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
textLabel.textColor = UIColor.grayColor()
textLabel.text = "Saving Photo"
boxView.addSubview(activityView)
boxView.addSubview(textLabel)
view.addSubview(boxView)
}
func buttonAction(sender:UIButton!) {
//When button is pressed it removes the boxView from screen
boxView.removeFromSuperview()
}
Here is an open source version of this: https://github.com/goktugyil/CozyLoadingActivity
Based o my previous answer, here is a more elegant solution with a custom class:
First define this custom class:
import UIKit
import Foundation
class ActivityIndicatorView
{
var view: UIView!
var activityIndicator: UIActivityIndicatorView!
var title: String!
init(title: String, center: CGPoint, width: CGFloat = 200.0, height: CGFloat = 50.0)
{
self.title = title
let x = center.x - width/2.0
let y = center.y - height/2.0
self.view = UIView(frame: CGRect(x: x, y: y, width: width, height: height))
self.view.backgroundColor = UIColor(red: 255.0/255.0, green: 204.0/255.0, blue: 51.0/255.0, alpha: 0.5)
self.view.layer.cornerRadius = 10
self.activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
self.activityIndicator.color = UIColor.blackColor()
self.activityIndicator.hidesWhenStopped = false
let titleLabel = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
titleLabel.text = title
titleLabel.textColor = UIColor.blackColor()
self.view.addSubview(self.activityIndicator)
self.view.addSubview(titleLabel)
}
func getViewActivityIndicator() -> UIView
{
return self.view
}
func startAnimating()
{
self.activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
}
func stopAnimating()
{
self.activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
self.view.removeFromSuperview()
}
//end
}
Now on your UIViewController class:
var activityIndicatorView: ActivityIndicatorView!
override func viewDidLoad()
{
super.viewDidLoad()
self.activityIndicatorView = ActivityIndicatorView(title: "Processing...", center: self.view.center)
self.view.addSubview(self.activityIndicatorView.getViewActivityIndicator())
}
func doSomething()
{
self.activityIndicatorView.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
//do something here that will taking time
self.activityIndicatorView.stopAnimating()
}
For activity indicator, its better you create one custom class.
Instead of creating UIActivityIndicator in each UIViewController.Subclass UIView and use from any UIViewController.
Updated for Swift 5.0:
import UIKit
import Foundation
class ProgressIndicator: UIView {
var indicatorColor:UIColor
var loadingViewColor:UIColor
var loadingMessage:String
var messageFrame = UIView()
var activityIndicator = UIActivityIndicatorView()
init(inview:UIView,loadingViewColor:UIColor,indicatorColor:UIColor,msg:String){
self.indicatorColor = indicatorColor
self.loadingViewColor = loadingViewColor
self.loadingMessage = msg
super.init(frame: CGRect(x: inview.frame.midX - 90, y: inview.frame.midY - 250 , width: 180, height: 50))
initalizeCustomIndicator()
}
convenience init(inview:UIView) {
self.init(inview: inview,loadingViewColor: UIColor.brown,indicatorColor:UIColor.black, msg: "Loading..")
}
convenience init(inview:UIView,messsage:String) {
self.init(inview: inview,loadingViewColor: UIColor.brown,indicatorColor:UIColor.black, msg: messsage)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initalizeCustomIndicator(){
messageFrame.frame = self.bounds
activityIndicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium)
activityIndicator.tintColor = indicatorColor
activityIndicator.hidesWhenStopped = true
activityIndicator.frame = CGRect(x: self.bounds.origin.x + 6, y: 0, width: 20, height: 50)
print(activityIndicator.frame)
let strLabel = UILabel(frame:CGRect(x: self.bounds.origin.x + 30, y: 0, width: self.bounds.width - (self.bounds.origin.x + 30) , height: 50))
strLabel.text = loadingMessage
strLabel.adjustsFontSizeToFitWidth = true
strLabel.textColor = UIColor.white
messageFrame.layer.cornerRadius = 15
messageFrame.backgroundColor = loadingViewColor
messageFrame.alpha = 0.8
messageFrame.addSubview(activityIndicator)
messageFrame.addSubview(strLabel)
}
func start(){
//check if view is already there or not..if again started
if !self.subviews.contains(messageFrame){
activityIndicator.startAnimating()
self.addSubview(messageFrame)
}
}
func stop(){
if self.subviews.contains(messageFrame){
activityIndicator.stopAnimating()
messageFrame.removeFromSuperview()
}
}
}
Put this class in your project and then call from any ViewController as
var indicator:ProgressIndicator?
override func viewDidLoad() {
super.viewDidLoad()
//indicator = ProgressIndicator(inview: self.view,messsage: "Hello from Nepal..")
//self.view.addSubview(indicator!)
//OR
indicator = ProgressIndicator(inview:self.view,loadingViewColor: UIColor.grayColor(), indicatorColor: UIColor.blackColor(), msg: "Landing within minutes,Please hold tight..")
self.view.addSubview(indicator!)
}
#IBAction func startBtn(sender: AnyObject) {
indicator!.start()
}
#IBAction func stopBtn(sender: AnyObject) {
indicator!.stop()
}
For Swift 3
Usage
class LoginTVC: UITableViewController {
var loadingView : LoadingView!
override func viewDidLoad() {
super.viewDidLoad()
// CASE 1: To Show loadingView on load
loadingView = LoadingView(uiView: view, message: "Sending you verification code")
}
// CASE 2: To show loadingView on click of a button
#IBAction func showLoadingView(_ sender: UIButton) {
if let loaderView = loadingView{ // If loadingView already exists
if loaderView.isHidden() {
loaderView.show() // To show activity indicator
}
}
else{
loadingView = LoadingView(uiView: view, message: "Sending you verification code")
}
}
}
// CASE 3: To hide LoadingView on click of a button
#IBAction func hideLoadingView(_ sender: UIButton) {
if let loaderView = loadingView{ // If loadingView already exists
self.loadingView.hide()
}
}
}
LoadingView Class
class LoadingView {
let uiView : UIView
let message : String
let messageLabel = UILabel()
let loadingSV = UIStackView()
let loadingView = UIView()
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
init(uiView: UIView, message: String) {
self.uiView = uiView
self.message = message
self.setup()
}
func setup(){
let viewWidth = uiView.bounds.width
let viewHeight = uiView.bounds.height
// Configuring the message label
messageLabel.text = message
messageLabel.textColor = UIColor.darkGray
messageLabel.textAlignment = .center
messageLabel.numberOfLines = 3
messageLabel.lineBreakMode = .byWordWrapping
// Creating stackView to center and align Label and Activity Indicator
loadingSV.axis = .vertical
loadingSV.distribution = .equalSpacing
loadingSV.alignment = .center
loadingSV.addArrangedSubview(activityIndicator)
loadingSV.addArrangedSubview(messageLabel)
// Creating loadingView, this acts as a background for label and activityIndicator
loadingView.frame = uiView.frame
loadingView.center = uiView.center
loadingView.backgroundColor = UIColor.darkGray.withAlphaComponent(0.3)
loadingView.clipsToBounds = true
// Disabling auto constraints
loadingSV.translatesAutoresizingMaskIntoConstraints = false
// Adding subviews
loadingView.addSubview(loadingSV)
uiView.addSubview(loadingView)
activityIndicator.startAnimating()
// Views dictionary
let views = [
"loadingSV": loadingSV
]
// Constraints for loadingSV
uiView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[loadingSV(300)]-|", options: [], metrics: nil, views: views))
uiView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-\(viewHeight / 3)-[loadingSV(50)]-|", options: [], metrics: nil, views: views))
}
// Call this method to hide loadingView
func show() {
loadingView.isHidden = false
}
// Call this method to show loadingView
func hide(){
loadingView.isHidden = true
}
// Call this method to check if loading view already exists
func isHidden() -> Bool{
if loadingView.isHidden == false{
return false
}
else{
return true
}
}
}
You can create your own. For example:
Create a view with white background and rounded corners:
var view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
view.backgroundColor = UIColor.whiteColor()
view.layer.cornerRadius = 10
Add two subviews, a UIActivityIndicatorView and a UILabel:
var wait = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
wait.color = UIColor.blackColor()
wait.hidesWhenStopped = false
var text = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
text.text = "Processing..."
view.addSubview(wait)
view.addSubview(text)
Based on "MirekE" answer here is a code that i tested now and its working:
var activityIndicator: UIActivityIndicatorView!
var viewActivityIndicator: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
let width: CGFloat = 200.0
let height: CGFloat = 50.0
let x = self.view.frame.width/2.0 - width/2.0
let y = self.view.frame.height/2.0 - height/2.0
self.viewActivityIndicator = UIView(frame: CGRect(x: x, y: y, width: width, height: height))
self.viewActivityIndicator.backgroundColor = UIColor(red: 255.0/255.0, green: 204.0/255.0, blue: 51.0/255.0, alpha: 0.5)
self.viewActivityIndicator.layer.cornerRadius = 10
self.activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
self.activityIndicator.color = UIColor.blackColor()
self.activityIndicator.hidesWhenStopped = false
let titleLabel = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
titleLabel.text = "Processing..."
self.viewActivityIndicator.addSubview(self.activityIndicator)
self.viewActivityIndicator.addSubview(titleLabel)
self.view.addSubview(self.viewActivityIndicator)
}
func doSometing()
{
self.activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
//do something here that will taking time
self.activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
self.viewActivityIndicator.removeFromSuperview()
}
simple activity controller class !!!
class ActivityIndicator: UIVisualEffectView {
let activityIndictor: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.WhiteLarge)
let label: UILabel = UILabel()
let blurEffect = UIBlurEffect(style: .Dark)
let vibrancyView: UIVisualEffectView
init() {
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blurEffect))
super.init(effect: blurEffect)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blurEffect))
super.init(coder: aDecoder)
self.setup()
}
func setup() {
contentView.addSubview(vibrancyView)
vibrancyView.contentView.addSubview(activityIndictor)
activityIndictor.startAnimating()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
let width: CGFloat = 75.0
let height: CGFloat = 75.0
self.frame = CGRectMake(superview.frame.size.width / 2 - width / 2,
superview.frame.height / 2 - height / 2,
width,
height)
vibrancyView.frame = self.bounds
let activityIndicatorSize: CGFloat = 40
activityIndictor.frame = CGRectMake(18, height / 2 - activityIndicatorSize / 2,
activityIndicatorSize,
activityIndicatorSize)
layer.cornerRadius = 8.0
layer.masksToBounds = true
}
}
func show() {
self.hidden = false
}
func hide() {
self.hidden = true
}}
usage :-
let activityIndicator = ActivityIndicator()
self.view.addSubview(activityIndicator)
to hide :-
activityIndicator.hide()
Xcode 10.1 • Swift 4.2
import UIKit
class ProgressHUD: UIVisualEffectView {
var title: String?
var theme: UIBlurEffect.Style = .light
let strLabel = UILabel(frame: CGRect(x: 50, y: 0, width: 160, height: 46))
let activityIndicator = UIActivityIndicatorView()
init(title: String, theme: UIBlurEffect.Style = .light) {
super.init(effect: UIBlurEffect(style: theme))
self.title = title
self.theme = theme
[activityIndicator, strLabel].forEach(contentView.addSubview(_:))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
frame = CGRect(x: superview.frame.midX - strLabel.frame.width / 2,
y: superview.frame.midY - strLabel.frame.height / 2, width: 160, height: 46)
layer.cornerRadius = 15.0
layer.masksToBounds = true
activityIndicator.frame = CGRect(x: 0, y: 0, width: 46, height: 46)
activityIndicator.startAnimating()
strLabel.text = title
strLabel.font = .systemFont(ofSize: 14, weight: UIFont.Weight.medium)
switch theme {
case .dark:
strLabel.textColor = .white
activityIndicator.style = .white
default:
strLabel.textColor = .gray
activityIndicator.style = .gray
}
}
}
func show() {
self.isHidden = false
}
func hide() {
self.isHidden = true
}
}
Use:
let progress = ProgressHUD(title: "Authorization", theme: .dark)
[progress].forEach(view.addSubview(_:))
With auto width and theme support also detects rotate while busy (Swift 3 version)
Use it like below:
var progressView: ProgressView?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.progressView = ProgressView(message: "Work in progress!",
theme: .dark,
isModal: true)
}
#IBAction func onPause(_ sender: AnyObject) {
self.progressView.show()
}
#IBAction func onResume(_ sender: AnyObject) {
self.progressView.hide()
}
ProgressView.swift
import UIKit
class ProgressView: UIView {
enum Theme {
case light
case dark
}
var theme: Theme
var container: UIStackView
var activityIndicator: UIActivityIndicatorView
var label: UILabel
var glass: UIView
private var message: String
private var isModal: Bool
init(message: String, theme: theme, isModal: Bool) {
// Init
self.message = message
self.theme = theme
self.isModal = isModal
self.container = UIStackView()
self.activityIndicator = UIActivityIndicatorView()
self.label = UILabel()
self.glass = UIView()
// Get proper width by text message
let fontName = self.label.font.fontName
let fontSize = self.label.font.pointSize
if let font = UIFont(name: fontName, size: fontSize) {
let fontAttributes = [NSFontAttributeName: font]
let size = (message as NSString).size(attributes: fontAttributes)
super.init(frame: CGRect(x: 0, y: 0, width: size.width + 50, height: 50))
} else {
super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
}
// Detect rotation
NotificationCenter.default.addObserver(self, selector: #selector(onRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
// Style
self.layer.cornerRadius = 3
if (self.theme == .dark) {
self.backgroundColor = .darkGray
} else {
self.backgroundColor = .lightGray
}
// Label
if self.theme == .dark {
self.label.textColor = .white
}else{
self.label.textColor = .black
}
self.label.text = self.message
// Container
self.container.frame = self.frame
self.container.spacing = 5
self.container.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
self.container.isLayoutMarginsRelativeArrangement = true
// Activity indicator
if (self.theme == .dark) {
self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
self.activityIndicator.color = .white
} else {
self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle:.whiteLarge)
self.activityIndicator.color = .black
}
self.activityIndicator.startAnimating()
// Add them to container
// First glass
if let superview = UIApplication.shared.keyWindow {
if (self.isModal) {
// glass
self.glass.frame = superview.frame;
if (self.theme == .dark) {
self.glass.backgroundColor = UIColor.black.withAlphaComponent(0.5)
} else {
self.glass.backgroundColor = UIColor.white.withAlphaComponent(0.5)
}
superview.addSubview(glass)
}
}
// Then activity indicator and label
container.addArrangedSubview(self.activityIndicator)
container.addArrangedSubview(self.label)
// Last attach it to container (StackView)
self.addSubview(container)
if let superview = UIApplication.shared.keyWindow {
self.center = superview.center
superview.addSubview(self)
}
//Do not show until show() is called
self.hide()
}
required init(coder: NSCoder) {
self.theme = .dark
self.Message = "Not set!"
self.isModal = true
self.container = UIStackView()
self.activityIndicator = UIActivityIndicatorView()
self.label = UILabel()
self.glass = UIView()
super.init(coder: coder)!
}
func onRotate() {
if let superview = self.superview {
self.glass.frame = superview.frame
self.center = superview.center
// superview.addSubview(self)
}
}
public func show() {
self.glass.isHidden = false
self.isHidden = false
}
public func hide() {
self.glass.isHidden = true
self.isHidden = true
}
}
This code work in SWIFT 2.0.
Must Declare a variable for initialize UIActivityIndicatorView
let actInd: UIActivityIndicatorView = UIActivityIndicatorView()
After initialize put this code in your controller.
actInd.center = ImageView.center
actInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
view.addSubview(actInd)
actInd.startAnimating()
after your download process complete then hide a animation.
self.actInd.stopAnimating()
In Swift 3
Declare variables which we will use
var activityIndicator = UIActivityIndicatorView()
let loadingView = UIView()
let loadingLabel = UILabel()
Set label , view and activityIndicator
func setLoadingScreen(myMsg : String) {
let width: CGFloat = 120
let height: CGFloat = 30
let x = (self.view.frame.width / 2) - (width / 2)
let y = (169 / 2) - (height / 2) + 60
loadingView.frame = CGRect(x: x, y: y, width: width, height: height)
self.loadingLabel.textColor = UIColor.white
self.loadingLabel.textAlignment = NSTextAlignment.center
self.loadingLabel.text = myMsg
self.loadingLabel.frame = CGRect(x: 0, y: 0, width: 160, height: 30)
self.loadingLabel.isHidden = false
self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.white
self.activityIndicator.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.activityIndicator.startAnimating()
loadingView.addSubview(self.spinner)
loadingView.addSubview(self.loadingLabel)
self.view.addSubview(loadingView)
}
Start Animation
#IBAction func start_animation(_ sender: Any) {
setLoadingScreen(myMsg: "Loading...")
}
Stop Animation
#IBAction func stop_animation(_ sender: Any) {
self.spinner.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
self.loadingLabel.isHidden = true
}
import UIKit
class ViewControllerUtils {
let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor(white: 0, alpha: 0.3)
return view
}()
let loadingView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor(white: 0, alpha: 0.7)
view.clipsToBounds = true
view.layer.cornerRadius = 10
return view
}()
let activityIndicatorView: UIActivityIndicatorView = {
let aiv = UIActivityIndicatorView()
aiv.translatesAutoresizingMaskIntoConstraints = false
aiv.style = UIActivityIndicatorView.Style.whiteLarge
return aiv
}()
let loadingLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Loading..."
label.textAlignment = .center
label.textColor = .white
label.font = .systemFont(ofSize: 15, weight: UIFont.Weight.medium)
return label
}()
func showLoader() {
guard let window = UIApplication.shared.keyWindow else { return }
window.addSubview(containerView)
containerView.addSubview(loadingView)
loadingView.addSubview(activityIndicatorView)
loadingView.addSubview(loadingLabel)
containerView.leftAnchor.constraint(equalTo: window.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: window.rightAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: window.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: window.bottomAnchor).isActive = true
loadingView.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
loadingView.centerYAnchor.constraint(equalTo: window.centerYAnchor).isActive = true
loadingView.widthAnchor.constraint(equalToConstant: 120).isActive = true
loadingView.heightAnchor.constraint(equalToConstant: 120).isActive = true
activityIndicatorView.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
activityIndicatorView.centerYAnchor.constraint(equalTo: window.centerYAnchor).isActive = true
activityIndicatorView.widthAnchor.constraint(equalToConstant: 60).isActive = true
activityIndicatorView.heightAnchor.constraint(equalToConstant: 60).isActive = true
loadingLabel.leftAnchor.constraint(equalTo: loadingView.leftAnchor).isActive = true
loadingLabel.rightAnchor.constraint(equalTo: loadingView.rightAnchor).isActive = true
loadingLabel.bottomAnchor.constraint(equalTo: loadingView.bottomAnchor).isActive = true
loadingLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true
DispatchQueue.main.async {
self.activityIndicatorView.startAnimating()
}
}
func hideLoader() {
DispatchQueue.main.async {
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.removeFromSuperview()
self.loadingLabel.removeFromSuperview()
self.loadingView.removeFromSuperview()
self.containerView.removeFromSuperview()
}
}
}
//// In order to show the activity indicator, call the function from your view controller
// let viewControllerUtils = ViewControllerUtils()
// viewControllerUtils.showLoader()
//// In order to hide the activity indicator, call the function from your view controller
// viewControllerUtils.hideLoader()
class ViewControllerUtils2 {
var container: UIView = UIView()
var loadingView: UIView = UIView()
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
let loadingLabel = UILabel()
func showLoader(_ uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColor(white: 0, alpha: 0.3)
loadingView.frame = CGRect(x: 0, y: 0, width: 120, height: 120)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColor(white: 0, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
activityIndicator.style = UIActivityIndicatorView.Style.whiteLarge
activityIndicator.center = CGPoint(x: loadingView.frame.size.width / 2, y: loadingView.frame.size.height / 2)
loadingLabel.frame = CGRect(x: 0, y: 80, width: 120, height: 40)
loadingLabel.text = "Loading..."
loadingLabel.textAlignment = .center
loadingLabel.textColor = .white
loadingLabel.font = .systemFont(ofSize: 15, weight: UIFont.Weight.medium)
uiView.addSubview(container)
container.addSubview(loadingView)
loadingView.addSubview(activityIndicator)
loadingView.addSubview(loadingLabel)
DispatchQueue.main.async {
self.activityIndicator.startAnimating()
}
}
func hideLoader() {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
self.loadingLabel.removeFromSuperview()
self.loadingView.removeFromSuperview()
self.container.removeFromSuperview()
}
}
}
For Swift 5
Indicator with label inside WKWebview
var strLabel = UILabel()
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
let loadingTextLabel = UILabel()
#IBOutlet var indicator: UIActivityIndicatorView!
#IBOutlet var webView: WKWebView!
var refController:UIRefreshControl = UIRefreshControl()
override func viewDidLoad() {
webView = WKWebView(frame: CGRect.zero)
webView.navigationDelegate = self
webView.uiDelegate = self as? WKUIDelegate
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
webView.allowsBackForwardNavigationGestures = true
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
setBackground()
}
func setBackground() {
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
func showActivityIndicator(show: Bool) {
if show {
strLabel = UILabel(frame: CGRect(x: 55, y: 0, width: 400, height: 66))
strLabel.text = "Please Wait. Checking Internet Connection..."
strLabel.font = UIFont(name: "Avenir Light", size: 12)
strLabel.textColor = UIColor(white: 0.9, alpha: 0.7)
effectView.frame = CGRect(x: view.frame.midX - strLabel.frame.width/2, y: view.frame.midY - strLabel.frame.height/2 , width: 300, height: 66)
effectView.layer.cornerRadius = 15
effectView.layer.masksToBounds = true
indicator = UIActivityIndicatorView(style: .white)
indicator.frame = CGRect(x: 0, y: 0, width: 66, height: 66)
indicator.startAnimating()
effectView.contentView.addSubview(indicator)
effectView.contentView.addSubview(strLabel)
indicator.transform = CGAffineTransform(scaleX: 1.4, y: 1.4);
effectView.center = webView.center
view.addSubview(effectView)
} else {
strLabel.removeFromSuperview()
effectView.removeFromSuperview()
indicator.removeFromSuperview()
indicator.stopAnimating()
}
}

Loading an "overlay" when running long tasks in iOS

What is example for loading overlay in Swift IOS application when do a long tasks. Example for loading data from remote server.
I googled but not found any answer.
Updated:
Thanks for #Sebastian Dressler this is simple way. I updated my code and it run cool
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(hex: 0x444444, 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()
}
}
let using:
LoadingOverlay.shared.showOverlay(self.view)
//To to long tasks
LoadingOverlay.shared.hideOverlayView()
The above answers add a loading view but it doesn't block click events on the screen also it does not provides overlay for rest of screen. You can achieve it as follows:
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .Alert)
alert.view.tintColor = UIColor.blackColor()
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
presentViewController(alert, animated: true, completion: nil)
Swift 3.0
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
Swift 4.0 and newer
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
and you can hide it as follows:
dismiss(animated: false, completion: nil)
It will be shown as follows:
Just create yourself an overlay view, which you add to your parent view and remove it once your task is done, e.g. to add it:
var overlay : UIView? // This should be a class variable
[ ... ]
overlay = UIView(frame: view.frame)
overlay!.backgroundColor = UIColor.blackColor()
overlay!.alpha = 0.8
view.addSubview(overlay!)
For removal:
overlay?.removeFromSuperview()
Blur background + Activity Indicator, Swift 5 example
extension UIView {
func showBlurLoader() {
let blurLoader = BlurLoader(frame: frame)
self.addSubview(blurLoader)
}
func removeBluerLoader() {
if let blurLoader = subviews.first(where: { $0 is BlurLoader }) {
blurLoader.removeFromSuperview()
}
}
}
class BlurLoader: UIView {
var blurEffectView: UIVisualEffectView?
override init(frame: CGRect) {
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = frame
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.blurEffectView = blurEffectView
super.init(frame: frame)
addSubview(blurEffectView)
addLoader()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addLoader() {
guard let blurEffectView = blurEffectView else { return }
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
blurEffectView.contentView.addSubview(activityIndicator)
activityIndicator.center = blurEffectView.contentView.center
activityIndicator.startAnimating()
}
}
For anyone late like me, I made some modifications to #Sonrobby code. As i understand, #Sonrobby adds the activity to the overlay on every showOverlay call. And some of the configuration can be passed to the init function, letting only the placement on the showOverlay method.
I also change the overlay's background to black, since my app it is mostly white.
here is the code :
public class LoadingOverlay{
var overlayView : UIView!
var activityIndicator : UIActivityIndicatorView!
class var shared: LoadingOverlay {
struct Static {
static let instance: LoadingOverlay = LoadingOverlay()
}
return Static.instance
}
init(){
self.overlayView = UIView()
self.activityIndicator = UIActivityIndicatorView()
overlayView.frame = CGRectMake(0, 0, 80, 80)
overlayView.backgroundColor = UIColor(white: 0, alpha: 0.7)
overlayView.clipsToBounds = true
overlayView.layer.cornerRadius = 10
overlayView.layer.zPosition = 1
activityIndicator.frame = CGRectMake(0, 0, 40, 40)
activityIndicator.center = CGPointMake(overlayView.bounds.width / 2, overlayView.bounds.height / 2)
activityIndicator.activityIndicatorViewStyle = .WhiteLarge
overlayView.addSubview(activityIndicator)
}
public func showOverlay(view: UIView) {
overlayView.center = view.center
view.addSubview(overlayView)
activityIndicator.startAnimating()
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
overlayView.removeFromSuperview()
}
}
#Ajinkya Patil answer as a reference. Swift 4.0 and newer
This is an Extension Solution to use on all viewController without clashing.
Create a LoadingDialog+ViewContoller.swift
import UIKit
struct ProgressDialog {
static var alert = UIAlertController()
static var progressView = UIProgressView()
static var progressPoint : Float = 0{
didSet{
if(progressPoint == 1){
ProgressDialog.alert.dismiss(animated: true, completion: nil)
}
}
}
}
extension UIViewController{
func LoadingStart(){
ProgressDialog.alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
ProgressDialog.alert.view.addSubview(loadingIndicator)
present(ProgressDialog.alert, animated: true, completion: nil)
}
func LoadingStop(){
ProgressDialog.alert.dismiss(animated: true, completion: nil)
}
}
call the function inside ViewController anywhere you like. like so:
self.LoadingStart()
and here's how to stop the loading dialog.
self.LoadingStop()
To add on to the answers given, you might run into issues if you are attempting to run the code sometimes. Personally, there was an occasion where showOverlay was not being properly called (because I was trying to segue into a scene, then immediately call this function during viewDidLoad).
If you run into an issue similar to mine, there is one fix to the code and a change in approach I recommend.
FIX: Place both blocks of code as closures to a dispatch_async call, like so:
dispatch_async(dispatch_get_main_queue(),
{ //code });
APPROACH: When calling your code, do a dispatch_after call onto the main queue to delay the call by a few milliseconds.
The reasoning? You're simply asking the UI to do too much during viewDidLoad.
If this appendix to the solution helped, I'd be glad.
-Joel Long
P.S. Solution worked for XCode v6.3.2
Use ATKit.
Refer:
https://aurvan.github.io/atkit-ios-release/index.html
ATProgressOverlay Class
https://aurvan.github.io/atkit-ios-release/helpbook/Classes/ATProgressOverlay.html
Code:
import ATKit
ATProgressOverlay.sharedInstance.show() // Does not show network activity indicator on status bar.
ATProgressOverlay.sharedInstance.show(isNetworkActivity: true) // Shows network activity indicator on status bar.
Screenshot:
Swift 5
class func showUniversalLoadingView(_ show: Bool, loadingText : String = "") {
let existingView = UIApplication.shared.windows[0].viewWithTag(1200)
if show {
if existingView != nil {
return
}
let loadingView = self.makeLoadingView(withFrame: UIScreen.main.bounds, loadingText: loadingText)
loadingView?.tag = 1200
UIApplication.shared.windows[0].addSubview(loadingView!)
} else {
existingView?.removeFromSuperview()
}
}
class func makeLoadingView(withFrame frame: CGRect, loadingText text: String?) -> UIView? {
let loadingView = UIView(frame: frame)
loadingView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
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 = loadingView.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = .white
activityIndicator.startAnimating()
activityIndicator.tag = 100 // 100 for example
loadingView.addSubview(activityIndicator)
if !text!.isEmpty {
let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 30))
let cpoint = CGPoint(x: activityIndicator.frame.origin.x + activityIndicator.frame.size.width / 2, y: activityIndicator.frame.origin.y + 80)
lbl.center = cpoint
lbl.textColor = UIColor.white
lbl.textAlignment = .center
lbl.text = text
lbl.tag = 1234
loadingView.addSubview(lbl)
}
return loadingView
}
Uses
showUniversalLoadingView(true, loadingText: "Downloading Data.......")
showUniversalLoadingView(true)
Remove loader
showUniversalLoadingView(false)
Updated #sonrobby answer, added a background view and orientation handling via resizing mask... this can be used for simple stuffs
public class LoadingOverlay{
var overlayView = UIView()
var activityIndicator = UIActivityIndicatorView()
var bgView = UIView()
class var shared: LoadingOverlay {
struct Static {
static let instance: LoadingOverlay = LoadingOverlay()
}
return Static.instance
}
public func showOverlay(view: UIView) {
bgView.frame = view.frame
bgView.backgroundColor = UIColor.gray
bgView.addSubview(overlayView)
bgView.autoresizingMask = [.flexibleLeftMargin,.flexibleTopMargin,.flexibleRightMargin,.flexibleBottomMargin,.flexibleHeight, .flexibleWidth]
overlayView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
overlayView.center = view.center
overlayView.autoresizingMask = [.flexibleLeftMargin,.flexibleTopMargin,.flexibleRightMargin,.flexibleBottomMargin]
overlayView.backgroundColor = UIColor.black
overlayView.clipsToBounds = true
overlayView.layer.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
activityIndicator.activityIndicatorViewStyle = .whiteLarge
activityIndicator.center = CGPoint(x: overlayView.bounds.width / 2, y: overlayView.bounds.height / 2)
overlayView.addSubview(activityIndicator)
view.addSubview(bgView)
self.activityIndicator.startAnimating()
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
bgView.removeFromSuperview()
}
}
if you add it to keywindow, it can then go over your nav and tab bars also... something like this
LoadingOverlay.shared.showOverlay(view: UIApplication.shared.keyWindow!)
Swift 3.
I used #Lucho's code in his answer below and I changed the overlay background color to clear and added a spinner color.
public class LoadingOverlay {
var overlayView : UIView!
var activityIndicator : UIActivityIndicatorView!
class var shared: LoadingOverlay {
struct Static {
static let instance: LoadingOverlay = LoadingOverlay()
}
return Static.instance
}
init(){
self.overlayView = UIView()
self.activityIndicator = UIActivityIndicatorView()
overlayView.frame = CGRect(0, 0, 80, 80)
overlayView.backgroundColor = .clear
overlayView.clipsToBounds = true
overlayView.layer.cornerRadius = 10
overlayView.layer.zPosition = 1
activityIndicator.frame = CGRect(0, 0, 40, 40)
activityIndicator.center = CGPoint(overlayView.bounds.width / 2, overlayView.bounds.height / 2)
activityIndicator.activityIndicatorViewStyle = .whiteLarge
activityIndicator.color = .gray
overlayView.addSubview(activityIndicator)
}
public func showOverlay(view: UIView) {
overlayView.center = view.center
view.addSubview(overlayView)
activityIndicator.startAnimating()
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
overlayView.removeFromSuperview()
}
}
I've created a protocol for presenting your own view controller as an overlay. The usage is very simple:
class ViewController: UIViewController, OverlayHost {
#IBAction func showOverlayButtonPressed() {
showOverlay(type: YourOverlayViewController.self,
fromStoryboardWithName: "Main")
}
}
Result:
Source code: https://github.com/agordeev/OverlayViewController
Related article: https://andreygordeev.com/2017/04/18/overlay-view-controller-protocols-swift/
Xamarin.iOS version:
var alert = UIAlertController.Create(string.Empty, "Please wait...", UIAlertControllerStyle.Alert);
var alertIndicatorView = new UIActivityIndicatorView();
alertIndicatorView.Frame = new CGRect(x: 10, y: 5, width: 50, height: 50);
alertIndicatorView.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray;
alertIndicatorView.HidesWhenStopped = true;
alertIndicatorView.StartAnimating();
alert.Add(alertIndicatorView);
controller.PresentViewController(alert, true, null);
If there's someone looking for a Lottie implementation for loading view here's a working solution I made using #Shourob Datta solution:
import Foundation
import UIKit
import Lottie
public class LogoLoadingAnimation{
class func showUniversalLoadingView(_ show: Bool, loadingText : String = "") {
let existingView = UIApplication.shared.windows[0].viewWithTag(1200)
if show {
if existingView != nil {
return
}
let loadingView = self.makeLoadingView(withFrame: UIScreen.main.bounds, loadingText: loadingText)
loadingView?.tag = 1200
UIApplication.shared.windows[0].addSubview(loadingView!)
} else {
existingView?.removeFromSuperview()
}
}
class func makeLoadingView(withFrame frame: CGRect, loadingText text: String?) -> UIView? {
let gradienView = GradientBackgroundView(frame: frame)
gradienView.startColor = UIColor(red: 255, green: 255, blue: 255, alpha: 1)
gradienView.endColor = UIColor(red: 238, green: 238, blue: 238, alpha: 1)
gradienView.startColor = UIColor(named: "dark") ?? .blue
gradienView.startColor = UIColor(named: "purpuleGrey") ?? .gray
let loadingAnimationView = AnimationView()
gradienView.addSubview(loadingAnimationView)
loadingAnimationView.translatesAutoresizingMaskIntoConstraints = false
loadingAnimationView.centerXAnchor.constraint(equalTo: gradienView.centerXAnchor).isActive = true
loadingAnimationView.centerYAnchor.constraint(equalTo: gradienView.centerYAnchor).isActive = true
loadingAnimationView.animation = UITraitCollection.current.userInterfaceStyle == .dark ? Animation.named("logoLoadingWhite") : Animation.named("logoLoadingBlue")
loadingAnimationView.backgroundBehavior = .pauseAndRestore
loadingAnimationView.contentMode = .scaleAspectFit
loadingAnimationView.loopMode = .loop
loadingAnimationView.play()
return gradienView
}
}

Resources