| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905 |
- //
- // WBFeedLayout.m
- // YYKitExample
- //
- // Created by ibireme on 15/9/5.
- // Copyright (c) 2015 ibireme. All rights reserved.
- //
- #import "WBStatusLayout.h"
- /*
- 将每行的 baseline 位置固定下来,不受不同字体的 ascent/descent 影响。
-
- 注意,Heiti SC 中, ascent + descent = font size,
- 但是在 PingFang SC 中,ascent + descent > font size。
- 所以这里统一用 Heiti SC (0.86 ascent, 0.14 descent) 作为顶部和底部标准,保证不同系统下的显示一致性。
- 间距仍然用字体默认
- */
- @implementation WBTextLinePositionModifier
- - (instancetype)init {
- self = [super init];
-
- if (kiOS9Later) {
- _lineHeightMultiple = 1.34; // for PingFang SC
- } else {
- _lineHeightMultiple = 1.3125; // for Heiti SC
- }
-
- return self;
- }
- - (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(YYTextContainer *)container {
- //CGFloat ascent = _font.ascender;
- CGFloat ascent = _font.pointSize * 0.86;
-
- CGFloat lineHeight = _font.pointSize * _lineHeightMultiple;
- for (YYTextLine *line in lines) {
- CGPoint position = line.position;
- position.y = _paddingTop + ascent + line.row * lineHeight;
- line.position = position;
- }
- }
- - (id)copyWithZone:(NSZone *)zone {
- WBTextLinePositionModifier *one = [self.class new];
- one->_font = _font;
- one->_paddingTop = _paddingTop;
- one->_paddingBottom = _paddingBottom;
- one->_lineHeightMultiple = _lineHeightMultiple;
- return one;
- }
- - (CGFloat)heightForLineCount:(NSUInteger)lineCount {
- if (lineCount == 0) return 0;
- // CGFloat ascent = _font.ascender;
- // CGFloat descent = -_font.descender;
- CGFloat ascent = _font.pointSize * 0.86;
- CGFloat descent = _font.pointSize * 0.14;
- CGFloat lineHeight = _font.pointSize * _lineHeightMultiple;
- return _paddingTop + _paddingBottom + ascent + descent + (lineCount - 1) * lineHeight;
- }
- @end
- /**
- 微博的文本中,某些嵌入的图片需要从网上下载,这里简单做个封装
- */
- @interface WBTextImageViewAttachment : YYTextAttachment
- @property (nonatomic, strong) NSURL *imageURL;
- @property (nonatomic, assign) CGSize size;
- @end
- @implementation WBTextImageViewAttachment {
- UIImageView *_imageView;
- }
- - (void)setContent:(id)content {
- _imageView = content;
- }
- - (id)content {
- /// UIImageView 只能在主线程访问
- if (pthread_main_np() == 0) return nil;
- if (_imageView) return _imageView;
-
- /// 第一次获取时 (应该是在文本渲染完成,需要添加附件视图时),初始化图片视图,并下载图片
- /// 这里改成 YYAnimatedImageView 就能支持 GIF/APNG/WebP 动画了
- _imageView = [UIImageView new];
- _imageView.size = _size;
- [_imageView setImageWithURL:_imageURL placeholder:nil];
- return _imageView;
- }
- @end
- @implementation WBStatusLayout
- - (instancetype)initWithStatus:(WBStatus *)status style:(WBLayoutStyle)style {
- if (!status || !status.user) return nil;
- self = [super init];
- _status = status;
- _style = style;
- [self layout];
- return self;
- }
- - (void)layout {
- [self _layout];
- }
- - (void)updateDate {
- [self _layoutSource];
- }
- - (void)_layout {
-
- _marginTop = kWBCellTopMargin;
- _titleHeight = 0;
- _profileHeight = 0;
- _textHeight = 0;
- _retweetHeight = 0;
- _retweetTextHeight = 0;
- _retweetPicHeight = 0;
- _retweetCardHeight = 0;
- _picHeight = 0;
- _cardHeight = 0;
- _toolbarHeight = kWBCellToolbarHeight;
- _marginBottom = kWBCellToolbarBottomMargin;
-
-
- // 文本排版,计算布局
- [self _layoutTitle];
- [self _layoutProfile];
- [self _layoutRetweet];
- if (_retweetHeight == 0) {
- [self _layoutPics];
- if (_picHeight == 0) {
- [self _layoutCard];
- }
- }
- [self _layoutText];
- [self _layoutTag];
- [self _layoutToolbar];
-
- // 计算高度
- _height = 0;
- _height += _marginTop;
- _height += _titleHeight;
- _height += _profileHeight;
- _height += _textHeight;
- if (_retweetHeight > 0) {
- _height += _retweetHeight;
- } else if (_picHeight > 0) {
- _height += _picHeight;
- } else if (_cardHeight > 0) {
- _height += _cardHeight;
- }
- if (_tagHeight > 0) {
- _height += _tagHeight;
- } else {
- if (_picHeight > 0 || _cardHeight > 0) {
- _height += kWBCellPadding;
- }
- }
- _height += _toolbarHeight;
- _height += _marginBottom;
- }
- - (void)_layoutTitle {
- _titleHeight = 0;
- _titleTextLayout = nil;
-
- WBStatusTitle *title = _status.title;
- if (title.text.length == 0) return;
-
- NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:title.text];
- if (title.iconURL) {
- NSAttributedString *icon = [self _attachmentWithFontSize:kWBCellTitlebarFontSize imageURL:title.iconURL shrink:NO];
- if (icon) {
- [text insertAttributedString:icon atIndex:0];
- }
- }
- text.color = kWBCellToolbarTitleColor;
- text.font = [UIFont systemFontOfSize:kWBCellTitlebarFontSize];
-
- YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(kScreenWidth - 100, kWBCellTitleHeight)];
- _titleTextLayout = [YYTextLayout layoutWithContainer:container text:text];
- _titleHeight = kWBCellTitleHeight;
- }
- - (void)_layoutProfile {
- [self _layoutName];
- [self _layoutSource];
- _profileHeight = kWBCellProfileHeight;
- }
- /// 名字
- - (void)_layoutName {
- WBUser *user = _status.user;
- NSString *nameStr = nil;
- if (user.remark.length) {
- nameStr = user.remark;
- } else if (user.screenName.length) {
- nameStr = user.screenName;
- } else {
- nameStr = user.name;
- }
- if (nameStr.length == 0) {
- _nameTextLayout = nil;
- return;
- }
-
- NSMutableAttributedString *nameText = [[NSMutableAttributedString alloc] initWithString:nameStr];
-
- // 蓝V
- if (user.userVerifyType == WBUserVerifyTypeOrganization) {
- UIImage *blueVImage = [WBStatusHelper imageNamed:@"avatar_enterprise_vip"];
- NSAttributedString *blueVText = [self _attachmentWithFontSize:kWBCellNameFontSize image:blueVImage shrink:NO];
- [nameText appendString:@" "];
- [nameText appendAttributedString:blueVText];
- }
-
- // VIP
- if (user.mbrank > 0) {
- UIImage *yelllowVImage = [WBStatusHelper imageNamed:[NSString stringWithFormat:@"common_icon_membership_level%d",user.mbrank]];
- if (!yelllowVImage) {
- yelllowVImage = [WBStatusHelper imageNamed:@"common_icon_membership"];
- }
- NSAttributedString *vipText = [self _attachmentWithFontSize:kWBCellNameFontSize image:yelllowVImage shrink:NO];
- [nameText appendString:@" "];
- [nameText appendAttributedString:vipText];
- }
-
- nameText.font = [UIFont systemFontOfSize:kWBCellNameFontSize];
- nameText.color = user.mbrank > 0 ? kWBCellNameOrangeColor : kWBCellNameNormalColor;
- nameText.lineBreakMode = NSLineBreakByCharWrapping;
-
- YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(kWBCellNameWidth, 9999)];
- container.maximumNumberOfRows = 1;
- _nameTextLayout = [YYTextLayout layoutWithContainer:container text:nameText];
- }
- /// 时间和来源
- - (void)_layoutSource {
- NSMutableAttributedString *sourceText = [NSMutableAttributedString new];
- NSString *createTime = [WBStatusHelper stringWithTimelineDate:_status.createdAt];
-
- // 时间
- if (createTime.length) {
- NSMutableAttributedString *timeText = [[NSMutableAttributedString alloc] initWithString:createTime];
- [timeText appendString:@" "];
- timeText.font = [UIFont systemFontOfSize:kWBCellSourceFontSize];
- timeText.color = kWBCellTimeNormalColor;
- [sourceText appendAttributedString:timeText];
- }
-
- // 来自 XXX
- if (_status.source.length) {
- // <a href="sinaweibo://customweibosource" rel="nofollow">iPhone 5siPhone 5s</a>
- static NSRegularExpression *hrefRegex, *textRegex;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=href=\").+(?=\" )" options:kNilOptions error:NULL];
- textRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=>).+(?=<)" options:kNilOptions error:NULL];
- });
- NSTextCheckingResult *hrefResult, *textResult;
- NSString *href = nil, *text = nil;
- hrefResult = [hrefRegex firstMatchInString:_status.source options:kNilOptions range:NSMakeRange(0, _status.source.length)];
- textResult = [textRegex firstMatchInString:_status.source options:kNilOptions range:NSMakeRange(0, _status.source.length)];
- if (hrefResult && textResult && hrefResult.range.location != NSNotFound && textResult.range.location != NSNotFound) {
- href = [_status.source substringWithRange:hrefResult.range];
- text = [_status.source substringWithRange:textResult.range];
- }
- if (href.length && text.length) {
- NSMutableAttributedString *from = [NSMutableAttributedString new];
- [from appendString:[NSString stringWithFormat:@"来自 %@", text]];
- from.font = [UIFont systemFontOfSize:kWBCellSourceFontSize];
- from.color = kWBCellTimeNormalColor;
- if (_status.sourceAllowClick > 0) {
- NSRange range = NSMakeRange(3, text.length);
- [from setColor:kWBCellTextHighlightColor range:range];
- YYTextBackedString *backed = [YYTextBackedString stringWithString:href];
- [from setTextBackedString:backed range:range];
-
- YYTextBorder *border = [YYTextBorder new];
- border.insets = UIEdgeInsetsMake(-2, 0, -2, 0);
- border.fillColor = kWBCellTextHighlightBackgroundColor;
- border.cornerRadius = 3;
- YYTextHighlight *highlight = [YYTextHighlight new];
- if (href) highlight.userInfo = @{kWBLinkHrefName : href};
- [highlight setBackgroundBorder:border];
- [from setTextHighlight:highlight range:range];
- }
-
- [sourceText appendAttributedString:from];
- }
- }
-
- if (sourceText.length == 0) {
- _sourceTextLayout = nil;
- } else {
- YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(kWBCellNameWidth, 9999)];
- container.maximumNumberOfRows = 1;
- _sourceTextLayout = [YYTextLayout layoutWithContainer:container text:sourceText];
- }
- }
- - (void)_layoutRetweet {
- _retweetHeight = 0;
- [self _layoutRetweetedText];
- [self _layoutRetweetPics];
- if (_retweetPicHeight == 0) {
- [self _layoutRetweetCard];
- }
-
- _retweetHeight = _retweetTextHeight;
- if (_retweetPicHeight > 0) {
- _retweetHeight += _retweetPicHeight;
- _retweetHeight += kWBCellPadding;
- } else if (_retweetCardHeight > 0) {
- _retweetHeight += _retweetCardHeight;
- _retweetHeight += kWBCellPadding;
- }
- }
- /// 文本
- - (void)_layoutText {
- _textHeight = 0;
- _textLayout = nil;
-
- NSMutableAttributedString *text = [self _textWithStatus:_status
- isRetweet:NO
- fontSize:kWBCellTextFontSize
- textColor:kWBCellTextNormalColor];
- if (text.length == 0) return;
-
- WBTextLinePositionModifier *modifier = [WBTextLinePositionModifier new];
- modifier.font = [UIFont fontWithName:@"Heiti SC" size:kWBCellTextFontSize];
- modifier.paddingTop = kWBCellPaddingText;
- modifier.paddingBottom = kWBCellPaddingText;
-
- YYTextContainer *container = [YYTextContainer new];
- container.size = CGSizeMake(kWBCellContentWidth, HUGE);
- container.linePositionModifier = modifier;
-
- _textLayout = [YYTextLayout layoutWithContainer:container text:text];
- if (!_textLayout) return;
-
- _textHeight = [modifier heightForLineCount:_textLayout.rowCount];
- }
- - (void)_layoutRetweetedText {
- _retweetHeight = 0;
- _retweetTextLayout = nil;
- NSMutableAttributedString *text = [self _textWithStatus:_status.retweetedStatus
- isRetweet:YES
- fontSize:kWBCellTextFontRetweetSize
- textColor:kWBCellTextSubTitleColor];
- if (text.length == 0) return;
-
- WBTextLinePositionModifier *modifier = [WBTextLinePositionModifier new];
- modifier.font = [UIFont fontWithName:@"Heiti SC" size:kWBCellTextFontRetweetSize];
- modifier.paddingTop = kWBCellPaddingText;
- modifier.paddingBottom = kWBCellPaddingText;
-
- YYTextContainer *container = [YYTextContainer new];
- container.size = CGSizeMake(kWBCellContentWidth, HUGE);
- container.linePositionModifier = modifier;
-
- _retweetTextLayout = [YYTextLayout layoutWithContainer:container text:text];
- if (!_retweetTextLayout) return;
-
- _retweetTextHeight = [modifier heightForLineCount:_retweetTextLayout.lines.count];
- }
- - (void)_layoutPics {
- [self _layoutPicsWithStatus:_status isRetweet:NO];
- }
- - (void)_layoutRetweetPics {
- [self _layoutPicsWithStatus:_status.retweetedStatus isRetweet:YES];
- }
- - (void)_layoutPicsWithStatus:(WBStatus *)status isRetweet:(BOOL)isRetweet {
- if (isRetweet) {
- _retweetPicSize = CGSizeZero;
- _retweetPicHeight = 0;
- } else {
- _picSize = CGSizeZero;
- _picHeight = 0;
- }
- if (status.pics.count == 0) return;
-
- CGSize picSize = CGSizeZero;
- CGFloat picHeight = 0;
-
- CGFloat len1_3 = (kWBCellContentWidth + kWBCellPaddingPic) / 3 - kWBCellPaddingPic;
- len1_3 = CGFloatPixelRound(len1_3);
- switch (status.pics.count) {
- case 1: {
- WBPicture *pic = _status.pics.firstObject;
- WBPictureMetadata *bmiddle = pic.bmiddle;
- if (pic.keepSize || bmiddle.width < 1 || bmiddle.height < 1) {
- CGFloat maxLen = kWBCellContentWidth / 2.0;
- maxLen = CGFloatPixelRound(maxLen);
- picSize = CGSizeMake(maxLen, maxLen);
- picHeight = maxLen;
- } else {
- CGFloat maxLen = len1_3 * 2 + kWBCellPaddingPic;
- if (bmiddle.width < bmiddle.height) {
- picSize.width = (float)bmiddle.width / (float)bmiddle.height * maxLen;
- picSize.height = maxLen;
- } else {
- picSize.width = maxLen;
- picSize.height = (float)bmiddle.height / (float)bmiddle.width * maxLen;
- }
- picSize = CGSizePixelRound(picSize);
- picHeight = picSize.height;
- }
- } break;
- case 2: case 3: {
- picSize = CGSizeMake(len1_3, len1_3);
- picHeight = len1_3;
- } break;
- case 4: case 5: case 6: {
- picSize = CGSizeMake(len1_3, len1_3);
- picHeight = len1_3 * 2 + kWBCellPaddingPic;
- } break;
- default: { // 7, 8, 9
- picSize = CGSizeMake(len1_3, len1_3);
- picHeight = len1_3 * 3 + kWBCellPaddingPic * 2;
- } break;
- }
-
- if (isRetweet) {
- _retweetPicSize = picSize;
- _retweetPicHeight = picHeight;
- } else {
- _picSize = picSize;
- _picHeight = picHeight;
- }
- }
- - (void)_layoutCard {
- [self _layoutCardWithStatus:_status isRetweet:NO];
- }
- - (void)_layoutRetweetCard {
- [self _layoutCardWithStatus:_status.retweetedStatus isRetweet:YES];
- }
- - (void)_layoutCardWithStatus:(WBStatus *)status isRetweet:(BOOL)isRetweet {
- if (isRetweet) {
- _retweetCardType = WBStatusCardTypeNone;
- _retweetCardHeight = 0;
- _retweetCardTextLayout = nil;
- _retweetCardTextRect = CGRectZero;
- } else {
- _cardType = WBStatusCardTypeNone;
- _cardHeight = 0;
- _cardTextLayout = nil;
- _cardTextRect = CGRectZero;
- }
- WBPageInfo *pageInfo = status.pageInfo;
- if (!pageInfo) return;
-
- WBStatusCardType cardType = WBStatusCardTypeNone;
- CGFloat cardHeight = 0;
- YYTextLayout *cardTextLayout = nil;
- CGRect textRect = CGRectZero;
-
- if ((pageInfo.type == 11) && [pageInfo.objectType isEqualToString:@"video"]) {
- // 视频,一个大图片,上面播放按钮
- if (pageInfo.pagePic) {
- cardType = WBStatusCardTypeVideo;
- cardHeight = (2 * kWBCellContentWidth - kWBCellPaddingPic) / 3.0;
- }
- } else {
- BOOL hasImage = pageInfo.pagePic != nil;
- BOOL hasBadge = pageInfo.typeIcon != nil;
- WBButtonLink *button = pageInfo.buttons.firstObject;
- BOOL hasButtom = button.pic && button.name;
-
- /*
- badge: 25,25 左上角 (42)
- image: 70,70 方形
- 100, 70 矩形
- btn: 60,70
-
- lineheight 20
- padding 10
- */
- textRect.size.height = 70;
- if (hasImage) {
- if (hasBadge) {
- textRect.origin.x = 100;
- } else {
- textRect.origin.x = 70;
- }
- } else {
- if (hasBadge) {
- textRect.origin.x = 42;
- }
- }
- textRect.origin.x += 10; //padding
- textRect.size.width = kWBCellContentWidth - textRect.origin.x;
- if (hasButtom) textRect.size.width -= 60;
- textRect.size.width -= 10; //padding
-
- NSMutableAttributedString *text = [NSMutableAttributedString new];
- if (pageInfo.pageTitle.length) {
- NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:pageInfo.pageTitle];
-
- title.font = [UIFont systemFontOfSize:kWBCellCardTitleFontSize];
- title.color = kWBCellNameNormalColor;
- [text appendAttributedString:title];
- }
-
- if (pageInfo.pageDesc.length) {
- if (text.length) [text appendString:@"\n"];
- NSMutableAttributedString *desc = [[NSMutableAttributedString alloc] initWithString:pageInfo.pageDesc];
- desc.font = [UIFont systemFontOfSize:kWBCellCardDescFontSize];
- desc.color = kWBCellNameNormalColor;
- [text appendAttributedString:desc];
- } else if (pageInfo.content2.length) {
- if (text.length) [text appendString:@"\n"];
- NSMutableAttributedString *content3 = [[NSMutableAttributedString alloc] initWithString:pageInfo.content2];
- content3.font = [UIFont systemFontOfSize:kWBCellCardDescFontSize];
- content3.color = kWBCellTextSubTitleColor;
- [text appendAttributedString:content3];
- } else if (pageInfo.content3.length) {
- if (text.length) [text appendString:@"\n"];
- NSMutableAttributedString *content3 = [[NSMutableAttributedString alloc] initWithString:pageInfo.content3];
- content3.font = [UIFont systemFontOfSize:kWBCellCardDescFontSize];
- content3.color = kWBCellTextSubTitleColor;
- [text appendAttributedString:content3];
- }
-
- if (pageInfo.tips.length) {
- if (text.length) [text appendString:@"\n"];
- NSMutableAttributedString *tips = [[NSMutableAttributedString alloc] initWithString:pageInfo.tips];
- tips.font = [UIFont systemFontOfSize:kWBCellCardDescFontSize];
- tips.color = kWBCellTextSubTitleColor;
- [text appendAttributedString:tips];
- }
-
- if (text.length) {
- text.maximumLineHeight = 20;
- text.minimumLineHeight = 20;
- text.lineBreakMode = NSLineBreakByTruncatingTail;
-
- YYTextContainer *container = [YYTextContainer containerWithSize:textRect.size];
- container.maximumNumberOfRows = 3;
- cardTextLayout = [YYTextLayout layoutWithContainer:container text:text];
- }
-
- if (cardTextLayout) {
- cardType = WBStatusCardTypeNormal;
- cardHeight = 70;
- }
- }
-
- if (isRetweet) {
- _retweetCardType = cardType;
- _retweetCardHeight = cardHeight;
- _retweetCardTextLayout = cardTextLayout;
- _retweetCardTextRect = textRect;
- } else {
- _cardType = cardType;
- _cardHeight = cardHeight;
- _cardTextLayout = cardTextLayout;
- _cardTextRect = textRect;
- }
-
- }
- - (void)_layoutTag {
- _tagType = WBStatusTagTypeNone;
- _tagHeight = 0;
-
- WBTag *tag = _status.tagStruct.firstObject;
- if (tag.tagName.length == 0) return;
-
- NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:tag.tagName];
- if (tag.tagType == 1) {
- _tagType = WBStatusTagTypePlace;
- _tagHeight = 40;
- text.color = [UIColor colorWithWhite:0.217 alpha:1.000];
- } else {
- _tagType = WBStatusTagTypeNormal;
- _tagHeight = 32;
- if (tag.urlTypePic) {
- NSAttributedString *pic = [self _attachmentWithFontSize:kWBCellCardDescFontSize imageURL:tag.urlTypePic.absoluteString shrink:YES];
- [text insertAttributedString:pic atIndex:0];
- }
- // 高亮状态的背景
- YYTextBorder *highlightBorder = [YYTextBorder new];
- highlightBorder.insets = UIEdgeInsetsMake(-2, 0, -2, 0);
- highlightBorder.cornerRadius = 2;
- highlightBorder.fillColor = kWBCellTextHighlightBackgroundColor;
-
- [text setColor:kWBCellTextHighlightColor range:text.rangeOfAll];
-
- // 高亮状态
- YYTextHighlight *highlight = [YYTextHighlight new];
- [highlight setBackgroundBorder:highlightBorder];
- // 数据信息,用于稍后用户点击
- highlight.userInfo = @{kWBLinkTagName : tag};
- [text setTextHighlight:highlight range:text.rangeOfAll];
- }
- text.font = [UIFont systemFontOfSize:kWBCellCardDescFontSize];
-
- YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(9999, 9999)];
- _tagTextLayout = [YYTextLayout layoutWithContainer:container text:text];
- if (!_tagTextLayout) {
- _tagType = WBStatusTagTypeNone;
- _tagHeight = 0;
- }
- }
- - (void)_layoutToolbar {
- // should be localized
- UIFont *font = [UIFont systemFontOfSize:kWBCellToolbarFontSize];
- YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(kScreenWidth, kWBCellToolbarHeight)];
- container.maximumNumberOfRows = 1;
-
- NSMutableAttributedString *repostText = [[NSMutableAttributedString alloc] initWithString:_status.repostsCount <= 0 ? @"转发" : [WBStatusHelper shortedNumberDesc:_status.repostsCount]];
- repostText.font = font;
- repostText.color = kWBCellToolbarTitleColor;
- _toolbarRepostTextLayout = [YYTextLayout layoutWithContainer:container text:repostText];
- _toolbarRepostTextWidth = CGFloatPixelRound(_toolbarRepostTextLayout.textBoundingRect.size.width);
-
- NSMutableAttributedString *commentText = [[NSMutableAttributedString alloc] initWithString:_status.commentsCount <= 0 ? @"评论" : [WBStatusHelper shortedNumberDesc:_status.commentsCount]];
- commentText.font = font;
- commentText.color = kWBCellToolbarTitleColor;
- _toolbarCommentTextLayout = [YYTextLayout layoutWithContainer:container text:commentText];
- _toolbarCommentTextWidth = CGFloatPixelRound(_toolbarCommentTextLayout.textBoundingRect.size.width);
-
- NSMutableAttributedString *likeText = [[NSMutableAttributedString alloc] initWithString:_status.attitudesCount <= 0 ? @"赞" : [WBStatusHelper shortedNumberDesc:_status.attitudesCount]];
- likeText.font = font;
- likeText.color = _status.attitudesStatus ? kWBCellToolbarTitleHighlightColor : kWBCellToolbarTitleColor;
- _toolbarLikeTextLayout = [YYTextLayout layoutWithContainer:container text:likeText];
- _toolbarLikeTextWidth = CGFloatPixelRound(_toolbarLikeTextLayout.textBoundingRect.size.width);
- }
- - (NSMutableAttributedString *)_textWithStatus:(WBStatus *)status
- isRetweet:(BOOL)isRetweet
- fontSize:(CGFloat)fontSize
- textColor:(UIColor *)textColor {
- if (!status) return nil;
-
- NSMutableString *string = status.text.mutableCopy;
- if (string.length == 0) return nil;
- if (isRetweet) {
- NSString *name = status.user.name;
- if (name.length == 0) {
- name = status.user.screenName;
- }
- if (name) {
- NSString *insert = [NSString stringWithFormat:@"@%@:",name];
- [string insertString:insert atIndex:0];
- }
- }
- // 字体
- UIFont *font = [UIFont systemFontOfSize:fontSize];
- // 高亮状态的背景
- YYTextBorder *highlightBorder = [YYTextBorder new];
- highlightBorder.insets = UIEdgeInsetsMake(-2, 0, -2, 0);
- highlightBorder.cornerRadius = 3;
- highlightBorder.fillColor = kWBCellTextHighlightBackgroundColor;
-
- NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:string];
- text.font = font;
- text.color = textColor;
-
- // 根据 urlStruct 中每个 URL.shortURL 来匹配文本,将其替换为图标+友好描述
- for (WBURL *wburl in status.urlStruct) {
- if (wburl.shortURL.length == 0) continue;
- if (wburl.urlTitle.length == 0) continue;
- NSString *urlTitle = wburl.urlTitle;
- if (urlTitle.length > 27) {
- urlTitle = [[urlTitle substringToIndex:27] stringByAppendingString:YYTextTruncationToken];
- }
- NSRange searchRange = NSMakeRange(0, text.string.length);
- do {
- NSRange range = [text.string rangeOfString:wburl.shortURL options:kNilOptions range:searchRange];
- if (range.location == NSNotFound) break;
-
- if (range.location + range.length == text.length) {
- if (status.pageInfo.pageID && wburl.pageID &&
- [wburl.pageID isEqualToString:status.pageInfo.pageID]) {
- if ((!isRetweet && !status.retweetedStatus) || isRetweet) {
- if (status.pics.count == 0) {
- [text replaceCharactersInRange:range withString:@""];
- break; // cut the tail, show with card
- }
- }
- }
- }
-
- if ([text attribute:YYTextHighlightAttributeName atIndex:range.location] == nil) {
-
- // 替换的内容
- NSMutableAttributedString *replace = [[NSMutableAttributedString alloc] initWithString:urlTitle];
- if (wburl.urlTypePic.length) {
- // 链接头部有个图片附件 (要从网络获取)
- NSURL *picURL = [WBStatusHelper defaultURLForImageURL:wburl.urlTypePic];
- UIImage *image = [[YYImageCache sharedCache] getImageForKey:picURL.absoluteString];
- NSAttributedString *pic = (image && !wburl.pics.count) ? [self _attachmentWithFontSize:fontSize image:image shrink:YES] : [self _attachmentWithFontSize:fontSize imageURL:wburl.urlTypePic shrink:YES];
- [replace insertAttributedString:pic atIndex:0];
- }
- replace.font = font;
- replace.color = kWBCellTextHighlightColor;
-
- // 高亮状态
- YYTextHighlight *highlight = [YYTextHighlight new];
- [highlight setBackgroundBorder:highlightBorder];
- // 数据信息,用于稍后用户点击
- highlight.userInfo = @{kWBLinkURLName : wburl};
- [replace setTextHighlight:highlight range:NSMakeRange(0, replace.length)];
-
- // 添加被替换的原始字符串,用于复制
- YYTextBackedString *backed = [YYTextBackedString stringWithString:[text.string substringWithRange:range]];
- [replace setTextBackedString:backed range:NSMakeRange(0, replace.length)];
-
- // 替换
- [text replaceCharactersInRange:range withAttributedString:replace];
-
- searchRange.location = searchRange.location + (replace.length ? replace.length : 1);
- if (searchRange.location + 1 >= text.length) break;
- searchRange.length = text.length - searchRange.location;
- } else {
- searchRange.location = searchRange.location + (searchRange.length ? searchRange.length : 1);
- if (searchRange.location + 1>= text.length) break;
- searchRange.length = text.length - searchRange.location;
- }
- } while (1);
- }
-
- // 根据 topicStruct 中每个 Topic.topicTitle 来匹配文本,标记为话题
- for (WBTopic *topic in status.topicStruct) {
- if (topic.topicTitle.length == 0) continue;
- NSString *topicTitle = [NSString stringWithFormat:@"#%@#",topic.topicTitle];
- NSRange searchRange = NSMakeRange(0, text.string.length);
- do {
- NSRange range = [text.string rangeOfString:topicTitle options:kNilOptions range:searchRange];
- if (range.location == NSNotFound) break;
-
- if ([text attribute:YYTextHighlightAttributeName atIndex:range.location] == nil) {
- [text setColor:kWBCellTextHighlightColor range:range];
-
- // 高亮状态
- YYTextHighlight *highlight = [YYTextHighlight new];
- [highlight setBackgroundBorder:highlightBorder];
- // 数据信息,用于稍后用户点击
- highlight.userInfo = @{kWBLinkTopicName : topic};
- [text setTextHighlight:highlight range:range];
- }
- searchRange.location = searchRange.location + (searchRange.length ? searchRange.length : 1);
- if (searchRange.location + 1>= text.length) break;
- searchRange.length = text.length - searchRange.location;
- } while (1);
- }
-
- // 匹配 @用户名
- NSArray *atResults = [[WBStatusHelper regexAt] matchesInString:text.string options:kNilOptions range:text.rangeOfAll];
- for (NSTextCheckingResult *at in atResults) {
- if (at.range.location == NSNotFound && at.range.length <= 1) continue;
- if ([text attribute:YYTextHighlightAttributeName atIndex:at.range.location] == nil) {
- [text setColor:kWBCellTextHighlightColor range:at.range];
-
- // 高亮状态
- YYTextHighlight *highlight = [YYTextHighlight new];
- [highlight setBackgroundBorder:highlightBorder];
- // 数据信息,用于稍后用户点击
- highlight.userInfo = @{kWBLinkAtName : [text.string substringWithRange:NSMakeRange(at.range.location + 1, at.range.length - 1)]};
- [text setTextHighlight:highlight range:at.range];
- }
- }
-
- // 匹配 [表情]
- NSArray<NSTextCheckingResult *> *emoticonResults = [[WBStatusHelper regexEmoticon] matchesInString:text.string options:kNilOptions range:text.rangeOfAll];
- NSUInteger emoClipLength = 0;
- for (NSTextCheckingResult *emo in emoticonResults) {
- if (emo.range.location == NSNotFound && emo.range.length <= 1) continue;
- NSRange range = emo.range;
- range.location -= emoClipLength;
- if ([text attribute:YYTextHighlightAttributeName atIndex:range.location]) continue;
- if ([text attribute:YYTextAttachmentAttributeName atIndex:range.location]) continue;
- NSString *emoString = [text.string substringWithRange:range];
- NSString *imagePath = [WBStatusHelper emoticonDic][emoString];
- UIImage *image = [WBStatusHelper imageWithPath:imagePath];
- if (!image) continue;
-
- NSAttributedString *emoText = [NSAttributedString attachmentStringWithEmojiImage:image fontSize:fontSize];
- [text replaceCharactersInRange:range withAttributedString:emoText];
- emoClipLength += range.length - 1;
- }
-
- return text;
- }
- - (NSAttributedString *)_attachmentWithFontSize:(CGFloat)fontSize image:(UIImage *)image shrink:(BOOL)shrink {
-
- // CGFloat ascent = YYEmojiGetAscentWithFontSize(fontSize);
- // CGFloat descent = YYEmojiGetDescentWithFontSize(fontSize);
- // CGRect bounding = YYEmojiGetGlyphBoundingRectWithFontSize(fontSize);
-
- // Heiti SC 字体。。
- CGFloat ascent = fontSize * 0.86;
- CGFloat descent = fontSize * 0.14;
- CGRect bounding = CGRectMake(0, -0.14 * fontSize, fontSize, fontSize);
- UIEdgeInsets contentInsets = UIEdgeInsetsMake(ascent - (bounding.size.height + bounding.origin.y), 0, descent + bounding.origin.y, 0);
-
- YYTextRunDelegate *delegate = [YYTextRunDelegate new];
- delegate.ascent = ascent;
- delegate.descent = descent;
- delegate.width = bounding.size.width;
-
- YYTextAttachment *attachment = [YYTextAttachment new];
- attachment.contentMode = UIViewContentModeScaleAspectFit;
- attachment.contentInsets = contentInsets;
- attachment.content = image;
-
- if (shrink) {
- // 缩小~
- CGFloat scale = 1 / 10.0;
- contentInsets.top += fontSize * scale;
- contentInsets.bottom += fontSize * scale;
- contentInsets.left += fontSize * scale;
- contentInsets.right += fontSize * scale;
- contentInsets = UIEdgeInsetPixelFloor(contentInsets);
- attachment.contentInsets = contentInsets;
- }
-
- NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
- [atr setTextAttachment:attachment range:NSMakeRange(0, atr.length)];
- CTRunDelegateRef ctDelegate = delegate.CTRunDelegate;
- [atr setRunDelegate:ctDelegate range:NSMakeRange(0, atr.length)];
- if (ctDelegate) CFRelease(ctDelegate);
-
- return atr;
- }
- - (NSAttributedString *)_attachmentWithFontSize:(CGFloat)fontSize imageURL:(NSString *)imageURL shrink:(BOOL)shrink {
- /*
- 微博 URL 嵌入的图片,比临近的字体要小一圈。。
- 这里模拟一下 Heiti SC 字体,然后把图片缩小一下。
- */
- CGFloat ascent = fontSize * 0.86;
- CGFloat descent = fontSize * 0.14;
- CGRect bounding = CGRectMake(0, -0.14 * fontSize, fontSize, fontSize);
- UIEdgeInsets contentInsets = UIEdgeInsetsMake(ascent - (bounding.size.height + bounding.origin.y), 0, descent + bounding.origin.y, 0);
- CGSize size = CGSizeMake(fontSize, fontSize);
-
- if (shrink) {
- // 缩小~
- CGFloat scale = 1 / 10.0;
- contentInsets.top += fontSize * scale;
- contentInsets.bottom += fontSize * scale;
- contentInsets.left += fontSize * scale;
- contentInsets.right += fontSize * scale;
- contentInsets = UIEdgeInsetPixelFloor(contentInsets);
- size = CGSizeMake(fontSize - fontSize * scale * 2, fontSize - fontSize * scale * 2);
- size = CGSizePixelRound(size);
- }
-
- YYTextRunDelegate *delegate = [YYTextRunDelegate new];
- delegate.ascent = ascent;
- delegate.descent = descent;
- delegate.width = bounding.size.width;
-
- WBTextImageViewAttachment *attachment = [WBTextImageViewAttachment new];
- attachment.contentMode = UIViewContentModeScaleAspectFit;
- attachment.contentInsets = contentInsets;
- attachment.size = size;
- attachment.imageURL = [WBStatusHelper defaultURLForImageURL:imageURL];
-
- NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
- [atr setTextAttachment:attachment range:NSMakeRange(0, atr.length)];
- CTRunDelegateRef ctDelegate = delegate.CTRunDelegate;
- [atr setRunDelegate:ctDelegate range:NSMakeRange(0, atr.length)];
- if (ctDelegate) CFRelease(ctDelegate);
-
- return atr;
- }
- - (WBTextLinePositionModifier *)_textlineModifier {
- static WBTextLinePositionModifier *mod;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- mod = [WBTextLinePositionModifier new];
- mod.font = [UIFont fontWithName:@"Heiti SC" size:kWBCellTextFontSize];
- mod.paddingTop = 10;
- mod.paddingBottom = 10;
- });
- return mod;
- }
- @end
|