PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/MYMGBoxPractise/MGBox/MGLine.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 552 lines | 414 code | 92 blank | 46 comment | 86 complexity | 9502466d7ecdbfd33f7e7461e49a5d56 MD5 | raw file
  1. //
  2. // Created by Matt Greenfield on 24/05/12
  3. // Copyright (c) 2012 Big Paua. All rights reserved
  4. // http://bigpaua.com/
  5. //
  6. #import "MGLine.h"
  7. #import "MGLayoutManager.h"
  8. @interface MGLine ()
  9. @property (nonatomic, retain) NSMutableArray *dontFit;
  10. - (void)wrapRawContents:(NSMutableArray *)contents
  11. align:(UITextAlignment)align;
  12. - (void)removeOldContents;
  13. - (void)layoutLeftWithin:(CGFloat)limit;
  14. - (void)layoutRightWithin:(CGFloat)limit;
  15. - (void)layoutMiddleWithin:(CGFloat)limit;
  16. - (CGFloat)size:(NSArray *)views within:(CGFloat)limit font:(UIFont *)font;
  17. @end
  18. @implementation MGLine {
  19. CGFloat leftUsed, middleUsed, rightUsed;
  20. }
  21. - (void)setup {
  22. [super setup];
  23. self.dontFit = @[].mutableCopy;
  24. // fonts
  25. self.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:16];
  26. self.textColor = UIColor.blackColor;
  27. self.textShadowColor = UIColor.whiteColor;
  28. self.rightFont = self.font;
  29. }
  30. + (id)line {
  31. return [self boxWithSize:CGSizeZero];
  32. }
  33. + (id)lineWithSize:(CGSize)size {
  34. return [self boxWithSize:size];
  35. }
  36. + (id)multilineWithText:(NSString *)text font:(UIFont *)font width:(CGFloat)width
  37. padding:(UIEdgeInsets)padding {
  38. // default font?
  39. font = font ? font : [UIFont fontWithName:@"HelveticaNeue-Light" size:14];
  40. // make the label
  41. UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
  42. label.backgroundColor = UIColor.clearColor;
  43. label.shadowColor = UIColor.whiteColor;
  44. label.shadowOffset = CGSizeMake(0, 1);
  45. label.numberOfLines = 0;
  46. label.font = font;
  47. label.text = text;
  48. // usable width
  49. CGFloat innerWidth = width - padding.left - padding.right;
  50. // size the label
  51. CGSize textSize = [label.text sizeWithFont:label.font
  52. constrainedToSize:CGSizeMake(innerWidth, FLT_MAX)];
  53. label.size = (CGSize){innerWidth, textSize.height};
  54. // make the line
  55. MGLine *line = [MGLine lineWithSize:(CGSize){width,
  56. label.height + padding.top + padding.bottom
  57. }];
  58. line.leftItems = @[label].mutableCopy;
  59. line.padding = padding;
  60. line.font = font;
  61. return line;
  62. }
  63. + (id)lineWithLeft:(NSObject *)left right:(NSObject *)right {
  64. return [self lineWithLeft:left right:right size:CGSizeZero];
  65. }
  66. + (id)lineWithLeft:(NSObject *)left right:(NSObject *)right
  67. size:(CGSize)size {
  68. MGLine *line = [self lineWithSize:size];
  69. if ([left isKindOfClass:NSArray.class]) {
  70. line.leftItems = left.mutableCopy;
  71. } else {
  72. line.leftItems = left ? @[left].mutableCopy : nil;
  73. }
  74. if ([right isKindOfClass:NSArray.class]) {
  75. line.rightItems = right.mutableCopy;
  76. } else {
  77. line.rightItems = right ? @[right].mutableCopy : nil;
  78. }
  79. return line;
  80. }
  81. #pragma mark - Layout
  82. - (void)layout {
  83. // wrap NSStrings and UIImages
  84. [self wrapRawContents:self.leftItems align:NSTextAlignmentLeft];
  85. [self wrapRawContents:self.rightItems align:NSTextAlignmentRight];
  86. [self wrapRawContents:self.middleItems align:NSTextAlignmentCenter];
  87. [self removeOldContents];
  88. // max usable space
  89. CGFloat maxWidth = self.width - self.leftPadding - self.rightPadding;
  90. // lay things out
  91. switch (self.sidePrecedence) {
  92. case MGSidePrecedenceLeft:
  93. [self layoutLeftWithin:maxWidth];
  94. [self layoutRightWithin:maxWidth - leftUsed];
  95. [self layoutMiddleWithin:maxWidth - leftUsed - rightUsed];
  96. break;
  97. case MGSidePrecedenceRight:
  98. [self layoutRightWithin:maxWidth];
  99. [self layoutLeftWithin:maxWidth - rightUsed];
  100. [self layoutMiddleWithin:maxWidth - leftUsed - rightUsed];
  101. break;
  102. case MGSidePrecedenceMiddle:
  103. [self layoutMiddleWithin:maxWidth];
  104. [self layoutLeftWithin:maxWidth - middleUsed];
  105. [self layoutRightWithin:maxWidth - leftUsed - middleUsed];
  106. break;
  107. }
  108. // deal with attached boxes
  109. for (UIView <MGLayoutBox> *attachee in self.allItems) {
  110. if (![attachee conformsToProtocol:@protocol(MGLayoutBox)]
  111. || attachee.boxLayoutMode != MGBoxLayoutAttached) {
  112. continue;
  113. }
  114. CGRect frame = attachee.frame;
  115. frame.origin = attachee.attachedTo.frame.origin;
  116. frame.origin.x += attachee.leftMargin;
  117. frame.origin.y += attachee.topMargin;
  118. attachee.frame = frame;
  119. }
  120. // zIndex stack plz
  121. [MGLayoutManager stackByZIndexIn:self];
  122. }
  123. - (void)wrapRawContents:(NSMutableArray *)contents
  124. align:(UITextAlignment)align {
  125. for (int i = 0; i < contents.count; i++) {
  126. id item = contents[i];
  127. if ([item isKindOfClass:NSString.class]) {
  128. UILabel *label = [self makeLabel:item align:align];
  129. contents[i] = label;
  130. } else if ([item isKindOfClass:UIImage.class]) {
  131. UIImageView *view = [[UIImageView alloc] initWithImage:item];
  132. contents[i] = view;
  133. }
  134. }
  135. }
  136. - (void)removeOldContents {
  137. // start with all views that aren't in boxes
  138. NSMutableSet *gone = [MGLayoutManager findViewsInView:self
  139. notInSet:self.boxes].mutableCopy;
  140. // intersect views not items arrays
  141. [gone intersectSet:[MGLayoutManager findViewsInView:self
  142. notInSet:self.allItems]];
  143. // now kick 'em out
  144. [gone makeObjectsPerformSelector:@selector(removeFromSuperview)];
  145. }
  146. - (void)layoutLeftWithin:(CGFloat)limit {
  147. // size and discard
  148. leftUsed = [self size:self.leftItems within:limit font:self.font];
  149. // widen as needed
  150. if (self.widenAsNeeded) {
  151. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  152. + self.rightPadding;
  153. self.width = needed > self.width ? needed : self.width;
  154. }
  155. // lay out
  156. CGFloat x = self.leftPadding;
  157. int i;
  158. for (i = 0; i < self.leftItems.count; i++) {
  159. UIView *view = self.leftItems[i];
  160. if ([self.dontFit indexOfObject:view] != NSNotFound) {
  161. continue;
  162. }
  163. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  164. && [(id <MGLayoutBox>)view boxLayoutMode] == MGBoxLayoutAttached) {
  165. continue;
  166. }
  167. x += self.itemPadding;
  168. CGFloat y = (self.height - view.height) / 2;
  169. // MGLayoutBoxes have margins to deal with
  170. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  171. UIView <MGLayoutBox> *box = (id)view;
  172. y += box.topMargin;
  173. x += box.leftMargin;
  174. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  175. x += box.rightMargin;
  176. // better be a UIView then
  177. } else {
  178. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  179. }
  180. x += view.width + self.itemPadding;
  181. view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
  182. }
  183. }
  184. - (void)layoutRightWithin:(CGFloat)limit {
  185. // size and discard
  186. rightUsed = [self size:self.rightItems within:limit font:self.rightFont];
  187. // widen as needed
  188. if (self.widenAsNeeded) {
  189. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  190. + self.rightPadding;
  191. self.width = needed > self.width ? needed : self.width;
  192. }
  193. // lay out
  194. CGFloat x = self.width - self.rightPadding;
  195. int i;
  196. for (i = 0; i < self.rightItems.count; i++) {
  197. UIView *view = self.rightItems[i];
  198. if ([self.dontFit indexOfObject:view] != NSNotFound) {
  199. continue;
  200. }
  201. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  202. && [(id <MGLayoutBox>)view boxLayoutMode] == MGBoxLayoutAttached) {
  203. continue;
  204. }
  205. x -= self.itemPadding;
  206. CGFloat y = (self.height - view.height) / 2;
  207. // MGLayoutBoxes have margins to deal with
  208. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  209. UIView <MGLayoutBox> *box = (id)view;
  210. y += box.topMargin;
  211. x -= box.width + box.rightMargin;
  212. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  213. x -= box.leftMargin;
  214. // hopefully is a UIView then
  215. } else {
  216. x -= view.width;
  217. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  218. }
  219. x -= self.itemPadding;
  220. view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  221. }
  222. }
  223. - (void)layoutMiddleWithin:(CGFloat)limit {
  224. // size and discard
  225. middleUsed = [self size:self.middleItems within:limit font:self.font];
  226. // widen as needed
  227. if (self.widenAsNeeded) {
  228. CGFloat needed = self.leftPadding + leftUsed + middleUsed + rightUsed
  229. + self.rightPadding;
  230. self.width = needed > self.width ? needed : self.width;
  231. }
  232. // lay out
  233. CGFloat x;
  234. if (self.sidePrecedence == MGSidePrecedenceMiddle) {
  235. x = roundf((self.width - middleUsed) / 2);
  236. } else {
  237. x = self.leftPadding + leftUsed + roundf((limit - middleUsed) / 2);
  238. }
  239. int i;
  240. for (i = 0; i < self.middleItems.count; i++) {
  241. UIView *view = self.middleItems[i];
  242. if ([self.dontFit indexOfObject:view] != NSNotFound) {
  243. continue;
  244. }
  245. if ([view conformsToProtocol:@protocol(MGLayoutBox)]
  246. && [(id <MGLayoutBox>)view boxLayoutMode] == MGBoxLayoutAttached) {
  247. continue;
  248. }
  249. x += self.itemPadding;
  250. CGFloat y = (self.height - view.height) / 2;
  251. // MGLayoutBoxes have margins to deal with
  252. if ([view conformsToProtocol:@protocol(MGLayoutBox)]) {
  253. UIView <MGLayoutBox> *box = (id)view;
  254. y += box.topMargin;
  255. x += box.leftMargin;
  256. box.frame = CGRectMake(x, roundf(y), box.width, box.height);
  257. x += box.rightMargin;
  258. // better be a UIView then
  259. } else {
  260. view.frame = CGRectMake(x, roundf(y), view.width, view.height);
  261. }
  262. x += view.width + self.itemPadding;
  263. view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin
  264. | UIViewAutoresizingFlexibleRightMargin;
  265. }
  266. }
  267. - (CGFloat)size:(NSArray *)views within:(CGFloat)limit font:(UIFont *)font {
  268. NSMutableArray *expandables = @[].mutableCopy;
  269. CGFloat used = 0;
  270. unsigned int i;
  271. for (i = 0; i < views.count; i++) {
  272. UIView *item = views[i];
  273. CGSize itemSize = item.frame.size;
  274. [self.dontFit removeObject:item];
  275. // add to subviews
  276. if (item.superview != self) {
  277. [self addSubview:item];
  278. }
  279. // lay out child MGBoxes first
  280. if ([item conformsToProtocol:@protocol(MGLayoutBox)]) {
  281. UIView <MGLayoutBox> *box = (id)item;
  282. box.parentBox = self;
  283. [box layout];
  284. // collect expandables
  285. if (box.sizingMode == MGResizingExpandWidthToFill) {
  286. [expandables addObject:box];
  287. }
  288. // don't layout attached boxes yet
  289. if (box.boxLayoutMode == MGBoxLayoutAttached) {
  290. continue;
  291. }
  292. }
  293. // everything gets left and right padding
  294. used += self.itemPadding * 2;
  295. // not even enough space for the padding alone?
  296. if (!self.widenAsNeeded && used > limit) {
  297. break; // yep, out of space
  298. }
  299. // single line UILabels can be shrunk
  300. if ([item isKindOfClass:UILabel.class] && [(UILabel *)item numberOfLines]
  301. == 1) {
  302. UILabel *label = (id)item;
  303. label.font = font;
  304. CGSize labelSize = [label.text sizeWithFont:label.font];
  305. if (used + labelSize.width > limit) { // needs slimming
  306. label.width = limit - used;
  307. }
  308. used += label.width;
  309. // MGLayoutBoxes have margins to deal with
  310. } else if ([item conformsToProtocol:@protocol(MGLayoutBox)]) {
  311. MGBox *box = (id)item;
  312. used += box.leftMargin + box.width + box.rightMargin;
  313. // hopefully is a UIView then
  314. } else {
  315. used += itemSize.width;
  316. }
  317. // ran out of space after counting the view size?
  318. if (!self.widenAsNeeded && used > limit) {
  319. break;
  320. }
  321. }
  322. // ditch leftovers if out of space
  323. if (i < views.count) {
  324. NSMutableArray *ditch = @[].mutableCopy;
  325. for (; i < views.count; i++) {
  326. [ditch addObject:views[i]];
  327. }
  328. [ditch makeObjectsPerformSelector:@selector(removeFromSuperview)];
  329. [self.dontFit addObjectsFromArray:ditch];
  330. [expandables removeObjectsInArray:ditch];
  331. }
  332. // distribute leftover space to expandables
  333. if (limit - used > 0) {
  334. CGFloat remaining = limit - used;
  335. CGFloat perBox = floorf(remaining / expandables.count);
  336. CGFloat leftover = remaining - perBox * expandables.count;
  337. for (MGBox *expandable in expandables) {
  338. expandable.width += perBox;
  339. used += perBox;
  340. if (expandable == expandables.lastObject) {
  341. expandable.width += leftover;
  342. used += leftover;
  343. }
  344. [expandable layout];
  345. }
  346. }
  347. return used;
  348. }
  349. - (UILabel *)makeLabel:(NSString *)text
  350. align:(UITextAlignment)align {
  351. UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
  352. label.backgroundColor = UIColor.clearColor;
  353. label.text = text;
  354. label.font = align == UITextAlignmentRight && self.rightFont
  355. ? self.rightFont
  356. : self.font;
  357. label.textColor = self.textColor;
  358. label.shadowColor = self.textShadowColor;
  359. label.shadowOffset = CGSizeMake(0, 1);
  360. label.lineBreakMode = align == NSTextAlignmentRight
  361. ? NSLineBreakByTruncatingHead
  362. : NSLineBreakByTruncatingTail;
  363. label.textAlignment = align;
  364. CGSize size = [label.text sizeWithFont:label.font];
  365. label.size = CGSizeMake(size.width, self.height);
  366. return label;
  367. }
  368. #pragma mark - Setters
  369. - (void)setHeight:(CGFloat)height {
  370. super.height = height;
  371. self.underlineType = self.underlineType;
  372. }
  373. - (void)setUnderlineType:(MGUnderlineType)type {
  374. _underlineType = type;
  375. switch (_underlineType) {
  376. case MGUnderlineTop:
  377. self.solidUnderline.frame = CGRectMake(0, 0, self.width, 2);
  378. [self.layer addSublayer:self.solidUnderline];
  379. break;
  380. case MGUnderlineBottom:
  381. self.solidUnderline.frame = CGRectMake(0, self.height - 1, self.width, 2);
  382. [self.layer addSublayer:self.solidUnderline];
  383. break;
  384. case MGUnderlineNone:
  385. default:
  386. [self.solidUnderline removeFromSuperlayer];
  387. break;
  388. }
  389. }
  390. - (void)setTextColor:(UIColor *)textColor {
  391. _textColor = textColor;
  392. if (!textColor) {
  393. return;
  394. }
  395. for (UILabel *label in self.subviews) {
  396. if ([label isKindOfClass:UILabel.class]) {
  397. label.textColor = textColor;
  398. }
  399. }
  400. for (UILabel *label in self.allItems) {
  401. if ([label isKindOfClass:UILabel.class]) {
  402. label.textColor = textColor;
  403. }
  404. }
  405. }
  406. - (void)setTextShadowColor:(UIColor *)textShadowColor {
  407. _textShadowColor = textShadowColor;
  408. if (!textShadowColor) {
  409. return;
  410. }
  411. for (UILabel *label in self.subviews) {
  412. if ([label isKindOfClass:UILabel.class]) {
  413. label.shadowColor = textShadowColor;
  414. }
  415. }
  416. for (UILabel *label in self.allItems) {
  417. if ([label isKindOfClass:UILabel.class]) {
  418. label.shadowColor = textShadowColor;
  419. }
  420. }
  421. }
  422. #pragma mark - Getters
  423. - (NSSet *)allItems {
  424. NSMutableSet *items = [NSMutableSet setWithArray:self.leftItems];
  425. [items addObjectsFromArray:self.middleItems];
  426. [items addObjectsFromArray:self.rightItems];
  427. return items;
  428. }
  429. - (CALayer *)solidUnderline {
  430. if (_solidUnderline) {
  431. return _solidUnderline;
  432. }
  433. _solidUnderline = CALayer.layer;
  434. _solidUnderline.frame = CGRectMake(0, 0, self.width, 2);
  435. _solidUnderline.backgroundColor = [UIColor colorWithWhite:0.87 alpha:1].CGColor;
  436. CALayer *bot = CALayer.layer;
  437. bot.frame = CGRectMake(0, 1, self.frame.size.width, 1);
  438. bot.backgroundColor = UIColor.whiteColor.CGColor;
  439. [_solidUnderline addSublayer:bot];
  440. return _solidUnderline;
  441. }
  442. - (NSMutableArray *)leftItems {
  443. if (!_leftItems) {
  444. _leftItems = @[].mutableCopy;
  445. }
  446. return _leftItems;
  447. }
  448. - (NSMutableArray *)middleItems {
  449. if (!_middleItems) {
  450. _middleItems = @[].mutableCopy;
  451. }
  452. return _middleItems;
  453. }
  454. - (NSMutableArray *)rightItems {
  455. if (!_rightItems) {
  456. _rightItems = @[].mutableCopy;
  457. }
  458. return _rightItems;
  459. }
  460. @end