-1

I'm trying to figure out how to mask a UIView to display a shape that represents a Square with half of a circle sitting ontop of it.

Basically, it would be like applying a border radius to only the top two corners to round them out and create an arc.

I'm using swift, and this is driving me crazy.

EDIT: CornerRadius does not do what I want. I feel like I'll have to use a CGShapeLayer but I'm not sure how to create the arc.

EDIT2: enter image description here

EDIT3:

Considering explaining what I want, providing pictures, and explaining that I didn't understand what I needed to do, or how I needed to do it isn't enough for some people, here's what I've tried:

Attempted setting the layer.cornerRadius property, while pushing the bottom of the view out past the screen to hide the bottom corners from appearing cut off, this type of masking was too circular and did not provide the proper arc results.

I've attempted using UIBezierPath setting the top two corners to use a cornerRadii of the views width / 2. This also did not yield proper results. Upon attempting to hardcode values into the cornerRadii, I noticed that regardless of the value, I could not seem to obtain the results that I had wanted.

What other options are there? Or am I just using the BezierPath incorrectly?

EDIT 4:

Here is my original code.

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    let bezierPath = UIBezierPath(roundedRect: navBarView.bounds, byRoundingCorners: [.TopLeft, .TopRight], cornerRadii: CGSizeMake(navBarView.bounds.width / 2, navBarView.bounds.width / 2)).CGPath;

    let shapeLayer = CAShapeLayer();
    shapeLayer.bounds = navBarView.bounds
    shapeLayer.path = bezierPath
    navBarView.layer.mask = shapeLayer

    updateUI() // <-- Label text, etc. 
}

Also, as stated before, had attempted doing navBarView.layer.cornerRadius = x

Peter Hornsby
  • 4,208
  • 1
  • 25
  • 44
Hobbyist
  • 15,888
  • 9
  • 46
  • 98
  • can you show your tried code and which ttype of output u need – Anbu.Karthik Jan 19 '16 at 13:38
  • see this one: http://stackoverflow.com/questions/22679886/how-to-make-a-uiview-with-optional-rounded-corners-and-border/22680538#22680538 – Kirit Modi Jan 19 '16 at 13:42
  • Added what I was trying to accomplish – Hobbyist Jan 19 '16 at 13:43
  • 1
    *I'm using swift, and this is driving me crazy.* Where is the code? – vikingosegundo Jan 19 '16 at 13:45
  • @vikingosegundo - The code, is the same as what is posted below. I've tried using cornerRadius and UIBezierPath and could not obtain proper results. – Hobbyist Jan 19 '16 at 13:51
  • so you should show us YOUR code and describe what result it delivered and why that isn't correct. You seem to be around long enough to know that you need to show your effort, tries and failures to get proper answers. – vikingosegundo Jan 19 '16 at 13:55
  • oh, and if your crop your images to not reveal what is on it, you probably should change them. I have no idea what your example should point out. – vikingosegundo Jan 19 '16 at 13:56
  • *Or am I just using the BezierPath incorrectly?* Probably, we just can answer this as long you don't show your code! – vikingosegundo Jan 19 '16 at 13:57
  • @vikingosegundo- Posted original code, although it's the same thing you see in the answers, basically, which is what I've already said. The image I posted wasn't cropped to hide anything. It was cropped to show the arc that I wanted. There's only ONE element on that image with an arc. The white background. – Hobbyist Jan 19 '16 at 14:02
  • For complex masks and if all the images have the same size the simplest solution is using a prerendered mask - a `UIImage`. – Sulthan Jan 19 '16 at 14:12
  • are u using auto layout? – vikingosegundo Jan 19 '16 at 14:17
  • @vikingosegundo - I believe so, that's what they call it when you use all the constraints and stuff, correct? – Hobbyist Jan 19 '16 at 14:28
  • try moving the code in to `viewDidLayoutSubviews()` – vikingosegundo Jan 19 '16 at 15:03

5 Answers5

5

Try this:

let path = UIBezierPath(roundedRect:self.view.bounds, byRoundingCorners:[.TopLeft, .TopRight], cornerRadii: CGSizeMake(20, 20))
    let maskLayer = CAShapeLayer()
    maskLayer.frame = self.view.bounds;
    maskLayer.path = path.CGPath
    self.view.layer.mask = maskLayer;

The UIBezierPath class lets you define a path consisting of straight and curved line segments and render that path in your custom views.

  1. Generate rounded corners of the path in UIBezierPath
  2. To generate a CAShapeLayer, it is set to one of the path
  3. layer to the view that you want to on one side only rounded corners 2 as the mask layer

Example:

let DynamicView=UIView(frame: CGRectMake(50, 200, 200, 300))
        DynamicView.backgroundColor=UIColor.redColor()
        let path = UIBezierPath(roundedRect:DynamicView.bounds, byRoundingCorners:[.TopLeft, .TopRight], cornerRadii: CGSizeMake(20, 20))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = DynamicView.bounds;
        maskLayer.path = path.CGPath
        DynamicView.layer.mask = maskLayer;


        self.view.addSubview(DynamicView)

Result:

Blockquote

Ramesh_T
  • 1,251
  • 8
  • 19
  • Hello Ramesh, Please add further details to your answer explaining how does this solve this issue for future audience who might not be able to understand the code. – NSNoob Jan 19 '16 at 13:42
  • This answer doesn't explain much of anything, and even changing the radii values around isn't giving me the desired results. It cuts into it a tiny bit regardless of numbers on a single side. I added an image of the arc I'm after. Perhaps I'm still doing it wrong, I attempted settings the raid to the bounds of the view / 2 as-well as a few hardcoded values – Hobbyist Jan 19 '16 at 13:44
  • In your code,you are using bounds. These may giving different result. check the updated the answer – Ramesh_T Jan 19 '16 at 14:08
  • @Ramesh_T - This is the result of the answer provided: http://i.imgur.com/Ncgvqvw.png It also only effects the top left side. -- Changing the hardcoded values do not adjust the shape of the mask either. – Hobbyist Jan 19 '16 at 14:10
  • I uploaded sample app here: http://efshare.com/?s=L8N8MC. It is working fine for me. – Ramesh_T Jan 19 '16 at 14:29
1

Is not that difficult, you need to add mask to the layer of the view. Pay attention that adding the mask will cut everything out of the mask itself.
The other thing you should pay attention is that if your view is resizing also you mask need to be adapted, since it doesn't update automatically the path.
Imagining that you are using SWIFT, you can subclass UIView:


override func layoutSubviews() {
        super.layoutSubviews()
        maskLayer.frame = layer.bounds
        maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.TopLeft, .TopRight], cornerRadii: CGSizeMake(bounds.size.widht/2, bounds.size.widht/2)).CGPath
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        layer.mask = maskLayer

    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        layer.mask = maskLayer
    }

maskLayer is a CAShapeLayer that you must create as a constant or as a lazy property.
The code is not tested.

Andrea
  • 26,120
  • 10
  • 85
  • 131
  • The issue with this answer, as-well as the one below is that I've already tried UIBezier path (as well as tried both of your solutions, which are ultimately the same). The result is no different than using the layer.cornerRadius property. – Hobbyist Jan 19 '16 at 13:48
  • Are you sure that you deleted the old corenrRadius settings ? to better understanding what you want could you post a little drawing? – Andrea Jan 19 '16 at 13:49
  • The drawing is an image in the main post, I'm sure I've removed the corner radius settings, – Hobbyist Jan 19 '16 at 13:50
1

Hi I had same issue I rounded corners after view is resized by dispatching the queue

    self.lblShortDescription.frame = CGRectMake(self.lblShortDescription.frame.origin.x, self.lblShortDescription.frame.origin.y, width, self.lblShortDescription.requiredHeight()) // here required height is the function to resize the label according to the content.
    dispatch_async(dispatch_get_main_queue(), {
                self.viewTopTitaleBack.round([UIRectCorner.TopLeft, UIRectCorner.TopRight], radius: 8.0) // here rounding is again a function I used for rounding the view by passing the parameters.
            });


extension UILabel{
    func requiredHeight() -> CGFloat{
        let label:UILabel = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.ByWordWrapping
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        return label.frame.height
    }
}

extension UIView {

    func round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.CGPath
        self.layer.mask = mask
        return mask
    }

}

Hope this helps.

1

Tested and tried Ramesh_T's answer, it works perfectly with the addition of .clipsToBounds made true. Works like a charm, especially if nested into an extension function such as:

(SWIFT 3)

extension UIView {
  func roundCorners(_ corner: UIRectCorner,_ radii: CGFloat) {
      let maskLayer = CAShapeLayer()
      maskLayer.frame = self.layer.bounds
      maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corner, cornerRadii: CGSize(width: radii, height: radii)).cgPath
    
      self.layer.mask = maskLayer
      layer.masksToBounds = true
  }
}

Called like this to round a specific views top left and right corners:

someView.roundCorners([.topRight,.topLeft], 60)

EDIT

However, looking at the method declaration for UIBezierPath(roundedRect..) : it seems the corner radii is maxed out to either half the view's height or width, whichever is smaller...

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
jlmurph
  • 1,050
  • 8
  • 17
0

Following this you can apply cornerRadius easily.

view.layer.cornerRadius = 8
view.layer.maskedCorners = [CACornerMask.topLeft, CACornerMask.topRight]
view.layer.masksToBounds = true

Extension which make it more visible:

extension CACornerMask {
    static var topLeft: CACornerMask {
        get {
            return CACornerMask.layerMinXMinYCorner
        }
    }

    static var topRight: CACornerMask {
        get {
            return CACornerMask.layerMaxXMinYCorner
        }
    }

    static var bottomLeft: CACornerMask {
        get {
            return CACornerMask.layerMinXMaxYCorner
        }
    }

    static var bottomRight: CACornerMask {
        get {
            return CACornerMask.layerMaxXMaxYCorner
        }
    }
}
alucabj
  • 11
  • 2