Applying Gradient to button doesn't reflect in swift - ios

I have written the following code and don't know what I'm doing wrong the button is not reflecting the gradient
I also tried calling my function under awakefromNib
class ButtonGradient: UIButton {
var color1 = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
var color2 = #colorLiteral(red: 0.521568656, green: 0.1098039225, blue: 0.05098039284, alpha: 1)
var gradient = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
applyGradient()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyGradient()
}
func applyGradient() {
gradient.frame = self.bounds
gradient.colors = [color1,color2]
gradient.startPoint = CGPoint.zero
gradient.endPoint = CGPoint.init(x: 0.0, y: 0.5)
gradient.locations = [0,1]
self.layer.addSublayer(gradient)
}
}
I want to apply gradient vertically to my button.

It's pretty simple, here you are
#IBDesignable class GradientButton: UIButton {
#IBInspectable var topColor: UIColor = .white
#IBInspectable var bottomColor: UIColor = .black
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
(layer as? CAGradientLayer)?.colors = [topColor.cgColor, bottomColor.cgColor]
}
}
Just change class from UIButton to GradientButton

Related

Incomprehensible work of the sublayer for cells in the CollectionView. Swift

So, the problem is that the gradient stroke is applied in a way that is not clear to me. At the same time, the problem is only when I do the logic of clicking on the cells as a radio button. If you leave the default (multiple choice), then there is no problem. Maybe somewhere I'm missing changing the height of the view on reloading the collection. Also, if I remove the gradient and include just a stroke, then everything works well. Who has any ideas?
I add the gradient directly in the cell.
class InvestorTypeCollectionViewCell: UICollectionViewCell {
#IBOutlet private weak var cellTitleLabel: UILabel!
#IBOutlet private weak var cellDescriptionLabel: UILabel!
#IBOutlet private weak var checkMarkImageView: UIImageView!
#IBOutlet private weak var itemContainerView: UIView!
weak var delegate: InvestorTypeCollectionViewCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
checkMarkImageView.image = UIImage(named: "uncheckedBox")
// remove sublayer
itemContainerView.layer.sublayers?.filter{ $0 is CAGradientLayer }.forEach{ $0.removeFromSuperlayer() }
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func configCellWith(item: InvestorTypeModel, indexPath row: Int, selectedIndex: Int? = nil) {
itemContainerView.backgroundColor = UIColor.LIGHT_BLUE_BACKGROUND
itemContainerView.layer.cornerRadius = 8
cellTitleLabel.text = item.itemTitle.uppercased()
cellDescriptionLabel.text = Transformer.share.strippingOutHtmlFrom(text: item.itemDescription)
if item.checkBoxSelected {
diselectUISetUp()
// delegate to change item checkbox to false
delegate?.cellDidSelectedAt(indexPath: row, withCheckbox: false)
} else {
if let selectedIndex = selectedIndex {
if row == selectedIndex {
// select this cell
selectUISetUp()
// delegate to change item checkbox propertie to true
delegate?.cellDidSelectedAt(indexPath: row, withCheckbox: true)
}
}
}
}
private func diselectUISetUp() {
checkMarkImageView.image = UIImage(named: "uncheckedBox")
itemContainerView.layer.borderWidth = 0
itemContainerView.layer.borderColor = UIColor.clear.cgColor
itemContainerView.layer.cornerRadius = 8
}
private func selectUISetUp() {
checkMarkImageView.image = UIImage(named: "checkedBox")
// add gradient
let colors = [UIColor(red: 0.30, green: 0.84, blue: 0.74, alpha: 1.00),
UIColor(red: 0.44, green: 0.35, blue: 0.92, alpha: 1.00),
UIColor(red: 0.26, green: 0.20, blue: 0.87, alpha: 0.93)]
itemContainerView.gradientBorder(width: 1, colors: colors, andRoundCornersWithRadius: 8)
}
}
this is where the gradient is configured
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0),
endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0) {
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
width: bounds.size.width + width, height: bounds.size.height + width)
border.colors = colors.map { return $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
mask.path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = width
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
When the data arrives, I change the size of the collection
func refreshData() {
DispatchQueue.main.async {
self.collectionView.reloadData()
guard let dataCount = self.viewModelPresenter?.data?.count else { return }
self.heightConstraintCV.constant = 1000 + 50
}
}
You will find it much easier to use a subclassed UIView that handles its own gradient border.
For example:
#IBDesignable
class GradientBorderView: UIView {
// turns on/off the gradient border
#IBInspectable public var selected: Bool = false { didSet { setNeedsLayout() } }
// default colors
// - can be set at run-time if desired
public var colors: [UIColor] = [UIColor(red: 0.30, green: 0.84, blue: 0.74, alpha: 1.00),
UIColor(red: 0.44, green: 0.35, blue: 0.92, alpha: 1.00),
UIColor(red: 0.26, green: 0.20, blue: 0.87, alpha: 0.93)]
{
didSet {
setNeedsLayout()
}
}
// default boder line width, corner radius, and inset-from-edges
// - can be set at run-time if desired
#IBInspectable public var bWidth: CGFloat = 1 { didSet { setNeedsLayout() } }
#IBInspectable public var cRadius: CGFloat = 8 { didSet { setNeedsLayout() } }
#IBInspectable public var inset: CGFloat = 0.5 { didSet { setNeedsLayout() } }
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
override func layoutSubviews() {
super.layoutSubviews()
let shapeLayer = CAShapeLayer()
if selected {
gLayer.colors = colors.compactMap( {$0.cgColor })
// make shapeLayer path the size of view inset by "inset"
// with rounded corners
// strokes are centered, so inset must be at least 1/2 of the border width
let mInset = max(inset, bWidth * 0.5)
shapeLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: mInset, dy: mInset), cornerRadius: cRadius).cgPath
// clear fill color
shapeLayer.fillColor = UIColor.clear.cgColor
// stroke color can be any non-clear color
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = bWidth
} else {
// we'll mask with an empty path
shapeLayer.path = UIBezierPath().cgPath
}
gLayer.mask = shapeLayer
}
}
So, use a GradientBorderView instead of a UIView:
#IBOutlet private weak var itemContainerView: GradientBorderView!
and all you have to do is set its selected property to show/hide the border:
itemContainerView.selected = true
or you could set .isHidden to show/hide it.
The "gradient border" will automatically update any time the view size changes.
The view is also marked #IBDesignable with #IBInspectable properties, so you can see how it looks while designing in Storyboard (note: selected is false by default, so you won't see anything unless you change that to true).

Remove gradient from subview on rotation

I can remove the gradient from the subview fine, But If a button is added to the subview I can not remove the gradient layer. How can I Remove gradient from subview on rotation.
Here is the code for the View Controller.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var buttonOutlet: UIButton!
#IBOutlet weak var labelOutlet: UILabel!
#IBOutlet weak var mySubView: UIView!
#IBOutlet weak var mySubView2: UIView!
#IBOutlet weak var mySubView3: UIView!
override func viewDidLayoutSubviews() {
addGradient()
}
fileprivate func addGradient() {
mySubView.mainGradientBackground()
mySubView2.subGradientBackground()
mySubView3.subGradientBackground()
labelOutlet.labelTextfieldShadow()
buttonOutlet.buttonGradientBackground(cornerRadius: 10, shadowRadius: 3)
}
/// This will let you know when the device rotates.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isValidInterfaceOrientation {
removeSublayer(mySubView, layerIndex: 0)
removeSublayer(mySubView, layerIndex: 0)
removeSublayer(mySubView2, layerIndex: 0)
removeSublayer(mySubView2, layerIndex: 0)
removeSublayer(mySubView3, layerIndex: 0)
removeSublayer(mySubView3, layerIndex: 0)
removeSublayer(buttonOutlet, layerIndex: 0)
removeSublayer(buttonOutlet, layerIndex: 0)
removeSublayer(labelOutlet, layerIndex: 0)
}
}
func removeSublayer(_ view: UIView, layerIndex index: Int) {
guard let sublayers = view.layer.sublayers else {
print("The view does not have any sublayers.")
return
}
if sublayers.count > index {
view.layer.sublayers!.remove(at: index)
} else {
print("There are not enough sublayers to remove that index.")
}
}
}
This is the Gradient Extension used to color any views I need.
import Foundation
import UIKit
extension UIView {
/// Use this to set a gradient for the background.
/// = This is a gradient with no shadow.
func mainGradientBackground() {
let color = Color()
let leading = color.backgroundLeading
let trailing = color.backgroundTrailing
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [leading, trailing]
gradientLayer.locations = [0,0.3]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
layer.insertSublayer(gradientLayer, at: 0)
}
/// Use this to set a gradient for the background.
/// = This is a gradient with a shadow.
func subGradientBackground() {
let corner: CGFloat = 15
let color = Color()
let leading = color.subBackgroundLeading
let trailing = color.subBackgroundTrailing
let borderColor = color.subBorderColor
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [leading, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 4
gradientLayer.cornerRadius = corner
layer.insertSublayer(gradientLayer, at: 0)
let subLayer = CALayer()
subLayer.frame = bounds
subLayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: corner - 2).cgPath
subLayer.shadowColor = UIColor.red.cgColor
subLayer.shadowOpacity = 1
subLayer.shadowRadius = 2
subLayer.shadowOffset = CGSize(width: 3, height: 4)
layer.insertSublayer(subLayer, at: 0)
}
/// Use this to set a gradient for the background.
/// = This is a gradient with a shadow.
func subGradientBackgroundShadow() {
let corner: CGFloat = 15
let subLayer = CALayer()
subLayer.frame = bounds
subLayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: corner - 2).cgPath
subLayer.shadowColor = UIColor.red.cgColor
subLayer.shadowOpacity = 1
subLayer.shadowRadius = 2
subLayer.shadowOffset = CGSize(width: 3, height: 4)
layer.insertSublayer(subLayer, at: 0)
}
/// LabelShadow
/// - Parameter masksToBounds: Keep the layer in its bound
/// - Exp.. Set to true to keep text in textview in its frame.
/// - Exp.. Set to false to let shadow out side the frame.
/// - Defalr corner radius is 7
func labelTextfieldShadow() {
let color = Color()
let dark = color.labelBackgroundDark
let borderColor = color.labelBorderColor
layer.masksToBounds = false
layer.borderWidth = 0.5
layer.borderColor = borderColor
layer.cornerRadius = 7
self.backgroundColor = dark
layer.shadowRadius = 7
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 3, height: 3)
layer.shadowOpacity = 0.6
}
/// Button gradient
/// - Parameter cornerRadius: Set the radius of the button. Use outlet height / 2
/// - Parameter shadowRadius: Set the shadow radius. 2
func buttonGradientBackground(cornerRadius: CGFloat, shadowRadius: CGFloat) {
let color = Color()
let leading = color.buttonLeading
let middle = color.buttonMiddle
let trailing = color.buttonTrailing
let borderColor = color.buttonBorderColor
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [leading, middle, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 1
gradientLayer.cornerRadius = cornerRadius
layer.insertSublayer(gradientLayer, at: 0)
let subLayer = CALayer()
subLayer.frame = bounds
subLayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius - 2).cgPath
subLayer.shadowColor = UIColor.black.cgColor
subLayer.shadowOpacity = 1
subLayer.shadowRadius = shadowRadius
subLayer.shadowOffset = CGSize(width: 2, height: 3)
layer.insertSublayer(subLayer, at: 0)
}
}
Here is four screen shots of the screen.
This is a color struct to color all of the views.. Main view, Sub view, Labels and Buttons.
struct Color {
// Use these colors to style the main background.
/// This color is used for the main view's background leading greadient color.
let backgroundLeading = #colorLiteral(red: 0.2745098039, green: 0.231372549, blue: 0.231372549, alpha: 1).cgColor
/// TThis color is used for the main view's background trailing greadient color.
let backgroundTrailing = #colorLiteral(red: 0.2784313725, green: 0.2352941176, blue: 0.2352941176, alpha: 1).cgColor
// Use these colors to style the sub view.
/// This color is used for the sub view's leading greadient color.
let subBackgroundLeading = #colorLiteral(red: 0.1960784314, green: 0.2549019608, blue: 0.2549019608, alpha: 1).cgColor
/// This color is used for the sub view's trailing greadient color.
let subBackgroundTrailing = #colorLiteral(red: 0.1012082246, green: 0.2111029723, blue: 0.1947238818, alpha: 1).cgColor
/// This color is used for the sub view's border.
let subBorderColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1).cgColor
// Use these colors to style the labels.
/// This color is used for editing text background.
let labelBackgroundLight = #colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1) as UIColor
/// This color is used for disabled text background.
let labelBackgroundDark = #colorLiteral(red: 0.1843137255, green: 0.137254902, blue: 0.1384684741, alpha: 1) as UIColor
/// This color is used for the label's border.
let labelBorderColor = #colorLiteral(red: 0.6, green: 0.6, blue: 0.6, alpha: 1).cgColor
// Use these colors to style the buttons.
/// This color is used for the button's leading greadient color.
let buttonLeading = #colorLiteral(red: 0.1960784314, green: 0.2549019608, blue: 0.2549019608, alpha: 1).cgColor
/// This color is used for the button's middle greadient color.
let buttonMiddle = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1).cgColor
/// This color is used for the button's trailing greadient color.
let buttonTrailing = #colorLiteral(red: 0.1012082246, green: 0.2111029723, blue: 0.1947238818, alpha: 1).cgColor
/// This color is used for the button's border color.
let buttonBorderColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1).cgColor
// Use these colors for the text
/// This color is used for the deiabled label and text field.
let labelDisableColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
/// This color is used for the enabled label and text field.
let labelEnableColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
/// The text color white.
let labelWhiteTextColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
/// The text color Black
let labelBlackTextColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
}
Instead of assuming that a layer is at a given index, just retain the laters you want to remove as an instance variable (or in an array of CALayers) and then tell the layer(s) to remove themselves from their super layer.
For example:
var layersToRemoveLater = [CALayer]()
...
layersToRemoveLater.append(someLayer)
layersToRemoveLater.append(someOtherLayer)
...
layersToRemoveLater.forEach { $0.removeFromSuperLayer() }
The way to do this was to change each one of the extension to a class. With extension you have to take care of all of the layout your self. By using these as a class you can set each Button, Sub view Ect... in the storyboard, this way the view itself will redrawing the layout.
/// Use this to set a greadient for the background.
/// = This is a greadient with no shadow.
class MainGradientView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
guard let gradientLayer = self.layer as? CAGradientLayer else {
return;
}
let color = Color()
let leading = color.backgroundLeading
let trailing = color.backgroundTrailing
gradientLayer.frame = bounds
gradientLayer.colors = [leading, trailing]
gradientLayer.locations = [0,0.3]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}
/// Use this to set a greadient for the background.
/// = This is a greadient with a shadow.
class SubGradientView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
guard let gradientLayer = self.layer as? CAGradientLayer else {
return;
}
let corner: CGFloat = 15
let color = Color()
let leading = color.subBackgroundLeading
let trailing = color.subBackgroundTrailing
let borderColor = color.subBorderColor
gradientLayer.frame = self.bounds
gradientLayer.colors = [leading, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 0.4
gradientLayer.cornerRadius = corner
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 2
self.layer.shadowOffset = CGSize(width: 3, height: 4)
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}
/// Use this to set a greadient for the background.
/// = This is a greadient with no shadow.
class TabBarGradientView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
guard let gradientLayer = self.layer as? CAGradientLayer else {
return;
}
let color = Color()
let leading = color.buttonLeading
let middle = color.buttonMiddle
let trailing = color.buttonTrailing
let borderColor = color.buttonBorderColor
gradientLayer.frame = bounds
gradientLayer.colors = [leading, middle, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 1
gradientLayer.cornerRadius = self.frame.width / 50
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}
/// Use this to set a greadient for the background.
/// = This is a greadient with a shadow.
class ButtonGradientView: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
guard let gradientLayer = self.layer as? CAGradientLayer else {
return;
}
let color = Color()
let leading = color.buttonLeading
let middle = color.buttonMiddle
let trailing = color.buttonTrailing
let borderColor = color.buttonBorderColor
gradientLayer.frame = bounds
gradientLayer.colors = [leading, middle, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 1
gradientLayer.cornerRadius = self.frame.height / 2
self.layer.frame = bounds
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 2
self.layer.shadowOffset = CGSize(width: 2, height: 3)
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}
/// Use this to set a greadient for the keypad switch.
/// = This is a greadient with a shadow.
class SwitchGradientView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
guard let gradientLayer = self.layer as? CAGradientLayer else {
return;
}
let color = Color()
let leading = color.buttonLeading
let middle = color.buttonMiddle
let trailing = color.buttonTrailing
let borderColor = color.buttonBorderColor
gradientLayer.frame = bounds
gradientLayer.colors = [leading, middle, trailing]
gradientLayer.locations = [0,0.3,1]
gradientLayer.startPoint = CGPoint(x: 0.05, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5)
gradientLayer.borderColor = borderColor
gradientLayer.borderWidth = 1
gradientLayer.cornerRadius = 9
self.layer.frame = bounds
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 2
self.layer.shadowOffset = CGSize(width: 2, height: 3)
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
}

Title not showing on UIButton Gradient View

I have a gradient class attached to my UIButton and the gradient shows fine. However, the title or text of the UIButton is not showing. It seems the gradient is covering the text. Heres my code:
#IBDesignable
class GradientView: UIButton {
#IBInspectable var topColor: UIColor = #colorLiteral(red: 1, green: 0.0318463631, blue: 0, alpha: 1) {
didSet {
self.setNeedsLayout()
}
}
#IBInspectable var bottomColor: UIColor = #colorLiteral(red: 1, green: 0.4265971184, blue: 0, alpha: 1) {
didSet {
self.setNeedsLayout()
}
}
override func layoutSubviews() {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.frame = self.bounds
gradientLayer.cornerRadius = layer.cornerRadius
self.layer.insertSublayer(gradientLayer, at: 0)
}
//Rounded Button Functions
#IBInspectable var cornerRadius : CGFloat = 3.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
override func awakeFromNib() {
self.setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setupView()
}
func setupView() {
self.layer.cornerRadius = cornerRadius
}
#IBInspectable var border : CGFloat = 1.0 {
didSet {
self.layer.borderWidth = border
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue?.cgColor
}
}
}
I don't know what the problem is. I used this code for another project and it showed fine.
Thanks
You forgot to call super.layoutSubviews() in the layoutSubviews().
override func layoutSubviews() {
super.layoutSubviews()
// your code
}

xcode Interface Builder not updating IBDesignable class

In my project, I have several custom class files based on UITextField and UIButton.
As an example, the UITextField class looks like this:
import UIKit
#IBDesignable class RoundedTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 25
layer.borderWidth = 2
layer.borderColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
clipsToBounds = true
textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
// Placeholder colour
attributedPlaceholder = NSAttributedString(string: placeholder!, attributes: [NSAttributedStringKey.foregroundColor: #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)])
}
// Placeholder text indent
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}
// Editing text indent
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}
}
Here is the correct version that the simulator shows me:
SimScreenshot
Here is what IB shows me:
IBScreenshot
As you can see from my IB screenshot, the only element that updates is the indentation of the placeholder text. The rest of the customisation from the class is not updating.
Any thoughts?
P.s. when I select an element, in the identity inspector, the 'Designables' field is "Up to date".
I have tried cleaning, building and restarting Xcode.
Thanks!
Scott
I solved the issue with help from Reinier's 2nd link and a course I previously took on Udemy.
I implemented my customisation in a setupView() function. I called this in prepareForInterfaceBuilder() and also awakeFromNib()
import UIKit
#IBDesignable class RoundedTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setupView()
}
// Placeholder text indent
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}
// Editing text indent
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}
func setupView() {
layer.cornerRadius = 25
layer.borderWidth = 2
layer.borderColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
clipsToBounds = true
textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
// Placeholder colour
attributedPlaceholder = NSAttributedString(string: placeholder!, attributes: [NSAttributedStringKey.foregroundColor: #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)])
}
}
//It will update both Simulator and InterfaceBuilder
import UIKit
#IBDesignable class ViewExtended: UIView {
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
func setupView() {
layer.borderColor = borderColor.cgColor
layer.borderWidth = borderWidth
layer.cornerRadius = cornerRadius
layer.shadowOpacity = shadowOpacity
layer.shadowRadius = shadowRadius
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
#IBInspectable var borderColor: UIColor = UIColor.red {
didSet {
layer.borderColor = borderColor.cgColor
setNeedsLayout()
}
}
#IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
layer.borderWidth = borderWidth
setNeedsLayout()
}
}
#IBInspectable var cornerRadius: CGFloat = 10.0 {
didSet {
layer.cornerRadius = cornerRadius
setNeedsLayout()
}
}
#IBInspectable var shadowColor: UIColor = UIColor.black {
didSet {
layer.borderColor = borderColor.cgColor
setNeedsLayout()
}
}
#IBInspectable var shadowOpacity: Float = 0.7 {
didSet {
layer.shadowOpacity = shadowOpacity
setNeedsLayout()
}
}
#IBInspectable var shadowRadius: CGFloat = 4.0 {
didSet {
layer.shadowRadius = shadowRadius
setNeedsLayout()
}
}
#IBInspectable var shadowOffset: CGSize = CGSize(width: 3, height: 3) {
didSet {
layer.shadowOffset = shadowOffset
setNeedsLayout()
}
}
}

Properly Subclassing UITextField in swift

So i have these textfields that i realised that they all have same properties, so i created new class called "UserInputs" and extended from UITextField, everything works properly except one thing, UITextFieldDelegate functions doesn't work, i mean when i focus on them it doesn't work, i want to add it in code because when you focus on my input fields they change border, how do i properly subclass from UITextField
the only problems that i have are that functions:
textFieldDidBeginEditing
textFieldDidEndEditing
and so doesn't work.
this is my class where everything happens:
import Foundation
import UIKit
class RegistrationViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var firstName: UserInputs!
#IBOutlet weak var test: UserInputs!
override func viewDidLoad() {
super.viewDidLoad()
self.firstName.delegate = self
self.test.delegate = self
}
}
This is my subclass:
class UserInputs: UITextField{
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
createBorder()
}
func createBorder(){
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height-width, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
//print("border created")
}
func textFieldDidBeginEditing() {
print("focused")
self.pulseBorderColor()
}
func textFieldDidEndEditing() {
print("lost focus")
self.reversePulseBorderColor()
}
func pulseBorderColor(){
let pulseAnimation = CABasicAnimation(keyPath: "borderColor")
pulseAnimation.duration = 0.35
pulseAnimation.fromValue = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
pulseAnimation.toValue = UIColor(red: 252/255, green: 180/255, blue: 29/255, alpha: 1.0).CGColor
pulseAnimation.fillMode = kCAFillModeForwards
pulseAnimation.removedOnCompletion = false
pulseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
self.layer.sublayers![0].addAnimation(pulseAnimation,forKey: nil)
}
func reversePulseBorderColor(){
let pulseAnimation = CABasicAnimation(keyPath: "borderColor")
pulseAnimation.duration = 0.35
pulseAnimation.fromValue = UIColor(red: 252/255, green: 180/255, blue: 29/255, alpha: 1.0).CGColor
pulseAnimation.toValue = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
pulseAnimation.fillMode = kCAFillModeForwards
pulseAnimation.removedOnCompletion = false
pulseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
self.layer.sublayers![0].addAnimation(pulseAnimation,forKey: nil)
}
}
this code worked when i had no subclass and was doing it inside my main class, but after creating subclass focus functions stopped working, everything else works
main problem is that i want to implement
func textFieldDidBeginEditing() {
print("focused")
}
func textFieldDidEndEditing() {
print("lost focus")
}
these in my textfields so i don't write it over and over again
It looks like the UITextFieldDelegate functions you have in your code are a little off. They should be:
func textFieldDidBeginEditing(textField: UITextField) {
print("focused")
}
func textFieldDidEndEditing(textField: UITextField) {
print("lost focus")
}
And since you want the UserInputs objects to be their own delegates, I've added that code, too. To demonstrate this, I have the following two files:
ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var textField: UserInputs!
override func viewDidLoad() {
super.viewDidLoad()
textField = UserInputs(frame: CGRectMake(100, 100, 200, 40))
view.addSubview(textField!)
}
}
UserInputs.swift
import UIKit
class UserInputs: UITextField, UITextFieldDelegate {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
delegate = self
createBorder()
}
required override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
createBorder()
}
func createBorder(){
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height-width, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
//print("border created")
}
func textFieldDidBeginEditing(textField: UITextField) {
print("focused")
}
func textFieldDidEndEditing(textField: UITextField) {
print("lost focus")
}
}
Your UITextFieldDelegate should probably stay in your RegistrationViewController.
Instead of overriding the delegate you can do this.
In your init method, add a target to self with the appropriate function.
class UserInputs: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: "formatText", for: .editingChanged)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: "formatText", for: .editingChanged)
}
func formatText() {
// Edit self.text here
}
//.......//
}
You can change border by calling delegate in UserInput
class UserInputs: UITextField, UITextFieldDelegate{
let border = CALayer()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
createBorder()
self.delegate = self
}
func createBorder(){
let width = CGFloat(2.0)
border.borderColor = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
border.frame = CGRect(x: 0, y: self.frame.size.height-width, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
//print("border created")
}
func pulseBorderColor(){
border.borderColor = UIColor.greenColor().CGColor
}
func normalColor(){
border.borderColor = UIColor(red: 55/255, green: 78/255, blue: 95/255, alpha: 1.0).CGColor
}
func textFieldDidBeginEditing(textField: UITextField) {
print("focused")
pulseBorderColor()
}
func textFieldDidEndEditing(textField: UITextField) {
print("lost focus")
normalColor()
}
}

Resources