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.
Related
I have a custom UIView that's my little component lets call it PlaceholderView.
My UITableViewCell prototype has a Label and a PlaceholderView that sits inside a UIStackView that's vertically axis.
The PlaceholderView, is supposed to call some custom code that goes to a cache to retrieve a specific view then it adds it to the SubView of the PlaceholderView.
I want this subview to take up the whole surface of the entire PlaceholderView. How do I go about doing that? I tried this but not sure if it does the job
if (view != null)
{
AddSubview(view);
view.SizeToFit();
}
Second question. These view's that I am adding, when I create them during design time, I make a new storyboard, drag and drop a ViewController then proceed to place other controls like Labels and Button's on it.
How do I restrict this ViewController's overall height so it's completely fixed size? I know I can set the simulated metrics, and I am also setting the View. Frame's size to restrict the height.
Are there better ways to make these views constrained easier?
Currently, when I am setting these fixed height values, it does cause some weird issues with overlaps if I set UITableView.RowHeigh to AutomaticDimension.
// attaches all sides of the child to its parent view
extension UIView {
func layoutToSuperview() {
guard let view = self.superview else {return}
self.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
self.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}
Usage:
Instead of view.SizeToFit() use view.layoutToSuperview()
I want this subview to take up the whole surface of the entire
PlaceholderView
You can try autolayout:
if (view != null)
{
view.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(view);
view.LeadingAnchor.ConstraintEqualTo(this.LeadingAnchor).Active = true;
view.TopAnchor.ConstraintEqualTo(this.TopAnchor).Active = true;
view.TrailingAnchor.ConstraintEqualTo(this.TrailingAnchor).Active = true;
view.BottomAnchor.ConstraintEqualTo(this.BottomAnchor).Active = true;
}
Are there better ways to make these views constrained easier?
The answer is also auto layout. Set a control's height constraint to claim how much space it wants to take. See this thread for more details: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
Though it is native oc, you can see its concept.
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.
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.
I want to create a page which has a vertical but no horizontal scroll. It must adjust width of the content and vertical scroll automatically as per screen size.
Something similar to this:
I can not use UITableView since, my page may not have necessarily homogenous elements. It could have a combination of textfields , dropdown etc.
The previous answer was quite right, but not right at all. Indeed I tried to solve this problem using the method described before, but to make it work, I made some adjustments.
Your view's hierarchy has to be as follow :
UIScrollview :
View1
View2
View3
You don't need a container inside the UIScrollview, because apart the fact that it will be an extraview that you don't need, there is the problem that if you use this container-view you will get problem getting touch events in the views added.
So, let's make a step-by-step process:
Add scrollview to your viewController
The first step is to add the scrollview to your viewController, and we can simply do this programmatically in the following way:
UIScrollView scrollView = new UIScrollView();
scrollView.TranslatesAutoresizingMaskIntoConstraints = false;
View.AddSubview(scrollView);
View is the main-view of the viewController you are working in (aka Self.View).
Put attention to set TranslateAutoResizionMaskIntoConstrains property of the scrollview to false, otherwise autoresizing will mess your constraints.
Add constraint (autolayout) to your scrollView
You need to ensure that you layout will adjust for every different iPhone-screen, so simply use auotlayout to pin your scrollView to the viewController main-view (is the View used in the next code sample):
scrollView.TopAnchor.ConstraintEqualTo(View.TopAnchor, 0).Active = true;
scrollView.BottomAnchor.ConstraintEqualTo(View.BottomAnchor, 0).Active = true;
scrollView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor, 0).Active = true;
scrollView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor, 0).Active = true;
In this way your scrollView is pinned to the bound of the main-view.
Create the view to be added
You need to create the view that you will add to the scrollView:
UIView viewToBeAdded = new UIView();
viewToBeAdded.TranslatesAutoresizingMaskIntoConstraints = false;
viewToBeAdded.Frame = new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, 200);
We have created a new UIView that setting its frame large as the screen (UIScreen.MainScreen.Bounds.Width) so it won't scroll horizontally, and with an arbitrary height (200 in the sample).
NOTE : even in this case you have to set TranslateAutoResizingMaskProperty to false, otherwise you will get a mess.
Add the view to the scrollView
Next step is to add our new view to the scrollView as follow:
scrollView.AddSubview(view);
Nothing more.
Set constraint for the view added in relation to the scrollView
Once you have added your view you have to said which will her behavior related to the scrollView. We assume that we will add several view to the scrollView, so we have to made a distinction, to the behavior of the FIRST view, the IN-BETWEEN views, and the LAST view.
So to be clear we assume that we are adding only 3 views, so we will have the three different cases.
FIRST VIEW
The important thing is that the first view has to be pinned to the top of the scrollView, we do this as follow :
firstView.TopAnchor.ConstraintEqualTo(scrollView.TopAnchor, 0).Active = true;
and then we set the others constraints:
firstView.WidthAnchor.ConstraintEqualTo(firstView.Bounds.Width).Active = true;
firstView.HeightAnchor.ConstraintEqualTo(firstView.Bounds.Height).Active = true;
IN-BETWEEN VIEW
The in between views (in our sample the secondView) need to be pinned to the previous view added (in our case the first view). So we do as follow:
secondView.TopAnchor.ConstraintEqualTo(firstView.BottomAnchor).Active = true;
So the top of the secondView is pinned to the bottom of the firstView.
And then we add the others constraints:
secondView.WidthAnchor.ConstraintEqualTo(secondView.Bounds.Width).Active = true;
secondView.HeightAnchor.ConstraintEqualTo(secondView.Bounds.Height).Active = true;
LAST VIEW
The last view (in our case the third view) instead needs to be pinned to the bottom of the previousView (in our case the secondView) and to the bottom of the scrollView.
thirdView.TopAnchor.ConstraintEqualTo(secondView.BottomAnchor).Active = true;
thirdView.BottomAnchor.ConstraintEqualTo(scrollView.BottomAnchor).Active = true;
And the usual other constraints for width and eight:
thirdView.WidthAnchor.ConstraintEqualTo(thirdView.Bounds.Width).Active = true;
thirdView.HeightAnchor.ConstraintEqualTo(thirdView.Bounds.Height).Active = true;
In this way the eight of the scrollView will adapt to the eight of the views added, due to the fact that the views inside are pinned to the top and the bottom of the scrollView.
CONCLUSIONS
If you follow these simple instruction you will get everything work. Remember to disable autoResizingMask, as this is on of the common mistake.
Hope it was helpful.
Cheers
In a custom renderer for Xamarin.Forms i've written my UITableViewController like this:
_controller = new InfoFieldItemsTableViewController();
_controller.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
_controller.TableView.SeparatorColor = UIColor.Clear;
_controller.TableView.AllowsSelection = false;
// http://useyourloaf.com/blog/self-sizing-table-view-cells/
_controller.TableView.RowHeight = UITableView.AutomaticDimension;
In my controller i am doing this to register all potential cell candidates:
private void RegisterCells()
{
foreach (var tuple in InfoFieldCellMapping.Map)
{
this.TableView.RegisterNibForCellReuse(tuple.Value.Item1, tuple.Value.Item2);
}
}
public override void ViewDidLoad()
{
RegisterCells();
base.ViewDidLoad();
}
I am doing this in my controller so cells resize themselves depending on how much height they need:
public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
{
return 100;
}
Now all you need to do is create cell files from within your IDE which should be .xib files and design them in the editor using autolayout (so they can adapt to orientation changes automatically).
Within your TableViews datasource all that's left to do is mapping between your data item and it's corresponding cell similar to:
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var dataItem = Items[indexPath.Row];
var key = ""; // use info from data item to figure out which key identifies your table cell to dequeue the correct kind of cell
var cell = collectionView.DequeueReusableCell(key, indexPath) as UICollectionViewCell;
}
That's all you need really. In my scenario i am mapping fields which may contain different controls for date entries, number entries, long texts, short texts etc etc.
I hope that helps
1.Add Leading,Trailing,Top,Bottom Constraints on scrollView to it'superview.
2.Add UIView as containerView of scrollview and add 6 Constraints from containerView to scrollview as below.
a)Leading b)trailing c)top d)bottom e)Center Horizontally.
3.Make sure top elements in container view must bind to top by adding top constraints and also bind bottom most element to bottom of container view by adding bottom constraints.And also all the items between topmost and bottommost in the container view must be vertically connected to each other so it will define the content size of container view.
it will define the actual content height for scrollview.
and finally define content size for scrollview in code.
As I mentioned here .
Refer to Step 1 and Step 2 ,set constraints on Scrollview and containerView.
I remove the margin between Scrollview and View , and I add some controls on the containerView , so it looks like as below:
Notice
Since we set the containerView's width equal to scrollview's width, the width is fixed, so we can scroll vertically not horizontally.
Height of controls and spaces between them should be set clearly, because the contentSize is auto calculated by adding them. (If contentSize is greater than the height of screen ,the scrollview can be scrolled)
I saw you set those constrains on controls , but you can't scroll down to see the controls out of screen, I think you are missing to set bottom margin on the last control(the downmost one).
Let us do a test.
1. We set the margin (between button and textfield ) to 1000 and don't set bottom margin between the textfield and containerView.
Result : can't scroll down to see the textfield out of screen.
2. Set the margin 1000 and add a bottom margin(10) between textfiled and containerView.
Result: can scroll
Demo Link
I have a view that I am making hidden at the bottom of the screen, and want the scrollView above it to adjust and fill the void space.
The view at the bottom of the screen is a GADBannerView and has a fixed height of 50 (bannerHeight). The scroll view above it has a constraint to the bottom of the container that equals 50 (scrollConstraint). See photo.
In viewDidLoad is am setting these constraints to the following:
bannerHeight.constant = 0
scrollConstraint.constant = 0
This is causing the bannerView did disappear but the scroll view is staying in it's original position and not filling the void space.
You can force the superview to take into account the change of the constraint because this does not happen automatically. Add your code to viewDidLayoutSubviews() instead or simply call view.layoutIfNeeded() after you set the constants to 0 in the viewDidLoad().
If this does not work, you can try this alternative approach:
Go to your Storyboard and click on the scroll view's bottom constraint (the blue line that gives the scroll view its bottom constraint of 50). In the Attributes Inspector you should be able to see details about your constraint, it should look something like this
In the field that asks for an Identifier, give it the name "ScrollViewBottom" or whatever name you like.
Now loop over all the constraints that make up your scroll view and use the identifier name to find the correct one and change it's constant as follows
for constraint in yourScrollView.superview!.constraints {
if constraint.identifier == "ScrollViewBottom" {
constraint.constant = 0
}
}
Finally, force the view to take into account of this change by calling the following straight after
view.layoutIfNeeded()