Search code examples
swiftxcodeuiviewcalayeruibezierpath

Custom rounded Textfield with inner shadows does not get a correct layout


I want to make a textfield that has inset shadows.

I found an answer here but i do not get the expected result so because i don't have enough karma and cannot post a comment.

This is the result i get :

enter image description here

This is the code :

func applyDesign() {

    let innerShadow = CALayer()
    innerShadow.frame = bounds

    // Shadow path (1pt ring around bounds)
    let radius = self.frame.size.height/2
    let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -1, dy:-1), cornerRadius:radius)
    let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()


    path.append(cutout)
    innerShadow.shadowPath = path.cgPath
    innerShadow.masksToBounds = true
    // Shadow properties
    innerShadow.shadowColor = UIColor.darkGray.cgColor
    innerShadow.shadowOffset = CGSize(width: 0, height: 2)
    innerShadow.shadowOpacity = 0.5
    innerShadow.shadowRadius = 2
    innerShadow.cornerRadius = self.frame.size.height/2
    layer.addSublayer(innerShadow)
}

How do i edit the code so that the textfield scales over the screen correctly again ?


Solution

  • The key thing is where do you call applyDesign. When view is loaded, it usually has size derived from nib / init method. So if you apply design then, it might not match what's on the screen later.

    In your case, you should reapply these custom design every time text field is layouted by auto-layout engine.

    Short example how could it look:

    class CustomField: UITextField {
    
        lazy var innerShadow: CALayer = {
            let innerShadow = CALayer()
            layer.addSublayer(innerShadow)
            return innerShadow
        }()
    
        override func layoutSubviews() {
            super.layoutSubviews()
            applyDesign()
        }
    
        func applyDesign() {
            innerShadow.frame = bounds
    
            // Shadow path (1pt ring around bounds)
            let radius = self.frame.size.height/2
            let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -1, dy:-1), cornerRadius:radius)
            let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()
    
    
            path.append(cutout)
            innerShadow.shadowPath = path.cgPath
            innerShadow.masksToBounds = true
            // Shadow properties
            innerShadow.shadowColor = UIColor.darkGray.cgColor
            innerShadow.shadowOffset = CGSize(width: 0, height: 2)
            innerShadow.shadowOpacity = 0.5
            innerShadow.shadowRadius = 2
            innerShadow.cornerRadius = self.frame.size.height/2
        }
    }
    

    You can improve it a bit by moving some redundant setup from applyDeisgn to some kind of one-time initializer (even in this lazy var definition).