Swift - Problems with corner radius and drop shadow

前端 未结 14 1372
失恋的感觉
失恋的感觉 2020-11-28 18:35

I\'m trying to create a button with rounded corners and a drop shadow. No matter how I switch up, the button will not display correctly. I\

相关标签:
14条回答
  • 2020-11-28 18:47

    You can create a protocol and conform it to you UIView, UIButton, Cell or whatever you want like that:

    protocol RoundedShadowable: class {
        var shadowLayer: CAShapeLayer? { get set }
        var layer: CALayer { get }
        var bounds: CGRect { get }
    }
    ​
    extension RoundedShadowable {
        func applyShadowOnce(withCornerRadius cornerRadius: CGFloat, andFillColor fillColor: UIColor) {
            if self.shadowLayer == nil {
                let shadowLayer = CAShapeLayer()
                shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
                shadowLayer.fillColor = fillColor.cgColor
                shadowLayer.shadowColor = UIColor.black.cgColor
                shadowLayer.shadowPath = shadowLayer.path
                shadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
                shadowLayer.shadowOpacity = 0.2
                shadowLayer.shadowRadius = 3
                self.layer.insertSublayer(shadowLayer, at: 0)
                self.shadowLayer = shadowLayer
            }
        }
    }
    ​
    class RoundShadowView: UIView, RoundedShadowable {
    
        var shadowLayer: CAShapeLayer?
        private let cornerRadius: CGFloat
        private let fillColor: UIColor
    
        init(cornerRadius: CGFloat, fillColor: UIColor) {
            self.cornerRadius = cornerRadius
            self.fillColor = fillColor
            super.init(frame: .zero)
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
        }
    }
    ​
    class RoundShadowButton: UIButton, RoundedShadowable {
    
        var shadowLayer: CAShapeLayer?
        private let cornerRadius: CGFloat
        private let fillColor: UIColor
    
        init(cornerRadius: CGFloat, fillColor: UIColor) {
            self.cornerRadius = cornerRadius
            self.fillColor = fillColor
            super.init(frame: .zero)
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
        }
    }
    
    0 讨论(0)
  • 2020-11-28 18:49

    My custom button with some shadow and rounded corners, I use it directly within the Storyboard with no need to touch it programmatically.

    Swift 4

    class RoundedButtonWithShadow: UIButton {
        override func awakeFromNib() {
            super.awakeFromNib()
            self.layer.masksToBounds = false
            self.layer.cornerRadius = self.frame.height/2
            self.layer.shadowColor = UIColor.black.cgColor
            self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
            self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
            self.layer.shadowOpacity = 0.5
            self.layer.shadowRadius = 1.0
        }
    }
    

    0 讨论(0)
  • 2020-11-28 18:49

    Swift 5 & No need of "UIBezierPath"

        view.layer.cornerRadius = 15
        view.clipsToBounds = true
        view.layer.masksToBounds = false
        view.layer.shadowRadius = 7
        view.layer.shadowOpacity = 0.6
        view.layer.shadowOffset = CGSize(width: 0, height: 5)
        view.layer.shadowColor = UIColor.red.cgColor
    
    0 讨论(0)
  • 2020-11-28 18:50

    To expand on Imanou's post, it's possible to programmatically add the shadow layer in the custom button class

    @IBDesignable class CustomButton: UIButton {
        var shadowAdded: Bool = false
    
        @IBInspectable var cornerRadius: CGFloat = 0 {
            didSet {
                layer.cornerRadius = cornerRadius
                layer.masksToBounds = cornerRadius > 0
            }
        }
    
        override func drawRect(rect: CGRect) {
            super.drawRect(rect)
    
            if shadowAdded { return }
            shadowAdded = true
    
            let shadowLayer = UIView(frame: self.frame)
            shadowLayer.backgroundColor = UIColor.clearColor()
            shadowLayer.layer.shadowColor = UIColor.darkGrayColor().CGColor
            shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: self.cornerRadius).CGPath
            shadowLayer.layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
            shadowLayer.layer.shadowOpacity = 0.5
            shadowLayer.layer.shadowRadius = 1
            shadowLayer.layer.masksToBounds = true
            shadowLayer.clipsToBounds = false
    
            self.superview?.addSubview(shadowLayer)
            self.superview?.bringSubviewToFront(self)
        }
    }
    
    0 讨论(0)
  • 2020-11-28 18:55

    Exact solution for 2020 syntax

    import UIKit
    class ColorAndShadowButton: UIButton {
        override init(frame: CGRect) { super.init(frame: frame), common() }
        required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder), common() }
        private func common() {
            // UIButton is tricky: you MUST set the clear bg in bringup;  NOT in layout
            backgroundColor = .clear
            clipsToBounds = false
            self.layer.insertSublayer(backgroundAndShadow, below: layer)
        }
        
       lazy var colorAndShadow: CAShapeLayer = {
            let s = CAShapeLayer()
            // set your button color HERE (NOT on storyboard)
            s.fillColor = UIColor.black.cgColor
            // now set your shadow color/values
            s.shadowColor = UIColor.red.cgColor
            s.shadowOffset = CGSize(width: 0, height: 10)
            s.shadowOpacity = 1
            s.shadowRadius = 10
            // now add the shadow
            layer.insertSublayer(s, at: 0)
            return s
        }()
        
        override func layoutSubviews() {
            super.layoutSubviews()
            // you MUST layout these two EVERY layout cycle:
            colorAndShadow = bounds
            colorAndShadow = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
        }
    }
    

    • Note that the very old top answer here is correct but has a critical error

    Note that UIButton is unfortunately quite different from UIView in iOS.

    • Due to a strange behavior in iOS, you must set the background color (which of course must be clear in this case) in initialization, not in layout. You could just set it clear in storyboard (but you usually click it to be some solid color simply so you can see it when working in storyboard.)

    In general combos of shadows/rounding are a real pain in iOS. Similar solutions:

    https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
    https://stackoverflow.com/a/41553784/294884 - two-corner problem
    https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
    https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
    https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers

    0 讨论(0)
  • 2020-11-28 18:56

    Refactored this to support any view. Subclass your view from this and it should have rounded corners. If you add something like a UIVisualEffectView as a subview to this view you likely need to use the same rounded corners on that UIVisualEffectView or it won't have rounded corners.

    /// Inspiration: https://stackoverflow.com/a/25475536/129202
    class ViewWithRoundedcornersAndShadow: UIView {
        private var theShadowLayer: CAShapeLayer?
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            if self.theShadowLayer == nil {
                let rounding = CGFloat.init(22.0)
    
                let shadowLayer = CAShapeLayer.init()
                self.theShadowLayer = shadowLayer
                shadowLayer.path = UIBezierPath.init(roundedRect: bounds, cornerRadius: rounding).cgPath
                shadowLayer.fillColor = UIColor.clear.cgColor
    
                shadowLayer.shadowPath = shadowLayer.path
                shadowLayer.shadowColor = UIColor.black.cgColor
                shadowLayer.shadowRadius = CGFloat.init(3.0)
                shadowLayer.shadowOpacity = Float.init(0.2)
                shadowLayer.shadowOffset = CGSize.init(width: 0.0, height: 4.0)
    
                self.layer.insertSublayer(shadowLayer, at: 0)
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题