Swift CGPoint x and y overrides programatic constraints - ios

I am trying to programatically create some text fields, and use programatic constraints to lay them out as creating them in the interface builder and using that to create constrains will not work as everything else is created programatically. So the problem is that to create a Text field, I have to use frame: CGRect(x y width height). But doing this overrides all the programatic constraints telling where to be placed based on other UI elements.
// Create username and password entry fields
let usernameTextField: UITextField
usernameTextField = UITextField(frame: CGRect(x: 0, y: 0, width: screenWidth - 40 - usernameLabel.bounds.width, height: 30)) // Without this line usernameTextField is uninitialised.
usernameTextField.textColor = UIColor.black()
usernameTextField.textAlignment = NSTextAlignment.left
usernameTextField.placeholder = "username"
self.view.addSubview(usernameTextField)
usernameTextField.translatesAutoresizingMaskIntoConstraints = true
let passwordTextField: UITextField
passwordTextField = UITextField(frame: CGRect(x: 0, y: 0, width: screenWidth - 40 - passwordLabel.bounds.width, height: 30))
passwordTextField.textColor = UIColor.black()
passwordTextField.textAlignment = NSTextAlignment.left
passwordTextField.placeholder = "password"
self.view.addSubview(passwordTextField)
passwordTextField.translatesAutoresizingMaskIntoConstraints = true
// Constraints for username and password entry fields
// Horizontal placement
usernameTextField.leadingAnchor.constraint(equalTo: usernameLabel.trailingAnchor).isActive = true
passwordTextField.leadingAnchor.constraint(equalTo: passwordLabel.trailingAnchor).isActive = true
// Verticle placement
usernameTextField.bottomAnchor.constraint(equalTo: usernameUnderline.topAnchor).isActive = true
passwordTextField.bottomAnchor.constraint(equalTo: passwordUnderline.topAnchor).isActive = true
//Width
usernameTextField.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
passwordTextField.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
So how can I programatically create these text fields and use constraints to place them?

Once you start adding constraints to a UIView, you have to go all in. Set translatesAutoresizingMaskIntoConstraints = false on your fields and then add constraints for the desired width, height, and placement.
In that case, you don't pass a frame to the UITextField constructor when you create it:
let usernameTextField = UITextField()
It looks like you are already accounting for usernameTextField's length by setting its leading and trailing anchors. So add a constraint for its height:
usernameTextField.heightAnchor.constraintEqualToConstant(30)
And repeat this for your passwordTextField.

Related

Auto-sizing a UILabel without setting an explicit height

How do I get a multi-line label to size itself? I don't want to set an explicit height for it but I do need to place it in view.
The way my app is built, we explicitly set frames and origins rather than using NSLayoutConstraints. It's a mature app so this isn't up for discussion.
I'd like to be able to give my UILabel an origin and a width and let it figure its own height out.
How can I do this? This is my playground code:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 180))
view.backgroundColor = .white
let l = UILabel()
l.text = "this is a really long label that should wrap around and stuff. it should maybe wrap 2 or three times i dunno"
l.textColor = .black
l.lineBreakMode = .byWordWrapping
l.numberOfLines = 0
l.textAlignment = .center
l.sizeToFit()
let margin: CGFloat = 60
view
view.addSubview(l)
l.frame = CGRect(x: margin, y: 0, width: view.bounds.width - (margin * 2), height: 100)
// I don't want to do this ^^
This may do what you want...
As requested, you want to set the .origin and .width of a UILabel and have it set its own .height based on the text.
class ZackLabel: UILabel {
override public func layoutSubviews() {
super.layoutSubviews()
let h = sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
self.frame.size.height = h.height
}
}
class ViewController: UIViewController {
var testLabel: ZackLabel!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// instantiate a 300 x 180 UIView at 20, 80
let myView = UIView(frame: CGRect(x: 20, y: 80, width: 300, height: 180))
myView.backgroundColor = .white
// instantiate a ZackLabel
testLabel = ZackLabel()
testLabel.text = "this is a really long label that should wrap around and stuff. it should maybe wrap 2 or three times i dunno"
testLabel.textColor = .black
testLabel.lineBreakMode = .byWordWrapping
testLabel.numberOfLines = 0
testLabel.textAlignment = .center
// set background color so we can see its frame
testLabel.backgroundColor = .cyan
let margin: CGFloat = 60
// set label's origin
testLabel.frame.origin = CGPoint(x: margin, y: 0)
// set label's width (label will set its own height)
testLabel.frame.size.width = myView.bounds.width - margin * 2
// add the view
view.addSubview(myView)
// add the label to the view
myView.addSubview(testLabel)
// add a tap recognizer so we can change the label's text at run-time
let rec = UITapGestureRecognizer(target: self, action: #selector(tapFunc(_:)))
view.addGestureRecognizer(rec)
}
#objc func tapFunc(_ sender: UITapGestureRecognizer) -> Void {
testLabel.text = "This is dynamic text being set."
}
}
Result (on an iPhone 8):
and, after tapping on the (yellow) view, dynamically changing the text:
label.sizeThatFits(CGSize(width: <your required width>, height: CGFloat.greatestFiniteMagnitude))
This returns the labels needed size, growing infinitely in height, but fitted to your required width. I've occasionally noticed minor inaccuracies with this function (rounding error?), so I tend to bump the width and height by 1 just to be safe.
UILabel comes with an intrinsic size that should be calculated based on the text and the label's .font property. You may need to add a margin to it...
var height = l.intrinsicContentSize.height
height += margin
l.frame = CGRect(x: margin, y: 0, width: view.bounds.width - (margin * 2), height: height)
Failing that, maybe you can try something like:
let size = CGSize(width: view.bounds.width - (margin * 2), height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
var estimatedFrame = CGRect()
if let font = l.font {
estimatedFrame = NSString(string: l.text).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: font], context: nil)
}
//if you need a margin:
estimatedFrame.height += margin
l.frame = estimatedFrame
Give your UILabel as a UIScrollview or UITableView cell subview.
Then you setup UILabel leading, tralling, top, bottom constrain.
If you give UITableview then set table view hight auto dynamic. If you give UIScrollview
just set UILabel bottom constrain priority low

How to fill a label with text until it reaches it's width

I want to know if there is a way to determine if a UILabel is full of text.
For example, if a have a label with size of:
| |
I want to know when the label is full of text, such as:
|.........|
I need to fill its with dots until it reaches the textFields width.
Well, If I got it right, probably what are you looking for is Label intrinsicContentSize:
The natural size for the receiving view, considering only properties
of the view itself.
the width of the label intrinsicContentSize should be the actual width of the label, doesn't matter what's the frame.size.width value.
Based on that, you simply implement:
let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 21))
print(lbl.frame.size.width)
// 150.0
lbl.text = ""
let intrinsicSizeWidth = lbl.intrinsicContentSize.width
// since the label text is still empty, its value should be 0.0
print(intrinsicSizeWidth)
while lbl.intrinsicContentSize.width < lbl.frame.size.width {
lbl.text?.append(".")
}
print(lbl.text!)
// ................................
Note that increasing your label width would leads to contains more dots:
let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
.
.
.
print(lbl.text!)
// ...........................................
Obviously, if you would like to compare it with a UITextField -for instance- (as you mentioned in the question), it should be:
// "textField" the name of your text field...
while lbl.intrinsicContentSize.width < textField.frame.size.width {
lbl.text?.append(".")
}
Create label with frame and default text
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 100))
label.text = ""
Populate text with dots until text's width reaches label's width
while label.sizeThatFits(label.frame.size).width <= label.frame.width {
label.text = label.text! + "."
}

padding setting for password field

I am using Swift 3. I have two txtFields, one of them for email, the other one for password. I use the following code to give the email field a padding:
txtEmail.leftView = paddingView;
txtEmail.leftViewMode = .always
However, the app seems to run into an infinity loop when I apply the same to the password field:
txtPassword.leftView = paddingView;
txtPassword.leftViewMode = .always
What am I missing?
This UITextField extension will ease your implementation later on and should solve any problems related to using the same UIView on multiple textfields:
extension UITextField {
func pad(_ amount: Int) {
self.leftView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.height))
self.leftViewMode = UITextFieldViewMode.always
}
}
Then you can use it like this in viewDidLoad or anywhere lower in the view controller's call hierarchy
txtEmail.pad(15)
txtPassword.pad(15)
As your code display you are using same view object for both textfield at the same time and may be that's why your app is looping. you can not add same view object as a subview for two diffrent textfield at the same time. so create new view instance and set it as a padding for individual textfiled.
let paddingForEmail = UIView(frame: CGRectMake(0, 0, 15,txtEmail.frame.size.height))
//Adding the padding to the Email textField
txtEmail.leftView = paddingForFirst
txtEmail.leftViewMode = UITextFieldViewMode .Always
//Create new padding object of view for second textField
let paddingForPassword = UIView(frame: CGRectMake(0, 0, 15,txtPassword.frame.size.height))
//Add padding to the Password textField
txtPassword.leftView = paddingForPassword
txtPassword.leftViewMode = UITextFieldViewMode .Always
hope this will help you.

centering two views in Navigation titleView

I want my application to behave like this:
It is important to have the exact behavior for my NavigationItem.titleView
I followed these steps so far:
I am creating three views programmatically in my application:
(1) container => holds (2) and (3) => has a gestureRecognizer attached
let container = UIView(frame: CGRectMake(0,0,200,40))
(2) imageContainer => has an image
let imageContainer = UIImageView(frame: CGRectMake(0, 0, 30, 30))
imageContainer.image = UIImage(named: "mock.jpg")
(3) textContainer => has some text
let textContainer = UILabel(frame: CGRectMake(0,0, 180, 20))
textContainer.text = "Group xY"
Following I am setting the center of the images to align them:
imageContainer.center = CGPointMake(container.frame.size.width / 2,
container.frame.size.height / 2)
textContainer.center = CGPointMake(container.frame.size.width / 2,
container.frame.size.height / 2)
Now I am adding all the subViews to my View and setting
self.navigationItem.titleView = (1)
Starting the app shows, that the titleView's elements aren't properly aligned
Is there a way to implement this exact behavior correctly?
Note: don't worry about the circular image. I know how to implement this.
You should set the size of textContainer to be closer to the bounds of the text. You can do this by calling sizeToFit then you need to set the imageContainer to be on the left of the text so the center of the image should be half the width of the image plus a buffer from the start of the text. You could do that by saying imageContainer.center = CGPointMake(textContainer.frame.minX - imageContainer.frame.size.width * 0.5 - buffer,container.frame.size.height / 2). Your code should look something like:
let container = UIView(frame: CGRectMake(0,0,200,40))
let buffer:CGFloat = 8.0
let maxWidth:CGFloat = 120.0
let imageContainer = UIImageView(frame: CGRectMake(0, 0, 30, 30))
imageContainer.image = UIImage(named: "profile.jpg")
let textContainer = UILabel(frame: CGRectMake(0,0, 180, 20))
textContainer.text = "Group xY"
textContainer.adjustsFontSizeToFitWidth = true
textContainer.minimumScaleFactor = 0.95
textContainer.sizeToFit()
textContainer.frame.size.width = min(maxWidth, textContainer.frame.size.width)
textContainer.center = CGPointMake(container.frame.size.width / 2,
container.frame.size.height / 2)
imageContainer.center = CGPointMake(textContainer.frame.minX - imageContainer.frame.size.width * 0.5 - buffer,
container.frame.size.height / 2)
container.addSubview(imageContainer)
container.addSubview(textContainer)
Which will give you for your container.

Position of rightView UITextField

Is there a way to adjust the position of a rightView on UITextField? I tried setting the frame on the view (set as rightView) but it didn't change anything.
I'd like to avoid making two views, one as the rightView and one as the rightView's subview where I change the subview's position, if possible.
The right overlay view is placed in the rectangle returned by the rightViewRectForBounds: method of the receiver.
So I suggest you subclass UITextField and override this method, something like this:
#interface CustomTextField: UITextField
#end
#implementation CustomTextField
// override rightViewRectForBounds method:
- (CGRect)rightViewRectForBounds:(CGRect)bounds{
CGRect rightBounds = CGRectMake(bounds.origin.x + 10, 0, 30, 44);
return rightBounds ;
}
#Puneet Sharma's answer was great but that would give the need to create a class that would subclass UITextField, what I did instead was create a UIView that would act as a padding.
This code works without the need to subclass
Here's my code, although it's written in Swift 3
// this is the view I want to see on the rightView
let checkImageView = UIImageView(image: UIImage(named: "check24.png"))
checkImageView.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
checkImageView.curveEdges(12)
checkImageView.contentMode = .scaleAspectFit
// declare how much padding I want to have
let padding: CGFloat = 6
// create the view that would act as the padding
let rightView = UIView(frame: CGRect(
x: 0, y: 0, // keep this as 0, 0
width: checkImageView.frame.width + padding, // add the padding
height: checkImageView.frame.height))
rightView.addSubview(checkImageView)
// set the rightView UIView as the textField's rightView
self.textField.rightViewMode = .whileEditing
self.textField.rightView = rightView
What happened here is, that the rightView which is a UIView that has a transparent colored background which then gave the illusion that there is a padding whereas there is not.
Right Padding you can use as
let imageview = UIImageView(image: UIImage(named: "image name"))
imageview.contentMode = .center
let rightPadding: CGFloat = 14 //--- change right padding
imageview.frame = CGRect(x: 0, y: 0, width: imageview.frame.size.width + rightPadding , height:imageview.frame.size.height)
textField.rightViewMode = .always
textFieldd.rightView = imageview

Resources