T1StatusLayout.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. //
  2. // T1StatusLayout.m
  3. // YYKitExample
  4. //
  5. // Created by guoyaoyuan on 15/10/10.
  6. // Copyright (C) 2015 ibireme. All rights reserved.
  7. //
  8. #import "T1StatusLayout.h"
  9. #import "T1Helper.h"
  10. @implementation T1StatusLayout
  11. - (void)setTweet:(T1Tweet *)tweet {
  12. if (_tweet != tweet) {
  13. _tweet = tweet;
  14. [self layout];
  15. }
  16. }
  17. - (void)layout {
  18. [self reset];
  19. if (!_tweet) {
  20. if (_conversation) {
  21. _isConversationSplit = YES;
  22. _height = kT1ConversationSplitHeight;
  23. return;
  24. } else {
  25. return;
  26. }
  27. }
  28. if (_conversation) {
  29. BOOL isTop = NO, isBottom = NO;
  30. if (_tweet.tidStr) {
  31. NSUInteger index = [_conversation.contextIDs indexOfObject:_tweet.tidStr];
  32. if (index == 0) {
  33. isTop = YES;
  34. } else if (index + 1 == _conversation.contextIDs.count) {
  35. isBottom = YES;
  36. }
  37. }
  38. if (isTop) {
  39. _showTopLine = YES;
  40. _showConversationBottomJoin = YES;
  41. } else if (isBottom) {
  42. _showConversationTopJoin = YES;
  43. } else {
  44. _showConversationTopJoin = YES;
  45. _showConversationBottomJoin = YES;
  46. }
  47. } else {
  48. _showTopLine = YES;
  49. }
  50. T1Tweet *tweet = self.displayedTweet;
  51. UIFont *nameSubFont = [UIFont systemFontOfSize:kT1UserNameSubFontSize];
  52. NSMutableAttributedString *dateText = [[NSMutableAttributedString alloc] initWithString:[T1Helper stringWithTimelineDate:tweet.createdAt]];
  53. if (tweet.card) {
  54. UIImage *iconImage = [T1Helper imageNamed:@"ic_tweet_attr_summary_default"];
  55. NSAttributedString *icon = [NSAttributedString attachmentStringWithContent:iconImage contentMode:UIViewContentModeCenter attachmentSize:iconImage.size alignToFont:nameSubFont alignment:YYTextVerticalAlignmentCenter];
  56. [dateText insertString:@" " atIndex:0];
  57. [dateText insertAttributedString:icon atIndex:0];
  58. }
  59. dateText.font = nameSubFont;
  60. dateText.color = kT1UserNameSubColor;
  61. dateText.alignment = NSTextAlignmentRight;
  62. _dateTextLayout = [YYTextLayout layoutWithContainerSize:CGSizeMake(kT1ContentWidth, kT1UserNameSubFontSize * 2) text:dateText];
  63. UIFont *nameFont = [UIFont systemFontOfSize:kT1UserNameFontSize];
  64. NSMutableAttributedString *nameText = [[NSMutableAttributedString alloc] initWithString:(tweet.user.name ? tweet.user.name : @"")];
  65. nameText.font = nameFont;
  66. nameText.color = kT1UserNameColor;
  67. if (tweet.user.screenName) {
  68. NSMutableAttributedString *screenNameText = [[NSMutableAttributedString alloc] initWithString:tweet.user.screenName];
  69. [screenNameText insertString:@" @" atIndex:0];
  70. screenNameText.font = nameSubFont;
  71. screenNameText.color = kT1UserNameSubColor;
  72. [nameText appendAttributedString:screenNameText];
  73. }
  74. nameText.lineBreakMode = NSLineBreakByCharWrapping;
  75. YYTextContainer *nameContainer = [YYTextContainer containerWithSize:CGSizeMake(kT1ContentWidth - _dateTextLayout.textBoundingRect.size.width - 5, kT1UserNameFontSize * 2)];
  76. nameContainer.maximumNumberOfRows = 1;
  77. _nameTextLayout = [YYTextLayout layoutWithContainer:nameContainer text:nameText];
  78. NSString *socialString = nil;
  79. if (_tweet.retweetedStatus) {
  80. if (_tweet.user.name) {
  81. socialString = [NSString stringWithFormat:@"%@ Retweeted",_tweet.user.name];
  82. }
  83. } else if (tweet.inReplyToScreenName) {
  84. socialString = [NSString stringWithFormat:@"in reply to @%@",tweet.inReplyToScreenName];
  85. }
  86. if (socialString) {
  87. NSMutableAttributedString *socialText = [[NSMutableAttributedString alloc] initWithString:socialString];
  88. socialText.font = nameSubFont;
  89. socialText.color = kT1UserNameSubColor;
  90. socialText.lineBreakMode = NSLineBreakByCharWrapping;
  91. YYTextContainer *socialContainer = [YYTextContainer containerWithSize:CGSizeMake(kT1ContentWidth, kT1UserNameFontSize * 2)];
  92. socialContainer.maximumNumberOfRows = 1;
  93. _socialTextLayout = [YYTextLayout layoutWithContainer:socialContainer text:socialText];
  94. }
  95. YYTextContainer *textContainer = [YYTextContainer containerWithSize:CGSizeMake(kT1ContentWidth + 2 * kT1TextContainerInset, CGFLOAT_MAX)];
  96. textContainer.insets = UIEdgeInsetsMake(0, kT1TextContainerInset, 0, kT1TextContainerInset);
  97. _textLayout = [YYTextLayout layoutWithContainer:textContainer text:[self textForTweet:tweet]];
  98. if (tweet.medias.count || tweet.extendedMedias.count) {
  99. NSMutableArray *images = [NSMutableArray new];
  100. NSMutableSet *imageIDs = [NSMutableSet new];
  101. for (T1Media *media in tweet.medias) {
  102. if ([media.type isEqualToString:@"photo"]) {
  103. if (media.mediaSmall && media.mediaLarge) {
  104. if (media.midStr && ![imageIDs containsObject:media.midStr]) {
  105. [images addObject:media];
  106. [imageIDs addObject:media.midStr];
  107. }
  108. }
  109. }
  110. }
  111. for (T1Media *media in tweet.extendedMedias) {
  112. if ([media.type isEqualToString:@"photo"]) {
  113. if (media.mediaSmall && media.mediaLarge) {
  114. if (media.midStr && ![imageIDs containsObject:media.midStr]) {
  115. [images addObject:media];
  116. [imageIDs addObject:media.midStr];
  117. }
  118. }
  119. }
  120. }
  121. while (images.count > 4) {
  122. [images removeLastObject];
  123. }
  124. if (images.count > 0) {
  125. _images = images;
  126. }
  127. }
  128. if (!_images && !_tweet.retweetedStatus && _tweet.quotedStatus) {
  129. T1Tweet *quote = _tweet.quotedStatus;
  130. NSMutableAttributedString *nameText = [[NSMutableAttributedString alloc] initWithString:(quote.user.name ? quote.user.name : @"")];
  131. nameText.font = nameFont;
  132. nameText.color = kT1UserNameColor;
  133. if (quote.user.screenName) {
  134. NSMutableAttributedString *screenNameText = [[NSMutableAttributedString alloc] initWithString:quote.user.screenName];
  135. [screenNameText insertString:@" @" atIndex:0];
  136. screenNameText.font = nameSubFont;
  137. screenNameText.color = kT1UserNameSubColor;
  138. [nameText appendAttributedString:screenNameText];
  139. }
  140. nameText.lineBreakMode = NSLineBreakByCharWrapping;
  141. YYTextContainer *nameContainer = [YYTextContainer containerWithSize:CGSizeMake(kT1QuoteContentWidth, kT1UserNameFontSize * 2)];
  142. nameContainer.maximumNumberOfRows = 1;
  143. _quotedNameTextLayout = [YYTextLayout layoutWithContainer:nameContainer text:nameText];
  144. NSAttributedString *quoteText = [self textForTweet:quote];
  145. _quotedTextLayout = [YYTextLayout layoutWithContainerSize:CGSizeMake(kT1QuoteContentWidth, CGFLOAT_MAX) text:quoteText];
  146. }
  147. _retweetCountTextLayout = [self retweetCountTextLayoutForTweet:tweet];
  148. _favoriteCountTextLayout = [self favoriteCountTextLayoutForTweet:tweet];
  149. if (_socialTextLayout) {
  150. _paddingTop = kT1CellExtendedPadding;
  151. } else {
  152. _paddingTop = kT1CellPadding;
  153. }
  154. _textTop = _paddingTop + kT1UserNameFontSize + kT1CellInnerPadding;
  155. _textHeight = _textLayout ? (CGRectGetMaxY(_textLayout.textBoundingRect)) : 0;
  156. _imagesTop = _quoteTop = _textTop + _textHeight + kT1CellInnerPadding;
  157. if (_images) {
  158. _imagesHeight = kT1ContentWidth * (9.0 / 16.0);
  159. } else if (_quotedTextLayout) {
  160. _quoteHeight = 2 * kT1CellPadding + kT1UserNameFontSize + CGRectGetMaxY(_quotedTextLayout.textBoundingRect);
  161. }
  162. CGFloat height = 0;
  163. if (_imagesHeight > 0) {
  164. height = _imagesTop + _imagesHeight;
  165. } else if (_quoteHeight > 0) {
  166. height = _quoteTop + _quoteHeight;
  167. } else {
  168. height = _textTop + _textHeight;
  169. }
  170. height += kT1ActionsHeight;
  171. if (height < _paddingTop + kT1AvatarSize) {
  172. height = _paddingTop + kT1AvatarSize;
  173. }
  174. height += kT1CellExtendedPadding;
  175. _height = height;
  176. }
  177. - (void)reset {
  178. _height = 0;
  179. _paddingTop = 0;
  180. _textTop = 0;
  181. _textHeight = 0;
  182. _imagesTop = 0;
  183. _imagesHeight = 0;
  184. _quoteTop = 0;
  185. _quoteHeight = 0;
  186. _showTopLine = NO;
  187. _isConversationSplit = NO;
  188. _showConversationTopJoin = NO;
  189. _showConversationBottomJoin = NO;
  190. _socialTextLayout = nil;
  191. _nameTextLayout = nil;
  192. _dateTextLayout = nil;
  193. _textLayout = nil;
  194. _quotedNameTextLayout = nil;
  195. _quotedTextLayout = nil;
  196. _retweetCountTextLayout = nil;
  197. _favoriteCountTextLayout = nil;
  198. _images = nil;
  199. }
  200. - (NSAttributedString *)textForTweet:(T1Tweet *)tweet{
  201. if (tweet.text.length == 0) return nil;
  202. NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:tweet.text];
  203. text.font = [UIFont systemFontOfSize:kT1TextFontSize];
  204. text.color = kT1TextColor;
  205. for (T1URL *url in tweet.urls) {
  206. if (url.ranges) {
  207. for (NSValue *value in url.ranges) {
  208. [self setHighlightInfo:@{@"T1URL" : url} withRange:value.rangeValue toText:text];
  209. }
  210. } else {
  211. [self setHighlightInfo:@{@"T1URL" : url} withRange:url.range toText:text];
  212. }
  213. }
  214. for (T1Media *media in tweet.medias) {
  215. if (media.ranges) {
  216. for (NSValue *value in media.ranges) {
  217. [self setHighlightInfo:@{@"T1Media" : media} withRange:value.rangeValue toText:text];
  218. }
  219. } else {
  220. [self setHighlightInfo:@{@"T1Media" : media} withRange:media.range toText:text];
  221. }
  222. }
  223. for (T1Media *media in tweet.extendedMedias) {
  224. if (media.ranges) {
  225. for (NSValue *value in media.ranges) {
  226. [self setHighlightInfo:@{@"T1Media" : media} withRange:value.rangeValue toText:text];
  227. }
  228. } else {
  229. [self setHighlightInfo:@{@"T1Media" : media} withRange:media.range toText:text];
  230. }
  231. }
  232. return text;
  233. }
  234. - (void)setHighlightInfo:(NSDictionary*)info withRange:(NSRange)range toText:(NSMutableAttributedString *)text {
  235. if (range.length == 0 || text.length == 0) return;
  236. {
  237. NSString *str = text.string;
  238. unichar *chars = malloc(str.length * sizeof(unichar));
  239. if (!chars) return;
  240. [str getCharacters:chars range:NSMakeRange(0, str.length)];
  241. NSUInteger start = range.location, end = range.location + range.length;
  242. for (int i = 0; i < str.length; i++) {
  243. unichar c = chars[i];
  244. if (0xD800 <= c && c <= 0xDBFF) { // UTF16 lead surrogates
  245. if (start > i) start++;
  246. if (end > i) end++;
  247. }
  248. }
  249. free(chars);
  250. if (end <= start) return;
  251. range = NSMakeRange(start, end - start);
  252. }
  253. if (range.location >= text.length) return;
  254. if (range.location + range.length > text.length) return;
  255. YYTextBorder *border = [YYTextBorder new];
  256. border.cornerRadius = 3;
  257. border.insets = UIEdgeInsetsMake(-2, -2, -2, -2);
  258. border.fillColor = kT1TextHighlightedBackgroundColor;
  259. YYTextHighlight *highlight = [YYTextHighlight new];
  260. [highlight setBackgroundBorder:border];
  261. highlight.userInfo = info;
  262. [text setTextHighlight:highlight range:range];
  263. [text setColor:kT1TextHighlightedColor range:range];
  264. }
  265. - (T1Tweet *)displayedTweet {
  266. return _tweet.retweetedStatus ? _tweet.retweetedStatus : _tweet;
  267. }
  268. - (YYTextLayout *)retweetCountTextLayoutForTweet:(T1Tweet *)tweet {
  269. if (tweet.retweetCount > 0) {
  270. NSMutableAttributedString *retweet = [[NSMutableAttributedString alloc] initWithString:[T1Helper shortedNumberDesc:tweet.retweetCount]];
  271. retweet.font = [UIFont systemFontOfSize:kT1ActionFontSize];
  272. retweet.color = tweet.retweeted ? kT1TextActionRetweetColor : kT1TextActionsColor;
  273. return [YYTextLayout layoutWithContainerSize:CGSizeMake(100, kT1ActionFontSize * 2) text:retweet];
  274. }
  275. return nil;
  276. }
  277. - (YYTextLayout *)favoriteCountTextLayoutForTweet:(T1Tweet *)tweet {
  278. if (tweet.favoriteCount > 0) {
  279. NSMutableAttributedString *favourite = [[NSMutableAttributedString alloc] initWithString:[T1Helper shortedNumberDesc:tweet.favoriteCount]];
  280. favourite.font = [UIFont systemFontOfSize:kT1ActionFontSize];
  281. favourite.color = tweet.favorited ? kT1TextActionFavoriteColor : kT1TextActionsColor;
  282. return [YYTextLayout layoutWithContainerSize:CGSizeMake(100, kT1ActionFontSize * 2) text:favourite];
  283. }
  284. return nil;
  285. }
  286. @end