use constraints to automatically adjust the spacing ios - ios

hello i am using constraints to show the view as desired horizontally and vertically an it is working well,
But now according to my requirement i have to use constraints in a seance that if i hide(set hidden) any of the placed button or label then remaining buttons or labels have to adjsut automatially.
for ex :- i am using four button in a series with equal spacing(filling the view). Now if i hide one of the button from the code then remaining three buttons should adjust themseleves according to space
I want this:
to convert to:
if i hide one of the four buttons.

Instead of hiding the button, you can animate and change it's width constraint to 0. This will probably look better also
i.e something like this:
button2WidthConstraint.constant = 0
UIView.animateWithDuration(0.3) { () -> Void in
self.view.setNeedsLayout()
}

This is kinda difficult using autolayout only. Even if you just change the width constraint to 0 you're gonna have to deal with the spacings. You will need to adjust spacing for edge buttons differently then for buttons in middle - when you change width to 0, there's still left and right constraint and you need to determine which one to zero out as well.
One approach is to use UIStackView which handles this case automatically for you. You can set spacing and distribution of buttons in storyboard, made little sample app so you can see the settings yourself.
After that you just call: removeFromSuperview and the UIStackView distributes other buttons for you. You can animate the transition as well.

i have tried to solve the problem this way, you can change it as per your requiremne. I have taken UIView(you can take UIButton here) and UIStackView(horizontal axis).
-(void)createOrUpdateUI
{
//View 1
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor blueColor];
[view1.heightAnchor constraintEqualToConstant:40].active = true;
[view1.widthAnchor constraintEqualToConstant:60].active = true;
//View 2
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
[view2.heightAnchor constraintEqualToConstant:40].active = true;
[view2.widthAnchor constraintEqualToConstant:60].active = true;
//View 3
UIView *view3 = [[UIView alloc] init];
view3.backgroundColor = [UIColor magentaColor];
[view3.heightAnchor constraintEqualToConstant:40].active = true;
[view3.widthAnchor constraintEqualToConstant:60].active = true;
//View 4
UIView *view4 = [[UIView alloc] init];
view4.backgroundColor = [UIColor magentaColor];
[view4.heightAnchor constraintEqualToConstant:40].active = true;
[view4.widthAnchor constraintEqualToConstant:60].active = true;
//Stack View
UIStackView *stackView = [[UIStackView alloc] init];
stackView.axis = UILayoutConstraintAxisHorizontal;
stackView.distribution = UIStackViewDistributionEqualSpacing;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 30;
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, nil];
for (UIView *view in viewArray) {
[stackView addArrangedSubview:view];
}
stackView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:stackView];
//Layout for Stack View
[stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = true;
[stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = true;
}
and it outpit's as :
in above code just change line :
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, nil];
to
NSArray *viewArray = [[NSArray alloc] initWithObjects:view1, view2, view3, view4, nil];
and output is :
Hope it helps.

Related

UIStackView on TitleView Spacing issue after iOS 16

I have an UIStackView set up on the title view of the navigation bar in my app. It simply shows the icon of the app to the left of the title. In iOS 15 and earlier, this displayed fine. Right now, however, it shows fine the first time you launch the app, but navigating to a different view, the icon is in the wrong spot of THAT view, and will be in the incorrect spot when returning to the parent view as well. Pictures of this are below the code.
- (void)viewWillAppear:(BOOL)animated
UIImageView *theimageView = [[UIImageView alloc] init];
theimageView.contentMode = UIViewContentModeScaleAspectFit;
theimageView.image = [UIImage imageNamed:nameOfColor];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.font = [UIFont fontWithName:#"Roboto-Bold" size:16];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.text = self.title;
UIStackView *hStack = [[UIStackView alloc] init];
[hStack addArrangedSubview:theimageView];
[hStack addArrangedSubview:titleLabel];
hStack.axis = UILayoutConstraintAxisHorizontal;
self.navigationItem.titleView = hStack;
[super viewWillAppear:animated];
I'm able to reproduce your issue. One thing I'm sure of is that apple has made some changes to this part of UIKit.
Providing a custom UIStackView as a titleView to the navigation bar is totally OK. But to properly set a UIStackView you should add some constraints.
You should add several constraints to help the UIStackView know how you want to layout your arranged subview.
UIImageView *theimageView = [[UIImageView alloc] init];
theimageView.contentMode = UIViewContentModeScaleAspectFit;
theimageView.image = [UIImage systemImageNamed: #"peacesign"];
// Contraints to image height
[theimageView.heightAnchor constraintEqualToConstant: 20].active = true;
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.font = [UIFont fontWithName:#"Roboto-Bold" size:16];
titleLabel.textColor = [UIColor blackColor];
titleLabel.text = self.title;
// Contraints to label height
[titleLabel.heightAnchor constraintEqualToConstant: 20].active = true;
UIStackView *hStack = [[UIStackView alloc] init];
[hStack addArrangedSubview:theimageView];
[hStack addArrangedSubview:titleLabel];
hStack.axis = UILayoutConstraintAxisHorizontal;
// Center the items
hStack.alignment = UIStackViewAlignmentCenter;
// Spacing of 6
hStack.spacing = 6;
self.navigationItem.titleView = hStack;
[super viewWillAppear:animated];
If you add the two missing constraints, now the titleView works like iOS 15 and earlier.
The way we solved this in our app is by setting both the content compression resistance priority and the content hugging priority, of both the image view and the label, for both horizontal and vertical, to UILayoutPriorityRequired.
https://developer.apple.com/documentation/uikit/uiview/1622485-setcontenthuggingpriority?language=objc
https://developer.apple.com/documentation/uikit/uiview/1622526-setcontentcompressionresistancep?language=objc

Keep subviews as original dimensions in UIStackView

Currently I'm trying to make 3 buttons in a horizontal group of 3 that have equal spacing
UIStackView * menuButtons = [[UIStackView alloc] initWithFrame:CGRectMake(0, 0, 60, 16)];
menuButtons.axis = UILayoutConstraintAxisHorizontal;
menuButtons.alignment = UIStackViewAlignmentBottom;
menuButtons.spacing = 6;
menuButtons.distribution = UIStackViewDistributionEqualSpacing;
UIButton* btnOne = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 16, 16)];
UIButton* btnTwo = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 16, 16)];
UIButton* btnThree = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 16, 16)];
[menuButtons addArrangedSubview:btnOne];
[menuButtons addArrangedSubview:btnTwo];
[menuButtons addArrangedSubview:btnThree];
ive noticed when I inspect the frame of the button views after adding them to my stackview programatically, they all show different sizes than originally set, when I expect them all to still be 16
ie: btnOne CGSize(17, 16), btnTwo CGSize(23.5, 23,5), btnThree CGSize(21.3, 21.3)
I don't understand why this happens, and I've tried all the distributions but I can't figure this out when I'm not setting the frames on these views anywhere else
Here is an example - based on your code - using auto-layout constraints.
Note that you do NOT need to specify width and height constraints for your UIStackView, as it will expand as needed to fit the arranged subviews.
If you do set a width constraint, you'll end up with (likely) unexpected results, because you've also specified fixed-width spacing of 6.
So, you would want either:
width constraint + UIStackViewDistributionEqualSpacing, or
no width constraint + default distribution + .spacing = 6
Hope this helps...
//
// StackTestViewController.m
//
// Created by Don Mag on 6/24/17.
//
#import "StackTestViewController.h"
#interface StackTestViewController ()
#end
#implementation StackTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupMenuButtons];
}
- (void)setupMenuButtons {
// instantiate a UIStackView
UIStackView *menuButtons = [[UIStackView alloc] init];
// horizontal
menuButtons.axis = UILayoutConstraintAxisHorizontal;
// spacing between arranged views
menuButtons.spacing = 6;
// instantiate 3 buttons
UIButton* btnOne = [UIButton new];
UIButton* btnTwo = [UIButton new];
UIButton* btnThree = [UIButton new];
// give 'em background colors so we can see them
btnOne.backgroundColor = [UIColor redColor];
btnTwo.backgroundColor = [UIColor greenColor];
btnThree.backgroundColor = [UIColor blueColor];
// for each button,
// tell it not to use Autoresizing Mask
// set width and height constraints each to 16
for (UIButton *b in #[btnOne, btnTwo, btnThree]) {
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[b.widthAnchor constraintEqualToConstant:16.0].active = YES;
[b.heightAnchor constraintEqualToConstant:16.0].active = YES;
}
// add them to the Stack View
[menuButtons addArrangedSubview:btnOne];
[menuButtons addArrangedSubview:btnTwo];
[menuButtons addArrangedSubview:btnThree];
// add the Stack View to self view
[self.view addSubview:menuButtons];
// tell Stack View not to use Autoresizing Mask
[menuButtons setTranslatesAutoresizingMaskIntoConstraints:NO];
// set X and Y position for the Stack View (just using 80,80 for this example)
[menuButtons.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:80.0].active = YES;
[menuButtons.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:80.0].active = YES;
}
#end

custom 'add' view animation to uistackview

I'm playing with iOS9 uistackview and having problems to customize animation when adding element. You can notice that the yellow element position is being animated from top-left corner and starting with zero width.
I would like yellow element already being positioned below violet element and animate only height from 0 to some value (inverse behavior of green element animation)
I know its easy to do it when element is already in arrangeViews hierarchy unfortunately in my project I have to add and remove items dynamically at arbitrary index. Code below:
#interface ViewController ()
{
NSLayoutConstraint* heightConstraint2;
UIStackView *stackView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//View 1
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 60)];
view1.backgroundColor = [UIColor blueColor];
[view1.heightAnchor constraintEqualToConstant:60].active = true;
[view1.widthAnchor constraintEqualToConstant:300].active = true;
//View 2
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
// [view2.heightAnchor constraintEqualToConstant:100].active = true;
[view2.widthAnchor constraintEqualToConstant:70].active = true;
heightConstraint2 = [view2.heightAnchor constraintEqualToConstant:100];
heightConstraint2.active = true;
//View 3
UIView *view3 = [[UIView alloc] init];
view3.backgroundColor = [UIColor magentaColor];
[view3.heightAnchor constraintEqualToConstant:100].active = true;
[view3.widthAnchor constraintEqualToConstant:180].active = true;
//Stack View
stackView = [[UIStackView alloc] init];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.distribution = UIStackViewDistributionEqualSpacing;
stackView.alignment = UIStackViewAlignmentCenter;
stackView.spacing = 0;
[stackView addArrangedSubview:view1];
[stackView addArrangedSubview:view2];
[stackView addArrangedSubview:view3];
stackView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:stackView];
}
-(void)viewDidAppear:(BOOL)animated {
heightConstraint2.constant = 0;
UIView *view4 = [[UIView alloc] init];
view4.backgroundColor = [UIColor yellowColor];
[view4.heightAnchor constraintEqualToConstant:80].active = true;
[view4.widthAnchor constraintEqualToConstant:180].active = true;
NSLayoutConstraint* heightConstraint4 = [view4.heightAnchor constraintEqualToConstant:0];
[stackView insertArrangedSubview:view4 atIndex:3];
// [stackView layoutIfNeeded];
heightConstraint2.constant = 0;
heightConstraint4.constant = 80;
[UIView animateWithDuration:0.5
delay:3
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[stackView layoutIfNeeded];
}
completion:nil];
}
Add your view but set it as hidden, Then in animationBlock, set hidden = true

UITextField does not interact with user when added to a clipped view area

I created a UIView with an outlet named _waypointSubview inside a UIScrollView, which is also inside another UIView, and all 3 were created in my storyboard. From here I am adding UITextFields programmatically, and once there are over 5 UITextFields added it is clipped from the UIScrollView. The UIScrollView and the _waypointSubview are then resized to compensate for the extra UITextField added. The user can then scroll down to see all the UITextFields that were clipped from the view. Only the UITextFields that were clipped do not allow editing. I am not sure why that is. So if I add 10 of them, I cannot edit 6 thru 10. I tried programmatically telling it to enable user interaction using the call [waypointTextField setUserInteractionEnabled:YES];but it did not work.
Can someone please explain why I cannot edit those fields? Thank you.
- (IBAction)addWaypoint:(id)sender {
UITextField *waypointTextField = [[UITextField alloc] initWithFrame:CGRectMake(xCord, yCord, 250, 40)];
waypointTextField.borderStyle = UITextBorderStyleRoundedRect;
waypointTextField.font = [UIFont systemFontOfSize:18];
waypointTextField.placeholder = #"enter waypoint";
waypointTextField.autocorrectionType = UITextAutocorrectionTypeNo;
waypointTextField.keyboardType = UIKeyboardTypeDefault;
waypointTextField.returnKeyType = UIReturnKeyDone;
waypointTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
waypointTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
waypointTextField.delegate = self;
[_waypointSubview addSubview:waypointTextField];
UITextField *commentTextField = [[UITextField alloc] initWithFrame:CGRectMake(xCord + 300, yCord, 380, 40)];
commentTextField.borderStyle = UITextBorderStyleRoundedRect;
commentTextField.font = [UIFont systemFontOfSize:18];
commentTextField.placeholder = #"enter comment";
commentTextField.autocorrectionType = UITextAutocorrectionTypeNo;
commentTextField.keyboardType = UIKeyboardTypeDefault;
commentTextField.returnKeyType = UIReturnKeyDone;
commentTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
commentTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
commentTextField.delegate = self;
[_waypointSubview addSubview:commentTextField];
// if waypointArray is not initialized yet, create and add textfield
if (waypointArray == nil) {
waypointArray = [NSMutableArray arrayWithCapacity:1];
[waypointArray insertObject:waypointTextField atIndex:0];
commentArray = [NSMutableArray arrayWithCapacity:1];
[commentArray insertObject:commentTextField atIndex:0];
}
else{
// check amount of space left in scroll view, and adjust if needed
if ([waypointArray count] > 4) {
self.waypointScrollView.contentSize = CGSizeMake(708, 255 + (([waypointArray count] - 4)*50));
CGRect frame = self.waypointSubview.frame;
frame.size.height += 50;
self.waypointSubview.frame = frame;
//[waypointTextField setUserInteractionEnabled:YES];
}
// add new text fields to corresponding arrays
[waypointArray addObject:waypointTextField];
[commentArray addObject:commentTextField];
}
yCord += 50; // update the yCoordinate that will be used for the next textField placement
}
I am assuming you need to connect _waypointSubview to the view controller on your storyboard by just reading the comments.
In order to do that
Right click on the the view controller icon on the storyboard view controller
Click and drag the little circle to the object you are trying to edit or the method you're trying to implement from the IBAction.
Check your bounds of your textfield as -
UITextField *commentTextField = [[UITextField alloc] initWithFrame:CGRectMake(xCord + 300, yCord, 380, 40)];
Here if you observe the xcordinate, an Iphone has a width of 320points.
If your textfield is 380points wide then how can you accomodate it WHEN its (0,0) is starting from xCord(I dont know what is its value) + 300.
Lower the xcordinate value and color its background, to check whether it falls in bounds as pointed out by #rdelmar

Why does navigationItem.titleView align left when presentmodalviewcontroller called?

I'm using a UILabel for the titleView of a navigation bar (I'm making simple in-app web browser). It works fine, except that when I present a modal view controller, the titleView shifts from the center of the navbar to the far left (underneath the back button). I've tested in 3.0 and up. Here is relevant code:
- (void)viewDidLoad {
[super viewDidLoad];
// Title view label
CGRect labelFrame = CGRectMake(0.0, 0.0, 120.0, 36.0);
UILabel *label = [[[UILabel alloc] initWithFrame:labelFrame] autorelease];
label.font = [UIFont boldSystemFontOfSize:14];
label.numberOfLines = 2;
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor blackColor];
label.shadowOffset = CGSizeMake(0.0, -1.0);
label.lineBreakMode = UILineBreakModeMiddleTruncation;
self.navigationItem.titleView = label;
}
-(void)displayComposerSheet:(NSString*)mailto
{
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
}
Screenshots:
Any idea why this is happening? Thanks.
I looked into the problem with some hit and try and found the following facts:
If the UINavigationBar doesn't have the rightBarButtonItem, the titleView shifts towards the right by ~30pts.
It could be reproduced for leftBarButtonItem. But I haven't tried.
In a scenario where the a default UINavigationBar's (with no changes to rightBarButtonItem defaults) titleView is set. And then a new UIView is pushed to the navigation stack which HAS a rightBarButtonItem. Now, if this view is popped [with back button], the navigation bar will remove the rightBarButtonItem. And this will account for the weird offset that shifts the titleView towards a side.
How I fixed the problem was like this:
self.navigationItem.titleView = myCustomTitleView;
// Fake right button to align titleView properly.
UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:[[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 1)]];
// Width equivalent to system default Done button's (which appears on pushed view in my case).
rightBarButtonItem.enabled = NO;
self.navigationItem.rightBarButtonItem = rightBarButtonItem;
Everything is sweet now. yummmm.
Thanks to DougW for pointing me in right direction. Here's the best hack I found. Basically I retain the UILabel as a class property. Before presenting modal view I unset the titleView, and then reset it immediately after. When the modal view is dismissed I unset then reset the titleView. To the user none of this is visibly notable.
-(void)displayComposerSheet:(NSString*)mailto
{
self.navigationItem.titleView = nil;
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
picker.navigationBar.tintColor = [APPDELEGATE getNavTintColor];
[picker setToRecipients:[NSArray arrayWithObject:mailto]];
[self presentModalViewController:picker animated:YES];
[picker release];
self.navigationItem.titleView = titlelabel;
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
self.navigationItem.titleView = nil;
self.navigationItem.titleView = titlelabel;
[self dismissModalViewControllerAnimated:YES];
}
Does it animate? It may be animating the title view as though it's transitioning to a new view. I don't see anything wrong with your code as written.
I would suggest in your displayComposerSheet, you just unset the titleView, or animate the alpha of the titleView to 0.0. Then, animate it back to 1.0 when you dismiss the modal view controller. Not ideal, but it may look better that way.
Frankly, the whole UINavigation system is crap. We went ahead and re-wrote it ground up because of bizarre issues like these.
The only problem is your frame size. so u have to change it.
Try this one.
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 36.0)];
label.font = [UIFont boldSystemFontOfSize:14];
label.numberOfLines = 2;
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor blackColor];
label.shadowOffset = CGSizeMake(0.0, -1.0);
label.lineBreakMode = UILineBreakModeMiddleTruncation;
label.text=#"Stack Overflow";
self.navigationItem.titleView = label;
You can try move the code in viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// You code to customize title view
self.navigationItem.titleView = logoImage;
}
It works for me.
If you change the width size to be small like 100 points or smaller instead of 120 you set, this problem may go away. Setting width of the label smaller worked for me.
UIView *view= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
[view setUserInteractionEnabled:NO];
view.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"logo_small.png"]];
UIBarButtonItem *barButton=[[UIBarButtonItem alloc]initWithCustomView:view ];
self.navigationItem.leftBarButtonItem = barButton;

Resources