single border for UITextField within a UIcollectionViewCell (swift 3 xcode) - ios

I'm trying to apply a single bottom border to a textField that sits within one of my collectionViewCells. Here is the code:
class AddressCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func basicTextField(placeHolderString: String) -> UITextField {
let textField = UITextField()
textField.font = UIFont.boldSystemFont(ofSize: 12)
textField.attributedPlaceholder = NSAttributedString(string: placeHolderString, attributes:[NSForegroundColorAttributeName: UIColor.lightGray, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 12)])
textField.backgroundColor = UIColor.white
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = UIColor.white
layer.addBorder(edge: UIRectEdge.bottom, color: .black, thickness: 0.5)
let streetTextfield = basicTextField(placeHolderString: "street")
addSubview(streetTextfield)
}
}
I am using an extension that enables me to apply a single border, which has worked great so far:
extension CALayer {
func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {
let border = CALayer()
switch edge {
case UIRectEdge.top:
border.frame = CGRect.init(x: 0, y: 0, width: frame.width, height: thickness)
break
case UIRectEdge.bottom:
border.frame = CGRect.init(x: 0, y: frame.height - thickness, width: frame.width, height: thickness)
break
case UIRectEdge.left:
border.frame = CGRect.init(x: 0, y: 0, width: thickness, height: frame.height)
break
case UIRectEdge.right:
border.frame = CGRect.init(x: frame.width - thickness, y: 0, width: thickness, height: frame.height)
break
default:
break
}
border.backgroundColor = color.cgColor;
self.addSublayer(border)
}
}
When i simply add a borderWidth to the textfield like this:
textField.layer.borderWidth = 0.5
I get a border and it renders fine. However, when i apply the extension to add a bottom border, like this:
textField.layer.addBorder(edge: UIRectEdge.bottom, color: .black, thickness: 0.5)
the border doesn't apply for some reason.

I'm not sure about the extension, but I have a working solution for a TextField with a bottom border.
basically create a class BottomBorderedTextField subclassing UITextField and insert the following code:
class BottomBorderedTextField: UITextField {
override func draw(_ rect: CGRect) {
super.draw(rect)
//this is the color of the bottom border. Change to whatever you what
let color: UIColor = UIColor.rgb(red: 230, green: 230, blue: 230)
let bottomBorder = CALayer()
bottomBorder.frame = CGRect(x: 0, y: bounds.size.height - 1, width: bounds.size.width, height: 2)
bottomBorder.backgroundColor = color.cgColor
self.layer.addSublayer(bottomBorder)
}
}
Then on your basicTextField function set the return type to the BottomBorderedTextField class you just made. so your new code will look like:
func basicTextField(placeHolderString: String) -> BottomBorderedTextField {
let textField = BottomBorderedTextField()
textField.font = UIFont.boldSystemFont(ofSize: 12)
textField.attributedPlaceholder = NSAttributedString(string: placeHolderString, attributes:[NSForegroundColorAttributeName: UIColor.lightGray, NSFontAttributeName: UIFont.boldSystemFont(ofSize: 12)])
textField.backgroundColor = UIColor.white
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}

I wrote a UIView extension that accomplishes the same thing:
extension UIView {
func addBottomBorder(width: CGFloat, color: UIColor, alpha: CGFloat) {
let border = CALayer()
let width = width
border.borderColor = color.withAlphaComponent(alpha).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
}
}
Usage:
usernameField.addBottomBorder(width: 2.0, color: UIColor.white, alpha: 0.5)
Also, seems within setupViews() you're calling:
layer.addBorder... //this is being called on the collection view cells layer
As opposed to:
streetTextfield.layer.addBorder...

Related

Multiple shadows under UIView iOS Swift

I am trying to make a UIButton with rounded corners that has 2 colored shadows. Why is the red (and at this point also the blue "shadow" layer covering the button? How to get the shadows below the button canvas). I thought it was helping to insert sublayers instead of just adding them.
I have made a playground illustrating the issue
import UIKit
import PlaygroundSupport
This is the button I'm trying to implement
class PrimaryButton: UIButton {
required init(text: String = "Test 1", hasShadow: Bool = true) {
super.init(frame: .zero)
setTitle(text, for: .normal)
backgroundColor = UIColor.blue
layer.cornerRadius = 48 / 2
layer.masksToBounds = false
if hasShadow {
insertShadow()
}
}
fileprivate func insertShadow() {
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer)
layer2.applySketchShadow(color: UIColor.red, alpha: 0.5, x: 0, y: 15, blur: 35, spread: -10)
layer3.applySketchShadow(color: UIColor.blue, alpha: 0.5, x: 0, y: 10, blur: 21, spread: -9)
layer.insertSublayer(layer2, at: 0)
layer.insertSublayer(layer3, at: 0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { (sublayer) in
sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
}
This is an extension that helps adding the shadow from Sketch specification:
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
masksToBounds = false
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let button = PrimaryButton()
button.frame = CGRect(x: 150, y: 200, width: 200, height: 48)
view.addSubview(button)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
It seems legit to me. layer1 & layer2 are sublayers of the button layer.
You could add a third layer that will serve as a background. Here is an example based on your code:
class PrimaryButton: UIButton {
let layer1 = CALayer(), layer2 = CALayer(), layer3 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
layer1.backgroundColor = UIColor.blue.cgColor
layer1.cornerRadius = 48 / 2
[layer1, layer2, layer3].forEach {
$0.masksToBounds = false
$0.frame = layer.bounds
layer.insertSublayer($0, at: 0)
}
layer2.applySketchShadow(color: UIColor.red, alpha: 0.5, x: 0, y: 15, blur: 35, spread: -10)
layer3.applySketchShadow(color: UIColor.blue, alpha: 0.5, x: 0, y: 10, blur: 21, spread: -9)
}
}
Note that I put most of the code inside layoutSubviews because most of your methods use the actual bounds of the button.
Change your insertions to:
layer.insertSublayer(layer2, at: 1)
layer.insertSublayer(layer3, at: 2)
That should do it.
Another way is to add double buttons without change your class.
let button = PrimaryButton()
button.frame = CGRect(x: 150, y: 200, width: 200, height: 48)
button.backgroundColor = UIColor.clear
view.addSubview(button)
self.view = view
let button1 = PrimaryButton()
button1.frame = CGRect(x: 0, y: 0, width: 200, height: 48)
button.addSubview(button1)
button1.layer.sublayers?.forEach{$0.removeFromSuperlayer()}

Border width and placeholder in custom UITextField

I want to make this TextField
So I write this class
class UnderLineTextField:UITextField{
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkgray().cgColor
border.frame = CGRect(x: 0, y: frame.size.height - width, width: frame.size.width, height: frame.size.height)
textColor = UIColor.gold()
backgroundColor = UIColor.clear
border.borderWidth = width
layer.addSublayer(border)
layer.masksToBounds = true
attributedPlaceholder = NSAttributedString(string: "",
attributes: [NSAttributedStringKey.foregroundColor: UIColor.darkgray()])}
}
But there are two problems
The underline is not equal to TextField, if I add more width width: frame.size.width + 500, the problem can be fixed, but why? what is the correct value I should pass to width:?
attributedPlaceholder will replace the placeholder I set in xib, how can I change placeholder without adding empty string?
The underline is not equal to TextField, if I add more width width: frame.size.width + 500, the problem can be fixed, but why? what is the correct value I should pass to width:?
You should put your draw underline code to draw(_ rect: CGRect) instead of init. Because of you using autolayout, so the width, that you got in init is difference with the width that you can see when run app.
attributedPlaceholder will replace the placeholder I set in xib, how can I change placeholder without adding empty string?
You can redraw the attributedPlaceholder base on your placeholder in draw(_ rect: CGRect), too
attributedPlaceholder = NSAttributedString(
string: self.placeholder,
attributes: [NSAttributedStringKey.foregroundColor: UIColor.darkgray()]
)
It seems your code is ok.
use below code for setting placeholder:
class UnderLineTextField:UITextField{
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var placeHolderText: String = "default place holder" {
didSet {
attributedPlaceholder = NSAttributedString(string: placeHolderText,
attributes: [NSAttributedStringKey.foregroundColor: UIColor.darkGray])}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: frame.size.height - width, width: frame.size.width, height: frame.size.height)
textColor = UIColor.lightGray
backgroundColor = UIColor.clear
border.borderWidth = width
layer.addSublayer(border)
layer.masksToBounds = true
attributedPlaceholder = NSAttributedString(string: placeHolderText,
attributes: [NSAttributedStringKey.foregroundColor: UIColor.darkGray])}
}
and set your placeholder in storyboard:
The result will look like below:
For UITextField underline use this one...
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.gray.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
Hey already lot of answers :), let's see if this detailed answer can help you achieve more:
I prefer using extensions over inheritance if possible.
extension UITextField {
func setTextColor(_ color: UIColor, font: UIFont) {
self.textColor = color
self.font = font
}
func setBottomBorder(with color: UIColor, width: CGFloat) {
self.borderStyle = .none
self.layer.backgroundColor = UIColor.white.cgColor // bg color of your choice
self.layer.masksToBounds = false
self.layer.shadowColor = color.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: width)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 0.0
}
func setPlaceHolderAttributes(placeHolderText : String, colour : UIColor , font : UIFont){
self.attributedPlaceholder = NSAttributedString(string:placeHolderText, attributes:[NSAttributedStringKey.foregroundColor: colour , NSAttributedStringKey.font : font])
}
}
//MARK: To give text insets
class MyTextField: UITextField {
override func textRect(forBounds bounds: CGRect) -> CGRect {
return CGRect.init(x:10, y:2, width:bounds.width-20, height:bounds.height - 4)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
}
And I prefer to configure UI from Code, it will fasten up your coding speed and achieve doing project wise change from one place, so you can do it from code like:
let font: UIFont = UIFont.systemFont(ofSize: 14.0)
let placeHolderTextColor: UIColor = UIColor.brown
let textColor: UIColor = UIColor.darkText
nameTextField.setBottomBorder(with: UIColor.darkGray, width: 1.0)
passwordTextField.setBottomBorder(with: UIColor.darkGray, width: 1.0)
nameTextField.setPlaceHolderAttributes(placeHolderText: "Name", colour: placeHolderTextColor, font: font)
nameTextField.setTextColor(textColor, font: font)
passwordTextField.setPlaceHolderAttributes(placeHolderText: "Password", colour: placeHolderTextColor, font: font)
passwordTextField.setTextColor(textColor, font: font)
Hope this helps :)

How to update CustomView width with different devices

I have a custom Button with 2 Labels and Image as shown in below class
import UIKit
#IBDesignable
class CustomButton : UIButton {
let secondLine : UILabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
// self.layer.cornerRadius = 10
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.layer.shadowOpacity = 1.0
self.layer.masksToBounds = false
}
#IBInspectable var rightLebelText : String?{
didSet {
updateView()
}
}
func updateView(){
if let mytext = rightLebelText {
let firstLine = UILabel(frame: CGRect(x: self.bounds.size.width - 210, y: 0, width: 200, height: 40))
firstLine.text = mytext
firstLine.textAlignment = .right
firstLine.font = UIFont.systemFont(ofSize: 20)
self.addSubview(firstLine)
var imageView : UIImageView
imageView = UIImageView(frame:CGRect(x: 5, y: 10, width: 20, height: 20))
imageView.image = UIImage(named:"arrow.png")
self.addSubview(imageView)
}
}
public func setSecondlabel(title : String){
secondLine.removeFromSuperview()
secondLine.frame = CGRect(x: 50, y: 0, width: 200, height: 40)
secondLine.text = title
secondLine.font = UIFont.boldSystemFont(ofSize: 20)
secondLine.removeFromSuperview()
self.addSubview(secondLine)
}
}
My issue is my view size is not updating on different devices when using
self.bounds.size.width
for the firstLine label as shown in below image its position should be on the custom button right edge
You need to override the layoutSubviews function have the frame of each element examine and update based on updated bounds of the custom view or assign proper layout constraints on each element while adding it.
If you are overriding the UIButton which has already a label and image property, you can use that one as well or create a custom class inherited from UIControl and create required three property as needed. I am adding an example of the custom class with image, title, and detail as shown in the problem.
class CustomButton : UIControl {
let imageView : UIImageView
let titleLabel : UILabel
let detailLabel : UILabel
fileprivate func setup() {
self.detailLabel.textAlignment = .right
self.detailLabel.font = UIFont.systemFont(ofSize: 20)
self.detailLabel.textColor = UIColor.black
self.titleLabel.font = UIFont.boldSystemFont(ofSize: 20)
self.titleLabel.textColor = UIColor.black
self.backgroundColor = UIColor.white
self.addSubview(self.imageView)
self.addSubview(self.titleLabel)
self.addSubview(self.detailLabel)
}
override init(frame: CGRect) {
self.imageView = UIImageView(frame: .zero)
self.titleLabel = UILabel(frame: .zero)
self.detailLabel = UILabel(frame: .zero)
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.imageView = UIImageView(frame: .zero)
self.titleLabel = UILabel(frame: .zero)
self.detailLabel = UILabel(frame: .zero)
super.init(coder: aDecoder)
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageView.frame = CGRect(x: 5.0, y: self.bounds.midY - 10.0, width: 20.0, height: 20.0)
//You can make this width dynamic if you want to calculate width of text using self.detailLabel.text
var width : CGFloat = 200.0
self.titleLabel.frame = CGRect(x: self.imageView.frame.maxX + 5.0, y: self.bounds.minY, width: 200.0, height: self.bounds.height)
//Give the remaining space to the second label
width = self.bounds.width - (self.titleLabel.frame.maxX + 15.0)
self.detailLabel.frame = CGRect(x: self.titleLabel.frame.maxX + 5.0, y: self.bounds.minY, width: width, height: self.bounds.height)
}
}

change bottom line border of uitextfield swift

I'm trying to change border properties with the following code:
class SimpleTextField: UITextField, UITextFieldDelegate {
override func layoutSubviews() {
super.layoutSubviews()
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.black.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
self.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.green.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
func textFieldDidEndEditing(_ textField: UITextField) {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.black.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
}
I wrote code in layoutSubviews() function as a default appearance for textField. I used textFieldDidBeginEditing() and textFieldDidEndEditing() functions for changing bottom border color. If I use the code layoutSubviews() the border color won't be changed at all but if I remove the code that I use as default appearance, border color will change but I don't have default border as the first time when text field is appeared.
Don't create new layer every time you start/end editing the text field. Instead, reuse it: make it a property and then just update when needed accordingly.
The default appearance you can set at the same point where the text field itself and the border are created, e.g.:
let textField = UITextField(frame: CGRect(x: 100.0, y: 100.0, width: 200.0, height: 50.0))
let border = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textField)
textField.layer.addSublayer(border)
textField.delegate = self
updateBorder(textField: textField, color: UIColor.black, width: 2.0)
}
func updateBorder(textField: UITextField, color: UIColor, width: CGFloat) -> Void {
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0.0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: width);
}
func textFieldDidBeginEditing(_ textField: UITextField) {
updateBorder(textField: textField, color: UIColor.green, width: 2.0)
}
func textFieldDidEndEditing(_ textField: UITextField) {
updateBorder(textField: textField, color: UIColor.black, width: 2.0)
}

iOS UITextField Underline Style in swift

I've added this image, I hope you can see it, of a user interface login. Notice the text field is transparent with the exception of the line at the bottom. What code do I put in to get that affect? Can I put the necessary information in the "user defined runtime attributes"?
Create a subclass of UITextField as below, And simply set this class in your storyboard to UITextField
Swift 5 Support With #IBInspectable
import UIKit
class HSUnderLineTextField: UITextField , UITextFieldDelegate {
let border = CALayer()
#IBInspectable open var lineColor : UIColor = UIColor.black {
didSet{
border.borderColor = lineColor.cgColor
}
}
#IBInspectable open var selectedLineColor : UIColor = UIColor.black {
didSet{
}
}
#IBInspectable open var lineHeight : CGFloat = CGFloat(1.0) {
didSet{
border.frame = CGRect(x: 0, y: self.frame.size.height - lineHeight, width: self.frame.size.width, height: self.frame.size.height)
}
}
required init?(coder aDecoder: (NSCoder?)) {
super.init(coder: aDecoder!)
self.delegate=self;
border.borderColor = lineColor.cgColor
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
border.frame = CGRect(x: 0, y: self.frame.size.height - lineHeight, width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = lineHeight
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
override func draw(_ rect: CGRect) {
border.frame = CGRect(x: 0, y: self.frame.size.height - lineHeight, width: self.frame.size.width, height: self.frame.size.height)
}
override func awakeFromNib() {
super.awakeFromNib()
border.frame = CGRect(x: 0, y: self.frame.size.height - lineHeight, width: self.frame.size.width, height: self.frame.size.height)
self.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
border.borderColor = selectedLineColor.cgColor
}
func textFieldDidEndEditing(_ textField: UITextField) {
border.borderColor = lineColor.cgColor
}
}
Set lineColor and selectedLineColor from the storyboard and run your project.

Resources