I want to make chat screen, and the chat bubbles have some gradient. While applying gradient to UIView its creating problem in reloading.
GradientView has been setup as the class of UIView which is placed on UITableViewCell
class GradientView: UIView {
let gradientLayer = CAGradientLayer()
func setup() {
layer.addSublayer(gradientLayer)
}
func configure() {
gradientLayer.frame = self.bounds
gradientLayer.colors = [RGBA(r: 167, g: 1, b: 88, a: 1.0).cgColor, RGBA(r: 243, g: 135, b: 32, a: 1.0).cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
configure()
}
}
The problem is while reloading the table view the gradient shows up with animation.
Many properties you set on CALayer and its subclasses will be animated by default. For a gradient layer, this includes the bounds, the gradient colours, and the start and end points, all of which you are setting here.
This is called implicit animation.
You can turn it off by creating a transaction, disabling actions, performing your changes, then committing the transaction:
CATransaction.begin()
CATransaction.setDisableActions(true)
// Do your stuff
CATransaction.commit()
However, that can get a bit messy. An alternative is to create a non-animating layer subclass and use that instead of CAGradientLayer:
class NonAnimatingGradientLayer: CAGradientLayer {
override func action(forKey event: String) -> CAAction? {
return NSNull()
}
}
Use this instead of CAGradientLayer and nothing will be animated.
layoutSubviews is called many times
class GradientView: UIView {
var gradientLayer:CAGradientLayer!
func setup() {
gradientLayer = CAGradientLayer()
layer.addSublayer(gradientLayer)
gradientLayer.frame = self.bounds
gradientLayer.colors = [RGBA(r: 167, g: 1, b: 88, a: 1.0).cgColor, RGBA(r: 243, g: 135, b: 32, a: 1.0).cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
if gradientLayer == nil {
setup()
}
}
}
Related
I write the one custom class for gradient button, I want to apply shadow also for that.
I getting Gradient colour but I don't get the shadow.
Here I think I have issue with self.clipsToBounds = true
find my code with following.
class GradientButton: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.cornerRadius = self.frame.size.height / 2.0
gradientLayer.locations = [0.0, 1.0]
// top-right to bottom-left
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
// we only add the layer once, here when called from init
layer.addSublayer(gradientLayer)
self.clipsToBounds = true
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
applyShadow(shadoeclr: colorOne)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
func applyShadow(shadoeclr:UIColor){
layer.shadowColor = shadoeclr.cgColor
layer.shadowRadius = 5
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: 0, height: 0)
}
}
try to use masksToBounds instead of clipsToBounds
I I am trying to give the Gradient color for Button Baground, I am trying with following code
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
layer.insertSublayer(gradientLayer, at: 0)
}
#IBOutlet weak var loginbtn: UIButton!
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.blue)
I am trying to access this method it will show the output like this How to we fix this,
If I add following code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.green)
}
it will show the following error.
Make sure you are setting the gradient frame after auto-layout has set the size of the button.
If you subclass the button, you can do so in layoutSubviews().
Otherwise, A good place to do this in your view controller is in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.blue)
}
Edit
Based on the second image you posted - getting over-lapping gradients when called from viewDidLayoutSubviews()... As auto-layout "does its thing," that can be called more than once, and you are adding a new layer each time that is called.
I don't know what else you might be doing in your custom class, but give it a try like this:
class GradButton: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
gradientLayer.locations = [0.0, 1.0]
// top-right to bottom-left
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
// we only add the layer once, here when called from init
layer.addSublayer(gradientLayer)
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
class MyTest: UIViewController {
#IBOutlet var loginbtn: GradButton!
override func viewDidLoad() {
super.viewDidLoad()
loginbtn.setGradientBackground(colorOne: .red, colorTwo: .yellow)
}
}
Result:
In a UICollectionViewCell i'm adding a gradient to a UIImageView like this:
override func layoutSubviews() {
super.layoutSubviews()
if thumbnailImageView.layer.sublayers?.first == nil {
thumbnailImageView.addGradient(firstColor: .clear, secondColor: .black)
}
}
I want to do the same but in a UIViewController, so far I could only make it work on viewDidAppear
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if thumbnailImageView.layer.sublayers?.first == nil {
thumbnailImageView.addGradient(firstColor: .clear, secondColor: .black)
}
}
I want to the UIImageView to have the gradient as soon as it appears like I did in UICollectionView. Do you know how can I do this?
extension UIView{
func addGradient(firstColor: UIColor, secondColor: UIColor){
clipsToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
You can call it in viewDidLayoutSubviews
So my Class looks like this :
import UIKit
class GradientBarView: UIView {
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPointMake(1, 0.5)
gradient.colors = [AppColor.SecondaryGreen.CGColor, AppColor.HeritageGreen.CGColor, AppColor.HeritageGreen.CGColor]
layer.insertSublayer(gradient, atIndex: 0)
translatesAutoresizingMaskIntoConstraints = false
}
}
and this is how I use it(in my viewDidLoad)
let gradientBar = GradientBarView(frame: CGRectZero)
self.view.addSubview(gradientBar)
Then add the necessary constraints(which is fine)...so the problem is that the view is just black but if i add it via the Storyboard it works just fine
please help,what am i missing?
Thanks
Your frame is CGRectZero by the time you call gradient.frame = bounds in commonInit. Thus your bounds will also be CGRectZero. Thus the frame you are inserting has no height or width.
You can add the gradient in the draw method of your custom view. override func draw(_ rect: CGRect) Just ensure that you do not add it more than once.
Alternatively add it to layoutSubviews, depending on your use. Something like:
class GradientBarView: UIView {
var gradientLayer: CAGradientLayer?
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.removeFromSuperlayer()
gradientLayer = self.addHorizontalGradient(color: UIColor.blue)
}
}
extension UIView {
func addHorizontalGradient(color: UIColor) -> CAGradientLayer {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.startPoint = CGPoint(x:0,y:0.5)
gradient.endPoint = CGPoint(x:1,y:0.5)
gradient.colors = [color.cgColor, UIColor.white.withAlphaComponent(0).cgColor]
self.layer.insertSublayer(gradient, at: 0)
return gradient
}
}
I had the same issue. If I added a view to viewController at interface builder then gradient was displayed. But if I did it programmatically in this case gradient was absent.
This is the code that doesn't work properly.
import UIKit
class HeaderLandscape: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupFromNib()
setupView()
}
private func setupView() {
setGradientBackground()
}
private func setGradientBackground() {
let gradient = CAGradientLayer()
gradient.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: self.bounds.height)
gradient.colors = [UIColor(red: 0, green: 0, blue: 0, alpha: 0.32).cgColor,
UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor]
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
self.layer.insertSublayer(gradient, at: 0)
}
}
and moving setGradientBackground() function call from setupView() into hext method fixed the problem.
override func layoutSubviews() {
super.layoutSubviews()
setGradientBackground()
}
I have a tableview and it works well. But I have to add gradient on background. I did it with this code in viewDidLoad:
gradientLayer.frame = self.view.bounds
let color1 = UIColor.blueColor().CGColor as CGColorRef
let color2 = UIColor.redColor().CGColor as CGColorRef
gradientLayer.colors = [color1, color2]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 0)
self.view.layer.addSublayer(gradientLayer)
self.view.backgroundColor = UIColor.greenColor()
But now I don't see any content of my tableview.
For a background gradient to be appropriately configured you can use backgroundView property of UITableView and a View with CAGradientLayer. A resulting code (in Swift 3) will be:
/* A small UIView subclass displaying gradient */
class GradientView: UIView {
/* Overriding default layer class to use CAGradientLayer */
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
/* Handy accessor to avoid unnecessary casting */
private var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
/* Public properties to manipulate colors */
public var fromColor: UIColor = UIColor.red {
didSet {
var currentColors = gradientLayer.colors
currentColors![0] = fromColor.cgColor
gradientLayer.colors = currentColors
}
}
public var toColor: UIColor = UIColor.blue {
didSet {
var currentColors = gradientLayer.colors
currentColors![1] = toColor.cgColor
gradientLayer.colors = currentColors
}
}
/* Initializers overriding to have appropriately configured layer after creation */
override init(frame: CGRect) {
super.init(frame: frame)
gradientLayer.colors = [fromColor.cgColor, toColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
gradientLayer.colors = [fromColor.cgColor, toColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
}
}
/* Code that should be called in viewDidLoad method */
class MyViewController: UITableViewController {
/* ... */
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundView = GradientView()
}
/* ... */
}