UIStackView on TitleView Spacing issue after iOS 16 - ios

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

Related

Presenting UIAlertController increases UILabel font size inside UIToolbar in iOS 11

I have an issue with font size of UILabel, which is programmatically added in UIToolbar:
- (UILabel *)createTitleLabel
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(_topToolbar.frame)/3, CGRectGetHeight(_topToolbar.frame))];
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor colorWithWhite:0.1 alpha:1.0];
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont fontWithName:#"ArialMT" size:18];
label.adjustsFontSizeToFitWidth = YES;
label.minimumScaleFactor = 0.8;
label.lineBreakMode = NSLineBreakByTruncatingTail;
return label;
}
// -----
UILabel *label = [self createTitleLabel];
self.titleLabel = label;
[self.topToolbar insertItem:[[UIBarButtonItem alloc] initWithCustomView:label] atIndex:_topToolbar.items.count/2 animated:NO];
// ------ code for adding item to toolbar
- (void)insertItem:(UIBarItem *)barItem atIndex:(NSUInteger)index animated:(BOOL)animated
{
NSMutableArray *toolbarItems = [self.items mutableCopy];
NSAssert(index <= toolbarItems.count, #"Invalid index for toolbar item");
[toolbarItems insertObject:barItem atIndex:index];
[self setItems:toolbarItems animated:animated];
}
After setting a bit large text to title label, it works as expected, font is reduced, and tail is truncated, but when I present UIAlertController, this font is getting larger, and UILabel width is growing, which hides other bar button items inside toolbar.
Beginning with iOS 11, views added to toolbars as UIBarButtonItem using customView initializer are now laid out using auto layout. You should add sizing constraints on your label. For example:
[label.widthAnchor constraintEqualToConstant:CGRectGetWidth(_topToolbar.frame)/3].active = YES;
[label.heightAnchor constraintEqualToConstant:CGRectGetHeight(_topToolbar.frame)].active = YES;
Otherwise, auto layout will use the intrinsic content size of your label which is causing the label.adjustsFontSizeToFitWidth = YES; to be ignored. Sizing likely works initially because adding a bar button item doesn't trigger a layout pass, however presenting and dismissing another view controller will cause auto layout to perform a pass for your view controller's entire view hierarchy.
For more information see the WWDC 2017 session Updating your app for iOS 11.

Adding an Image with a Title in a UINavigationController

I would like to show a small icon next to the title in my UINavigationController.
Through the magic of Photoshop, like this:
I know I need to create a new view and build the image and title into it. Here is what I am doing:
In viewDidLoad in the UINavigationController view controller, I call the method
[self setTitleBar];
Which calls this method:
- (void) setTitleBar {
CGRect navBarFrame = self.navigationController.navigationBar.frame;
//UIView *titleView = [[UIView alloc] initWithFrame:CGRectMake(navBarFrame.origin.x, navBarFrame.origin.y, (leftButtonFrame.origin.x + leftButtonFrame.size.width) - rightButtonFrame.origin.x, navBarFrame.size.height)];
UIView *titleView = [[UIView alloc] initWithFrame:CGRectMake(navBarFrame.origin.x, navBarFrame.origin.y,self.view.frame.size.width,navBarFrame.size.height)];
titleView.backgroundColor = [UIColor clearColor];
CGPoint tvCenter = CGPointMake(titleView.frame.size.width/2, titleView.frame.size.height/2);
UIImage * icon = [UIImage imageNamed:#"star"];
UIImageView *iconView = [[UIImageView alloc] initWithImage:icon];
iconView.frame = CGRectMake(0, 0, icon.size.width, icon.size.height);
UILabel *title = [[UILabel alloc] init];
title.backgroundColor = [UIColor clearColor];
title.textColor = [UIColor whiteColor];
title.textAlignment = NSTextAlignmentCenter;
title.text = #"SOME TITLE";
title.frame = CGRectMake(0, 0, 100, titleView.frame.size.height);
[title sizeToFit];
iconView.center = CGPointMake(tvCenter.x - (icon.size.width/2), tvCenter.y);
[titleView addSubview:iconView];
[titleView addSubview:title];
self.navigationItem.titleView = titleView;
}
My logic in the titleView is: Get the left most button's frame and get the right most buttons frame. THEN do some math to figure out how big the view can be. That should be the titleView's frame size.
However, I can't seem to get it to work. If I plug in a frame size of 0,0,100,40; then it shows the frame but everything is squished together. But you see it. I know that 100 should be dynamic to ensure that the title is shown.
But I can't seem to figure it out.
Any help?
You can place objects on the Navigation Controller View, as subviews.
- (void) setTitleBar {
//Let's say your icon size is 20
int starSize = 20;
//Now you'll have to calculate where to place the ImageView respect the TextSize (for this you'll need to know the text and font of your UINavigationItem title)
CGSize textSize = [#"SOME TITLE" sizeWithAttributes:#{NSFontAttributeName:[UIFont fontWithName:#"navfontname" size:15]}];
UIImageView *startImageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.navigationController.view.frame.size.width/2 - textSize.width/2, self.navigationController.view.frame.size.height/2 - starSize/2, starSize,starSize)];
startImageView.image = [UIImage imageNamed:#"star"];
[self.navigationController.view addSubview:startImageView];
}

use constraints to automatically adjust the spacing 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.

Center UILabel Subview

I have a UIImageView as the background view of my table view. When there is no data for the table, I am adding a UILabel as a subview of the image view. I am having trouble getting the label centered though. The label is always off to the right.
UIImageView *backgroundImageView = [[UIImageView alloc]initWithFrame:self.tableView.frame];
backgroundImageView.image = [UIImage imageNamed:#"background"];
backgroundImageView.center = self.tableView.center;
self.noLocationsLabel = [[UILabel alloc]initWithFrame:backgroundImageView.frame];
self.noLocationsLabel.center = backgroundImageView.center;
self.noLocationsLabel.text = #"No saved locations";
self.noLocationsLabel.textAlignment = NSTextAlignmentCenter;
self.noLocationsLabel.textColor = [UIColor blackColor];
self.noLocationsLabel.font = [UIFont fontWithName:#"Chalkboard SE" size:24.0];
self.tableView.backgroundView = backgroundImageView;
if (self.locationsArray.count == 0) {
self.navigationItem.rightBarButtonItem.enabled = NO;
[self.tableView.backgroundView addSubview:self.noLocationsLabel];
self.tableView.userInteractionEnabled = NO;
}
If your image frame is say 50,50,50,50, then your label will also be 50,50,50,50, but since it's a subview of the image view, it will never be in the center. Same concept applies to center. Center is only in respect to the views superview.
In order to make it center you could set the frame like:
CGRect rect = CGRectMake(0,0,backgroundImageView.frame.size.width,backgroundImageView.frame.size.height);
self.noLocationsLabel = [[UILabel alloc]initWithFrame: rect];
Or perhaps even:
self.noLocationsLabel = [[UILabel alloc]initWithFrame: backgroundImageView.bounds];
Edit:
You can use center on subviews. But you have to set the correct center.
self.noLocationsLabel.center = CGPointMake(CGRectGetMidX(backgroundImageView.bounds),CGRectGetMidY(backgroundImageView.bounds));
Difference between frame and bounds

Vertical Shift UILabel Text Without Affecting Background

I've been trying to figure out how to vertically align my UILabel text to the top or bottom. The problem is, I have an image behind it which also gets shifted with the text.
Is there a way to keep the background color/image in place, but only shift the text up or down?
Here's the current code:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (!self) return nil;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,
0,
frame.size.width,
frame.size.height)];
label.textAlignment = NSTextAlignmentLeft;
label.font = [UIFont fontWithName:#"AvenirNext-Bold" size:12];
label.backgroundColor = [UIColor clearColor];
[self addSubview:label];
self.numberLabel = label;
self.layer.cornerRadius = 3;
return self;
}
I've been following this tutorial, but it shifts everything, including my label background.
Vertically align text to top within a UILabel
find the height of the text.
Use another view to show image.
Set frame of label relative image view with height of the text and whatever position you want to set for label.
ON the label if you're not using auto layout use:
[YOUR_LABEL sizeToFit];
This will resize your label to fit the text.
What is the background image ? A separate UIImageView ? How are you setting this image ?

Resources