UITableViewCell drawRect bottom border disappears on insertRow - ios

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.

Related

Adding a dashed bottom border to table view cell overlaps with text randomly in iOS

I am using the below code to add a dashed custom bottom border to tableview cells. It is now overlapping with content randomly. Sometimes, the border is not getting displayed.
class AppTableCell: UITableViewCell {
var shapeLayer: CAShapeLayer?
var isBorderAdded = false
func isBottomBorderAdded() -> Bool {
return isBorderAdded
}
func getBottomBorderShapeLayer() -> CAShapeLayer {
return self.shapeLayer!
}
func setBottomBorderShapedLayer(_ layer: CAShapeLayer) {
self.shapeLayer = layer
}
}
The extend tableview cell from the above class and calls the below function in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method.
func addDashedBottomBorder(to cell: AppTableCell) {
let color = UIColor.init(red: 191/255, green: 191/255, blue: 191/255, alpha: 1.0).cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = cell.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: 0)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2.0
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPhase = 3.0
shapeLayer.lineDashPattern = [9,6]
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 0), cornerRadius: 0).cgPath
if (cell.isBorderAdded) {
cell.shapeLayer!.removeFromSuperlayer()
}
cell.shapeLayer = shapeLayer
cell.isBorderAdded = true
cell.layer.addSublayer(shapeLayer)
}
How to display dashed bottom border at the end of each cell properly?
Your are adding a sublayer in your cell's layer with a fixed position:
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height)
[...]
cell.layer.addSublayer(shapeLayer)
So it doesn't stick to your cell's bottom side.
You could update this layer's position each time the cell's height change, but my suggestion from a performance and simplicity point of view would be use autolayout.
Add a subview at the bottom of your cell, add the constraints to make it stick, and add only once (in awakeFromNib for example if it's designed in IB) the dashedLayer inside.

Auto Resizing CAShapeLayer while Animating UIView

Apologies, if this question has already been answered elsewhere. I tried searching in multiple places but could not find a good solution. I am a beginner to Swift development.
As per the code below, I am creating a SubView, adding an oval ShapeLayer to it and then animating the SubView by moving its center and increasing its size.
The SubView is animating correctly, however the ShapeLayer inside the SubView is not changing size. I would like the Red Oval to increase in size, similar to the SubView. I would really appreciate it if could let me know what I am missing.
class playGroundView: UIView {
override func draw(_ rect: CGRect) {
// Add a blue rectangle as subview
let startFrame = CGRect(x: self.frame.midX, y: self.frame.midY, width: 10, height: 20)
self.addSubview(UIView(frame: startFrame))
self.subviews[0].backgroundColor = UIColor.blue
// Create red oval shape that is bounded by blue rectangle
// Add red oval shape as sub-layer to blue rectangle view
let subView = self.subviews[0]
let ovalSymbol = UIBezierPath(ovalIn: subView.bounds)
let shapeLayer = CAShapeLayer()
shapeLayer.path = ovalSymbol.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
subView.layer.addSublayer(shapeLayer)
// Animate movement and increase in size of blue rectangle view
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 3, delay: 0, options: [], animations: {
let endFrame = CGRect(x:self.frame.midX - 50, y:self.frame.midY - 50, width: 20, height: 40)
self.subviews[0].frame = endFrame
self.setNeedsLayout()
self.layoutIfNeeded()
})
}
}
Image of Incorrect Output
Ok, after spending more time researching and getting a better understanding of what goes inside a ViewController and UIView class, entering the following in those classes works for me:
Inside class ViewController: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
let startFrame = CGRect(x: playGround.frame.midX, y: playGround.frame.midY, width: 30, height: 60)
cardSubView = cardView(frame: startFrame)
playGround.addSubview(cardSubView!)
let endFrame = CGRect(x: playGround.frame.midX - 100, y: playGround.frame.midY - 100, width: 60, height: 120)
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 10, delay: 0, options: [], animations: {
self.cardSubView?.frame = endFrame
self.cardSubView?.layoutIfNeeded()
})
}
Inside a UIView class:
class cardView: UIView {
override func draw(_ rect: CGRect) {
let backGroundPath = UIBezierPath(rect: rect)
UIColor.blue.setFill()
backGroundPath.fill()
let ovalSymbol = UIBezierPath(ovalIn: rect)
UIColor.red.setFill()
ovalSymbol.fill()
}
}
This still results in a weird frame shadowing towards the end of the animation in my iPhone simulator, however when running on device there is no issue.

CAGradientLayer overflows on UIView when using in UITableViewCell

I've add CAGradientLayer to a UIView that've already inside in UITableViewCell. This UIView has 20 trailing and leading space from the UITableViewCell contentView. But that UIView's overflow when it running.
var gradientLayer: CAGradientLayer!
#IBOutlet weak var view: UIView!
func createGradientLayer() {
gradientLayer = CAGradientLayer.init(layer: view.layer)
gradientLayer.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
// gradientLayer.frame = CGRect(x: 0, y: 0, width: self.contentView.frame.size.width, height: )
let colory: UIColor = UIColor(red: 0, green: 189/255, blue: 237/255, alpha: 1.0)
gradientLayer.colors = [UIColor.ceruleanBlue.cgColor, colory.cgColor]
gradientLayer.startPoint = CGPoint.init(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x:1.0,y: 0.5)
// self.view.layer.mask = gradientLayer
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
Then in the cellForRow method, I used like this:
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "statisticsCell") as! StatisticsCommentCell
// cell.frame.size.width = UIScreen.main.bounds.size.width
cell.view.translatesAutoresizingMaskIntoConstraints = false
return cell
}
If I changed gradientLayer.frame like below:
gradientLayer.frame.size.width = self.view.frame.width
the gradientView is not overflow but there seems just ceruleanBlue color.
I do not any idea how can I solve this problem for my UIView.
Any idea? Thanks in advance.
Its a screenshot from the simulator that have overflow problem:
Hint: Normally the UIView has already trailing and leading from the UITableViewCell's contentView.
Another problem is when I add maskToBounds the view seems like below
All constraints for UIView
Set your gradientLayer as optional, instead of var gradientLayer: CAGradientLayer! use var gradientLayer: CAGradientLayer? and add this code in your cellLayoutSubViews method
Use this code
override func layoutSubviews() {
super.layoutSubviews()
if(self.gradientLayer == nil){
self.gradientLayer = CAGradientLayer(layer: self.view.layer)
//your gradient configuration
self.layer.addSublayer(self.gradientLayer!)
}
self.gradientLayer?.drawsAsynchronously = true
self.gradientLayer?.frame = self.view.bounds
}

How to add gradient layer which fills the view frame exactly

In my app i'm adding the gradient layer to UIView and UIToolBar but it doesn't fill the views exactly
let gradient:CAGradientLayer = CAGradientLayer()
gradient.frame = self.vw_gradientForToolBar.bounds
gradient.colors = [hexStringToUIColor(hex: "#5d8f32").cgColor,hexStringToUIColor(hex: "#04667f").cgColor]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 0)
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0.0)
let img : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.toolBar.setBackgroundImage(img, forToolbarPosition: .any, barMetrics: .default)
vw_gradientForToolBar.layer.addSublayer(gradient)
View Hirarchy
enter image description here
It's a little tough to tell exactly what you have going on, based on the images you posted, however... This may simplify things for you.
First, keep in mind that Layers do not auto-scale, so when your tool bar changes size (different devices, device rotation, etc), you want your gradient layer to also resize. Best way to do that is to use a UIView subclass and override layoutSubviews().
So, add this class to your code:
class GradientView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
let gradientLayer = layer as! CAGradientLayer
gradient.colors = [hexStringToUIColor(hex: "#5d8f32").cgColor,hexStringToUIColor(hex: "#04667f").cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
}
}
Then in your controller's viewDidLoad() function:
override func viewDidLoad() {
super.viewDidLoad()
let vwGrad = GradientView()
vwGrad.frame = toolBar.frame
vwGrad.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.toolBar.insertSubview(vwGrad, at: 0)
}
Note: you would no longer need your vw_gradientForToolBar (which, I'm assuming, is a UIView connected via #IBOutlet).

Swift textfields without border

I am new to swift. Your help will be really appreciated.
I have two textfields in my application. How would I create same UI as given in the pic below.
I want to create textfields with only one below border as given in the screenshot.
https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
#IBOutlet var textField: UITextField! {
didSet {
let border = CALayer()
let width: CGFloat = 1 // this manipulates the border's width
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width,
width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
}
Create a subclass of UITextField so you can reuse this component across multiple views without have to re implement the drawing code. Expose various properties via #IBDesignable and #IBInspectable and you can have control over color and thickness in the story board. Also - implement a "redraw" on by overriding layoutSubviews so the border will adjust if you are using auto layout and there is an orientation or perhaps constraint based animation. That all said - effectively your subclass could look like this:
import UIKit
class Field: UITextField {
private let border = CAShapeLayer()
#IBInspectable var color: UIColor = UIColor.blue {
didSet {
border.strokeColor = color.cgColor
}
}
#IBInspectable var thickness: CGFloat = 1.0 {
didSet {
border.lineWidth = thickness
}
}
override func draw(_ rect: CGRect) {
self.borderStyle = .none
let from = CGPoint(x: 0, y: rect.height)
let here = CGPoint(x: rect.width, y: rect.height)
let path = borderPath(start: from, end: here).cgPath
border.path = path
border.strokeColor = color.cgColor
border.lineWidth = thickness
border.fillColor = nil
layer.addSublayer(border)
}
override func layoutSubviews() {
super.layoutSubviews()
let from = CGPoint(x: 0, y: bounds.height)
let here = CGPoint(x: bounds.width, y: bounds.height)
border.path = borderPath(start: from, end: here).cgPath
}
private func borderPath(start: CGPoint, end: CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
return path
}
}
Then when you add a text field view to your story board - update the class in the Identity Inspector to use this subclass, Field - and then in the attributes inspector, you can set color and thickness.
Add border at Bottom in UITextField call below function:
func setTextFieldBorder(_ dimension: CGRect) -> CALayer {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: dimension.size.height - width, width: dimension.size.width, height: dimension.size.height)
border.borderWidth = width
return border
}
How to set UITextField border in textField below sample code for that:
txtDemo.layer.addSublayer(setTextFieldBorder(txtDemo.frame))
txtDemo.layer.masksToBounds = true
Where txtDemo is IBOutlet of UITextField.

Resources