引言
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>";
此外,例如 textAlignment 、 lineBreakMode 等一些 UILabel 的其他属性也是存储在 _content 的 NSParagraphStyle 中的。
验证
通过给这些属性赋值,并查看 _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 属性有关。
通过 NSAttributedString 的 attributesAtIndex:longestEffectiveRange:inRange: 方法,从 0 开始匹配了一个属性字典,如果 longestEffectiveRange 和 length 长度相等,且字典里面有值,则进行一系列操作,并比较了新的字典如果和 attr 字典不一样,就用新字典。最后初始化的时候就用这个字典去初始化整个 _content 。
所以,为整个富文本设置了一个属性时,会将 UILabel 对象默认的属性给替换掉。
总结
在同时使用 attributedText 和 text 属性时,需要特别注意,在为 text 属性赋值之前,请先清空 attributedText 属性中的值,避免可能修改 UILabel 对象默认的颜色值的问题。
如果觉得本文对你有所帮助,给我点个赞吧~ 👍🏻