How to use CAGradientLayers on UITableViewCell - ios

I am running into a problem with using CAGradientLayers on my UITableViewCells. What I am attempting to do is simulate what actionsForRowAtIndexPath does, but I want to use a custom action view rather than just setting a background color.
So, my plan of action is in my UITableViewCell I have laid out a UIView (the action view) that is hidden beneath another UIView (main content view) that are both in the default contentView of UITableViewCell.
So in my 'cellForRowAt' tableView function, I have this code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
// Set Up Gradient
let actionGradient = CUSTOM_GRADIENTS.blueToGreen!
actionGradient.frame = cell.actionView.frame
// Add Gradient to ActionView
cell.actionView.insertSubLayer(actionGradient, at: 0)
return cell
}
And just for a reference my CUSTOM_GRADIENTS is a class that is initialized at in the ApplicationDelegate:
class safeGradiants {
var blueToGreen: CAGradientLayer!
init() {
self.blueToGreen = CAGradientLayer()
self.blueToGreen.colors = [UIColor.blue, UIColor.]
self.blueToGreen.startPoint = CGPoint(x: 0.0, y: 0.5)
self.blueToGreen.endPoint = CGPoint(x: 1.0, y: 0.5)
}
}
Where the error occurs is when my tableView is loaded, the only cell with the gradient is the LAST CELL in the table view. I can only presume this has something to do with the reuse of cells, but I can't for the life of me figure out how to fix it.
Any ideas on what I need to do to get this gradient showing on the actionView for all the cells?

After a little bit of messing around, I figured out my problem lied in my safeGradients class. Since I was using one global instance of that class, I was un able to reuse the same variable blueToGreen.
The solution to this is instead of creating a class and having one global instance of that class, is to create an extension of CAGradientLayer that hosts subclass functions. In other words, I changed this code:
class safeGradiants {
var blueToGreen: CAGradientLayer!
init() {
self.blueToGreen = CAGradientLayer()
self.blueToGreen.colors = [UIColor.blue, UIColor.]
self.blueToGreen.startPoint = CGPoint(x: 0.0, y: 0.5)
self.blueToGreen.endPoint = CGPoint(x: 1.0, y: 0.5)
}
}
into this code:
extension CAGradientLayer {
class var blueToGreen: CAGradientLayer {
let blueToGreen = CAGradientLayer()
blueToGreen.colors = [UIColor.blue, UIColor.green]
blueToGreen.startPoint = CGPoint(x: 0.0, y: 0.5)
blueToGreen.endPoint = CGPoint(x: 1.0, y: 0.5)
return blueToGreen
}
}

Related

Is it possible that UICollectionViewCell gradient background layer render issue happens in case of rotation?

So I have a custom UICollectionViewController, which contains simple rectangle cells (UICollectionViewCell). I would like to customize these with rounded corners and gradient background color. I could achieve this by setting the proper parameters for cells in the viewcontroller.
However, in case of rotation, my cells "collapse" just like there is something wrong with the rendering. Do you think I am missing something, or should I do this in a completely different way?
 Code
override func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = self.collectionView
.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier,
for: indexPath) as? AllCollectionViewCell else {
fatalError("The dequeued cell is not an instance of AllCollectionViewCell.")
}
// Configure the cell
cell.layer.cornerRadius = 15;
cell.layer.masksToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.colors = [UIColor.red.withAlphaComponent(0.3).cgColor,
UIColor.red.withAlphaComponent(0.7).cgColor]
gradientLayer.frame = cell.bounds
cell.backgroundView = UIView()
cell.backgroundView?.layer.addSublayer(gradientLayer)
return cell
}
The problem is not absolutely predictable, but it happens very often after rotation (going back to portrait after landscape view) and scrolling.
Expected
https://i.imgur.com/tGvauZG.png
Problem
https://i.imgur.com/DRPZjcD.png
i've tried it but i didn't get that UI bug. may you send me your code and let me check if you want.
but generally i for a better practice i think, you should move cell UI code on it's cell class AllCollectionViewCell
I tried to set the properties in the cell class itself (AllCollectionViewCell) as suggested by canister_exister and Mohamed Shaban, and it solved it partially:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.cornerRadius = 15;
self.layer.masksToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
self.backgroundView = UIView()
self.backgroundView?.layer.addSublayer(gradientLayer)
}
However, the frames of gradientLayer just didn't meet my expectations, so I searched a bit more further, and found out, that it should be placed into another function. So I moved the gradientLayer to a class property, then overriden this function:
override func layoutSubviews() { self.gradientLayer.frame = self.bounds }
Thank you for the helpful answers!

Add Facebook Shimmer on multiple UIViews

I am trying to add Facebook Shimmer on UICollectionViewCell which has multiple UIViews.
For one UIView, it's working fine with below code:
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Where backgroundView is the view in which I have all the subviews such as imageView, labelView and others.
While I am trying to add multiple views then first view is getting correct frame but other views' widths are becoming zero. I'm adding this code inside collectionView(_:cellForItemAt:).
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
let shimmeringView = FBShimmeringView(frame: labelView.frame)
shimmeringView.contentView = labelView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Can anyone tell me if it's the correct way to implement Facebook Shimmer for multiple UIViews or Where I am doing it wrong?
I believe there are many ways to implement FBShimmeringView, it's a matter of preferences. So in my case, I prefer the easiest way (according to me).
What I do in my tableViewCell that has of course multiple views such as imageView and labels, just like yours, is that I have multiple UIView gray color, placed on top of each views in my cell.
Then I only have ONE instance of FBShimmeringView added to my cell.
Here are some more details about what I practice for using FBShimmeringView.
*Take note that I use SnapKit to layout my views programmatically.
I have a property in my cell called isLoading like so, which determines if the gray colored views should be shown or now. If shown, of course turn on shimmering:
public var serviceIsLoading: Bool = false {
didSet {
_ = self.view_Placeholders.map { $0.isHidden = !self.serviceIsLoading }
self.view_Shimmering.isHidden = !self.serviceIsLoading
self.view_Shimmering.isShimmering = self.serviceIsLoading
}
}
Then I add a white view to my cell after adding all the subviews to the cell:
// Place the FBShimmeringView
// Try to add a dummy view
let dummyView = UIView()
dummyView.backgroundColor = .white
self.addSubview(dummyView)
dummyView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
Add the ShimerringView to the cell as well:
self.addSubview(self.view_Shimmering)
self.view_Shimmering.snp.makeConstraints { (make) in
make.height.width.equalToSuperview()
make.center.equalToSuperview()
}
Finally, make the dummyView as the contentView of the cell:
self.view_Shimmering.contentView = dummyView
My screen would look like this. Also remember to disable interaction in your tableView.
This looks cool to me when it shimmers, just one shimerring view.
Hope it helps!
Below extension is working fine.
extension UIView {
func startShimmeringViewAnimation() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
let gradientColorOne = UIColor(white: 0.90, alpha: 1.0).cgColor
let gradientColorTwo = UIColor(white: 0.95, alpha: 1.0).cgColor
gradientLayer.colors = [gradientColorOne, gradientColorTwo, gradientColorOne]
gradientLayer.locations = [0.0, 0.5, 1.0]
self.layer.addSublayer(gradientLayer)
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = [-1.0, -0.5, 0.0]
animation.toValue = [1.0, 1.5, 2.0]
animation.repeatCount = .infinity
animation.duration = 1.25
gradientLayer.add(animation, forKey: animation.keyPath)
}
}
In UITableViewCell class, we need to add the Shimmer for each views.
class UAShimmerCell: UITableViewCell {
#IBOutlet weak var thumbNailView: UIView!
#IBOutlet weak var label1View: UIView!
#IBOutlet weak var label2View: UIView!
override func awakeFromNib() {
super.awakeFromNib()
thumbNailView.startShimmeringViewAnimation()
label1View.startShimmeringViewAnimation()
label2View.startShimmeringViewAnimation()
}
}

Adding gradient on background image of UITableViewCell

I have a UITableView inside of a UIViewController. I'm attempting to apply a gradient over a background image in a UITableViewCell. I can get a gradient to draw, but it's drawing a clean line between the colors instead of a gradient.
In my cellForRowAt, I assign an object to my CustomTableViewCell, like so:
let cellIdentifier = "Cell"
// all the standard data source, delegate stuff for uitableviews
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CustomTableViewCell
let scheduleItem = scheduleData[indexPath.row]
cell.scheduleStruct = scheduleItem
return cell
}
Over in CustomTableViewCell, I've got this property declared:
public var scheduleStruct: FullScheduleStruct? {
didSet {
configureCell()
}
// emptyView to throw on top of a background image
var gradientView: UIView?
I'm also using Kingfisher to pull images off of the internet, which is what's going on with the ImageResource(downloadURL: url) part. The configureCell() method called when scheduleStruct is set like this:
private func configureCell() {
// get the background image
if let url = scheduleStruct?.show.image?.original {
let imageUrl = ImageResource(downloadURL: url)
backgroundImageView.contentMode = .scaleAspectFill
backgroundImageView.kf.setImage(with: imageUrl)
// configure the gradientView
gradientView = UIView(frame: backgroundImageView.frame)
let gradient = CAGradientLayer()
gradient.frame = gradientView!.frame
gradient.colors = [UIColor.clear.cgColor, UIColor.white.cgColor]
// gradient.locations = [0.0, 1.0]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 0, y: 1)
gradientView!.layer.insertSublayer(gradient, at: 0)
backgroundImageView.insertSubview(gradientView!, at: 0)
}
Any suggestions re: where my mistake lies are greatly appreciated. Thank you for reading.
At David Shaw's suggestion, I commented out this line in configureCell()
// backgroundImageView.kf.setImage(with: imageUrl)
and the gradient drew properly. I deduced I had a timing issue because I'm getting the image asynchronously.
I looked through the method signatures and saw there's one with a completion handler, so I used it instead and it worked:
backgroundImageView.kf.setImage(with: imageUrl, completionHandler: { (image, error, cacheType, imageUrl) in
// do stuff
})
...so I did this.

What is the correct place to add a CAGradientLayer in my custom table view cell?

I have a table with custom table view cells. each table view cell has a background image plus some text on top of the image. I would like to add a gradient above the image view so that the text above the image is more easily readable.
Where is the CORRECT place to add this gradient?
I am currently adding this in cellForRowAtIndexPath, i'm pasting the code below.. the array cellShown keeps track of whether this cell was shown or not.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("AirportPlaceListTableViewCell") as! AirportPlaceListTableViewCell
cell.titleLabel.text = tableData[indexPath.row] as? String
let imageName = tableImageData[indexPath.row] as? String
let image = UIImage(named: imageName!)
cell.placeImageView.image = TripnaryHelper.imageWithImage(image, scaledToSize: CGSizeMake(320, 320))
if cellShown[indexPath.row] == false
{
let gradient = CAGradientLayer.init()
gradient.frame = cell.bounds
let bottomColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
gradient.colors = [UIColor.clearColor().CGColor, bottomColor.CGColor]
gradient.startPoint = CGPoint(x: 0, y: 0.66)
gradient.endPoint = CGPoint(x: 0, y: 1.0)
cell.placeImageView.layer .addSublayer(gradient)
cellShown[indexPath.row] = true
}
return cell
}
what is happening here is that when the cells get reused, the gradient is getting added again. so my question is
- how do I check for that?
- or, is there another place to add this gradient for my custom table view cell class?
You should add the CAGradientLayer in your custom cell's (AirportPlaceListTableViewCell) overridden initialization method:
init(style style: UITableViewCellStyle, reuseIdentifier reuseIdentifier: String?)
However, if you have built your custom cell in a xib, then you'll want to add the CAGradientLayer after the xib has been loaded. In which case you can do it in AirportPlaceListTableViewCell's:
func awakeFromNib()

How to add mask on UIImageView in Cell - Swift

I've a tableView and I'm trying to add black mask on each image in the cell not on the whole cell just on the image. But I have two issues;
-It is masking the half cell from the left to the middle and each I scroll it is becoming darker. So please where would be my issue?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! PlacesTableViewCell
cell.backgroundImage.sd_setImageWithURL(NSURL(string: place.Image), placeholderImage: nil, options: .HighPriority)
}
class PlacesTableViewCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
maskArrangement()
}
func maskArrangement(){
var maskLayer = CALayer()
maskLayer.frame = CGRectMake(0, 0, backgroundImage.frame.size.width, backgroundImage.frame.size.height)
maskLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.5).CGColor
//backgroundImage.layer.mask = maskLayer
backgroundImage.layer.addSublayer(maskLayer)
}
}
Every time cell is dequeued you are adding new sublayer to your backgroundImage. In this way it is getting darker when you scroll (when you dequeued your cells). I think you can add to your PlacesTableViewCell class at awakeFromNib method if it has a .xib file.
Meanwhile your image's frame should be half of the cell. Use autolayout to fix it's frame (for instance, set width and height constraints) in your PLacesTableViewCell class.
In #Shripada 's way:
class PlacesTableViewCell: UITableViewCell {
var maskLayer : CALayer
override func layoutSubviews() {
super.layoutSubviews()
maskArrangement()
}
func maskArrangement(){
if maskLayer == nil {
maskLayer.frame = CGRectMake(0, 0, backgroundImage.frame.size.width, backgroundImage.frame.size.height)
maskLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.5).CGColor
//backgroundImage.layer.mask = maskLayer
backgroundImage.layer.addSublayer(maskLayer)
}
}
}
I haven't tried this code. But your solution can be like that.

Resources