How to update CustomView width with different devices - ios

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

Related

UITextField Standard Style programmatically

When I create a UITextField using storyboard, it looks like the image below. However, when I create a UITextField programmatically it has absolutely no style. I know that I can apply custom styles to the text field, but is there an easy way to get this standard style when creating the text field programmatically?
Something like this:
let t = UITextField()
t.frame = CGRect(x: 10, y: 20, width: self.view.frame.width - 20, height: 40)
t.layer.cornerRadius = 5
t.layer.borderColor = UIColor.lightGray.cgColor
t.layer.borderWidth = 1
t.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: t.frame.height))
t.leftViewMode = .always
t.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: t.frame.height))
t.rightViewMode = .always
t.clearButtonMode = .whileEditing
self.view.addSubview(t)
EDIT:
Add this class below somewhere such that it is easily / globally accessible.
class StyledTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1
self.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: t.frame.height))
self.leftViewMode = .always
self.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: t.frame.height))
self.rightViewMode = .always
self.clearButtonMode = .whileEditing
}
}
Then you can call this UITextField from where ever you want as follows
let t = StyledTextField()
t.frame = CGRect(x: 10, y: 20, width: self.view.frame.width - 20, height: 40)
self.view.addSubview(t)
EDIT 2:
Use UIEdgeInsets to get padding on all four sides.
class StyledTextField: UITextField {
let insetConstant = UIEdgeInsets(top: 4, left: 10, bottom: 4, right: 10)
override func textRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, insetConstant)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, insetConstant)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, insetConstant)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor(white: 2/3, alpha: 0.5).cgColor
self.layer.borderWidth = 1
self.clearButtonMode = .whileEditing
self.keyboardType = UIKeyboardType.decimalPad
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can add borderStyle as .roundedRect and use White background. Something like this:
class MyCustmUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.borderStyle = .roundedRect
self.backgroundColor = .white
}
}
You can create a subclass from UITextField to serve as your custom text field.
Following this, you need only to instantiate your custom class and then you're good to go.
Something like this:
class MyCustmUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.black
self.font = UIFont.systemFont(ofSize: 20)
self.adjustsFontSizeToFitWidth = true
// then add whatever styles you wish
}
}
From there all you need is to create a new instance of MyCustomUITextField

Add icon or image in UITextField on left / right in Swift 4

I have set up the left icon in UITextField. When I set text, it is over the left icon. I want to set text after the icon in the UITextField. I have used below code.
let imageView = UIImageView(image: UIImage(named: strImgname))
imageView.frame = CGRect(x: 0, y: 0, width: imageView.image!.size.width , height: imageView.image!.size.height)
let paddingView: UIView = UIView.init(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
paddingView.addSubview(imageView)
txtField.leftViewMode = .always
txtField.leftView = paddingView
You can use #IBDesignable to make a designable UITextField with these capabilities and even more and use it through out your project.
DesignableTextField with Delegate methods when icon in UITextField is tapped:
import Foundation
import UIKit
protocol DesignableTextFieldDelegate: UITextFieldDelegate {
func textFieldIconClicked(btn:UIButton)
}
#IBDesignable
class DesignableTextField: UITextField {
//Delegate when image/icon is tapped.
private var myDelegate: DesignableTextFieldDelegate? {
get { return delegate as? DesignableTextFieldDelegate }
}
#objc func buttonClicked(btn: UIButton){
self.myDelegate?.textFieldIconClicked(btn: btn)
}
//Padding images on left
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.leftViewRect(forBounds: bounds)
textRect.origin.x += padding
return textRect
}
//Padding images on Right
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.rightViewRect(forBounds: bounds)
textRect.origin.x -= padding
return textRect
}
#IBInspectable var padding: CGFloat = 0
#IBInspectable var leadingImage: UIImage? { didSet { updateView() }}
#IBInspectable var color: UIColor = UIColor.lightGray { didSet { updateView() }}
#IBInspectable var imageColor: UIColor = UIColor.init(hex: "3EB2FF") { didSet { updateView() }}
#IBInspectable var rtl: Bool = false { didSet { updateView() }}
func updateView() {
rightViewMode = UITextFieldViewMode.never
rightView = nil
leftViewMode = UITextFieldViewMode.never
leftView = nil
if let image = leadingImage {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
let tintedImage = image.withRenderingMode(.alwaysTemplate)
button.setImage(tintedImage, for: .normal)
button.tintColor = imageColor
button.setTitleColor(UIColor.clear, for: .normal)
button.addTarget(self, action: #selector(buttonClicked(btn:)), for: UIControlEvents.touchDown)
button.isUserInteractionEnabled = true
if rtl {
rightViewMode = UITextFieldViewMode.always
rightView = button
} else {
leftViewMode = UITextFieldViewMode.always
leftView = button
}
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedStringKey.foregroundColor: color])
}
}
Now its Designable in the Storyboard as follows:
NOTE: Rtl when set to Off icon will move to left of UITextField
Conforming the Delegate in desired ViewController.
class MyViewController: UIViewController, DesignableTextFieldDelegate {
#IBOutlet weak var txtFieldSomeSearch: DesignableTextField!
txtFieldSomeSearch.delegate = self // can be done in storyboard as well
... // other codes
func textFieldIconClicked(btn: UIButton) {
print("MyViewController : textFieldIconClicked")
}
... // other codes
}
Finally Output :
Using the extension in Swift4, We can easily put the image on the right or on the left with padding to TextField.
extension UITextField {
//MARK:- Set Image on the right of text fields
func setupRightImage(imageName:String){
let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
imageView.image = UIImage(named: imageName)
let imageContainerView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 55, height: 40))
imageContainerView.addSubview(imageView)
rightView = imageContainerView
rightViewMode = .always
self.tintColor = .lightGray
}
//MARK:- Set Image on left of text fields
func setupLeftImage(imageName:String){
let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
imageView.image = UIImage(named: imageName)
let imageContainerView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 55, height: 40))
imageContainerView.addSubview(imageView)
leftView = imageContainerView
leftViewMode = .always
self.tintColor = .lightGray
}
}
Use code as for right image setup:-
self.password_text_field.setupRightImage(imageName: "unlock")
Output :)
please use the below code
//Left side icon
textField.leftViewMode = UITextFieldViewMode.Always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
let image = UIImage(named: imageName)
imageView.image = image
textField.leftView = imageView
//Right side icon
textField.rightViewMode = UITextFieldViewMode.Always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
let image = UIImage(named: imageName)
imageView.image = image
textField.rightView = imageView
Extension to UITextField in Swift 5. This allows you to check if the image is a system image, otherwise, you use a custom image. You can also set which side of the text field you want the image on using the TextFieldImageSide enumeration.
enum TextFieldImageSide {
case left
case right
}
extension UITextField {
func setUpImage(imageName: String, on side: TextFieldImageSide) {
let imageView = UIImageView(frame: CGRect(x: 10, y: 5, width: 30, height: 30))
if let imageWithSystemName = UIImage(systemName: imageName) {
imageView.image = imageWithSystemName
} else {
imageView.image = UIImage(named: imageName)
}
let imageContainerView = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 40))
imageContainerView.addSubview(imageView)
switch side {
case .left:
leftView = imageContainerView
leftViewMode = .always
case .right:
rightView = imageContainerView
rightViewMode = .always
}
}
}
Usage:
searchBar.setUpImage(imageName: "mappin.and.ellipse", on: .left)
Produces the following:

Duplicate UILabel inside UIButton

Inside willWillLayoutSubviews() I call UIButton's method setTileTheme(), which I created. Result can be seen below - duplicate UILabel appears under another one. I have already tried calling my method from viewDidLoad() etc., but it didn't help.
Do someone know why I am facing this problem?
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
let titleLabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: width, height: 30))
titleLabel.center = CGPoint(x: width/2, y: height-offset)
titleLabel.text = title
titleLabel.font = titleLabel.font.withSize(15)
titleLabel.textAlignment = .center
titleLabel.textColor = .darkGray
self.insertSubview(titleLabel, at: 0)
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
UIButton already has titleLabel and imageView. What you are doing is you are creating a new label and adding it to buttons view which will not replace the default label.
All that you need is
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRectCGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: 30)
}
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
self.titleLabel?.text = title
self.titleLabel?.font = titleLabel.font.withSize(15)
self.titleLabel?.textAlignment = .center
self.titleLabel?.textColor = .darkGray
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
I believe you were trying to set the button's title label frame which you can easily set to the default title label of button itself by overriding titleRect
EDIT:
I can see that you are trying to set the inset to Button's image as well. Adding inset to imageView will simply move the image inside imageView but the imageView frame remains the same. Rather in case you wanna affect the imageView's frame itself then you can simply override
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
//whatever calculation you wanna provide
//for example
return CGRect(x: (self.bounds.size.width/2 + 5), y: self.bounds.size.height/2, width: (self.bounds.size.width/2 - 5), height: self.bounds.size.height)
}
Hope it helps

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

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...

Add subviews on Customize UIView works not as expect

I subclass a class of UIView called:AURTabView. Then I drag 3 views(white background) to storyboard to auto layout them with 1/3 screen width of each. Then i add a UIButton on the init method, it works well so far as below:
Then i want to add another two UIViews on to them. The weird thing is the first and third AUTRabView works as expect, but the middle one doesn't.
This is really weird. I checked UIView hierarchy like below:
Any point?
Here is the code:
class AURTabView: UIView {
let tabButton = UIButton()
let smallCircle = UIView()
let largeCircle = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(tabButton)
self.addSubview(smallCircle)
self.addSubview(largeCircle)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = self.frame.height
tabButton.frame = CGRect(x: (self.frame.width-height)/2, y: 0, width: height, height: height)
tabButton.backgroundColor = UIColor.greenColor()
smallCircle.frame = CGRect(x: CGRectGetMidX(self.frame)-2.5, y: height-10-8, width: 5, height: 5)
smallCircle.backgroundColor = UIColor.redColor()
largeCircle.frame = CGRect(x: CGRectGetMidX(self.frame)-5, y: height-8, width: 10, height: 10)
largeCircle.backgroundColor = UIColor.redColor()
print(smallCircle)
print(largeCircle)
}
override func drawRect(rect: CGRect) {
tabButton.layer.cornerRadius = tabButton.frame.width/2
}
}
Use this
import UIKit
class AURTabView: UIView {
let tabButton = UIButton()
let smallCircle = UIView()
let largeCircle = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(tabButton)
self.addSubview(smallCircle)
self.addSubview(largeCircle)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = self.frame.height
tabButton.frame = CGRect(x: (self.frame.width-height)/2, y: 0, width: height, height: height)
tabButton.backgroundColor = UIColor.greenColor()
smallCircle.frame = CGRect(x: self.frame.width/2 - 2.5, y: height-10-8, width: 5, height: 5)
smallCircle.backgroundColor = UIColor.blackColor()
largeCircle.frame = CGRect(x: self.frame.width/2 - 5, y: height-8, width: 10, height: 10)
largeCircle.backgroundColor = UIColor.redColor()
print(smallCircle)
print(largeCircle)
}
override func drawRect(rect: CGRect) {
tabButton.layer.cornerRadius = tabButton.frame.width/2
}
}

Resources