Related
It seems that with Xcode 8, on viewDidLoad, all viewcontroller subviews have the same size of 1000x1000. Strange thing, but okay, viewDidLoad has never been the better place to correctly size the views.
But viewDidLayoutSubviews is!
And on my current project, I try to print the size of a button:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(#"%#", self.myButton);
}
The log shows a size of (1000x1000) for myButton! Then if I log on a button click, for example, the log shows a normal size.
I'm using autolayout.
Is it a bug?
Now, Interface Builder lets the user change dynamically the size of every view controllers in storyboard, to simulate the size of a certain device.
Before this functionality, the user should set manually each view controller size. So the view controller was saved with a certain size, which was used in initWithCoder to set the initial frame.
Now, it seems that initWithCoder do not use the size defined in storyboard, and define a 1000x1000 px size for the viewcontroller view & all its subviews.
This is not a problem, because views should always use either of these layout solutions:
autolayout, and all the constraints will layout correctly your views
autoresizingMask, which will layout each view which doesn't have any constraint attached to (note autolayout and margin constraints are now compatible in the same view \o/ !)
But this is a problem for all layout stuff related to the view layer, like cornerRadius, since neither autolayout nor autoresizing mask applies to layer properties.
To answer this problem, the common way is to use viewDidLayoutSubviews if you are in the controller, or layoutSubview if you are in a view. At this point (don't forget to call their super relative methods), you are pretty sure that all layout stuff has been done!
Pretty sure? Hum... not totally, I've remarked, and that's why I asked this question, in some cases the view still has its 1000x1000 size on this method. I think there is no answer to my own question. To give the maximum information about it:
1- it happends only when laying out cells! In UITableViewCell & UICollectionViewCell subclasses, layoutSubview won't be called after subviews would be correctly layed out.
2- As #EugenDimboiu remarked (please upvote his answer if useful for you), calling [myView layoutIfNeeded] on the not-layed out subview will layout it correctly just in time.
- (void)layoutSubviews {
[super layoutSubviews];
NSLog (self.myLabel); // 1000x1000 size
[self.myLabel layoutIfNeeded];
NSLog (self.myLabel); // normal size
}
3- To my opinion, this is definitely a bug. I've submitted it to radar (id 28562874).
PS: I'm not english native, so feel free to edit my post if my grammar should be corrected ;)
PS2: If you have any better solution, feel free not write another answer. I'll move the accepted answer.
Are you using rounded corners for your button ?
Try calling layoutIfNeeded() before.
Solution: Wrap everything inside viewDidLayoutSubviews in DispatchQueue.main.async.
// swift 3
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
// do stuff here
}
}
I know this wasn't your exact question, but I ran into a similar problem where as on the update some of my views were messed up despite having the correct frame size in viewDidLayoutSubviews. According to iOS 10 Release notes:
"Sending layoutIfNeeded to a view is not expected to move the view,
but in earlier releases, if the view had
translatesAutoresizingMaskIntoConstraints set to NO, and if it was
being positioned by constraints, layoutIfNeeded would move the view to
match the layout engine before sending layout to the subtree. These
changes correct this behavior, and the receiver’s position and usually
its size won’t be affected by layoutIfNeeded.
Some existing code may be relying on this incorrect behavior that is
now corrected. There is no behavior change for binaries linked before
iOS 10, but when building on iOS 10 you may need to correct some
situations by sending -layoutIfNeeded to a superview of the
translatesAutoresizingMaskIntoConstraints view that was the previous
receiver, or else positioning and sizing it before (or after,
depending on your desired behavior) layoutIfNeeded.
Third party apps with custom UIView subclasses using Auto Layout that
override layoutSubviews and dirty layout on self before calling super
are at risk of triggering a layout feedback loop when they rebuild on
iOS 10. When they are correctly sent subsequent layoutSubviews calls
they must be sure to stop dirtying layout on self at some point (note
that this call was skipped in release prior to iOS 10)."
Essentially you cannot call layoutIfNeeded on a child object of the View if you are using translatesAutoresizingMaskIntoConstraints - now calling layoutIfNeeded has to be on the superView, and you can still call this in viewDidLayoutSubviews.
If the frames are not correct in layoutSubViews (which they are not) you can dispatch async a bit of code on the main thread. This gives the system some time to do the layout. When the block you dispatch is executed, the frames have their proper sizes.
This fixed the (ridiculously annoying) issue for me:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);
}
Edit/Note: This is for a full screen ViewController.
Actually viewDidLayoutSubviews also is not the best place to set frame of your view. As far as I understood, from now on the only place it should be done is layoutSubviews method in the actual view's code. I wish I wasn't right, someone correct me please if it is not true!
I already reported this issue to apple, this issue exist since a long time, when you are initializing UIViewController from Xib, but i found quite nice workaround. In addition to that i found that issue in some cases when layoutIfNeeded on UICollectionView and UITableView when datasource is not set in initial moment, and needed also swizzle it.
extension UIViewController {
open override class func initialize() {
if self !== UIViewController.self {
return
}
DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
ins_applyFixToViewFrameWhenLoadingFromNib()
}
}
#objc func ins_setView(view: UIView!) {
// View is loaded from xib file
if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
view.frame = UIScreen.main.bounds
view.layoutIfNeeded()
}
ins_setView(view: view)
}
private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
with: #selector(UIViewController.ins_setView(view:)))
UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
with: #selector(UICollectionView.ins_layoutSubviews))
UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
with: #selector(UITableView.ins_layoutSubviews))
}
}
extension UITableView {
#objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
extension UICollectionView {
#objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
Dispatch once extension:
extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block: (Void) -> Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
Swizzle extension:
extension NSObject {
#discardableResult
class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {
var originalMethod: Method?
var swizzledMethod: Method?
originalMethod = class_getInstanceMethod(self, originalSelector)
swizzledMethod = class_getInstanceMethod(self, selector)
if originalMethod != nil && swizzledMethod != nil {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
return true
}
return false
}
}
My issue was solved by changing the usage from
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
to
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
So, from Did to Will
Super weird
Better solution for me.
protocol LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView)
}
private class LayoutCaptureView: UIView {
var targetView: UIView!
var layoutComplements: [LayoutComplementProtocol] = []
override func layoutSubviews() {
super.layoutSubviews()
for layoutComplement in self.layoutComplements {
layoutComplement.didLayoutSubviews(with: self.targetView)
}
}
}
extension UIView {
func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
func findLayoutCapture() -> LayoutCaptureView {
for subView in self.subviews {
if subView is LayoutCaptureView {
return subView as? LayoutCaptureView
}
}
let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
layoutCapture.targetView = self
self.addSubview(layoutCapture)
return layoutCapture
}
let layoutCapture = findLayoutCapture()
layoutCapture.layoutComplements.append(layoutComplement_)
}
}
Using
class CircleShapeComplement: LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView) {
targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
}
}
myButton.add(layoutComplement: CircleShapeComplement())
Override layoutSublayers(of layer: CALayer) instead of layoutSubviews in cell subview to have correct frames
If you need to do something based on your view's frame - override layoutSubviews and call layoutIfNeeded
override func layoutSubviews() {
super.layoutSubviews()
yourView.layoutIfNeeded()
setGradientForYourView()
}
I had the issue with viewDidLayoutSubviews returning the wrong frame for my view, for which I needed to add a gradient. And only layoutIfNeeded did the right thing :)
As per new update in ios this is actually a bug but we can reduce this by using -
If you are using xib with autolayout in your project then you have to just update frame in autolayout setting please find image for this .
I have some views where I have to remove a UIView and let all the other views rearrange without this view. I usually do this by creating multiple constraints with different weights, so when I removeFromSuperView all the other constraints with lower weight start working and everything rearranges as I want.
It works perfectly but now I have to show and hide the view but when I removeFromSuperView, that view is gone with all its constraints... I have no idea what to do... I can change width and heights constraints to zero for the view I want to hide... but the problem with this is that then I have to hard code the height in the code and I don't like it...
What's the best way of hiding and showing UIViews? Something like the "Installed" property that you can set in the IB but from the code.
When you remove the view from super view you lose the constraint, you're basically making it available for any object in your view and that way you have to write some methods to add to other views (with programmatically adding constraint) or removing it again.
From my experience to hide view you can use constraint animation or view itself animation and throw it off to left, right, up or down. Also for it to not be available within user interaction you can just disable it with self.view.hidden = true or self.view.userInteractionEnabled = false. use dispatch_async() when you also updating view, apple love people who code standard :).
Hide View itself with animation:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.view.hidden = true // Or userInteractionEnable = false
UIView.animateWithDuration(1.0, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.reportView.alpha = 0
}, completion: nil)
})
Hide View with Constraint:
You need to create constraint IBOutlet to use the constant with animation.
self.someView.constant = -205 // Throw it off canaves
UIView.animateWithDuration(1.0, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (finished: Bool)-> Void in
self.someView.hidden = true
})
To make it appear just do the same think in reverse. but don't removeFromSuperView(), unless you trying to attach it to First Responder or something like that.
If targeting iOS 9 and above, you can try UIStackView.
One way we can think of preserving constraints is by creating a category on UIView.
#interface UIView (ConstraintsPreserving)
#property (nonatomic, strong) NSMutableArray * preservedConstraints;
-(void)removeFromSuperViewPreservingConstriants;
-(void)addToSuperViewPreservingConstriants:(UIView *)superView;
#end
#import <objc/runtime.h>
#implementation UIView (ConstraintsPreserving)
#dynamic preservedConstraints;
-(void)setPreservedConstraints:(NSMutableArray *)object {
objc_setAssociatedObject(self, #selector(preservedConstraints), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSMutableArray *)preservedConstraints {
return objc_getAssociatedObject(self, #selector(preservedConstraints));
}
-(void)removeFromSuperViewPreservingConstriants
{
if(!self.superview) return;
self.preservedConstraints = [NSMutableArray new];
[self.preservedConstraints addObjectsFromArray:[self constraints]];
for (NSLayoutConstraint * constraint in self.superview.constraints) {
if([constraint.firstItem isEqual:self] || [constraint.secondItem isEqual:self])
[self.preservedConstraints addObject:constraint];
}
[self removeFromSuperview];
}
-(void)addToSuperViewPreservingConstriants:(UIView *)superView
{
if(self.superview) return;
[superView addSubview:self];
[superView addConstraints:self.preservedConstraints];
self.preservedConstraints = nil;
}
#end
PS: This strongly captures the constraints, there by capturing all the view involved in these constraints.
I am not sure if appropriate to your problem, but what I did a couple of times, was to make sure that the view you want to hide is in a container. Such container should have a height and width constraint (and this is where you might not like it). You then create IBOutlets for each one of these constraints in the relevant view controller (control drag from the constraint within storyboard). Finally you can save the initial value of the constraint in viedidload, use it later to switch from zero to that value, whatever it is.
So, I ended up creating creating IBOutlets to the views I want to hide. I set the width and height constraints to zero that way I hide the view. Also I set multiple IBOutlets and changed the priority of this constraints so I can re arrange stuff...
That's the best way I found of doing it.
I'm developing kind of UITextView copy with customizations. For now I need to implement selection cursors (see screenshot of original UITextView below)
As I discovered from Debug View Hierarchy Apple developers draw these dots on separate Window to avoid clipping, and when UIScrollView starts dragging they move these dots inside UITextView, when it stops dragging they move it back to separate window. The only problem with this approach is how can I detect when some of my TextView superview's are UIScrollView and they start/end scrolling? Setting delegate for each of UIScrollView-type superviews looks bad and will bring a lot of headache, cause I will need to manage several delegates if needed (and even detect there change). Any ideas?
/*
In your viewDidLoad or where ever you create the UITextView call this :[self checkParentViewOfTextView:textField];
*/
-(void)checkParentViewOfTextView:(UITextView*)txv {
if ([txv.superview isKindOfClass:[UIScrollView class]]) { // Check if the superview if UIScrollView
UIScrollView *superScroll =(UIScrollView*) txv.superview;
superScroll.delegate = self;// In order to call the delegate methods below
superScroll.tag = 5; // Set a tag to access the current scrollView at these delegate methods
}
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//Any scrollView did begin scrolling
if (scrollView.tag == 5) {
//Actions for your scrollView
}
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//Any scrollView did end scrolling
if (scrollView.tag == 5) {
//Actions for your scrollView
}
}
You can use the same scroll view delegate for all UIScrollViews.
scrollView1.delegate = self
scrollView2.delegate = self
etc...
Just implement the delegate methods and take different action if necessary for each scroll view. Either do this by referencing properties in the class or setting the tag.
func scrollViewDidScroll(scrollView: UIScrollView!) {
if scrollView.tag == 0 {
// Do stuff
} else {
// Do other stuff
}
}
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...
I have designed my custom Cell in IB, subclassed it and connected my outlets to my custom class. I have three subviews in cell content which are: UIView (cdView) and two labels (titleLabel and emailLabel). Depending on data available for each row, sometimes I want to have UIView and two labels displayed in my cell and sometimes only two labels. What I am trying to do is to set constraints that way if I set UIView property to hidden or I will remove it from superview the two labels will move to the left. I tried to set UIView leading constraint to Superview (Cell content) for 10px and UILabels leading Constraints for 10 px to the next view (UIView). Later in my code
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
// ...
Record *record = [self.records objectAtIndex:indexPath.row];
if ([record.imageURL is equalToString:#""]) {
cell.cdView.hidden = YES;
}
}
I am hiding my cell.cdView and I would like the labels to move to the left however they are staying in the same position in Cell. I tried to remove cell.cdView from superview but it didn't work either. I have attached image to clarify what I am about.
I know how to do this programatically and I am not looking for that solution. What I want is to set constraints in IB and I expect that my subviews will move dynamically if other views are removed or hidden. Is it possible to do this in IB with auto-layout?
.....
It is possible, but you'll have to do a little extra work. There are a couple conceptual things to get out of the way first:
Hidden views, even though they don't draw, still participate in Auto Layout and usually retain their frames, leaving other related views in their places.
When removing a view from its superview, all related constraints are also removed from that view hierarchy.
In your case, this likely means:
If you set your left view to be hidden, the labels stay in place, since that left view is still taking up space (even though it's not visible).
If you remove your left view, your labels will probably be left ambiguously constrained, since you no longer have constraints for your labels' left edges.
What you need to do is judiciously over-constrain your labels. Leave your existing constraints (10pts space to the other view) alone, but add another constraint: make your labels' left edges 10pts away from their superview's left edge with a non-required priority (the default high priority will probably work well).
Then, when you want them to move left, remove the left view altogether. The mandatory 10pt constraint to the left view will disappear along with the view it relates to, and you'll be left with just a high-priority constraint that the labels be 10pts away from their superview. On the next layout pass, this should cause them to expand left until they fill the width of the superview but for your spacing around the edges.
One important caveat: if you ever want your left view back in the picture, not only do you have to add it back into the view hierarchy, but you also have to reestablish all its constraints at the same time. This means you need a way to put your 10pt spacing constraint between the view and its labels back whenever that view is shown again.
Adding or removing constraints during runtime is a heavyweight operation that can affect performance. However, there is a simpler alternative.
For the view you wish to hide, set up a width constraint. Constrain the other views with a leading horizontal gap to that view.
To hide, update the .constant of the width constraint to 0.f. The other views will automatically move left to assume position.
See my other answer here for more details:
How to change label constraints during runtime?
For those who support iOS 8+ only, there is a new boolean property active. It will help to enable only needed constraints dynamically
P.S. Constraint outlet must be strong, not weak
Example:
#IBOutlet weak var optionalView: UIView!
#IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
#IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!
func showView() {
optionalView.isHidden = false
viewIsVisibleConstraint.isActive = true
viewIsHiddenConstraint.isActive = false
}
func hideView() {
optionalView.isHidden = true
viewIsVisibleConstraint.isActive = false
viewIsHiddenConstraint.isActive = true
}
Also to fix an error in storyboard you'll need to uncheck Installed checkbox for one of these constraints.
UIStackView (iOS 9+)
One more option is to wrap your views in UIStackView. Once view is hidden UIStackView will update layout automatically
UIStackView repositions its views automatically when the hidden property is changed on any of its subviews (iOS 9+).
UIView.animateWithDuration(1.0) { () -> Void in
self.mySubview.hidden = !self.mySubview.hidden
}
Jump to 11:48 in this WWDC video for a demo:
Mysteries of Auto Layout, Part 1
My project uses a custom #IBDesignable subclass of UILabel (to ensure consistency in colour, font, insets etc.) and I have implemented something like the following:
override func intrinsicContentSize() -> CGSize {
if hidden {
return CGSizeZero
} else {
return super.intrinsicContentSize()
}
}
This allows the label subclass to take part in Auto Layout, but take no space when hidden.
For the Googlers: building on Max's answer, to solve the padding issue that many have noticed I simply increased the height of the label and used that height as the separator instead of actual padding. This idea could be expanded for any scenario with containing views.
Here's a simple example:
In this case, I map the height of the Author label to an appropriate IBOutlet:
#property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
and when I set the height of the constraint to 0.0f, we preserve the "padding", because the Play button's height allows for it.
connect constraint between uiview and labels as IBOutlet and set priority member to a less value when set hidden = YES
What I ended up doing was creating 2 xibs. One with the left view and one without it. I registered both in the controller and then decided which to use during cellForRowAtIndexPath.
They use the same UITableViewCell class. The downside is that there is some duplication of the content between the xibs, but these cells are pretty basic. The upside is that I don't have a bunch of code to manually manage removing view, updating constraints, etc.
In general, this is probably a better solution since they are technically different layouts and therefore should have different xibs.
[self.table registerNib:[UINib nibWithNibName:#"TrackCell" bundle:nil] forCellReuseIdentifier:#"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:#"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:#"TrackCellNoImage"];
TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? #"TrackCell" : #"TrackCellNoImage") forIndexPath:indexPath];
In this case, I map the height of the Author label to an appropriate IBOutlet:
#property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
and when I set the height of the constraint to 0.0f, we preserve the "padding", because the Play button's height allows for it.
cell.authorLabelHeight.constant = 0;
Use two UIStackView Horizontal and Vertical, when some subview view in stack is hidden other stack subviews will be moved, use Distribution -> Fill Proporionally for Vertical stack with two UILabels and need set width and height constaints for first UIView
Just use UIStackView and everything will be work fine.
No need to worry about other constraint, UIStackView will handle the space automatically.
For this specific layout the constraint to be working with is the 'leading' constraint on the view that is being hidden. The below theory will work in all directions though.
1: Setup all your constraints how you want it to look when all views are visible.
2: Add a second 'leading' constraint to the view you want to hide. This will break the constraints for a moment.
3: Change the priority of the original leading constraint to be '999' - this then gives priority to your new constraint which will be at 1000 and no constraints will be broken anymore.
4: Change the new constraint from 'leading=leading' to be 'trailing=leading'. This will move the view you want to hide off the leading edge of its parent shifting it out of the way.
5: Toggling the new constraint's isActive value will now toggle if it's in the view or outside it. Set that to true/false at the same time as setting the visibility to true/false. Eg:
#IBOutlet var avatar:UIImage!
#IBOutlet var avatarLeadHid:NSLayoutConstraint!
func hideAvatar() {
self.avatar.isHidden = true
self.avatarLeadHid.isActive = true
}
func showAvatar() {
self.avatar.isHidden = false
self.avatarLeadHid.isActive = false
}
Bonus: You can adjust the 'constant' value of the new hider-constraint in order to alter the padding/margin to use when the view is hidden. This value can be negative.
Extra Bonus: It's possible to see what your layout will look like from within the Interface Builder without running any code just by toggling the 'Installed' checkbox on the hider-constraint.
Further Help: I made a video that shows what I do better that a list of points: https://youtu.be/3tGEwqtQ-iU
In my case I set the constant of the height constraint to 0.0f and also set the hidden property to YES.
To show the view (with the subviews) again I did the opposite: I set the height constant to a non-zero value and set the hidden property to NO.
Try this,I have implemented below code ,
I have one View on ViewController in that added other three views, When any view is hidden other two view will move,Follow below steps.
,
1.ViewController.h File
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIView *viewOne;
#property (strong, nonatomic) IBOutlet UIView *viewTwo;
#property (strong, nonatomic) IBOutlet UIView *viewThree;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
#end
2.ViewController.m
#import "ViewController.h"
#interface ViewController ()
{
CGFloat viewOneWidthConstant;
CGFloat viewTwoWidthConstant;
CGFloat viewThreeWidthConstant;
CGFloat viewBottomWidthConstant;
}
#end
#implementation ViewController
#synthesize viewOne, viewTwo, viewThree;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a
nib.
/*
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
*/
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
[self hideShowBottomBar];
}
- (void)hideShowBottomBar
{
BOOL isOne = !viewOne.isHidden;
BOOL isTwo = !viewTwo.isHidden;
BOOL isThree = !viewThree.isHidden;
viewOneWidthConstant = _viewOneWidth.constant;
viewTwoWidthConstant = _viewTwoWidth.constant;
viewThreeWidthConstant = _viewThreeWidth.constant;
viewBottomWidthConstant = _viewBottomWidth.constant;
if (isOne && isTwo && isThree) {
// 0 0 0
_viewOneWidth.constant = viewBottomWidthConstant / 3;
_viewTwoWidth.constant = viewBottomWidthConstant / 3;
_viewThreeWidth.constant = viewBottomWidthConstant / 3;
}
else if (isOne && isTwo && !isThree) {
// 0 0 1
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = 0;
}
else if (isOne && !isTwo && isThree) {
// 0 1 0
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (isOne && !isTwo && !isThree) {
// 0 1 1
_viewOneWidth.constant = viewBottomWidthConstant;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
else if (!isOne && isTwo && isThree) {
// 1 0 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (!isOne && isTwo && !isThree) {
// 1 0 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant;
_viewThreeWidth.constant = 0;
}
else if (!isOne && !isTwo && isThree) {
// 1 1 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant;
}
else if (isOne && isTwo && isThree) {
// 1 1 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Hope So this logic will help some one.
I will use horizontal stackview. It can remove the frame when the subview is hidden.
In image below, the red view is the actual container for your content and has 10pt trailing space to orange superview (ShowHideView), then just connect ShowHideView to IBOutlet and show/hide/remove it programatically.
This is when the view is visible/installed.
This is when the view is hidden/not-installed.
This my another solution using priority constraint. The idea is set the width to 0.
create container view (orange) and set width.
create content view (red) and set trailing space 10pt to superview (orange). Notice trailing space constraints, there are 2 trailing constraint with different priority. Low(=10) and High(<=10). This is important to avoid ambiguity.
Set orange view's width to 0 to hide the view.
The easiest solution is to use UIStackView (horizontal). Add to stack view: first view and second view with labels.
Then set isHidden property of first view to false.
All constrains will be calculated and updates automatically.
Instead of hiding view, create the width constrain and change it to 0 in code when you want to hide the UIView.
It may be the simplest way to do so. Also, it will preserve the view and you don't need to recreate it if you want to show it again (ideal to use inside table cells). To change the constant value you need to create a constant reference outlet (the same way as you do outlets for the view).
As no_scene suggested, you can definitely do this by changing the priority of the constraint at runtime. This was much easier for me because I had more than one blocking view which would have to be removed.
Here's a snippet using ReactiveCocoa:
RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;
RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
combineLatest:#[isViewOneHiddenSignal,
isViewTwoHiddenSignal,
isViewThreeHiddenSignal]]
and]
distinctUntilChanged]
map:^id(NSNumber* allAreHidden) {
return [allAreHidden boolValue] ? #(780) : #(UILayoutPriorityDefaultHigh);
}];
RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
subscribeNext:^(id x) {
#strongify(self);
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}];
In case this helps someone, I built a helper class for using visual format constraints. I'm using it in my current app.
AutolayoutHelper
It might be a bit tailored to my needs, but you might find it useful or you might want to modify it and create your own helper.
I have to thank Tim for his answer above, this answer about UIScrollView and also this tutorial.
Here's how I would re-align my uiviews to get your solution:
Drag drop one UIImageView and place it to the left.
Drag drop one UIView and place it to the right of UIImageView.
Drag drop two UILabels inside that UIView whose leading and trailing constraints are zero.
Set the leading constraint of UIView containing 2 labels to superview instead of UIImagView.
IF UIImageView is hidden, set the leading constraint constant to 10 px to superview. ELSE, set the leading constraint constant to 10 px + UIImageView.width + 10 px.
I created a thumb rule of my own. Whenever you have to hide / show any uiview whose constraints might be affected, add all the affected / dependent subviews inside a uiview and update its leading / trailing / top / bottom constraint constant programmatically.
This is an old question but still I hope it will helps. Coming from Android, in this platform you have an handy method isVisible to hide it from the view but also not have the frame considered when the autolayout draw the view.
using extension and "extend" uiview you could do a similar function in ios (not sure why it is not in UIKit already) here an implementation in swift 3:
func isVisible(_ isVisible: Bool) {
self.isHidden = !isVisible
self.translatesAutoresizingMaskIntoConstraints = isVisible
if isVisible { //if visible we remove the hight constraint
if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
self.removeConstraint(constraint)
}
} else { //if not visible we add a constraint to force the view to have a hight set to 0
let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
self.addConstraint(height)
}
self.layoutIfNeeded()
}
the proper way to do it is to disable constraints with isActive = false. note however that deactivating a constraint removes and releases it, so you have to have strong outlets for them.
I think this is the most simple answer. Please verify that it works:
StackFullView.layer.isHidden = true
Task_TopSpaceSections.constant = 0. //your constraint of top view
check here https://www.youtube.com/watch?v=EBulMWMoFuw