Subclassing UITextfield in Swift - ios

I am currently initializing quite a few properties in my view controller, but it just feels very, very messy. I have been creating UITextField like:
lazy var passwordTextField: UITextField = {
let textField = UITextField()
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 30))
textField.leftView = paddingView
textField.leftViewMode = .always
textField.placeholder = "Password"
textField.isSecureTextEntry = true
textField.autocorrectionType = .no
textField.autocapitalizationType = UITextAutocapitalizationType.none
textField.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
textField.tintColor = UIColor.white
textField.borderStyle = .none
textField.layer.borderColor = UIColor.white.cgColor
textField.layer.borderWidth = 0.7
textField.delegate = self
textField.textColor = UIColor.white
return textField
}()
I have about 5 of these per UIViewController. I would like to create a UITextField class that is basically a factory that pumps out custom UITextFields. How might I accomplish this?

Subclassing any item is quite easy. Just do something like
class MyTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
textFieldSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
textFieldSetup()
}
private func textFieldSetup() {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 30))
leftView = paddingView
leftViewMode = .always
...
...
//Add the common properties here that you want replicated for every instance
...
...
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 0.7
textColor = UIColor.white
}
}
Then, you can create an instance of MyTextField instead of UITextField
var passwordTextField: MyTextField!

You can subclass UITextField like,
class CustomTextField: UITextField {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.customInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.customInit()
}
func customInit(){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 30))
self.leftView = paddingView
self.leftViewMode = .always
self.placeholder = "Password"
self.isSecureTextEntry = true
self.autocorrectionType = .no
self.autocapitalizationType = .none
self.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
self.tintColor = .white
self.borderStyle = .none
self.layer.borderColor = UIColor.white.cgColor
self.layer.borderWidth = 0.7
self.textColor = .white
}
}
To use the custom label or text field,
class ViewController: UIViewController {
#IBOutlet weak var myCustomTextField: CustomTextField!
}

You can create extension for textfield like,
extension UITextField {
func setLeftPaddingPoints(_ amount:CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
}
func setRightPaddingPoints(_ amount:CGFloat) {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height))
self.rightView = paddingView
self.rightViewMode = .always
}
}

Related

Shadow and Corner Radius at the same time doesn't show in swift

I have a CameraView which has two button 1.takePictureButton 2.recordVideoButton.
I want to add corner radius and shadow in the CameraView. So I add a innerView and add it as subview of CameraView. In the innerView I set the buttons. Though it shows corner Radius but not shadow.
I tried this :
class CameraView: UIView, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .clear
self.layer.cornerRadius = 0.08 * self.bounds.size.width
self.layer.masksToBounds = true
let innerView = UIView(frame: self.bounds)
innerView.translatesAutoresizingMaskIntoConstraints = false
innerView.backgroundColor = .clear
self.addSubview(innerView)
let shadowPath = UIBezierPath(rect: innerView.frame)
innerView.layer.shadowColor = UIColor.black.cgColor
innerView.layer.shadowOpacity = 0.5
innerView.layer.shadowOffset = CGSize(width: 0, height: 0)
innerView.layer.shadowRadius = 5
innerView.layer.shadowPath = shadowPath.cgPath
innerView.clipsToBounds = true
let takePictureButton = UIButton(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height/2))
takePictureButton.backgroundColor = .white
takePictureButton.setTitle("Take Picture", for: .normal)
takePictureButton.setTitleColor(.black, for: .normal)
takePictureButton.addTarget(self, action: #selector(takePictureButtonTapped), for: .touchUpInside)
innerView.addSubview(takePictureButton)
let recordVideoButton = UIButton(frame: CGRect(x: 0, y: frame.size.height/2 + 1, width: frame.size.width, height: frame.size.height/2 - 1))
recordVideoButton.backgroundColor = .white
recordVideoButton.setTitle("Record Video", for: .normal)
recordVideoButton.addTarget(self, action: #selector(recordVideoButtonTapped), for: .touchUpInside)
innerView.addSubview(recordVideoButton)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Instead of:
innerView.clipsToBounds = true
try:
innerView.clipsToBounds = false

How to make "safe area" in UITextField with SecureTextEntry toggle?

I have a problem, I added secureTextEntry toggle in my text field, but text is on toggle button.
extension UITextField {
fileprivate func setPasswordToggleImage(_ button: UIButton) {
if(isSecureTextEntry){
button.setImage(UIImage(named: "eye-active"), for: .normal)
}else{
button.setImage(UIImage(named: "eye-inactive"), for: .normal)
}
}
func enablePasswordToggle(){
let button = UIButton(type: .custom)
setPasswordToggleImage(button)
button.frame = CGRect(x: CGFloat(self.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
button.addTarget(self, action: #selector(self.togglePasswordView), for: .touchUpInside)
self.rightView = button
self.rightViewMode = .always
}
#IBAction func togglePasswordView(_ sender: Any) {
self.isSecureTextEntry.toggle()
setPasswordToggleImage(sender as! UIButton)
}
}
Also I have a bug that places my icon in far right, instead of the place I meant and I don't know why but it's 2nd icon
You can use this directly to add image and padding to your UITextField. Can be done from code as well as storyboard
// MARK: - UITextField
extension UITextField {
#IBInspectable var leftPadding: CGFloat {
get {
return leftView?.frame.size.width ?? 0.0
}
set {
let frame = CGRect(x: 0, y: 0, width: newValue, height: bounds.size.height)
leftView = UIView(frame: frame)
leftViewMode = .always
}
}
#IBInspectable var leftImage: UIImage? {
get {
let imgView = leftView?.subviews.first(where: {$0 is UIImageView}) as? UIImageView
return imgView?.image
}
set {
let frame = CGRect(x: 0, y: 0, width: leftPadding, height: bounds.size.height)
leftView = UIView(frame: frame)
let imgView = UIImageView(frame: frame.insetBy(dx: 4, dy: 4))
imgView.contentMode = .scaleAspectFit
leftView?.addSubview(imgView)
imgView.image = newValue
leftViewMode = .always
}
}
#IBInspectable var rightPadding: CGFloat {
get {
return rightView?.frame.size.width ?? 0.0
}
set {
let frame = CGRect(x: 0, y: 0, width: newValue, height: bounds.size.height)
rightView = UIView(frame: frame)
rightViewMode = .always
}
}
#IBInspectable var rightImage: UIImage? {
get {
let imgView = rightView?.subviews.first(where: {$0 is UIImageView}) as? UIImageView
return imgView?.image
}
set {
let frame = CGRect(x: 0, y: 0, width: rightPadding, height: bounds.size.height)
rightView = UIView(frame: frame)
let imgView = UIImageView(frame: frame.insetBy(dx: 4, dy: 4))
imgView.contentMode = .scaleAspectFit
rightView?.addSubview(imgView)
imgView.image = newValue
rightViewMode = .always
}
}
}

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

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

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