iOS Animation – UIStackView inside UIScrollView

Filed Under: iOS

We have already discussed through examples and implemented UIStackView in a tutorial before. In this tutorial, we’ll learn how to add a UIStackView inside a UIScrollView and also implement adding and removing views from a UIStackView programmatically and animate them.

UIStackView inside UIScrollView

Following are the steps you need to follow in order to add a UIStackView inside a UIScrollView.

  • Add the UIScrollView to the View Controller View and set the constraints to all the sides of the View.
  • Add a UIStackView inside the UIScrollView. Set the constraints: Leading, Trailing, Top & Bottom to the ScrollView.
  • If the StackView is to be scrolled Vertically, set an Equal Width Constraint between the UIStackView and the UIScrollView.
  • If the StackView is to be scrolled Horizontally, set an Equal Height Constraint between the UIStackView and the UIScrollView.
  • Add your SubViews inside the UIStackView and enjoy scrolling.

Let’s demonstrate this in our XCode Project Main.storyboard.

ios uistackview uiscrollview part 1

Now let’s add a few Buttons inside the StackView:

ios uistackview uiscrollview part 2

The output in the simulator is given below:
ios uistackview uiscrollview output

Let’s look at building a UIStackView programmatically next.

UIStackView programmatically

Open up your ViewController.swift file and add the following lines of code:


import UIKit

class ViewController: UIViewController {

    
    var stackView = UIStackView()
    var constraintBottom : NSLayoutConstraint?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.textColor = UIColor.darkGray
        textField.minimumFontSize = 17.0
        textField.borderStyle = UITextField.BorderStyle.roundedRect
        textField.keyboardType = UIKeyboardType.emailAddress
        textField.returnKeyType = UIReturnKeyType.next
        
        
        let textFieldPassword = UITextField()
        textFieldPassword.placeholder = "Password"
        textFieldPassword.textColor = UIColor.darkGray
        textFieldPassword.minimumFontSize = 17.0
        textFieldPassword.textAlignment = .right
        textFieldPassword.isSecureTextEntry = true
        textFieldPassword.borderStyle = UITextField.BorderStyle.roundedRect
        textFieldPassword.keyboardType = UIKeyboardType.default
        textFieldPassword.returnKeyType = UIReturnKeyType.done
        
        
        let button = UIButton(type: UIButton.ButtonType.system)
        button.setTitle("Login", for: UIControl.State.normal)
        button.setTitleColor(UIColor.white, for: UIControl.State.normal)
        button.backgroundColor = UIColor.black
        button.layer.borderColor = UIColor.white.cgColor
        button.layer.borderWidth = 1.0
        button.layer.cornerRadius = 5.0
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        button.tag = 1
        
        
        stackView = UIStackView(arrangedSubviews: [textField, textFieldPassword, button])
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.distribution = .fillEqually
        stackView.alignment = .fill
        stackView.spacing = 20.0
        
        view.addSubview(stackView)

        stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
        stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
        stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
        constraintBottom = stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40)
        constraintBottom?.isActive = true
        
    }
    
    @objc func buttonAction(sender: UIButton!) {
        
            if(sender.tag == 1){
            constraintBottom?.isActive = false
            stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
            }
    }


}

In this code, we’ve set UITextField and Buttons inside a UIStackView vertically which are filled equally. We set the constraints on the stackView using NSLayoutConstraint.

The subViews are passed in the arrangeSubView array. On Button click, we set a selector to disable the bottom constraint of the StackView and add a new one which increases the bottom margin.

translatesAutoresizingMaskIntoConstraints must be set to false since UIStackViews use Auto-layouts.

isHidden property is used to toggle the visibility of a complete StackView including all it’s elements.

Loading SubViews using Function

Instead of passing individual subViews in the array, we can populate the UIStackView using a function as shown below:


class ViewController: UIViewController {

    
    var stackView = UIStackView()
    var constraintBottom : NSLayoutConstraint?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        
        stackView = UIStackView(arrangedSubviews: createButtonArray(named: "1","2","3","4"))
        

        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.distribution = .fillEqually
        stackView.alignment = .fill
        stackView.spacing = 20.0

        view.addSubview(stackView)

        stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
        stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
        stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
        constraintBottom = stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40)
        constraintBottom?.isActive = true
        
    }

    
    func createButtonArray(named: String...) -> [UIButton]
    {
        return named.map{name in
            let button  = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            button.setTitle("Button \(name)", for: .normal)
            button.backgroundColor = UIColor.red
            button.setTitleColor(UIColor.white, for: .normal)
            return button
        }
    }
}

createButtonArray creates an array by transforming the strings into their corresponding Button instances to compose the array.

ios uistackview programmatically using function

Adding SubViews On Click

Let’s configure each of the above Buttons to add another subView on button click. We will be adding a nested subview.

The viewDidLoad method remains the same as before. The other methods in the ViewController.swift are:


func createButtonArray(named: String...) -> [UIButton]
    {
        return named.map{name in
            let button  = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            button.setTitle("Button \(name)", for: .normal)
            button.backgroundColor = UIColor.red
            button.setTitleColor(UIColor.white, for: .normal)
            button.tag = named.firstIndex(of: name)!
            button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
            return button
        }
    }
    
    @objc func buttonAction(sender: UIButton!) {
            print("Button tag is")
            print(sender.tag)
            createNestedSubView(sender.tag)
    }
    
    func createNestedSubView(_ tag: Int)
    {
        let ss = UIStackView(arrangedSubviews: createButtonArray(named: "\(tag+1).1","\(tag+1).2","\(tag+1).3","\(tag+1).4"))
        
        
        ss.axis = .horizontal
        ss.translatesAutoresizingMaskIntoConstraints = false
        ss.distribution = .fillEqually
        ss.alignment = .fill
        ss.spacing = 5.0
        
        stackView.insertArrangedSubview(ss, at: tag)
        
        ss.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
        ss.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
        ss.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
    }

Thus, on button click, we pass the index of the button. A nested horizontal StackView is created.
The new stack view is inserted at the tag number index.

insertArrangedSubview is used to insert the SubView in the UIStackView at a certain index.

arrangeSubView is used to insert the new SubView at the end of the UIStackView.

ios uistackview programmatically adding nested stackview

Removing SubViews Programmatically

We can remove the SubViews using the function removeArrangedSubview on the UIStackView instance.
Also, we need to remove the view from the SuperView too.

In the ViewController.swift createButtonArray function we add another button click action for the nested StackView buttons. When any of them are clicked they would be deleted:


func createButtonArray(named: String...) -> [UIButton]
    {
        return named.map{name in
            let button  = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            button.setTitle("Button \(name)", for: .normal)
            button.backgroundColor = UIColor.red
            button.setTitleColor(UIColor.white, for: .normal)
            button.tag = named.firstIndex(of: name)!
            if(name.contains("."))
            {
            button.addTarget(self, action: #selector(deleteAction), for: .touchUpInside)
            }
            else{
            button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
            }
            return button
        }
    }

 @objc func deleteAction(sender: UIButton!) {
        print("Delete tag is")
        print(sender.titleLabel?.text! ?? "NA")
        stackView.removeArrangedSubview(sender)
        sender.removeFromSuperview()
        
    }

The output of the application in action is given below:
ios uistackview programmatically removing sub views

Animating SubViews

We can animate the subViews when the isHidden property is toggled using:


UIView.animate(withDuration: 0.5){
                    button.isHidden = true //or false
                }

In the above application, we hide and animate the Button 4 when it is clicked:


@objc func buttonAction(sender: UIButton!) {
            print("Button tag is")
            print(sender.tag)
        
            if(sender.titleLabel?.text?.contains("4"))!
            {
                UIView.animate(withDuration: 0.5){
                    sender.isHidden = true //or false
                }
            }
        
            createNestedSubView(sender.tag)
    }

ios uistackview programmatically animate sub views

This brings an end to this tutorial. You can download the project from the link below:

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages