Search code examples
iosswiftuikituipagecontrol

Scaling current dot of UIPageControl and keeping it centered


I've subclassed UIPageControl in order to have its current dot bigger.

class CustomPageControl: UIPageControl {
    override var currentPage: Int {
        didSet {
            updateDots()
        }
    }

    func updateDots() {
        let currentDot = subviews[currentPage]
        let largeScaling = CGAffineTransform(scaleX: 3, y: 3)

        subviews.forEach {
            // apply the large scale of newly selected dot
            // restore the normal scale of previously selected dot
            $0.transform = $0 == currentDot ? largeScaling : .identity
        }
    }
}

But the result of the transform isn't centered (the red dot should be aligned with the others):
enter image description here

I've tried (on iOS 12):

  • changing the frame or center of currentDot has no effect.
  • changing the transform to include a translatedBy(x: CGFloat, y: CGFloat) has no effect.
  • changing the constraints like here is making the first dot jumping:

    currentDot.translatesAutoresizingMaskIntoConstraints = false
    currentDot.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0)
    currentDot.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0)
    

    enter image description here


Solution

  • I got it finally working by rewriting all the subviews constraints by myself.

    // https://stackoverflow.com/a/55063316/1033581
    class DefaultPageControl: UIPageControl {
    
        override var currentPage: Int {
            didSet {
                updateDots()
            }
        }
    
        override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
            super.sendAction(action, to: target, for: event)
            updateDots()
        }
    
        private func updateDots() {
            let currentDot = subviews[currentPage]
            let largeScaling = CGAffineTransform(scaleX: 3.0, y: 3.0)
            let smallScaling = CGAffineTransform(scaleX: 1.0, y: 1.0)
    
            subviews.forEach {
                // Apply the large scale of newly selected dot.
                // Restore the small scale of previously selected dot.
                $0.transform = $0 == currentDot ? largeScaling : smallScaling
            }
        }
    
        override func updateConstraints() {
            super.updateConstraints()
            // We rewrite all the constraints
            rewriteConstraints()
        }
    
        private func rewriteConstraints() {
            let systemDotSize: CGFloat = 7.0
            let systemDotDistance: CGFloat = 16.0
    
            let halfCount = CGFloat(subviews.count) / 2
            subviews.enumerated().forEach {
                let dot = $0.element
                dot.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.deactivate(dot.constraints)
                NSLayoutConstraint.activate([
                    dot.widthAnchor.constraint(equalToConstant: systemDotSize),
                    dot.heightAnchor.constraint(equalToConstant: systemDotSize),
                    dot.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0),
                    dot.centerXAnchor.constraint(equalTo: centerXAnchor, constant: systemDotDistance * (CGFloat($0.offset) - halfCount))
                ])
            }
        }
    }
    

    System constants in the code (7.0 and 16.0) are respectively the size and the distance found for a default UIPageControl dot on iOS 12.

    result