Swift - UI Button Shadow Gradient

Swift - UI Button Shadow Gradient

You can do it like this

Init your gradient layer

let gradientLayer = CAGradientLayer.init()

gradientLayer.colors = [UIColor.red.cgColor,
UIColor.yellow.cgColor,
UIColor.green.cgColor,
UIColor.blue.cgColor]

gradientLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)

Set preferable size for example 40 from button's frame

gradientLayer.frame = CGRect.init(
x: button.frame.minX - 40,
y: button.frame.minY - 40,
width: button.frame.width + 80,
height: button.frame.height + 80)
gradientLayer.masksToBounds = true

Init shadow layer

let shadowLayer = CALayer.init()
shadowLayer.frame = gradientLayer.bounds
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOpacity = 0.08
shadowLayer.shadowRadius = 20
shadowLayer.shadowPath = CGPath.init(rect: shadowLayer.bounds, transform: nil)

Set the shadow layer as a mask for gradient layer

gradientLayer.mask = shadowLayer

Insert gradient layer in button's superView below button's layer

backgroungView.layer.insertSublayer(gradientLayer, below: button.layer)

How do you give a UIButton a gradient and give that button a bottom shadow with that same gradient in SwiftUI?

You can do it like this:

Button(action: createTask) {
Text(“MyButton“)
.color(.white)
.font(Font.system(size: 17))
.frame(height: 56)
.frame(minWidth: 0, maxWidth: .infinity)
.background(LinearGradient.actionButton, cornerRadius: 28)
.shadow() // configure shadow as you want
}

And just to improve code's readable I've created gradient separately:

fileprivate extension LinearGradient {
static let actionButton = LinearGradient(gradient: Gradient(colors: [Color(“ActionGradientFirst”), Color(“ActionGradientSecond”)]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
}

Colors ActionGradientFirst and ActionGradientSecond I declared in Assets.xcasset

How to add shadows to gradient border layer . refer the below image

Edit

Minor changes from initial code:

  • background layer doesn't interfere with added subviews
  • handles resizing correctly (when called in viewDidLayoutSubviews)

You can do this by adding a shadow properties to the view's layer, and adding another layer as a "background" layer.

After Edit... Here is your UIView extension - slightly modified (see the comments):

extension UIView {

private static let kLayerNameGradientBorder = "GradientBorderLayer"
private static let kLayerNameBackgroundLayer = "BackgroundLayer"

func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0),
endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0,
bgColor: UIColor = .white,
shadowColor: UIColor = .black,
shadowRadius: CGFloat = 5.0,
shadowOpacity: Float = 0.75,
shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0)
) {

let existingBackground = backgroundLayer()
let bgLayer = existingBackground ?? CALayer()

bgLayer.name = UIView.kLayerNameBackgroundLayer

// set its color
bgLayer.backgroundColor = bgColor.cgColor

// insert at 0 to not cover other layers
if existingBackground == nil {
layer.insertSublayer(bgLayer, at: 0)
}

// use same cornerRadius as border
bgLayer.cornerRadius = cornerRadius
// inset its frame by 1/2 the border width
bgLayer.frame = bounds.insetBy(dx: width * 0.5, dy: width * 0.5)

// set shadow properties
layer.shadowColor = shadowColor.cgColor
layer.shadowRadius = shadowRadius
layer.shadowOpacity = shadowOpacity
layer.shadowOffset = shadowOffset

let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()

border.name = UIView.kLayerNameGradientBorder

// don't do this
// border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
// width: bounds.size.width + width, height: bounds.size.height + width)

// use this instead
border.frame = bounds

border.colors = colors.map { $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint

let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)

let path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.path = path
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.backgroundColor = UIColor.black.cgColor
mask.lineWidth = width
mask.masksToBounds = false
border.mask = mask

let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}

}

private func backgroundLayer() -> CALayer? {
let aLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameBackgroundLayer }
if aLayers?.count ?? 0 > 1 {
fatalError()
}
return aLayers?.first
}

private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}

After Edit... and here's an example in-use:

class GradBorderViewController: UIViewController {

var topGradView: UIView = UIView()

// make bottom grad view a button
var botGradView: UIButton = UIButton()

var topBkgView: UIView = UIView()
var botBkgView: UIView = UIView()

let embededLabel: UILabel = UILabel()

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .white

embededLabel.textColor = .red
embededLabel.textAlignment = .center
embededLabel.text = "Label as subview"

botGradView.setTitle("Button", for: [])
botGradView.setTitleColor(.red, for: [])
botGradView.setTitleColor(.lightGray, for: .highlighted)

topGradView.backgroundColor = .clear
botGradView.backgroundColor = .clear

topBkgView.backgroundColor = .yellow
botBkgView.backgroundColor = UIColor(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)

[topBkgView, topGradView, botBkgView, botGradView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}

embededLabel.translatesAutoresizingMaskIntoConstraints = false

// embed label in topGradView
topGradView.addSubview(embededLabel)

let g = view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

// yellow background view on top half, dark-red background view on bottom half
topBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
topBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),

botBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
botBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),

topBkgView.topAnchor.constraint(equalTo: g.topAnchor),
botBkgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),

topBkgView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.5),
botBkgView.topAnchor.constraint(equalTo: topBkgView.bottomAnchor),

// each grad border view 75% of width, 80-pt constant height
topGradView.widthAnchor.constraint(equalTo: topBkgView.widthAnchor, multiplier: 0.75),
topGradView.heightAnchor.constraint(equalToConstant: 80.0),

botGradView.widthAnchor.constraint(equalTo: topGradView.widthAnchor),
botGradView.heightAnchor.constraint(equalTo: topGradView.heightAnchor),

// center each grad border view in a background view
topGradView.centerXAnchor.constraint(equalTo: topBkgView.centerXAnchor),
topGradView.centerYAnchor.constraint(equalTo: topBkgView.centerYAnchor),

botGradView.centerXAnchor.constraint(equalTo: botBkgView.centerXAnchor),
botGradView.centerYAnchor.constraint(equalTo: botBkgView.centerYAnchor),

// center the embedded label in the topGradView
embededLabel.centerXAnchor.constraint(equalTo: topGradView.centerXAnchor),
embededLabel.centerYAnchor.constraint(equalTo: topGradView.centerYAnchor),

])

botGradView.addTarget(self, action: #selector(self.testTap(_:)), for: .touchUpInside)
}

@objc func testTap(_ sender: Any?) -> Void {
print("Tapped!")
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

let a1: [CGFloat] = [173, 97, 222].map({$0 / 255.0})
let a2: [CGFloat] = [0, 198, 182].map({$0 / 255.0})
let c1 = UIColor(red: a1[0], green: a1[1], blue: a1[2], alpha: 1.0)
let c2 = UIColor(red: a2[0], green: a2[1], blue: a2[2], alpha: 1.0)

topGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5
)

botGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5,
shadowColor: .white,
shadowRadius: 12,
shadowOpacity: 0.95,
shadowOffset: CGSize(width: 0.0, height: 0.0)
)

}

}

After Edit... Results:

Sample Image

How to add a gradient above button in SwiftUI that is overlapping the content? iOS 13

If it helps someone, I managed to do it with overlay.

   Button(action: {
state.isExpanded.toggle()
}, label: {
Text(state.isExpanded ? "Collapse" : "Expand")
.frame(maxWidth: .infinity,
minHeight: 60,
maxHeight: 60,
alignment: .center)
.foregroundColor(Color(.red))
.background(Color(.white))
}).overlay(Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(
gradient: Gradient(colors: [.white.opacity(0), .white.opacity(1)]),
startPoint: .top,
endPoint: .bottom))
.frame(height: 30)
.alignmentGuide(.top) { $0[.top] + 30 },
alignment: .top)

UIView Shadow Gradient

I think you can't do more with standard CALayer shadow.
Take a look at filters property of CALayer.

https://developer.apple.com/documentation/quartzcore/calayer/1410901-filters

Create second CAGradientLayer and apply a GausianBlur filter for example.

https://developer.apple.com/library/content/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIGaussianBlur

Disable standard layer shadows and add your blurred layer as sublayer.



Related Topics



Leave a reply



Submit