PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/MGBox2-master/MGBox/MGLine.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 867 lines | 659 code | 141 blank | 67 comment | 178 complexity | 894a0dd0cedc3179a38646f70ea715c7 MD5 | raw file
  1. //
  2. // Created by Matt Greenfield on 24/05/12
  3. // http://bigpaua.com/
  4. //
  5. #import "MGLine.h"
  6. #import "MGLayoutManager.h"
  7. #import "MGMushParser.h"
  8. #import "NSAttributedString+MGTrim.h"
  9. #define FALLBACK(potential, fallback) (potential ? potential : fallback)
  10. @interface MGLine ()
  11. @property (nonatomic, retain) NSMutableArray *dontFit;
  12. @end
  13. @implementation MGLine {
  14. CGFloat leftUsed, middleUsed, rightUsed;
  15. NSMutableArray *_leftItems, *_middleItems, *_rightItems;
  16. BOOL asyncDrawing, asyncDrawOnceing;
  17. }
  18. - (void)setup {
  19. [super setup];
  20. self.dontFit = @[].mutableCopy;
  21. // default font styles
  22. self.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:16];
  23. self.textColor = UIColor.blackColor;
  24. self.textShadowColor = UIColor.whiteColor;
  25. self.leftTextShadowOffset = (CGSize){0, 1};
  26. self.middleTextShadowOffset = (CGSize){0, 1};
  27. self.rightTextShadowOffset = (CGSize){0, 1};
  28. // default text alignments
  29. self.leftItemsTextAlignment = NSTextAlignmentLeft;
  30. self.middleItemsTextAlignment = NSTextAlignmentCenter;
  31. self.rightItemsTextAlignment = NSTextAlignmentRight;
  32. // may be deprecated in future. use MGBox borders instead
  33. self.underlineType = MGUnderlineNone;
  34. }
  35. #pragma mark - Factories
  36. + (id)line {
  37. return [self boxWithSize:CGSizeZero];
  38. }
  39. + (id)lineWithSize:(CGSize)size {
  40. return [self boxWithSize:size];
  41. }
  42. + (id)lineWithMultilineLeft:(NSString *)left right:(id)right width:(CGFloat)width
  43. minHeight:(CGFloat)height {
  44. MGLine *line = [self lineWithSize:(CGSize){width, height}];
  45. line.multilineLeft = left;
  46. line.rightItems = right;
  47. line.minHeight = height;
  48. line.maxHeight = 0;
  49. return line;
  50. }
  51. + (id)lineWithLeft:(id)left multilineRight:(NSString *)right width:(CGFloat)width
  52. minHeight:(CGFloat)height {
  53. MGLine *line = [self lineWithSize:(CGSize){width, height}];
  54. line.leftItems = left;
  55. line.multilineRight = right;
  56. line.minHeight = height;
  57. line.maxHeight = 0;
  58. return line;
  59. }
  60. + (id)multilineWithText:(NSString *)text font:(UIFont *)font width:(CGFloat)width
  61. padding:(UIEdgeInsets)padding {
  62. // compute min height
  63. CGSize minSize = [text sizeWithFont:font];
  64. CGFloat height = minSize.height + padding.top + padding.bottom;
  65. // make the line
  66. MGLine *line = [self lineWithSize:(CGSize){width, height}];
  67. line.minHeight = height;
  68. line.maxHeight = 0;
  69. line.font = font ? font : [line.font fontWithSize:14];
  70. line.padding = padding;
  71. line.multilineLeft = text;
  72. return line;
  73. }
  74. + (id)lineWithLeft:(NSObject *)left right:(NSObject *)right {
  75. return [self lineWithLeft:left right:right size:CGSizeZero];
  76. }
  77. + (id)lineWithLeft:(NSObject *)left right:(NSObject *)right size:(CGSize)size {
  78. MGLine *line = [self lineWithSize:size];
  79. if ([left isKindOfClass:NSArray.class]) {
  80. line.leftItems = left.mutableCopy;
  81. } else {
  82. line.leftItems = left ? @[left].mutableCopy : nil;
  83. }
  84. if ([right isKindOfClass:NSArray.class]) {
  85. line.rightItems = right.mutableCopy;
  86. } else {
  87. line.rightItems = right ? @[right].mutableCopy : nil;
  88. }
  89. return line;
  90. }
  91. #pragma mark - Layout
  92. - (void)layout {
  93. // wrap NSStrings, NSAttributedStrings, and UIImages
  94. [self wrapRawContents:self.leftItems placement:MGLeft];
  95. [self wrapRawContents:self.rightItems placement:MGRight];
  96. [self wrapRawContents:self.middleItems placement:MGMiddle];
  97. [self removeOldContents];
  98. // max usable space
  99. CGFloat maxWidth = self.width - self.leftPadding - self.rightPadding;
  100. // lay things out
  101. switch (self.sidePrecedence) {
  102. case MGSidePrecedenceLeft:
  103. [self layoutLeftWithin:maxWidth];
  104. [self layoutRightWithin:maxWidth - leftUsed];
  105. [self layoutMiddleWithin:maxWidth - leftUsed - rightUsed];
  106. break;
  107. case MGSidePrecedenceRight:
  108. [self layoutRightWithin:maxWidth];
  109. [self layoutLeftWithin:maxWidth - rightUsed];
  110. [self layoutMiddleWithin:maxWidth - leftUsed - rightUsed];
  111. break;
  112. case MGSidePrecedenceMiddle:
  113. [self layoutMiddleWithin:maxWidth];
  114. [self layoutLeftWithin:(maxWidth - middleUsed) / 2];
  115. [self layoutRightWithin:(maxWidth - middleUsed) / 2];
  116. break;
  117. }
  118. // adjust height to fit contents
  119. [self adjustHeight];
  120. // deal with attached boxes
  121. for (UIView <MGLayoutBox> *attachee in self.allItems) {
  122. if (![attachee conformsToProtocol:@protocol(MGLayoutBox)]
  123. || attachee.boxLayoutMode != MGBoxLayoutAttached) {
  124. continue;
  125. }
  126. CGRect frame = attachee.frame;
  127. frame.origin = attachee.attachedTo.frame.origin;
  128. frame.origin.x += attachee.leftMargin;
  129. frame.origin.y += attachee.topMargin;
  130. attachee.frame = frame;
  131. }
  132. // zIndex stack plz
  133. [MGLayoutManager stackByZIndexIn:self];
  134. // async draws
  135. if (self.asyncLayout || self.asyncLayoutOnce) {
  136. dispatch_async(self.asyncQueue, ^{
  137. if (self.asyncLayout && !asyncDrawing) {
  138. asyncDrawing = YES;
  139. self.asyncLayout();
  140. asyncDrawing = NO;
  141. }
  142. if (self.asyncLayoutOnce && !asyncDrawOnceing) {
  143. asyncDrawOnceing = YES;
  144. self.asyncLayoutOnce();
  145. self.asyncLayoutOnce = nil;
  146. asyncDrawOnceing = NO;
  147. }
  148. });
  149. }
  150. }
  151. - (void)wrapRawContents:(NSMutableArray *)items
  152. placement:(MGItemPlacement)placement {
  153. for (int i = 0; i < items.count; i++) {
  154. id item = items[i];
  155. if ([item isKindOfClass:NSString.class]
  156. || [item isKindOfClass:NSAttributedString.class]) {
  157. items[i] = [self makeLabel:item placement:placement];
  158. } else if ([item isKindOfClass:UIImage.class]) {
  159. items[i] = [[UIImageView alloc] initWithImage:item];
  160. }
  161. }
  162. }
  163. - (void)removeOldContents {
  164. // start with all views that aren't in boxes
  165. NSMutableSet *gone = [MGLayoutManager findViewsInView:self
  166. notInSet:self.boxes].mutableCopy;
  167. // intersect views not in items arrays
  168. [gone intersectSet:[MGLayoutManager findViewsInView:self
  169. notInSet:self.allItems]];
  170. // now kick 'em out
  171. [gone makeObjectsPerformSelector:@selector(removeFromSuperview)];
  172. }
  173. - (void)layoutLeftWithin:(CGFloat)limit {
  174. // size and discard
  175. leftUsed = [self size:self.leftItems within:limit];
  176. // widen as needed
  177. if (self.widenAsNeeded) {
  178. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  179. + self.rightPadding;
  180. self.width = needed > self.width ? needed : self.width;
  181. }
  182. // lay out
  183. CGFloat x = self.leftPadding;
  184. for (int i = 0; i < self.leftItems.count; i++) {
  185. UIView *view = self.leftItems[i];
  186. if ([self.dontFit containsObject:view]) {
  187. continue;
  188. }
  189. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  190. && [(id)view boxLayoutMode] == MGBoxLayoutAttached) {
  191. continue;
  192. }
  193. x += self.itemPadding;
  194. CGFloat y = self.paddedVerticalCenter - view.height / 2;
  195. // MGLayoutBoxes have margins to deal with
  196. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  197. UIView <MGLayoutBox> *box = (id)view;
  198. y += box.topMargin;
  199. x += box.leftMargin;
  200. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  201. x += box.rightMargin;
  202. // better be a UIView then
  203. } else {
  204. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  205. }
  206. x += view.width + self.itemPadding;
  207. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin
  208. | UIViewAutoresizingFlexibleBottomMargin
  209. | UIViewAutoresizingFlexibleRightMargin;
  210. }
  211. }
  212. - (void)layoutRightWithin:(CGFloat)limit {
  213. // size and discard
  214. rightUsed = [self size:self.rightItems within:limit];
  215. // widen as needed
  216. if (self.widenAsNeeded) {
  217. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  218. + self.rightPadding;
  219. self.width = needed > self.width ? needed : self.width;
  220. }
  221. // lay out
  222. CGFloat x = self.width - self.rightPadding;
  223. for (int i = 0; i < self.rightItems.count; i++) {
  224. UIView *view = self.rightItems[i];
  225. if ([self.dontFit containsObject:view]) {
  226. continue;
  227. }
  228. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  229. && [(id <MGLayoutBox>)view boxLayoutMode] == MGBoxLayoutAttached) {
  230. continue;
  231. }
  232. x -= self.itemPadding;
  233. CGFloat y = self.paddedVerticalCenter - view.height / 2;
  234. // MGLayoutBoxes have margins to deal with
  235. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  236. UIView <MGLayoutBox> *box = (id)view;
  237. y += box.topMargin;
  238. x -= box.width + box.rightMargin;
  239. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  240. x -= box.leftMargin;
  241. // hopefully is a UIView then
  242. } else {
  243. x -= view.width;
  244. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  245. }
  246. x -= self.itemPadding;
  247. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin
  248. | UIViewAutoresizingFlexibleBottomMargin
  249. | UIViewAutoresizingFlexibleLeftMargin;
  250. }
  251. }
  252. - (void)layoutMiddleWithin:(CGFloat)limit {
  253. // size and discard
  254. middleUsed = [self size:self.middleItems within:limit];
  255. // widen as needed
  256. if (self.widenAsNeeded) {
  257. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  258. + self.rightPadding;
  259. self.width = needed > self.width ? needed : self.width;
  260. }
  261. // lay out
  262. CGFloat x;
  263. if (self.sidePrecedence == MGSidePrecedenceMiddle) {
  264. x = roundf((self.width - middleUsed) / 2);
  265. } else {
  266. x = self.leftPadding + leftUsed + roundf((limit - middleUsed) / 2);
  267. }
  268. for (int i = 0; i < self.middleItems.count; i++) {
  269. UIView *view = self.middleItems[i];
  270. if ([self.dontFit containsObject:view]) {
  271. continue;
  272. }
  273. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  274. && [(id <MGLayoutBox>)view boxLayoutMode] == MGBoxLayoutAttached) {
  275. continue;
  276. }
  277. x += self.itemPadding;
  278. CGFloat y = self.paddedVerticalCenter - view.height / 2;
  279. // MGLayoutBoxes have margins to deal with
  280. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  281. UIView <MGLayoutBox> *box = (id)view;
  282. y += box.topMargin;
  283. x += box.leftMargin;
  284. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  285. x += box.rightMargin;
  286. // better be a UIView then
  287. } else {
  288. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  289. }
  290. x += view.width + self.itemPadding;
  291. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin
  292. | UIViewAutoresizingFlexibleBottomMargin
  293. | UIViewAutoresizingFlexibleLeftMargin
  294. | UIViewAutoresizingFlexibleRightMargin;
  295. }
  296. }
  297. - (void)adjustHeight {
  298. // no room for adjustment?
  299. if (self.minHeight == self.maxHeight) {
  300. return;
  301. }
  302. // find the highest item
  303. CGFloat maxItemHeight = 0;
  304. for (UIView <MGLayoutBox> *item in self.allItems) {
  305. if ([item conformsToProtocol:@protocol(MGLayoutBox)] && item.boxLayoutMode
  306. == MGBoxLayoutAttached) {
  307. continue;
  308. }
  309. maxItemHeight = MAX(maxItemHeight, item.height);
  310. }
  311. // adjust box height while respecting minHeight/maxHeight properties
  312. CGFloat newHeight = MAX(maxItemHeight + self.topPadding
  313. + self.bottomPadding, self.minHeight);
  314. if (self.maxHeight) {
  315. newHeight = MIN(newHeight, self.maxHeight);
  316. }
  317. if (newHeight != self.height) {
  318. self.height = ceilf(newHeight);
  319. }
  320. }
  321. - (CGFloat)size:(NSArray *)views within:(CGFloat)limit {
  322. NSMutableArray *expandables = @[].mutableCopy;
  323. CGFloat used = 0;
  324. unsigned int i;
  325. for (i = 0; i < views.count; i++) {
  326. // little bit of thread safety
  327. if (![views[i] isKindOfClass:UIView.class]) {
  328. continue;
  329. }
  330. UIView *item = views[i];
  331. CGSize itemSize = item.frame.size;
  332. [self.dontFit removeObject:item];
  333. // add to subviews
  334. if (item.superview != self) {
  335. [self addSubview:item];
  336. }
  337. // lay out child MGBoxes first
  338. if ([item conformsToProtocol:@protocol(MGLayoutBox)]) {
  339. UIView <MGLayoutBox> *box = (id)item;
  340. box.parentBox = self;
  341. [box layout];
  342. // collect expandables
  343. if (box.sizingMode == MGResizingExpandWidthToFill) {
  344. [expandables addObject:box];
  345. }
  346. // don't layout attached boxes yet
  347. if (box.boxLayoutMode == MGBoxLayoutAttached) {
  348. continue;
  349. }
  350. }
  351. // everything gets left and right padding
  352. used += self.itemPadding * 2;
  353. // not even enough space for the padding alone?
  354. if (!self.widenAsNeeded && used > limit) {
  355. break; // yep, out of space
  356. }
  357. // UILabels made by MGLine can be resized
  358. if ([item isKindOfClass:UILabel.class] && item.tag == -1) {
  359. UILabel *label = (id)item;
  360. // multiline
  361. if (!label.numberOfLines) {
  362. CGFloat maxHeight = self.maxHeight ? self.maxHeight - self.topPadding
  363. - self.bottomPadding : FLT_MAX;
  364. // attributed string?
  365. if ([label respondsToSelector:@selector(attributedText)]) {
  366. CGSize maxSize = (CGSize){limit - used, maxHeight};
  367. CGSize size = [label.attributedText boundingRectWithSize:maxSize
  368. options:NSStringDrawingUsesLineFragmentOrigin
  369. | NSStringDrawingUsesFontLeading context:nil].size;
  370. size.width = ceilf(size.width);
  371. size.height = ceilf(size.height);
  372. // for auto resizing margin sanity, make height odd/even match with self
  373. if ((int)size.height % 2 && !((int)self.height % 2)) {
  374. size.height += 1;
  375. }
  376. label.size = size;
  377. // plain old string
  378. } else {
  379. label.size = [label.text sizeWithFont:label.font
  380. constrainedToSize:(CGSize){limit - used, maxHeight}];
  381. }
  382. // single line
  383. } else {
  384. if (used + label.width > limit) { // needs slimming
  385. label.width = limit - used;
  386. }
  387. }
  388. used += label.width;
  389. // MGLayoutBoxes have margins to deal with
  390. } else if ([item conformsToProtocol:@protocol(MGLayoutBox)]) {
  391. UIView <MGLayoutBox> *box = (id)item;
  392. // undocumented optional 'maxWidth' property
  393. if ([box respondsToSelector:@selector(setMaxWidth:)]) {
  394. CGFloat totalWidth = box.leftMargin + box.width + box.rightMargin;
  395. if (used + totalWidth > limit) { // needs slimming
  396. box.maxWidth = limit - used - box.leftMargin - box.rightMargin;
  397. }
  398. }
  399. used += box.leftMargin + box.width + box.rightMargin;
  400. // hopefully is a UIView then
  401. } else {
  402. used += itemSize.width;
  403. }
  404. // ran out of space after counting the view size?
  405. if (!self.widenAsNeeded && used > limit) {
  406. break;
  407. }
  408. }
  409. // ditch leftovers if out of space
  410. if (i < views.count) {
  411. NSMutableArray *ditch = @[].mutableCopy;
  412. for (; i < views.count; i++) {
  413. [ditch addObject:views[i]];
  414. }
  415. [ditch makeObjectsPerformSelector:@selector(removeFromSuperview)];
  416. [self.dontFit addObjectsFromArray:ditch];
  417. [expandables removeObjectsInArray:ditch];
  418. }
  419. // distribute leftover space to expandables
  420. if (limit - used > 0) {
  421. CGFloat remaining = limit - used;
  422. CGFloat perBox = floorf(remaining / expandables.count);
  423. CGFloat leftover = remaining - perBox * expandables.count;
  424. for (MGBox *expandable in expandables) {
  425. expandable.width += perBox;
  426. used += perBox;
  427. if (expandable == expandables.lastObject) {
  428. expandable.width += floorf(leftover);
  429. used += leftover;
  430. }
  431. [expandable layout];
  432. }
  433. }
  434. return used;
  435. }
  436. #pragma mark - Label factory
  437. - (UILabel *)makeLabel:(id)text placement:(MGItemPlacement)placement {
  438. // base label
  439. UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
  440. label.backgroundColor = UIColor.clearColor;
  441. // styling
  442. switch (placement) {
  443. case MGLeft:
  444. label.font = self.font;
  445. label.textAlignment = self.leftItemsTextAlignment;
  446. label.shadowOffset = self.leftTextShadowOffset;
  447. label.shadowColor = self.textShadowColor;
  448. label.textColor = self.textColor;
  449. break;
  450. case MGMiddle:
  451. label.font = FALLBACK(self.middleFont, self.font);
  452. label.textAlignment = self.middleItemsTextAlignment;
  453. label.shadowOffset = self.middleTextShadowOffset;
  454. label.shadowColor
  455. = FALLBACK(self.middleTextShadowColor, self.textShadowColor);
  456. label.textColor = FALLBACK(self.middleTextColor, self.textColor);
  457. break;
  458. case MGRight:
  459. label.font = FALLBACK(self.rightFont, self.font);
  460. label.textAlignment = self.rightItemsTextAlignment;
  461. label.shadowOffset = self.rightTextShadowOffset;
  462. label.shadowColor
  463. = FALLBACK(self.rightTextShadowColor, self.textShadowColor);
  464. label.textColor = FALLBACK(self.rightTextColor, self.textColor);
  465. break;
  466. }
  467. // newline chars trigger a multiline label
  468. NSString *plain = [text isKindOfClass:NSAttributedString.class]
  469. ? [text string]
  470. : text;
  471. if ([plain rangeOfString:@"\n"].location != NSNotFound) {
  472. label.lineBreakMode = NSLineBreakByWordWrapping;
  473. label.numberOfLines = 0;
  474. // trim newlines off start and end
  475. id ws = NSCharacterSet.whitespaceAndNewlineCharacterSet;
  476. if ([text isKindOfClass:NSAttributedString.class]) {
  477. text = [text attributedStringByTrimming:ws];
  478. } else {
  479. text = [text stringByTrimmingCharactersInSet:ws];
  480. }
  481. // single line
  482. } else {
  483. label.lineBreakMode = NSLineBreakByTruncatingTail;
  484. }
  485. // turn mush strings into attributed strings
  486. if ([text isKindOfClass:NSString.class] && [text hasSuffix:@"|mush"]) {
  487. text = [text substringToIndex:[text length] - 5];
  488. text = [MGMushParser attributedStringFromMush:text font:label.font
  489. color:label.textColor];
  490. }
  491. // attributed string?
  492. if ([text isKindOfClass:NSAttributedString.class]) {
  493. if ([label respondsToSelector:@selector(attributedText)]) {
  494. label.attributedText = text;
  495. } else {
  496. label.text = [text string];
  497. }
  498. } else {
  499. label.text = text;
  500. }
  501. // final resizing will be done at layout time
  502. if ([label respondsToSelector:@selector(attributedText)]) {
  503. CGSize maxSize = (CGSize){self.width, 0};
  504. CGSize size = [label.attributedText boundingRectWithSize:maxSize
  505. options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
  506. size.width = ceilf(size.width);
  507. size.height = ceilf(size.height);
  508. // for auto resizing margin sanity, make height odd/even match with self
  509. if ((int)size.height % 2 && !((int)self.height % 2)) {
  510. size.height += 1;
  511. }
  512. label.size = size;
  513. } else {
  514. label.size = [label.text sizeWithFont:label.font];
  515. }
  516. // tag as modifiable
  517. label.tag = -1;
  518. return label;
  519. }
  520. #pragma mark - Style setters
  521. - (void)setFrame:(CGRect)frame {
  522. super.frame = frame;
  523. self.underlineType = self.underlineType;
  524. }
  525. // this may disappear in future. use MGBox borders instead
  526. - (void)setUnderlineType:(MGUnderlineType)type {
  527. _underlineType = type;
  528. switch (_underlineType) {
  529. case MGUnderlineTop:
  530. self.solidUnderline.frame = CGRectMake(0, 0, self.width, 2);
  531. [self.layer addSublayer:self.solidUnderline];
  532. break;
  533. case MGUnderlineBottom:
  534. self.solidUnderline.frame = CGRectMake(0, self.height - 2, self.width, 2);
  535. [self.layer addSublayer:self.solidUnderline];
  536. break;
  537. case MGUnderlineNone:
  538. default:
  539. [self.solidUnderline removeFromSuperlayer];
  540. break;
  541. }
  542. }
  543. - (void)setTextColor:(UIColor *)color {
  544. _textColor = color;
  545. NSMutableArray *items = self.leftItems.mutableCopy;
  546. if (!self.middleTextColor) {
  547. [items addObjectsFromArray:self.middleItems];
  548. }
  549. if (!self.rightTextColor) {
  550. [items addObjectsFromArray:self.rightItems];
  551. }
  552. for (UILabel *label in items) {
  553. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  554. label.textColor = color;
  555. }
  556. }
  557. }
  558. - (void)setMiddleTextColor:(UIColor *)color {
  559. _middleTextColor = color;
  560. for (UILabel *label in self.middleItems) {
  561. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  562. label.textColor = color;
  563. }
  564. }
  565. }
  566. - (void)setRightTextColor:(UIColor *)color {
  567. _rightTextColor = color;
  568. for (UILabel *label in self.rightItems) {
  569. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  570. label.textColor = color;
  571. }
  572. }
  573. }
  574. - (void)setTextShadowColor:(UIColor *)color {
  575. _textShadowColor = color;
  576. NSMutableArray *items = self.leftItems.mutableCopy;
  577. if (!self.middleTextShadowColor) {
  578. [items addObjectsFromArray:self.middleItems];
  579. }
  580. if (!self.rightTextShadowColor) {
  581. [items addObjectsFromArray:self.rightItems];
  582. }
  583. for (UILabel *label in items) {
  584. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  585. label.shadowColor = color;
  586. }
  587. }
  588. }
  589. - (void)setMiddleTextShadowColor:(UIColor *)color {
  590. _middleTextShadowColor = color;
  591. for (UILabel *label in self.middleItems) {
  592. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  593. label.shadowColor = color;
  594. }
  595. }
  596. }
  597. - (void)setRightTextShadowColor:(UIColor *)color {
  598. _rightTextShadowColor = color;
  599. for (UILabel *label in self.rightItems) {
  600. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  601. label.shadowColor = color;
  602. }
  603. }
  604. }
  605. - (void)setLeftTextShadowOffset:(CGSize)offset {
  606. _leftTextShadowOffset = offset;
  607. for (UILabel *label in self.leftItems) {
  608. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  609. label.shadowOffset = offset;
  610. }
  611. }
  612. }
  613. - (void)setMiddleTextShadowOffset:(CGSize)offset {
  614. _middleTextShadowOffset = offset;
  615. for (UILabel *label in self.middleItems) {
  616. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  617. label.shadowOffset = offset;
  618. }
  619. }
  620. }
  621. - (void)setRightTextShadowOffset:(CGSize)offset {
  622. _rightTextShadowOffset = offset;
  623. for (UILabel *label in self.rightItems) {
  624. if ([label isKindOfClass:UILabel.class] && label.tag == -1) {
  625. label.shadowOffset = offset;
  626. }
  627. }
  628. }
  629. #pragma mark - Content setters
  630. - (void)setLeftItems:(id)items {
  631. if (!items) {
  632. _leftItems = NSMutableArray.array;
  633. } else if ([items isKindOfClass:NSMutableArray.class]) {
  634. _leftItems = items;
  635. } else if ([items isKindOfClass:NSArray.class]) {
  636. _leftItems = [items mutableCopy];
  637. } else {
  638. _leftItems = @[items].mutableCopy;
  639. }
  640. }
  641. - (void)setMiddleItems:(id)items {
  642. if (!items) {
  643. _middleItems = NSMutableArray.array;
  644. } else if ([items isKindOfClass:NSMutableArray.class]) {
  645. _middleItems = items;
  646. } else if ([items isKindOfClass:NSArray.class]) {
  647. _middleItems = [items mutableCopy];
  648. } else {
  649. _middleItems = @[items].mutableCopy;
  650. }
  651. }
  652. - (void)setRightItems:(id)items {
  653. if (!items) {
  654. _rightItems = NSMutableArray.array;
  655. } else if ([items isKindOfClass:NSMutableArray.class]) {
  656. _rightItems = items;
  657. } else if ([items isKindOfClass:NSArray.class]) {
  658. _rightItems = [items mutableCopy];
  659. } else {
  660. _rightItems = @[items].mutableCopy;
  661. }
  662. }
  663. - (void)setMultilineLeft:(NSString *)text {
  664. if ([text rangeOfString:@"\n"].location == NSNotFound) {
  665. self.leftItems = (id)[@"\n" stringByAppendingString:text];
  666. } else {
  667. self.leftItems = (id)text;
  668. }
  669. }
  670. - (void)setMultilineMiddle:(NSString *)text {
  671. if ([text rangeOfString:@"\n"].location == NSNotFound) {
  672. self.middleItems = (id)[@"\n" stringByAppendingString:text];
  673. } else {
  674. self.middleItems = (id)text;
  675. }
  676. }
  677. - (void)setMultilineRight:(NSString *)text {
  678. if ([text rangeOfString:@"\n"].location == NSNotFound) {
  679. self.rightItems = (id)[@"\n" stringByAppendingString:text];
  680. } else {
  681. self.rightItems = (id)text;
  682. }
  683. }
  684. #pragma mark - Getters
  685. - (NSSet *)allItems {
  686. NSMutableSet *items = [NSMutableSet setWithArray:self.leftItems];
  687. [items addObjectsFromArray:self.middleItems];
  688. [items addObjectsFromArray:self.rightItems];
  689. return items;
  690. }
  691. - (CALayer *)solidUnderline {
  692. if (_solidUnderline) {
  693. return _solidUnderline;
  694. }
  695. _solidUnderline = CALayer.layer;
  696. _solidUnderline.frame = CGRectMake(0, 0, self.width, 2);
  697. _solidUnderline.backgroundColor = [UIColor colorWithWhite:0.87 alpha:1].CGColor;
  698. CALayer *bot = CALayer.layer;
  699. bot.frame = CGRectMake(0, 1, self.frame.size.width, 1);
  700. bot.backgroundColor = UIColor.whiteColor.CGColor;
  701. [_solidUnderline addSublayer:bot];
  702. return _solidUnderline;
  703. }
  704. - (NSMutableArray *)leftItems {
  705. if (!_leftItems) {
  706. _leftItems = @[].mutableCopy;
  707. }
  708. return _leftItems;
  709. }
  710. - (NSMutableArray *)middleItems {
  711. if (!_middleItems) {
  712. _middleItems = @[].mutableCopy;
  713. }
  714. return _middleItems;
  715. }
  716. - (NSMutableArray *)rightItems {
  717. if (!_rightItems) {
  718. _rightItems = @[].mutableCopy;
  719. }
  720. return _rightItems;
  721. }
  722. - (CGFloat)paddedVerticalCenter {
  723. CGFloat innerHeight = self.height - self.topPadding - self.bottomPadding;
  724. return innerHeight / 2 + self.topPadding;
  725. }
  726. @end