Custom iOS UIButton Sub-Class Not Updating Title - ios

I have a subclass of a UIButton that takes the title label, and puts it under the button's image, as opposed to the right of the image:
final class ImageButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 8
#IBInspectable var borderColor: UIColor? = .black
private enum Constants {
static let imageSize: CGFloat = 40
static let titleHeight: CGFloat = 12
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if #available(iOS 15, *) {
return super.titleRect(forContentRect: contentRect)
}
else {
_ = super.titleRect(forContentRect: contentRect)
return CGRect(
x: 0,
y: contentRect.height - Constants.titleHeight,
width: contentRect.width,
height: Constants.titleHeight
)
}
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
if #available(iOS 15, *) {
return super.imageRect(forContentRect: contentRect)
} else {
return CGRect(
x: contentRect.width / 2 - Constants.imageSize / 2,
y: (contentRect.height - titleRect(forContentRect: contentRect).height) / 2 - Constants.imageSize / 2,
width: Constants.imageSize,
height: Constants.imageSize
)
}
}
override var intrinsicContentSize: CGSize {
if #available(iOS 15, *) {
return super.intrinsicContentSize
}
else {
_ = super.intrinsicContentSize
let size = titleLabel?.sizeThatFits(contentRect(forBounds: bounds).size) ?? .zero
let spacing: CGFloat = 12
return CGSize(
width: max(size.width, Constants.imageSize),
height: Constants.imageSize + Constants.titleHeight + spacing
)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
if #available(iOS 15, *) {
var myConfiguration = UIButton.Configuration.plain()
myConfiguration.imagePlacement = .top
self.configuration = myConfiguration
} else {
titleLabel?.textAlignment = .center
}
}
override func draw(_ rect: CGRect) {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = borderColor?.cgColor
}
}
Trying to change the button's title does not have any effect:
myCustomButton.setTitle("Disable Box Select", for: .normal)
I tried adding:
myCustomButton.layer.setNeedsLayout()
myCustomButton.layer.setNeedsDisplay()
But, nothing seems to change the title of myCustomButton

This is not really an answer, but in response to the OP's comment...
Storyboard:
Complete controller code (using your posted ImageButton class, unedited):
class CustBtnVC: UIViewController {
#IBOutlet var myCustomButton: ImageButton!
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 15.0, *) {
var cfg = myCustomButton.configuration
cfg?.title = "Test"
myCustomButton.configuration = cfg
} else {
// Fallback on earlier versions
myCustomButton.setTitle("Pre-15 Test", for: .normal)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if #available(iOS 15.0, *) {
var cfg = myCustomButton.configuration
cfg?.title = "Disable Box Select"
myCustomButton.configuration = cfg
} else {
// Fallback on earlier versions
myCustomButton.setTitle("Pre-15 Disable Box Select", for: .normal)
}
}
}
Note: since your custom button code is setting the button style for iOS 15+ the button .configuration should be used instead of .setTitle(...) when running on iOS 15+
On launch:
after tap on the view:

Related

Round Corners in custom UICollectionViewCell not working in Swift

I have custom UICollectionViewCell and try to round corners for button, which doesn't work.
I had the same problem for ViewController and the problem was that I was doing rounding in viewDidLoad instead of subviewsDidLoad.
I had no idea what is a problem now.
sharing my code.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
initialsButton.layer.cornerRadius = 0.5 * initialsButton.frame.size.width
initialsButton.clipsToBounds = true
initialsButton.layer.masksToBounds = true
}
-> but I also tried without .clipsToBounds and .masksToBounds. Same result.
Here is the result. It is not a circle, it makes corner wrongly
SEE THIS RESULT
The problem was that I was doing rounding corners in awakeFromNib(), instead you MUST do it in layoutSubviews() and it works nicely.
I suppose you are using IBOutlet's
Create a class something like this, add button to your cell in storyboard, edit there how ever you want, it easier. Your code up is not good, just remove size, frame.height / 2 is what you want if you are going that way.
#IBDesignable
class RoundedBtn: UIButton {
#IBInspectable var cornerRadius: Double {
get {
return Double(self.layer.cornerRadius)
}set {
self.layer.cornerRadius = CGFloat(newValue)
}
}
#IBInspectable var borderWidth: Double {
get {
return Double(self.layer.borderWidth)
}
set {
self.layer.borderWidth = CGFloat(newValue)
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: self.layer.borderColor!)
}
set {
self.layer.borderColor = newValue?.cgColor
}
}
#IBInspectable var shadowColor: UIColor? {
get {
return UIColor(cgColor: self.layer.shadowColor!)
}
set {
self.layer.shadowColor = newValue?.cgColor
}
}
#IBInspectable var shadowOffSet: CGSize {
get {
return self.layer.shadowOffset
}
set {
self.layer.shadowOffset = newValue
}
}
#IBInspectable var shadowRadius: Double {
get {
return Double(self.layer.shadowRadius)
}set {
self.layer.shadowRadius = CGFloat(newValue)
}
}
#IBInspectable var shadowOpacity: Float {
get {
return self.layer.shadowOpacity
}
set {
self.layer.shadowOpacity = newValue
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = .center
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .horizontal)
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}

Swift to SwiftUI migration issue

So I am not fully understanding how this needs to be implemented in SwiftUI for it to work properly.
I have the following code:
class RulerView: UIView {
// MARK: - Constants
private struct Constants {
static let labelWidth: CGFloat = 100
static let labelHeight: CGFloat = 20
static let labelMarginTop: CGFloat = 5
static let rulerHeight: CGFloat = 100
}
// MARK: - Properties
override var tintColor: UIColor! {
didSet { _updateStyle().setNeedsDisplay() }
}
internal let unit: Length.Unit
internal let length: CGFloat
private var lineWidth: CGFloat
private var replicatorLayer: CAReplicatorLayer?
private var labels: [UILabel]?
private var lengthUnitWidth: CGFloat {
switch unit {
case .centimeter:
return Length.pixels(fromCentimeter: 1.0)
case .inch:
return Length.pixels(fromInch: 1.0)
}
}
// MARK: - View Life Cycle
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/*
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
*/
init(unit: Length.Unit, length: CGFloat, lineWidth: CGFloat) {
self.unit = unit
self.length = length
self.lineWidth = lineWidth
super.init(frame: .zero)
let rulerWidth = lengthUnitWidth * length
frame = .init(x: 0, y: 0, width: rulerWidth, height: Constants.rulerHeight)
_setupLabels()
}
override func didMoveToWindow() {
super.didMoveToWindow()
_updateStyle()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
replicatorLayer?.removeFromSuperlayer()
replicatorLayer = CAReplicatorLayer()
if let replicatorLayer = replicatorLayer {
replicatorLayer.instanceCount = Int(ceil(length))
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(lengthUnitWidth, 0, 0)
let unitLayer = LengthUnitLayer(unit: unit, lineWidth: lineWidth, lineColor: tintColor.cgColor, height: frame.height)
unitLayer.frame = CGRect(x: -lineWidth / 2, y: 0, width: unitLayer.bounds.width, height: (bounds.height - Constants.labelHeight - Constants.labelMarginTop))
unitLayer.setNeedsDisplay()
replicatorLayer.addSublayer(unitLayer)
layer.addSublayer(replicatorLayer)
}
labels?.enumerated().forEach { (offset, element) in
element.frame = .init(x: (CGFloat(offset) * lengthUnitWidth - Constants.labelWidth / 2),
y: (bounds.height - Constants.labelHeight),
width: Constants.labelWidth,
height: Constants.labelHeight)
}
}
// MARK: - Setup
#discardableResult
private func _setupLabels() -> Self {
labels?.forEach { $0.removeFromSuperview() }
labels = [UILabel]()
for i in 0...Int(ceil(length)) {
let label = UILabel()
label.text = "\(i)"
label.textAlignment = .center
addSubview(label)
labels?.append(label)
}
return self
}
// MARK: - Layout
#discardableResult
private func _updateStyle() -> Self {
labels?.forEach { $0.textColor = tintColor }
return self
}
}
And I am trying to call it in the ContentView as such:
var viewSize = UIScreen.main.bounds.size
var body: some View {
RulerView(coder: viewSize)
}
But it is not working? Does anyone know how I can resolve this?
The UIView needs to be wrapped into UIViewRepresentable to be used in SwiftUI, like below.
public struct RulerViewRep: UIViewRepresentable {
public func makeUIView(context: Context) -> RulerView {
return RulerView()
}
public func updateUIView(_ uiView: RulerView, context: Context) {
}
}
and then use it
var body: some View {
// by default consumes all space, so UIScreen.main.bounds not needed
RulerView()
}

How to marquee UITextField placeholder if text longer then the UITextField width?

Is it possible to marquee UITextFiled placeholder, if placeholder text is longer then the size of UITextField width, There are lib for UILabel MarqueeLabel but I am not sure how to marquee placeholder, please provide some suggestion, Or you can explain what is a placeholder is actually, it doesn't look like UILabel
I am using bellow code for CustomTextField with validation error message
import UIKit
#IBDesignable
class CustomTextField: UITextField {
var placeholdertext: String?
#IBInspectable
public var cornerRadius :CGFloat {
set { layer.cornerRadius = newValue }
get {
return layer.cornerRadius
}
}
// Provides left padding for images
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.leftViewRect(forBounds: bounds)
textRect.origin.x += leftPadding
return textRect
}
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.rightViewRect(forBounds: bounds)
textRect.origin.x -= rightPadding
return textRect
}
#IBInspectable var leftImage: UIImage? {
didSet {
updateView()
}
}
#IBInspectable var isUnderLine:Bool = false
{
didSet{
updateView()
}
}
#IBInspectable var rightImage: UIImage? {
didSet {
updateView()
}
}
func setError(error:String?){
if(error != nil)
{
self.attributedPlaceholder = NSAttributedString(string: error!, attributes: [NSForegroundColorAttributeName: UIColor.red])
}
}
#IBInspectable var leftPadding: CGFloat = 0
#IBInspectable var rightPadding: CGFloat = 0
#IBInspectable var textLeftPadding:CGFloat = 0
#IBInspectable var color: UIColor = UIColor.lightGray {
didSet {
updateView()
}
}
#IBInspectable var underlineColor:UIColor = UIColor.black
{
didSet{
self.updateView()
}
}
private var placeholderColorValue:UIColor = UIColor.lightGray
#IBInspectable public var placeholderColor:UIColor
{
set{
self.attributedPlaceholder = NSAttributedString(string:placeholder!, attributes: [NSForegroundColorAttributeName: newValue])
placeholderColorValue = newValue
}
get{
return placeholderColorValue
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
func setUpView() {
if(rightImage != nil)
{
self.leftViewMode = UITextFieldViewMode.always
let rightImageView:UIImageView = UIImageView(image: rightImage)
rightImageView.frame = CGRect(x: 0, y: 0, width: self.frame.size.height, height: self.frame.size.height)
self.rightView = rightImageView
}
else {
rightViewMode = UITextFieldViewMode.never
rightView = nil
}
}
func updateView() {
if let imageLeft = leftImage {
leftViewMode = UITextFieldViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.image = imageLeft
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
leftView = imageView
} else {
leftViewMode = UITextFieldViewMode.never
leftView = nil
}
if let imageRight = rightImage {
rightViewMode = UITextFieldViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.image = imageRight
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
rightView = imageView
} else {
rightViewMode = UITextFieldViewMode.never
rightView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSForegroundColorAttributeName: color])
if(self.isUnderLine)
{
let underline:UIView = UIView()
underline.frame = CGRect(x: 0, y: self.frame.size.height-1, width: self.frame.size.width, height: 1)
underline.backgroundColor = underlineColor
self.addSubview(underline)
}
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: textLeftPadding, y: 0, width: bounds.width, height: bounds.height)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.textRect(forBounds: bounds)
}
}
and using validation as
if (txtField.text != "condtion" ){
txtField.setError(error:"Error message for text field");
valid = false;
txtField.becomeFirstResponder()
}

UITextView IBDesignable Padding

How do I create an IBDesignable UITextView such that I can adjust the insets of the text in interface builder? I've added inspectable properties topInset, bottomInset, etc. but now I'm having trouble figure out how to actually update the insets of the UITextView such that the changes are reflected in IB
import UIKit
private let kPlaceholderTextViewInsetSpan: CGFloat = 8
#IBDesignable class UIDesignableTextView: UITextView {
// variables
#IBInspectable var topInset: CGFloat = 0.0
#IBInspectable var leftInset: CGFloat = 0.0
#IBInspectable var bottomInset: CGFloat = 0.0
#IBInspectable var rightInset: CGFloat = 0.0
var insets: UIEdgeInsets {
get {
return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
}
set {
topInset = newValue.top
leftInset = newValue.left
bottomInset = newValue.bottom
rightInset = newValue.right
}
}
#IBInspectable var placeholder: NSString? { didSet { setNeedsDisplay() } }
#IBInspectable var placeholderColor: UIColor = UIColor.lightGray
override var text: String! { didSet { setNeedsDisplay() } }
override var attributedText: NSAttributedString! { didSet { setNeedsDisplay() } }
override var contentInset: UIEdgeInsets { didSet { setNeedsDisplay() } }
override var font: UIFont? { didSet { setNeedsDisplay() } }
override var textAlignment: NSTextAlignment { didSet { setNeedsDisplay() } }
// MARK: - Lifecycle
/** Override coder init, for IB/XIB compatibility */
#if !TARGET_INTERFACE_BUILDER
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
listenForTextChangedNotifications()
}
/** Override common init, for manual allocation */
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
listenForTextChangedNotifications()
}
#endif
/** Initializes the placeholder text view, waiting for a notification of text changed */
func listenForTextChangedNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(UIDesignableTextView.textChangedForPlaceholderTextView(_:)), name:NSNotification.Name.UITextViewTextDidChange , object: self)
NotificationCenter.default.addObserver(self, selector: #selector(UIDesignableTextView.textChangedForPlaceholderTextView(_:)), name:NSNotification.Name.UITextViewTextDidBeginEditing , object: self)
}
/** willMoveToWindow will get called with a nil argument when the window is about to dissapear */
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
if newWindow == nil { NotificationCenter.default.removeObserver(self) }
else { listenForTextChangedNotifications() }
}
func textChangedForPlaceholderTextView(_ notification: Notification) {
setNeedsDisplay()
setNeedsLayout()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if text.characters.count == 0 && self.placeholder != nil {
let baseRect = placeholderBoundsContainedIn(self.bounds)
let font = self.font ?? self.typingAttributes[NSFontAttributeName] as? UIFont ?? UIFont.systemFont(ofSize: UIFont.systemFontSize)
self.placeholderColor.set()
var customParagraphStyle: NSMutableParagraphStyle!
if let defaultParagraphStyle = typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
customParagraphStyle = defaultParagraphStyle.mutableCopy() as! NSMutableParagraphStyle
} else { customParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle }
// set attributes
customParagraphStyle.lineBreakMode = NSLineBreakMode.byTruncatingTail
customParagraphStyle.alignment = self.textAlignment
let attributes = [NSFontAttributeName: font, NSParagraphStyleAttributeName: customParagraphStyle.copy() as! NSParagraphStyle, NSForegroundColorAttributeName: self.placeholderColor]
// draw in rect.
self.placeholder?.draw(in: baseRect, withAttributes: attributes)
}
}
func placeholderBoundsContainedIn(_ containerBounds: CGRect) -> CGRect {
// get the base rect with content insets.
let baseRect = UIEdgeInsetsInsetRect(containerBounds, UIEdgeInsetsMake(kPlaceholderTextViewInsetSpan, kPlaceholderTextViewInsetSpan/2.0, 0, 0))
// adjust typing and selection attributes
if let paragraphStyle = typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
baseRect.offsetBy(dx: paragraphStyle.headIndent, dy: paragraphStyle.firstLineHeadIndent)
}
return baseRect
}
This is all you need to do:
import UIKit
#IBDesignable class TextViewWithInsets: UITextView {
#IBInspectable var topInset: CGFloat = 0 {
didSet {
self.contentInset = UIEdgeInsetsMake(topInset, self.contentInset.left, self.contentInset.bottom, self.contentInset.right)
}
}
#IBInspectable var bottmInset: CGFloat = 0 {
didSet {
self.contentInset = UIEdgeInsetsMake(self.contentInset.top, self.contentInset.left, bottmInset, self.contentInset.right)
}
}
#IBInspectable var leftInset: CGFloat = 0 {
didSet {
self.contentInset = UIEdgeInsetsMake(self.contentInset.top, leftInset, self.contentInset.bottom, self.contentInset.right)
}
}
#IBInspectable var rightInset: CGFloat = 0 {
didSet {
self.contentInset = UIEdgeInsetsMake(self.contentInset.top, self.contentInset.left, self.contentInset.bottom, rightInset)
}
}
}
As you can see, these are properties of the TextViewWithInsets subclass of UITextView I have created. You need to override the didSet portion of the property method. Then, in Interface Builder these four properties (Top Inset, Bottom Inset, Left Inset, and Right Inset) will appear in the Attributes Inspector: attributes inspector in IB for new class
Just make sure that in the Identity Inspector you set the TextView object in the storyboard to be TextViewWithInsets or whatever you choose to name it like this: Set class of Text View object in storyboard to custom class
Swift 5
If you want to use it for all UITextViews in the project use the following:
import UIKit
#IBDesignable extension UITextView {
#IBInspectable var topPadding: CGFloat {
get {
return contentInset.top
}
set {
self.contentInset = UIEdgeInsets(top: newValue,
left: self.contentInset.left,
bottom: self.contentInset.bottom,
right: self.contentInset.right)
}
}
#IBInspectable var bottomPadding: CGFloat {
get {
return contentInset.bottom
}
set {
self.contentInset = UIEdgeInsets(top: self.contentInset.top,
left: self.contentInset.left,
bottom: newValue,
right: self.contentInset.right)
}
}
#IBInspectable var leftPadding: CGFloat {
get {
return contentInset.left
}
set {
self.contentInset = UIEdgeInsets(top: self.contentInset.top,
left: newValue,
bottom: self.contentInset.bottom,
right: self.contentInset.right)
}
}
#IBInspectable var rightPadding: CGFloat {
get {
return contentInset.right
}
set {
self.contentInset = UIEdgeInsets(top: self.contentInset.top,
left: self.contentInset.left,
bottom: self.contentInset.bottom,
right: newValue)
}
}
}

Setting the frame of a sublayer inside of layoutSubviews() makes changing it impossible. Any workaround for that?

I'm working on a customizable UITextField (see code below). I have added a border at the bottom (you can set it in the storyboard). However, I had problems setting the frame of the CALayer that this border consists of.
If I set it inside the didSet method of var showBottomBorder it doesn't appear on the screen. I think this is because the frame (of the UITextField) hasn't been calculated yet (maybe didSet gets called before that).
So I moved it to the layoutSubviews() method (see code below). This works perfectly.
But now I have another problem. I can't really change that frame anymore. Every time I change it, it gets reset by layoutSubviews() which I think is called then.
At the bottom of my code, there is the method textFieldDidBeginEditing. In there, I wanted to move up my bottom border (animated). But it doesn't work. The border does not move anywhere. And like I said, I think it's because I set the frame inside the layoutSubviews() method.
Is there a better way to set the frame of the bottom border? A way which allows me to change stuff?
#IBDesignable
class CustomizableTextField: UITextField, UITextFieldDelegate {
// MARK: - Properties
private var bottomBorder = CALayer()
// MARK: - #IBInspectables
#IBInspectable var roundCorners: CGFloat = 0 {
didSet {
self.layer.cornerRadius = roundCorners
self.clipsToBounds = true
}
}
/** -- */
#IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
self.layer.borderWidth = self.borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
self.layer.borderColor = self.borderColor.cgColor
}
}
/** -- */
/** -- */
private var showBottomBorder: Bool = false {
didSet {
switch showBottomBorder {
case true:
bottomBorder.borderColor = self.bottomBorderColor.cgColor
bottomBorder.borderWidth = self.bottomBorderWidth
self.layer.addSublayer(bottomBorder)
self.layer.masksToBounds = true
break
case false:
bottomBorder.removeFromSuperlayer()
break
}
}
}
#IBInspectable var bottomBorderWidth: CGFloat = 1.0 {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
#IBInspectable var bottomBorderColor: UIColor = UIColor.white {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
/** -- */
/** -- */
// Somwhow, the default panel for my font color doesn't change anything, so I created this
#IBInspectable var fixedFontColor: UIColor = UIColor.white {
didSet {
self.textColor = fixedFontColor
}
}
#IBInspectable var placeholderFontColor: UIColor = UIColor.white {
didSet {
var placeholderTxt = ""
if let txt = self.placeholder {
placeholderTxt = txt
}
self.attributedPlaceholder = NSAttributedString(string: placeholderTxt, attributes: [NSForegroundColorAttributeName: placeholderFontColor])
}
}
/** -- */
// MARK: - Overrides and Initializers
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
// HERE
bottomBorder.frame = CGRect(x: 0, y: self.frame.size.height - self.bottomBorderWidth, width: self.frame.size.width, height: self.frame.size.height)
}
// setting the textField delegate to self
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//self.borderStyle = .none
self.delegate = self
}
// MARK: - Events
func textFieldDidBeginEditing(_ textField: UITextField) {
}
You can use and extension on UITextFiled for setting the border.
And keep a reference to it with KVC.
By overriding LayoutSubview, every the layout will change, we'l check if the border exists, if so remove it, and re-create a new one with the new frame:
import UIKit
let MyTopBorder = "myTopBorder"
let MyBottomBorder = "myBottomBorder"
struct Defaults {
static let width = CGFloat(1.0)
static func bottonBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: view.frame.size.height - Defaults.width, width: view.frame.size.width, height: view.frame.size.height)
}
static func topBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: CGFloat(0) , width: view.frame.size.width, height: Defaults.width)
}
}
extension UITextField
{
func setBottomBorder(color:CGColor)
{
if let isBottomBorder = self.getBottomBorderIfExists() {
isBottomBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.bottonBorderFrame(self), color: color, andKey: MyBottomBorder)
}
func setTopBorder(color:CGColor)
{
if let isTopBorder = self.getTopBorderIfExists() {
isTopBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.topBorderFrame(self), color: color, andKey: MyTopBorder)
}
func setBorderWithFrame(frame: CGRect, color: CGColor, andKey: String) {
self.borderStyle = UITextBorderStyle.None;
let border = CALayer()
border.borderColor = color
border.frame = frame
border.borderWidth = Defaults.width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
self.layer.setValue(border, forKey: andKey)
}
func removeTopBorder() {
if let isTopBorder = self.getTopBorderIfExists() {
self.layer.setValue(nil, forKey: MyTopBorder)
isTopBorder.removeFromSuperlayer()
}
}
func removeBottomBorder() {
if let isBottomBorder = self.getBottomBorderIfExists() {
self.layer.setValue(nil, forKey: MyBottomBorder)
isBottomBorder.removeFromSuperlayer()
}
}
private func getBorderIfExistsByKey(key: String)->CALayer? {
if let isBorderSet = self.layer.valueForKey(key) {
if let borderIsCALayer = isBorderSet as? CALayer {
return borderIsCALayer
}
}
return nil
}
private func getTopBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyTopBorder)
}
private func getBottomBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyBottomBorder)
}
public override func layoutSubviews() {
super.layoutSubviews()
// Update bottom on frame change
if let isBottomBorder = self.getBottomBorderIfExists() {
let borderColor = isBottomBorder .borderColor
self.removeBottomBorder()
self.setBottomBorder(borderColor!)
}
// Update top on frame change
if let isTopBorder = self.getTopBorderIfExists() {
let borderColor = isTopBorder.borderColor
self.removeTopBorder()
self.setTopBorder(borderColor!)
}
}
}
Usage:
let textField = UITextField(frame: CGRect(x: 100,y: 100, width: 100, height: 100))
textField.backgroundColor = UIColor.blueColor() // Thie color is for visulizing better
self.view.addSubview(textField)
textField.setBottomBorder(UIColor.blackColor().CGColor) // Now you have a border
textField.frame = CGRect(x: 150, y: 200, width: 200, height: 200) // And the border updated to the new frame
// Now if you would like to change from bottom to top, simply do this:
textField.removeBottomBorder()
textField.setTopBorder(UIColor.blackColor().CGColor)

Resources