skip to Main Content

I am having issue with constraints. I am using UIBezierPath and auto layout. I have done almost everything given here and still the issue is not rectified and that is why I am posting this question. I am using the following function for rounding certain corner of my view (which I have made using storyboard)

func addShadowAndCorner(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {
    
    let shadowLayer = CAShapeLayer()
    let size = CGSize(width: cornerRadius, height: cornerRadius)
    let cgPath = UIBezierPath(roundedRect: self.curvedView.bounds, byRoundingCorners: corners, cornerRadii: size).cgPath //1
    shadowLayer.path = cgPath //2
    shadowLayer.fillColor = fillColor.cgColor //3
    shadowLayer.shadowColor = shadowColor.cgColor //4
    shadowLayer.shadowPath = cgPath
    shadowLayer.shadowOffset = offSet //5
    shadowLayer.shadowOpacity = opacity
    shadowLayer.shadowRadius = shadowRadius
    self.curvedView.layer.addSublayer(shadowLayer)
}

Then I am calling this function as below

func configureView() {
    self.view.backgroundColor = UIColor(named: "appBackgroundColor")
    curvedView.backgroundColor = .clear
    self.addShadowAndCorner(shadowColor: .darkGray, offSet: CGSize.init(width: 3.0, height: 3.0), opacity: 0.6, shadowRadius: 8, cornerRadius: 80, corners: [.topRight, .bottomLeft], fillColor: .white)
}

I have called the above function inside "override func viewDidLayoutSubviews()". But I am not getting expected behaviours for constraints on different phone sizes

The code works well when running on iPhone 11 simulator, the screenshot is shared below.

enter image description here

But when running the same code on iPhone SE simulator it is not working as expected. The screenshot is shared below for iPhone SE

enter image description here

The constraints for curvedView in storyboard is.

enter image description here

Please help me here. Thanks in advance

2

Answers


  1. You should apply the shadow code in didLayoutSubviews. At that point your views know their frame sizes. Make sure you call super too.

    Be careful not to re-add the shadow sublayer every time by just calling your addShadowAndCorner function from didLayoutSubviews.

    Login or Signup to reply.
  2. You’ll have the most reliable (and flexible) results by creating a custom UIView subclass.

    Here’s a quick example:

    class MyCustomView: UIView {
    
        // properties with default values
        var shadowColor: UIColor = .darkGray
        var offset: CGSize = .zero
        var opacity: Float = 1.0
        var shadowRadius: CGFloat = 0.0
        var cornerRadius: CGFloat = 0.0
        var corners: UIRectCorner = []
        var fillColor: UIColor = .white
        
        let shadowLayer = CAShapeLayer()
    
        convenience init(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {
            self.init(frame: .zero)
            self.shadowColor = shadowColor
            self.offset = offSet
            self.opacity = opacity
            self.shadowRadius = shadowRadius
            self.cornerRadius = cornerRadius
            self.corners = corners
            self.fillColor = fillColor
        }
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            layer.addSublayer(shadowLayer)
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let size = CGSize(width: cornerRadius, height: cornerRadius)
            let cgPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size).cgPath //1
            shadowLayer.path = cgPath //2
            shadowLayer.fillColor = fillColor.cgColor //3
            shadowLayer.shadowColor = shadowColor.cgColor //4
            shadowLayer.shadowPath = cgPath
            shadowLayer.shadowOffset = offset //5
            shadowLayer.shadowOpacity = opacity
            shadowLayer.shadowRadius = shadowRadius
        }
    
        func configureView(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {
            self.shadowColor = shadowColor
            self.offset = offSet
            self.opacity = opacity
            self.shadowRadius = shadowRadius
            self.cornerRadius = cornerRadius
            self.corners = corners
            self.fillColor = fillColor
            setNeedsLayout()
        }
    
    }
    

    You can then add a UIView in Storyboard, assign its Custom Class to MyCustomView, connect it to an @IBOutlet, and then in viewDidLoad():

    class MyTestVC: UIViewController {
        
        @IBOutlet var curvedView: MyCustomView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            curvedView.backgroundColor = .clear
            curvedView.configureView(shadowColor: .darkGray, offSet: CGSize.init(width: 3.0, height: 3.0), opacity: 0.6, shadowRadius: 8, cornerRadius: 80, corners: [.topRight, .bottomLeft], fillColor: .white)
    
        }
    }
    

    Now, it will auto-update itself when its frame changes… such as on different devices or on device rotation:

    enter image description here

    enter image description here

    enter image description here

    You can create it via code like this:

    let curvedView = MyCustomView(shadowColor: .darkGray, offSet: CGSize.init(width: 3.0, height: 3.0), opacity: 0.6, shadowRadius: 8, cornerRadius: 80, corners: [.topRight, .bottomLeft], fillColor: .white)
    

    and, with very little effort, you can make it @IBDesignable and configure your properties as @IBInspectable … then you can visually see the result when you lay it out in Storyboard.

    The only tricky property is the Corners, because there is no direct @IBInspectable option for UIRectCorner … so we can use Bool properties for each corner:

    @IBDesignable
    class MyCustomView: UIView {
        
        // properties with default values
        @IBInspectable var shadowColor: UIColor = .darkGray
        @IBInspectable var offset: CGSize = .zero
        @IBInspectable var opacity: Float = 1.0
        @IBInspectable var shadowRadius: CGFloat = 0.0
        @IBInspectable var cornerRadius: CGFloat = 0.0
        @IBInspectable var topLeft: Bool = false
        @IBInspectable var topRight: Bool = false
        @IBInspectable var bottomLeft: Bool = false
        @IBInspectable var bottomRight: Bool = false
        @IBInspectable var fillColor: UIColor = .white
        
        private let shadowLayer = CAShapeLayer()
        
        convenience init(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {
            self.init(frame: .zero)
            self.shadowColor = shadowColor
            self.offset = offSet
            self.opacity = opacity
            self.shadowRadius = shadowRadius
            self.cornerRadius = cornerRadius
            self.fillColor = fillColor
            
            self.topLeft = corners.contains(.topLeft)
            self.topRight = corners.contains(.topRight)
            self.bottomLeft = corners.contains(.bottomLeft)
            self.bottomRight = corners.contains(.bottomRight)
        }
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            layer.addSublayer(shadowLayer)
        }
        override func layoutSubviews() {
            super.layoutSubviews()
    
            backgroundColor = .clear
            
            var corners: UIRectCorner = UIRectCorner()
            
            if self.topLeft { corners.insert(.topLeft) }
            if self.topRight { corners.insert(.topRight) }
            if self.bottomLeft { corners.insert(.bottomLeft) }
            if self.bottomRight { corners.insert(.bottomRight) }
    
            let size = CGSize(width: cornerRadius, height: cornerRadius)
            let cgPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size).cgPath //1
            shadowLayer.path = cgPath
            shadowLayer.fillColor = self.fillColor.cgColor
            shadowLayer.shadowColor = self.shadowColor.cgColor
            shadowLayer.shadowPath = cgPath
            shadowLayer.shadowOffset = self.offset
            shadowLayer.shadowOpacity = self.opacity
            shadowLayer.shadowRadius = self.shadowRadius
        }
        
        public func configureView(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {
            self.shadowColor = shadowColor
            self.offset = offSet
            self.opacity = opacity
            self.shadowRadius = shadowRadius
            self.cornerRadius = cornerRadius
            self.fillColor = fillColor
            
            self.topLeft = corners.contains(.topLeft)
            self.topRight = corners.contains(.topRight)
            self.bottomLeft = corners.contains(.bottomLeft)
            self.bottomRight = corners.contains(.bottomRight)
    
            setNeedsLayout()
        }
        
    }
    

    Now, we get this in Storyboard:

    enter image description here

    enter image description here

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