Can't make content resistance work in a table view cell - ios

I have a UITableViewController that has a static field that displays a location.
The style this static field is "right detail".
The left side of the cell shows the city chosen by the user and the right side has an disclosure indicator with the label "Item Location".
When the user first navigates to this controller the left label is set to "No location" and everything looks fine, like this: (I can't post images since I don't have the rep)
"No location Item Location >"
The problem is that if I set a long city name (such as "Rancho Santa Margarita, California) the "Item Location" label shrinks and becomes filled with ellipsis (...)
"Rancho Santa Margarita, California It... >"
What I want to happen is that when the user picks a long city name then the city label will take as much space as it can without causing the "Item Location" label to shrink.
So basically it will look something like this:
"Rancho Santa Margarita, ... Item Location >"
Using the interface builder I tried setting both the Horizontal Content Hugging priority and Compression Resistance Priority of the right detail label (Item Location) to 1000 and I even set Horizontal Content Hugging priority and Compression Resistance priority of the city label to 1, but this doesn't seem to work. (it still looks like the second screenshot).
One more thing, when the user selects a new location I use the following method to update the location text:
- (void)setLocationText:(NSString *)text {
self.locationField.textLabel.text = text;
[self.locationField setNeedsLayout];
}
Where locationField is an outlet of the location UITableViewCell.
Without the code to setNeedsLayout the city text label doesn't grow to fit the city name, so that's the reason why I used this.
I'm using XCode 4.6 and targeting iOS 6.1
Do you guys know what could be the issue?
Thanks in advance,
Ido

Another option to using autolayout, is to subclass UITableViewCell with two label outlets and implement layoutSubviews
- (void)layoutSubviews {
[super layoutSubviews];
CGSize rightLabelSize = [self.rightLabel sizeThatFits:self.contentView.frame.size];
CGRect rightLabelFrame = CGRectZero;
rightLabelFrame.origin.x = self.contentView.frame.size.width - rightLabelSize.width;
rightLabelFrame.size = rightLabelSize;
self.rightLabel.frame = rightLabelFrame;
CGRect leftLabelFrame = CGRectZero;
leftLabelFrame.size.width = self.contentView.frame.size.width - rightLabelSize.width;
self.leftLabel.frame = rightLabelFrame;
}
Note that this does not add padding or really care much about the height of the labels, so you would need to modify to account for those things.
If you don't like subclassing, then you could construct and size the labels before you return the cells in your controllers tableView:cellForRowAtIndexPath: protocol method.

Related

How to prevent UILabel to fill the entire screen?

I am trying to display a UILabel that may take up multiple lines but I'm having problem with how the height is resized.
Here is what it looks when I have text over a single line, displaying correctly:
When the text spans multiple lines however this happens:
Here's the interface builder settings I'm using:
Ideally I'd like the text view to remain at athe top of the screen and just take up as much space as it needs to diaplay the text but I really can't tell where I am going wrong.
The text view is a bit tricky to handle with automatic layout. If possible use an UILabel. If not then there are several issues with the text view and the most manageable solution is to add the height constraint which is then manipulated in the code.
The height of the text view content can be determined as:
let height = textView.sizeThatFits(textView.frame.size).height
It is also possible to use
let height = textView.contentSize.height
But the results are sometimes incorrect.
You do need to then set the delegate for the text view so that on change you will refresh the size of the text view.
Well you did give it permission to do so based on your constraints. Any height > 0 as long as it's 20 from the top margin. Since you don't have any other views to base your height off of you can hook up an outlet to your label and use this:
#IBOutlet weak var label: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
label.sizeToFit()
}
Uncheck the "Preferred Width" explicit checkbox(In Size Inspector)
Remove the height constraint on you UILabel.
It will definitely work.

UILabel text property when set to nil or "" makes UILabel disappear from view (Swift / Autolayout/ iOS9.1)

I am going through the Stanford Winter 2015 Swift/iOS course and while doing the assignments I run into a behavior I'd like to change.
I use Autolayout as described in the videos (making the display pin to leading and trailing view edges) and the Calculator app "Display" UILabel is fine with an initial value of 0 and whenever the value used to set it (a String) is non-nil and non "".
If it is either nil or "", the entire UILabel disappears. What I am trying to do is to "clear" the display whenever there is no value to display or an incorrect calculation resulted in nil.
Any tips on who to deal with this in general? "Clearing" a UILabel without changing it's on-screen dimensions?
Edit (thanks Rob)
The UILabel has the following constraints
1. Option-click drag-left to containing UIView, selected "leading" something (on commute to work can't check yet for exact wording.
2. Same method as (1) except that the drag is to the right edge and selecting "trailing"
3. Option click-drag up to top of view, select "vertical" menu option.
4. Same as (3) except that drag is to a UIButton underneath the UILabel on the GUI.
With those settings, the label when it contains a number is always visible and (if understand, will color it to verify) stretches across the screen even if the text doesn't.
The layout looks correct in profile and landscape as long as content of UILabel is not empty. If empty, it seems to "shrink to fit" so much that the buttons below get moved up towards the top.
I'm a C++ dev since mid 90s but I have little UI experience and no more than a couple weeks experience in iOS/Swift development.
Thanks!
You can always give the UILabel a min width and min height or constraints that holds the left and right side of the label. That should keep the label from changing it's dimensions to zero.
Use a custom UILabel class assigned in Interface Builder >> Identity inspector >> Custom Class >> Class to override UILabel intrinsic content size.
No need to create any superfluous auto-layout constraints.
Swift:
class UILabelNonCompressible: UILabel
{
private static let NonCompressibleInvisibleContent = " "
override var intrinsicContentSize: CGSize
{
if /* zero-width */ text == nil ? true : text!.isEmpty
{
// prefer mirror-and-calculate over modify-calculate-restore due to KVO
let doppelganger = createCopy()
// calculate for any non-zero -height content
doppelganger.text = UILabelNonCompressible.NonCompressibleInvisibleContent
// override
return doppelganger.intrinsicContentSize
}
else
{
return super.intrinsicContentSize
}
}
}
You will also need "How do copy for UILabel?":
extension UILabel
{
func createCopy() -> UILabel
{
let archivedData = NSKeyedArchiver.archivedData(withRootObject: self)
return NSKeyedUnarchiver.unarchiveObject(with: archivedData) as! UILabel
}
}

What to do if label is bigger than parent UIVIew?

I have two multiline UILabels. The parent UIView only has a certain size (height) but the labels can contain a long string. What I want is that the first label is always shown (e.g. abbreviated with ... if it gets too long). If there is room the second label should be shown. Again if it's too long it should get abbreviated with ...
This is the constraint I'm currently using: V:|-5-[title]-0-[description]-(>=0)-|
In some cases the second label gets cut off like this:
What should I do? Do I have to change the constraint? Can I hide the second label somehow? But how should I detect when the label is cut off?
In another case the second label wasn't shown at all. The second label should also not overflow the parent UIView. How should one handle such cases?
I believe rounding occurs somewhere in iOS because the label would fit (one line and then at the end with ...) if there would be 0.75pt more space. So it draws it but it is cut off because of the missing 0.75pt space.
Now I took the following approach: Calculate if the second label (reference text which should be about one line) would fit in the parent view? And if not hide it. I only have code in C# but you should get the idea:
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
NSString referenceTextToMeasure = new NSString ("Lorem ipsum dolor sit amet.");
// take the font of the description label which was cut off
CGRect referenceTextRect = referenceTextToMeasure.GetBoundingRect (
new CGSize(this.Frame.Width, nfloat.MaxValue),
NSStringDrawingOptions.UsesLineFragmentOrigin,
new UIStringAttributes () { Font = descriptionLabelFont },
new NSStringDrawingContext ()
);
// 5 is the spacing constraint I have on the top
if ((this.Frame.Height - TitleLabel.Frame.Height - 5) < referenceTextRect.Height )
DescriptionLabel.Hidden = true;
}
Seems to work quite well, though a full test has to be made. You could also use a fixed height instead of referenceTextRect.Height and you would save some calculations. Nevertheless, the way I used allows of adapting the fonts so everything is dynamically calculated.

Update segmented control in ios without change interface

when I update segmented control text, the interface (segment's width) changed and cut some letters.
[segmentedcontoll setTitle:#"test" forSegmentAtIndex:1];
segmentedcontoll.apportionsSegmentWidthsByContent = YES;
How can I solve this ?
EDIT:
It looks like your content has outgrown the dimensions of the standard UISegmentedControl.
If you are okay with smaller font, it's possible to set the entire control to have a smaller font point size, seen here.
Another option is to configure the segments the other supported way.. With images. It's a little bit of a hack, but you can create images on the fly with the UIView Snapshotting API of views/labels configured however you want and set images for each segment instead of using text. This would allow you to create 2 line labels with fixed widths and set images for each section to be images generated from the label as the content changes. More work, but you would still be using the standard class.
The last option, which might work the best for you, is to create some other custom control that does what you would like. After all, UISegmentedControl really is just a nice button container. And it does somewhat seem like you are using the control in a non-standard way - both as a control and an input form section.
Others have gone this route before and created alternatives that you can use.
You can create a separate class as below,
class CustomSegmentedControl: UISegmentedControl {
//code for creating multi line
override func didMoveToSuperview()
{
for segment in subviews
{
for subview in segment.subviews
{
if let segmentLabel = subview as? UILabel
{
segmentLabel.numberOfLines = 0 //just change here the number of lines and check it.
}
}
}
}
}
and create an outlet in your viewcontroller as,
// Initialize
let items = ["Purple", "Green", "New Segment"]
let customSC = CustomSegmentedControl(items: items)
use customSC and do what ever you want to do, similar to segmentedControl object.

How can I detect taps on a particular part (substring) of a UILabel?

I am new to iOS development. I dont know whether this questions has been asked already or not,
I tried searching for the solution on stackoverflow but didn't get any results.
Question :
I have a UILabel called myLabel with the text: "Click here to proceed"
Now the problem is I want to perform an action when user taps only "Click".
I know how to use UITapGestureRecognizer, but it responds to the whole UILabel. Is it possible to just detect when user taps only on string "Click"?
You could check the location of the touch to see if it is on the word "Click". This may not be completely accurate though and may break if you change the text in your label.
What you could do is first get the location of the click by using:
CGPoint location = [gesture locationInView:gesture.view];
This gets the location of the gesture in your view.
Once you have this, you can either have the size of the word "Click" hard-coded or you can create an identical label to your gesture label to get the size, such as:
UILabel *sizeLabel = //create label with same font.
[sizeLabel setText:#"Click"];
float width = [sizeLabel sizeThatFits:CGSizeMake(MAXFLOAT,MAXFLOAT)].width;
You could also do this when creating the actual label and store this width in a static variable so that you only have to do it once and you don't have to create an extra label (ie set the text of your real label to #"Click", get the width, and then set it to the real text).
Once you have the width, you and the location of the tap, you can check if the word "Click" was tapped by comparing:
if (location.x < width) {
//Put your tap code in here.
}

Resources