Gradient view not working when added programmatically - ios

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()
}

Related

Apply Shadow along with the gradient colour with custom class

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

Horizontal Gradient colour Showing Half only in Swift?

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:

Gradient addition in UITableviewCell causing animation issue on reloading

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()
}
}
}

Custom UIButton subclass

I'm trying my own custom UIButton. I subclassed the UIButton class and tried to add some functions this is my code:
import UIKit
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
setGradientBackground()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
private func setup() {
layer.cornerRadius = 8
}
private func setGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.blue.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
}
I linked it to my IBOutlet :
#IBOutlet weak var myButton: CustomButton!
And nothing apply !
If you're adding the button in a storyboard, the initialiser that's called is init?(coder aDecoder: NSCoder), not init(frame: CGRect), so you need to add…
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
setGradientBackground()
}
Use func awakeFromNib() :
class CustomButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
setup()
setGradientBackground()
}
private func setup() {
layer.cornerRadius = 8
}
private func setGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.blue.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
}

UITextField: Bottom Border

I am creating bottom bordered textfield. I am subclass of UITextField. Here it is:
#IBDesignable class LinedTextField: UITextField {
#IBInspectable var borderColor: UIColor = UIColor.whiteColor() {
didSet {
let border = CALayer()
border.borderColor = self.borderColor.CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height - borderWidth, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
#IBInspectable var borderWidth: CGFloat = 0.5 {
didSet {
let border = CALayer()
border.borderColor = self.borderColor.CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height - borderWidth, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
override init(frame : CGRect) {
super.init(frame : frame)
setup()
}
convenience init() {
self.init(frame:CGRectZero)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
func setup() {
let border = CALayer()
border.borderColor = self.borderColor.CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height - borderWidth, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
}
Then in interface builder I set that 2 properties(border colour and border width) and everything looks good:
But when I run application on my real 5.5" device, it looks this way:
Border is not long as the text field. What's wrong here?
I think it's because the border frame need to be computed on layoutSubviews. Here is an example on how to do so (plus a simplification of your code):
Supported in Swift 3
#IBDesignable class UnderlinedTextField: UITextField {
let border = CALayer()
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
setup()
}
}
#IBInspectable var borderWidth: CGFloat = 0.5 {
didSet {
setup()
}
}
override init(frame : CGRect) {
super.init(frame : frame)
setup()
}
convenience init() {
self.init(frame:CGRect.zero)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
func setup() {
border.borderColor = self.borderColor.cgColor
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
border.frame = CGRect(x: 0, y: self.frame.size.height - borderWidth, width: self.frame.size.width, height: self.frame.size.height)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return editingRect(forBounds: bounds)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return editingRect(forBounds: bounds)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 10, dy: 0)
}
}
EDIT:
I Added code for the text area. In this example, there is a 20px horizontal inset. There in more infos on editingRectForBounds (and its friends) in the Apple's doc: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextField_Class/#//apple_ref/occ/instm/UITextField/textRectForBounds:
import UIKit
#IBDesignable
class MyTextField: UITextField {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView(){
self.borderStyle = UITextBorderStyle.None
let border = CALayer()
let borderWidth: CGFloat = 2
border.borderColor = UIColor(red: 5/255, green: 84/255, blue: 115/255, alpha: 1.0).CGColor
border.frame = CGRectMake(0, self.frame.size.height - borderWidth, self.frame.size.width, self.frame.size.height)
border.borderWidth = borderWidth
self.layer.addSublayer(border)
}
}
let border = CALayer()
border.borderColor = UIColor.blackColor().CGColor
border.frame = CGRectMake(-30, textField.frame.size.height - 1.0, textField.frame.size.width*1.5 , 1.0)
border.borderWidth = 1.0
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true

Resources