Fold and unfold animation of UIImageView using constraints - ios

As you can see in my code bellow, I'm trying to make a fold/unfold animation using constraints. Certainly the gray background has the fold/unfold animation but the image itself doesn't.
How can I get same fold/unfold effect of the image itself?
class ViewController2: UIViewController {
var folded: Bool = false
var imagen: UIImageView!
private var foldConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let imagen = UIImageView(contentMode: .scaleAspectFill, image: #imageLiteral(resourceName: "gpointbutton"))
imagen.translatesAutoresizingMaskIntoConstraints = false
imagen.backgroundColor = .gray
view.addSubview(imagen)
self.imagen = imagen
imagen.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imagen.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 0)
createAnimationButton()
}
private func createAnimationButton() {
let button = UIButton(title: "Animate", titleColor: .blue)
button.translatesAutoresizingMaskIntoConstraints = false
button.addAction(for: .touchUpInside) { [weak self] (_) in
guard let self = self else { return }
self.folded = !self.folded
if self.folded {
self.foldConstraint.isActive = true
UIView.animate(withDuration: 0.5) {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}
} else {
self.foldConstraint.isActive = false
UIView.animate(withDuration: 0.5) {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}
}
}
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}

One thing to note here is that the width or height constraint is set to 0 (accurately also includes 0.1), and the same is hidden.
Then you need to set the height constraint to be greater than 0.1
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 0)
Replace with this, temporarily set to 1
foldConstraint = imagen.heightAnchor.constraint(equalToConstant: 1)
Hide it at the end of the animation
self.folded = !self.folded
if self.folded {
self.foldConstraint.isActive = true
UIView.animate(withDuration: 1, animations: {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
}) { (completion) in
self.imagen.isHidden = true
}
} else {
self.imagen.isHidden = false
self.foldConstraint.isActive = false
UIView.animate(withDuration: 1, animations: {
self.imagen.setNeedsLayout()
self.imagen.superview?.layoutIfNeeded()
})
}
Update:
scaleAspectFill is not suitable for animation, it should be set to scaleAspectFit
let imagen = UIImageView(contentMode: .scaleAspectFit, image: #imageLiteral(resourceName: "gpointbutton"))

Related

UIView is not visible when inside subview

Inside my project I have Custom-DropDownView. The problem is the actual DropView is not appearing when the DropDownButton is being pressed if the DropDownButton is inside a Subview.
My structure: ViewController -> UIView -> UIStackView -> arrangedSubview ->DropdownButton
If adding the DropDownButton inside the ViewController it works just fine. However if I add it inside the StackView (where I actually need it to be) it won't show the dropView that should appear when the user taps the DropDownButton.
DropDownButton:
class DropDownBtn: UIButton, DropDownProtocol {
let label: UILabel = {
let v = UILabel()
v.font = UIFont(name: "AvenirNext-Regular", size: 15)
v.textColor = .white
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let listImage: UIImageView = {
let v = UIImageView()
v.backgroundColor = .clear
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
func dropDownPressed(string: String, image: UIImage) {
self.setTitle("", for: .normal)
self.label.text = string
self.listImage.image = image
self.dismissDropDown()
}
var dropView = DropDownView()
var height = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.layer.borderColor = UIColor.white.cgColor
self.layer.borderWidth = 1.0
self.layer.cornerRadius = 3
// add image
self.addSubview(listImage)
self.listImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
self.listImage.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.listImage.widthAnchor.constraint(equalToConstant: 22).isActive = true
self.listImage.heightAnchor.constraint(equalToConstant: 22).isActive = true
// add label
self.addSubview(label)
self.label.leadingAnchor.constraint(equalTo: self.listImage.leadingAnchor, constant: 32).isActive = true
self.label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
dropView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
dropView.delegate = self
dropView.layer.borderColor = UIColor.white.cgColor
dropView.layer.borderWidth = 1.0
dropView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(dropView)
self.superview?.bringSubviewToFront(dropView)
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
override func removeFromSuperview() {
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = false
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = false
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = false
}
var isOpen = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isOpen == false {
isOpen = true
NSLayoutConstraint.deactivate([self.height])
if self.dropView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.dropView.tableView.contentSize.height
}
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.layoutIfNeeded()
self.dropView.center.y -= self.dropView.frame.height / 2
}, completion: nil)
} else {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
}
func dismissDropDown() {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
Usage at the moment:
Inside UIView:
let dropDownButton: DropDownBtn = {
let v = DropDownBtn()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
and I add it like this (+ using autolayout, the button is displayed perfectly fine and not behind any other view):
theStackView.addArrangedSubview(self.itemView)
itemView.addSubview(dropDownButton)
I tried calling self.view.bringSubViewToFront(self.view.theUIView.dropDownButton.dropView) but that didn't do anything. actual DropDownButton seems alright. I checked it in the View-Hierarchy and I also tested that touchesBegan is actually called and it is.
So I guess the issue is how i constrain the dropView but I am completely stuck here. If anyone can help me out here I am very grateful!
If anything is unclear or you need more code, just let me know!
Update:
The reason the tableView was not visible is because I forgot to call reloadData. However that does not fix the main problem !
The tableView is not clickable when adding it inside the arrangedSubview. Only if I add it to the UIView directly it becomes clickable. In both times the view-hierarchy looks the same.. It is always at the front.
If you want to have a look yourself just let me know and I give you the link to the project.

Accessing an image from another class

I have a quick question. I am trying to set an image from another view controller. what am I doing wrong?
The main view controller:
//how I set up the pop view:
lazy var popUpView: PopViews = {
let view = PopViews()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 24
view.delegate = self
return view
}()
The call to pop view
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()){
self.handleShowPop()
}
handleShowPop function:
#objc func handleShowPop(){
view.addSubview(popUpView)
let image: UIImage = UIImage(named: "Test")!
popUpView.imageView = UIImageView(image: image)
popUpView.button.setTitle("View Our Menu2", for: .normal)
popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40).isActive = true
popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
popUpView.heightAnchor.constraint(equalToConstant: view.frame.width - 154).isActive = true
popUpView.widthAnchor.constraint(equalToConstant: view.frame.width - 104).isActive = true
popUpView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
popUpView.alpha = 0
UIView.animate(withDuration: 0.5){
self.visualEffectView.alpha = 0.7
self.popUpView.alpha = 1
self.popUpView.transform = CGAffineTransform.identity
}
}
This is the PopViews class:
I can add the image here but I cannot access it from the first view controller
class PopViews: UIView {
var imageView: UIImageView = {
let img = UIImageView(image: #imageLiteral(resourceName: "test"))
img.translatesAutoresizingMaskIntoConstraints = false
img.heightAnchor.constraint(equalToConstant: 80).isActive = true
img.widthAnchor.constraint(equalToConstant: 80).isActive = true
img.image = imgz
return img
}()
}

Animate constraints change UIViewController

I have "error" view, that is placed above navigation bar and hidden. When error occured, i want that view to smoothly show from top. I tried:
class AuthViewController: UIViewController {
let error: ErrorView = {
let error = ErrorView()
error.setup()
return error
}()
var topAnchor: NSLayoutConstraint!
var botAnchor: NSLayoutConstraint!
override func viewDidLoad() {
setupErrorView()
}
private func setupErrorView(){
view.addSubview(error)
botAnchor = error.bottomAnchor.constraint(equalTo: view.topAnchor)
botAnchor.isActive = true
topAnchor = error.topAnchor.constraint(equalTo: view.topAnchor, constant: CGFloat(Offsets.navigationAndStatusBarHeight))
topAnchor.isActive = false
error.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
error.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
func showError(_ text: String){
UIView.animate(withDuration: 2.0) {[weak self] in
guard let weakSelf = self else { return }
print("attempt to animate")
weakSelf.error.show(text)
weakSelf.botAnchor.isActive = false
weakSelf.topAnchor.isActive = true
weakSelf.view.setNeedsLayout()
}
}
}
class ErrorView: UIView {
private var label: UILabel = {
return LabelSL.regular()
}()
fileprivate func setup(){
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = Theme.Color.orange.value
addSubview(label)
}
fileprivate func show(_ text: String){
let sideOffset: CGFloat = 10
let verticalOffset: CGFloat = 10
label.text = text
label.topAnchor.constraint(equalTo: topAnchor, constant: verticalOffset).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor, constant: sideOffset).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor, constant: -sideOffset).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalOffset).isActive = true
}
}
Animation should be done when func showError(_ text: String){ method called, but it's not. View just appear instantly.
You're trying to animate constraints in wrong way. You should set constraints outside of animation block and only layoutIfNeeded in animation:
func showError(_ text: String){
botAnchor.isActive = false
topAnchor.isActive = true
error.show(text)
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}

Animating from top and not center of intrinsic size

I'm trying to get my views to animate from top to bottom. Currently, when changing the text of my label, between nil and some "error message", the labels are animated from the center of its intrinsic size, but I want the regular "label" to be "static" and only animate the errorlabel. Basically the error label should be located directly below the regular label and the errorlabel should be expanded according to its (intrinsic)height. This is essentially for a checkbox. I want to show the error message when the user hasn't checked the checkbox yet, but are trying to proceed further. The code is just a basic implementation that explains the problem. I've tried adjusting anchorPoint and contentMode for the containerview but those doesn't seem to work the way I thought. Sorry if the indentation is weird
import UIKit
class ViewController: UIViewController {
let container = UIView()
let errorLabel = UILabel()
var bottomLabel: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
container.contentMode = .top
container.translatesAutoresizingMaskIntoConstraints = false
container.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
container.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true
container.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
container.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let label = UILabel()
label.text = "Very long text that i would like to show to full extent and eventually add an error message to. It'll work on multiple rows obviously"
label.numberOfLines = 0
container.contentMode = .top
container.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
label.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
container.addSubview(errorLabel)
errorLabel.setContentHuggingPriority(UILayoutPriority(300), for: .vertical)
errorLabel.translatesAutoresizingMaskIntoConstraints = false
errorLabel.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
errorLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
errorLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
bottomLabel = errorLabel.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
bottomLabel.isActive = false
errorLabel.numberOfLines = 0
container.backgroundColor = .green
let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.addTarget(self, action: #selector(onTap))
container.addGestureRecognizer(tapRecognizer)
}
#objc func onTap() {
self.container.layoutIfNeeded()
UIView.animate(withDuration: 0.3, animations: {
let active = !self.bottomLabel.isActive
self.bottomLabel.isActive = active
self.errorLabel.text = active ? "A veru very veru very veru very veru very veru very veru very veru very veru very long Error message" : nil
self.container.layoutIfNeeded()
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have found it a bit difficult to get dynamic multiline labels to "animate" in the way I want - particularly when I want to "hide" the label.
One approach: Create 2 "error" labels, with one overlaid on top of the other. Use the "hidden" label to control the constraints on the container view. When animating the change, the container view's bounds will effectively "reveal" and "conceal" (show/hide) the "visible" label.
Here is an example, that you can run directly in a Playground page:
import UIKit
import PlaygroundSupport
class RevealViewController: UIViewController {
let container = UIView()
let staticLabel = UILabel()
let hiddenErrorLabel = UILabel()
let visibleErrorLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// colors, just so we can see the bounds of the labels
view.backgroundColor = .lightGray
container.backgroundColor = .green
staticLabel.backgroundColor = .yellow
visibleErrorLabel.backgroundColor = .cyan
// we don't want to see this label, so set its alpha to zero
hiddenErrorLabel.alpha = 0.0
// we want the Error Label to be "revealed" - so when it is has text it is initially "covered"
container.clipsToBounds = true
// all labels may be multiple lines
staticLabel.numberOfLines = 0
hiddenErrorLabel.numberOfLines = 0
visibleErrorLabel.numberOfLines = 0
// initial text in the "static" label
staticLabel.text = "Very long text that i would like to show to full extent and eventually add an error message to. It'll work on multiple rows obviously"
// add the container view to the VC's view
// pin it to the sides, and 100-pts from the top
// NO bottom constraint
view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
container.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0).isActive = true
container.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
container.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// add the static label to the container
// pin it to the top and sides
// NO bottom constraint
container.addSubview(staticLabel)
staticLabel.translatesAutoresizingMaskIntoConstraints = false
staticLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
staticLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
staticLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
// add the "hidden" error label to the container
// pin it to the sides, and pin its top to the bottom of the static label
// NO bottom constraint
container.addSubview(hiddenErrorLabel)
hiddenErrorLabel.translatesAutoresizingMaskIntoConstraints = false
hiddenErrorLabel.topAnchor.constraint(equalTo: staticLabel.bottomAnchor).isActive = true
hiddenErrorLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
hiddenErrorLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
// add the "visible" error label to the container
// pin its top, leading and trailing constraints to the hidden label
container.addSubview(visibleErrorLabel)
visibleErrorLabel.translatesAutoresizingMaskIntoConstraints = false
visibleErrorLabel.topAnchor.constraint(equalTo: hiddenErrorLabel.topAnchor).isActive = true
visibleErrorLabel.leadingAnchor.constraint(equalTo: hiddenErrorLabel.leadingAnchor).isActive = true
visibleErrorLabel.trailingAnchor.constraint(equalTo: hiddenErrorLabel.trailingAnchor).isActive = true
// pin the bottom of the hidden label ot the bottom of the container
// now, when we change the text of the hidden label, it will
// "push down / pull up" the bottom of the container view
hiddenErrorLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
// add a tap gesture
let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.addTarget(self, action: #selector(onTap))
container.addGestureRecognizer(tapRecognizer)
}
var myActive = false
#objc func onTap() {
let errorText = "A veru very veru very veru very veru very veru very veru very veru very veru very long Error message"
self.myActive = !self.myActive
if self.myActive {
// we want to SHOW the error message
// set the error message in the VISIBLE error label
self.visibleErrorLabel.text = errorText
// "animate" it, with duration of 0.0 - so it is filled instantly
// it will extend below the bottom of the container view, but won't be
// visible yet because we set .clipsToBounds = true on the container
UIView.animate(withDuration: 0.0, animations: {
}, completion: {
_ in
// now, set the error message in the HIDDEN error label
self.hiddenErrorLabel.text = errorText
// the hidden label will now "push down" the bottom of the container view
// so we can animate the "reveal"
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
})
} else {
// we want to HIDE the error message
// clear the text from the HIDDEN error label
self.hiddenErrorLabel.text = ""
// the hidden label will now "pull up" the bottom of the container view
// so we can animate the "conceal"
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: {
_ in
// after its hidden, clear the text of the VISIBLE error label
self.visibleErrorLabel.text = ""
})
}
}
}
let vc = RevealViewController()
PlaygroundPage.current.liveView = vc
So, since it's a control that I wanted to create (checkbox) in this case with an error message, I manipulated the frames directly, based on the bounds. So to get it to work properly, I used a combination of overriding intrinsicContentSize and layoutSubviews and some minor extra stuff. The class contains a bit more than provided, but the provided code should hopefully explain the approach I went with.
open class Checkbox: UIView {
let imageView = UIImageView()
let textView = ThemeableTapLabel()
private let errorLabel = UILabel()
var errorVisible: Bool = false
let checkboxPad: CGFloat = 8
override open var bounds: CGRect {
didSet {
// fixes layout when bounds change
invalidateIntrinsicContentSize()
}
}
open var errorMessage: String? {
didSet {
self.errorVisible = self.errorMessage != nil
UIView.animate(withDuration: 0.3, animations: {
if self.errorMessage != nil {
self.errorLabel.text = self.errorMessage
}
self.setNeedsLayout()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}, completion: { success in
if self.errorMessage == nil {
self.errorLabel.text = nil
}
})
}
}
func checkboxSize() -> CGSize {
return CGSize(width: imageView.image?.size.width ?? 0, height: imageView.image?.size.height ?? 0)
}
override open func layoutSubviews() {
super.layoutSubviews()
frame = bounds
let imageFrame = CGRect(x: 0, y: 0, width: checkboxSize().width, height: checkboxSize().height)
imageView.frame = imageFrame
let textRect = textView.textRect(forBounds: CGRect(x: (imageFrame.width + checkboxPad), y: 0, width: bounds.width - (imageFrame.width + checkboxPad), height: 10000), limitedToNumberOfLines: textView.numberOfLines)
textView.frame = textRect
let largestHeight = max(checkboxSize().height, textRect.height)
let rect = errorLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: bounds.width, height: 10000), limitedToNumberOfLines: errorLabel.numberOfLines)
//po bourect = rect.offsetBy(dx: 0, dy: imageFrame.maxY)
let errorHeight = errorVisible ? rect.height : 0
errorLabel.frame = CGRect(x: 0, y: largestHeight, width: bounds.width, height: errorHeight)
}
override open var intrinsicContentSize: CGSize {
get {
let textRect = textView.textRect(forBounds: CGRect(x: (checkboxSize().width + checkboxPad), y: 0, width: bounds.width - (checkboxSize().width + checkboxPad), height: 10000), limitedToNumberOfLines: textView.numberOfLines)
let rect = errorLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: bounds.width, height: 10000), limitedToNumberOfLines: errorLabel.numberOfLines)
let errorHeight = errorVisible ? rect.height : 0
let largestHeight = max(checkboxSize().height, textRect.height)
return CGSize(width: checkboxSize().width + 200, height: largestHeight + errorHeight)
}
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
//...
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.numberOfLines = 0
contentMode = .top
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(checkboxTap(sender:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapGesture)
addSubview(errorLabel)
errorLabel.contentMode = .top
errorLabel.textColor = .red
errorLabel.numberOfLines = 0
}
}

How can i use activity indicator when a user registers or login messageView using swift 3

How can i use activity Indicator in the following code when a user registers or logs in the message view.
The Code below is a loginViewController which handles the login and Registeration of the User.
so, How can i use Activity Indicator View or Progress view PROGRAMATICALLY whenever a user hits the Login or Register Button .
class LoginController: UIViewController {
var messagesController: MessagesController?
let inputsContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 209, g:238, b:252).withAlphaComponent(0.3)
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
return view
}()
lazy var loginRegisterButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(r: 255, g: 45, b: 85)
button.setTitle("Register", for: UIControlState())
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(UIColor.white, for: UIControlState())
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(handleLoginRegister), for: .touchUpInside)
return button
}()
func handleLoginRegister() {
if loginRegisterSegmentedControl.selectedSegmentIndex == 0 {
handleLogin()
} else {
handleRegister()
}
}
func handleLogin() {
guard let email = emailTextField.text, let password = passwordTextField.text else {
print("Form is not valid")
return
}
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
print(error ?? "")
return
}
//successfully logged in our user
self.messagesController?.fetchUserAndSetupNavBarTitle()
self.dismiss(animated: true, completion: nil)
})
}
// TextField, EmailTextField, PasswordTextField, seperator view
let nameTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Name"
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let nameSeparatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 220, g: 220, b: 220)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let emailTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Email"
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let emailSeparatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 220, g: 220, b: 220)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let passwordTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Password"
tf.translatesAutoresizingMaskIntoConstraints = false
tf.isSecureTextEntry = true
return tf
}()
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "backslash_inc02main")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 20
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectProfileImageView)))
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var loginRegisterSegmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["Login", "Register"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.white
sc.selectedSegmentIndex = 1
sc.addTarget(self, action: #selector(handleLoginRegisterChange), for: .valueChanged)
return sc
}()
func handleLoginRegisterChange() {
let title = loginRegisterSegmentedControl.titleForSegment(at: loginRegisterSegmentedControl.selectedSegmentIndex)
loginRegisterButton.setTitle(title, for: UIControlState())
// change height of inputContainerView, but how???
inputsContainerViewHeightAnchor?.constant = loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 100 : 150
// change height of nameTextField
nameTextFieldHeightAnchor?.isActive = false
nameTextFieldHeightAnchor = nameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 0 : 1/3)
nameTextFieldHeightAnchor?.isActive = true
nameTextField.isHidden = loginRegisterSegmentedControl.selectedSegmentIndex == 0
emailTextFieldHeightAnchor?.isActive = false
emailTextFieldHeightAnchor = emailTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 1/2 : 1/3)
emailTextFieldHeightAnchor?.isActive = true
passwordTextFieldHeightAnchor?.isActive = false
passwordTextFieldHeightAnchor = passwordTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 1/2 : 1/3)
passwordTextFieldHeightAnchor?.isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
//view.backgroundColor = UIColor(r: 255, g: 149, b: 0)
self.view.addBackground()
view.addSubview(inputsContainerView)
view.addSubview(loginRegisterButton)
view.addSubview(profileImageView)
view.addSubview(loginRegisterSegmentedControl)
setupInputsContainerView()
setupLoginRegisterButton()
setupProfileImageView()
setupLoginRegisterSegmentedControl()
}
func setupLoginRegisterSegmentedControl() {
//need x, y, width, height constraints
loginRegisterSegmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterSegmentedControl.bottomAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: -12).isActive = true
loginRegisterSegmentedControl.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 1).isActive = true
loginRegisterSegmentedControl.heightAnchor.constraint(equalToConstant: 36).isActive = true
}
func setupProfileImageView() {
//need x, y, width, height constraints
profileImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
profileImageView.bottomAnchor.constraint(equalTo: loginRegisterSegmentedControl.topAnchor, constant: -12).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 150).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
}
var inputsContainerViewHeightAnchor: NSLayoutConstraint?
var nameTextFieldHeightAnchor: NSLayoutConstraint?
var emailTextFieldHeightAnchor: NSLayoutConstraint?
var passwordTextFieldHeightAnchor: NSLayoutConstraint?
func setupInputsContainerView() {
//need x, y, width, height constraints
inputsContainerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
inputsContainerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
inputsContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -24).isActive = true
inputsContainerViewHeightAnchor = inputsContainerView.heightAnchor.constraint(equalToConstant: 150)
inputsContainerViewHeightAnchor?.isActive = true
inputsContainerView.addSubview(nameTextField)
inputsContainerView.addSubview(nameSeparatorView)
inputsContainerView.addSubview(emailTextField)
inputsContainerView.addSubview(emailSeparatorView)
inputsContainerView.addSubview(passwordTextField)
//need x, y, width, height constraints
nameTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
nameTextField.topAnchor.constraint(equalTo: inputsContainerView.topAnchor).isActive = true
nameTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
nameTextFieldHeightAnchor = nameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
nameTextFieldHeightAnchor?.isActive = true
//need x, y, width, height constraints
nameSeparatorView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
nameSeparatorView.topAnchor.constraint(equalTo: nameTextField.bottomAnchor).isActive = true
nameSeparatorView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
nameSeparatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
//need x, y, width, height constraints
emailTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
emailTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor).isActive = true
emailTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
emailTextFieldHeightAnchor = emailTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
emailTextFieldHeightAnchor?.isActive = true
//need x, y, width, height constraints
emailSeparatorView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
emailSeparatorView.topAnchor.constraint(equalTo: emailTextField.bottomAnchor).isActive = true
emailSeparatorView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
emailSeparatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
//need x, y, width, height constraints
passwordTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor).isActive = true
passwordTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
passwordTextFieldHeightAnchor = passwordTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
passwordTextFieldHeightAnchor?.isActive = true
}
func setupLoginRegisterButton() {
//need x, y, width, height constraints
loginRegisterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterButton.topAnchor.constraint(equalTo: inputsContainerView.bottomAnchor, constant: 12).isActive = true
loginRegisterButton.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
loginRegisterButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
}
extension UIColor {
convenience init(r: CGFloat, g: CGFloat, b: CGFloat) {
self.init(red: r/255, green: g/255, blue: b/255, alpha: 1)
}
}
To show activity indicator, first declare it at class level and then initialize it.
var activityIndicator:UIActivityIndicatorView!
Then in your viewdidLoad() method, initialize activityIndicator.
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicator.center = view.center
activityIndicator.isHidden = true
self.view.addSubview(activityIndicator)
You can write two methods to start and stop activity indicator in your view controller as:
func displayActivityIndicatorView() -> () {
UIApplication.shared.beginIgnoringInteractionEvents()
self.view.bringSubview(toFront: self.activityIndicator)
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
}
func hideActivityIndicatorView() -> () {
if !self.activityIndicator.isHidden{
DispatchQueue.main.async {
UIApplication.shared.endIgnoringInteractionEvents()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
}
}
}
Now start activity indicator just after validating the login data and before hitting the login API as:
func handleLogin() {
guard let email = emailTextField.text, let password = passwordTextField.text else {
print("Form is not valid")
return
}
//start activity indicator
self. displayActivityIndicatorView()
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
print(error ?? "")
//stop activity indicator
self. hideActivityIndicatorView()
return
}
//successfully logged in our user
//stop activity indicator
self. hideActivityIndicatorView()
self.messagesController?.fetchUserAndSetupNavBarTitle()
self.dismiss(animated: true, completion: nil)
})
}
Step 1:
Create activity indicator somewhere in your code. Specially in viewDidLoad
if you want to create it programmatically or if you want to show activity indicator with an alert design in programmatically.
Step 2: To show activity indicator
There is a button like login or register . There you will start the to show the activity indicator and start it to animate.
Step 3: To hide activity indicator
You will hide activity indicator in FIRAuth.auth()?.signIn..... completion handler for login. just like
[indicator stopAnimating];
[indicator isHidden:YES];
There are two cases in that login method. one is error and the other is successful. Its better to show an error alert to the user or something else.
Hope you will find this solution helpful. :)
You can use below code.
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
alert.view.tintColor = UIColor.black
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRect(x:10, y:5, width:50, height:50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.color = UIColor(red: 255.0/255, green: 65.0/255, blue: 42.0/255,alpha : 1.0)
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
///dismisss///
dismiss(animated: false, completion: nil)

Resources