-1

I've been trying to create a triangle shaped button. To do that, initially I've tried to create a basic triangle shape with using BezierPath.

In order to create a triangle shape; I've created a UIView Class. Here is the code:

class BezierView: UIView {

override func draw(_ rect: CGRect) {
    
    let triangle = UIBezierPath()
    triangle.move(to: CGPoint(x: 200, y: 200))
    triangle.addLine(to: CGPoint(x: 160, y: 240))
    triangle.addLine(to: CGPoint(x: 250, y: 240))
    triangle.close()
    

}

After creating that class of Bezierview, I've tried to call this custom class in my main ViewController.

 class ViewController: UIViewController {


  override func viewDidLoad() {

    super.viewDidLoad()
    
    let bezierView = BezierView(frame: CGRect(x: 100, y: 100, width: 10, height: 10)) // Here I'm trying to determine the location and size of the triangle
    
    bezierView.backgroundColor = .black
    
    self.view.addSubview(bezierView)

 }
}

What this code does is only bringing a black rectangle Shape to the screen. While I'm trying to draw a basic triangle, my code block is not working.

Although after creating that kind of a triangle shape, I want to use it as the frame of a UIButton. So is that something possible to do? Can I set the frame of a button to that custom made triangle? From what I know; frame is a CGRect and can not be converted to UIBezierPath. What can I do about these 2 problems. (First creating a triangle then shaping a button frame as that custom triangle shape)

Thank you for your help. Hopefully everything was clearly explained.

Cheers.

Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
cemogolog
  • 3
  • 2

2 Answers2

0

First, you can directly create a subclass of UIButton and add create a shape. Second, use a button frame rect to create a triangle, so the triangle is dynamic and dependent on the size and frame of the button.

Also, use .fill() for set background color to triangle.

Here, is the button subclass.

class TriangleButtonView: UIButton {
    
    private let triangle = UIBezierPath()
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        triangle.move(to: CGPoint(x: 0, y: rect.height))
        triangle.addLine(to: CGPoint(x:rect.width/2, y: 0))
        triangle.addLine(to: CGPoint(x:rect.width, y:rect.height))
        triangle.close()
        UIColor.red.setFill() //<--- Fill your color here
        triangle.fill()
    }
    
    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)
            // or you can use send action
            // self.sendActions(for: .touchUpInside)
        }
    }
}

Your view controller,

class ViewController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        let button = TriangleButtonView(frame: CGRect(x: 100, y: 100, width: 10, height: 10))
        self.view.addSubview(button)
    }
}

The problem in your code is, you are set triangle points more than the given frame from view controller class.

Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • Incredible explanation. Thanks! Now I have a small problem. Drawing a triangle shape has been done. To be able to use that specified triangle shape, I've changed the class of my IBAction Button to the customer "TriangleButtonView" . But now I need to edit the clickable section of my button. My button is shaped as triangle inside it, but clickable section is still a rectangle. So I can click outside of the triangle as well. Is there any possible way to edit the clickable section of the button? – cemogolog Feb 15 '21 at 11:37
  • Additional Question: Why do we use super.draw(rect) and triangle.close() ? because the code works perfectly even without them. Thanks. – cemogolog Feb 15 '21 at 11:52
  • No need but if we have more than one subclass for this class super methods are useful and a close path also not needed for this case. – Raja Kishan Feb 15 '21 at 11:55
  • This code of block you've recently updated works perfectly. Trying to understand how that works :) Thank you a lot. – cemogolog Feb 15 '21 at 11:57
0
  1. Using bezier path for drawing a triangular icon for button is an overkill. Simply use an image / icon / SFSymbol. SFSymbol has an empty triangle, a filled triangle, and right and left filled triangles too. If you cannot or dont want to use SFSymbols, just use an image (even SVG images are supported starting iOS 14).

  2. If you are ok with just showing a triangular button, above point solves your issue. If you also want to allow tapping only within the triangular area, and not the entire button's frame rectangle (which is not reccommended unless absolutely necessary), use an image view instead of a button and handle the touches yourself, instead of button's default actions. since you are working with a triangle, brushing up some basic school level geometry shold help you find which of the touches are within you triangle :).

EDIT:

More details to answer your comment below:

  • UIButton is a UIView, that provides a specific behaviour using additional subviews (label, and image), gestures, states, and actions
  • If you don't use a UIButton, you will not be able to use any of that by default
  • However, what you expect (allow to tap only a cestain area) is not one of the default behaviors provided by UIButton. In fact none of the views provide this behavior by default
  • So you HAVE to write you implementation irrespective of whether you use a button or image
  • You can find similar solutions which you can use as reference for your implementation
  • but with UIView / UIImage, you have sort of a clean slate, where only your implementation works. With a UIButton, you need to make sure button's own touch handling doesn't meddle with your touch handling code
Swapnil Luktuke
  • 10,385
  • 2
  • 35
  • 58
  • Thank you for a different point of view. I'll probably choose this way to apply in the app. This looks a bit easier. But as you said I want to allow tapping section of the button as the triangular are only. So will I be able to do that without using clickable imageview? What are the downsides of using a clickable image rather than a button? Triangular shaped clickable image might solve my problem I suppose. – cemogolog Feb 15 '21 at 11:43
  • Was sort of a long write up to answer what you've asked in the comment above, so added it in the answer directly :) – Swapnil Luktuke Feb 15 '21 at 14:05