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 ?
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)
}
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)
}) }
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)
}
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
2) Set property like this
3) This will show view in side the design like this
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.
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.
Below is the code for Rounded bottom left and bottom right.Sorry for poor coding style:
Below is My view controller's configuration for showing tableView with rounded cells.
And Below is the moment of truth:
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()
}
This worked for me:
view.clipsToBounds = true
view.layer.cornerRadius = value
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
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
Related
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)
this is by UI extension method
extension UIView {
func roundCorners(corners:UIRectCorner, radiusWidth: CGFloat,radiusHeight: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radiusWidth/2, height: radiusHeight/2))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}}
by this extension method i want to make my buttons with round corners with this code on viewdidload
btnRideNow.roundCorners(corners: [.topRight], radiusWidth: btnRideNow.frame.width,radiusHeight: btnRideNow.frame.height )
btnRideLater.roundCorners(corners: [.topLeft], radiusWidth: btnRideLater.frame.width,radiusHeight: btnRideLater.frame.height )
but on iPhone 5 i am getting this result
ScreenShot
you can see left button wouldn't render properly but in iPhone 6 this work properly Why?
It's all a matter of timing.
If you call roundCorners too soon, e.g. in viewDidLoad, the button's frame and bounds may not yet have been finalized. But your roundCorners depends on the bounds, so if you add the mask and the button is then resized as a result of layout, you will naturally get the wrong result.
If you want round corners you can simple do:
view.layer.cornerRadius
and if you want a border you can do
view.layer.borderWidth
and color
view.layer.borderColor
Here's a simple subclass that works with IB. You should be able to easily adapt it to your needs:
#IBDesignable
public class Button: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
Now if you wish to simply code things, you were given most of the answer. For a complete circle:
view.layer.cornerRadius = view.frame.height / 2
You can use width though, if it's square. But like #Matt says (he's very good), be careful to do this after you have the frame/bounds properly set!
I have created a UIProgressView with following properties
progressView.progressTintColor = UIColor.appChallengeColorWithAlpha(1.0)
progressView.trackTintColor = UIColor.clearColor()
progressView.clipsToBounds = true
progressView.layer.cornerRadius = 5
I am using a UIView for border. It appears like his progress = 1, which is exactly the way I want.
But if progress value is less then 1. Corners are not rounded as it should be.
Am I missing something ? How can I make it rounded corner ?
UIProgressView has two part, progress part and track part. If you use Reveal, you can see it only has two subviews. The progress view hierarchy is very simple. so...
Objective-C
- (void)layoutSubviews
{
[super layoutSubviews];
[self.progressView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.layer.masksToBounds = YES;
obj.layer.cornerRadius = kProgressViewHeight / 2.0;
}];
}
Swift (3, 4 and 5+)
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = kProgressViewHeight / 2.0
}
}
I admit subclass or extend progressView is the recommended way. In case of you don't want to do that for such a simple effect, this may do the trick.
Keep the situation that Apple will change the view hierarchy, and something may go wrong in mind.
Just do this in init
layer.cornerRadius = *desired_corner_radius*
clipsToBounds = true
It's very late to answer but actually I had the same problem.
Here my simplest solution (no code needed !) :
Add a container to embed your progress view
Round corner for your container (10 = height of container / 2)
The result :)
After searching and trying I decided to create my own custom progress view. Here is the code for anyone who may find them selevs in same problem.
import Foundation
import UIKit
class CustomHorizontalProgressView: UIView {
var progress: CGFloat = 0.0 {
didSet {
setProgress()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clearColor()
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = CGRectGetHeight(self.frame) / 2.0
self.layer.borderColor = UIColor.grayColor().CGColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (CGRectGetWidth(self.frame) - margin) * progress
let height = CGRectGetHeight(self.frame) - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
UIColor.redColor().setFill()
pathRef.fill()
UIColor.clearColor().setStroke()
pathRef.stroke()
pathRef.closePath()
self.setNeedsDisplay()
}
}
Just put above code in a swift file and drag drop a UIView in IB and give it class CustomHorizontalProgressView. and That is it.
Another answer to throw in the mix, super hacky but very quick to use.
You can just grab the sublayer and set its radius. No need to write your own UIProgressView or mess with clip paths.
progressView.layer.cornerRadius = 5
progressView.layer.sublayers[1].cornerRadius = 5
progressView.subviews[1]. clipsToBounds = true
progressView.layer.masksToBounds = true
So you round the corner of your overall UIProgressView (no need for ClipsToBounds)
Then the fill bar is the 2nd sublayer, so you can grab that and round its Corners, but you also need to set the subview for that layer to clipsToBounds.
Then set the overall layer to mask to its bounds and it all looks good.
Obviously, this is massively reliant on the setup of UIProgressView not changing and the 2nd subview/layer being the fill view.
But. If you're happy with that assumption, super easy code wise to use.
Basically progress view's (Default Style) subviews consist of 2 image view.
One for the "progress", and one for the "track".
You can loop the subviews of progress view, and set the each of image view's corner radius.
for let view: UIView in self.progressView.subviews {
if view is UIImageView {
view.clipsToBounds = true
view.layer.cornerRadius = 15
}
}
Yes ,one thing is missed...corner radius is set to progressview and it is reflecting as expected..
But if you want your track image to be rounded you have to customise your progressview.
You have to use image with rounded corner.
[progressView setTrackImage:[UIImage imageNamed:#"roundedTrack.png"]];
//roundedTrack.png must be of rounded corner
This above code will help you to change image of trackView for your progressview.
You may face the inappropriate stretching of image. You have to make your image resizable.
May be the link below will be useful if issue arise
https://www.natashatherobot.com/ios-stretchable-button-uiedgeinsetsmake/
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = ProgessView(frame: CGRect(x: 20, y: 200, width: 100, height: 10))
view.addSubview(v)
//v.progressLayer.strokeEnd = 0.8
}
}
class ProgessView: UIView {
lazy var progressLayer: CAShapeLayer = {
let line = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 5, y: 5))
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
line.path = path.cgPath
line.lineWidth = 6
line.strokeColor = UIColor(colorLiteralRed: 127/255, green: 75/255, blue: 247/255, alpha: 1).cgColor
line.strokeStart = 0
line.strokeEnd = 0.5
line.lineCap = kCALineCapRound
line.frame = self.bounds
return line
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor(colorLiteralRed: 197/255, green: 197/255, blue: 197/255, alpha: 1).cgColor
layer.addSublayer(progressLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Test my codes. You can design the height and the width as your want. You can use strokeEnd to change the progress of the progressView. You can add an animation to it. But actually, it is already animatable, you can change the value of the strokeEnd to see its primary effect. If you want to design your own animation. Try CATransaction like below.
func updateProgress(_ progress: CGFloat) {
CATransaction.begin()
CATransaction.setAnimationDuration(3)
progressLayer.strokeEnd = progress
CATransaction.commit()
}
I had this exact same problem, which is what led me to your question after googling like crazy. The problem is two-fold. First, how to make the inside of the progress bar round at the end (which 季亨达's answer shows how to do), and secondly, how to make the round end of the CAShapeLayer you added match up with the square end of the original progress bar underneath (the answer to this other StackOverflow question helped with that How to get the exact point of objects in swift?) If you replace this line of code in 季亨达's answer:
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
with this:
path.addLine(to: CGPoint(x: (Int(self.progress * Float(self.bounds.width))), y: 5))
you will hopefully get the result you're looking for.
With swift 4.0 I'm doing in this way:
let progressViewHeight: CGFloat = 4.0
// Set progress view height
let transformScale = CGAffineTransform(scaleX: 1.0, y: progressViewHeight)
self.progressView.transform = transformScale
// Set progress round corners
self.progressView.layer.cornerRadius = progressViewHeight
self.progressView.clipsToBounds = true
//Updated for swift 4
import Foundation
import UIKit
class CustomHorizontalProgressView: UIView {
var progress: CGFloat = 0.0 {
didSet {
setProgress()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = self.frame.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (self.frame.width - margin) * progress
let height = self.frame.height - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
UIColor.red.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
self.setNeedsDisplay()
}
}
Swift 4.2 version from Umair Afzal's solution
class CustomHorizontalProgressView: UIView {
var strokeColor: UIColor?
var progress: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = frame.size.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (frame.size.width - margin) * progress
let height = frame.size.height - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
strokeColor?.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
}
}
And to use it
var progressView: CustomHorizontalProgressView = {
let view = CustomHorizontalProgressView()
view.strokeColor = UIColor.orange
view.progress = 0.5
return view
}()
Set line cap :
.lineCap = kCALineCapRound;
So I've been stuck on this one for awhile, read and tried many different solutions online, but can't figure out what I'm doing wrong. What I'm trying to accomplish should be very simple: I have a UIView and I want to round its bottom left and right corners so that they are clipped and not drawn.
This is what I have so far (vwView is my outlet for a custom view on screen):
var layerTest = CAShapeLayer()
var bzPath = UIBezierPath(roundedRect: vwView.bounds, byRoundingCorners: [.BottomLeft, .BottomRight], cornerRadii: CGSizeMake(10, 10) )
layerTest.path = bzPath.CGPath
vwView.layer.mask = layerTest;
What am I doing wrong? ALSO: This is just my prototype because I really want to do this on a UITableViewCell, so if there is a different approach I need to take for that, that could also be helpful.
Thanks
James
The problem is that vwView gets resized later to fit the screen, but you don't update the path for its new size. There's no particularly trivial fix for this. You basically need to make vwView a custom class (if it's not already) and update the mask in layoutSubviews. Example:
public class RoundedBorderView: UIView {
public var roundedCorners: UIRectCorner = [ .BottomLeft, .BottomRight ] {
didSet { self.setNeedsLayout() }
}
public var cornerRadii: CGSize = CGSizeMake(10, 10) {
didSet { self.setNeedsLayout() }
}
public override func layoutSubviews() {
super.layoutSubviews()
updateMask()
}
private func updateMask() {
let mask = maskShapeLayer()
mask.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: roundedCorners, cornerRadii: cornerRadii).CGPath
}
private func maskShapeLayer() -> CAShapeLayer {
if let mask = layer.mask as? CAShapeLayer {
return mask
}
let mask = CAShapeLayer()
layer.mask = mask
return mask
}
}
Change the class of vwView to RoundedBorderView and it will maintain its own mask layer.