1

I have a client who requested the following in his design:

What is the easiest way to create the yellow container, with the slanted edge on the bottom? Can I do it within the interface builder or do I have to do it programatically? I'm trying to do this in swift.

sushibrain
  • 2,712
  • 5
  • 33
  • 62
  • Possible duplicate of [Create a UIView with only one diagonal side](https://stackoverflow.com/questions/26339943/create-a-uiview-with-only-one-diagonal-side) – DonMag Nov 14 '17 at 18:43
  • I would use `UINavigationController` and make a generic class derived from `UIViewController` like `MyCustomizedUIViewController` and inherit all my classes from it. This will have a custom View added on top with proper *programmatic constraints and button actions* of this *custom View* will use `UINavigationController` actions like `show` , to navigate. – ahmed Nov 14 '17 at 19:10

2 Answers2

4

Can I do it within the interface builder or do I have to do it programmatically?

You'll have to do this programmatically: Define the UIBezierPath, using it as the path of a CAShapeLayer and then mask the view's layer using this shape layer.

@IBDesignable
class SlantedView: UIView {

    @IBInspectable var slantHeight: CGFloat = 50 { didSet { updatePath() } }

    private let shapeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = 0
        shapeLayer.fillColor = UIColor.white.cgColor    // with masks, the color of the shape layer doesn’t matter; it only uses the alpha channel; the color of the view is dictate by its background color
        return shapeLayer
    }()

    override func layoutSubviews() {
        super.layoutSubviews()
        updatePath()
    }

    private func updatePath() {
        let path = UIBezierPath()
        path.move(to: bounds.origin)
        path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
        path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
        path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY - slantHeight))
        path.close()
        shapeLayer.path = path.cgPath
        layer.mask = shapeLayer
    }
}

But by making this an @IBDesignable view, you can then render it in IB so you can at least design the UI with a greater sense of what the final product will look like:

enter image description here

FYI, if you do make this designable, they recommend that you create a separate framework target for your designables (so that if you go to IB while working on your main project, the ability to render the designable views isn't impacted by the fact that your main project may not be in a stable, compilable state).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

Maybe make a custom view that you can drop down in interface builder? This version has a property you can set in IB that determines the slant fraction so you can size it the way you want:

@IBDesignable class SlantedView: UIView {

    @IBInspectable var slant: Double = 0.8

    override func prepareForInterfaceBuilder() {
        // Called when initialized in IB
        setup()
    }

    override func awakeFromNib() {
        // Called when initialized in actual app
        setup()
    }

    func setup() {
        layer.backgroundColor = UIColor.yellow.cgColor

        recalculateMask()
    }

    func recalculateMask() {
        let shapeLayer = CAShapeLayer()

        let maskPath = CGMutablePath()
        maskPath.move(to: CGPoint(x: 0, y: 0))
        maskPath.addLine(to: CGPoint(x: bounds.width, y: 0))
        maskPath.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        maskPath.addLine(to: CGPoint(x: 0, y: bounds.height * CGFloat(slant)))

        shapeLayer.path = maskPath
        layer.mask = shapeLayer
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        recalculateMask()
    }
}
whistler
  • 540
  • 4
  • 14