skip to Main Content

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

3

Answers


  1. I have added some code in your existing code and you can try it. It might help you.
    I recommend you to ask for rectangular shape image.

       class TriangleShapeButton: UIButton {
            
           let triangle = UIBezierPath()
           let triangleLayer = CAShapeLayer()
            
            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))
                
                triangleLayer.path = triangle.cgPath
                triangleLayer.fillColor = UIColor.red.cgColor
                self.layer.addSublayer(triangleLayer)
                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)
                    
                }
            }
        }
    

    enter image description here

    Login or Signup to reply.
  2. Your original approach of changing the shape of a UIView is not possible. Every UIView is a rectangle at the low-level, so adjacent triangles as in your case will necessarily overlap.

    But there is a different approach:

    You can create a single class for the entire circular view. Inside this class, you modify the drawRect function in such a way, that the individual triangles (or circle segments) are drawn adjacent to each other.

    Inside this class you can then override the touchesBegan method. There you can check for the touch position using touches.first?.location(in: self). This will give you a CGPoint. Then you can manually check in which triangle the touch occurred and perform actions accordingly.

    Login or Signup to reply.
  3. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search