Search code examples
iosswiftuibuttonuibezierpath

How to plot circular buttons around a central point in a ring?


I am attempting to plot circular buttons in a ring like this.

enter image description here

I have 6 UI buttons which all have a corner-radius of 360 degrees, what I am attempting to do in Swift is plot them equally around a central button/point just like the image above. Each circle is equally spaced from its neighbour. What is the best way of achieving this functionality? Should I create a bezier curve and then use math to plot the buttons around said curve or is there another way which you would suggest?

A side note is that I am not looking for a radial menu of any kind there is no animation required. I am just trying to plot my existing UI Buttons in the format above.

Thanks!


Solution

  • I would suggest using math (trigonometry) to compute the horizontal and vertical offsets from the center button and then use layout anchors to position the buttons.

    Here is a self contained example:

    class ViewController: UIViewController {
    
        func createButton(size: CGFloat) -> UIButton {
            let button = UIButton(type: .custom)
            button.backgroundColor = .red
            button.translatesAutoresizingMaskIntoConstraints = false
            button.widthAnchor.constraint(equalToConstant: size).isActive = true
            button.heightAnchor.constraint(equalToConstant: size).isActive = true
            button.layer.cornerRadius = size / 2
    
            return button
        }
    
        func setUpButtons(count: Int, around center: UIView, radius: CGFloat) {
            // compute angular separation of each button
            let degrees = 360 / CGFloat(count)
    
            for i in 0 ..< count {
                let button = createButton(size: 50)
                self.view.addSubview(button)
    
                // use trig to compute offsets from center button
                let hOffset = radius * cos(CGFloat(i) * degrees * .pi / 180)
                let vOffset = radius * sin(CGFloat(i) * degrees * .pi / 180)
    
                // set new button's center relative to the center button's
                // center using centerX and centerY anchors and offsets
                button.centerXAnchor.constraint(equalTo: center.centerXAnchor, constant: hOffset).isActive = true
                button.centerYAnchor.constraint(equalTo: center.centerYAnchor, constant: vOffset).isActive = true
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let centerButton = createButton(size: 50)
            self.view.addSubview(centerButton)
    
            // use anchors to place center button in the center of the screen
            centerButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
            centerButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
    
            setUpButtons(count: 6, around: centerButton, radius: 100)
        }
    
    }
    

    Notes:

    • If you don't need the center button, just set up the buttons around self.view:

      setupButtons(count: 6, around: self.view, radius: 100)
      

      or around an arbitrary point:

      let point = CGPoint(x: 180, y: 300)
      let centerView = UIView(frame: CGRect(origin: point, size: CGSize.zero))
      self.view.addSubview(centerView)
      setUpButtons(count: 6, around: centerView, radius: 140)
      
    • Using a UIView as the center instead of a point is more flexible because you can dynamically move that UIView and the buttons will follow.

    Here it is running in the simulator:

    Demo running in the simulator