so I am implementing a simple gradient on the lower half a UIView subclass like the following
however, every time I leave the app and reenter it, the gradient gets darker, and so forth. How do I make it so it just stays the same shade?
Here is my code for the gradient. I don't know if its correct to place it in layoutSubviews, but I couldn't get it to work just by calling the gradient code directly, such as in viewDidLoad for example:
override func layoutSubviews() {
let gradientLayer = CAGradientLayer()
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.frame = bounds
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
layer.addSublayer(gradientLayer)
}
layoutSubviews() gets called multiple times, so your gradient is getting added repeatedly.
layoutSubviews() is not really the place to be adding this gradient. The reason adding it in viewDidLoad() was not working for you is because the bounds of the view haven't been established yet. Go ahead and set up your gradient in viewDidLoad(). Keep the gradient as a property, and use viewDidLayoutSubviews() to update the frame.
class ViewController: UIViewController {
let gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
view.layer.addSublayer(gradientLayer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = view.bounds
}
}
For a view that is a subclass of UIView, you'd handle it like this:
class MyView: UIView {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
addGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addGradient()
}
func addGradient() {
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
layer.addSublayer(gradientLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
Related
I use SnapKit in my project and trying to add gradient on my button
I have extension:
extension UIButton {
public func setGradientColor(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: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
layer.insertSublayer(gradientLayer, at: 0)
}
}
i have button config where i am trying to use gradient:
private var playersButton: UIButton = {
let button = UIButton(type: .custom)
button.setGradientColor(colorOne: .red, colorTwo: .blue)
button.frame = button.layer.frame
return button
}()
and SnapKit here
playersButton.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(60)
make.trailing.equalToSuperview().inset(60)
make.bottom.equalTo(startGameButton).inset(100)
make.height.equalTo(screenHeight/12.82)
}
The problem is i have not result of it, i dont see gradientm but if i delete snapKit config and will use setGradient extension in viewDidLoad it works well!
Example:
private var playersButton: UIButton = {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
playersButton.setGradientColor(colorOne: .blue, colorTwo: .red)
}
How do i change my code to use first method? Thank you
The difference is that when using SnapKit you're using Auto-Layout, so your button's frame is not set when you call button.setGradientColor(colorOne: .red, colorTwo: .blue).
The result is that your gradient layer ends up with a frame size of Zero - so you don't see it.
You will likely find it much easier and more reliable to use a button subclass like this (simple example based on the code you posted):
class MyGradientButton: UIButton {
let gradLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
layer.insertSublayer(gradLayer, at: 0)
}
public func setGradientColor(colorOne: UIColor, colorTwo: UIColor) {
gradLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradLayer.locations = [0.0, 1.0]
gradLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradLayer.frame = bounds
}
}
Then change your button declaration to:
private var playersButton: MyGradientButton = {
let button = MyGradientButton()
button.setGradientColor(colorOne: .red, colorTwo: .blue)
return button
}()
Now, whether you set its frame explicitly, or if you use auto-layout (normal syntax or with SnapKit), the gradient layer will automatically adjust whenever the button's frame changes.
I want my swift code to draw a line like the place of a x axis. So the line is in the center of the screen no matter what screen size the object is displayed in. My code below produces a line but it does not account for the device size. My image below displays a black line which is what my current code produces and the orange line is the desired output.
Pic
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let lineView = UIView(frame: CGRect(x: 0, y: 100, width: 320, height: 1.0))
lineView.layer.borderWidth = 1.0
lineView.layer.borderColor = UIColor.black.cgColor
self.view.addSubview(lineView)
}
}
To draw a line in the middle of the screen, just assign the Line.swift file to UIView on which you want to draw a line.
Line.swift file
class Line:UIView {
var line = UIBezierPath()
func drawLine() {
line.move(to: CGPoint(x: 0, y: bounds.height / 2))
line.addLine(to: CGPoint(x: (bounds.width) , y: bounds.height / 2))
UIColor.black.setStroke()
line.lineWidth = 0.1
line.stroke()
}
override func draw(_ rect: CGRect) {
drawLine()
}
}
To assign it to your UIView, open the main.storyboard and click on the view where you want to draw the line. Then, go into upper right tabs which would look like this
You can see inside the class field, there is a placeholder called "UIView". Type there Line and hit enter, and then just run the project. You 'll be able to see the line in the middle of the view. No need to declare or call any function. Just assign this class and run the project.
If you want to respond with the width of the view, you can use constraints:
override func viewDidLoad() {
super.viewDidLoad()
let lineView = UIView()
lineView.translatesAutoresizingMaskIntoConstraints = false
lineView.layer.borderWidth = 1
lineView.layer.borderColor = UIColor.black.cgColor
view.addSubview(lineView)
NSLayoutConstraint.activate([
lineView.leftAnchor.constraint(equalTo: view.leftAnchor),
lineView.rightAnchor.constraint(equalTo: view.rightAnchor),
lineView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
lineView.heightAnchor.constraint(equalToConstant: 1)
])
}
For what it’s worth, rather than drawing lines using the border of a view, I'd define a UIBezierPath and use that for a CAShapeLayer’s path:
#IBDesignable
class GraphView: UIView {
let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 2
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
return shapeLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
layer.addSublayer(shapeLayer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX, y: 100))
path.addLine(to: CGPoint(x: bounds.maxX, y: 100))
shapeLayer.path = path.cgPath
}
}
Then you can add this view to your view hierarchy like shown above, or right in Interface Builder (obviously, defining the constraints in IB, too).
I created a custom UIView and then added it to a number of viewControllers. The view controller become glitchy and do not render in the storyboard. I just see the view controllers' outlines and no content. If I remove the custom view then all goes back to normal.
Can someone please take a look at my custom view code and see if I am doing anything wrong? Thank you so much.
import UIKit
#IBDesignable
class ProgressView: UIView {
#IBOutlet var contentView: UIView!
#IBInspectable var percentageProgress: CGFloat = 0.0 {
didSet {
self.setGradient()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("ProgressView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
setGradient()
}
private func setGradient() {
if let sublayers = self.contentView.layer.sublayers {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor(#colorLiteral(red: 0.3935321569, green: 0.3986310661, blue: 0.6819975972, alpha: 1)).cgColor, UIColor(#colorLiteral(red: 0.1705667078, green: 0.649338007, blue: 0.5616513491, alpha: 1)).cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.cornerRadius = contentView.frame.height / 2
contentView.layer.cornerRadius = contentView.frame.height / 2
self.contentView.layer.insertSublayer(gradientLayer, at: 0)
gradientLayer.frame = CGRect(x: contentView.frame.minX, y: contentView.frame.minY,
width: self.contentView.frame.maxX * (percentageProgress ?? 1.0), height:
contentView.frame.maxY)
self.contentView.setNeedsDisplay()
}
}
I am trying to create a custom UIButton that i can use throughout my application. I would like to apply a gradient as its background however the gradient is not showing up using this very simple Code.
It would be very helpful if someone could point out my mistake in the code below.
class GradientBtn: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = CGSize.zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1.0
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//rounded corners
layer.cornerRadius = frame.size.height / 2
//gradient
gradientLayer.locations = [0.0, 1.0]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.frame = bounds
layer.insertSublayer(gradientLayer, at: 0)
}
}
Replace
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
with
gradientLayer.colors = [Theme.colorlightBlue.cgColor, Theme.colorMidBlue.cgColor]
And it's always good to add
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = self.bounds
}
Sh_Khan is right about the CGColor references, but a few additional refinements:
Rather than adding the gradient as a sublayer, make it the layerClass of the UIButton. This makes animations much better (e.g. if the button changes size on device rotation).
When you do this, remove all attempts to set the frame of the gradientLayer.
In layoutSubviews, you’ll no longer need to update the gradientLayer.frame, so remove that entirely, but you do want to put your corner rounding there.
It’s extremely unlikely to ever matter, but when you set layer.cornerRadius (or any property of the view, its layer, or that layer's sublayers), you should never refer to self.frame. The frame of the view is the CGRect in the superview’s coordinate system. Only refer to the view’s bounds within the subclass; never its frame.
Thus:
class GradientBtn: UIButton {
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = .zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//gradient
gradientLayer.locations = [0, 1]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue].map { $0.cgColor }
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.height, bounds.width) / 2
}
}
Trying to show the progress bar made it custom, took an UIView set it frame to percent of progress. Say 20% of frame width and made gradient but remaining 80% should be white color and text on it defining percentage.
Problems facing is not able to display text set UILabel instead of UIView but text not displaying. Please guide.
Below is what i have tried.
let view: UILabel = UILabel(frame: CGRectMake(0.0, self.scrollMainView.frame.size.height-50, self.view.frame.size.width/5, 50))
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = view.bounds
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
let color0 = UIColor(red:71.0/255, green:198.0/255, blue:134.0/255, alpha:1.0).CGColor
let color1 = UIColor(red:25.0/255, green:190.0/255, blue: 205.0/255, alpha:1.0).CGColor
// let color2 = UIColor(red:0.0/255, green:0.0/255, blue: 0.0/255, alpha:1.0).CGColor
gradient.colors = [color1, color0]
view.layer.insertSublayer(gradient, atIndex: 0)
self.scrollMainView.addSubview(view)
view.text = "20%"
view.textColor = UIColor.blackColor()
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOpacity = 1
view.layer.shadowOffset = CGSizeZero
view.layer.shadowRadius = 2
I cant understand your problem from your question exactly but I will answer based on question title only. To get half color gradient and half single color you have to use three colors gradient and set their locations accordingly :
gradient.colors = [color1,color1, color0]
gradient.locations = [0.0, 0.5, 1.0]
This way you will draw a gradient from color1 to color1 (which in fact is single color) and fill 50% of frame's area with it and a gradient from color1 to color0 that will fill other half of the frame.
I answer to why you can't see the text.
Forget for one second layers and think about subviews. What should happen if you add a subview to a UILabel? It will be on top of the label's content, of course.
So, the same applies to layers. The UILabel draws its text on its main layer, and any sublayer you add to the main layer is on top of it.
My suggestion is to use a UIView with a CAGradientLayer sublayer and a UILabel subview.
Or even better, subclass a UIView in order to use a CAGradientLayer as backing layer (through class func layerClass() -> AnyClass method) and just add UILabel as subview.
Here is an example:
class CustomView : UIView {
lazy var label : UILabel = {
let l = UILabel(frame: self.bounds)
l.text = "20%"
l.textColor = UIColor.blackColor()
l.backgroundColor = UIColor.clearColor()
return l
}()
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
let gradient: CAGradientLayer = self.layer as! CAGradientLayer
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
let color0 = UIColor(red:71.0/255, green:198.0/255, blue:134.0/255, alpha:1.0).CGColor
let color1 = UIColor(red:25.0/255, green:190.0/255, blue: 205.0/255, alpha:1.0).CGColor
gradient.colors = [color1, color0]
label.frame = bounds
label.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(label)
}
}