CAGradientLayer overflows on UIView when using in UITableViewCell - ios

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
}

Related

Adding Gradient View and UILabel Programmatically to a UIView Is Not Working

I have a Storyboard with three UIViews set up and connected to the storyboard's UIViewController.
#IBOutlet weak var availableView: UIView!
#IBOutlet weak var maybeAvailableView: UIView!
#IBOutlet weak var notAvailableView: UIView!
I have two functions to add a UILabel and CAGradientLayer to each of the UIViews. This one's for adding the UILabels.
func setUpLabels(view1: UIView, view2: UIView, view3: UIView) {
let availableLabel = UILabel()
let maybeAvailableLabel = UILabel()
let notAvailableLabel = UILabel()
availableLabel.text = "Available"
maybeAvailableLabel.text = "Maybe Available"
notAvailableLabel.text = "Not Available"
availableLabel.backgroundColor = .clear
maybeAvailableLabel.backgroundColor = .clear
notAvailableLabel.backgroundColor = .clear
availableLabel.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight(rawValue: 10))
maybeAvailableLabel.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight(rawValue: 10))
notAvailableLabel.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight(rawValue: 10))
availableLabel.textColor = .black
maybeAvailableLabel.textColor = .black
notAvailableLabel.textColor = .black
availableLabel.frame = view1.bounds
maybeAvailableLabel.frame = view2.bounds
notAvailableLabel.frame = view3.bounds
view1.addSubview(availableLabel)
view2.addSubview(maybeAvailableLabel)
view3.addSubview(notAvailableLabel)
}
This one's for adding the gradients.
func setGradientBackground(colorTop: UIColor, colorBottom: UIColor, view: UIView) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorBottom.cgColor, colorTop.cgColor]
gradientLayer.startPoint = CGPoint(x: 1, y: 1)
gradientLayer.endPoint = CGPoint(x: 0, y: 0)
gradientLayer.locations = [0, 1]
gradientLayer.frame = view.bounds
view.layer.insertSublayer(gradientLayer, at: 0)
}
This is what I call in viewDidLayoutSubviews
availableView.layer.masksToBounds = true
maybeAvailableView.layer.masksToBounds = true
notAvailableView.layer.masksToBounds = true
setGradientBackground(colorTop: darkBlue, colorBottom: .systemIndigo, view: notAvailableView)
setGradientBackground(colorTop: .systemRed, colorBottom: .systemOrange, view: maybeAvailableView)
setGradientBackground(colorTop: .systemBlue, colorBottom: .systemTeal, view: availableView)
setUpLabels(view1: availableView, view2: maybeAvailableView, view3: notAvailableView)
However, nothing appears in the viewController or in the debug hierarchy view. I've tried many things from other questions but nothing changes.
May be you have any issues with constraints. I've checked your code (I changed setGradientBackground(colorTop: .darkBlue, colorBottom: .systemIndigo, view: notAvailableView) to setGradientBackground(colorTop: .darkGray, colorBottom: .systemIndigo, view: notAvailableView) because I haven't this color and I see normal behavior:
Set gradient background to view
// - Parameters:
// - colors: gradient colors
// - opacity: opacity
// - direction: gradient direction
// - radius: radius
func setGradientBackground(_ colors: [UIColor], opacity: Float = 1, direction: GradientColorDirection = .vertical, radius: CGFloat = 25) {
if let sublayers = self.layer.sublayers {
for item in sublayers {
if item is CAGradientLayer {
item.removeFromSuperlayer()
}
}
}
let gradientLayer = CAGradientLayer()
gradientLayer.opacity = opacity
gradientLayer.colors = colors.map { $0.cgColor }
if case .horizontal = direction {
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
}
gradientLayer.bounds = self.bounds
gradientLayer.cornerRadius = radius
gradientLayer.anchorPoint = CGPoint.zero
self.layer.insertSublayer(gradientLayer, at: 0)
}
Gradient color direction
enum GradientColorDirection {
case vertical
case horizontal
}
Color for top to bottom masking
static let topToBottomMask: [UIColor] = {
return [UIColor.clear, UIColor.black.withAlphaComponent(0.40), UIColor.black.withAlphaComponent(0.43)]
}()
Set the gradient
self.view.setGradientBackground(topToBottomMask, radius: 0)
Hope it helps.

Creating a clear button with gradient border and gradient text

I'm trying to add a gradient to my UIButton Title and to the border of the button. I've gone through most of the solution on here which I cannot get working for the life of me, might be outdated, I'm not sure. So currently I extend the UIView in order to set the gradient of whatever. So how would I add another function for this feature?
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientlayer = CAGradientLayer()
gradientlayer.frame = bounds
gradientlayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientlayer.locations = [0, 1]
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientlayer, at: 0)
}
I have created a demo for you, you can do this with the help of CAGradientLayer see the following output and code for this.
Storyboard:
For gradient button text color and border put your UIButton inside UIView, then assign CAGradientLayer to UIview.
Note:- Don't forget to set the button as the views mask, See the following code.
import UIKit
class ViewController: UIViewController {
#IBOutlet var viewForButton: UIView!
#IBOutlet var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewForButton.bounds
// add the gradient layer to the views layer for rendering
viewForButton.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewForButton.mask = myButton
//Set corner Radius and border Width of button
myButton.layer.cornerRadius = myButton.frame.size.height / 2
myButton.layer.borderWidth = 5.0
}
}
Extension: You can also prefer this extension for the same.
extension UIView{
func gradientButton(_ buttonText:String, startColor:UIColor, endColor:UIColor) {
let button:UIButton = UIButton(frame: self.bounds)
button.setTitle(buttonText, for: .normal)
let gradient = CAGradientLayer()
gradient.colors = [startColor.cgColor, endColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
self.mask = button
button.layer.cornerRadius = button.frame.size.height / 2
button.layer.borderWidth = 5.0
}
}
How to use:
testView.gradientButton("Hello", startColor: .red, endColor: .blue)
You just need to add below UIView extension and call the function to get desire gradient button,
func covertToGradientButtonWith(title: String, radius: CGFloat, borderWidth: CGFloat, gradientStartColor: UIColor, gradientEndColor: UIColor) {
let button:UIButton = UIButton(frame: self.bounds)
button.setTitle(title, for: .normal)
let gradient = CAGradientLayer()
gradient.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
self.mask = button
button.layer.cornerRadius = radius
button.layer.borderWidth = borderWidth
}
Hope, this solution may help you.

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).

UIView / can't get corner radius to show when applying gradient

The following code creates a square UIView frame with a gradient layer inside a detail view controller. However, the square.layer.cornerRadius doesn't show. It remains square.
class Colors {
let colorTop = UIColor(red: 68.0/255.0, green: 107.0/255.0, blue: 207.0/255, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 68.0/255.0, green: 108.0/255.0, blue: 179.0/255, alpha: 1.0).cgColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}
class DetailViewController: UIViewController {
func viewWillAppear {
let colors = Colors() // is a class that creates the gradient
let square = UIView(frame: CGRect(x: 18, y: 109, width: 60, height: 60))
square.layer.cornerRadius = 10
let backgroundLayer = colors.gl
backgroundLayer.frame = square.frame
backgroundLayer.maskToBounds = true
view.layer.insertSublayer(backgroundLayer, at: 1)
}
}
You are giving cornerRadius to your square view but not adding it your main view instead you are creating backgroundLayer and adding it your main view.
BackgroundLayer is not rounded as the when your are assigning the square view's frame a rectangular(square in your case) is assigned to the backgroundLayer without any cornerRadius.
You should add your backgroundLayer to your square view and then add the square view to your main view. Like,
square.layer.insertSublayer(backgroundLayer, at: 1)
view.addSubview(square)
Also do,
square.clipsToBounds = true
This should resolve your issue.
I have added some additional properties to the original GradientView to add the desired functionality to it:
#IBDesignable
class GradientView: UIView {
#IBInspectable var startColor: UIColor = .black
#IBInspectable var endColor: UIColor = .white
#IBInspectable var startLocation: Double = 0.05
#IBInspectable var endLocation: Double = 0.95
#IBInspectable var horizontalMode: Bool = false
#IBInspectable var diagonalMode: Bool = false
// add border color, width and corner radius properties to your GradientView
#IBInspectable var cornerRadius: CGFloat = 0
#IBInspectable var borderColor: UIColor = .clear
#IBInspectable var borderWidth: CGFloat = 0
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override func layoutSubviews() {
super.layoutSubviews()
if horizontalMode {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 1, y: 0) : CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0.5)
} else {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 0, y: 0) : CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 1, y: 1) : CGPoint(x: 0.5, y: 1)
}
gradientLayer.locations = [startLocation as NSNumber, endLocation as NSNumber]
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
// add border and corner radius also to your layer
gradientLayer.cornerRadius = cornerRadius
gradientLayer.borderColor = borderColor.cgColor
gradientLayer.borderWidth = borderWidth
}
}
I know you did it programmatically but here's another tip, all you have to do if it's a storyboard UIView is just enable "Clip to Bounds" on the UIView. This always works for me when I add a gradient and set the cornerRadius programmatically.
Set your gradient's corner radius equal to the view's corner radius. (the view that you want to apply the gradient on)
gradient.cornerRadius = view.layer.cornerRadius
gradient.masksToBounds = true

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.

Resources