CAGradientLayer of UIView in TableView not resizing to Autolayout - ios

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.

Related

CAGradientLayer not applying to the full view in programmatically created UI

I'm working on an iOS project that's creating all its UIs programmatically. Not storyboards.
I want to add a gradient to the background of a UIViewController's UIView.
I created the following extension method.
extension UIView {
func addGradient(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.bounds = bounds
gradientLayer.colors = colors.map { $0.cgColor }
layer.insertSublayer(gradientLayer, at: 0)
}
}
And I added the gradient in the viewDidLoad of the ViewController like so.
let topColor = UIColor(red: 0.28, green: 0.521, blue: 0.696, alpha: 1)
let bottomColor = UIColor(red: 0.575, green: 0.615, blue: 0.692, alpha: 1)
view.addGradient(colors: [topColor, bottomColor])
But the gradient is not being applied to the full width and height of the screen.
I printed out the bounds of the view and it shows these values.
(0.0, 0.0, 375.0, 812.0)
So I'm not sure why the gradient is still not covering the full view.
In addGradient(colors:) method set the gradientLayer's frame instead of bounds, i.e.
func addGradient(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds //here.....
//rest of the code...
}
Also, as suggested move your code to viewDidLayoutSubviews() method,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topColor = UIColor(red: 0.28, green: 0.521, blue: 0.696, alpha: 1)
let bottomColor = UIColor(red: 0.575, green: 0.615, blue: 0.692, alpha: 1)
view.addGradient(colors: [topColor, bottomColor])
}
This is because, the gradient's frame must change everytime there are any changes in the view's layout. Example, in portrait and landscape orientations.
When we are adding view bounds to Gradient layer frame that time we have to update frame in viewDidLayoutSubviews. It works fine for me
Following worked for me
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.gradient.frame = containerView.bounds
}
containerView is the view where i am setting gradient layer.

How to have a Gradient Layer's size placed behind a UITextField, match the UITextField size, after re-sizing the layout?

What you will see in the first image, if you scroll down a little bit, is the initial screen of a fin app. Basically, every section you see on the screen is placed within a vertical Stack View. Then every Label and Cell Is placed in a horizontal Stack View and so on, so the app auto-resizes for any screen size. I was using Story Board to create the elements.
The last section has two blue UITextFields that have a gradient layer behind it. I created an extension UITextField Class in a separate file that has the gradient function constructor in it and then another Class that placed the gradient behind any UITextField that have that class attached like this:
extension UITextField {
func gradientBackground(firstColor: UIColor, secondColor: UIColor){
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
layer.addSublayer(gradientLayer)
}
}
class gradientToTextField: UITextField {
var once = true
override func layoutSubviews() {
super.layoutSubviews()
if once {
self.gradientBackground(firstColor: UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1), secondColor: UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1))
once = false
}
}
}
Now if you take a look at the second image on the right, the last section expands on tapping the Plus sign button. As a result, the third section(starting with Total) is hidden and a new stack view appears below within the same section. Everything resizes perfectly except the gradient layer. I have set a red background behind it which is the UITextField's Background, so the problem is highlighted. It appears that the Gradient Layer is shorter in height than the UITextField, which is its parent element.
Here's the full project on GitHub: https://github.com/silviuisidor/layerResizeProblem
How can I fix this?
tl:dr — Instead of gradient layer that is a sublayer of the text field, you use a gradient view with the text field as its subview.
More details
Instead of a gradient layer, use a gradient view. The view exists purely to host a gradient layer as its underlying layer; you arrange this by subclassing UIView and implementing layerClass to return CAGradientLayer.self.
class MyGradientView : UIView {
override class var layerClass : AnyClass { return CAGradientLayer.self }
private func config() {
let gradientLayer = self.layer as! CAGradientLayer
let firstColor = UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1)
let secondColor = UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1)
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
}
override init(frame: CGRect) {
super.init(frame:frame)
self.config()
}
required init?(coder: NSCoder) {
super.init(coder:coder)
self.config()
}
}
Now make the text field the subview of our gradient view. Pin it using autolayout to the center of the gradient view. The gradient view, with its text field subview, is what goes into the interface. When the animation takes place, it is the gradient view that is resized! And when that happens, the text field is automatically repositioned along with it.
Resizing the layer frame in layoutSubviews should solve your problem
extension UITextField {
func gradientBackground(firstColor: UIColor, secondColor: UIColor) -> CAGradientLayer{
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
layer.addSublayer(gradientLayer)
return gradientLayer
}
}
class gradientToTextField: UITextField {
var coloredLayer : CAGradientLayer! = nil
var once = true
override func layoutSubviews() {
super.layoutSubviews()
if once {
coloredLayer = self.gradientBackground(firstColor: UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1), secondColor: UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1))
once = false
}
coloredLayer.frame = self.bounds
}
}

custom collectionViewCell's selectedBackgroundView with rounded corners

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;

iOS fade to black at top of UIImageView

I have a UITableView where my cell backgroundView is a UIImageView. I would like top of each image to fade to black so I can overlay some white text.
I have looked at some answers like this, but none seem to be working.
In tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) I have tried:
var imageView = UIImageView(image: image)
var gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = imageView.frame
gradient.colors = [UIColor.blackColor(), UIColor.clearColor()]
gradient.locations = [0.0, 0.1]
imageView.layer.insertSublayer(gradient, atIndex: 0)
cell!.backgroundView = imageView
But I see no gradient and no difference from when I remove the gradient code. Is my gradient sitting under the image?
If I replace the line imageView.layer.insertSublayer(gradient, atIndex: 0) with imageView.layer.mask = gradient then my cells are just blank and white (no image anymore).
In your gradient.colors you need to have array of CGColor, so:
gradient.colors = [UIColor.blackColor().CGColor, UIColor.clearColor().CGColor]
I forget how the layers array is ordered. However, you should just add your gradient layer on top of your image view's layer using addSubLayer, not insertSublayer:atIndex: You want the gradient layer on top of the other layer so that it's non-opaque parts cover the image view.
For Swift 3.0, some slight tweaks required:
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = imageView.frame
gradient.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
gradient.locations = [0.0, 0.1]
imageView.layer.insertSublayer(gradient, at: 0)

CALayer not resizing with Autolayout

I have created a progress bar to be used in a tableView by creating a gradient layer. It works perfectly.
iPhone5:
In order to use the app on multiple devices, I have created the UIView in Storyboard, tagged it and added constraints.
However, when I use the app on an iPhone 6 the CALayer don't resize.
iPhone6:
I find this extremely stupid, but never mind. I have looked around and tried to understand how to solve this for months, but I have come up short. Does ANYONE know how to make CALayers resize with the UIView? Any help would be very much appreciated ! Thank you.
progressBar = cell.contentView.viewWithTag(3) as UIView!
progressBar.layer.cornerRadius = 4
progressBar.layer.masksToBounds = true
// create gradient layer
let gradient : CAGradientLayer = CAGradientLayer()
// create color array
let arrayColors: [AnyObject] = [
UIColor (red: 255/255, green: 138/255, blue: 1/255, alpha: 1).CGColor,
UIColor (red: 110/255, green: 110/255, blue: 118/255, alpha: 1).CGColor]
// set gradient frame bounds to match progressBar bounds
gradient.frame = progressBar.bounds
// set gradient's color array
gradient.colors = arrayColors
//Set progress(progressBar)
var percentageCompleted = 0.6
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [percentageCompleted, percentageCompleted]
gradient.endPoint = CGPoint(x: 1, y: 0.5)
// replace base layer with gradient layer
progressBar.layer.insertSublayer(gradient, atIndex: 0)
The default layer of a UIView does resize with its view, but sublayers don't (as you found out). One way to make this work is to create a custom view class, move the code you have in your question to it, and override layoutSublayersOfLayer where you can set the gradient layer to be the same size as the view. Because this code is now in a custom class, I also created a property percentageCompleted (instead of a local variable), and added a willSet clause so the bar's appearance is updated any time you change the percentageCompleted property.
class RDProgressView: UIView {
private let gradient : CAGradientLayer = CAGradientLayer()
var percentageCompleted: Double = 0.0 {
willSet{
gradient.locations = [newValue, newValue]
}
}
override func awakeFromNib() {
self.layer.cornerRadius = 4
self.layer.masksToBounds = true
// create color array
let arrayColors: [AnyObject] = [
UIColor (red: 255/255, green: 138/255, blue: 1/255, alpha: 1).CGColor,
UIColor (red: 110/255, green: 110/255, blue: 118/255, alpha: 1).CGColor]
// set gradient's color array
gradient.colors = arrayColors
//Set progress(progressBar)
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [percentageCompleted, percentageCompleted]
gradient.endPoint = CGPoint(x: 1, y: 0.5)
self.layer.insertSublayer(gradient, atIndex: 0)
}
override func layoutSublayersOfLayer(layer: CALayer!) {
super.layoutSublayersOfLayer(layer)
gradient.frame = self.bounds
}
}
In IB, you would change the class of your view to RDProgressView (in my example), and in cellForRowAtIndexPath, you would only need to get a reference to the view, and set its percentageCompleted property.
progressBar = cell.contentView.viewWithTag(3) as RDProgressView!
progressBar.percentageCompleted = 0.2
As an alternative to the accepted answer, you could also change the views layer class to be CAGradientLayer. The views layer will always be resized according to layout changes. You can achieve that by subclassing UIView
class GradientView: UIView {
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
}
then set the colors
if let gradientLayer = gradientView.layer as? CAGradientLayer {
gradientLayer.colors = arrayColors
}
It's less code than adding and maintaining a sublayer, but might not suit all use cases.

Resources