UITextField subclass with bar underneath: bar not going all the way - ios

The objective
I've made my own UITextField subclass, to design it in such a way that there is no border, only a white line under the text. (Subclass code below)
The issue
Unfortunately, the white line does not go to the end of the text field (in terms of width):
I've constrained the text field to have 0 px distance to both the left and right edges of the view, but as the images below show, the line does not meet those constraints.
My investigation and hypotheses
I've put in quite some time to debug, the only thing I can think of is that the dimensions specified in the code (copied below) are exécute before the constraints are applied to the view, which is not something I know how to resolve.
The code
//In a UITextField subclass
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
public override func awakeFromNib() {
super.awakeFromNib()
setup()
}
func setup() {
self.delegate = self
// BORDER
// Remove rectangular border
self.borderStyle = .none
self.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0).cgColor //Make transparent
self.layer.borderWidth = 0 // width = 0
// Add bottom border
bottomBorder = UIView(frame: CGRect(x: 0, y: self.frame.height, width: self.frame.width, height: 1))
bottomBorder.backgroundColor = UIColor.white
self.addSubview(bottomBorder)
}

the only thing I can think of is that the dimensions specified in the code (copied below) are exécute before the constraints are applied to the view,
Perfectly correct! The whole problem is that you are hard-coding the frame of the bar based on the frame of the text field. But the frame of the text field is not yet known during init.
Your entire approach here is wrong. You should be using autolayout constraints to constrain the bar to the text field, not giving the bar a fixed frame. That way, no matter what happens to the size of the text field (as it obeys the constraints you have given it), the size of the bar will follow along in perfect harmony.

You need
override func layoutSubviews() {
super.layoutSubviews()
bottomBorder.frame = CGRect(x: 0, y: self.frame.height - 1, width: self.frame.width, height: 1)
}
As correct frame size of superView is captured inside layoutSubviews not in init / awakeFromNib where the frame is set for current development device dimensions if it's in IB

Related

How to get Width or Height of a UIView when I'm using "Equal width" constrain

I need help to solve this problem: I have a UITextFiled and I'm trying to apply a border at the bottom using this code:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.addSublayer(border)
}
The problem is that the result is not correct, the border goes outside the textfield because in the text Field I'm using the "Equal width constrain" and the Width at design time is not the same Width at "Didload()" time. There is a way to get the width to the textField after "Equal width constrain" correction?
A much better approach is to
subclass UITextField
create the "underline border" layer on initialization
change the frame of that layer in layoutSubviews()
Example:
#IBDesignable
class UnderlinedTextField: UITextField {
let underlineLayer: CALayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(underlineLayer)
underlineLayer.backgroundColor = UIColor.black.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
underlineLayer.frame = CGRect(x: 0, y: bounds.height - 2.0, width: bounds.width, height: 2)
}
}
Result (I gave the text field a background color of .cyan to make it easy to see):
It automatically resizes the "underline" when the field size changes - such as on device rotation:
Note that, by making it #IBDesignable, you can also see the underline layer during design-time.
This example uses a default color of black for the "underline" but you can change it via code just like any other property change, e.g.:
testField.underlineLayer.backgroundColor = UIColor.red.cgColor
Override bounds variable and call your border drawing in didSet. Your layer would be updated every time view changes bounds.
var border = CALayer()
init(frame: CGRect) {
super.init(farme: frame)
self.layer.addSublayer(border)
}
override var bounds: CGRect {
didSet {
addBottomBorderWithColor(color: .black, width: 2)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.setNeedsLayout()
}
I found a possible solution by myself (not the perfect one).
Because the Constrains are probably applied after DidLoad() and after viewDidLayoutSubviews(), I called the function to add the border inside the function viewDidAppear(). Now it works even if the new borders are shown with a small delay.
The best way is sub-class a UITextFiled as described here.
Custom class that can be applied to every UITextField - Swift
In this case there the object is created correctly

Adding CAShapeLayer to UIButton but it appears under the border line

I want to add a red dot to the border of a UIButton. My current code for adding a dot is like so:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
override func setUp() {
super.setUp()
layer.borderWidth = borderWidth
layer.borderColor = normalBorderColor.cgColor
let redDotLayer = CAShapeLayer()
redDotLayer.path = CGPath(ellipseIn: CGRect(x: 30, y: -3.5, width: 8, height: 8), transform: nil)
redDotLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(redDotLayer)
}
However when I add the red dot it appears under the border line. I need it to be on top of the border line.
What am I doing wrong here?
Nothing wrong with your approach.
For example just consider adding subview to parent UIView, subview always stays inside the parent it can't be added above the parent.
Same scenario applies to CALayer . You can't addSublayer above parent layer.
As an exception,
Unlike views, a superlayer does not automatically clip the contents of sublayers that lie outside its bounds rectangle. Instead, the superlayer allows its sublayers to be displayed in their entirety by default. However, you can reenable clipping by setting the masksToBounds property of the layer to YES.
as per apple documentation sublayer can go beyond parent visible region, but not above the parent.
Even below methods won't help us.
- insertSublayer:atIndex:
- insertSublayer:above:
- insertSublayer:below:
addSublayer:
Appends the layer to the layer’s list of sublayers.
Solution
Draw another CAShapeLayer around the button.
CALayers may just draw the border around itself last. I think the easiest solution for you is just be to draw the border in a separate layer so that you can control the ordering.
override func setUp() {
super.setUp()
let borderLayer = CALayer()
borderLayer.borderWidth = borderWidth
borderLayer.borderColor = normalBorderColor.cgColor
layer.addSublayer(borderLayer)
let redDotLayer = CAShapeLayer()
redDotLayer.path = CGPath(ellipseIn: CGRect(x: 30, y: -3.5, width: 8, height: 8), transform: nil)
redDotLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(redDotLayer)
}
In my code, I was unable to use another layer as a border so I've used another UIView as a border.
An important part is to set the border UIView isUserInteractionEnabled to false so the responder chain would pass it to the UIButton:
let borderView = UIView(frame: .zero)
borderView.backgroundColor = .clear
borderView.layer.borderWidth = 0.5
borderView.layer.borderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.18).cgColor
borderView.layer.cornerRadius = 19
// Dont't forget to set it to false or else the button won't work
borderView.isUserInteractionEnabled = false

Bottom Border Width on Swift TextField in TableView

i builded a static tableview with more Rowes than the screen has, so the user has to scroll to see all cell.
Every cell has a textfield with the following class to add a bottom border:
class TextFieldWithBottomBorder: UITextField {
let border = CALayer()
let width = CGFloat(1.0)
func addBottomBorder(color: UIColor){
self.border.borderColor = color.cgColor
self.border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height:self.frame.size.height)
self.border.borderWidth = self.width
self.layer.addSublayer(self.border)
self.layer.masksToBounds = true
}
func changeBorderColor(color: UIColor){
self.border.borderColor = color.cgColor
}
}
And i call the method after receiving some data from the server e. g.
self.firstnameTextField.text = firstNameFromDB
self.firstnameTextField.addBottomBorder(color: .blue)
This works fine for every cell is currently displayed. But the cells which are out of the current view the with is shorter than the textfield.
See this screenshot, for "Vorname", means firstName everything looks good, but for email, password etc. the border is to short.
http://share-your-photo.com/34b5e80253
Looks like the size of the UITextField is being resized after you have called addBottomBorder and so the UIView being used at the line is now not wide enough. It's difficult to say why this would be without seeing more code but there are several methods you could use to overcome it.
1) Switch to a UIView instead of a CALayer and use auto layout to keep the view in the correction position.
2) Override layoutSubviews to update the frame of the bottom line.
The simplest for you is probably option 2 (although I would go option 1) and it would look like this:
override func layoutSubviews() {
super.layoutSubviews()
self.border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height:self.frame.size.height)
}
Now whenever the frame/size of the text field changes the frame/size of the border line CALayer will be updated appropriately.
Use this class for bottom line text field
#IBDesignable class BottomTextField: UITextField {
var lineView = UIView()
#IBInspectable var lineViewBgColor:UIColor = UIColor.gray{
didSet {
if !isFirstResponder {
lineView.backgroundColor = lineViewBgColor
}
}
}
required init?(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)!
setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
// MARK:- Private Methods
private func setup() {
lineView.frame = CGRect(x:CGFloat(0), y:self.frame.size.height-2, width:self.frame.size.width, height:CGFloat(1))
lineView.backgroundColor = lineViewBgColor
self.addSubview(lineView)
}
}

Custom view - a subclass of uiview cannot be resized

It must be a simple thing but I couldn't find a clue,
I am having a imageview and trying a scribbling view which is a subclass of UIview on top of it to allow scribbling. All works fine except for resizing the scribbling view, it occupies the whole screen all the time I couldn't make it smaller.
class ScribbleView: UIView
{
let backgroundLayer1 = CAShapeLayer()
required init()
{
// super.init(frame: CGRect.zero)
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
layer.addSublayer(backgroundLayer1)
layer.masksToBounds = true
}
class HermiteScribbleView: ScribbleView, Scribblable
{
required init() {
print ("calling parent")
super.init()
}
}
/** main code **/
let hermiteScribbleView = HermiteScribbleView()
hermiteScribbleView.backgroundColor = .red
// imgView.autoresizesSubviews = true
// hermiteScribbleView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
//once this is done, hermieScribbleView is always stretched to full width and height no matter what I try
imgView.addSubview(hermiteScribbleView)
What i guess from above information is that, you might be using the frame of UIImageView to set the frame of UIView. you need to set the size of UIView same as the size of UIImage.
imgImageView.image.size
and secondly set the centre of UIView same as centre of UIImageView.
Hope this will work.

Change a UIButton's text (padding) programmatically in Swift

Still learning Swift and don't know Objective-C. I saw that in order to changing a button's text programmatically requires the use of titleEdgeInsets but I am not really sure how to use it.
I would like to change the text in the button (padding) in the bottom and both the left and the right.
Thanks for any responses and/or examples!
Still valid for:
iOS 12/Xcode 10/Swift 4.2/OSX 10.13.2
iOS 9.1/Xcode 7.1/Swift 2.1/OSX 10.10.5:
The method titleEdgeInsets didn't work for me. My button's frame tightly hugs the text (I didn't specify a frame size), and the frame has a red background color. After doing:
myButton.titleEdgeInsets = UIEdgeInsetsMake(10,10,10,10)
the red background shrunk inwards by 10 pixels on each side, which meant that now some of the text was outside the red background. Using negative values had no effect on the original frame size.
I was able to get padding around the text, effectively making the frame bigger, by doing:
myButton.contentEdgeInsets = UIEdgeInsetsMake(5,5,5,5)
iOS 10 and Swift 3 example
Tested and works on IOS 11 and Swift 4.
refineButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
You can add padding from top, left, right and bottom by doing these lines of code.
button.titleEdgeInsets.left = 10; // add left padding.
button.titleEdgeInsets.right = 10; // add right padding.
button.titleEdgeInsets.top = 10; // add top padding.
button.titleEdgeInsets.bottom = 10; // add bottom padding.
You can also do this in the storyboard:
For iOS 9.1/Xcode 7.1/Swift 2.1
#IBOutlet weak var ratingButton: UIButton!
override func viewDidLoad() {
ratingButton.contentEdgeInsets = UIEdgeInsets(top:15,right:10,bottom:15,left:10)
super.viewDidLoad()
}
Or you can use a custom class if you don't want to define the properties everytime you create a button;
class UIPaddedButton: UIButton {
let padding = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return contentRect.inset(by: padding)
}
}
And when initializing;
let btn = UIPaddedButton()
For xcode 13.4 and iOS 12
Tested and working
To make space to both sides of a button
ButtonName.contentEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)

Resources