Bottom Border Width on Swift TextField in TableView - ios

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

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

how to make designable textfield code class in swift

I am a beginner in programming and in iOS development. I want to make a swift file that contains a code to make Designable textfield, so I will not edit the UI element display by coding, all the UI that will be edited, I will edit it in Interface builder.
I need to input an image in UITextfield, and I follow along a tutorial in here https://www.youtube.com/watch?v=PjXOUd4hI6U&t=932s to put an image in the left side of the UITextField, like the lock image in the picture above. here is the the to do this
import UIKit
#IBDesignable
class DesignableTextField: UITextField {
#IBInspectable var leftImage : UIImage? {
didSet {
updateView()
}
}
#IBInspectable var leftPadding : CGFloat = 0 {
didSet {
updateView()
}
}
#IBInspectable var cornerRadiusOfField : CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadiusOfField
}
}
func updateView() {
if let image = leftImage {
leftViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: leftPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = leftPadding + 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width += 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
leftView = view
} else {
// image is nill
leftViewMode = .never
}
}
}
but now I need add another image on the right side only and both of them (i.e on the the right side AND left side). how do I edit those code? I have tried but it doesn't appear and to be honest I little bit confused to adjust the x and y coordinate of the view. I need your help :)
There are two solutions for this
The First solution is the easiest one. You can have a view and insert inside the view a UIImageView, UITextField, UIImageView. Add constraints to set the desired sizes. You can make the text field transparent. With this method, you can customize it how you want.
The Second solution is how you are doing it.
The first thing you need to do is add the properties to the right image and right padding. Under the left padding property add the following code:
#IBInspectable var rightImage : UIImage? {
didSet {
updateRightView()
}
}
#IBInspectable var rightPadding : CGFloat = 0 {
didSet {
updateRightView()
}
}
With this new properties, you can choose the image and edit the x location.
After the update function create a new function called updateRigthView
Like this:
func updateRightView() {
if let image = rightImage {
rightViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: rightPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = rightPadding - 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width -= 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
rightView = view
} else {
// image is nill
rightViewMode = .never
}
}
You had to edit the right properties.Now head to storyboard and try it out. To move the image to the left decrease the right padding 0,-1,-2,-3, etc. To move the image to the right increase the right padding 0,1,2,3.

UIButton subview is offset in subclass

I have a UIButton subclass intended to show a selected state of a button. The selected state simply places a thick black line at the bottom of the button view and when unselected it hides the black line. However, when using this in a UIButton subclass, the black line view is offset. I have tried playing around with insets, but I don't think that is the problem. Here is my subclass:
class TabButton: UIButton {
private var height:CGFloat = 5
private var selectedIndicator:UIView?
override var isSelected: Bool {
didSet {
selectedIndicator?.isHidden = !isSelected
}
}
fileprivate func initializeSelector(_ frame: CGRect) {
selectedIndicator = UIView(frame: CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height))
selectedIndicator?.backgroundColor = UIColor.black
self.addSubview(selectedIndicator!)
}
override func awakeFromNib() {
super.awakeFromNib()
initializeSelector(self.frame)
}
}
The desired button should look like this:
But instead it looks like this:
Can anyone help me understand what is happening here and how to fix it? Thanks!
Try this, in layoutSubviews you get the final frame:
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
The frame of the selectedIndicator is set only once when initializeSelector is called. When the button changes its frame, it does not change the frame of subviews, you need to manually update the frame of selectedIndicator.
To do so, you need to override layoutSubviews() method of UIView.
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator?.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
See this answer to know when layoutSubviews() is called.

How to display clear button in left side of UITextField?

I'm developing an app in Arabic language and I have UITextField with textAlignment right. Now I want to show the clear button of the textField in left side. Is it possible to do this without adding a custom button?
Current position
Desired position
Use below category and make sure your text alignment should be right :)
#interface UICrossButtonTextField:UITextField
- (CGRect)clearButtonRectForBounds:(CGRect)bounds;
#end
#implementation UICrossButtonTextField
- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
CGRect originalRect = [super clearButtonRectForBounds:bounds];
return CGRectOffset(originalRect, -originalRect.origin.x+5, 0); }
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect originalRect = [super clearButtonRectForBounds:bounds];
bounds = CGRectMake(originalRect.size.width, bounds.origin.y, bounds.size.width-originalRect.size.width, bounds.size.height);
return CGRectInset(bounds, 13, 3);
}
#end
Although I would recommend to check this answer for handling Left-to-Right App languages, as a workaround you could follow userar's answer, the following code snippet is a Swift 3 version of his answer:
Create a custom UITextField class, as follows:
class CustomTextField: UITextField {
private var originalRect = CGRect.zero
override func awakeFromNib() {
super.awakeFromNib()
originalRect = super.clearButtonRect(forBounds: bounds)
clearButtonMode = .whileEditing
textAlignment = .right
}
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
return originalRect.offsetBy(dx: -originalRect.origin.x + 5, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
let bounds = CGRect(x: originalRect.size.width, y: bounds.origin.y, width: bounds.size.width-originalRect.size.width, height: bounds.size.height)
return bounds.insetBy(dx: 13, dy: 3)
}
}
The output would be:
SWIFT 3 syntax:
class TextFields: UITextField {
// You will need this
private var firstPlace = CGRect.zero
override func awakeFromNib() {
super.awakeFromNib()
firstPlace = super.clearButtonRect(forBounds: bounds) // to access clear button properties
/* uncomment these following lines if you want but you can change them in main.storyboard too
clearButtonMode = .whileEditing // to show the clear button only when typing starts
textAlignment = .right // to put the text to right side
*/
}
// Function to change the clear button
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
return firstPlace.offsetBy(dx: -firstPlace.origin.x + 5, dy: 0)
}
}
hope it works

Adding Bottom Line Border To A TextView - iOS

What is the best way to add a bottom line border to a TextView with a dynamic height?
I tried this:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.CGColor
border.frame = CGRectMake(0, self.frame.size.height - width,
self.frame.size.width, width)
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
Swift 4 Version
func addBottomBorderWithColor() {
let border = CALayer()
border.backgroundColor = UIColor.black.cgColor
border.frame = CGRect(x: 0, y: yourTextArea.frame.height - 1, width: yourTextArea.frame.width, height: 1)
yourTextArea.layer.addSublayer(border)
self.view.layer.masksToBounds = true
}
Update:
I thought a lot about my original solution.
If you are subclassing UITextView to add a bottom line, then the bottom line had better be a subview of the text view itself, rather than its super view's.
And finally, I figure out one solution that can add bottom line as a subview of TextView itself and the bottom line will not move when the user scrolls the text of TextView. In your view controller, you can also change the frame of TextView dynamically, and the bottom line will also stick to the the bottom.
Here is the reference code:
import UIKit
class TextView: UITextView {
var border: UIView
var originalBorderFrame: CGRect
var originalInsetBottom: CGFloat
deinit {
removeObserver(self, forKeyPath: "contentOffset")
}
override var frame: CGRect {
didSet {
border.frame = CGRectMake(0, frame.height+contentOffset.y-border.frame.height, frame.width, border.frame.height)
originalBorderFrame = CGRectMake(0, frame.height-border.frame.height, frame.width, border.frame.height);
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "contentOffset" {
border.frame = CGRectOffset(originalBorderFrame, 0, contentOffset.y)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color
border.frame = CGRectMake(0, frame.height+contentOffset.y-width, self.frame.width, width)
originalBorderFrame = CGRectMake(0, frame.height-width, self.frame.width, width)
textContainerInset.bottom = originalInsetBottom+width
}
}
Note: Since I used to write code in Objective-C, I am not familiar with Swift. The code above is only for your reference (though I have tested the corresponding Objective-C code, and it works as expected):
As you can see, there is no initialisation code. I have tried to write such code, but it always shows an error and I still have no idea about that. Just make sure to add the below code to your TextView initialisation code:
border = UIView()
addSubview(border)
originalInsetBottom = textContainerInset.bottom
addObserver(self, forKeyPath: "contentOffset", options: .New, context: nil)
I am not familiar with the concept of optional value, wrap, unwrap... So you should add ?, ! to the code if needed.
Original answer:
Does self in your code mean TextView?
If so, when you add border as a sublayer of the TextView, the border will move up and down when the user scrolls the text of TextView.
Try to add border as a sublayer of TextView's super view rather than TextView itself.
Here is the code (Note that I change border from CALayer to UIView):
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = UIView()
border.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y+self.frame.height-width, textView.frame.width, width)
border.backgroundColor = color
self.superview!.insertSubview(border, aboveSubview: textView)
}
Here is the capture:
PS. I suggest you to change the second paramter name from width to height since width is ambiguous in this context.
It works better with following line:
border.autoresizingMask = [.flexibleWidth, .flexibleHeight]
extension UITextView {
func addBottomBorderWithColor(color: UIColor, height: CGFloat) {
let border = UIView()
border.autoresizingMask = [.flexibleWidth, .flexibleHeight]
border.frame = CGRect(self.frame.origin.x,
self.frame.origin.y+self.frame.height-height, self.frame.width, height)
border.backgroundColor = color
self.superview!.insertSubview(border, aboveSubview: self)
}
}
I've tried other answers, they are either hard to implement, or come with bugs, however, I've found a simple and perhaps the best way to achieve this:
Just add a UIView next to your UITextView, with a height of 1 (or maybe 2 of your choice), set up the constraints, make sure the UIView is above the UITextView.
All of this can be achieved in storyboard so I'm not giving code here, since this UIView can be replaced with an UIImageView, you can add mustache to your UITextView if you wish
:)
Set the mask to bounds to be false then only it show the bounds of the line that you want to show..
Try following code it may helps you
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.CGColor
border.frame = CGRectMake(0, yourTextview.frame.origin.y+yourTextview.frame.size.height+1 , width, 1)
self.view.layer.addSublayer(border)
self.view.layer.masksToBounds = true
}
Sometimes it's easier to just keep things a little more independent of one another.
UIImageView * border = [UIImageView new];
border.frame = CGRectMake(0,y,100,0.5f);
border.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3f];
[self.view addSubview:border];

Resources