Search code examples
swiftxcodeuibuttoncore-animationuibezierpath

Making the frame of a button triangle instead of rectangle in Swift


I have a button which has a triangle shaped image in it. Which looks like below.

enter image description here

This button also has a triangle clickable area. In order to do that I've created a UIButton class as below.

Button's clickable area is triangle but the frame of the button is still rectangle. How can I make the frame of the button a triangle? If there is a such way, I won't be using bezierpath to draw a clickable area because the frame will already be a triangle.

Thanks.

class triangleShapeButton: UIButton {
private let triangle = UIBezierPath()

override func draw(_ rect: CGRect) {
        super.draw(rect)
        triangle.move(to: CGPoint(x: rect.width, y: rect.height))
        triangle.addLine(to: CGPoint(x:rect.width/2, y: 0))
        triangle.addLine(to: CGPoint(x: 0, y:rect.height))
        triangle.close()
    }

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        // This is for allow touches only in shape area.
        if triangle.contains(touches.first?.location(in: self) ?? .zero) {
            super.touchesBegan(touches, with: event)
         
        }
    }

EDIT: What I want to create is a circular shaped button set as below. And while doing that, the rectangular frames of some of the triangle buttons overlaps the next triangle button. This causes to block the clickable area of some of the triangle buttons. I've tried to change the layer hierarchy but at least 1 of the buttons still overlaps the other one. How can I solve that issue if I can not change the frame?

Thanks a lot

enter image description here


Solution

  • Here's what I would suggest:

    Draw your shapes using CAShapeLayers. If you ultimately want to fill those shapes with images, look at setting up layers where the contents of the layer is a CGImage. Add another CAShapeLayer to draw the border of the shape. Then apply a CAShapeLayer as a mask to crop the image to your triangle/circle wedge shape.

    Now, for figuring out what the user tapped: Build an array of your shape paths, and their frame rectangles. When you get a tap in your view, first loop through the array of rectangles and find all the rectangles that contain the tap point. (There may be more than one.)

    Then loop through the smaller array where the tap was contained in the rectangle, and use UIBezierPath.contains(point:) or CGPath.contains(_:using:transform:) to figure out which of your shapes actually contains the tap point.

    By first filtering your array to only those shapes who's frames contain the tap point, you use a very fast test to reduce the number of shapes you have to check using the much slower path contains() method.