Swift: Views not adjusting to programmatic constraints - ios

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()

Related

How to corrently use UIViews systemLayoutSizeFitting to get the height to show all subviews using a given width?

TD;DR
It seems that in some cases systemLayoutSizeFitting does not return the correct height to correctly show / position all subviews of a view. Am I using systemLayoutSizeFitting wrong or is there some other way to avoid this?
Long story:
The XIB file of a UIViewController does not only contain the main view but also a number of other views which are added to the view controllers view at runtime. All these additional views should get the same height when they are added to the view controllers view.
The views might look like this: A simple container view holding some subviews which are stacked on top of each other.
Since the height of the container view should be flexible, the vertical spacing between the bottom button and the lable above it, uses a grater-than constraint.
To give all views the same height, I tried to measure the necessary height of each view using systemLayoutSizeFitting:
#IBOutlet var pageViews: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
var maxHeight: CGFloat = 0
for pageView in pageViews {
// Add pageView somewhere on view and give it leading, trailing and top
// constraint, but no height constraint yet.
addToView(pageView)
maxHeight = max(maxHeight, pageView.systemLayoutSizeFitting(CGSize(width: view.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel).height)
}
for pageView in pageViews {
// Give all pageViews the same height
pageView.heightAnchor.constraint(equalToConstant: maxHeight).isActive = true
}
}
This does not work, when the label text becomes to long:
In the right example the height is not large enough and thus the button is squeezed. I can counter act this by raising the vertical compression resistance of the button, however in this case the other controls (e.g. the title label) is squeezed...
Why is this? Why does not systemLayoutSizeFitting return a height which is sufficent to show all controls without any squeezing?
Its actually smash button's height when label text is getting bigger . You are setting top and bottom constraints but button height is not declared so when label getting bigger , view basically say "I can reduce button height before updating my height , I have space.Bottom and top constraints are still same , didn't effect."
Giving the constant height constraints of button might be fix your issue.
If you want your view to resist to compression you should use the defaultHigh priority as a verticalFittingPriority instead of fittingSizeLevel.

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.

Updating constraints programmatically

Situation is like I have a view with four buttons
There comes a condition when button with text Search Community should be hidden and width of Options button should get increased
Using Constraints for the first time in my project, I am not getting how to achieve this. Set of constraints added on both these buttons would be clear from the following two images.
The problem I see with your setup is that you have this constraint between the 2 buttons of equal width. This is true in the first case but when you want to hide one of them, it is not anymore.
So, you will need to rethink your constraints a bit. Maybe instead of the equal width constraint, you could use a static width constraint one the first button(the left one, that you want to hide). Then the second one, will just have a horizontal space constraint to the first one, and a trailing space to the superView.
Then you make an outlet in the VC for the width constraint of the first button and when you want to hide it you do something like this:
self.searchButtonWidthConstraint.constant = 0
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.view.layoutIfNeeded() // if you want to animate the constraint change
})
Let me know if you have questions. Good luck and hope it works out!
Update
For the landscape use case, you could listen to the orientation change notification, to update the width constraint of the button
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.searchButtonWidthConstraint.constant = LANDSCAPE_WIDTH
UIView.animateWithDuration(duration, animations: { () -> Void in
self.view.layoutIfNeeded() // if you want to animate the constraint change
})
}
Add constraint to 1st Button
Leading Space To NewView(gray colored)
Bottom space to NewView
Top Space to Topbutton (blue colored in your 1st and 2nd image)
Width (from Bottom - Right portion )
Add Constraint to 2nd Button
Trailing Space to NEwView
Bottom space to NewView
Top Space to Topbutton (blue colored in your 1st and 2nd image)
Horizontal space FirstButton ( Search Community )
Width

Where to update Autolayout constraints when size changes?

I have several UIViews laid out along the bottom of a containing UIView. I want these views to always be equal width, and always stretch to collectively fill the width of the containing view (like the emoji keyboard buttons at the bottom). The way I'm approaching this is to set equal widths to one of the views, then just update the width constraint of that view to be superviewWidth / numberOfViews which will cause all of the other views to update to that same value.
I am wondering where the code to change the constraint constant needs to go. It needs to be set before the keyboard appears on screen for the first time and update when rotating the device.
My first attempt at a solution was to place it in updateViewConstraints and calculate the width via containerView.frame.size.width. But this method is called twice upon load, the first time it calculates the values correctly, but the second time for some reason the containerView's width is 0.0. Another issue is that when rotating, the containerView's width is not the value that it will be after rotation, it's the current value before rotation. But I don't want to wait until after the rotation completes to update the constraint, because the buttons will be the original size then change which will be jarring to the user.
My question is: where is the most appropriate place to put this code? Is there a better way to calculate what the width will be? I can guarantee it will always be the exact same width as the screen width. And I am using Size Classes in Xcode 6, so willRotateToInterfaceOrientation and similar methods are deprecated.
On all classes that implement the UITraitEnvironment protocol the method traitCollectionDidChange will be called when the trait collection changes, like on rotation. This is the appropiate place to manually update the constraints when using the new Size Classes. You can also animate the transition with the method willTransitionToTraitCollection
Basic example:
class ViewController: UIViewController {
var constraints = [NSLayoutConstraint]()
func updateConstraintsWithTraitCollection(traitCollection: UITraitCollection) {
// Remove old constraints
view.removeConstraints(constraints)
// Create new constraints
}
override func willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext!) in
self.updateConstraintsWithTraitCollection(newCollection)
self.view.setNeedsLayout()
}, completion: nil)
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection!) {
updateConstraintsWithTraitCollection(traitCollection)
}
}
Besides that I want to recommend Cartography, which is a nice library that helps to make auto layout more readable and enjoyable. https://github.com/robb/Cartography
There is no reason to update the width manually:
Place all the views with equal width in your view with no spacing in between each other
Add an equal width constraint to all of them
Add constraints with 0 width for spacing between sides and each other
Lower the priority of one or more of the equal width constraints just in case the width cannot be divided equally.
Then auto layout will handle everything for you.

Resources