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)
}
}
}
Related
Labels in iOS are create like (1), no horizontal margin and no beauty at all.
I would like to create a label like in (2), curved edges and a margin left and right
The contents of this label is updated 2 times per second and its width must change dynamically.
So I have created this class
#IBDesignable
class BeautifulLabel : UILabel {
// private var internalRect : CGRect? = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: marginTop,
left: marginLeft,
bottom: marginBottom,
right: marginRight)
super.drawText(in: rect.inset(by: insets))
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var marginTop: CGFloat = 0
#IBInspectable var marginBottom: CGFloat = 0
#IBInspectable var marginLeft: CGFloat = 0
#IBInspectable var marginRight: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
var bounds = self.bounds
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
This works but adjusting self.bounds inside layoutSubviews(), makes this method to be called again, resulting in a huge loop, CPU spike and memory leak.
Then I tried this:
override var text: String? {
didSet {
let resizingLabel = UILabel(frame: self.bounds)
resizingLabel.text = self.text
var bounds = resizingLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 500, height: 50), limitedToNumberOfLines: 1)
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
}
this simply does not work. Label is not adjusted to the proper size.
The label must have just one line, fixed height, truncated tail and fixed font size (System 17). I am interested in its width.
Any ideas?
A view should not change its own size. It should only change its intrinsicContentSize.
When you add a view to the view hierarchy, that’s when you specify whether it should observe the intrinsic content size or not (e.g. content hugging settings, compression resistance, absence of explicit width and height constraints, etc.). If you do this, the auto layout engine will do everything for you.
So, by way of example, a minimalist approach would be something that just overrides intrinsicContentSize:
#IBDesignable
class BeautifulLabel: UILabel {
#IBInspectable var marginX: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
#IBInspectable var marginY: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
#IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + marginX * 2, height: size.height + marginY * 2)
}
}
A more complete example might be a UIView subclass, where the label is a subview, inset by the appropriate margins:
#IBDesignable
class BeautifulLabel: UIView {
#IBInspectable var marginTop: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginBottom: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginLeft: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginRight: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var cornerRadius: CGFloat = -1 { didSet { setNeedsLayout() } }
#IBInspectable var text: String? {
get {
label.text
}
set {
label.text = newValue
invalidateIntrinsicContentSize()
}
}
#IBInspectable var font: UIFont? {
get {
label.font
}
set {
label.font = newValue
invalidateIntrinsicContentSize()
}
}
private var topConstraint: NSLayoutConstraint!
private var leftConstraint: NSLayoutConstraint!
private var rightConstraint: NSLayoutConstraint!
private var bottomConstraint: NSLayoutConstraint!
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override var intrinsicContentSize: CGSize {
let size = label.intrinsicContentSize
return CGSize(width: size.width + marginLeft + marginRight,
height: size.height + marginTop + marginBottom)
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let maxCornerRadius = min(bounds.width, bounds.height) / 2
if cornerRadius < 0 || cornerRadius > maxCornerRadius {
layer.cornerRadius = maxCornerRadius
} else {
layer.cornerRadius = cornerRadius
}
}
}
private extension BeautifulLabel {
func configure() {
addSubview(label)
topConstraint = label.topAnchor.constraint(equalTo: topAnchor, constant: marginTop)
leftConstraint = label.leftAnchor.constraint(equalTo: leftAnchor, constant: marginLeft)
rightConstraint = rightAnchor.constraint(equalTo: label.rightAnchor, constant: marginRight)
bottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: marginBottom)
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
func didUpdateInsets() {
topConstraint.constant = marginTop
leftConstraint.constant = marginLeft
rightConstraint.constant = marginRight
bottomConstraint.constant = marginBottom
invalidateIntrinsicContentSize()
}
}
Now in this case, I'm only exposing text and font, but you'd obviously repeat for whatever other properties you want to expose.
But let’s not get lost in the details of the above implementation. The bottom line is that a view should not attempt to adjust its own size, but rather merely its own intrinsicContentSize. And it should perform invalidateIntrinsicContentSize where necessary.
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()
}
I'm a beginner of Swift 3 and I plan to write several Custom Control (IBDesignable) in XCode 8. When I write a custom text field, I want to add right placeholder to it. However, the right placeholder would only display successfully when this text field has no constraint. I don't know what happened to it.
I hope someone can help me fix this bug, thanks a lot.
import UIKit
#IBDesignable
class RMLDesignableUITextField: UITextField {
// MARK: PROPERTIES
#IBInspectable var insetX: CGFloat = 0
#IBInspectable var insetY: CGFloat = 0
#IBInspectable var placeholderColor: UIColor = UIColor.white {
didSet {
if let placeholder = self.placeholder {
let attributes = [NSForegroundColorAttributeName: placeholderColor]
attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes)
}
}
}
// MARK: Border
var bottomBorder = CALayer()
var rightBorder = CALayer()
var topBorder = CALayer()
var leftBorder = CALayer()
#IBInspectable var showsTopBorder: Bool = false {
didSet {
setupSubviews()
}
}
#IBInspectable var showsBottomBorder: Bool = false {
didSet {
setupSubviews()
}
}
#IBInspectable var showsLeftBorder: Bool = false {
didSet {
setupSubviews()
}
}
#IBInspectable var showsRightBorder: Bool = false {
didSet {
setupSubviews()
}
}
#IBInspectable var borderColor: UIColor = UIColor.clear
#IBInspectable var borderWidth: CGFloat = 0.0 {
didSet {
self.setNeedsDisplay()
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
#IBInspectable var rightPlaceholder: String = "" {
didSet {
rightPlaceholderLabel.text = rightPlaceholder
}
}
fileprivate var fakePlaceholderLabel: UILabel!
fileprivate var rightPlaceholderLabel: UILabel!
fileprivate var translateX: CGFloat!
{
get {
let attributes = [NSFontAttributeName: font!]
let rightPlaceholderTextSize = rightPlaceholderLabel.text!.size(attributes: attributes)
let rightPlaceholderTextWidth = rightPlaceholderTextSize.width
let translateX = textRect(forBounds: bounds).width - rightPlaceholderTextWidth
return translateX
}
}
// MARK: Initializers
override init(frame: CGRect) {
super.init(frame: frame)
// self.setNeedsDisplay()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// self.setNeedsDisplay()
fakePlaceholderLabel = UILabel(frame: placeholderRect(forBounds: bounds))
fakePlaceholderLabel.font = font
fakePlaceholderLabel.text = placeholder
fakePlaceholderLabel.textColor = UIColor.lightGray
fakePlaceholderLabel.alpha = 0.0
rightPlaceholderLabel = UILabel(frame: placeholderRect(forBounds: bounds))
rightPlaceholderLabel.font = font
rightPlaceholderLabel.text = rightPlaceholder
rightPlaceholderLabel.textColor = UIColor.lightGray
rightPlaceholderLabel.alpha = 0.0
}
override func layoutSubviews() {
super.layoutSubviews()
addSubview(fakePlaceholderLabel)
addSubview(rightPlaceholderLabel)
setupSubviews()
setNeedsDisplay()
}
}
// MARK: - Lifecycle
extension RMLDesignableUITextField {
override func awakeFromNib() {
super.awakeFromNib()
}
override var intrinsicContentSize : CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: UIViewNoIntrinsicMetric)
}
override func prepareForInterfaceBuilder() {
//setupSubviews()
}
}
// MARK: - Delegate Methods
extension RMLDesignableUITextField {
// placeholder position
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: insetX, dy: insetY)
}
// text position
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: insetX, dy: insetY)
}
}
// MARK: - UITextField Observing
extension RMLDesignableUITextField {
override internal func willMove(toSuperview newSuperview: UIView!) {
if newSuperview != nil {
NotificationCenter.default.addObserver(self, selector: #selector(RMLDesignableUITextField.didBeginEditing(_:)), name: NSNotification.Name.UITextFieldTextDidBeginEditing, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(RMLDesignableUITextField.didEndEditing(_:)), name: NSNotification.Name.UITextFieldTextDidEndEditing, object: self)
} else {
NotificationCenter.default.removeObserver(self)
}
}
func didBeginEditing(_ notification: Notification) {
placeholder = nil
if notification.object as! RMLDesignableUITextField === self{
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: .curveEaseIn, animations: { () -> Void in
if self.text!.isEmpty {
self.fakePlaceholderLabel.transform = self.fakePlaceholderLabel.transform.translatedBy(x: self.translateX, y: 0.0)
self.fakePlaceholderLabel.alpha = 0.0
self.rightPlaceholderLabel.transform = self.rightPlaceholderLabel.transform.translatedBy(x: self.translateX, y: 0.0)
self.rightPlaceholderLabel.alpha = 1.0
}
}, completion: nil)
}
}
func didEndEditing(_ notification: Notification) {
if notification.object as! RMLDesignableUITextField === self {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: .curveEaseIn, animations: { () -> Void in
if self.text!.isEmpty {
self.fakePlaceholderLabel.transform = self.fakePlaceholderLabel.transform.translatedBy(x: -self.translateX, y: 0.0)
self.fakePlaceholderLabel.alpha = 1.0
self.rightPlaceholderLabel.transform = self.rightPlaceholderLabel.transform.translatedBy(x: -self.translateX, y: 0.0)
self.rightPlaceholderLabel.alpha = 0.0
}
}, completion: nil)
}
}
}
// MARK: - Methods
extension RMLDesignableUITextField {
fileprivate func pnpAddBorder(_ border: CALayer, color: UIColor, frame: CGRect) {
border.backgroundColor = color.cgColor
border.frame = frame
layer.addSublayer(border)
// layer.masksToBounds = true
}
func pnpAddTopBorder(_ width: CGFloat, color: UIColor = UIColor.black) {
pnpAddBorder(topBorder, color: color, frame: CGRect(x: 0, y: 0, width: frame.width, height: width))
}
func pnpAddBottomBorder(_ width: CGFloat, color: UIColor = UIColor.black) {
pnpAddBorder(bottomBorder, color: color, frame: CGRect(x: 0, y: frame.height - width, width: frame.width, height: width))
}
func pnpAddLeftBorder(_ width: CGFloat, color: UIColor = UIColor.black) {
pnpAddBorder(leftBorder, color: color, frame: CGRect(x: 0, y: 0, width: width, height: frame.height))
}
func pnpAddRightBorder(_ width: CGFloat, color: UIColor = UIColor.black) {
pnpAddBorder(rightBorder, color: color, frame: CGRect(x: frame.width - width, y: 0, width: width, height: frame.height))
}
func setupSubviews() {
// if (showsTopBorder || showsBottomBorder || showsLeftBorder || showsRightBorder) && borderWidth == 0 {
// borderWidth = 1
// }
if showsTopBorder {
pnpAddTopBorder(borderWidth, color: borderColor)
} else {
topBorder.removeFromSuperlayer()
}
if showsBottomBorder {
pnpAddBottomBorder(borderWidth, color: borderColor)
} else {
bottomBorder.removeFromSuperlayer()
}
if showsLeftBorder {
pnpAddLeftBorder(borderWidth, color: borderColor)
} else {
leftBorder.removeFromSuperlayer()
}
if showsRightBorder {
pnpAddRightBorder(borderWidth, color: borderColor)
} else {
rightBorder.removeFromSuperlayer()
}
self.setNeedsDisplay()
}
}
This is how looks my subclass for UILabel:
#IBDesignable class AttributedLabel: UILabel {
#IBInspectable var padding: CGFloat = 0
override func drawTextInRect(rect: CGRect) {
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, UIEdgeInsetsMake(padding, padding, padding, padding)))
}
}
I correctly set padding in storyboard but It doesnt work because padding is still 0.
What to do to make it working? Is it possible to render it live in Storyboard?
Use like this; Change top , bottom, left, right inset paddings.
#IBDesignable class AttributedLabel: UILabel {
#IBInspectable var topInset: CGFloat = 5.0
#IBInspectable var bottomInset: CGFloat = 5.0
#IBInspectable var leftInset: CGFloat = 7.0
#IBInspectable var rightInset: CGFloat = 7.0
override func drawTextInRect(rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
override func intrinsicContentSize() -> CGSize {
var intrinsicSuperViewContentSize = super.intrinsicContentSize()
intrinsicSuperViewContentSize.height += topInset + bottomInset
intrinsicSuperViewContentSize.width += leftInset + rightInset
return intrinsicSuperViewContentSize
}
}
Thanks
Your subclass looks incomplete. As mentioned in the documentation, you should override both of these methods :
public func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect
public func drawTextInRect(rect: CGRect)
Here is an example implementation that should work :
#IBDesignable class AttributedLabel : UILabel
{
#IBInspectable var padding: CGFloat = 0 {
didSet {
self.textInsets = UIEdgeInsets(top: self.padding, left: self.padding, bottom: self.padding, right: self.padding)
}
}
var textInsets = UIEdgeInsetsZero {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect
{
var insets = self.textInsets
let insetRect = UIEdgeInsetsInsetRect(bounds, insets)
let textRect = super.textRectForBounds(insetRect, limitedToNumberOfLines: numberOfLines)
insets = UIEdgeInsets(top: -insets.top, left: -insets.left, bottom: -insets.bottom, right: -insets.right)
return UIEdgeInsetsInsetRect(textRect, insets)
}
override func drawTextInRect(rect: CGRect) {
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, self.textInsets))
}
}
You will not be able to render it live in Interface Builder though.
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)