UI Implementation - Popup

Make a popup and customize what you want

Posted by Ky Nguyen on October 05, 2018

Popup is not the best choice to notify users. It takes the users’ attention, and need action to remove popup. But sometimes, popup is a good selection. It displays on the same view, not change eyesight.

Today, I will implement a popup, inspired by the design of Joseph Liu. Change something to make it simpler for the first post. :D

Prepare project

  • Create new Single View App project
  • Add new file ReferralPopup.swift

Define components


    class ReferralPopup: knView {
        let blackView: UIButton = {
            let button = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            button.backgroundColor = UIColor.black.withAlphaComponent(0.5)
            return button
        }()
        let container: UIView = {
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .white
            v.createRoundCorner(7)
            return v
        }()
        let okButton: UIButton = {
            let button = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            let color = UIColor.color(r: 241, g: 66, b: 70)
            button.backgroundColor = color
            button.setTitle("COPY & CONTINUE", for: .normal)
            button.backgroundColor = UIColor.color(r: 71, g: 204, b: 54)
            button.height(54)
            button.createRoundCorner(27)
            return button
        }()
    }

blackView is the transparent background cover the whole screen. We will dismiss the popup when it clicked. You can use UIView and add UIGestureRecognizer also.

container is the white background container. It’s the main view for popup.

Layout the container

The most important part is how to layout the container


    override func setupView() {
        let color = UIColor.color(r: 241, g: 66, b: 70)
        
        let instruction = "GET YOUR FREE $10 CREDITS"
        let label = UIMaker.makeLabel(text: instruction,
                                      font: UIFont.boldSystemFont(ofSize: 15),
                                      color: .white,
                                      alignment: .center)
        
        let circle = UIMaker.makeView(background: color)
        let logo = UIMaker.makeImageView(image: UIImage(named: "swift"), contentMode: .scaleAspectFit)
        logo.backgroundColor = .white
        
        let codeTitle = UIMaker.makeLabel(text: "REFERRAL CODE",
                                          font: UIFont.boldSystemFont(ofSize: 12),
                                          color: UIColor.color(r: 155, g: 165, b: 172),
                                          alignment: .center)
        let codeLabel = UIMaker.makeLabel(text: "KYNGUYEN",
                                          font: UIFont.boldSystemFont(ofSize: 40),
                                          color: UIColor.color(r: 242, g: 64, b: 65),
                                          alignment: .center)
        
        
        container.addSubviews(views: circle, label, okButton, logo, codeTitle, codeLabel)
        
        // (1)
        label.top(toView: container, space: 24)
        label.horizontal(toView: container, space: 24)
        
        // (2)
        let edge: CGFloat = UIScreen.main.bounds.width * 2
        circle.square(edge: edge)
        circle.createRoundCorner(edge / 2)
        circle.centerX(toView: container)
        circle.bottom(toAnchor: logo.centerYAnchor)
        
        // (3)
        let logoEdge: CGFloat = 66
        logo.square(edge: logoEdge)
        logo.centerX(toView: circle)
        logo.verticalSpacing(toView: label, space: 40)
        logo.createRoundCorner(logoEdge / 2)
        logo.createBorder(1, color: color)
        
        // (4)
        codeTitle.centerX(toView: container)
        codeTitle.verticalSpacing(toView: logo, space: 24)
        
        // (5)
        codeLabel.centerX(toView: container)
        codeLabel.verticalSpacing(toView: codeTitle, space: 8)
        
        // (6)
        okButton.verticalSpacing(toView: codeLabel, space: 24)
        okButton.bottom(toView: container, space: -24)
        okButton.horizontal(toView: container, space: 36)
        okButton.createRoundCorner(28)
        
        okButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
        blackView.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
    }

The sketch is like this

(1): label is the instruction label, created by my helper UIMaker. It is sticked to the container's topAnchor with 24px spacing. It should be sticked to left and right, and textAlignment is center to prevent long text can break the UI.

Notes


top: layout the view1's topAnchor to the view2's topAnchor.

horizontal: layout the view1's leftAnchor to view2's leftAnchor and view1's rightAnchor to view2's rightAnchor.


(2): circle is the top curve. You can use an image instead. The circle has width and height equal to UIScreen.main.bounds.width * 2. It is bigger than the view so we can put its lower edge overlay on the container.

Layout circle horizonal center to the view, so the circle looks balance. You can try align to left or right to see differences.

The circle’s bottom edge will align to the logo center. A little bit difficult to understand. The logo is sticked to the top, and circle is sticked to the logo. I set the circle sticked to the container’s top, but the view can’t auto layout. This way, container can easy auto layout, we don’t need to care the container’s height.

Notes


square(value: CGFloat): set the view's width equals to view's height and equal to value

centerX: stick view1's centerXAnchor to view2's centerXAnchor

bottom: layout view1's bottomAnchor to view2's bottomAnchor.


(3): logo has verticalSpacing to label and has 40px spacing. It means the logo’s top and the label’s bottom has 40px spacing.

Notes


verticalSpacing: layout view1's topAnchor to view2's bottomAnchor


(4), (5): same to codeTitle and codeLabel, verticalSpacing will make a space between them.

(6): okButton has verticalSpacing and bottom. So keep in mind, the view can automatically calculate the height of the view.

Display the popup

The popup is ready to display. Let’s display it.


    func show(in view: UIView) {
        addSubviews(views: blackView, container)
        blackView.fill(toView: self)
        container.centerY(toView: self)
        container.horizontal(toView: self, space: 24)
        
        blackView.alpha = 0
        UIView.animate(withDuration: 0.1, animations:
            { [weak self] in self?.blackView.alpha = 1 })
        container.zoomIn(true)
        
        view.addSubviews(views: self)
        fill(toView: view)
    }

We will display the popup inside the controller’s view. Rememeber 3 views: blackView, container, self. We will add blackView and container to self. And we add self to containerView.

view.zoomIn(true) will show the popup with a slight zoom animation.

Dismiss the popup

Quite hard here for dismiss animation. The popup zooms in a little bit and zooms out to disappear.


    @objc func dismiss() {
        let initialValue: CGFloat = 1
        let middleValue: CGFloat = 1.025
        let endValue: CGFloat = 0.001
        func fadeOutContainer() {
            UIView.animate(withDuration: 0.2, animations:
                { [weak self] in self?.container.alpha = 0 })
        }
        func zoomInContainer() {
            UIView.animate(withDuration: 0.05,
                           animations: { [weak self] in self?.container.scale(value: middleValue) })
        }
        func zoomOutContainer() {
            UIView.animate(withDuration: 0.3, delay: 0.05, options: .curveEaseIn,
                           animations:
                { [weak self] in
                    self?.container.scale(value: endValue)
                    self?.blackView.alpha = 0
                }, completion: { [weak self] _ in self?.removeFromSuperview() })
        }
        
        container.transform = container.transform.scaledBy(x: initialValue , y: initialValue)
        fadeOutContainer()
        zoomInContainer()
        zoomOutContainer()
    }

Time to run

  • Add a button to ViewController and show the popup in the button event

    @objc func showReferralPopup() {
        ReferralPopup().show(in: view)
    }

This is how it looks after coding.

Do it better

You can add new class Popup and move components, full show function, full dismiss function and empty setupView to Popup.

The ReferralPopup is inherited from Popup. You can add new popup easily just set inherit from Popup and layout your new view in its setupView.

Full code can pull from my Github