custom collectionViewCell's selectedBackgroundView with rounded corners - ios

I'm trying to create a custom selectedBackgroundView for my collectionView cell. I subclassed the UIView and this is my drawRect implementation:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context)
let bezierPath = UIBezierPath(roundedRect: rect, cornerRadius: 5.0)
bezierPath.lineWidth = 5
let color = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
color.setStroke()
UIColor(red:0.529, green:0.808, blue:0.922, alpha:1).setFill()
bezierPath.fill()
bezierPath.stroke()
CGContextRestoreGState(context)
}
The following image depicts what I get when the cell is selected.
As you can see, I'm getting this ugly black corners. I want the black corners to be fully transparent. How can I acchieve that? Thanks for your help.

You can set the view's layer's cornerRadius to 5.
self.view.layer.cornerRadius = 5;
And set the view's clipsToBounds property to true.
self.view.clipsToBounds = true;

Related

How to attain opacity in a shape drawn in a layer of UIView that is a subView of a UIImageView?

I have an image presented in a UIImageView which takes up the entire view of the UIViewController. I would like to draw a circle(fill: yellow) in the layer of a subview(UIView) of this UIImageView. My problem is the following, how do I make the opacity of this yellow circle such that it can see through to the background image (e.g opacity 0.2). I have tried the following without success. All I get is a solid yellow circle on top of the background image. I need this yellow circle to be in the layer of the subView as described, rather than in a layer of the UIImageView.
let dotSize: CGFloat = 30
var fourthDotView: UIView?
#IBOutlet weak var imgPreViewOutlet: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
fourthDotView = UIView.init()
fourthDotView?.frame = CGRect.init(x: fourthOrigin.x, y: fourthOrigin.y, width: dotSize, height: dotSize)
drawFourthDot()
imgPreViewOutlet.addSubview(fourthDotView!)
}
// DRAW YELLOW CIRCLE
func drawFourthDot() -> Void {
let layer = CAShapeLayer.init()
layer.path = UIBezierPath.init(roundedRect: fourthDotView!.bounds, cornerRadius: 50).cgPath
layer.fillColor = UIColor.init(red: 255, green: 255, blue: 0, alpha: 0.2).cgColor
fourthDotView?.layer.addSublayer(layer)
}
For setting the opacity, you should use the opacity property.
layer.opacity = 0.20

Round borders aren't clipping/masking perfectly

I make a label in Interface Builder, with constraints for fixed height and fixed width:
I subclass it to give it a white round border:
class CircularLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = frame.size.height / 2
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 5
layer.masksToBounds = true
clipsToBounds = true
}
}
But the clipping/masking isn't good at runtime:
I was expecting a perfect white border, without orange pixels.
iPhone 8 (Simulator and real device), iOS 11.2, Xcode 9.2, Swift 3.2
MCVE at https://github.com/Coeur/stackoverflow48658502
You should use UIBezierPath to round corners and draw border line with same path.
I my case i created CAShapeLayer with all adjustments and added it as sublayer to view.
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: *get your view bounds*, cornerRadius: *needed radius*).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = *border width*
borderLayer.strokeColor = *cgColor of your border*
//apply shape layer as mask to your view, it will cut your view by the corners
*yourViewInstance*.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
*your view instance*.layer.addSublayer(borderLayer)
In your case may be the problem with dynamic label's text: if text will be e.g. 900000 it will be drew under border. To solve this you could place you UILAbel inside another view (which will contain shape and border adjustments) and layout it.
Example:
Structure and constraints
What i got: container BG - orange, border - white, superview's BG - red
Controller's viewDidLoad method code:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
self.containerView.backgroundColor = UIColor.orange
self.label.backgroundColor = UIColor.clear
self.label.textAlignment = .center
self.label.adjustsFontSizeToFitWidth = true
self.label.text = "9000000"
//Create Border and shape and apply it to container view
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: containerView.bounds, cornerRadius: containerView.bounds.width / 2).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = 20
borderLayer.strokeColor = UIColor.white.cgColor
//apply shape layer as mask to your view, it will cut your view by the corners
self.containerView.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
self.containerView.layer.addSublayer(borderLayer)
}
Mystery solved.
Nice solution
Add a 1 pixel stroke and masksToBounds will do the job for clipping the edges correctly:
override func draw(_ rect: CGRect) {
super.draw(rect)
// workaround incomplete borders: https://stackoverflow.com/a/48663935/1033581
UIColor(cgColor: layer.borderColor!).setStroke()
let path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius)
path.lineWidth = 1
path.stroke()
}
Explanations
Actually, from my tests, setting layer.borderWidth = 5 is equivalent to formula:
let borderWidth: CGFloat = 5
UIColor(cgColor: layer.borderColor!).setStroke()
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: borderWidth / 2, dy: borderWidth / 2),
cornerRadius: layer.cornerRadius - borderWidth / 2)
path.lineWidth = borderWidth
path.stroke()
But on the other hand layer.cornerRadius = frame.size.height / 2 + layer.masksToBounds = true is going to clip with a different unknown method that has a different aliasing formula on the edges. Because the clipping and the drawing don't have the same aliasing, there are some pixels displaying the background color instead of the border color.
Another solution is to forget about imperfect borderWidth altogether and use two views inside each other:
extension UIView {
func roundBounds() {
layer.cornerRadius = frame.size.height / 2
clipsToBounds = true
}
}
class RoundLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}
class RoundView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}

How to draw a rectangle inside a tableviewcell

Probably it's a weird question, but I gave up. Situation: I have a TableView with a prototype cell. Inside the cell (I have a custom class) I want to have a background, but not the whole cell, and a label on it. The size of the background would be changed when I know the length of the text. The background is a rectangle, with a gradient fill, corner radius, gradient fill. How should I draw this rect? Should I do it with UIKit or with CoreGraphics? My first thought was to import the image but because i have to make it bigger if there is a long text, I decided to make it programmaticaly. Thanks in advance.
Try this solution:
// Add a label with sizeToFit
let label = UILabel()
label.text = "Add some text that you want"
label.sizeToFit()
// Add a rectangle view
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: label.frame.size.width, height: 40))
// Add gradient
let gradientLayer = CAGradientLayer()
gradientLayer.frame = rectangle.bounds
let color1 = UIColor.yellowColor().CGColor as CGColorRef
let color2 = UIColor(red: 1.0, green: 0, blue: 0, alpha: 1.0).CGColor as CGColorRef
let color3 = UIColor.clearColor().CGColor as CGColorRef
let color4 = UIColor(white: 0.0, alpha: 0.7).CGColor as CGColorRef
gradientLayer.colors = [color1, color2, color3, color4]
gradientLayer.locations = [0.0, 0.25, 0.75, 1.0]
rectangle.layer.addSublayer(gradientLayer)
// Add corner radius
gradientLayer.cornerRadius = 10
// Add the label to your rectangle
rectangle.addSubview(label)
// Add the rectangle to your cell
cell.addSubview(rectangle)

UITableViewCell drawRect bottom border disappears on insertRow

I have a custom UITableViewCell where I use drawRect to paint a simple bottom border:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMaxY(rect))
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMaxY(rect))
CGContextSetStrokeColorWithColor(context, UIColor(netHex: 0xEFEEF4).CGColor)
CGContextSetLineWidth(context, 2)
CGContextStrokePath(context)
}
This works perfectly. However when I insert a row with animation the borders of ALL cells disappear and appear again when insert animation finishes:
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: cells.count - 1, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
Any idea how to avoid this?
Well You can try this code in tablecell's awakeFromNib if you only want to use CALayers assuming that your TableView is covering the full width of the device.
override func awakeFromNib() {
super.awakeFromNib()
let borderLayer = CALayer()
let lineHeight:CGFloat = 2
borderLayer.frame = CGRect(x: 0, y: self.frame.height - lineHeight , width: UIScreen.mainScreen().bounds.width, height: lineHeight)
borderLayer.backgroundColor = UIColor.redColor().CGColor
self.layer.addSublayer(borderLayer)
}
and you will get the output as:
Also make your tableview separator color to clear
self.tableView.separatorColor = UIColor.clearColor()
So that it doesn't gets overlapped with your layer.
What I can observe that now no borders of cells disappears ever whenever a new row is inserted.
Alternatively
We can just use a UIImageView in the cell storyboard and provide a color with the following constraints.
Adding UIImageView
Adding Constraints to UIImageView
And we are done!
There is one more alternate solution to achieve this using Bezier Paths
override func awakeFromNib() {
super.awakeFromNib()
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.moveToPoint(CGPointMake(0, self.frame.height))
linePath.addLineToPoint(CGPointMake(UIScreen.mainScreen().bounds.width, self.frame.height))
line.lineWidth = 3.0
line.path = linePath.CGPath
line.strokeColor = UIColor.redColor().CGColor
self.layer.addSublayer(line)
}
This also yields the same output.
EDIT:
If we are not using storyboards or nibs for the cell and creating the cell programatically, then we can do a workaround like this:
Create a property in your CustomTableViewCell class
var borderLayer:CALayer!
Now there is a method called layoutSubviews in CustomTableViewCell class
override func layoutSubviews() {
if(borderLayer != nil) {
borderLayer.removeFromSuperlayer()
}
borderLayer = CALayer()
let lineHeight:CGFloat = 2
borderLayer.frame = CGRect(x: 0, y: self.frame.height - lineHeight , width: UIScreen.mainScreen().bounds.width, height: lineHeight)
borderLayer.backgroundColor = UIColor.redColor().CGColor
self.layer.addSublayer(borderLayer)
}
Try this out.
It's not direct answer, just my suggestion how to achieve equal visual result without drawing.
See example code and result image
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let layer = CALayer()
let lineHeight:CGFloat = 2
layer.frame = CGRect(x: 0, y: button.bounds.height - lineHeight , width: button.bounds.width, height: lineHeight)
layer.backgroundColor = UIColor(colorLiteralRed: 0xEF/255.0, green: 0xEE/255.0, blue: 0xF4/255.0, alpha: 1).CGColor
button.layer.addSublayer(layer)
}
Button and background view colors configured in IB.

CAGradientLayer of UIView in TableView not resizing to Autolayout

I have created a UIView in IB with tag: 6. I use it in tableView.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
// create background view
var backgroundView = cell.contentView.viewWithTag(6) as UIView!
backgroundView.layer.cornerRadius = 10
backgroundView.layer.masksToBounds = true
// create gradient layer
let gradient : CAGradientLayer = CAGradientLayer()
// create color array
let arrayColors: [AnyObject] = [
UIColor (red: 33/255, green: 33/255, blue: 39/255, alpha: 1).CGColor,
UIColor (red: 24/255, green: 24/255, blue: 28/255, alpha: 1).CGColor]
// set gradient frame bounds to match view bounds
gradient.frame = backgroundView.bounds
// set gradient's color array
gradient.colors = arrayColors
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.locations = [0.1, 0.9]
gradient.endPoint = CGPoint(x: 1, y: 1)
// replace base layer with gradient layer
backgroundView.layer.insertSublayer(gradient, atIndex: 0)
The problem is, while the backgroundView resize to Autolayout, the gradient layer does not, as I would expect per:
gradient.frame = backgroundView.bounds
I could not find any answers applicable to UIViews in a TableView cell.
Question: What is the correct code to force a layer applied to a UIView in TableView to resize with autolayout?
I think you can try to subclass UITableViewCell then make CAGradientLayer as its default layer.
Then its size should follow its equivalent view although I do not test it.
This is the answer you are looking for: https://stackoverflow.com/a/4111917/2831015
Subclass UIView and use it as your "backgroundView". By overriding the layer class to a CAGradientLayer then you don't need to add the gradient as a sublayer, and such your gradient will autoResize based on the parent.

Resources