Adding multiple shadows hides the background colour and text UIButton - swift

I was trying to add multiple shadows to my UIButton. The two shadows were added as you can see in the image. However, they hide the title and background colour of UIButton. Why is this happening? So, has the order of the layers become bottomLayer, topLayer, text and background?
The actual result
The expected Result
This is how my UIButton class looks.
class PrimaryButton: UIButton {
let cornerRadius: CGFloat = 10
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init() {
self.init(frame: .zero)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow()
}
private func configure() {
backgroundColor = .white;
layer.cornerRadius = cornerRadius
setTitle("Get Followers", for: .normal)
setTitleColor(Colours.buttonTextColour, for: .normal)
}
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
layer.addSublayer(topLayer)
layer.addSublayer(bottomLayer)
}
private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
shadowLayer.shouldRasterize = true
shadowLayer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 10).cgPath
return shadowLayer
}
}

I was able to solve the problem as follows:
Instead of adding the layers with the addSublayer(_ layer: CALayer) method, I used the insertSublayer(_ layer: CALayer, at idx: UInt32) method.
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
// Modified
layer.insertSublayer(topLayer, at: 0)
layer.insertSublayer(bottomLayer, at: 0)
}
Also, in your createShadowLayer method, instead of CALayer, I returned a CAShapeLayer.
private func createShadowLayer(color: UIColor, offset: CGSize) -> CAShapeLayer {
// Modified
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.cornerRadius).cgPath
shadowLayer.fillColor = self.backgroundColor?.cgColor
shadowLayer.shadowPath = shadowLayer.path
// shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
// shadowLayer.shouldRasterize = true
return shadowLayer
}
I don't know if either change is essential. But that's how it worked for me :)

Hello below code will help you, I got exact result what you want in button shadow
just replace some function with my code,
Your code:
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow()
}
Replace it with my code:
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow(color: UIColor.red, offset: CGSize(width: -6, height: -6), btnLayer: self.layer)
addDropShadow(color: UIColor.blue, offset: CGSize(width: 6, height: 6), btnLayer: self.layer)
}
Your code:
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
layer.addSublayer(topLayer)
layer.addSublayer(bottomLayer)
}
private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
shadowLayer.shouldRasterize = true
shadowLayer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 10).cgPath
return shadowLayer
}
Replace it with below code:
private func addDropShadow(color: UIColor, offset: CGSize, btnLayer : CALayer)
{
btnLayer.masksToBounds = false
btnLayer.shadowColor = color.cgColor
btnLayer.shadowOpacity = 1
btnLayer.shadowOffset = offset
btnLayer.shadowRadius = 10
}
no need to you private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer
you can remove that function.
and make sure your button type is custom

Related

Subclassing UITextField with other UI elements / Swift 5

Hello everyone!)
Need some help!)
I have custom text field with input limit which was in my view controller. If you look below, you will see that my text field has: UIView (underlayer with some borders), two UILabels (name label and counter label), and UITextField inside of UIView. Now I want to make UITextField subclass and configure my text field there with whole UI-es.
MARK: - I working without storyboards, the code only.
The question is, can I implement this in UITextField class?) Or maybe better to use UIView class?)
I experimented and tried to do it in TextField class, but stuck on UIView (underlayer), I can't make it behind my text field. I add a bit of code.)
Have you any ideas how to implement this in right way?)
Thanks for every answer!)
Example
Code...
UIViewController class
import UIKit
class ViewController: UIViewController {
var inputLimitTextField = InputLimitTextField(frame: CGRect(x: 45, y: 200, width: 300, height: 40))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(inputLimitTextField)
}
}
UITextField class
import UIKit
class InputLimitTextField: UITextField {
var underlayerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configureTextField()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureTextField()
}
func configureTextField() {
backgroundColor = .purple
underlayerView.backgroundColor = .red
underlayerView.alpha = 0.5
addSubview(underlayerView)
underlayerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
underlayerView.topAnchor.constraint(equalTo: self.bottomAnchor),
underlayerView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
underlayerView.centerXAnchor.constraint(equalTo: self.centerXAnchor)
])
}
override func layoutSubviews() {
super.layoutSubviews()
underlayerView.frame = self.bounds
sendSubviewToBack(underlayerView)
}
}
Considering the fact that there is still no answer to my question that would solve this issue… Also, given that using subclasses is a pretty popular practice in programming... I didn't find a specific answer to such a question on the stack. That's why I decided to answer my own question. I hope my approach to solving the problem helps someone in the future...
Code...
UIViewController class...
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
private lazy var inputLimitTextField = InputLimitTextField()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(inputLimitTextField)
inputLimitTextFieldPosition()
}
private func inputLimitTextFieldPosition() {
inputLimitTextField.center.x = self.view.center.x
inputLimitTextField.center.y = self.view.center.y - 100
}
}
UITextField class...
import UIKit
class InputLimitTextField: UITextField, UITextFieldDelegate {
private lazy var nameLabel = UILabel()
private lazy var counterLabel = UILabel()
private let textLayer = CATextLayer()
private let padding = UIEdgeInsets(top: 0.5, left: 10, bottom: 0.5, right: 17)
private let purpleUIColor = UIColor(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1.0)
private let purpleCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
components: [0.2849253164, 0.1806431101, 0.5, 1.0])
private let redUIColor = UIColor(red: 1, green: 0.1806431101, blue: 0.09760022642, alpha: 1)
private let redCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
components: [ 1, 0.1806431101, 0.09760022642, 1.0])
override init(frame: CGRect) {
super.init(frame: frame)
configureTextField()
configureNameLabel()
configureCunterLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureTextField()
configureNameLabel()
configureCunterLabel()
}
private func configureTextField() {
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width - 25
let textFieldFrame = CGRect(x: 0, y: 0, width: screenWidth, height: 40)
frame = textFieldFrame
backgroundColor = .clear
textColor = purpleUIColor
font = UIFont(name: "Helvetica", size: 17)
placeholder = "Input limit"
textAlignment = .left
contentVerticalAlignment = .center
clearButtonMode = .always
autocorrectionType = .no
keyboardType = .default
returnKeyType = .done
delegate = self
textLayer.backgroundColor = UIColor.white.cgColor
textLayer.borderColor = purpleCGColor
textLayer.borderWidth = 1.2
textLayer.cornerRadius = 10
textLayer.frame = layer.bounds
layer.insertSublayer(textLayer, at: 0)
layer.shadowColor = .init(gray: 0.5, alpha: 0.5)
layer.shadowOpacity = 0.7
layer.shadowOffset = .init(width: 2, height: 2)
addSubview(nameLabel)
nameLabel.frame = CGRect(x: 12, y: -12, width: 55, height: 16)
addSubview(counterLabel)
counterLabel.frame = CGRect(x: screenWidth - 34, y: 9, width: 22, height: 22)
}
override internal func textRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.textRect(forBounds: bounds)
return bounds.inset(by: padding)
}
override internal func editingRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.editingRect(forBounds: bounds)
return bounds.inset(by: padding)
}
override internal func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width - 25
return CGRect(x: screenWidth - 70, y: 0, width: 40, height: 40)
}
private func enableUI() {
self.textLayer.borderColor = redCGColor
self.counterLabel.layer.borderColor = redCGColor
self.counterLabel.textColor = redUIColor
self.textColor = redUIColor
self.nameLabel.layer.borderColor = redCGColor
self.nameLabel.textColor = redUIColor
}
private func disableUI() {
self.textLayer.borderColor = purpleCGColor
self.counterLabel.layer.borderColor = purpleCGColor
self.counterLabel.textColor = purpleUIColor
self.textColor = purpleUIColor
self.nameLabel.layer.borderColor = purpleCGColor
self.nameLabel.textColor = purpleUIColor
}
func firstTenCharsColor(text: String) -> NSMutableAttributedString {
let characterCount = 10
let stringLength = text.utf16.count
let attributedString = NSMutableAttributedString(string: text)
if stringLength >= characterCount {
attributedString.addAttribute(.foregroundColor, value: #colorLiteral( red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1), range: NSMakeRange(0, characterCount) )
}
return attributedString
}
private func updateUI(inputText: String?) {
guard let textCount = inputText?.count else { return }
guard let text = self.text else { return }
if (textCount <= 10){
self.counterLabel.text = "\(10 - textCount)"
disableUI()
} else if (textCount >= 10) {
self.counterLabel.text = "\(10 - textCount)"
enableUI()
self.attributedText = firstTenCharsColor(text: text)
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = self.text, let textRange = Range(range, in: text) else { return true }
let updatedText = text.replacingCharacters(in: textRange, with: string)
self.updateUI(inputText: updatedText)
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
self.textLayer.borderColor = purpleCGColor
self.counterLabel.text = "10"
disableUI()
return true
}
private func configureNameLabel() {
nameLabel.backgroundColor = .white
nameLabel.layer.cornerRadius = 3
nameLabel.layer.borderWidth = 1.2
nameLabel.layer.borderColor = purpleCGColor
nameLabel.layer.masksToBounds = true
nameLabel.font = UIFont(name: "Helvetica", size: 11)
nameLabel.text = "Input limit"
nameLabel.textAlignment = .center
nameLabel.textColor = purpleUIColor
}
private func configureCunterLabel() {
counterLabel.backgroundColor = .white
counterLabel.layer.cornerRadius = 5
counterLabel.layer.borderWidth = 1.2
counterLabel.layer.borderColor = purpleCGColor
counterLabel.layer.masksToBounds = true
counterLabel.font = UIFont(name: "Helvetica", size: 12)
counterLabel.text = "10"
counterLabel.textAlignment = .center
counterLabel.textColor = purpleUIColor
}
}
You can use it for any iPhone...
Stay safe and good luck! :)

SnapKit and custom gradient button Swift

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.

make the uibutton rounded, shadow and gradient

can someone tell me please how to make the button rounded, shadow and gradient
here I set the gradient and shadow to the button, but without rounding:
#IBOutlet weak var info: UIButton!
info.setTitle("INFO", for: .normal)
info.setTwoGradients(colorOne: Colors.OrangeGrad, colorTwo: Colors.OrangeGradSec)
info.setTitleColor(UIColor.black, for: .normal)
info.layer.shadowColor = UIColor.darkGray.cgColor
info.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
info.layer.shadowOpacity = 0.8
info.layer.shadowRadius = 2
view.addSubview(info)
I know that the shadow disappears because of the rounding and I found a way to fix it, ex:
final class CustomButton: UIButton {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 5.0, height: 2.0)
shadowLayer.shadowOpacity = 0.7
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
}
}
but there is a line:
shadowLayer.fillColor = UIColor.white.cgColor
because of which I can't set the gradient
therefore, I cannot find a way by which all three conditions would be met
Output:
Usage
class ViewController: UIViewController {
#IBOutlet var btnGradient: CustomButton!
override func viewDidLoad() {
super.viewDidLoad()
btnGradient.gradientColors = [.red, .green]
btnGradient.setTitle("Gradient Button", for: .normal)
btnGradient.setTitleColor(.white, for: .normal)
}
}
Custom Class
Use this class as a reference to setup the attributes:
class CustomButton: UIButton {
var gradientColors : [UIColor] = [] {
didSet {
setupView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let startPoint = CGPoint(x: 0, y: 0.5)
let endPoint = CGPoint(x: 1, y: 0.5)
var btnConfig = UIButton.Configuration.plain()
btnConfig.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: layer.frame.height / 2, bottom: 5, trailing: layer.frame.height / 2)
self.configuration = btnConfig
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
//Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = gradientColors.map { $0.cgColor }
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.cornerRadius = layer.frame.height / 2
if let oldValue = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldValue, with: gradientLayer)
} else {
layer.insertSublayer(gradientLayer, below: nil)
}
//Shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.frame.height / 2).cgPath
layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.shadowOpacity = 0.7
layer.shadowRadius = 2.0
}
}
You can use UIButton Extension or refer code
Pass colors in array with start & end point of gradient effect, you want to start & end. i.e. x=0, y=0 means TopLeft & x=1, y=1 means BottomRight
extension UIButton {
func setGradientLayer(colorsInOrder colors: [CGColor], startPoint sPoint: CGPoint = CGPoint(x: 0, y: 0.5), endPoint ePoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
let gLayer = CAGradientLayer()
gLayer.frame = self.bounds
gLayer.colors = colors
gLayer.startPoint = sPoint
gLayer.endPoint = ePoint
gLayer.cornerRadius = 5
gLayer.shadowOpacity = 0.8
gLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.insertSublayer(gLayer, at: 0)
}
}

Is possible make a UITextField like this?

Situation: I want to use a custom UITextField class for my textField in a xcode project.
I want to the textField look like this:
I had no problems in making the edges rounded, and change the color of my placeholder, but I have no idea how to keep the bottom edges flat and draw a black border only on the bottom.
This is my code:
import UIKit
class GrayTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .grayf1f1f1
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
layer.cornerRadius = 10
clipsToBounds = true
}
override var placeholder: String? {
didSet {
let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .thin)]
attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: attributes)
}
}
}
And my current result:
In your answer, still there is some issue in bottom left and right corner.
To achieve exact result, change your UITextField Border Style to No Border.
Padding for Text:
class GrayTextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 5)
..... Your Exact Code .....
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
}
OutPut
Well, after some researching, i do it.
Here if my final code:
import UIKit
class GrayTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .grayf1f1f1
clipsToBounds = true
let maskPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 10.0, height: 10.0))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
addBottomBorder(with: .darkGray, andWidth: 1)
}
override var placeholder: String? {
didSet {
let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .thin)]
attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: attributes)
}
}
func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
let border = UIView()
border.backgroundColor = color
border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
border.frame = CGRect(x: 0, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
addSubview(border)
}
}
And this is the result:

What is the best way to auto-resize a UIButton based on its contents, mainly its title and image?

I have written the below code in order to generate a custom UIButton, which I intend to use in different locations across my applications:
import UIKit
import ChameleonFramework
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(buttonTitleTexForNormalState titleForNormalState: String, buttonTitleTextColourForNormalState normalTextColour: UIColor, buttonTitleTextColourForHighlightedState highlightedTextColour: UIColor, buttonTitleFontType fontType: String, buttonTitleFontSize fontSize: CGFloat, buttonBackgroundHexColourCode hexColour: String, buttonFrameCornerRadius cornerRadius: CGFloat, buttonFrameBorderWidth borderWidth: CGFloat, buttonFrameBorderColour borderColour: String, buttonBackgroundTransperancyAlphaValue transperancy: CGFloat, buttonTagNumber tagValue: Int, buttonTarget: Any?, buttonSelector: Selector, buttonImageForNormalState normalButtonImage: String) {
self.init()
setupButtonEssentials(buttonTitleTexForNormalState: titleForNormalState, buttonTitleTextColourForNormalState: normalTextColour, buttonTitleTextColourForHighlightedState: highlightedTextColour, buttonTitleFontType: fontType, buttonTitleFontSize: fontSize, buttonBackgroundHexColourCode: hexColour, buttonFrameCornerRadius: cornerRadius, buttonFrameBorderWidth: borderWidth, buttonFrameBorderColour: borderColour, buttonBackgroundTransperancyAlphaValue: transperancy, buttonTagNumber: tagValue, buttonTarget: buttonTarget, buttonSelector: buttonSelector, buttonImageForNormalState: normalButtonImage)
}
func setupButtonEssentials(buttonTitleTexForNormalState titleForNormalState: String, buttonTitleTextColourForNormalState normalTextColour: UIColor, buttonTitleTextColourForHighlightedState highlightedTextColour: UIColor, buttonTitleFontType fontType: String, buttonTitleFontSize fontSize: CGFloat, buttonBackgroundHexColourCode hexColour: String, buttonFrameCornerRadius cornerRadius: CGFloat, buttonFrameBorderWidth borderWidth: CGFloat, buttonFrameBorderColour borderColour: String, buttonBackgroundTransperancyAlphaValue transperancy: CGFloat, buttonTagNumber tagValue: Int, buttonTarget: Any?, buttonSelector: Selector, buttonImageForNormalState normalButtonImage: String) {
setTitleColor(normalTextColour, for: .normal)
setTitleColor(highlightedTextColour, for: .highlighted)
titleLabel?.font = UIFont(name: fontType, size: fontSize)
setTitle(titleForNormalState, for: .normal)
backgroundColor = UIColor(hexString: hexColour)
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
layer.borderColor = UIColor(hexString: borderColour)?.cgColor
showsTouchWhenHighlighted = true
alpha = transperancy
contentHorizontalAlignment = .center
self.tag = tagValue
addTarget(target, action: buttonSelector, for: .touchUpInside)
if let buttonImage = UIImage(named: normalButtonImage) {
setImage(buttonImage.withRenderingMode(.alwaysOriginal), for: .normal)
contentMode = .scaleAspectFit
}
setShadow()
translatesAutoresizingMaskIntoConstraints = false
}
private func setShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 6.0)
layer.shadowRadius = 8
layer.shadowOpacity = 0.5
clipsToBounds = true
layer.masksToBounds = false
}
func shake() {
let shake = CABasicAnimation(keyPath: "position")
shake.duration = 0.1
shake.repeatCount = 2
shake.autoreverses = true
let fromPoint = CGPoint(x: center.x-8, y: center.y)
let fromValue = NSValue(cgPoint: fromPoint)
let toPoint = CGPoint(x: center.x+8, y: center.y)
let toValue = NSValue(cgPoint: toPoint)
shake.fromValue = fromValue
shake.toValue = toValue
layer.add(shake, forKey: "position")
}
}
And the below code illustrates an example of where I created an instance from the custom UIButton class (as demonstrated above) inside a ViewController:
import UIKit
import ChameleonFramework
class MainScreen: UIViewController, UINavigationBarDelegate {
var newFileButton = CustomButton(buttonTitleTexForNormalState: "New...", buttonTitleTextColourForNormalState: .black, buttonTitleTextColourForHighlightedState: .blue, buttonTitleFontType: "Apple SD Gothic Neo", buttonTitleFontSize: 17, buttonBackgroundHexColourCode: "#B24B32", buttonFrameCornerRadius: 25, buttonFrameBorderWidth: 2, buttonFrameBorderColour: "#7AFFD2", buttonBackgroundTransperancyAlphaValue: 0.75, buttonTagNumber: 1, buttonTarget: self, buttonSelector: #selector(buttonPressed(sender:)), buttonImageForNormalState: "New File")
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
view.addSubview(newFileButton)
newFileButton.frame.size.width = 140
newFileButton.frame.size.height = 100
}
#objc func buttonPressed(sender : UIButton) {
if sender.tag == 1 {
newFileButton.shake()
guard let nextViewControllerToGoTo = storyboard?.instantiateViewController(withIdentifier: "NewFileButtonPressedTabController") else {
print("NewFileButtonPressedTabController could not be presented")
return
}
present(nextViewControllerToGoTo, animated: true, completion: nil)
}
}
However, as it can be seen from the above code, I ended up defining the size of the custom UIButton in terms of its width and height manually.
I wonder if there is a way for Xcode to figure out the best width and height for each custom UIButton based on its contents, mainly its title and image?
P.S: I tried to use intrinsicContent, however, it did not work out for me. Any suggestions are much appreciated.
Thanks,
Shadi.
Remove the code that sets newFileButton.frame.size.width and .height, and it should take on the content size of whatever titles/images you add to it - but only at a minimum. If you want it to be any bigger than that, just set content insets (sort of like padding around the content).
In your button's class:
self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 30, bottom: 10, right: 20)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: 0)