CAGradientLayer isn't spanning the whole width of my button - xcode - ios

I'm new to xcode and I am placing a gradient layer over a button and it is not filling the whole width. I'm not sure what I'm doing wrong. My code is here and screenshot of how it is rendering.
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.btnSavePhoto.bounds
let color1 = UIColor(red:0.05, green:0.29, blue:0.49, alpha:1.0).CGColor as CGColorRef
let color2 = UIColor(red:0.08, green:0.23, blue:0.39, alpha:1.0).CGColor as CGColorRef
gradientLayer.colors = [color1, color2]
gradientLayer.locations = [0.0, 1.0]
self.btnSavePhoto.layer.addSublayer(gradientLayer)

It seems like your button changes it's size later on (due to autoresizing masks or constraints), but the gradientLayer stays of the original (smaller) size. It is caused by the fact that layers don't resize automatically.
You can create a custom UIButton subclass that would update the sizing of a custom layer in layoutSubviews method.
Or you can create a custom layer subclass, although it would also require to create a UIButton subclass, as described in this question: CALayers didn't get resized on its UIView's bounds change. Why?

Related

Adding a gradient to UIImageView

I'm trying to add a gradient to UIImageView.
viewDidLoad() {
self.bookImageBig.sd_setImage(with: URL(string: self.book.image), placeholderImage: nil)
self.bookImage.sd_setImage(with: URL(string: self.book.image), placeholderImage: nil)
let view = UIView(frame: bookImageBig.frame)
let gradient = CAGradientLayer()
gradient.frame = view.frame
gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradient.locations = [0.0, 1.0]
view.layer.insertSublayer(gradient, at: 0)
bookImageBig.addSubview(view)
bookImageBig.bringSubviewToFront(view)
I'm trying to add a gradient to bookImageBig UIImageView.
However, the gradient only partially covers the image. 30px of the image is not covered by the gradient. The image was added to the story board and is being referenced in the view controller.
Can someone please help?
Instead of frame use bounds
gradient.frame = self.bookImageBig.bounds
For frame and bounds difference look at this post

CAGradientLayer not filling width

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.

Scrolling UITableView mess with gradient

I have a custom TableViewCell on my TableViewController.
The cell has a UIView that is turned into a gradient that displays black at the bottom and clear (transparent) color on the top of the UIView.
It looks fine when the app loads, but as I scroll, the gradient turns more and more black and the 'fading' itself becomes smaller and gets closer to the top of the UIView.
I do not know how to make it stay as it is when the app loads at first.
Here's my code:
let gradient = CAGradientLayer()
gradient.frame = cell.gradientView.bounds
let colour:UIColor = .black
gradient.colors = [colour.withAlphaComponent(0.0).cgColor,colour.cgColor]
cell.gradientView.layer.insertSublayer(gradient, at: 0)
Add this code in awakeFromNib not in cellForRow , as because of dequeuing the gradient is being added multiple times to the cell
let gradient = CAGradientLayer()
gradient.frame = cell.gradientView.bounds
let colour:UIColor = .black
gradient.colors = [colour.withAlphaComponent(0.0).cgColor,colour.cgColor]
self.gradientView.layer.insertSublayer(gradient, at: 0)

Swift - Most code-efficient way to reuse gradients?

My UI calls for about 7-10 different gradients.
I have the first (default) gradient set up as followed:
let gradientLayer = CAGradientLayer()
And in my viewDidLoad:
gradientLayer.frame = self.view.bounds
gradientLayer.colors = [lightBlue, lightPurple]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.zPosition = -1
self.view.layer.addSublayer(gradientLayer)
gradientLayer is the default gradient. so let's say I want to define:
gradientVariantOne / gradientVariantTwo / gradientVariantThree (etc.)
but ALL of these new gradients will have the exact frame / locations / zPosition as gradientLayer.
How can I write my code so that these new gradients inherit those properties? This will keep the code much leaner, and easy to modify in the future.
Thank you
Actually I don't see what is gained by inheritance here. Just encapsulate the setting of the frame, locations, and zPosition into a method:
extension CAGradientLayer {
func configure(view:UIView) {
self.frame = view.bounds
self.locations = [0.0, 1.0]
self.zPosition = -1
}
}
Now you can just call configure to get the configuration of each gradient layer started. You could even add more parameters to configure (such as the colors array) and turn the whole thing into a one-liner:
extension CAGradientLayer {
func configure(view:UIView, colors:[CGColor]) {
self.frame = view.bounds
self.locations = [0.0, 1.0]
self.zPosition = -1
self.colors = colors
}
}
Thus a single call to configure configures the whole layer and you're done.
You can create a UIView extension and add your gradients by calling a method.
extension UIView {
private func prepareGradient() -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
//add all common setup here
return gradientLayer
}
func addGradientVariantOne() {
let gradientLayer = prepareGradient()
gradientLayer.colors = [lightBlue, lightPurple]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.zPosition = -1
self.layer.addSublayer(gradientLayer)
}
}
Then you can simply call:
self.view.addGradientVariantOne()
Just create a similar function for each gradient.

How can UIVisualEffectView be used inside of a circle?

I'm making a custom control circle. Part of the circle may be transparent. It would make more visual sense and look better if it was translucent instead of transparent.
Because views are rectangular, and I only want the circle to be translucent, not the rest of the rectangle, this is a problem.
The UIVisualEffectView is behind the custom control.
(Without anything rendered inside of the circle for debugging purposes)
As you can see, the view is blurring things outside of the circle.
I don't know how to blur inside only the view, and the prerelease documentation is practically empty. My only thought is to create many 1x1 views to cover the circle, but this seems like it would not really work, and even if it did it would be a slow and ugly solution. How can I blur the content inside the view, without blurring anything outside it?
Set the circle view's layer mask to a filled circle:
CircleControlView *circleView = yourCircleView();
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = [UIBezierPath bezierPathWithOvalInRect:circleView.bounds].CGPath;
circleView.layer.mask = mask;
UPDATE
Swift playground example:
import UIKit
import XCPlayground
import QuartzCore
UIGraphicsBeginImageContext(CGSize(width: 100, height: 100))
let viewPath = UIBezierPath(ovalInRect:CGRect(x:1,y:1,width:98,height:98))
viewPath.lineWidth = 2
viewPath.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let view = UIView(frame:CGRect(x:0,y:0,width:100,height:100))
XCPShowView("view", view)
let label = UILabel(frame:view.bounds)
label.text = "This is the text in the background behind the circle."
label.numberOfLines = 0
view.addSubview(label)
let effectView = UIVisualEffectView(effect:UIBlurEffect(style:.ExtraLight))
effectView.frame = view.bounds
view.addSubview(effectView)
let circleView = UIImageView(image:image)
effectView.addSubview(circleView)
let maskPath = UIBezierPath(ovalInRect:circleView.bounds)
let mask = CAShapeLayer()
mask.path = maskPath.CGPath
effectView.layer.mask = mask
Result:

Resources