erakol erakol - 4 years ago 176
Objective-C Question

UIButton shape according to image frame

Consider these are two button shapes without overlapping touch event how can i achive this shape?I want to do UIButton shape like exacly different images ex. If image if triangular button should looks like triangular.

I doing folowing it normally but not getting exact shape like image frame,

UIImage *btnmage = [UIImage imageNamed:@"triangular.png"];
UIButton *button = [[UIButton alloc]init];
[button setImage: btnImage forState:UIControlStateNormal];


Can anyone please tell me how can i achive this?

Answer Source

Note: I published this code on GitHub.

This UIButton subclass will take a shape and use this for rendering and hit testing:

import UIKit

class ShapedButton: UIButton {

    var shape: UIBezierPath = UIBezierPath() {
        didSet{
            let l = CAShapeLayer()
            l.path = shape.cgPath
            self.layer.mask = l
        }
    }


    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return  shape.cgPath.contains(point)
    }


    func configureUpFacingTriangle() {
        let triangleBezier = UIBezierPath()
        triangleBezier.move(to: CGPoint(x: self.frame.size.width / 2, y:0))
        triangleBezier.addLine(to: CGPoint(x: 0, y: self.frame.size.height))
        triangleBezier.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
        triangleBezier.close()
        self.shape = triangleBezier

        let pointSize = self.titleLabel?.font.pointSize ?? 0
        self.titleEdgeInsets.top = self.titleEdgeInsets.top + (self.frame.size.height - pointSize - 7)
    }

    func configureDownFacingTriangle() {
        let triangleBezier = UIBezierPath()
        triangleBezier.move(to: CGPoint.zero)
        triangleBezier.addLine(to: CGPoint(x: self.frame.size.width, y: 0))
        triangleBezier.addLine(to: CGPoint(x:self.frame.size.width/2, y: self.frame.size.height ))
        triangleBezier.close()

        let pointSize = self.titleLabel?.font.pointSize ?? 0
        self.titleEdgeInsets.top = self.titleEdgeInsets.bottom - (self.frame.size.height - pointSize - 7)
        self.shape = triangleBezier
    }
}

use it like

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var leftButton: ShapedButton!
    @IBOutlet weak var button: ShapedButton!
    @IBOutlet weak var rightButton: ShapedButton!
    override func viewDidLoad() {
        super.viewDidLoad()

        for (idx, b) in [leftButton, button, rightButton].enumerated() {
            if let b = b {
                if idx % 2 == 0 {
                    b.configureDownFacingTriangle()
                } else {
                    b.configureUpFacingTriangle()
                }
            }
        }
    }
}

storyboard:

storyboard

result:

screenshot

Although the buttons overlap, they don't consume the touch events for the other buttons, as they will only successfully hit test it, if they occur inside of the shape.


Note: I changed the code to add everything but point(inside:with:) to UIButton via an extension. This allows regular UIButton to be configured as an triangle, circle or any other shape. Though only an instance of ShapedButton will use the shape in hit testing, as it needs the overriden point(inside:with:). UIButton just will test the bounding box (aka: bounds). See the changes on GitHub.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download