Adding gradient on background image of UITableViewCell - ios

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.

Related

UITableViewCell Has Incorrect Shadow Size

I originally had an issue where the UITableViewCells would have multiple shadows because I was making a call to add a shadow to the contentView in dequeueReusableCell. After learning that this reused cells, I figured the proper way to initialize the shadows on the tableViewCells was in the initialization function.
In my custom table view cell class I call:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addShadow()
}
Now the shadows are the incorrect size and I need help figuring out the proper way to set the shadow size to match the content.
Here is my shadow function, which is added as a UIView extension:
func addShadow() {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.23
self.layer.shadowRadius = 2.0
self.layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 8, height: 8)).cgPath
}
Here is a screenshot showing the initial shadows and then the images that are loaded in their place:
The images are put onto the tableview cells with this function, which is called in the dequeueReusableCell function:
func makeBackgroundImageCall(anime: Anime) {
guard let url = anime.getCoverImageOriginalUrl() else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if (error != nil) {
print(error as Any)
return
}
guard let image = UIImage(data: data!) else { return }
DispatchQueue.main.async() { [weak self] in
guard let contentView = self?.contentView else { return }
let imageView = UIImageView(image: image)
contentView.addSubview(imageView)
imageView.pin(to: contentView) // sets constraints to fill parent
}
}.resume()
}
Lastly, here is my dequeueReusableCell function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseID) as! AnimeTableCell
if let anime = trendingAnimes[indexPath.section] {
cell.makeBackgroundImageCall(anime: anime)
}
return cell
}
Another note is that this is all programatic, there's no storyboard so this is why I'm using init instead of awakeFromNib.
How can I get the shadow to properly overlay the uiTableViewCell and its content image?
mylayer.frame = self.bounds;
try to set bound for the shadow layer.
You are setting the shadowPath when your cell is first initialized, and doesn't have its final size yet.
From your example it looks like your cells are rectangular. In this case you don't need to set a shadowPath at all, and the shadow will fit the shape and size of the cell.
If you do want to define a custom shadowPath, you will need to create it after the cell is resized to fit its contents.

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!

How to use CAGradientLayers on UITableViewCell

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
}
}

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