Export or Print Auto Layout Constraints from Storyboard iOS - ios

Is it possible to print out the constraints set up in auto layout that you create in a Storyboard? I would like to re-create what I have done in the Storyboard in code.

All instances of UIView has a property named constraints. That is an NSArray of all the NSLayoutConstraint's associated with that particular view. You could traverse a view hierarchy recursively from the root view and then print out the desired layout constraint properties.
// for each view in hierarchy
for (NSLayoutConstraint *constraint in view.constraints) {
// print what you need here
}

Have you tried to open the storyboard file in an external editor (not xCode)?
You can read it, it's just xml.

If you need print the view you should find the constraints in the super view. For example:
view.superview?.constraints.forEach { (constraints: NSLayoutConstraint) in
// If you want to find some specific constrainst
if constraints.firstItem as! NSObject == view && (constraints.firstAttribute == .centerX) {
// Remove it.
constraints.isActive = false
}
}
The accepted answer should only print height and width of the view itself.

Related

How do I resize my UITableView using Autolayout?

I have a UITableView in a ViewController in a Storyboard (not a UITableViewController). What I want to do is add a custom UIView above the TableView in code. When the View is not there, the TableView's top anchor is anchored to PrimaryNavCollectionViewOutlet. I store this constraint as an outlet, and then if I have to add the View, I can use this outlet to remove the storyboard constraint.
I then constrain the inserted View to be below where the TableView was, and constraint the TableView to be below that.
Here's my code:
if (_viewAboveTableView != null)
{
TableView.RemoveConstraint(TableViewTopConstraint);
View.AddSubview(_viewAboveTableView);
_viewAboveTableView.TranslatesAutoresizingMaskIntoConstraints = false;
TableView.TranslatesAutoresizingMaskIntoConstraints = false;
_viewAboveTableView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor).Active = true;
_viewAboveTableView.TopAnchor.ConstraintEqualTo(PrimaryNavCollectionViewOutlet.BottomAnchor).Active = true;
_viewAboveTableView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor).Active = true;
_viewAboveTableView.BottomAnchor.ConstraintEqualTo(TableView.TopAnchor).Active = true;
}
Unfortunately, when I run it, I can't see _viewAboveTableView. I feel like I'm missing something easy, but I can't figure out what. I've tried LayoutIfNeeded() and a few other methods on the View, but they don't make it appear. What have I missed?
Add height constraint to _viewAboveTableView.
Or
Make sure _viewAboveTableView has content that will specify intrinsic content size -- it should have properly laid-out subviews with content size (intrinsic or explicit via layouts. e.g. image, label etc.)
My guess is _viewAboveTableView is not getting inflated because it doesn't specify intrinsic content size (height > 0). If it's wrong constraints, you should be able to see it in the console.
It looks like the problem with what I was doing was that TableView.RemoveConstraint(TableViewTopConstraint); didn't have the intended effect.
From this answer I learned that RemoveConstraint is, or is about to be, deprecated in favour of TableViewTopConstraint.Active = false;. This made my code work the way that was intended.

How to set alignments right for dynamic UIStackView with inner XIB

My original ViewController consists of only one scrollView like this:
Now I also have my own xib file (CheckBoxView) which mainly consists of one button, see this screenshot:
I dynamically create some UIStackViews and add them to the ScrollView Inside these UIStackViews I add multiple instances of my xib file.
What I want to achieve is, that the StackViews are just vertically stacked. And inside the StackViews the UIViews from my xib-file should also be vertically stacked.
At the moment it looks like this:
So the xib-Views are not in the whole view. Since I am using multi-os-engine I can't provide swift/obj-c code. But here is my Java-Code:
for (ItemConfiguration config : itemInstance.getConfigurations()) {
List<DLRadioButton> radioButtons = new ArrayList<DLRadioButton>();
UIStackView configView = UIStackView.alloc().initWithFrame(new CGRect(new CGPoint(0, barHeight), new CGSize(displayWidth, displayHeight - barHeight)));
configView.setAxis(UILayoutConstraintAxis.Vertical);
configView.setDistribution(UIStackViewDistribution.EqualSpacing);
configView.setAlignment(UIStackViewAlignment.Center);
configView.setSpacing(30);
for (ConfigurationOption option : config.getOptions()) {
UIView checkBox = instantiateFromNib("CheckBoxView");
for (UIView v : checkBox.subviews()) {
if (v instanceof DLRadioButton) {
((DLRadioButton) v).setTitleForState(option.getName(), UIControlState.Normal);
//((DLRadioButton) v).setIconSquare(true);
radioButtons.add((DLRadioButton) v);
}
}
configView.addArrangedSubview(checkBox);
}
// group radiobuttons
//groupRadioButtons(radioButtons);
configView.setTranslatesAutoresizingMaskIntoConstraints(false);
scrollView().addSubview(configView);
configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
}
private UIView instantiateFromNib(String name) {
return (UIView) UINib.nibWithNibNameBundle(name, null).instantiateWithOwnerOptions(null, null).firstObject();
}
How do I need to set the Alignments etc. to Achieve what I want. It should look like this:
I don't know if there is a reason to not use UITableView, that i highly recommend for your case. In case it's not possible, below you can find some pieces of advice that should help.
If you use Auto Layout, you should set constraints for all views instantiated in your code. The constraints must be comprehensive for iOS to know each view's position and size.
Remove redundant constraints
configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
These two constraint just doesn't make sense to me. You need the stackviews be stacked within you ScrollView, but not centered. If i understand you goal correctly, this should be removed
Set width/x-position constraints for UIStackViews
configView.setTranslatesAutoresizingMaskIntoConstraints(false);
scrollView().addSubview(configView);
Right after a stack view is added to the ScrollView, you need to set up constraints for it. I'll provide my code in swift, but it looks quite similar to what your Java code is doing, so hopefully you'll be able to transpile it without difficulties:
NSLayoutConstraint.activate([
configView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor),
configView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor)
]);
Set height constraints for UIStackViews
StackViews doesn't change their size whenever you add arranged view in it. So you need to calculate a desired stackview size yourself and specify it explicitly via constraints. It should be enough to accommodate items and spaces between them. I suppose that all items should be of the same size, let it be 32 points, then height should be:
let stackViewHeight = items.count * 32 + stackView.space * (items.count + 1)
And make new height constraint for the stack view:
configView.heightAnchor.constraint(equalToConstant: stackViewHeight).isActive = true
Set y-position for UIStackView
This is a little bit more challenging part, but the most important for the views to work properly in a scroll view.
1) Change loop to know the index of a UIStackView
A scroll view should always be aware of height of its content, so you need to understand which stack view is the top one, and which is the bottom. In order to do that, you need to change for each loop to be written as for(;;) loop:
for (int i = 0; i < itemInstance.getConfigurations().length; i++) {
ItemConfiguration config = itemInstance.getConfigurations()[i]
...
}
I'm not aware of which type your array is, so if it doesn't have subscript functionality, just replace it with corresponding method.
2) Set top anchor for stack views
For the first stack view in the array, top anchor should be equal to the scroll view top anchor, for others it should be bottom anchor of the previous stack view + spacing between them (say, 8 points in this example):
if i == 0 {
configView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor, constant: 8).isActive = true
} else {
let previousConfigView = itemInstance.getConfigurations()[i - 1]
configView.topAnchor.constraintEqualToAnchor(previousConfigView.bottomAnchor, constant: 8).isActive = true
}
3) Set bottom anchor for the last stack view
As was said - for the Scroll View to be aware of content size, we need to specify corresponding constraints:
if i == itemInstance.getConfigurations() - 1 {
configView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor, constant: 8).isActive = true
}
Note: please be advised, that all constraints should be set on views that are already added to the scroll view.

Update widthAnchor on Device orientation

I have this constraint in my controller for a view like this:
someView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
Now I've added this constraint in willLayoutSubviews to update it on device rotation.
But it doesn't seem to update, even more like it adds another width constraint, which of course conflicts with the old width constraint.
Now I don't really know a proper solution to update this width constraint, but it seems to me like I need to remove the constraint first and then set it again.
Which if I test this like this:
someView.constraints.forEach {
someView.removeContraint($0)
}
This works like expected, only it deletes of course some constraints I don't want to delete... so also not a solution.
Sol1
hook the width constraint as IBOutlet and change it's constant in code like this
self.widthCon.constant = //value
self.view.layoutIfNeeded()
Sol2
delete the constraint with identifier
someView.constraints.forEach {
if $0.identifier == "set_id" {
someView.removeConstraint($0)
someView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
}
}
Alternate Solution:
If you have a reference to the superview of someView then you can simply assign equal width constraints like so:
someView.widthAnchor.constraint(equalTo: superView.widthAnchor)
This way you can let the Autolayout engine update the width automatically when the device orientation changes.

How do I update a constraint from the Storyboard without an IBOutlet or Identifier?

I have a lot of views that are created in the storyboard, but I want them to be able to update their constraints dynamically without having to use an IBOutlet each time.
I started by making a custom class for the superview of the view I want to update, and change its subview's bottom constraint like this:
myView.constraints.filter{ $0.firstAnchor is NSLayoutAttribute.bottom }.constant -= 200
'NSLayoutAttribute.bottom' doesn't seem to be the correct way to check the type of the Anchor.
How do I check the type of the constraints I want to change?
Am I correct in updating the constraints in the superview of the view I want to change, not the view itself?
NSLayoutConstraint from iOS7 have a property called identifier, from code or from IB you can set this property.
After that to get the constraint you are looking for is just a matter of searching it in a particular view.
Consider this UIView extension:
func constraint(withIdentifier:String) -> NSLayoutConstraint? {
return constraints.filter{ $0.identifier == withIdentifier }.first
}
As per dahlia_boy's suggestion, I used UIView.animate to achieve this functionality, however it doesn't seem to be permanent:
translatesAutoresizingMaskIntoConstraints = true
UIView.animate(withDuration: 1, animations: {
self.frame.size.height -= 200
})

How to access Constraint Data Programatically?

I would like to access firstAttribute constraint property programatically. so that i can able to find if a constraint is height constraint,width constraint,topMargin constraint etc...
when i try,
print(constraint.firstAttribute)
i get the general result as
"NSLayoutAttribute"
but what i want more specific result to treat the constraints differently based on it attribute(height,width,topMargin etc..) type.
The property on the constraint that you're looking for is firstAttribute (or secondAttribute). However, there's really no reason you should need to "guess" which constraint you're looking at - just store the constraints as properties and name them accordingly.
EDIT: to tell which type of attribute you have, switch over it:
switch myConstraint.firstAttribute {
case .height:
// height constraint
case .leading:
// leading constraint
case .topMargin:
// top margin constraint
// etc
}
The solution for this is to store the constraint as a property. You can do this with an outlet from a storyboard or nib (just like a button or label) or in code.
That way, you don’t have to dive into the view to try and find the constraint. You just access it like...
buttonHeightConstraint.constant = 50
Etc...
For me firstAttribute didn't work at all, and also, it's not compatible with versions prior to iOS 9.0
So I finally solved by assigning an identifier to the NSLayoutConstraint object. Then, you can iterate though the constraints of your view and compare the identifier to retrieve the desired constraint.
yourConstraint.identifier = "yourString"
So:
for tmpConst in self.view.constraints {
if tmpConst.identifier == "yourString" {
return tmpConst
}
}
Will return your constraint.

Resources