iosswiftautolayoutuipagecontrol

Vertically rotated UIPageControl taking too much space


I used CGAffineTransform to rotate a horizontal UIPageControl vertical. But when I added it besides my collection view it's taking too much width. And when I add a width anchor on it, the UIPageControl disappears.

noticesPagingIndicator = UIPageControl()
let angle = CGFloat.pi/2
noticesPagingIndicator.transform = CGAffineTransform(rotationAngle: angle)
 NSLayoutConstraint.activate([
//            noticesPagingIndicator.widthAnchor.constraint(equalToConstant: 30),
            noticesPagingIndicator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            noticesPagingIndicator.centerYAnchor.constraint(equalTo: noticesCollectionView.centerYAnchor),
            noticesCollectionView.leadingAnchor.constraint(equalTo: noticesPagingIndicator.trailingAnchor),
            noticesCollectionView.topAnchor.constraint(equalTo: noticeStackView.bottomAnchor, constant: 8),
            noticesCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
            noticesCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
 ])

page control taking too much width

When I look at the UIView hierarchy, I see a lot of padding along the UIPageControl

UI view hierarchy with too much width for page controller

With the width anchor enabled:

enter image description here with width anchor


Solution

  • Get to know the Debug View Hierarchy tool. It can help you figure out most layout issues.

    When you transform a view, that doesn't change its bounds and thus doesn't change its constraint relationships to other UI elements.

    With this code (8 pages, so 8 dots):

    class ViewController: UIViewController {
        
        let pgc = UIPageControl()
        let greenLabel = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemYellow
            
            pgc.translatesAutoresizingMaskIntoConstraints = false
            greenLabel.translatesAutoresizingMaskIntoConstraints = false
            
            view.addSubview(pgc)
            view.addSubview(greenLabel)
            
            let g = view.safeAreaLayoutGuide
            
            NSLayoutConstraint.activate([
                
                // page control Leading to safe area Leading + 20, centerY
                pgc.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                pgc.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
                
                // constrain greenLabel Leading to page control trailing + 8 and centerY, safe area trailing -8
                greenLabel.leadingAnchor.constraint(equalTo: pgc.trailingAnchor, constant: 8.0),
                greenLabel.centerYAnchor.constraint(equalTo: pgc.centerYAnchor),
                greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                
            ])
    
            // rotate the page control
            let angle = CGFloat.pi/2
            pgc.transform = CGAffineTransform(rotationAngle: angle)
            
            pgc.backgroundColor = .systemBlue
            greenLabel.backgroundColor = .green
            
            pgc.numberOfPages = 8
            
            greenLabel.numberOfLines = 0
            greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
            
        }
        
    }
    

    You get this output:

    enter image description here

    As you've seen, the Green Label Leading constraint to the page control Trailing Anchor shows the page control width matches what it would be without the rotation.

    If you inspect the views with Debug View Hierarchy, you'll see the page control looks like this:

    enter image description here

    The frame is w: 27.5 h: 217 but the bounds is w: 217 h: 27.5.

    To fix this, you need to embed the page control in a "holder" view, constrain the holder view's Height to the page control's Width and Width to Height. Then constrain your other elements to that "holder" view:

    class ViewController: UIViewController {
        
        let pgcHolder = UIView()
        let pgc = UIPageControl()
        let greenLabel = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemYellow
            
            pgcHolder.translatesAutoresizingMaskIntoConstraints = false
            pgc.translatesAutoresizingMaskIntoConstraints = false
            greenLabel.translatesAutoresizingMaskIntoConstraints = false
        
            pgcHolder.addSubview(pgc)
            view.addSubview(pgcHolder)
            view.addSubview(greenLabel)
            
            let g = view.safeAreaLayoutGuide
        
            NSLayoutConstraint.activate([
    
                // center page control in its "holder" view
                pgc.centerXAnchor.constraint(equalTo: pgcHolder.centerXAnchor),
                pgc.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
                
                // constrain holder view leading to view + 20, centerY
                pgcHolder.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                pgcHolder.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
                
                // constrain holder view WIDTH to page control HEIGHT
                pgcHolder.widthAnchor.constraint(equalTo: pgc.heightAnchor),
                // constrain holder view HEIGHT to page control WIDTH
                pgcHolder.heightAnchor.constraint(equalTo: pgc.widthAnchor),
                
                // constrain greenLabel Leading to holder view trailing + 8 and centerY, safe area trailing -8
                greenLabel.leadingAnchor.constraint(equalTo: pgcHolder.trailingAnchor, constant: 8.0),
                greenLabel.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
                greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                
            ])
            
            let angle = CGFloat.pi/2
            pgc.transform = CGAffineTransform(rotationAngle: angle)
    
            pgcHolder.backgroundColor = .systemRed
            pgc.backgroundColor = .systemBlue
            greenLabel.backgroundColor = .green
            
            pgc.numberOfPages = 8
            
            greenLabel.numberOfLines = 0
            greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
    
        }
        
    }
    

    Now we have our desired output:

    enter image description here