First I set constraints to a button in storyboard. Then, I edited it in code to make it look good.
When I apply my gradient function to a button it becomes bigger than before. How do you fix that?
extension UIView {
func applyGradient(colours: [UIColor]) -> Void {
self.applyGradient(colours, locations: nil)
}
func applyGradient(colours: [UIColor], locations: [NSNumber]?) -> Void {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.CGColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, atIndex: 0)
}
}
I just applied it on one button and..
Here is the pic...
Now after that it looks like this...The gradient bound did nothing It goes way outside frame PLZPLZPLZ try to help me :) I appreciate ur help so far
Add self.layer.masksToBounds = true to the function. This will mean that the sublayer you added won't draw outside of the original frame of the button
EDIT:
If you want the shadow as well you'll need to set self.layer.masksToBounds to false, since it will clip the shadow too.
Try gradient.frame = self.layer.bounds instead maybe?
Related
as you probably know, if you try to set a gradient background to an UILabel it overwrites the text.
I would like to write an extension to UILabels in order to put a gradient background.
This is my code, but the gradient is put in a square moved down and right to the label. I cannot understand why, since I am using the same frame
public extension UILabel {
func applyGradientToLabelAtAngle(_ angle : CGFloat, _ color1 : UIColor, _ color2 : UIColor){
let gradient = CAGradientLayer()
gradient.frame = self.frame
gradient.colors = [color1.cgColor, color2.cgColor]
gradient.calculatePoints(for: angle)
let index = self.layer.superlayer!.sublayers!.firstIndex(of: self.layer)!
self.layer.superlayer!.sublayers!.insert(gradient, at: index)
}
}
edit: calculatePoints function calculate the locations, not important for the problem
Insufficient information. Your code works fine for me when I call it like this:
let lab = UILabel()
lab.text = "Howdy there"
lab.sizeToFit()
lab.frame.origin = .init(x:100, y:100)
self.view.addSubview(lab)
lab.applyGradientToLabelAtAngle(.pi, .green, .red)
The problem is evidently that you not calling it like that. But you didn't say how you are calling it. I suspect that you are calling it at the wrong time, namely at a time when the all-important self.frame is not yet known.
I'm using this approach to cut out a rounded rect "window" from a background view:
override func draw(_ rect: CGRect) {
guard let rectsArray = rectsArray else {
return
}
for holeRect in rectsArray {
let holeRectIntersection = rect.intersection(holeRect)
if let context = UIGraphicsGetCurrentContext() {
let roundedWindow = UIBezierPath(roundedRect: holeRect, cornerRadius: 15.0)
if holeRectIntersection.intersects(rect) {
context.addPath(roundedWindow.cgPath)
context.clip()
context.clear(holeRectIntersection)
context.setFillColor(UIColor.clear.cgColor)
context.fill(holeRectIntersection)
}
}
}
}
In layoutSubviews() I update the background colour add my "window frame" rect:
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = self.baseMoodColour
isOpaque = false
self.rectsArray?.removeAll()
self.rectsArray = [dragAreaView.frame]
}
I'm adding the rect here because layoutSubviews() updates the size of the "window frame" (i.e., the rect changes after layoutSubviews() runs).
The basic mechanism works as expected, however, if I change the background colour, the cutout window fills with black. So I'm wondering how I can animate a background colour change with this kind of setup? That is, I want to animate the colour of the area outside the cutout window (the window remains clear).
I've tried updating backgroundColor directly, and also using didSet in the accessor of a custom colour variable in my UIView subclass, but both cause the same filling-in of the "window".
var baseMoodColour: UIColor {
didSet {
self.backgroundColor = baseMoodColour
self.setNeedsDisplay()
}
}
Try to use UIView.animate, you can check it here
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveEaseOut], animations: {
self.backgroundColor = someNewColour
//Generally
//myView.backgroundColor = someNewColor
}, nil)
The problem in the short run is that that is simply what clear does if the background color is opaque. Just give your background color some transparency — even a tiny bit of transparency, so tiny that the human eye cannot perceive it — and now clear will cut a hole in the view.
For example, your code works fine if you set the view's background color to UIColor.green.withAlphaComponent(0.99).
By the way, you should delete the lines about UIColor.clear; that's a red herring. You should also cut the lines about the backgroundColor; you should not be repainting the background color into your context. They are two different things.
The problem in the long run is that what you're doing is not how to punch a hole in a view. You should be using a mask instead. That's the only way you're going to get the animation while maintaining the hole.
Answering my own question, based on #matt's suggestion (and linked example), I did it with a CAShapeLayer. There was an extra "hitch" in my requirements, since I have a couple of views on top of the one I needed to mask out. So, I did the masking like this:
func cutOutWindow() {
// maskedBackgroundView is an additional view, inserted ONLY for the mask
let r = self.maskedBackgroundView.bounds
// Adjust frame for dragAreaView's border
var dragSize = self.dragAreaView.frame.size
var dragPosition = self.dragAreaView.frame.origin
dragSize.width -= 6.0
dragSize.height -= 6.0
dragPosition.x += 3.0
dragPosition.y += 3.0
let r2 = CGRect(x: dragPosition.x, y: dragPosition.y, width: dragSize.width, height: dragSize.height)
let roundedWindow = UIBezierPath(roundedRect: r2, cornerRadius: 15.0)
let mask = CAShapeLayer()
let path = CGMutablePath()
path.addPath(roundedWindow.cgPath)
path.addRect(r)
mask.path = path
mask.fillRule = kCAFillRuleEvenOdd
self.maskedBackgroundView.layer.mask = mask
}
Then I had to apply the colour change to maskedBackgroundView.layer.backgroundColor (i.e., to the layer, not the view). With that in place, I get the cutout I need, with animatable colour changes. Thanks #matt for pointing me in the right direction.
I am trying to add a Gradient in IOS like I did in Android. So I can see my label on top of the UIImageView and its not hidden.
In android I did this in a drawable `
<gradient
android:angle="90"
android:endColor="#00ffffff"
android:startColor="#aa000000"
android:centerColor="#00ffffff" />
<corners android:radius="0dp" />
`
I am trying to do this in IOS Swift 4.2 and I get the following :
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
gradient.frame = CGRect(x: 0.0, y: 0.0, width: showImageView.frame.size.width, height: showImageView.frame.size.height)
showImageView.layer.addSublayer(gradient)
How do I get the Gradient to start black from the bottom and go up in 90 degrees?How do I change the opacity?Any idea?
A few thoughts:
Choose colors with alpha less than 1. Perhaps:
gradient.colors = [
UIColor.black.withAlphaComponent(0.8).cgColor,
UIColor.black.withAlphaComponent(0).cgColor
]
To have this gradient go vertically, choose start and end points that have the same x value. E.g. to cover bottom half, perhaps:
gradient.startPoint = CGPoint(x: 0.5, y: 1)
gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
Be very careful about using frame. You want the layer to reference the bounds of the image view (using the image view’s coordinate system), not the frame (which specifies where the image view is in its superview’s coordinate system). If your image view happens to be at 0, 0, you might not see a difference, but if you move the image view around at all, these will start to deviate. So, assuming you’re adding this gradient to the image view, itself, you’d use the image view’s bounds:
gradient.frame = showImageView.bounds
Be aware that the frame/bounds of the image view can sometimes change. So, we will implement layoutSubviews in our UIImageView or UITableViewCell subclass, and update the gradient’s frame there, e.g.
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}
That way it will update the gradient’s frame when the view’s layout changes.
The other solution is to define a UIView subclass, say GradientView, that renders the CAGradientLayer and define the base layerClass of that view to be a CAGradientLayer. Then add this view in between the image view and the label, define its constraints, and then you’ll have a gradient that changes dynamically as the GradientView size changes. (This layerClass approach has the advantage that it yields better animation/transitions than you’d get by just updating the frame programmatically.)
Thus, something like:
class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
var firstColor: UIColor = UIColor.black.withAlphaComponent(0.8) {
didSet { updateColors() }
}
var secondColor: UIColor = UIColor.black.withAlphaComponent(0) {
didSet { updateColors() }
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
}
private extension GradientView {
func configure() {
updateColors()
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.5)
}
func updateColors() {
gradientLayer.colors = [firstColor, secondColor].map { $0.cgColor }
}
}
If you really want your white text to pop, in addition to adding gradient over the image, you might also add a black glow/shadow to the text. It’s subtle, but really makes the text pop. Just make its shadow color the same color as the gradient.
So you can see the effect, here are four renditions of a cell, with (a) no gradient; (b) with gradient; (c) with gradient and nice gaussian blur around text; and (d) with simple shadow around text:
The nice, gaussian blur around the text is rendered with:
customLabel.layer.shadowColor = UIColor.black.cgColor
customLabel.layer.shadowRadius = 3
customLabel.layer.shadowOpacity = 1
customLabel.layer.masksToBounds = false
customLabel.layer.shouldRasterize = true
I think that third rendition (with gradient over the image, with glow around the text) is best. It’s subtle, but the text really pops. But gaussian blurs are computationally expensive, so if you find this adversely affects your performance too much, you can use the fourth option, with the simple, non-blurred shadow:
customLabel.shadowColor = .black
// perhaps also
// customLabel.shadowOffset = CGSize(width: -1, height: -1)
I have been able to apply the gradient I want to the backgroundView of my UICollectionViewCell. But every time I reload the cell, it applies again my gradient, accumulating all the gradient.
First of all here is my method for the gradient :
static func setGradientWhite(uiView: UIView) {
let colorBottomWhite = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0.30).cgColor
let colorTopWhite = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [ colorTopWhite, colorBottomWhite]
gradientLayer.locations = [ 0.0, 1.0]
gradientLayer.frame = uiView.bounds
uiView.layer.insertSublayer(gradientLayer, at: 0)
}
I know I can do it in a more generic way, and change the way I am handling the colors, but it's not the point, it is for testing purpose for now.
I tried to call it like this :
override func awakeFromNib() {
super.awakeFromNib()
UIUtils.setGradientWhite(uiView: self)
}
The gradients do not accumulate this way, in the awakeFromNib, BUT, the animation isn't finish and I only have half of my cell with gradient applied.
And if I do it in this method :
override func layoutSubviews() {
super.layoutSubviews()
CATransaction.begin()
CATransaction.setDisableActions(true)
UIUtils.setGradientWhite(uiView: self)
CATransaction.commit()
}
The gradient animation is properly finished, applying to the whole view, but when the cell is reloading, it applies a new gradient on top of the previous one, until I cannot see my cell anymore (in this case too much white here).
Any ideas on how can I solve this issue?
You can try
override func layoutSubviews() {
super.layoutSubviews()
if !(self.layer.sublayers?.first is CAGradientLayer) {
CATransaction.begin()
CATransaction.setDisableActions(true)
UIUtils.setGradientWhite(uiView: self)
CATransaction.commit()
}
}
I am relatively new to Swift. I'm just messing around trying to design a taxi app.
I discovered CGGradientLayer and thought it would add a nice effect to the top bar on the app.
I've added it and for some reason, it's not fully stretching to the views width.
This is my subclass:
import UIKit
class GradientView: UIView {
let gradient = CAGradientLayer()
override func awakeFromNib() {
setupGradientView()
}
func setupGradientView() {
gradient.frame = self.frame
gradient.colors = [UIColor.white.cgColor, UIColor.init(white: 1.0, alpha: 0.0).cgColor]
gradient.startPoint = CGPoint.zero
gradient.endPoint = CGPoint(x: 0, y: 1)
gradient.locations = [0.9, 1.0]
self.layer.addSublayer(gradient)
}
}
And this is the result.
The green background is actually the width of the UIView which the sub class is applied too.
You are setting the frame of the gradient too early. You should set it up in an override of layoutSubviews:
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = self.bounds
}
Notes:
You should be setting the gradient frame to self.bounds. That is the coordinate system used inside of the view. The gradient is placed relative to the GradientView and doesn't change if the GradientView is placed in another location on the screen (because the bounds do not change in that case, but the frame does).
At the time your view is first created, Auto Layout has not established the size of your view for your current device (or orientation). By setting the frame in layoutSubviews you always get the latest value for the size of the view.