Related
I'm trying to make the same behavior of the Material design textfield with a custom textfield.
I created a class that inherits from textfield and every thing is working fine. The only problem is in one scenario. when I have an object under the textfield, and i add the error label under the text field. the error label might be more than one line. so it overlays the object under the textfield. However, in the material design library, the objects under the textfield are automatically pushed down according ton the number of lines of the error label.
here is my custom textfield code:
import UIKit
import RxSwift
import RxCocoa
class FloatingTextField2: UITextField {
var placeholderLabel: UILabel!
var line: UIView!
var errorLabel: UILabel!
let bag = DisposeBag()
var activeColor = Constants.colorBlue
var inActiveColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
//var errorColorParcial = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 0.5)
private var lineYPosition: CGFloat!
private var lineXPosition: CGFloat!
private var lineWidth: CGFloat!
private var lineHeight: CGFloat!
private var errorLabelYPosition: CGFloat!
private var errorLabelXPosition: CGFloat!
private var errorLabelWidth: CGFloat!
private var errorLabelHeight: CGFloat!
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
var animationDuration = 0.35
var placeholderText: String = "" {
didSet {
if placeholderLabel != nil {
placeholderLabel.text = placeholderText
}
}
}
var isTextEntrySecured: Bool = false {
didSet {
self.isSecureTextEntry = isTextEntrySecured
}
}
override func draw(_ rect: CGRect) {
//setUpUI()
}
override func awakeFromNib() {
setUpUI()
}
func setUpUI() {
if placeholderLabel == nil {
placeholderLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: 20))
self.addSubview(placeholderLabel)
self.borderStyle = .none
placeholderLabel.text = "Placeholder Preview"
placeholderLabel.textColor = inActiveColor
self.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholder = ""
self.textColor = .black
setUpTextField()
}
if line == nil {
lineYPosition = self.frame.height
lineXPosition = -16
lineWidth = self.frame.width + 32
lineHeight = 1
line = UIView(frame: CGRect(x: lineXPosition, y: lineYPosition, width: lineWidth, height: lineHeight))
self.addSubview(line)
line.backgroundColor = inActiveColor
}
if errorLabel == nil {
errorLabelYPosition = lineYPosition + 8
errorLabelXPosition = 0
errorLabelWidth = self.frame.width
errorLabelHeight = calculateErrorLabelHeight(text: "")
errorLabel = UILabel(frame: CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight))
self.addSubview(errorLabel)
errorLabel.numberOfLines = 0
errorLabel.textColor = errorColorFull
errorLabel.text = ""
errorLabel.font = errorLabelFont
sizeToFit()
}
}
func setUpTextField(){
self.rx.controlEvent(.editingDidBegin).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}).disposed(by: bag)
self.rx.controlEvent(.editingDidEnd).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}).disposed(by: bag)
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?) {
if let errorText = error {
self.resignFirstResponder()
errorLabelHeight = calculateErrorLabelHeight(text: errorText)
self.errorLabel.frame = CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight)
self.errorLabel.text = errorText
self.errorLabel.isHidden = false
self.line.backgroundColor = errorColorFull
}else{
self.errorLabel.text = ""
self.errorLabel.isHidden = true
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
func animatePlaceholderUp(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 2
self.line.backgroundColor = self.activeColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
self.placeholderLabel.frame = CGRect(x: 0, y: (self.frame.height/2 + 8) * -1, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 1
self.line.backgroundColor = self.inActiveColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
self.placeholderLabel.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func calculateErrorLabelHeight(text:String) -> CGFloat{
let font = errorLabelFont
let width = self.frame.width
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
}
How can I solve this problem? I could not find anything on stack overflow or google related to my problem.
As mentioned in the comments:
You'll be much better off using constraints rather than explicit frames
Adding subviews to a UITextField will show them outside the Bounds of the field, meaning they won't affect the frame (and thus the constraints)
If the constraints are set properly, they will control the "containing view" height
The key to getting your "error" label to expand the view is to apply multiple vertical constraints, and activate / deactivate as needed.
Here is a complete example of a custom UIView which contains a text field, a placeholder label and an error label. The example view controller includes "demo" buttons to show the capabilities.
I suggest you add this code and try it out. If it suits your needs, there are plenty of comments in it that you should be able to tweak fonts, spacing, etc to your liking.
Or, it should at least give you some ideas of how to set up your own.
FloatingTextFieldView - UIView subclass
class FloatingTextFieldView: UIView, UITextFieldDelegate {
var placeHolderTopConstraint: NSLayoutConstraint!
var placeHolderCenterYConstraint: NSLayoutConstraint!
var placeHolderLeadingConstraint: NSLayoutConstraint!
var lineHeightConstraint: NSLayoutConstraint!
var errorLabelBottomConstraint: NSLayoutConstraint!
var activeColor: UIColor = UIColor.blue
var inActiveColor: UIColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull: UIColor = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
var animationDuration = 0.35
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
let placeholderLabel: UILabel = {
let v = UILabel()
v.text = "Default Placeholder"
v.setContentHuggingPriority(.required, for: .vertical)
return v
}()
let line: UIView = {
let v = UIView()
v.backgroundColor = .lightGray
return v
}()
let errorLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "Default Error"
v.setContentCompressionResistancePriority(.required, for: .vertical)
return v
}()
let textField: UITextField = {
let v = UITextField()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
backgroundColor = .white
[textField, line, placeholderLabel, errorLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// place holder label gets 2 vertical constraints
// top of view
// centerY to text field
placeHolderTopConstraint = placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0.0)
placeHolderCenterYConstraint = placeholderLabel.centerYAnchor.constraint(equalTo: textField.centerYAnchor, constant: 0.0)
// place holder leading constraint is 16-pts (when centered on text field)
// when animated above text field, we'll change the constant to 0
placeHolderLeadingConstraint = placeholderLabel.leadingAnchor.constraint(equalTo: textField.leadingAnchor, constant: 16.0)
// error label bottom constrained to bottom of view
// will be activated when shown, deactivated when hidden
errorLabelBottomConstraint = errorLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
// line height constraint constant changes between 1 and 2 (inactive / active)
lineHeightConstraint = line.heightAnchor.constraint(equalToConstant: 1.0)
NSLayoutConstraint.activate([
// text field top 16-pts from top of view
// leading and trailing = 0
textField.topAnchor.constraint(equalTo: topAnchor, constant: 16.0),
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// text field height = 24
textField.heightAnchor.constraint(equalToConstant: 24.0),
// text field bottom is AT LEAST 4 pts
textField.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -4.0),
// line view top is 2-pts below text field bottom
// leading and trailing = 0
line.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 2.0),
line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
line.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// error label top is 4-pts from text field bottom
// leading and trailing = 0
errorLabel.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 4.0),
errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
placeHolderCenterYConstraint,
placeHolderLeadingConstraint,
lineHeightConstraint,
])
// I'm not using Rx, so set the delegate
textField.delegate = self
textField.font = UIFont(name: "Lato-Regular", size: maxFontSize)
textField.textColor = .black
placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
placeholderLabel.textColor = inActiveColor
line.backgroundColor = inActiveColor
errorLabel.textColor = errorColorFull
errorLabel.font = errorLabelFont
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}
func animatePlaceholderUp() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// increase line height
self.lineHeightConstraint.constant = 2.0
// set line to activeColor
self.line.backgroundColor = self.activeColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
// deactivate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = false
// activate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = true
// move placeholder label leading to 0
self.placeHolderLeadingConstraint.constant = 0
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// decrease line height
self.lineHeightConstraint.constant = 1.0
// set line to inactiveColor
self.line.backgroundColor = self.inActiveColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
// deactivate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = false
// activate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = true
// move placeholder label leading to 16
self.placeHolderLeadingConstraint.constant = 16
self.layoutIfNeeded()
}) { (done) in
}
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?, endEditing: Bool) {
if let errorText = error {
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = errorText
self.line.backgroundColor = self.errorColorFull
self.errorLabel.isHidden = false
// activate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = true
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}else{
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = ""
self.line.backgroundColor = self.inActiveColor
self.errorLabel.isHidden = true
// deactivate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = false
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
// func to set / clear element background colors
// to make it easy to see the frames
func showHideFrames(show b: Bool) -> Void {
if b {
self.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0)
placeholderLabel.backgroundColor = .cyan
errorLabel.backgroundColor = .green
textField.backgroundColor = .yellow
} else {
self.backgroundColor = .white
[placeholderLabel, errorLabel, textField].forEach {
$0.backgroundColor = .clear
}
}
}
}
DemoFLoatingTextViewController
class DemoFLoatingTextViewController: UIViewController {
// FloatingTextFieldView
let sampleFTF: FloatingTextFieldView = {
let v = FloatingTextFieldView()
return v
}()
// a label to constrain below the FloatingTextFieldView
// so we can see it gets "pushed down"
let demoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "This is a label outside the Floating Text Field. As you will see, it gets \"pushed down\" when the error label is shown."
v.backgroundColor = .brown
v.textColor = .yellow
return v
}()
// buttons to Demo the functionality
let btnA: UIButton = {
let b = UIButton(type: .system)
b.setTitle("End Editing", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnB: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnC: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnD: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnE: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnF: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Show Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnG: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Hide Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let errorMessages: [String] = [
"Simple Error",
"This will end up being a Multiline Error message. It is long enough to cause word wrapping."
]
var errorCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// add Demo buttons
let btnStack = UIStackView()
btnStack.axis = .vertical
btnStack.spacing = 6
btnStack.translatesAutoresizingMaskIntoConstraints = false
[[btnA], [btnB, btnC], [btnD, btnE], [btnF, btnG]].forEach { btns in
let sv = UIStackView()
sv.distribution = .fillEqually
sv.spacing = 12
sv.translatesAutoresizingMaskIntoConstraints = false
btns.forEach {
sv.addArrangedSubview($0)
}
btnStack.addArrangedSubview(sv)
}
view.addSubview(btnStack)
// add FloatingTextFieldView and demo label
view.addSubview(sampleFTF)
view.addSubview(demoLabel)
sampleFTF.translatesAutoresizingMaskIntoConstraints = false
demoLabel.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// buttons stack Top = 20, centerX, width = 80% of view width
btnStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btnStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btnStack.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
// FloatingTextFieldView Top = 40-pts below buttons stack
sampleFTF.topAnchor.constraint(equalTo: btnStack.bottomAnchor, constant: 40.0),
// FloatingTextFieldView Leading = 60-pts
sampleFTF.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
// FloatingTextFieldView width = 240
sampleFTF.widthAnchor.constraint(equalToConstant: 240.0),
// Note: we are not setting the FloatingTextFieldView Height!
// constrain demo label Top = 8-pts below FloatingTextFieldView bottom
demoLabel.topAnchor.constraint(equalTo: sampleFTF.bottomAnchor, constant: 8.0),
// Leading = FloatingTextFieldView Leading
demoLabel.leadingAnchor.constraint(equalTo: sampleFTF.leadingAnchor),
// Width = 200
demoLabel.widthAnchor.constraint(equalToConstant: 200.0),
])
// add touchUpInside targets for demo buttons
btnA.addTarget(self, action: #selector(endEditing(_:)), for: .touchUpInside)
btnB.addTarget(self, action: #selector(setError(_:)), for: .touchUpInside)
btnC.addTarget(self, action: #selector(clearError(_:)), for: .touchUpInside)
btnD.addTarget(self, action: #selector(setAndEnd(_:)), for: .touchUpInside)
btnE.addTarget(self, action: #selector(clearAndEnd(_:)), for: .touchUpInside)
btnF.addTarget(self, action: #selector(showFrames(_:)), for: .touchUpInside)
btnG.addTarget(self, action: #selector(hideFrames(_:)), for: .touchUpInside)
}
#objc func endEditing(_ sender: Any) -> Void {
sampleFTF.textField.resignFirstResponder()
}
#objc func setError(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: false)
errorCount += 1
}
#objc func clearError(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: false)
}
#objc func setAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: true)
errorCount += 1
}
#objc func clearAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: true)
}
#objc func showFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: true)
}
#objc func hideFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: false)
}
}
Example results:
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)
How to add badge notification on UIBarButton in ios swift,
this is my UIBarButton Code in swift
let addtocartButton = UIBarButtonItem(image: UIImage(named: "Addtocartimg"),
style: .plain,
target: self,
action: #selector(self.movToToCarts))
self.navigationItem.rightBarButtonItem = addtocartButton
let cartButton = SSBadgeButton()
override func viewDidLoad() {
super.viewDidLoad()
cartButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
cartButton.setImage(UIImage(named: "menu")?.withRenderingMode(.alwaysTemplate), for: .normal)
cartButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
cartButton.addTarget(self, action: #selector(cartButtonTapped), for: .touchUpInside)
cartButton.badge = "4"
self.navigationItem.rightBarButtonItems = [UIBarButtonItem(customView: cartButton)
}
Badgebutton file
import Foundation
import UIKit
class SSBadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addBadgeToButon(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addBadgeToButon(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addBadgeToButon(badge: nil)
}
func addBadgeToButon(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addBadgeToButon(badge: nil)
fatalError("init(coder:) has not been implemented")
}
}
You can see my answer under this question. Recommend you to reference QMUIBadge's code. But it is in Objective-C.
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()
}
}
class PhotoViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
private var backgroundImage: UIImage
init(image: UIImage) {
self.backgroundImage = image
super.init(nibName: nil, bundle: nil)
print(image)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.gray
let backgroundImageView = UIImageView(frame: view.frame)
backgroundImageView.image = backgroundImage
view.addSubview(backgroundImageView)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(_:)))
self.view.addGestureRecognizer(panGestureRecognizer)
let cancelButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: 20.0, height: 20.0))
cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControlState())
cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
let next = UIButton(frame: CGRect(x: (view.frame.width)-90, y: (view.frame.height)-125, width: 70, height: 70))
next.setImage(#imageLiteral(resourceName: "Next"), for: UIControlState())
next.addTarget(self, action: #selector(cancel), for: .touchUpInside)
view.addSubview(next)
view.addSubview(cancelButton)
}
func panGestureRecognizerAction(_ gesture: UIPanGestureRecognizer){
let translation = gesture.translation(in: view)
view.frame.origin.y = translation.y
if gesture.state == .ended{
if view.frame.origin.y > 100 && view.frame.origin.y < 200{
UIView.animate(withDuration: 0.5, animations: {
dismiss(animated: false, completion: nil)
})
} else{
UIView.animate(withDuration: 0.3, animations: {
self.view.frame.origin = CGPoint(x: 0, y: 0)
})
}
}
}
When I pan up or down and drag the image view down the background is black eventhough i specified it to be gray. How can the background be of my chosen color. If any more required info is needed to answer the question please let me know
Looks like your panGestureRecognizerAction is moving the view instead of the backgroundImageView