Skip to content

Instantly share code, notes, and snippets.

@mickspecial
Created January 20, 2019 00:34
Show Gist options
  • Save mickspecial/284374a3f0c5b8752cac1651f5209e00 to your computer and use it in GitHub Desktop.
Save mickspecial/284374a3f0c5b8752cac1651f5209e00 to your computer and use it in GitHub Desktop.
Custom Circle Button
class CircleButton: UIControl {
// Mods
private var fontSize: CGFloat!
private var circleDiameter: CGFloat!
enum LabelPostition {
case topLeft, bottomLeft, topRight, bottomRight, left, right, top, bottom
}
enum IconSize {
case small, large, xlarge
}
private var iconSize = IconSize.small
private var labelPost = LabelPostition.top
private var frameSize: CGFloat {
return circleDiameter + (padding * 2) + fontSize
}
private var padding: CGFloat {
return fontSize * 0.55
}
private var radiusCalc: CGFloat {
return circleDiameter * 0.5 + padding
}
private var iconsize: CGFloat {
return circleDiameter * 0.6
}
var buttonImageView: UIImageView = {
let logoView = UIImageView()
logoView.contentMode = .scaleAspectFill
logoView.tintColor = UIColor.orange
logoView.translatesAutoresizingMaskIntoConstraints = false
logoView.isUserInteractionEnabled = false
return logoView
}()
var circleView: UIView = {
let circle = UIView()
circle.contentMode = .scaleAspectFill
circle.isUserInteractionEnabled = false
circle.translatesAutoresizingMaskIntoConstraints = false
return circle
}()
var mainTitle: String = ""
init(named name: String, image: UIImage, labelPostition: LabelPostition = .left, size: IconSize) {
super.init(frame:CGRect(x: 0, y: 0, width: 100, height: 100))
switch size {
case .small:
self.fontSize = 18
self.circleDiameter = 80
case .large:
self.fontSize = 24
self.circleDiameter = 120
case .xlarge:
self.fontSize = 32
self.circleDiameter = 200
}
mainTitle = name
labelPost = labelPostition
let tintedImage = image.withRenderingMode(.alwaysTemplate)
buttonImageView.image = tintedImage
isOpaque = false
setupView()
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let size = self.bounds.size
context.translateBy (x: size.width / 2, y: size.height / 2)
context.scaleBy (x: 1, y: -1)
let f = UIFont.systemFont(ofSize: fontSize, weight: UIFont.Weight.semibold)
var startAngle: CGFloat = .pi
var clock = true
switch labelPost {
case .topLeft:
startAngle = 3 / 4 * .pi
clock = true
case .bottomLeft:
startAngle = 5 / 4 * .pi
clock = false
case .topRight:
startAngle = (.pi / 4)
clock = true
case .bottomRight:
startAngle = -(.pi / 4)
clock = false
case .left:
startAngle = .pi
clock = true
case .right:
startAngle = 0
clock = true
case .top:
startAngle = .pi / 2
clock = true
case .bottom:
startAngle = -(.pi / 2)
clock = false
}
centreArcPerpendicular(text: mainTitle, context: context, radius: radiusCalc, angle: startAngle, colour: UIColor.orange, font: f, clockwise: clock)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func setupView() {
addSubview(circleView)
addSubview(buttonImageView)
setupLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
let h = circleView.frame.size.height
circleView.layer.cornerRadius = h * 0.5
circleView.layer.borderColor = UIColor.darkGray.cgColor
circleView.layer.borderWidth = 5
}
func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool) {
let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
let l = characters.count
let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font, NSAttributedString.Key.kern: 0.8]
var arcs: [CGFloat] = []
var totalArc: CGFloat = 0
for i in 0 ..< l {
arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: r)]
totalArc += arcs[i]
}
let direction: CGFloat = clockwise ? -1 : 1
let slantCorrection: CGFloat = clockwise ? -.pi / 2 : .pi / 2
var thetaI = theta - direction * totalArc / 2
for i in 0 ..< l {
thetaI += direction * arcs[i] / 2
centre(text: characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection)
thetaI += direction * arcs[i] / 2
}
}
func centre(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat) {
let attributes = [NSAttributedString.Key.foregroundColor: c, NSAttributedString.Key.font: font]
context.saveGState()
context.scaleBy(x: 1, y: -1)
context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
context.rotate(by: -slantAngle)
let offset = str.size(withAttributes: attributes)
context.translateBy (x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
context.restoreGState()
}
func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
return 2 * asin(chord / (2 * radius))
}
private func setupLayout() {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
heightAnchor.constraint(equalToConstant: frameSize),
widthAnchor.constraint(equalToConstant: frameSize),
circleView.heightAnchor.constraint(equalToConstant: circleDiameter),
circleView.widthAnchor.constraint(equalToConstant: circleDiameter),
circleView.centerXAnchor.constraint(equalTo: centerXAnchor),
circleView.centerYAnchor.constraint(equalTo: centerYAnchor),
buttonImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
buttonImageView.heightAnchor.constraint(equalToConstant: iconsize),
buttonImageView.widthAnchor.constraint(equalToConstant: iconsize)
])
}
}
@mickspecial
Copy link
Author

The reason I set the image size within the class was because I needed to draw the circle / text. Couldn't get it to work otherwise. Ideally I wouldn't but not sure how to achieve that. So I needed to hack it together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment