skip to Main Content

Hello I have a request to create a UIView with cornerRadius only on topLeft and topRight and add borderColor only at left, right and top

Searching for an answer I tried some solutions but the best I’ve got is this

enter image description here

As you can see I have the sides I want with borders but they’re not rounded

Here is what I tried:

let firstView = UIView(frame: CGRect(x: 30, y: 60, width: 100, height: 100))
firstView.layer.cornerRadius = 10
firstView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
firstView.backgroundColor = .white
firstView.borders(for: [.top, .left, .right])

This is the code I used searching SO

extension UIView {
func borders(for edges:[UIRectEdge], width:CGFloat = 1, color: UIColor = .black) {

    if edges.contains(.all) {
        layer.borderWidth = width
        layer.borderColor = color.cgColor
    } else {
        let allSpecificBorders:[UIRectEdge] = [.top, .bottom, .left, .right]

        for edge in allSpecificBorders {
            if let v = viewWithTag(Int(edge.rawValue)) {
                v.removeFromSuperview()
            }

            if edges.contains(edge) {
                let v = UIView()
                v.tag = Int(edge.rawValue)
                v.backgroundColor = color
                v.translatesAutoresizingMaskIntoConstraints = false
                addSubview(v)

                var horizontalVisualFormat = "H:"
                var verticalVisualFormat = "V:"

                switch edge {
                case UIRectEdge.bottom:
                    horizontalVisualFormat += "|-(0)-[v]-(0)-|"
                    verticalVisualFormat += "[v((width))]-(0)-|"
                case UIRectEdge.top:
                    horizontalVisualFormat += "|-(0)-[v]-(0)-|"
                    verticalVisualFormat += "|-(0)-[v((width))]"
                case UIRectEdge.left:
                    horizontalVisualFormat += "|-(0)-[v((width))]"
                    verticalVisualFormat += "|-(0)-[v]-(0)-|"
                case UIRectEdge.right:
                    horizontalVisualFormat += "[v((width))]-(0)-|"
                    verticalVisualFormat += "|-(0)-[v]-(0)-|"
                default:
                    break
                }

                self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: horizontalVisualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["v": v]))
                self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: verticalVisualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["v": v]))
            }
        }
    }
  }
}

If I add cornerRadius to v UIView the result is this:

enter image description here

Any ideas?

Thank you in advance

2

Answers


  1. You can do it with autolayout, take a look to this example:

    class YourViewController: UIViewController {
    
    let yourView = UIView()
    let bgView = UIView()
    
    let border: CGFloat = 2 // your border width
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .red
        
        // your background view --> border color
        bgView.backgroundColor = .black
        bgView.clipsToBounds = true
        bgView.layer.cornerRadius = 10
        bgView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        bgView.translatesAutoresizingMaskIntoConstraints = false
        
        yourView.backgroundColor = .white
        yourView.clipsToBounds = true
        yourView.layer.cornerRadius = 10
        yourView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        yourView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(bgView)
        bgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        bgView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        bgView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        bgView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        
        bgView.addSubview(yourView)
        yourView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: border).isActive = true
        yourView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: border).isActive = true
        yourView.trailingAnchor.constraint(equalTo: bgView.trailingAnchor, constant: -border).isActive = true
        yourView.bottomAnchor.constraint(equalTo: bgView.bottomAnchor).isActive = true
      }
    }
    

    This is the result:

    enter image description here

    Login or Signup to reply.
  2. I would strongly suggest using a custom UIView subclass, rather than a UIView extension. Many advantages.

    So, simple example…

    First, we’ll use Matt’s CGRect extension (from this answer) to make things a bit easier:

    extension CGRect {
        var topRight: CGPoint { CGPoint(x: maxX, y: minY) }
        var topLeft: CGPoint { CGPoint(x: minX, y: minY) }
        var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
        var bottomLeft: CGPoint { CGPoint(x: minX, y: maxY) }
    }
    

    then, a UIView subclass that does all the work:

    class MyCustomView: UIView {
        
        // this allows us to use the "base" layer as a shape layer
        //  instead of adding a sublayer
        lazy var shapeLayer: CAShapeLayer = self.layer as! CAShapeLayer
        override class var layerClass: AnyClass {
            return CAShapeLayer.self
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            shapeLayer.fillColor = nil
            shapeLayer.strokeColor = UIColor.black.cgColor
            shapeLayer.lineWidth = 1
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let radius: CGFloat = 10.0
            
            let pth = CGMutablePath()
            
            // start at bottom-left corner
            pth.move(to: bounds.bottomLeft)
            // rounded top-left corner
            pth.addArc(tangent1End: bounds.topLeft, tangent2End: bounds.topRight, radius: radius)
            // rounded top-right corner
            pth.addArc(tangent1End: bounds.topRight, tangent2End: bounds.bottomRight, radius: radius)
            // line to bottom-right corner
            pth.addLine(to: bounds.bottomRight)
    
            shapeLayer.path = pth
            
            // round top corners of view (self)
            shapeLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
            shapeLayer.cornerRadius = radius
        }
        
    }
    

    and an example view controller showing it "in action":

    class ViewController: UIViewController {
        
        let testView = MyCustomView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemCyan
            
            testView.backgroundColor = .white
            
            testView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(testView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                testView.widthAnchor.constraint(equalToConstant: 100.0),
                testView.heightAnchor.constraint(equalToConstant: 100.0),
                testView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ])
            
        }
        
    }
    

    The output:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search