TOMSMorphingLabel
TOMSMorphingLabel copied to clipboard
intrinsicSize not correct
I want to self-size cells in my collection view, so I need a means of updating the cell's constraints based on the content. The content in this case is a TOMSMorphingLabel
.
It appears to report a size of {0, 0}
regardless of what text is set onto it.
This is surprising, considering it is just a UILabel subclass, right.
I thought I could spend just 5 minutes fixing this but I saw the way that the label works and I think it's better if the author had a look at how best to approach the problem.
I wrote a couple of tests you might find useful...
- (void)test_set_text_makes_text_available
{
TOMSMorphingLabel *sut = [[TOMSMorphingLabel alloc] initWithFrame:CGRectMake(0, 0, 42, 42)];
sut.text = @"Text";
XCTAssertEqual(sut.text, @"Text");
}
- (void)test_intrinsic_size_of_target_text_should_be_the_same_as_ui_label
{
NSString *string = @"TOMSMorphingLabel rocks";
TOMSMorphingLabel *sut = [[TOMSMorphingLabel alloc] initWithFrame:CGRectMake(0, 0, 42, 42)];
sut.text = string;
UILabel *label = [[UILabel alloc] init];
label.font = sut.font;
label.text = string;
NSLog(@"normal label: %@", NSStringFromCGSize(sut.intrinsicContentSize));
NSLog(@"morphing label: %@", NSStringFromCGSize(label.intrinsicContentSize));
XCTAssertTrue(CGSizeEqualToSize(sut.intrinsicContentSize, label.intrinsicContentSize));
}
Hi. The problem with the intrinsicContentSize
probably originates from the fact that setText:
method animates by default and uses the actual text
property to store intermediate results while animating.
As a result we have the following:
- Calling
sut.text = string;
does not actually set thestring
as the text of the label immediately. It will asynchronously change the text to this value after the given amount of time. The intrinsic size is also updated with thetext
value being updated. -
text
and other properties which depend on it (includingintrinsicContentSize
) cannot be reliably read from theTOMSMorphingLabel
because at any given time thetext
property does not represent the final state of the text (which we try to set usingsetText:
setter), but rather it represents the current intermediate value which is displayed by the label in the current frame of the animation.
For example in the TOMSMorphingLabelExample project when I add the following code to the toggleTextForLabel:
method:
- (void)toggleTextForLabel:(UILabel *)label
{
NSString *text = self.textValues[self.idx++];
label.text = text;
NSLog(@"text: %@, label.text: %@", text, label.text);
.... // the rest unchanged
}
I get the following output on the iOS Simulator:
First label animation completed text: Swift, label.text: SwifTest text: Swiftilicious, label.text: Swiftilicious text: delicious, label.text: delSwiftilicious text: 开, label.text: 开delicious text: 开源, label.text: 开源 text: 2⃣3⃣4⃣, label.text: 2⃣3⃣4⃣开源 text: 1⃣2⃣3⃣4⃣5⃣, label.text: 1⃣2⃣3⃣4⃣5⃣ text: , label.text: 1⃣2⃣3⃣4⃣5⃣ text: Swift, label.text: Swift
Note the various 'hybrid' strings which are actually combinations of the text values passed to the setText:
. I guess the actual output would be dependent on the hardware on which the app is running.
So we can semi-reliably read the text
value only after the animation has finished. The same goes for the intrinsic content size.
A possible solution would be keeping an internal UILabel
for displaying the text and animations, but exposing the reliable label properties with getters and setters directly corresponding to each other.
This approach was used in cbpowell/MarqueeLabel which provides an UILabel
subclass with a special animated behavior.