I have a UITableView with constraints. When, I try to retrieve the constraints for a UITableView, it's returning 0 elements. Any help?
self.tableView.constraints
Retrieving constraints programatically is not a very good idea.
You can get all the constraints affecting a view using constraintsAffectingLayout(for:), however, you will probably get much more constraints than you expect.
The best solution is to save references to given constraints when you are adding them. If you are using a storyboard or a xib, create an IBOutlet for them.
You need to get table view superview constraints and check the table view as first or second item.
for constraint in (self.tableView.superview?.constraints)! {
if constraint.firstItem as! NSObject == self.tableView ||
constraint.secondItem as! NSObject == self.tableView {
// table view constraints
}
}
Depends where the constraints are applied.
If you have a UITableView in UIViewController and add constraints, the constraints are applied in UIViewController not in UITableView
Use this code instead:
self.tableView.superview?.constraints
Related
I have simple application which contains UICollectionViewController with custom UICollectionViewCell. The controller is placed in Mainstoryboard (I use native xamarin). My application works, but I want to add a common 'header' control above of my collection view (with buttons and labels which will work with/describe collection).
I had expected, it can be done by UIStackView when my already created collection view would be in the bottom of StackView and my newly creted 'header' control in the top. But as I may see, I cannot add my UICollectionViewController as item of Stack view.
I think it can be done by TabBarController, but it looks like it's not a good idea, especially If i have more then one a potencial control buttons for my collection view.
Maybe there is a better solution?
Could someone advise me?
You can Create a new UIViewController, put a UIStackView in it. Then initialize your UICollectionViewController in Storyboard, add its view in the StackView:
UIView headerView = new UIView();
...//Add some items in this view
//Add a height constraint
headerView.AddConstraint(NSLayoutConstraint.Create(headerView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1, 200));
MyStack.AddArrangedSubview(headerView);
MyCollectionView collection = Storyboard.InstantiateViewController("MyCollectionView") as MyCollectionView;
MyStack.AddArrangedSubview(collection.View);
But if you only want to add a header view, I recommend you to use UICollectionView's Header.
Firstly, Use the event below to create the header in the CollectionView's
Source:
public override UICollectionReusableView GetViewForSupplementaryElement(UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
{
if (elementKind == "UICollectionElementKindSectionHeader" && indexPath.Section == 0)
{
UICollectionReusableView header = collectionView.DequeueReusableSupplementaryView(new NSString("UICollectionElementKindSectionHeader"), "MyHeader", indexPath);
//Add some items
//header.AddSubview();
return header;
}
return null;
}
Do not forget register the Header Class: CollectionView.RegisterClassForSupplementaryView(typeof(UICollectionReusableView), new NSString("UICollectionElementKindSectionHeader"), "MyHeader");
At last set the Header's height to show it:
// This event exists in the UICollectionViewDelegateFlowLayout
public override CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
if (section == 0)
{
return new CGSize(UIScreen.MainScreen.Bounds.Size.Width, 300);
}
return CGSize.Empty;
}
You can add your UICollectionView as a subview to UIStackView by creating a UIViewController in storyboard and just drag and drop UIStackView and UICollectionView as a subview inside it. and assign the View Controller as the Collection View’s data source property
For Common header control, you might wanna a create a custom UIView using either XIB or by programmatic approach by creating a sub class of UIView.
so your controller hierarchy would be
->UIViewController
->UIStackView
->UIView
->UICollectionView
However, UIStackView is ideal for situation where you have a complex view hierarchy where you don't have to deal with multiple auto layout constraints by yourself, but it has some common issues you might want to look at this post.
If that's not the case then you should use a simpler approach and just add constraints manually to layout your views.
I have UITableViewController, on top of it I placed an UIView and inside it there is a UICollectionView.
When collectionView data is empty I want to remove the UIView. I tried:
self.collectionView.removeFromSuperview()
and
self.collectionView.hidden = true
These removes the UIView but there is an empty space above my tableview. How can I get rid of it?
Edit: added a photo of my storyboard. Recommended View is not inside the tableviewheader.
It looks like the collection view is a table header view (the way you have positioned it in the storyboard).
Try setting it to nil if data is empty
self.tableView.tableHeaderView = nil
I haven't tried but this code should solve your problem.
self.tableView.tableHeaderView?.isHidden = true
You have placed Recommended View inside the table view - not on top of it. So you could have placed it as header or cell. So when you don't want it to be present, change cell pr header height.
The RecommendedView is a tableHeaderView. Try to have a outlet to the height constraint of the RecommendedView.
Set the height constraint to 0 in case you don't have any element in the collection view.
#IBOutlet var tableHeaderHeighConstraint: NSLayoutConstraint!
override func viewDidLoad(){
if collectionIsEmpty(){
tableHeaderHeighConstraint.constant = 0
self.tableView.tableHeaderView?.layoutIfNeeded()
}
}
Hi Is it possible to combine to combine a UIView with a UITableView and reference it as a single object? I currently have a both combined as a Stack, but I feel there is a better way to do it. Thanks
They are two different objects, one property cannot reference two different views. You can however use an IBOutlet for both views in your ViewController, or within any ancestor view.
class MyViewController : UIViewController {
#IBOutlet var tableView : UITableView!
}
Then, you can just drag a reference outlet for that specific view from Storyboard to the view controller. For information on that:
https://developer.apple.com/library/ios/recipes/xcode_help-IB_connections/chapters/CreatingOutlet.html
Note that I did not add a property for your UIView because I assumed it is already the main view of the controller. Similarly, you can subclass your main view, add an outlet for the table view, and use that as the reference outlet.
In my opinion, there are two way to resolve your require:
Option 1. use table view header view. I suppose you want to combine a view named topView with a table view. call tableView.tableHeaderView = topView
Option 2. add topView to table view as a subview, then adjust contentInset of table view. Just like:
UIEdgeInsets tableViewInsets = self.tableView.contentInset;
tableViewInsets.top = topView.frame.size.height;
self.tableView.contentInset = tableViewInsets;
I have two table views set up side by side, and I need them to scroll at exactly the same time. So, when you scroll one, the other one will scroll at the same time.
I did some searching and I couldn't find any information, but I assume it must be possible somehow.
My table views are both connected to the same class and I differentiate between them like this:
if tableView == tableView1 {
//
} else if tableView = tableView2 {
//
}
You can get set the scrollView delegate to self on both of your tableView's scrollViews. And in -scrollViewDidScroll, take the contentOffset and set the other scrollView's contentOffset to the same value.
Like Schemetrical said you should use scrollViewDidScroll.
see the first answer of this:
Scrolling two UITableViews together
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
UITableView *slaveTable = nil;
if (self.table1 == scrollView) {
slaveTable = self.table2;
} else if (self.table2 == scrollView) {
slaveTable = self.table1;
}
[slaveTable setContentOffset:scrollView.contentOffset];
}
If you want them to scroll in prefect lock-step then this isn't a trivial problem. UITableView is a subclass of UIScrollview, so you could probably create a custom subclass of UITableView that overrode various UIScrollView methods and when something caused the table view to scroll, it would do the same thing to the other table view.
Edit: #Schemetrical's suggestion of using the scroll view delegate is cleaner than creating subclasses. You might have to monitor quite a few of the scroll view delegate methods and use them to match the behavior in the other scroll view.
EDIT #2:
Apparently I'm wrong and scrollViewDidScroll is called for every change in the scroll view, so it's simpler than I thought to keep them synced. I'm going to leave my answer for context even though I was wrong.
After doing some visual layout in Interface Builder I've created some constraints that I want to access at runtime. Is there a way to label or identify constraints in Interface Builder so they can be looked-up later?
The reason I want to do this is I need to perform some calculation base upon the constraints that are visually specified. I am aware that Apple has provided the Visual Format Language and I could specify the constraints programmatically in a controller. I rather not use this approach so I don't lose the design time feedback of IB.
Edit
Making a referencing outlet did work, but the question still stands.
Update:
As explained by Bartłomiej Semańczyk in his answer, there is now an Identifier field visible in the Attributes Inspector for the NSLayoutConstraint making it unnecessary to expose this field yourself. Just select the constraint in the Document Outline view or in the Storyboard view and then add an identifier in the Attributes Inspector on the right.
Earlier Answer:
Yes, this can be done. NSLayoutConstraint has a property called identifier than can be exposed in Interface Builder and assigned. To demo this, I created a Single View Application that has a single subview that is a red box. This subview has 4 constraints: width, height, centered horizontally in container, centered vertically in container. I gave the width constraint the identifier redBoxWidth by doing the following:
Click on the width constraint in the Document Layout View. Then in the Identity Inspector under User Defined Runtime Attributes, click on the + under Key Path. Change keyPath to identifier, change the Type Boolean to String, and set the Value to redBoxWidth.
Then in ViewDidLoad it is possible to find the constraint by name and change its value:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews as [UIView] {
for constraint in subview.constraints() as [NSLayoutConstraint] {
if constraint.identifier == "redBoxWidth" {
constraint.constant = 300
}
}
}
}
}
Since Xcode 7 you can do it in storyboard:
However if you set up your constraint in code, do the following:
let constraint = NSLayoutConstraint()
constraint.identifier = "identifier"
For these constraints you have set up in storyboard, but you must set identifier in code:
for subview in view.subviews {
for constraint in subview.constraints() {
constraint.identifier = "identifier"
}
}
Also you could link constraint to properties of your controller as you do for any other components. Just ctrl drag it into your code:
And then it will be accessible in code of your controller as property:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *myConstraint;
And you could change it value for example:
self.myConstraint.constant=100.;
Just adding on to #vacawama's answer. You can write a UIView category and pull out constraints using a simple function, as opposed to copy/pasting loops everywhere you need to find a constraint:
.h file:
#import <UIKit/UIKit.h>
#interface UIView (EasyAutolayout)
-(NSLayoutConstraint *)constraintForIdentifier:(NSString *)identifier;
#end
.m file:
#import "UIView+EasyAutolayout.h"
#implementation UIView (EasyAutolayout)
-(NSLayoutConstraint *)constraintForIdentifier:(NSString *)identifier {
for (NSLayoutConstraint *constraint in self.constraints) {
if ([constraint.identifier isEqualToString:identifier]) {
return constraint;
}
}
return nil;
}
#end
Take the IBOutlet of your auto layout constraint.
There is one property called constant in the NSLayoutConstraint class.
For eg, you've taken the IBOutlet of height constraint of any of the views from your IB, and you want to change it's height programmatically, all you need to do is:
constraint.constant = isMoreHeight ? height1 : height2;
After doing this, you need to update all other views in the view hierarchy of the superview. To do this you'll need to write below line:
[self setLayoutIfNeeded];
For better user experience, you can put this line inside your animations block for smoother transition effects,
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
}];
Hope this helps..
What about this:
if let myconstraint = self.view.constraints.filter( { $0.identifier == "myconstraintId" }).first {
// TODO: your code here...
}
my two cents for OSX (previous answers deal with UIKit..)
it work for OSX, too:
final private func getConstraintForButton(){
let constraints = self.myButton.constraints
for constraint in constraints {
if constraint.identifier == "redBoxWidth" {
constraint.constant = 300
break
}
}
}
Note: seems NOT working on custom NSView, instead...