How to sort correct CAShapeLayers in UIView and child button? - swift

I create a parent view with CAShapeLayer, and create a child button with CAShapeLayer.
class TestButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 60, y: 100))
path.addLine(to: CGPoint(x: 0, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: .pi, endAngle: .pi*1.5, clockwise: true)
path.addLine(to: CGPoint(x: 100, y: 60))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: .pi*1.5, endAngle: .pi, clockwise: false)
path.close()
let drawLayer = CAShapeLayer.init()
drawLayer.path = self.path.cgPath
drawLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(drawLayer)
}
}
class TestView:UIView{
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: rect.height-20))
path.addLine(to: CGPoint(x: rect.width/2, y: rect.height))
path.addLine(to: CGPoint(x: rect.width, y: rect.height-20))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
let drawLayer = CAShapeLayer.init()
drawLayer.path = self.path.cgPath
drawLayer.fillColor = UIColor.red.cgColor
self.layer.addSublayer(drawLayer)
}
}
createView
func createView() {
let announce = TestAnnotationview.init(frame: CGRect.init(x: 50, y: 400, width: 150, height: 150))
announce.backgroundColor = .blue
self.view.addSubview(announce)
let btn = TestButton2.init(frame: CGRect.init(x: 0, y: 0, width: 150, height: 150))
btn.backgroundColor = .yellow
announce.addSubview(btn)
}
It run at simular like this, parent view's CAShapeLayers cover its child button CAShapeLayers.
but it show in Debug View Hierarchy like this.
How do i sort them like Debug View Hierarchy?
Thanks for answer!!

Don't create and add a new sublayer in draw(_ rect:). You don't control how often that method is called, and you are adding a new sublayer every time it is.
Overriding drawRect is generally not a great idea, since it can affect the drawing of other parts of the view and performance during animations.
Create and add the sublayer once, in your init method, for example, and position it if you need to in layoutSubviews. Don't override draw(_ rect:).

Related

How to make a CAshapeLayer fit into a button?

I tried to create a function inside a custom UIButton class to add a shape to an existing button.
func drawStartButton(){
let shape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 500, y: 0))
path.addLine(to: CGPoint(x: 500, y: -100))
path.addLine(to: CGPoint(x: 250, y: -50))
path.addLine(to: CGPoint(x: 0, y: -100))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
shape.path = path.cgPath
shape.fillColor = redColor.cgColor
shape.frame = self.bounds
self.layer.addSublayer(shape)
}
So far no problem... BUT when i add the layer to the button, the layer is to big of course! How can i "autoresize" the layer to its button? I did expect something like
shape.frame = self.bounds
... but the path still keeps the same size as without the self.bounds.
Your best bet is to subclass the button and then layout your layer in layoutSubviews which gets called when your surrounding frame changes.
final class StartButton: UIButton {
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.frame = bounds
}
}
But I'm noticing that you are dictating a giant shape in terms of your CGPoints. You might have to subclass the layer, too, and redraw based on your bounds in layoutSublayers.
class CustomShapeCAshapeLayer: CAShapeLayer {
func shapeButton(width: CGFloat, height: CGFloat){
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: width, y: 0))
path.addLine(to: CGPoint(x: width, y: height + 30))
path.addLine(to: CGPoint(x: width/2, y: height))
path.addLine(to: CGPoint(x: 0, y: height + 30))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
self.fillColor = redColor.cgColor
self.path = path.cgPath
}
}
main:
let layer = CustomShapeCAshapeLayer()
layer.shapeButton(width: startGameButton.frame.width, height: startGameButton.frame.height)
startGameButton.layer.addSublayer(layer)

How to create Right Angle View in Swift?

I am able to create triangle view in swift using below code-
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
// Get Height and Width
let layerHeight = layer.frame.height
let layerWidth = layer.frame.width
// Create Path
let bezierPath = UIBezierPath()
// Draw Points
bezierPath.move(to: CGPoint(x: 0, y: layerHeight))
bezierPath.addLine(to: CGPoint(x: layerWidth, y: layerHeight))
bezierPath.addLine(to: CGPoint(x: layerWidth / 2, y: 0))
bezierPath.addLine(to: CGPoint(x: 0, y: layerHeight))
bezierPath.close()
// Apply Color
UIColor.red.setFill()
bezierPath.fill()
// Mask to Path
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
layer.mask = shapeLayer
}
}
override func viewDidLoad() {
super.viewDidLoad()
let triangle = TriangleView(frame: CGRect(x: 0, y: 0, width: 55 , height: 60))
triangle.backgroundColor = .white
triangle.transform = CGAffineTransform(rotationAngle: .pi * 4.49)
imageView.addSubview(triangle)
}
Now problem is that I want to change it to the right angle triangle using transform properties. How to achieve this?
Expected Result
Current Result
I hope you want this part.
Here is the code.
override func draw(_ rect: CGRect) {
let path = UIBezierPath.init()
// top left
path.move(to: .zero)
// top right
path.addLine(to: CGPoint(x: bounds.size.width,
y: 0))
// bottom left offset
path.addLine(to: CGPoint(x: bounds.size.width * 0.1,
y: bounds.size.height))
// bottom left
path.addLine(to: CGPoint(x: 0,
y: bounds.size.height))
// top left
path.close()
// change fill color here.
UIColor.black.setFill()
path.fill()
}
Result
How I drew
Modification
If you change the code UIColor.black.setFill() to
let fillColor = UIColor(red: 0xF9/0xff, green: 0x0D/0xff, blue: 0x5A/0xff, alpha: 1)
fillColor.setFill()
you get this result.
Have fun coding.
You're currently drawing 'a'. You want to draw 'b'.
extension UIBezierPath {
convenience init(rightAngledTriangleInRect: CGRect, offset withOffset: CGFloat) {
self.init()
move(to: CGPoint(x: rect.minX, y: rect.minY))
addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
addLine(to: CGPoint(x: rect.minX + offset, y: rect.maxY))
addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
addLine(to: CGPoint(x: rect.minX, y: rect.minY))
close()
}
}
Then use by:
let path = UIBezierPath.init(rightAngledTriangleInRect: rect, withOffset: 20.0)
Note that there's no need to add self. in the convenience inits for the bezier path instructions.
If you're doing a lot of drawing, I'd recommend adding a number of these inits to make life easier.
To use in your current TriangleView:
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath.init(rightAngledTriangleInRect: rect, withOffset: 20.0)
let shape = CAShapeLayer()
shape.path = path.cgPath
// Set the mask
layer.mask = shape
}
}
There are some easy way too for achivieng this but for using transform here is the sample code.use this transform instead of yours.it's sample code for having right angle triangle from your drawing:
triangle.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi).translatedBy(x: (triangle.bounds.width / 2), y: 0.0)
you should notice that CGAffineTransform rotate the view from it's origins

How to add curved view top left cornor in swift

How to add uiview to top-left corner with curves.
I have done somethings like this
class curvedView: UIView {
override func draw(_ rect: CGRect) {
let color = UIColor.blue
let y:CGFloat = 0
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: rect.height * 2))
myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))
myBezier.addLine(to: CGPoint(x: 0, y: rect.height))
myBezier.close()
color.setFill()
myBezier.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clear
}
}
But this create a curve in U shape but i want curve from x position 180 to Y position 250.
Any help would be so thankful.
you can try these codes.it will help you
let path = UIBezierPath(roundedRect:viewToRound.bounds, byRoundingCorners:[.TopRight, .BottomLeft], cornerRadii: CGSizeMake(20, 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
viewToRound.layer.mask = maskLayer

Swift SKShapeNode not accepting initialiser

I can't get the a sub class of SKShapeNode to accept an initialiser. To work around this I've tried to go by the example posted here Adding Convenience Initializers in Swift Subclass. The code i'm using is below.
class Ground1 : SKShapeNode {
override init() {
super.init()
print("Hello1")
}
convenience init(width: CGFloat, point: CGPoint) {
var points = [CGPoint(x: -900, y: -300),
CGPoint(x: -600, y: -100),
CGPoint(x: -100, y: -300),
CGPoint(x: 2, y: 150),
CGPoint(x: 100, y: -300),
CGPoint(x: 600, y: -100),
CGPoint(x: 900, y: -300)]
self.init(splinePoints: &points, count: points.count)
lineWidth = 5
strokeColor = UIColor.orange
physicsBody = SKPhysicsBody(edgeChainFrom: path!)
physicsBody?.restitution = 0.75
physicsBody?.isDynamic = false
print("Hello2")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
When initialised the convenience init() is ignored and not used. Resulting in no shape being added to the scene. What am I doing wrong ?
Thanks to 0x141E, who roundabout pointed me in the right direction. I found this worked but the code looks messy. The reasons for going about the initialisation this way is explained in detail here Adding Convenience Initializers in Swift Subclass. Although I was thrown out of finding a solution because of the use of splinePoints and assigning to a path.
class Ground1 : SKShapeNode {
override init() {
super.init()
}
convenience init(name: String) {
var points = [CGPoint(x: -900, y: -300),
CGPoint(x: -600, y: -100),
CGPoint(x: -100, y: -300),
CGPoint(x: 2, y: 150),
CGPoint(x: 100, y: -300),
CGPoint(x: 600, y: -100),
CGPoint(x: 900, y: -300)]
self.init(splinePoints: &points, count: points.count)
lineWidth = 5
strokeColor = UIColor.orange
physicsBody = SKPhysicsBody(edgeChainFrom: self.path!)
physicsBody?.restitution = 0.75
physicsBody?.isDynamic = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Add gradient color to a custom UIView

I realized a custom UIView class to a realize a sort of trapeze; following code I had used to realize the class:
import UIKit
import ChameleonFramework
class PolygonalView: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: 171))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
context.closePath()
context.setFillColor(FlatSkyBlue().cgColor)
context.fillPath()
}
}
Then in my UIViewController I had create my view:
let pol = PolygonalView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 196))
pol.backgroundColor = UIColor.clear
self.view.addSubview(pol)
I achieved my goal, yep! But a problem has arisen: if I set a gradient, this gradient cover all the rectangular frame of the view.
Following codes and image...
let gradientColor = CAGradientLayer()
gradientColor.frame = pol.bounds
gradientColor.colors = [FlatSkyBlue().cgColor, FlatSkyBlueDark().cgColor, FlatBlue().cgColor]
gradientColor.startPoint = CGPoint(x: 0, y: 0)
gradientColor.endPoint = CGPoint(x: 1, y: 1)
pol.layer.addSublayer(gradientColor)
Any solutions to adapt the gradient to the real shape of my View?
Set the gradientColor layer's mask property to CAShapeLayer() that has the correct shape filled with black
let mask = CAShapeLayer()
mask.frame = // to whatever size it should be
mask.path = yourShapeBezierHERE.cgPath
mask.fillColor = UIColor.black.cgColor
gradientColor.mask = mask