I add a UILabel (amountLabel) in UIViewController in storyboard editor. And then in swift file, viewDidLoad, I programatically create a UITextField (paymentTextField) and try to add a constraint between amountLabel and paymentTextField. Here is my code in viewDidload:
let paymentTextField = UITextField()
paymentTextField.translatesAutoresizingMaskIntoConstraints = false
paymentTextField.frame = CGRectMake(15, 100, CGRectGetWidth(self.view.frame) - 30, 44)
self.view.addSubview(paymentTextField)
let bottonConstraint = NSLayoutConstraint(item: paymentTextField, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.amountLabel , attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 30)
bottonConstraint.identifier = "paymentTextFieldSpacing"
NSLayoutConstraint.activateConstraints([bottonConstraint])
But I get an error:
"Terminating app due to uncaught exception 'NSGenericException',
reason: 'Unable to activate constraint with items > and > because they have no common ancestor.
Does the constraint reference items in different view hierarchies?
That's illegal."
Does anyone know what is wrong? amountLabel is directly dragged to the view in storyboard and "paymentTextField" is added programmatically to the same view. Why have they no common ancestor?
I ran into the same problem that you described earlier. In order to make the programmatic subview, (in your case the paymentTextField) you have to add this to the subview first and then apply your constraints.
By adding the subview to view first, this ensures both views have the same parent.
Checklist for this ISSUE:
Check whether you added the programmatically created view to its parent before activating constraints
Check whether you write constraints activation code inside viewDidLoad() / viewWillAppear(). You should write constraints activation code in updateViewConstraints or viewWillLayoutSubviews. ( suggested by vmeyer )
Check whether you turn off translatesAutoresizingMaskIntoConstraints.
The error states that "because they have no common ancestor", which means that they don't share the same parent. In order to relate constraint between two items, they have to have a child-parent relationship or a sibling one.
In your case just make sure they have the same parent view before adding the constraint programmatically.
Make sure you added the paymentTextField to your view:
paymentTextField.translatesAutoResizingMaskIntoConstraints = false
view.addSubview(paymentTextField)
...
Add your constraints, for example paymentTextField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
let nameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
if you forgot setting this label.translatesAutoresizingMaskIntoConstraints = false
or if you add subview after constraints
this problem arises
SWIFT 4 & 5
final class VoiceSearchViewController: UIViewController {
// MARK: - Properties
var backgroundView: UIView = {
let view = UIView()
view.layer.cornerRadius = 20
view.backgroundColor = .red
return view
}()
// MARK: - Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
initialSetup()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
dismiss(animated: true, completion: nil)
}
// MARK: - Private Methods
private func initialSetup() {
view.backgroundColor = .clear
view.addSubview(backgroundView)
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
backgroundView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
}
Related
coinsRightToSuperviewConstraint?.label = "coins2superview"
gives me "label is inaccessible due to internal protection level.
Is it only possible to give views and constrains names in xib editor to make
any sense or the view constraint maze in visual debugger?
Is it only possible to give views and constrains names in xib editor to make any sense or the view constraint maze in visual debugger?
Not only is it possible, it's essential.
Let's start with views. You can set a view's "label" in Interface Builder:
The value that you enter here is used in the "document browser" of Interface Builder.
But that doesn't help you when debugging in code. For that, here's an extension that gives a view a name property:
extension UIView {
#IBInspectable var name : String? {
get { return self.layer.name }
set { self.layer.name = newValue }
}
}
That's an inspectable property, so it shows up in Interface Builder.
Now let's talk about constraints. If you make a constraint in code, you'll probably call activate on it; here's an extension that lets you add an identifier at that time:
extension NSLayoutConstraint {
func activate(withIdentifier id: String) {
(self.identifier, self.isActive) = (id, true)
}
}
If you create a constraint in Interface Builder, the constraint's identifier is directly available already:
Finally, don't forget about the View Debugger. It's an important way to figure out what's going on.
You can add identifier instead of label:
coinsRightToSuperviewConstraint?.identifier = "coins2superview"
Not really clear what you're going for, but here is an example that shows setting the .identifier on a constraint:
class ConstraintIDViewController: UIViewController {
let label = UILabel()
var labelTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Testing"
label.backgroundColor = .cyan
view.addSubview(label)
let g = view.safeAreaLayoutGuide
labelTopConstraint = label.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0)
labelTopConstraint.identifier = "LabelTopID"
NSLayoutConstraint.activate([
labelTopConstraint,
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
view.constraints.forEach { c in
if c.identifier == "LabelTopID" {
print("found constraint:", c)
}
}
}
}
the debug console output is:
found constraint: <NSLayoutConstraint:0x6000036dc870 'LabelTopID' UILabel:0x7fd404d09770.top == UILayoutGuide:0x600002cc01c0'UIViewSafeAreaLayoutGuide'.top + 40 (active)>
and Debug View Hierarchy shows:
I am building a project without storyboard. Everything is working fine but I can't seem to figure why I can't add tableView programmatically. I have tried the same code for adding tableView in another empty project and its working fine but inside my project the tableview is not showing up. My view hierarchy is like below.
I have a BaseClass like this:
class BaseController: UIViewController{
override func viewDidLoad() {
setupViews()
}
func setupViews(){
}
}
Then I have firstViewController class inherited from Base Class:
class firstViewController: BaseController
Inside my firstController, I am declaring and initializing my tableView:
var tableView:UITableView = {
let tbl = UITableView()
tbl.translatesAutoresizingMaskIntoConstraints = false
tbl.backgroundColor = .blue
return tbl
}()
Then I am overriding setupView() inside firstViewController here like below:
override setupView() {
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
])
}
It should show empty tableView cells but it is not showing up. I guess there is something to do with the base and derive class thing but I can't figure out the exact problem.
You need to set trailing constraint for tableview correctly.
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
You were setting view.leading to tableview.trailing that makes your tableview invisible from current view.
First off, please do not propose a "clever" solution suggesting I remove my TableViewController as a child view. Thank you.
Summary
I am adding a Tableviewcontroller programatically , as a child of a view with a fixed size of 216. I have been messing with constraints....and using the View Hierachy Debugger, I see the TableView always has a height of 852...which is basically the full size of the screen. How can I properly size the TableView to its containing view?
enter image description here
Below is a bunch of the code I am trying to use to constrain things...to no avail. Thank you.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var xyz: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let child = UITableViewController()
xyz.addSubview(child.view)
self.addChild(child)
child.didMove(toParent: self)
//child.view.translatesAutoresizingMaskIntoConstraints = false
let safeArea = xyz.layoutMarginsGuide
var height = child.view.heightAnchor.constraint(equalToConstant: 292)
height = height.constraintWithMultiplier(2000)
height.isActive = true
child.view.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
child.view.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
child.view.leftAnchor.constraint(equalTo: safeArea.leftAnchor).isActive = true
child.view.rightAnchor.constraint(equalTo: safeArea.rightAnchor).isActive = true
}
}
extension NSLayoutConstraint {
func constraintWithMultiplier(_ multiplier: CGFloat) -> NSLayoutConstraint {
return NSLayoutConstraint(item: self.firstItem!, attribute: self.firstAttribute, relatedBy: self.relation, toItem: self.secondItem, attribute: self.secondAttribute, multiplier: multiplier, constant: self.constant)
}
}
Uncomment this line of code
child.view.translatesAutoresizingMaskIntoConstraints = false
Due to the extensive updates since iOS7, I wanted to ask this question because of my limited experience with autolayout and the new stackview, and I am wondering what is the best design practice to implement the following in Objective-C (not swift yet):
In my view, there is a container scroll view, with a child container UIView. Within this UIView, there are a number of elements. One of the elements is a stack of UIViews which differ in number once in a while.
This element is followed by a map and other views.
This is how I plan on organizing it:
Questions
Is this the correct thing to do? How would I modify the height constraint for the stackview when I remove and add elements programmatically?
How do you add a subview to the UIStackView through interface builder? When I do, the subview takes the size of the containing stackview.
Swift 4.2
If you want use code instead of story board, i create an example using auto layout that don't need to estimate size of content.
you just need to add to stack view or remove from it and scroll height modify automatically.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollViewContainer.addArrangedSubview(redView)
scrollViewContainer.addArrangedSubview(blueView)
scrollViewContainer.addArrangedSubview(greenView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// this is important for scrolling
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 500).isActive = true
view.backgroundColor = .red
return view
}()
let blueView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.backgroundColor = .blue
return view
}()
let greenView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
view.backgroundColor = .green
return view
}()
}
So you might want to make the whole layout contained within the stackview. Just something to consider.
There isn't really any right way to do things. I would not set a height constraint on your UIStackView (do add a width constraint that's equal to the view's width). Only set it on the elements you add to the stack view. You will get an error, but it's just IB complaining until you add an element to your UIStackView.
To size the elements in your stackview, you have to set a horizontal constraint on them. You can then modify that single horizontal constraint in code to change the height of the object.
To add a subview you simply do:
stackView.addArrangedSubview(childVC.view)
Or in interface builder, you just drag the element into the stack view. Make sure it has that horizontal constraint or it will resize on you.
Ok so I'm struggling here and haven't been able to find a working solution. I've been self learning Swift without Objective C experience (I know, I know).
In my app, I have my main UIViewController, a subview that is transparent but slides in from the bottom of the screen, and then 4 subviews of the sliding subview that are all working UIScrollViews. I have paging enabled and it works great but I'd like to add a UIPageControl for each of them. I seriously can't grasp delegates and how to implement the using swift. Any help would be much appreciated!
Also, I'm doing this all programmatically, so no IB please. Happy to provide code if it'll help. Thanks
I think you and/or anyone else looking for how to do this will find this answer helpful. The code example enabled me to create a page control indicator on my scrollView, and it was the first time attempting to do this. I found it very clear.
The lines you probably need to add to your project are:
1: add UIScrollViewDelegate as a protocol when you first name your view controller class.
2: in the class declaration create a pageControl variable. You will need to play with the frame numbers to get it to appear where you want it. the current numbers made one in the middle of the window for me.
For reference the numbers mean (x position for top left corner of indicator, y coordinate for top left corner, width of page indicator, height of page indicator)
var pageControl : UIPageControl = UIPageControl(frame: CGRectMake(50, 300, 200, 20))
in viewDidLoad set the scrollView delegate and call `configurePageControl():
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
configurePageControl()
}
you need to add two methods after viewDidLoad. one is called in viewDidLoad
func configurePageControl() {
self.pageControl.numberOfPages = <some reference to the number of pages>
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.redColor()
self.pageControl.pageIndicatorTintColor = UIColor.blackColor()
self.pageControl.currentPageIndicatorTintColor = UIColor.greenColor()
self.view.addSubview(pageControl)
}
and
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
The scrollView delegate is actually very simple to set up. Add UIScollViewDelegate as a protocol that your ViewController class will implement by adding it after the class declaration: class YourClassName: UIScrollViewDelegate. And then in viewDidLoad(), you complete the delegate setup by assigning the scroll view's delegate property to your class with the line scrollView.delegate = self. (again see the example I linked for if you need further clarification of where these commands go)
Just setup it in code like this:
private var pageControl = UIPageControl(frame: .zero)
private func setupPageControl() {
pageControl.numberOfPages = controllers.count
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.currentPageIndicatorTintColor = UIColor.orange
pageControl.pageIndicatorTintColor = UIColor.lightGray.withAlphaComponent(0.8)
let leading = NSLayoutConstraint(item: pageControl, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0)
let trailing = NSLayoutConstraint(item: pageControl, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0)
let bottom = NSLayoutConstraint(item: pageControl, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.insertSubview(pageControl, at: 0)
view.bringSubview(toFront: pageControl)
view.addConstraints([leading, trailing, bottom])
}
UIPageControl Integration using Swift 4
func configurePageControl() {
self.pageview.numberOfPages = items.count
self.pageview.currentPage = 0
self.pageview.tintColor = UIColor.red
self.pageview.pageIndicatorTintColor = UIColor.black
self.pageview.currentPageIndicatorTintColor = UIColor.green
}
Delegates are just methods of a class, that can be palmed off to another class. These methods are usually callbacks.
e.g. A callback for a TextField, when the user hits return, can be implemented in a Delegate. The delegate can be implemented in the ViewController class.
Now when the user hits return the TextField Object will call the delegate method in the ViewController object. The great thing about this is, you can access all the variables and methods of the ViewController object from the delegated method. Otherwise you would need a handle to the ViewController object within the TextField object itself.
Delegates are implemented as protocols, which are just interfaces. So if the ViewController implements the TextFieldDelegate protocol, all your textfield callbacks can be called from within the ViewController object.
I hope this helps.