UILabel 你真的会用么?深入源码及坑点分析

5,711 阅读11分钟

引言

UILabel 作为日常开发中出镜率极高的 UI 控件。本文将介绍 Apple 底层是如何设计 UILabel 的文字信息管理的,以及我们在日常使用中会遇到什么坑。

基础属性

UILabel *label = [[UILabel alloc] init];
NSLog(@"%@", label.textColor);
NSLog(@"%@", label.font);
NSLog(@"%@", label.shadowColor);
NSLog(@"%@", @(label.shadowOffset));

// 这些属性到底是如何存储的?
<UIDynamicSystemColor: 0x6000031597c0; name = labelColor>
<UICTFont: 0x7ffdb9e08fb0> font-family: ".SFUI-Regular"; font-weight: normal; font-style: normal; font-size: 17.00pt
(null)
NSSize: {0, -1}

猜想 1

通过这几个属性对应的成员变量进行存储的。

通过打印 UILabel 对象的成员变量列表,我们发现没有对应的 _textColor_font_shadowColor_shadowOffset 这些成员变量。那这些属性是存储在哪里的呢?

仔细研究这个列表之后,我们发现一个有意思的成员变量:_content

_content (_UILabelContent*): <_UILabelContent: 0x60000260cac0>

in UILabel:
	_size (struct CGSize): {0, 0}
	_highlightedColor (UIColor*): nil
	_numberOfLines (long): 1
	_baselineInfo (struct ?): {
		firstBaseline (double): 0
		lastBaseline (double): 0
		referenceBounds (struct CGRect): {{0, 0}, {0, 0}}
		measuredNumberOfLines (long): -1
	}
	_previousBaselineOffsetFromBottom (double): 0
	_previousFirstLineBaseline (double): 0
	_minimumScaleFactor (double): 0
	_content (_UILabelContent*): <_UILabelContent: 0x60000260cac0>
	_synthesizedAttributedText (NSAttributedString*): nil
	_cachedSynthesizedTextAttributes (NSDictionary*): nil
	_fallbackColorsForUserInterfaceStyle (NSMutableDictionary*): nil
	_minimumFontSize (double): 0
	_lineSpacing (long): 0
	_layout (id): nil
	_scaledMetrics (_UILabelScaledMetrics*): nil
	_intrinsicContentSizeCache (_UITextSizeCache*): nil
	_contentsFormat (long): 0
	_cuiCatalog (CUICatalog*): nil
	_cuiStyleEffectConfiguration (CUIStyleEffectConfiguration*): <CUIStyleEffectConfiguration: 0x600000701f40>
	_marqueeAnimations (NSMutableDictionary*): nil
	_marqueeMaskAnimations (NSMutableDictionary*): nil
	_textLabelFlags (struct ?): {
		highlighted (b1): NO
		autosizeTextToFit (b1): NO
		supportMultiLineShrinkToFit (b1): NO
		autotrackTextToFit (b1): NO
		baselineAdjustment (b2): 0
		enabled (b1): YES
		wordRoundingEnabled (b1): NO
		explicitAlignment (b1): NO
		enablesMarqueeWhenAncestorFocused (b1): NO
		marqueeEnabled (b1): NO
		marqueeRunable (b1): NO
		marqueeRequired (b1): NO
		usesExplicitPreferredMaxLayoutWidth (b1): NO
		drawsDebugBaselines (b1): NO
		explicitBaselineOffset (b1): NO
		usesSimpleTextEffects (b1): NO
		wantsUnderlineForAccessibilityButtonShapesEnabled (b1): NO
		disableUpdateTextColorOnTraitCollectionChange (b1): NO
		textAlignmentFollowsWritingDirection (b1): NO
		textAlignmentMirrored (b1): NO
		shortcutIntrinsicContentSize (b1): NO
		multilineLabelRequiresCarefulMeasurement (b1): NO
		noNeedsDisplayCheckForBaselineCalculationNeeded (b1): NO
		overallWritingDirectionFollowsLayoutDirection (b1): NO
		hyphenationFactorIgnoredIfURLsDetected (b1): NO
		extendedAccessibilityAdjustments (b1): YES
		canUseUILabelLayer (b1): YES
		implementsDefaultAttributes (b1): NO
		textColorFollowsTintColor (b1): NO
	}
	_adjustsFontForContentSizeCategory (BOOL): NO
	_preferredMaxLayoutWidth (double): 0
	_multilineContextWidth (double): 0
	_fontForShortcutBaselineCalculation (UIFont*): nil
	__visualStyle (_UILabelVisualStyle*): <_UILabelVisualStyle_iOS: 0x60000260c1f0>
in UIView:
	_constraintsExceptingSubviewAutoresizingConstraints (NSMutableArray*): nil
	_cachedTraitCollection (UITraitCollection*): <UITraitCollection: 0x600001f14b60>
	_animationInfo (UIViewAnimationInfo*): nil
	_layer (CALayer*): <_UILabelLayer: 0x600000701ef0>
	_layerRetained (CALayer*): <_UILabelLayer: 0x600000701ef0>
	_gestureRecognizers (NSMutableArray*): nil
	_window (UIWindow*): nil
	_subviewCache (NSArray*): nil
	_viewDelegate (UIViewController*): nil
	_cachedScreenScale (double): 3
	_layoutEngineWidth (double): 0
	_viewFlags (struct ?): {
		userInteractionDisabled (b1): YES
		implementsDrawRect (b1): YES
		implementsDidScroll (b1): NO
		implementsMouseTracking (b1): NO
		implementsIntrinsicContentSize (b1): YES
		hasBackgroundColor (b1): YES
		hasBackgroundColorSystemColorName (b1): YES
		hasInteractionTintColor (b1): NO
		isOpaque (b1): YES
		becomeFirstResponderWhenCapable (b1): NO
		interceptMouseEvent (b1): NO
		deallocating (b1): NO
		isInUIViewDealloc (b1): NO
		debugFlash (b1): NO
		isAncestorOfFirstResponder (b1): NO
		dontAutoresizeSubviews (b1): NO
		autoresizeMask (b6): 0
		patternBackground (b1): NO
		fixedBackgroundPattern (b1): NO
		dontAnimate (b1): NO
		superLayerIsView (b1): NO
		layerKitPatternDrawing (b1): NO
		multipleTouchEnabled (b1): NO
		exclusiveTouch (b1): NO
		hasViewController (b1): NO
		needsDidAppearOrDisappear (b1): NO
		deliversTouchesForGesturesToSuperview (b1): YES
		deliversButtonsForGesturesToSuperview (b1): YES
		chargeEnabled (b1): NO
		skipsSubviewEnumeration (b1): NO
		needsDisplayOnBoundsChange (b1): NO
		hasTiledLayer (b1): NO
		hasLargeContent (b1): NO
		traversalMark (b1): NO
		appearanceIsInvalid (b1): NO
		monitorsSubtree (b1): NO
		hostsAutolayoutEngine (b1): NO
		constraintsAreClean (b1): NO
		subviewLayoutConstraintsAreClean (b1): NO
		intrinsicContentSizeConstraintsAreClean (b1): NO
		strictDescendantNeedsDoubleUpdateConstraints (b1): NO
		strictDescendantNeedsDoubleUpdateConstraintsIsInvalid (b1): NO
		determiningWidthForDoubleUpdateConstraints (b1): NO
		inSecondConstraintsPass (b1): NO
		potentiallyHasDanglyConstraints (b1): NO
		doesNotTranslateAutoresizingMaskIntoConstraints (b1): NO
		autolayoutIsClean (b1): NO
		autolayoutBoundsAreClean (b1): NO
		layoutFlushingDisabled (b1): NO
		layingOutFromConstraints (b1): NO
		wantsAutolayout (b1): NO
		subviewWantsAutolayout (b1): NO
		isApplyingValuesFromEngine (b1): NO
		isResizingDueToParentResize (b1): NO
		isInLayoutSubviewsOrVCCallback (b1): NO
		isInAnimatedLayout (b1): NO
		isSubviewUpdatingAutoresizingConstraints (b1): NO
		isUpdatingConstraints (b1): NO
		isReapplyingStillActiveBrokenConstraints (b1): NO
		isHostingUpdateConstraintsPassDuringLayout (b1): NO
		isRunningEngineLevelConstraintsPass (b1): NO
		isUpdatingLayoutEngineHostConstraints (b1): NO
		isExpectingToFlushPendingLayoutChangeNotifications (b1): NO
		systemLayoutFittingSizeNeedsUpdate (b1): NO
		systemLayoutFittingSizeNeedsUpdateInWholeSubtree (b1): NO
		isCalculatingSystemLayoutFittingSize (b1): NO
		suppressEncapsulationConstraints (b1): NO
		isFetchingSizeForTAMIC_NOEngineHost (b1): NO
		stayHiddenAwaitingReuse (b1): NO
		stayHiddenAfterReuse (b1): NO
		skippedLayoutWhileHiddenForReuse (b1): NO
		isPendingHiddenForAnimation (b1): NO
		hasMaskView (b1): NO
		hasVisualAltitude (b1): NO
		hasBackdropMaskViews (b1): NO
		backdropMaskViewFlags (b5): 0
		delaysTouchesForSystemGestures (b1): NO
		subclassShouldDelayTouchForSystemGestures (b1): NO
		hasMotionEffects (b1): NO
		backdropOverlayMode (b2): 0
		tintAdjustmentMode (b2): 0
		isReferenceView (b1): NO
		focusState (b2): 0
		hasUserInterfaceIdiom (b1): NO
		userInterfaceIdiom (b3): 0
		ancestorDefinesTintColor (b1): NO
		ancestorDefinesTintAdjustmentMode (b1): NO
		ancestorIgnoresInvertColors (b1): NO
		needsTraitCollectionDidChangePropagation (b1): NO
		isRootOfTraitCollectionDidChangePropagation (b1): NO
		implementsTraitCollectionForChildEnvironment (b1): NO
		implementsBaselineOffsetsAtSize (b1): YES
		coloredViewBounds (b1): NO
		coloredAlignmentRects (b1): NO
		preservesSuperviewMargins (b4): 0
		insettingLayoutMarginsFromSafeArea (b4): 15
		safeAreaInsetsFrozen (b1): NO
		viewDelegateContentOverlayInsetsAreClean (b1): NO
		hasGeometryObservers (b1): NO
		observingGeometryChangesForSelfCount (b4): 0
		hasTraitStorageList (b1): NO
		cachedTraitCollectionIsValid (b1): YES
		dontUpdateInferredLayoutMargins (b1): NO
		areLayoutMarginsDirectional (b1): NO
		implementsViewForBaselineLayout (b1): NO
		tracksFocusedAncestors (b1): NO
		hasLayoutArrangements (b1): NO
		isHiddenManagedByLayoutArrangement (b1): NO
		hasAddedFocusGuides (b1): NO
		hasFocusSpeedBumpEdges (b1): NO
		hasFocusableContentMargins (b1): NO
		focusInteractionDisabled (b1): NO
		shouldReverseLayoutDirection (b1): NO
		cannotBeParentTraitEnvironment (b1): NO
		hasTemplateLayoutView (b2): 0
		ignoresTemplateLayoutView (b2): 0
		needsContentsFormatUpdate (b1): YES
		accessibilityIgnoresInvertColors (b1): NO
		ignoresLayerTransformForSafeAreaInsets (b1): NO
		accessibilityInterfaceStyleIntent (b2): 0
		accessibilityResolvedInterfaceStyle (b2): 0
		shouldArchiveUIAppearanceTags (b1): NO
		wantsDeepColorDrawing (b1): YES
		tagEnabled (b1): NO
		chargeSet (b1): NO
		ignoreBackdropViewsWhenHiding (b1): NO
		areChildrenFocused (b1): NO
		hasInteractionsArray (b1): NO
		hasHitTestDirectionalInsets (b1): NO
		hasLayoutDebuggingIdentifier (b1): NO
		hasContentSizeNotificationToken (b1): NO
		hasPresentationControllerToNotifyOnLayoutSubviews (b1): NO
		semanticContentAttribute (b3): 0
		hasDynamicBackgroundColor (b1): NO
		hasLocalOverrideTraitCollection (b1): NO
		forceEffectiveThemeDidChange (b1): NO
		allowsHighContrastForBackgroundColor (b1): NO
		hasPendingTraitStorageConstraints (b1): NO
		hasEverBeenInAWindow (b1): NO
		hasFocusGroupIdentifier (b2): 0
		allowsSkippingLayout (b1): YES
	}
	_unsatisfiableConstraintsLoggingSuspensionCount (unsigned short): Value not representable, S
	_pseudo_id (unsigned int): 35
	_retainCount (long): 0
	_draggingSourceDelegate (<_UIViewInternalDraggingSourceDelegate>*): nil
	_tintAdjustmentDimmingCount (unsigned short): Value not representable, S
	_layoutSubviewsCount (unsigned short): Value not representable, S
	_imminentLayoutSubviewsCount (unsigned short): Value not representable, S
	_countOfFocusedAncestorTrackingViewsInSubtree (unsigned short): Value not representable, S
	_layoutMarginsGuide (UILayoutGuide*): nil
	_minXVariable (NSISVariable*): nil
	_minYVariable (NSISVariable*): nil
	_boundsWidthVariable (NSISVariable*): nil
	_boundsHeightVariable (NSISVariable*): nil
	_layoutEngine (NSISEngine*): nil
	_stashedLayoutVariableObservations (NSMapTable*): nil
	_internalConstraints (NSMutableArray*): nil
	_safeAreaLayoutGuide (UILayoutGuide*): nil
	_readableContentGuide (UILayoutGuide*): nil
	__preferedContentsFormat (long): 0
	__lastNotifiedTraitCollection (UITraitCollection*): <UITraitCollection: 0x600001f14b60>
	__alignmentRectOriginCache (_UIViewLayoutEngineRelativeAlignmentRectOriginCache*): nil
	_rawLayoutMargins (struct UIEdgeInsets): {-1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308}
	_inferredLayoutMargins (struct UIEdgeInsets): {-1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308}
	_safeAreaInsets (struct UIEdgeInsets): {0, 0, 0, 0}
in UIResponder:
	_responderFlags (struct ?): {
		hasOverrideClient (b1): NO
		hasOverrideHost (b1): NO
		hasInputAssistantItem (b1): NO
		suppressSoftwareKeyboard (b1): NO
	}
in NSObject:
	isa (Class): UILabel (isa, 0x7fff86d7b1c0)

猜想 2

通过成员变量:_content 进行存储的。

(lldb) po [label valueForKey:@"content"]
<_UILabelContent:0x60000260cac0 attr={
    NSColor = "<UIDynamicSystemColor: 0x6000031597c0; name = labelColor>";
    NSFont = "<UICTFont: 0x7ffdb9e08fb0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>

我们通过 KVC 打印了 _content 的值,发现这些属性确实是存储在 _content 这个成员变量里面的。

NSLog(@"%@", label.textColor);

<UIDynamicSystemColor: 0x6000031597c0; name = labelColor>

_content 中 NSColor 和 label.textColor 指向同一个对象。

NSColor = "<UIDynamicSystemColor: 0x6000031597c0; name = labelColor>";

此外,例如 textAlignmentlineBreakMode 等一些 UILabel 的其他属性也是存储在 _contentNSParagraphStyle 中的。

验证

通过给这些属性赋值,并查看 _content 的值,也可以验证我们的猜想是正确的。

- (void)viewDidLoad {
    [super viewDidLoad];
    UILabel *label = [[UILabel alloc] init];
    label.textColor = [UIColor redColor];
    label.font = [UIFont systemFontOfSize:40.0];
    label.shadowColor = [UIColor redColor];
    label.shadowOffset = CGSizeZero;
    NSLog(@"%@", label.textColor);
    NSLog(@"%@", label.font);
    NSLog(@"%@", label.shadowColor);
    NSLog(@"%@", @(label.shadowOffset));
    NSLog(@"%@", [label valueForKey:@"content"]);
}

// 输出
UIExtendedSRGBColorSpace 1 0 0 1
<UICTFont: 0x7fea0e8096c0> font-family: ".SFUI-Regular"; font-weight: normal; font-style: normal; font-size: 40.00pt
UIExtendedSRGBColorSpace 1 0 0 1
NSSize: {0, 0}
<_UILabelContent:0x6000025fc6c0 attr={
    NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
    NSFont = "<UICTFont: 0x7fea0e8096c0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 40.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, 0} color = {UIExtendedSRGBColorSpace 1 0 0 1}";
}>

text 属性

我们看到初始化后 UILabel_content 中存储的是 _UILabelContent 对象。如果我们通过 text 进行了赋值,会产生什么变化呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    UILabel *label = [[UILabel alloc] init];
    label.text = @"ByteDance";
}

(lldb) po [label valueForKey:@"content"]
<_UILabelStringContent:0x600003573780 len=9 ByteDance attr={
    NSColor = "<UIDynamicSystemColor: 0x60000206a980; name = labelColor>";
    NSFont = "<UICTFont: 0x7fd56340bf30> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>

我们看到 _content 中存储的是 _UILabelStringContent 对象,里面记录了我们的字符串 @"ByteDance"、长度 9 以及属性信息。

也就是说,我们存入 text 的字符串最后被 UILabel 变成了富文本。

attributedText 属性

text 属性比较简单,基本没什么坑点。但 attributedText 就复杂很多了,如果和 text 再混用,比如有时设置 text ,有时设置 attributedText ,还会更复杂一些。

问题1

我们没有给 attributedText 添加任何属性时,UILabel 是如何展示的?

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
label.attributedText = [[NSAttributedString alloc] initWithString:text];

我们来看看 _content 属性。

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
label.attributedText = [[NSAttributedString alloc] initWithString:text];
NSLog(@"%@", [label valueForKey:@"content"]);

// 输出
<_UILabelAttributedStringContent:0x600003b88840 attributed len=9 ByteDance attr={
    NSColor = "<UIDynamicSystemColor: 0x600002ed7900; name = labelColor>";
    NSFont = "<UICTFont: 0x7fea39e090d0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>

可以看到这次 _content 属性,存储的是 _UILabelAttributedStringContent 对象,其中包括一个富文本、长度 9 以及属性信息,属性信息都是默认值。

那么富文本里面存储的是什么呢?

(lldb) po [0x600003b88840 valueForKey:@"attributedString"]
ByteDance{
}

通过打印我们发现,里面只有字符串,没有属性信息。

也就是说,UILabel 在进行展示的时候,为没有属性的字符串添加了默认的属性。

问题2

如果我们只给 attributedText 部分添加属性,会有什么影响呢?

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 1)];
label.attributedText = attStr;

id content = [label valueForKey:@"content"];
NSLog(@"%@", content);

id attributedString = [content valueForKey:@"attributedString"];
NSLog(@"%@", attributedString);

// 输出
<_UILabelAttributedStringContent:0x600000048200 attributed len=9 ByteDance attr={
    NSColor = "<UIDynamicSystemColor: 0x600001522500; name = labelColor>";
    NSFont = "<UICTFont: 0x7fa379f08ff0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>

B{
    NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
}yteDance{
}

我们可以看到添加了属性的部分使用的是添加的属性,而没有添加属性的部分还是使用的默认属性。

也就是说,决定最终显示的效果有两个因素:

  • attributedText 本身的属性(优先级最高)
  • 如果没有设置属性,则使用 UILabel 中的属性

坑点1

还是刚刚的代码,问:label.textColor 的颜色是什么?

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 1)];
label.attributedText = attStr;
NSLog(@"%@", label.textColor);

答案:redColor

UIExtendedSRGBColorSpace 1 0 0 1

那如果修改 range 为 NSMakeRange(1, 1) ,问:label.textColor 的颜色是什么?

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(1, 1)];
label.attributedText = attStr;
NSLog(@"%@", label.textColor);

答案:默认颜色

<UIDynamicSystemColor: 0x600002dc1ec0; name = labelColor>

探究

这里还是以 textColor 属性为例。我们查看源码发现系统调用 defaultValueForAttribute: 去获取值,如果有值则返回这个值;否则返回 _defaultColor

我们继续深入 defaultValueForAttribute: 。我们发现,如果文本信息长度大于等于1时,且 attributedString 的第0位如果有配置值的话,是取这个值进行返回的;否则,都从 attr 字典中取相关的配置值。

这也变相解释了渲染时,UILabel是如何取文本属性进行绘制的。

总结

我们在日常使用中,如果涉及使用 UILabel 的相关配置属性时需要注意使用场景,特别是在给 attributedText 属性赋值之后,属性的获取会不一样,需要特别注意。

问题3

问题2 只给 attributedText 部分添加属性,那如果我们为整体都添加了属性,会有什么影响呢?

看起来 _UILabelAttributedStringContent 中存储的内容和 问题2 中的差不多,只是富文本整体都加上了属性。

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, attStr.length)];
label.attributedText = attStr;

id content = [label valueForKey:@"content"];
NSLog(@"%@", content);

id attributedString = [content valueForKey:@"attributedString"];
NSLog(@"%@", attributedString);

// 输出
<_UILabelAttributedStringContent:0x600000da26e0 attributed len=9 ByteDance attr={
    NSColor = "<UIDynamicSystemColor: 0x6000018cc840; name = labelColor>";
    NSFont = "<UICTFont: 0x7fa727c065d0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>
ByteDance{
    NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
}

坑点2

如果在给 attributedText 属性赋值后,我们又给 text 赋值,会有什么影响呢?

我们首先实验只给 attributedText 部分添加属性。

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 1)];
label.attributedText = attStr;
label.text = text;
  • 选项1

  • 选项2

相信大家应该都没有什么疑问,答案是 选项1

实验2

如果给 attributedText 整体都添加属性,现在你的选择是?

[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, attStr.length)];

答案是 选项2

不仅如此,打印 _content 还发现其中默认的 attr 属性变为了 redColor

也就是说,与只给 attributedText 添加部分属性不同,此时 UILabel 对象默认的颜色值已被被替换了。

id content = [label valueForKey:@"content"];
NSLog(@"%@", content);

//输出
<_UILabelStringContent:0x60000378dfe0 len=9 ByteDance attr={
    NSColor = "UIExtendedSRGBColorSpace 1 0 0 1";
    NSFont = "<UICTFont: 0x7f8333c072d0> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>
实验3

在给 text 赋值之前,先清空 attributedText ,现在你的选择是?

UILabel *label = [[UILabel alloc] init];
NSString *text = @"ByteDance";
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, attStr.length)];
label.attributedText = attStr;
label.attributedText = nil;
label.text = text;

答案是 选项1 。打印 _content 还发现其中默认的 attr 属性没有发生改变 。

<_UILabelStringContent:0x600001c4fd20 len=9 ByteDance attr={
    NSColor = "<UIDynamicSystemColor: 0x6000009710c0; name = labelColor>";
    NSFont = "<UICTFont: 0x7f8154d06770> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 4, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 65535";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}>
探究

通过上面的实验,我们发现这个问题和设置 text 属性有关。

通过 NSAttributedStringattributesAtIndex:longestEffectiveRange:inRange: 方法,从 0 开始匹配了一个属性字典,如果 longestEffectiveRangelength 长度相等,且字典里面有值,则进行一系列操作,并比较了新的字典如果和 attr 字典不一样,就用新字典。最后初始化的时候就用这个字典去初始化整个 _content

所以,为整个富文本设置了一个属性时,会将 UILabel 对象默认的属性给替换掉。

总结

在同时使用 attributedTexttext 属性时,需要特别注意,在为 text 属性赋值之前,请先清空 attributedText 属性中的值,避免可能修改 UILabel 对象默认的颜色值的问题。


如果觉得本文对你有所帮助,给我点个赞吧~ 👍🏻