14

I have a custom UITableView cell.I am setting it's bottom left & bottom right corner radius.I am setting corner radius in cellForAtindexPath.Below is the code for that

if indexPath.row == 9 {
  recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
  recipeInfoCell.layoutSubviews()
  recipeInfoCell.layoutIfNeeded()
} else  {
  recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 0)
  recipeInfoCell.layoutSubviews()
}

Now when i first launch the tableview then it does not set any corner radius. But when i scroll again then it is setting the corner radius.

I have created an extension of UIView in which there is one function which is setting the corner radius

func roundCorners(corners: UIRectCorner, radius: CGFloat) {
  let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
  let mask = CAShapeLayer()
  mask.path = path.cgPath
  self.layer.mask = mask
}

Please tell how do i resolve this ?

Nathan
  • 13
  • 1
  • 4
TechChain
  • 8,404
  • 29
  • 103
  • 228
  • see this : http://stackoverflow.com/a/37646053/3901620 – KKRocks May 10 '17 at 09:17
  • I am doing exactly but issue is corner radius is not shown when cell is created.When i scroll the tableview only the corner radius is set – TechChain May 10 '17 at 10:24
  • You corner radius second time, that means its not corner radius issue. Its table view issue. So check the conditions in cell for row at index path. – phani May 12 '17 at 10:15
  • but when i set corner radius like view.cornerRadius = 10 then it worlks fine – TechChain May 12 '17 at 10:49
  • Have you tried to put this code into "willDisplayCell"? ALso Background color modifications are put in there. https://developer.apple.com/reference/uikit/uitableviewdelegate/1614883-tableview?language=objc – Lepidopteron May 13 '17 at 08:27

8 Answers8

17

I do not think it is a good idea to set corner radius in cellForRow atIndexPath. The reason being, this function is called many times during the lifetime of UITableView and you only need to set the corner radius only once and that too when the cell is initialised. Changing the corner radius based on indexPath will also affect the UITableView's performance.

A better way to this would be to create two cells, one with corner radius as 0 and another with 10 and the use those cells based on indexPath.

Then you can put your cornerRadius set logic in layoutSubview function in your custom cell.

If you want to do it in your tableView methods only, the correct way is to do it in willDisplayCell because after that call, cell's layoutSubviews function in called.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row % 2 == 0 {
        let path = UIBezierPath(roundedRect: cell.contentView.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        cell.contentView.layer.mask = mask
    } else {
        let path = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 0, height: 0))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        cell.contentView.layer.mask = mask
    }
}

UPDATE: May 19, 2017

The above concept will work fine when the view that you want to round and put shadow on is the same size as the cell's content view. But if it is anything different than that, it won't work.

The reason for the above statement is that at the time when willDisplayCell is called, where the above code is using cell.contentView.bounds, the other views are not calculated yet. So when we will be using another view, we will have to use that view's bounds to calculate the mask's frame which we will be different from the actual one.

After reading up on this a bit, I found out that, to do this kind of a thing is by overriding draw(_ rect: CGRect) function of UITableViewCell. Because at this point, the view's size has been properly calculated and we can create a correct frame.

Below is the code from custom UITableViewCell class:

var shadowLayer = CAShapeLayer()

override func draw(_ rect: CGRect) {
    let path = UIBezierPath(roundedRect: self.outerView.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
    let mask = CAShapeLayer()
    mask.path = path.cgPath
    self.outerView.layer.mask = mask
    // Handle Cell reuse case        
    shadowLayer.removeFromSuperlayer()

    shadowLayer.shadowPath = path.cgPath
    shadowLayer.frame = self.outerView.layer.frame
    print(shadowLayer.frame)
    shadowLayer.shadowOffset = CGSize(width: 0, height: 0)
    shadowLayer.shadowColor = UIColor.black.cgColor
    shadowLayer.shadowOpacity = 0.9
    self.contentView.layer.insertSublayer(shadowLayer, below: self.outerView.layer)
    super.draw(rect)
}
Nathan
  • 13
  • 1
  • 4
kerry
  • 2,362
  • 20
  • 33
  • I can't follow this approach. – TechChain May 11 '17 at 10:06
  • What part you did not get? – kerry May 11 '17 at 13:46
  • I don't want to use other cell because i am already using too many cells in tableview.This might solve my problem but i would like to know what is wrong with my code.When i am setting corner radius through bazier path only then i face problem.When i set corner radius without bazier path then my code works fine.I am going to follow your approach to make sure if this is a way to solve my problem – TechChain May 11 '17 at 16:13
  • I have tried your approach but this does not work for me – TechChain May 12 '17 at 09:56
  • Corner raius does not set in first go even i addded corner radius to view in awakefromnib – TechChain May 12 '17 at 10:40
  • Thanks.Even if it does not work on your end then please tell me how to do this.Maybe any other aproach – TechChain May 12 '17 at 11:27
  • Hi @Kerry, did you check the issue at your end – TechChain May 13 '17 at 05:49
  • yes I did. the problem is that if we call corner radius function in awakeFromNib method, it won't show because after this method is called there are methods that reset the view. So after a little reading, I found this article that explains which methods are called. https://bradbambara.wordpress.com/2015/01/18/object-life-cycle-uiview/ So, the I tried calling the function in layoutSubviews and it worked. Can you try that at your end? – kerry May 13 '17 at 07:12
  • can you explain how did call in layout subviews?How do i get reference of cell in layout subview. – TechChain May 13 '17 at 07:33
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/144116/discussion-between-kerry-and-deepak-kumar). – kerry May 13 '17 at 07:38
  • @deepakkumar I have sent you concept to fix shadow in chatroom. Have a look. – kerry May 15 '17 at 12:56
  • @kerry Just one problem, shadow mask is adding duplicates when cell reused. Fixed with edit :) – Prashant Tukadiya Dec 18 '19 at 07:55
10

put round corner code in main queue like this :

if indexPath.row == 9 { dispatch_async(dispatch_get_main_queue(),{
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
   })} else{
 dispatch_async(dispatch_get_main_queue(),{
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 0)
}) }
Pradeep Kashyap
  • 921
  • 10
  • 15
7

Try writing these lines of code in layoutSubviews() of your custom UITableViewCell

override func layoutSubviews() {
  super.layoutSubviews()
  self.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
}
Nathan
  • 13
  • 1
  • 4
Himanshu
  • 2,832
  • 4
  • 23
  • 51
3

For acquiring that in swift only I use to create one subclass of the UIButton like below

(in any .swift file of project)

//MARK: Custom Class for UIView
open class CustomView: UIView {
    open func drawViewsForRect(_ rect: CGRect) {
        fatalError("\(#function) must be overridden")
    }

    open func updateViewsForBoundsChange(_ bounds: CGRect) {
        fatalError("\(#function) must be overridden")
    }

}

Then define the below methods in same or deferent .swift file like this

    //MARK: - UIView Property Class
@IBDesignable open class CView : CustomView{

    @IBInspectable dynamic open var borderColor: UIColor = UIColor.clear{
        didSet{
            updateBorderColor()
        }
    }

    @IBInspectable dynamic open var borderWidth: CGFloat = 1.0{
        didSet{
            updateBorderWidth()
        }
    }

    @IBInspectable dynamic open var cornerRadius: CGFloat = 0.0{
        didSet{
            updateBorderRadius()
        }
    }

    @IBInspectable dynamic open var shadowColor: UIColor?{
        didSet{
            updateShadowColor()
        }
    }

    @IBInspectable dynamic open var shadowRadius: CGFloat = 0.0{
        didSet{
            updateShadowRadius()
        }
    }

    @IBInspectable dynamic open var shadowOpacity: Float = 0.0{
        didSet{
            updateShadowOpacity()
        }
    }

    @IBInspectable dynamic open var shadowOffSet: CGSize = CGSize(width: 0.0, height: 0.0){
        didSet{
            updateShadowOffset()
        }
    }

    //Update Borders Properties
    open func updateBorderColor(){
        self.layer.borderColor = borderColor.cgColor
    }
    open func updateBorderRadius(){
        self.layer.cornerRadius = cornerRadius
    }
    open func updateBorderWidth(){
        self.layer.borderWidth = borderWidth
    }

    //Update Shadow Properties
    open func updateShadowColor(){
        self.layer.shadowColor = shadowColor?.cgColor
        self.clipsToBounds = false;
        self.layer.masksToBounds = false;
    }
    open func updateShadowOpacity(){
        self.layer.shadowOpacity = shadowOpacity
        self.clipsToBounds = false;
        self.layer.masksToBounds = false;
    }
    open func updateShadowRadius(){
        self.layer.shadowRadius = shadowRadius
        self.clipsToBounds = false;
        self.layer.masksToBounds = false;
    }
    open func updateShadowOffset(){
        self.layer.shadowOffset = CGSize(width: shadowOffSet.width, height: shadowOffSet.height)
        self.layer.shadowColor = shadowColor?.cgColor
        self.clipsToBounds = false;
        self.layer.masksToBounds = false;
    }
}

Then just assign the CView class in storyboard at design time for any view controller and just provide the required values for the properties for that in side the attribute inspector for that view

In Storyboard

1) Class of the View Like this

set calls of view

2) Set property like this

Property like this

3) This will show view in side the design like this

view in storyboard

With this you even been able to see the shadow or corner radius directly in your design builder i.e. in side the storyboard view like the third image.

The iOSDev
  • 5,237
  • 7
  • 41
  • 78
2

You want a specific cell to round corner if i understand correctly. here is my solution.Though i am bit late but i try to help others. :) I just added my solution as a picture.

Step 1:

Created a custom Cell and did some necessary steps.

enter image description here enter image description here

Below is the code for Rounded bottom left and bottom right.Sorry for poor coding style:

enter image description here

Below is My view controller's configuration for showing tableView with rounded cells.

enter image description here

enter image description here

And Below is the moment of truth:

enter image description here

elk_cloner
  • 2,049
  • 1
  • 12
  • 13
0

I took reference from (https://stackoverflow.com/a/44067058/2781088) and modified the code and now it's working for me:

Add below code to your custom cell class:

enum cellStyle: Int {
    case Normal = 0, Rounded
}

class CustomTableCell:UITableViewCell {
    var cellType: Int = 0 {
        didSet {
            let maskLayer = CAShapeLayer()
            let cell: cellStyle = cellStyle(rawValue: cellType)!
            
            switch cell {
            case .Normal:
                let normal = UIBezierPath(roundedRect: self.viewMain.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 0, height: 0))
                
                maskLayer.path = normal.cgPath
                self.viewMain.layer.mask = maskLayer
            case .Rounded:
                let rounded = UIBezierPath(roundedRect: self.viewMain.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10, height: 10))
                
                maskLayer.path = rounded.cgPath
                self.viewMain.layer.mask = maskLayer
            }
        }
    }
}

In your ViewController->cellForRowAt --- Call below code on Main Queue

DispatchQueue.main.async {
            if totalRows == index + 1 { //write your condition
                cell.cellType = cellStyle.Rounded.rawValue
            } else {
                cell.cellType = cellStyle.Normal.rawValue
            }
            cell.layoutSubviews()
            cell.layoutIfNeeded()
        }
Dharman
  • 30,962
  • 25
  • 85
  • 135
Mohit Kumar
  • 2,898
  • 3
  • 21
  • 34
0

This worked for me:

view.clipsToBounds = true
view.layer.cornerRadius = value
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
0

Based on my experience, this is what works for me to get the dynamic-sized layers work properly.

//Inside the cell or the view class
override func layoutSublayers(of layer: CALayer) {
    super.layoutSublayers(of: layer)
    
    if layer === self.layer {
        //Do any dynamic-layer update here
        myViewToRound.layer.cornerRadius = myViewToRound.bounds.width/2
    }
}

here, replace myViewToRound with the UIView subclass that you want to be rounded inside your cell/view