Triangle UIView - Swift

41,575

Solution 1

Updated for Swift 3:

class TriangleView : 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.maxY))
        context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
        context.closePath()

        context.setFillColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.60)
        context.fillPath()
    }
}


Swift 2:
import UIKit

class TriangleView : UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func drawRect(rect: CGRect) {

        var ctx : CGContextRef = UIGraphicsGetCurrentContext()

        CGContextBeginPath(ctx)
        CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect))
        CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMaxY(rect))
        CGContextAddLineToPoint(ctx, (CGRectGetMaxX(rect)/2.0), CGRectGetMinY(rect))
        CGContextClosePath(ctx)

        CGContextSetRGBFillColor(ctx, 1.0, 0.5, 0.0, 0.60);
        CGContextFillPath(ctx);
       }
 } 

This will start from MinX, MaxY;
Draw a line from the start to MaxX, MaxY;
Draw a line from MaxX,MaxY to MaxX/2, MinY;
Then close the path to the start location.

The next part sets the color you want to use. In this example 255,127,0, Alpha 0.6 Then will fill the path you just drew above with the set color.

Then in your View Controller

Swift 3:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let triangle = TriangleView(frame: CGRect(x: 10, y: 20, width: 25 , height: 30))
        triangle.backgroundColor = .white
        view.addSubview(triangle)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}


Swift 2:
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let triangle = TriangleView(frame: CGRectMake(10, 20, 25, 30))
        triangle.backgroundColor = .whiteColor()
        view.addSubview(triangle)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

However, this is going to cause the same problem as the frame of this view is still going to be a rectangle. UIKit works with rectangles, you would have to use another framework, like Sprite Kit.

Solution 2

Swift 5:

This code worked for me for different sides. for UIView using CGMutablePath() and CAShapeLayer()

*Assuming your view's height and width are same.

*Set your views's background color as clearColor.

A) Right Side

 @IBOutlet weak var triangleView: UIView!

 override func viewDidLoad() {
        super.viewDidLoad()
        self.setRightTriangle()   
    }

 func setRightTriangle(){
        let heightWidth = triangleView.frame.size.width //you can use triangleView.frame.size.height
        let path = CGMutablePath()

        path.move(to: CGPoint(x: heightWidth/2, y: 0))
        path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
        path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
        path.addLine(to: CGPoint(x:heightWidth/2, y:0))

        let shape = CAShapeLayer()
        shape.path = path
        shape.fillColor = UIColor.blue.cgColor

        triangleView.layer.insertSublayer(shape, at: 0)
    }

enter image description here


B) Left Side

 func setLeftTriangle(){
        let heightWidth = triangleView.frame.size.width
        let path = CGMutablePath()

        path.move(to: CGPoint(x: heightWidth/2, y: 0))
        path.addLine(to: CGPoint(x:0, y: heightWidth/2))
        path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
        path.addLine(to: CGPoint(x:heightWidth/2, y:0))

        let shape = CAShapeLayer()
        shape.path = path
        shape.fillColor = UIColor.blue.cgColor

        triangleView.layer.insertSublayer(shape, at: 0)
    }

enter image description here


C) Up Side

  func setUpTriangle(){
        let heightWidth = triangleView.frame.size.width
           let path = CGMutablePath()

           path.move(to: CGPoint(x: 0, y: heightWidth))
           path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
           path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
           path.addLine(to: CGPoint(x:0, y:heightWidth))

           let shape = CAShapeLayer()
           shape.path = path
           shape.fillColor = UIColor.blue.cgColor

           triangleView.layer.insertSublayer(shape, at: 0)
       }

enter image description here


D) Down Side

  func setDownTriangle(){
        let heightWidth = triangleView.frame.size.width
        let path = CGMutablePath()

        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
        path.addLine(to: CGPoint(x:heightWidth, y:0))
        path.addLine(to: CGPoint(x:0, y:0))

        let shape = CAShapeLayer()
        shape.path = path
        shape.fillColor = UIColor.blue.cgColor

        triangleView.layer.insertSublayer(shape, at: 0)
    }

enter image description here

*Change X any Y values according to your requirement.

Solution 3

I've modified the previous code a bit to add margin and fill color as inspectable and it works well with Swift4:

import UIKit

@IBDesignable
class TriangleView : UIView {
    var _color: UIColor! = UIColor.blue
    var _margin: CGFloat! = 0

    @IBInspectable var margin: Double {
        get { return Double(_margin)}
        set { _margin = CGFloat(newValue)}
    }


    @IBInspectable var fillColor: UIColor? {
        get { return _color }
        set{ _color = newValue }
    }

    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 + _margin, y: rect.maxY - _margin))
        context.addLine(to: CGPoint(x: rect.maxX - _margin, y: rect.maxY - _margin))
        context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY + _margin))
        context.closePath()

        context.setFillColor(_color.cgColor)
        context.fillPath()
    }
}

Solution 4

CAShapeLayer it can change the shape of layers.

    var mask = CAShapeLayer()
    mask.frame = self.layer.bounds

    let width = self.layer.frame.size.width
    let height = self.layer.frame.size.height

    var path = CGPathCreateMutable()

    CGPathMoveToPoint(path, nil, 30, 0)
    CGPathAddLineToPoint(path, nil, width, 0)
    CGPathAddLineToPoint(path, nil, width, height)
    CGPathAddLineToPoint(path, nil, 0, height)
    CGPathAddLineToPoint(path, nil, 30, 0)

    mask.path = path

    // CGPathRelease(path); - not needed

    self.layer.mask = mask

    var shape = CAShapeLayer()
    shape.frame = self.bounds
    shape.path = path
    shape.lineWidth = 3.0
    shape.strokeColor = UIColor.whiteColor().CGColor
    shape.fillColor = UIColor.clearColor().CGColor

    self.layer.insertSublayer(shape, atIndex: 0)

Solution 5

I have tried a PNG triangle but it detects the collision as the border of the image not the start of the triangle.

There is nothing you can do about that if you're going to use simple-minded collisions (e.g. the built-in UIKit Dynamics - it does only rectangular view collisions). If you want advanced shape collisions, either you must implement them yourself or you must use Sprites.

and for the user to move it

That is much easier to deal with: simply override hitTest for this view and return nil if the place the user touches is outside the borders of the triangle image.

Share:
41,575
Zog
Author by

Zog

I like to rearrange 1s and 0s for fun..

Updated on July 09, 2022

Comments

  • Zog
    Zog almost 2 years

    So I'm making a game in which I am dropping objects which have to be destroyed by a spike(triangle) at the bottom of the screen by a user.

    I cannot work out how to make a UIView that is a triangle. However I have been able to make it work as a rectangle like this:

     let barrier = UIView(frame: CGRect(x:125, y: 650, width: 130, height:20))
     barrier.backgroundColor = UIColor.orangeColor()
     view.addSubview(barrier)
    

    And this has worked. But I cannot work out how to make a triangle. The reason I want it as a UIView is because im using collisions on it and for the user to move it. I have tried a PNG triangle but it detects the collision as the border of the image not the start of the triangle.

    I have tried this but it doesn't work...

     let square = UIView(frame: CGPathMoveToPoint(path, nil, 50, 0), CGPathAddLineToPoint(path, nil, 100, 50), CGPathAddLineToPoint(path, nil, 0, 100))
     square.backgroundColor = UIColor.purpleColor()
     view.addSubview(square)
    

    Any and all help will be appreciated,

    Thanks,

    Alex

  • matt
    matt almost 9 years
    For how to do hit testing for drawn shapes, see the discussion in my book: apeth.com/iOSBook/ch18.html#_hit_testing_for_drawings
  • Zog
    Zog almost 9 years
    Im getting Initializers may only be declared within a type on: override init(frame: CGRect) { super.init(frame: frame) } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
  • Taylor M
    Taylor M almost 9 years
    Did you create a class declaration? class TriangeView : UIView { }
  • He Yifei 何一非
    He Yifei 何一非 over 7 years
    I believe triange is a typo? :)
  • Taylor M
    Taylor M over 7 years
    @Arefly Thanks! Fixed.
  • Uday Naidu
    Uday Naidu almost 7 years
    how can I change the color of the triangle dynamically?
  • Taylor M
    Taylor M almost 7 years
    @UdayNaidu To change the color dynamically, add a property to TriangleView to hold the color and use that to set the fill color. Also check out "The View Drawing Cycle" in the documentation.
  • Abdul Waheed
    Abdul Waheed almost 7 years
    i wasnt trying to draw triangle. but that helped my to draw trapezoid :d
  • user924
    user924 over 6 years
    it would draw black background, we should add self.isOpaque = false to init to fix it
  • User18474728
    User18474728 almost 4 years
    If someone needs upsaide-triagleview from middle of the view, use this path. path.move(to: CGPoint(x: 0, y: heightWidth/2)) path.addLine(to: CGPoint(x:heightWidth/2, y: 0)) path.addLine(to: CGPoint(x:heightWidth, y:heightWidth/2)) path.addLine(to: CGPoint(x:0, y:heightWidth/2))