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.
Related
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.
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!
ViewController.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell") as! CustomTableViewCell
cell.backgroundColor = UIColor.clear
cell.nameLbl.text = "\(adminNmaes[indexPath.row])"
cell.aboutLbl.text = "\(aboutarray[indexPath.row])"
if indexPath.row == 0
{
cell.view1.roundCorners(corners: [.topLeft,.topRight], radius: 10)
// tableView.reloadData()
}
return cell
}
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
i setup custom cell class using autolayout and used an extension to apply cornerradius for only 1st cell. i aslo want to give corner radius for bottom left and bottom right for the footer and also give it a padding of 10. any help is appreciated.thanks in advance.
As of iOS11, CALayer has property maskedCorners:
var maskedCorners: CACornerMask { get set }
In your case you would use:
cell.contentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMinYCorner]
This masks the top left and right corners to whatever cornerRadius you've set.
Also you need to make sure that the contentView is clipping its subviews so that they can conform to the shape
cell.contentView.clipsToBounds = true
I have a tableview with custom cell loaded via xib and in that cell I have status button which bottom right corner should be rounded. The button has constraints Trailing/Leading/Bottom space to superview=0 and height=30.
Without rounding it is working perfectly, as soon as I round one corner for example bottom right the constraints breaks
self.btnStatus.roundCorners(corners: [.bottomRight], radius: 7.0, borderWidth: nil, borderColor: nil)
Some guys here suggesting to call layoutSubviews() but it didn't helped me.
To be more specific I've created simple project where you can have a look into whole project.
Correct Link
ButtonRoundCorner.zip
You can get more reliable results by subclassing your button and placing your "rounding" code by overriding its layoutSubviews() function.
First, if you want to add a border, you don't want to add multiple "border sublayers" ... so change your UIView extension to this:
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat, borderWidth: CGFloat?, borderColor: UIColor?) {
let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
self.layer.masksToBounds = true
if (borderWidth != nil && borderColor != nil) {
// remove previously added border layer
for layer in layer.sublayers! {
if layer.name == "borderLayer" {
layer.removeFromSuperlayer()
}
}
let borderLayer = CAShapeLayer()
borderLayer.frame = self.bounds;
borderLayer.path = maskPath.cgPath;
borderLayer.lineWidth = borderWidth ?? 0;
borderLayer.strokeColor = borderColor?.cgColor;
borderLayer.fillColor = UIColor.clear.cgColor;
borderLayer.name = "borderLayer"
self.layer.addSublayer(borderLayer);
}
}
}
Next, add a UIButton subclass:
class RoundedButton: UIButton {
var corners: UIRectCorner?
var radius = CGFloat(0.0)
var borderWidth = CGFloat(0.0)
var borderColor: UIColor?
override func layoutSubviews() {
super.layoutSubviews()
// don't apply mask if corners is not set, or if radius is Zero
guard let _corners = corners, radius > 0.0 else {
return
}
roundCorners(corners: _corners, radius: radius, borderWidth: borderWidth, borderColor: borderColor)
}
}
This gives you a couple benefits: 1) It will update its mask layer frame when the button frame changes (rotating the device, for example), and 2) you could set these values either from your custom cell class or from cellForRowAt.
Either way, change your btnStatus class from UIButton to RoundedButton - both in your storyboard and the #IBOutlet connection.
Then change your CustomTableViewCell to this:
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var btnStatus: RoundedButton!
override func awakeFromNib() {
super.awakeFromNib()
// set up corner maskign
btnStatus.corners = .bottomRight
btnStatus.radius = 7.0
// set if desired
// btnStatus.borderWidth = 2.0
// btnStatus.borderColor = .blue
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
And finally, your cellForRowAt function becomes:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
return cell
}
That should do it...
I looked at your code. The problem is that your roundCorners function is depending on your view's bounds to set your layer's properties and you are calling roundCorners in awakeFromNib at which point your cell has the same bounds as in the NIB file because autolayout has not been calculated yet. You need to move your roundCorners call into layoutSubviews, so it gets called after autolayout is done computing your bounds.
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var btnStatus: UIButton!
override func layoutSubviews() {
super.layoutSubviews()
self.btnStatus.roundCorners(corners: [.bottomRight], radius: 7.0, borderWidth: nil, borderColor: nil)
}
}
EDIT also add cell.setNeedsLayout() to cellforRowAt to force layoutSubviews to be called before the cell is drawn for the first time.
Maybe your button is overlapping since it doesn't have an upper constraint? Try adding a bright border and see if you can see the top part, if not try adding a constraint to the top. Also, instead of giving it a static height, try making it a proportional height to the object above it (maybe the screen size of your device is causing it to overlap)
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()