I have created UITextFields dynamically. Now i want to refer to the TextFields to check for some constraints. How do i do so?
func displayTextBox1(height: Int, placeHolder: String, xtb: Int, ytb: Int, lableName: String, xl: Int, yl: Int) {
DispatchQueue.main.async{
self.textField = UITextField(frame: CGRect(x: xtb, y: ytb, width: 343, height: height))
self.textField.textAlignment = NSTextAlignment.left
self.textField.textColor = UIColor.black
self.textField.borderStyle = UITextBorderStyle.line
self.textField.autocapitalizationType = UITextAutocapitalizationType.words // If you need any capitalization
self.textField.placeholder = placeHolder
print("hi")
self.view.addSubview(self.textField)
self.displayLabel(labelName: lableName, x: xl, y: yl)
}
}
You can set constraints programmaticaly using the sample explained code below:
let constraint = NSLayoutConstraint(item: self.textField, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -50)
NSLayoutConstraint.activate([constraint])
As you can see, I am creating a constraint for item textField which width should be equal to width of view multiplied by 1 minus 50. That means the width of your textField will be 50 pixels less than the width of the view. The last line of code activates given set of created constraints.
Welcome to Stackoverflow, and hope you're enjoying learning Swift!
I'm going to make some assumptions based on your code snippet:
the details you need to create the textfield and label (position, placeholder text etc) are coming from some service that operates on a background thread (perhaps a HTTP request?) which is why you're using DispatchQueue.main.async to perform UI events back on the main thread.
there will be multiple textfield/label pairs that you're going to configure and add to the interface (not just a single pair)... perhaps a 'todo' list sort of app where a label and textfield let people enter a note (more on this in part 2 of answer) which is why these views (and constraints) are being created in code rather than in a storyboard.
you want to invest in a constraint-based layout rather than frame-based positioning.
Answer part 1
If any of those assumptions are incorrect, then parts of this answer probably won't be relevant.
But assuming the assumptions are correct I suggest a couple things:
Use a separate helper methods to create a textfield/view and return the result (rather than doing everything in a single method) -- methods that have a single purpose will make more sense and be easier to follow.
Don't use a mixture of setting view position/size with frame and constraints - use one approach or the other (since you're new it will be easier to keep a single mental model of how things are working rather than mixing).
Here's a snippet of what a view controller class might start to look like:
import UIKit
class ViewController: UIViewController {
func triggeredBySomeEvent() {
// assuming that you have some equivilent to `YouBackgroundRequestManager.getTextFieldLabelDetails`
// that grabs the info you need to create the label and textfield on a background thread...
YouBackgroundRequestManager.getTextFieldLabelDetails { tfPlaceholder, tfHeight, tfX, tfY, labelName, labelX, labelY in
// perform UI work on the main thread
DispatchQueue.main.async{
// use our method to dynamically create a textfield
let newTextField: UITextField = self.createTextfield(with: tfPlaceholder, height: tfHeight)
// add textfield to a container view (in this case the view controller's view)
self.view.addSubview(newTextField)
// add constraints that pin the textfield's left and top anchor relative to the left and top anchor of the view
newTextField.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: tfX).isActive = true
newTextField.topAnchor.constraint(equalTo: self.view.topAnchor, constant: tfY).isActive = true
// repeat for label...
let newLabel: UILabel = self.createLabel(with: labelName)
self.view.addSubview(newLabel)
newLabel.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: labelX).isActive = true
newLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: labelY).isActive = true
}
}
}
// create, configure, and return a new textfield
func createTextfield(with placeholder: String, height: CGFloat) -> UITextField {
let textField = UITextField(frame: .zero) // set the frame to zero because we're going to manage this with constraints
textField.placeholder = placeholder
textField.textAlignment = .left
textField.textColor = .black
textField.borderStyle = .line
textField.autocapitalizationType = .words
// translatesAutoresizingMaskIntoConstraints = false is important here, if you don't set this as false,
// UIKit will automatically create constraints based on the `frame` of the view.
textField.translatesAutoresizingMaskIntoConstraints = false
textField.heightAnchor.constraint(equalToConstant: height).isActive = true
textField.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
return textField
}
// create, configure and return a new label
func createLabel(with labelName: String) -> UILabel {
let label = UILabel()
label.text = labelName
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
}
Answer part 2
I'm struggling to imagine the situation where you actually want to do this. If you are making a UI where elements repeat themselves (like a todo list, or maybe a spreadsheet-type interface) then this approach is not the right way to do.
If you want to create a UI where elements repeat themselves as repeating elements in a single column you should investigate using a UITableViewController where you create a cell that represents a single element, and have a tableview manage that collection of cells.
If you want to create a UI where elements repeat themselves in any other way than a vertical list, then you investigate using UICollectionViewController which is a little more complex, but a lot more powerful.
Apologies if this answer goes off-topic, but hope that inspires some ideas that are useful for you.
Related
Hi I'm just wanting to have a childView centered on the x axis of a NSViewController view using auto layout. I'm doing the following:
override func viewWillAppear()
{
super.viewWillAppear()
let childView = PagesView.init(frame: CGRect(x: 0, y: 0, width:100, height: 100))
childView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(childView)
childView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
The childView doesn't even appear on the screen. When I remove the last line, then it gets positioned at 0,0. So something about that last line is causing it go haywire and I'm not sure why? I've used this exact same logic on iOS and it has worked fine.
All views should have constraints to define its location and size.
// This would create dimension constraints if your class cannot already infer them
childView.widthAnchor.constraint(equalToConstant: 100).isActive = true
childView.heightAnchor.constraint(equalToConstant: 100).isActive = true
You also need to specify a vertical anchor constraint so the view has enough information to be displayed.
childView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
I am trying to update the height of a UITextView based on the content. I have seen this solution but cannot get it to work with my current code (still learning swift)
I define the UITextView as such:
let eventDetailInfoTextBox : UITextView = {
let textbox = UITextView()
textbox.backgroundColor = UIColor.clear
textbox.layer.borderWidth = 1
textbox.layer.borderColor = ColorPallet.AppTertiaryColor.cgColor
textbox.layer.cornerRadius = 10
textbox.setNeedsDisplay()
let contentSize = textbox.sizeThatFits(textbox.bounds.size)
textbox.isEditable = false
return textbox
}()
The subview is then added in setupViews() along with defining its position in the view using a call to setupEventDetailInfoTextBox()
fileprivate func setupEventDetailInfoTextbox() {
print("Event Detail Info Text Box Height: \(eventDetailInfoTextBox.contentSize.height)")
var frame = eventDetailInfoTextBox.frame
frame.size.height = eventDetailInfoTextBox.contentSize.height
eventDetailInfoTextBox.anchor(eventDetailMapView.bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 8, leftConstant: 10, bottomConstant: 0, rightConstant: 10, widthConstant: 0, heightConstant: frame.size.height)
}
The call to .anchor is based on the Lets Build That App frameworks found via this link and basically wraps up the local functions from Xcode. I know this works and have reused the same function repeatedly throughout my app.
The output from the print statement is -8 and is represented by a height suitable to show 1 line of text (sometimes).
Can anyone see why my text box refuses to get any bigger if I have more than 1 line of text?
I'm using IOS 10, Xcode 8 and writing in swift 3.
Follow these step
Create height constraint outlet of TextView.
Calculate content height of Textview.
Update TextView height constraint constant value.
Call layoutIfNeeded() function.
I hope it will work.
If you want something static, down and dirty, you can streamline the process (not set and reset properties) as long as you place everything in the right order. If you need to handle device rotations or other runtime changes, that would require something slightly less down and dirty, but not by much--I'm not sure which you need so I opted for the simpler.
// instance property so that other constraints can refer to it
let descriptionTextView = UITextView()
// configure text view
descriptionTextView.text = descriptionModel
descriptionTextView.font = // UIFont
descriptionTextView.textColor = // UIColor
descriptionTextView.isEditable = false
descriptionTextView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(descriptionTextView)
// set constraints
descriptionTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
descriptionTextView.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 32).isActive = true
descriptionTextView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -32).isActive = true
descriptionTextView.sizeToFit()
descriptionTextView.layoutIfNeeded()
descriptionTextView.heightAnchor.constraint(equalToConstant: descriptionTextView.contentSize.height).isActive = true
I am trying to programmatically constraint a video into the center of the page. My AV controller is called avPlayerController .
I have already given its x and y values along with the width and height:
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
So how do i center it?
I HAVE TRIED: Programmatically Add CenterX/CenterY Constraints
But, as you can guess it did not work :(
Here is my code:
super.viewDidLoad()
let filepath: String? = Bundle.main.path(forResource: "rockline", ofType: "mp4")
let fileURL = URL.init(fileURLWithPath: filepath!)
avPlayer = AVPlayer(url: fileURL)
let avPlayerController = AVPlayerViewController()
avPlayerController.player = avPlayer
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
// hide/show control
avPlayerController.showsPlaybackControls = false
// play video
avPlayerController.player?.play()
self.view.addSubview(avPlayerController.view)
avPlayerController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Here's the code, with explanation.
Always remember that if you are using auto layout constraints, do not set frames. The layout engine will walk all over them. If you are instantiating your view in code, don't set a frame, or if necessary, it communicates things best if you set the frame to CGRect.zero.
Understand the view life cycle. Specifically, you can set your constraints in viewDidLoad, where they should be created only once.
Remember to set the auto resizing mask to false. This is the most common error when you learning auto layout in code.
There are actually three ways to create constraints, and a few ways to activate them. In your question, I think the easiest way is to use anchors.
Here's an example of centering a view (any view) with a width of 343 and a height of 264:
let myView = UIView() // note, I'm not setting any frame
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
myView.translatesAutoresizingMaskIntoConstraints = false
myView.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
myView.heightAnchor.constraint(equalToConstant: 264.0).isActive = true
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
That's all there is to it! BUT....
I'd suggest one more thing. Don't use constants in setting the height and width. That's not being "adaptive". Your 4 inch iPhone SE has a screen size of 568x320, where this may look centered and large enough. But on an iPhone Plus with a screen size of 736x414 it may be pretty small. (To say nothing of a 12.9 inch iPad Pro!)
Notice how my code uses the superview for the centerX/centerY anchors. (And instead of equalToConstant it's equalTo.) Do the same with the width and height. Through the use of multiplier and constant, along with UILayoutGuides, you can make your layouts adapt to whatever screen size Apple throws at you.
You can try this.
avPlayerController.view.enableAutoLayoutConstraint()
avPlayerController.view.setCenterXConstraint(.equal, constantValue: 0)
avPlayerController.view.setCenterYConstraint(.equal, constantValue: 0)
extension UIView
{
//Method to making translatesAutoresizingMaskIntoConstraints false.
func enableAutoLayoutConstraint()
{
self.translatesAutoresizingMaskIntoConstraints = false
}
//Method to set Center-X Consttraint
func setCenterXConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerX, relatedBy: relationType, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
//Method to set Center-Y Consttraint
func setCenterYConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerY, relatedBy: relationType, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
}
I have a variable in my UIViewController that is tied to a constraint setting up the UITextView's height:
var textViewHeight: Int!
Here is the constraint:
self.view.addConstraintsWithFormat(format: "V:|-74-[v0(\(textViewHeight!))]", views: self.textView)
I use this extension:
extension UIView
{
func addConstraintsWithFormat(format: String, views: UIView...)
{
var viewDict = [String: AnyObject]()
for (index, view) in views.enumerated()
{
view.translatesAutoresizingMaskIntoConstraints = false
let key = "v\(index)"
viewDict[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewDict))
}
}
I have set up a Notification that triggers when the keyboard shows up.
It is triggered correctly (I have a print and it always fires correctly) and the function that is executed includes this code:
if let keyboardSize = sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
print(keyboardSize.height)
self.textViewHeight = Int(self.view.frame.height-keyboardSize.height-100)
self.view.updateConstraints()
}
The keyboard's height is printed correctly but the text view's height is not changed.....
Thank you in advance!
Simply setting the constraints once with visual format will not update the constraints later if the variable value (in this case textViewHeight) changes later. So, you'd have to actually set up a constraint via code that can be modified later as the textViewHeight value changes.
Here are the changes you'd need:
1: Add a variable to hold a reference to the constraint you'll want to modify later.
var heightConstraint:NSLayoutConstraint!
2: Create the constraints for your text view individually instead of using the visual format (self.view.addConstraintsWithFormat(format: "V:|-74-[v0(\(textViewHeight!))]", views: self.textView))
// Add vertical constraints individually
let top = NSLayoutConstraint(item:textView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:topLayoutGuide, attribute: NSLayoutAttribute.bottom, multiplier:1.0, constant:74.0)
heightConstraint = NSLayoutConstraint(item:textView, attribute:NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem:nil, attribute:NSLayoutAttribute.notAnAttribute, multiplier:1.0, constant:textViewHeight)
view.addConstraint(top)
view.addConstraint(heightConstraint)
3: You are probably better off changing textViewHeight to CGFloat since all the values you'll deal with there would be CGFloat values rather than Int.
4: Where you get the keyboard notification, after you calculate textViewHeight, add the following line:
self.heightConstraint.constant = textViewHeight
And that should do the trick since now, when textViewHeight changes, the constraint will be updated as well :)
Edit 1
Hello, This is my first time using code to add constraints. I normally just use interface builder. I am trying to add a vertical UISlider, before I thought that mixing visually added constraints was interfering with coded constraints. I have now updated my code and therefore this question to create constrains using ONLY code in this particular view container.
What I have done is I created a view directly underneath another view. I created 3 small views inside that that match the width of the textfields in the view above and then spaced them out the same as how the textfields are spaced out.
For testing purposes only I gave these 3 small views a color to see if it worked, and it does.
When I actually finish the app those red, green, and blue views will be clear. The only reason I wanted them was so when I create the sliders I can constrain each one of them to the center of the view...which is how the labels above the textfields are constrained.
Here is the code for this
which works
// Mark: Hidden View
let leftHiddenView = UIView()
let centerHiddenView = UIView()
let rightHiddenView = UIView()
let hiddenViews = [leftHiddenView, centerHiddenView, rightHiddenView]
for views in hiddenViews {
views.translatesAutoresizingMaskIntoConstraints = false
sliderContainer.addSubview(views)
views.backgroundColor = .white
let widthConstraint = views.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = views.heightAnchor.constraint(equalToConstant: 5)
NSLayoutConstraint.activate([widthConstraint, heightConstraint])
}
let centerViewHorizontalConstraint = centerHiddenView.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
let centerViewTopConstraint = centerHiddenView.topAnchor.constraint(equalTo: sliderContainer.topAnchor, constant: 50)
NSLayoutConstraint.activate([centerViewHorizontalConstraint, centerViewTopConstraint])
let leftViewVerticalCenterConstraint = leftHiddenView.centerYAnchor.constraint(equalTo: centerHiddenView.centerYAnchor, constant: 0)
let leftViewTrailingConstraint = leftHiddenView.trailingAnchor.constraint(equalTo: centerHiddenView.leadingAnchor, constant: -60)
NSLayoutConstraint.activate([leftViewVerticalCenterConstraint, leftViewTrailingConstraint])
let rightViewVerticalCenterConstraint = rightHiddenView.centerYAnchor.constraint(equalTo: centerHiddenView.centerYAnchor, constant: 0)
let rightViewTrailingConstraint = rightHiddenView.leadingAnchor.constraint(equalTo: centerHiddenView.trailingAnchor, constant: 60)
NSLayoutConstraint.activate([rightViewVerticalCenterConstraint, rightViewTrailingConstraint])
Now, I started to add a UISlider as vertical. And the exact same thing that happened before happened now.
As you can see everything breaks.
Here is the code thus far on that
// Mark: Slider View
let leftSlider = UISlider()
let centerSlider = UISlider()
let rightSlider = UISlider()
let colorSliders = [leftSlider, centerSlider, rightSlider]
for slider in colorSliders {
slider.translatesAutoresizingMaskIntoConstraints = false
sliderContainer.addSubview(slider)
let w = sliderContainer.bounds.width
slider.bounds.size.width = w
slider.center = CGPoint(x: w/2, y: w/2)
slider.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
slider.value = 0
slider.minimumValue = 0
slider.maximumValue = 255
let sliderTopConstraint = slider.topAnchor.constraint(equalTo: centerHiddenView.bottomAnchor, constant: 5)
let sliderBottomConstraint = slider.bottomAnchor.constraint(equalTo: sliderContainer.bottomAnchor, constant: 5)
NSLayoutConstraint.activate([sliderTopConstraint, sliderBottomConstraint])
slider.backgroundColor = .purple
}
let centerSliderHorizontalConstraints = centerSlider.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
NSLayoutConstraint.activate([centerSliderHorizontalConstraints])
Don't ever misc the Design time constraints with adding Runtime constraints. Either add all constraints at design time or all constraints at runtime only. Else you will be in MESS. Make this as a good practice.
In case you need to change the frame, just change the constant property of the constraints and add all the required constraint at design time.
There will be very rare times when you need to add runtime constraints. (I am saying this because I always design in that way only. And that helps me a lot.) Design your screen in such a ways that even if you need to add 2 controls for dynamic UI changes, then keep 2 controls and do show hide with that controls. In can you need some kind of Animation with your controls, you don't need to change design time constraints.
I know this does not answer your question directly, but hope you will get the understanding of how to use constraints.
From your screenshot, I am not able understand exactly what is your UI looks like. Can you give little bit more idea of how your UI looks like? so that I can suggest some idea of how to give constraints...
Well, it turns out that the problem was actually quite easy to solve from the beginning. I only overlooked it because of being intimidated by the vertical UISlider. Since I did not give the above container a fixed height when I added the container below it and ran the app, the containers equally filled the space and the contents inside messed up accordingly. I simply gave the top container with just the labels and textfield a fixed height of 61 and its now much closer to being done. Sorry