I am trying to arrange numbered buttons programmatically in a circle. NSLayoutConstraint anchors the view while a subClass of UIView creates the circle of buttons. The frame rotates about the centre but buttons rotate inconsistently. Text orientation is the same on all but one rotation.
for example
My Swift code for arranging buttons improves on an earlier effort in Objective C. I have also followed up a few helpful suggestions for rotating a view about the screen centre including this one.
I'd be grateful if anyone can show me how to improve my code so the UI is consistent for every screen size and orientation.
Here is the Viewcontroller
import UIKit
class ViewController: UIViewController {
var circle: CircleView?
override func viewDidLoad() {
super.viewDidLoad()
let circle = CircleView()
circle.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(circle)
let horizontalConstraint = circle.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = circle.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
}
}
... and the subclass of UIView
import UIKit
class CircleView: UIView {
// MARK: Initialization
let points: Int = 10 // 80 25 16 10 5
let dotSize: CGFloat = 60 // 12 35 50 60 100
let radius: CGFloat = 48 // 72 70 64 48 45
var arcPoint = CGFloat(M_PI * -0.5) // clockwise from 12+ (not 3+)!
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
drawUberCircle()
drawBoundaryCircles()
}
... the second last function draws the coloured circular background
func drawUberCircle() {
// Create a CAShapeLayer
let shapeLayer = CAShapeLayer()
// give Bezier path layer properties
shapeLayer.path = createBezierPath().cgPath
shapeLayer.strokeColor = UIColor.cyan.cgColor
shapeLayer.fillColor = UIColor.cyan.cgColor
shapeLayer.lineWidth = 1.0
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
let path = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0),
radius: radius * 2,
startAngle: CGFloat(M_PI * -0.5),
endAngle: CGFloat(M_PI * 1.5),
clockwise: true)
return path
}
... while the last function draws buttons in a circular arc
func drawBoundaryCircles() {
for index in 1...points {
let point: CGPoint = makeBoundaryPoint()
drawButton(point: point, index: index)
}
}
func makeBoundaryPoint() -> (CGPoint) {
arcPoint += arcAngle()
print(arcPoint)
let point = CGPoint(x: 0 + (radius * 2 * cos(arcPoint)), y: 0 + (radius * 2 * sin(arcPoint)))
return (point)
}
func arcAngle() -> CGFloat {
return CGFloat(2.0 * M_PI) / CGFloat(points)
}
func drawButton(point: CGPoint, index: Int) {
let myButton = UIButton(type: .custom) as UIButton
myButton.frame = CGRect(x: point.x - (dotSize/2), y: point.y - (dotSize/2), width: dotSize, height: dotSize)
myButton.backgroundColor = UIColor.white
myButton.layer.cornerRadius = dotSize / 2
myButton.layer.borderWidth = 1
myButton.layer.borderColor = UIColor.black.cgColor
myButton.clipsToBounds = true
myButton.titleLabel!.font = UIFont(name: "HelveticaNeue-Thin", size: dotSize/2)
myButton.setTitleColor(UIColor.red, for: .normal)
myButton.setTitle(String(index), for: .normal)
myButton.tag = index;
myButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
addSubview(myButton)
}
func buttonAction(myButton: UIButton) {
let sender:UIButton = myButton
print("Button \(sender.tag) was tapped")
}
}
EDIT
Rotation 4 (shown above) appears when the iPhone is held upside down and is irrelevant for reasons explained in the accepted answer (see below). This becomes more obvious if the code runs on an actual device rather than the simulator.
A SOLUTION WITH WORKING BUTTONS
For a solution with buttons that remain on-centre and work, refer to this