Adding UIStackView as a empty container in Storyboard - ios

My wish is to have a UIStackView added in the Storyboard with 0 height that I reference in the code to add subviews to programatically. However, Storyboard is complaining about that it doesn't have a height (I haven't set it, so it is correct that it warns me). I only want it to act as a dynamic container for other views. The UIStackView I am talking about is added as a subview inside another UIStackView.
It is the stackview below "Name Label" it is all about.

Select your StackView which you want to set height 0
Select Size inspector
Select Intrinsic Size property of StackView
Change it to Placeholder from Default
Now change StackView Height as you wish!! even 0.

You can hide you inner stack view using the boolean .hidden property. Create an outlet to the inner stack view, say innerStackView, and hide it at initialization using innerStackView.hidden = true, e.g.:
#IBOutlet weak var innerStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// ...
innerStackView.hidden = true
}
This will hide the stack view even if it contains several other views; hence, it can act as your hidden dynamic container, and you needn't fiddle around with height properties. If you want to show the view again, simply bitswap the .hidden property to innerStackView.hidden = false.

Related

Xamarin iOS Autolayout: Resize width and vertical scroll automatically for various devices while keeping the horizontal scroll disabled

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

Change View Position based on ImageView size

I have Imageview and UIView on View Controller. If Imageview is nil or image is not available then UIView replace its postion.Do any know how is it possible using auto layout?
For trying purpose, I have fixed height and width of both(Imageview and UIView). Imageview have "top 8 pixel" and "Horizontally in container" margin. UIView have "top 0 from Imageview" and "Horizontally in container" margin. Set Imageview to nil but it doesn't work.
A good suggestion would be to add both of the image view and the view in a stackView and follow the steps mentioned in: UIStackView Distribution Fill Equally.
However, you can achieve what are you asking for by adding additional constraint between the bottom view and the top layout guide:
and then, set its priority value to be less than the default (1000) -in my example I set it to 500- and the its constant value to 0:
Its appearance should be displayed as dotted line, meaning that there is another constraint -with a higher priority value- deciding the x axis of the view, if this constraint has been removed/deactivated the dotted one should be activated.
Finally, make sure that if there is no available image you have to remove image view from its super view (call imageView.removeFromSuperview()), setting it as hidden or setting its alpha to 0.0 doesn't activate the dotted constraint:
class ViewController: UIViewController {
//...
#IBOutlet weak var imageView: UIImageView!
// I'm checking in 'viewDidLoad' method just for describing purposes,
// of course, you can do the check when needed...
override func viewDidLoad() {
// if there is something wrong, you should call:
imageView.removeFromSuperview()
}
//...
}
The output would be:
Take the outlet of height constraint of UIImageView and do below code
if (imageview == nil){
imageViewHeightConstraint.constant = 0
}
else{
imageViewHeightConstraint.constant = 60
}
And other query you can ask.
You can manage this by giving priority to the constraints using auto layout. Give your UIView top space from ImageView and the TopLayout. Assign a lower priority to the constraint (any value less than 1000) where you have given top space from top layout guide. If your image is nil , remove the image view from the view using imageview.RemoveFromSuperView(), the UIView will automatically take the next constraint i.e. from TopLayout guide. I am attaching a screenshot where to find the priority of the constraint on storyBoard.

How to get resizing tableView cell with different content without deleting constraints in iOS

I have tableView cell with different content(views, labels, imageViews) in one cell. But in something cells content can be not full. How can i use resizing cells without removing and adding always constraints? Thanks.
One of possible solutions for this problem:
Add constraints for hidden state with priority 1000
Add extra constraints for resized state with lower priority (ex 750)
Save constraints that is ONLY for hidden state into IBOutlet collection
Save constraints that is ONLY for resized state into another IBOutlet collection
Code:
#IBOutlet var hiddenConstraints: [NSLayoutConstraint] = []
#IBOutlet var visibleConstraints: [NSLayoutConstraint] = []
func hide(_ hide: Bool) {
for hiddenConstraint in self.hiddenConstraints {
hiddenConstraint.isActive = hide
}
for visibleConstraint in self.visibleConstraints {
visibleConstraint.isActive = !hide
}
self.layoutIfNeeded()
}
There is faster solution:
Move content that can be hidden into container view
Set height constraint for container view
Change from code height constraint constant to 0 if hidden or to proper height if visible
Code:
#IBOutlet var heightConstraint: NSLayoutConstraint!
func hide(_ hide: Bool) {
self. heightConstraint.constant = hide ? 0 : 150 //Estimated height
self.layoutIfNeeded()
}
This is not a good approach, as it will lead to constraint crashes at runtime. So I prefer to use first one.
Also you will need to update your cell from table to move other cells up or down.
Ray Wenderlich has a fantastic tutorial on dynamic sizing of table cells that can be found here:
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
TL;DR You need to make sure your cell's content is pinned on all four sides to the cell's content view, as well as setting as high priority vertical hugging, greater than or equal to height constraint on your label.

Sizing a Container View with a controller of dynamic size inside a scrollview

I'm trying to create a container view, with a controller that has a dynamic height, inside a UIScrollView and have it sized automatically using auto layout.
View Controller A is the scrollview, which has the container view included, along with more content below.
View Controller B is the view controller that I want to have a dynamic size and for all the content to be displayed in full height in View Controller A's Scroll View.
I'm having some problems getting the dynamic size of B to automatically set the size of the Container View in A. However if I set a height constraint on the Container View in A ,
It would be the expected output if View Controller B would also have 250 height. It also works fine for height 1000, so as far as I know, all the auto layout constraints are properly setup. Unfortunately, since the height should actually be dynamic, I would like to avoid setting a height constraint at all.
I'm not sure if there are any settings for view controller B I can set for it to automatically update its size depending on its contents, or if there are any other tricks I've missed. Any help would be much appreciated!
Is there any way to size the Container View in A according to how big the size of View Controller B is without setting a height constraint?
Yup, there is. I managed to achieve that kind of behavior in one of my own projects.
All you gotta do is to tell the system that it should not add constraints that mimic the fixed frame set for your root view in Interface Builder. The best place to do this is in your container view controller when your embed segue is triggered:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// You might want to check if this is your embed segue here
// in case there are other segues triggered from this view controller.
segue.destinationViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
}
Important:
You gotta make sure that the view that you load into the container is constrained from top to bottom and you need to set the priority of one of the vertical constraints to a value lower than 1000. (It's a good practice to always use the bottom constraint for this.) This is necessary because otherwise Interface Builder will complain — with a good reason:
At design time your root view has a fixed size (height). Now if all your subviews have a fixed height and are connected with fixed constraints that all have the same priority it's impossible to fulfil all these requirements unless the height of your fixed root view coincidentally matches exactly the total height of your subviews and the vertical constraints. If you lower the priority of one of the constraints to 999 Interface Builder knows which constraint to break. At runtime however — when the translatesAutoresizingMaskIntoConstraints property is set as stated above — there is no fixed frame for your root view anymore and the system will use your 999 priority constraint instead.
From the answer of #Mischa I was able to make the height of a containerView dynamic depending on its content doing this:
In the viewController of the containerView write:
override func loadView() {
super.loadView()
view.translatesAutoresizingMaskIntoConstraints = false
}
And taking care that the vertical constraints in IB are all set. Doing this you do not need to set view.translatesAutoresizingMaskIntoConstraints = false from outside the view controller.
In my case I was trying to resize the container to a tableView inside the viewController. Because the tableView has a flexible height depending on its superview (so all OK for IB), I completed the vertical constraints in code by doing this:
#IBOutlet private var tableView: UITableView! {
didSet {
tableView.addConstraint(tableViewHeight)
}
}
private lazy var tableViewHeight: NSLayoutConstraint = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)
And then observe the contentSize height of the tableview and adjust the constant of the tableViewHeight constraint programmatically when needed.
Swift 4, Xcode 9
The accepted answer alone didn't solve the problem for me.
My hierarchy: ScrollView --> Content View (UIView) --> Views | Container View | Other Views.
I had to add the following constraints to make both the ScrollView and Container dynamically adjust:
ScrollView: Top, Bottom, Leading, Trailing to Superview (Safe Areas)
Content View: Top, Bottom, Leading, Trailing, Equal Width to ScrollView, but also with Equal Heights (constraint with a lower priority: 250).
Views: normal auto-layout constraints.
Container View: Top, Bottom to neighbor views, Leading and Trailing to Safe Area.
Container View's embedded VC: Everything constraint connected vertically with the bottom constraint set to a lower priority, but bigger than the Content View's Equal Height one! In this case, a priority of 500 did the trick.
Set view.translatesAutoresizingMaskIntoConstraints = false in either prepareForSegue() or in loadView() as other answers stated.
Now I have a dynamically adjustable Container View inside an auto-resizing Scroll View.
Building on #Mischa's great solution to this problem, if the container embeds a dynamically sized UITableView, then you need to look at overriding it's contentSize and intrinsicContentSize, as well as setting translatesAutoresizingMaskIntoConstraints = false as per the accepted answer.
When the parent view loads and sets up the container view, the intrinsic height of each of the UITableViewCells is inferred to be 0, therefore the UITableViewDelegate method of:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
will not be called. This makes the UITableView appear as though it has no content.
Overriding the intrinsic content size to return the actual content size means that the tableview is displayed with the size it's contents require.
A great article by Emilio Peláez goes into more depth on this topic.
I tried this solution and worked for me.
In destination(child) view controller try to access to parent view controller like this:
if let parentVC = self.parent as? EmbededContinerViewController {
if let myParent = parentVC.parent as? ParentViewController {
myParent.subViewHeight.constant += 2000
myParent.subView.layoutIfNeeded()
}
}
Maybe this not normal solution but worked for me and solved my problem.
I tried this code in Swift 5.1 .

How to easily collapse vertical space around label when its text is nil?

Suppose I have three labels that are laid out below each other in a column. The uppermost label's top edge is pinned to the superview's top edge. All following labels' top edges are pinned to the preceding label's bottom edge. The leading and trailing edges of all labels are pinned to the leading and trailing edge of the superview. Here's what it looks like in Interface Builder (I added a blue background on every label to visualize its extent).
In the simulator the result looks like this.
All labels are connected to outlets in a view controller.
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
When I set the text of label2 to nil
label2.text = nil
the label itself collapses.
However, the top and bottom spaces around the label do not collapse. This is evident by the fact that there is no blue background on the middle label in the last screenshot. As a result, the space between label1 and label3 is double the space of the layout in the first screenshot.
My question is - on iOS8 - what is the easiest way to collapse either the middle label's top or bottom space so that the two remaining labels still use the vertical spacing defined in the original layout? To be clear, this is the result I want to achieve.
Options I've found so far:
Bottom/Top Spacing Constraint Outlet
Define an outlet for the middle label's top or bottom spacing constraint.
#IBOutlet weak var spacingConstraint: NSLayoutConstraint!
Store the constraint's initial constant into a variable (e.g. in awakeFromNib or viewDidLoad).
private var initialSpacing: CGFloat!
override func viewDidLoad() {
initialSpacing = spacingConstraint.constant
...
Set the constraint's constant to zero whenever the text is set to nil or back to its initial value when the text is not nil.
spacingConstraint.constant = label2.text == nil ? 0 : initialSpacing
This approach feels a bit clumsy since it requires two additional variables.
Height Constraint Outlet
Set the vertical spacing around the middle label to zero and increase its height by the same amount. Define an outlet for the height constraint and proceed as above, setting the height to zero when the text is nil and back to it's initial value when the height is not nil.
This is still as clumsy as the previous approach. In addition, you have to hardcode the spacing and cannot use the built-in default spacings (blank fields in Interface builder).
UIStackView
This is not an option since UIStackView is only available on iOS 9 and above.
I'm using this UIView category for this purpose.
It extends UIView by adding two more property named fd_collapsed and fd_collapsibleConstraints using objective-c runtime framework. You simply drag constraints that you want to be disabled when fd_collapsed property set to YES. Behind the scene, it captures the initial value of these constraints, then set to zero whenever fd_collapsed is YES. Reset to initial values when fd_collapsed is NO.
There is also another property called fd_autocollapsed
Not every view needs to add a width or height constraint, views like UILabel, UIImageView have their Intrinsic content size when they have content in it. For these views, we provide a Auto collapse property, when its content is gone, selected constraints will collapse automatically.
This property automatically sets fd_collapsed property to YES whenever specified view has content to display.
It's really simple to use. It's kinda shame that there is no builtin solution like that.
Your solutions are good enough for me and I'd do Bottom/Top Spacing Constraint Outlet solution but since you want something different. You can use this third party: https://github.com/orta/ORStackView It has iOS7+ support and do exactly what you need.
This is low-key a pain all perfectionist devs learn about when trying to stack a bunch of labels. Solutions can get too verbose, annoying to folow, and really annoying to implement (ie. keeping a reference to the top constraint... gets annoying once you do it multiple times, or just change the order of the labels)
Hopefully my code below puts an end to this:
class MyLabel: UILabel {
var topPadding: CGFloat = 0
override func drawText(in rect: CGRect) {
var newRect = rect
newRect.origin.y += topPadding/2
super.drawText(in: newRect)
}
override var intrinsicContentSize: CGSize {
var newIntrisicSize = super.intrinsicContentSize
guard newIntrisicSize != .zero else {
return .zero
}
newIntrisicSize.height += topPadding
return newIntrisicSize
}
}
Usage:
let label = MyLabel()
label.topPadding = 10
// then use autolayout to stack your labels with 0 offset
Granted, its only for top padding, but that should be the only thing you need to layout your labels properly. It works great with or without autolayout. Also its a big plus not needing to do any extra mental gymnastics just to do something so simple. Enjoy!

Resources