Created
September 17, 2018 14:09
-
-
Save aibobrov/054c68162c7b0b844858a2b63baf1d0e to your computer and use it in GitHub Desktop.
View masked by a rounded polygon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
extension UIBezierPath { | |
/// Makes a bezier path which can be used for a rounded polygon | |
/// layer | |
/// | |
/// - Parameters: | |
/// - rect: uiview rect bounds | |
/// - lineWidth: width border line | |
/// - sides: number of polygon's sides | |
/// - cornerRadius: radius for corners | |
/// - rotationOffset: offset of rotation of the view | |
/// - Returns: the newly created bezier path for layer mask | |
static func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: Int, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath { | |
let path = UIBezierPath() | |
let theta: CGFloat = CGFloat(2.0 * .pi) / CGFloat(sides) // How much to turn at every corner | |
let width = min(rect.size.width, rect.size.height) // Width of the square | |
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0) | |
// Radius of the circle that encircles the polygon | |
// Notice that the radius is adjusted for the corners, that way the largest outer | |
// dimension of the resulting shape is always exactly the width - linewidth | |
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0 | |
// Start drawing at a point, which by default is at the right hand edge | |
// but can be offset | |
var angle = CGFloat(rotationOffset) | |
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle)) | |
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))) | |
for _ in 0 ..< sides { | |
angle += theta | |
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle)) | |
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle)) | |
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta)) | |
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)) | |
path.addLine(to: start) | |
path.addQuadCurve(to: end, controlPoint: tip) | |
} | |
path.close() | |
// Move the path to the correct origins | |
let bounds = path.bounds | |
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0, y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0) | |
path.apply(transform) | |
return path | |
} | |
} | |
@IBDesignable | |
class PolygonMaskedView: UIView { | |
@IBInspectable var cornerRadius: CGFloat = 0 { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var lineWidth: CGFloat = 0 { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var sides: Int = 0 { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var rotationOffset: CGFloat = 0 { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var isChecked: Bool = false { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var strokeColor: UIColor = .clear { | |
didSet { | |
updateMask() | |
} | |
} | |
@IBInspectable var fillColor: UIColor = .clear { | |
didSet { | |
updateMask() | |
} | |
} | |
var borderLayer: CAShapeLayer = CAShapeLayer() | |
private func updateMask() { | |
layer.mask = nil | |
let mask = CAShapeLayer() | |
guard sides > 2 else { return } | |
let path: UIBezierPath = .roundedPolygonPath( | |
rect: bounds, | |
lineWidth: lineWidth, | |
sides: sides, | |
cornerRadius: cornerRadius, | |
rotationOffset: rotationOffset | |
) | |
let transform = CGAffineTransform(scaleX: bounds.width / path.bounds.width, y: bounds.height / path.bounds.height) | |
path.apply(transform) | |
mask.path = path.cgPath | |
layer.mask = mask | |
borderLayer.removeFromSuperlayer() | |
if !isChecked { | |
borderLayer.path = path.cgPath | |
borderLayer.lineWidth = lineWidth | |
borderLayer.strokeColor = strokeColor.cgColor | |
borderLayer.fillColor = UIColor.clear.cgColor | |
layer.addSublayer(borderLayer) | |
} | |
backgroundColor = isChecked ? fillColor : .clear | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
updateMask() | |
} | |
override func prepareForInterfaceBuilder() { | |
super.prepareForInterfaceBuilder() | |
updateMask() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment