TWMessageBarManager.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. //
  2. // TWMessageBarManager.m
  3. //
  4. // Created by Terry Worona on 5/13/13.
  5. // Copyright (c) 2013 Terry Worona. All rights reserved.
  6. //
  7. #import "TWMessageBarManager.h"
  8. // Quartz
  9. #import <QuartzCore/QuartzCore.h>
  10. // Numerics (TWMessageBarStyleSheet)
  11. CGFloat const kTWMessageBarStyleSheetMessageBarAlpha = 0.96f;
  12. // Numerics (TWMessageView)
  13. CGFloat const kTWMessageViewBarPadding = 10.0f;
  14. CGFloat const kTWMessageViewIconSize = 36.0f;
  15. CGFloat const kTWMessageViewTextOffset = 2.0f;
  16. NSUInteger const kTWMessageViewiOS7Identifier = 7;
  17. // Numerics (TWMessageBarManager)
  18. CGFloat const kTWMessageBarManagerDisplayDelay = 3.0f;
  19. CGFloat const kTWMessageBarManagerDismissAnimationDuration = 0.25f;
  20. CGFloat const kTWMessageBarManagerPanVelocity = 0.2f;
  21. CGFloat const kTWMessageBarManagerPanAnimationDuration = 0.0002f;
  22. // Strings (TWMessageBarStyleSheet)
  23. NSString * const kTWMessageBarStyleSheetImageIconError = @"icon-error.png";
  24. NSString * const kTWMessageBarStyleSheetImageIconSuccess = @"icon-success.png";
  25. NSString * const kTWMessageBarStyleSheetImageIconInfo = @"icon-info.png";
  26. // Fonts (TWMessageView)
  27. static UIFont *kTWMessageViewTitleFont = nil;
  28. static UIFont *kTWMessageViewDescriptionFont = nil;
  29. // Colors (TWMessageView)
  30. static UIColor *kTWMessageViewTitleColor = nil;
  31. static UIColor *kTWMessageViewDescriptionColor = nil;
  32. // Colors (TWDefaultMessageBarStyleSheet)
  33. static UIColor *kTWDefaultMessageBarStyleSheetErrorBackgroundColor = nil;
  34. static UIColor *kTWDefaultMessageBarStyleSheetSuccessBackgroundColor = nil;
  35. static UIColor *kTWDefaultMessageBarStyleSheetInfoBackgroundColor = nil;
  36. static UIColor *kTWDefaultMessageBarStyleSheetErrorStrokeColor = nil;
  37. static UIColor *kTWDefaultMessageBarStyleSheetSuccessStrokeColor = nil;
  38. static UIColor *kTWDefaultMessageBarStyleSheetInfoStrokeColor = nil;
  39. static NSString *prevMessage;
  40. @protocol TWMessageViewDelegate;
  41. @interface TWMessageView : UIView
  42. @property (nonatomic, copy) NSString *titleString;
  43. @property (nonatomic, copy) NSString *descriptionString;
  44. @property (nonatomic, assign) TWMessageBarMessageType messageType;
  45. @property (nonatomic, assign) BOOL hasCallback;
  46. @property (nonatomic, strong) NSArray *callbacks;
  47. @property (nonatomic, assign, getter = isHit) BOOL hit;
  48. @property (nonatomic, assign) CGFloat duration;
  49. @property (nonatomic, assign) UIStatusBarStyle statusBarStyle;
  50. @property (nonatomic, assign) BOOL statusBarHidden;
  51. @property (nonatomic, weak) id <TWMessageViewDelegate> delegate;
  52. // Initializers
  53. - (id)initWithTitle:(NSString *)title description:(NSString *)description type:(TWMessageBarMessageType)type;
  54. // Getters
  55. - (CGFloat)height;
  56. - (CGFloat)width;
  57. - (CGFloat)statusBarOffset;
  58. - (CGFloat)availableWidth;
  59. - (CGSize)titleSize;
  60. - (CGSize)descriptionSize;
  61. - (CGRect)statusBarFrame;
  62. - (UIFont *)titleFont;
  63. - (UIFont *)descriptionFont;
  64. - (UIColor *)titleColor;
  65. - (UIColor *)descriptionColor;
  66. // Helpers
  67. - (CGRect)orientFrame:(CGRect)frame;
  68. // Notifications
  69. - (void)didChangeDeviceOrientation:(NSNotification *)notification;
  70. @end
  71. @protocol TWMessageViewDelegate <NSObject>
  72. - (NSObject<TWMessageBarStyleSheet> *)styleSheetForMessageView:(TWMessageView *)messageView;
  73. @end
  74. @interface TWDefaultMessageBarStyleSheet : NSObject <TWMessageBarStyleSheet>
  75. + (TWDefaultMessageBarStyleSheet *)styleSheet;
  76. @end
  77. @interface TWMessageWindow : UIWindow
  78. @end
  79. @interface TWMessageBarViewController : UIViewController
  80. @property (nonatomic, assign) UIStatusBarStyle statusBarStyle;
  81. @property (nonatomic, assign) BOOL statusBarHidden;
  82. @end
  83. @interface TWMessageBarManager () <TWMessageViewDelegate>
  84. @property (nonatomic, strong) NSMutableArray *messageBarQueue;
  85. @property (nonatomic, assign, getter = isMessageVisible) BOOL messageVisible;
  86. @property (nonatomic, strong) TWMessageWindow *messageWindow;
  87. @property (nonatomic, readwrite) NSArray *accessibleElements; // accessibility
  88. // Static
  89. + (CGFloat)durationForMessageType:(TWMessageBarMessageType)messageType;
  90. // Helpers
  91. - (void)showNextMessage;
  92. - (void)generateAccessibleElementWithTitle:(NSString *)title description:(NSString *)description;
  93. // Gestures
  94. - (void)itemSelected:(UITapGestureRecognizer *)recognizer;
  95. // Getters
  96. - (UIView *)messageWindowView;
  97. - (TWMessageBarViewController *)messageBarViewController;
  98. // Master presetation
  99. - (void)showMessageWithTitle:(NSString *)title description:(NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration statusBarHidden:(BOOL)statusBarHidden statusBarStyle:(UIStatusBarStyle)statusBarStyle callback:(void (^)())callback;
  100. @end
  101. @implementation TWMessageBarManager
  102. #pragma mark - Singleton
  103. + (nonnull TWMessageBarManager *)sharedInstance
  104. {
  105. static dispatch_once_t pred;
  106. static TWMessageBarManager *instance = nil;
  107. dispatch_once(&pred, ^{
  108. instance = [[self alloc] init];
  109. });
  110. return instance;
  111. }
  112. #pragma mark - Static
  113. + (CGFloat)defaultDuration
  114. {
  115. return kTWMessageBarManagerDisplayDelay;
  116. }
  117. + (CGFloat)durationForMessageType:(TWMessageBarMessageType)messageType
  118. {
  119. return kTWMessageBarManagerDisplayDelay;
  120. }
  121. #pragma mark - Alloc/Init
  122. - (id)init
  123. {
  124. self = [super init];
  125. if (self)
  126. {
  127. _messageBarQueue = [[NSMutableArray alloc] init];
  128. _messageVisible = NO;
  129. _styleSheet = [TWDefaultMessageBarStyleSheet styleSheet];
  130. _managerSupportedOrientationsMask = UIInterfaceOrientationMaskAll;
  131. }
  132. return self;
  133. }
  134. #pragma mark - Public
  135. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type
  136. {
  137. [self showMessageWithTitle:title description:description type:type duration:[TWMessageBarManager durationForMessageType:type] callback:nil];
  138. }
  139. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type callback:(nullable void (^)())callback
  140. {
  141. [self showMessageWithTitle:title description:description type:type duration:[TWMessageBarManager durationForMessageType:type] callback:callback];
  142. }
  143. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration
  144. {
  145. [self showMessageWithTitle:title description:description type:type duration:duration callback:nil];
  146. }
  147. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration callback:(nullable void (^)())callback
  148. {
  149. [self showMessageWithTitle:title description:description type:type duration:duration statusBarStyle:UIStatusBarStyleDefault callback:callback];
  150. }
  151. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type statusBarStyle:(UIStatusBarStyle)statusBarStyle callback:(nullable void (^)())callback
  152. {
  153. [self showMessageWithTitle:title description:description type:type duration:kTWMessageBarManagerDisplayDelay statusBarStyle:statusBarStyle callback:callback];
  154. }
  155. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration statusBarStyle:(UIStatusBarStyle)statusBarStyle callback:(nullable void (^)())callback
  156. {
  157. [self showMessageWithTitle:title description:description type:type duration:duration statusBarHidden:NO statusBarStyle:statusBarStyle callback:callback];
  158. }
  159. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type statusBarHidden:(BOOL)statusBarHidden callback:(nullable void (^)())callback
  160. {
  161. [self showMessageWithTitle:title description:description type:type duration:[TWMessageBarManager durationForMessageType:type] statusBarHidden:statusBarHidden statusBarStyle:UIStatusBarStyleDefault callback:callback];
  162. }
  163. - (void)showMessageWithTitle:(nullable NSString *)title description:(nullable NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration statusBarHidden:(BOOL)statusBarHidden callback:(nullable void (^)())callback
  164. {
  165. [self showMessageWithTitle:title description:description type:type duration:duration statusBarHidden:statusBarHidden statusBarStyle:UIStatusBarStyleDefault callback:callback];
  166. }
  167. #pragma mark - Master Presentation
  168. - (void)showMessageWithTitle:(NSString *)title description:(NSString *)description type:(TWMessageBarMessageType)type duration:(CGFloat)duration statusBarHidden:(BOOL)statusBarHidden statusBarStyle:(UIStatusBarStyle)statusBarStyle callback:(void (^)())callback
  169. {
  170. //TWS
  171. if ([prevMessage isEqualToString:[title stringByAppendingString:description]]) {
  172. return;
  173. } else {
  174. prevMessage = [title stringByAppendingString:description];
  175. }
  176. for (TWMessageView *messageView in self.messageBarQueue) {
  177. if ([messageView.titleString isEqualToString:title] && [messageView.descriptionString isEqualToString:description])
  178. return;
  179. }
  180. TWMessageView *messageView = [[TWMessageView alloc] initWithTitle:title description:description type:type];
  181. messageView.delegate = self;
  182. messageView.callbacks = callback ? [NSArray arrayWithObject:callback] : [NSArray array];
  183. messageView.hasCallback = callback ? YES : NO;
  184. messageView.duration = duration;
  185. messageView.hidden = YES;
  186. messageView.statusBarStyle = statusBarStyle;
  187. messageView.statusBarHidden = statusBarHidden;
  188. [[self messageWindowView] addSubview:messageView];
  189. [[self messageWindowView] bringSubviewToFront:messageView];
  190. [self.messageBarQueue addObject:messageView];
  191. if (!self.messageVisible)
  192. {
  193. [self showNextMessage];
  194. }
  195. }
  196. - (void)hideAllAnimated:(BOOL)animated
  197. {
  198. for (UIView *subview in [[self messageWindowView] subviews])
  199. {
  200. if ([subview isKindOfClass:[TWMessageView class]])
  201. {
  202. TWMessageView *currentMessageView = (TWMessageView *)subview;
  203. if (animated)
  204. {
  205. [UIView animateWithDuration:kTWMessageBarManagerDismissAnimationDuration animations:^{
  206. currentMessageView.frame = CGRectMake(currentMessageView.frame.origin.x, -currentMessageView.frame.size.height, currentMessageView.frame.size.width, currentMessageView.frame.size.height);
  207. } completion:^(BOOL finished) {
  208. [currentMessageView removeFromSuperview];
  209. }];
  210. }
  211. else
  212. {
  213. [currentMessageView removeFromSuperview];
  214. }
  215. }
  216. }
  217. self.messageVisible = NO;
  218. [self.messageBarQueue removeAllObjects];
  219. [NSObject cancelPreviousPerformRequestsWithTarget:self];
  220. self.messageWindow.hidden = YES;
  221. self.messageWindow = nil;
  222. //TWS
  223. prevMessage = nil;
  224. }
  225. - (void)hideAll
  226. {
  227. [self hideAllAnimated:NO];
  228. }
  229. #pragma mark - Helpers
  230. - (void)showNextMessage
  231. {
  232. if ([self.messageBarQueue count] > 0)
  233. {
  234. self.messageVisible = YES;
  235. TWMessageView *messageView = [self.messageBarQueue objectAtIndex:0];
  236. [self messageBarViewController].statusBarHidden = messageView.statusBarHidden; // important to do this prior to hiding
  237. messageView.frame = CGRectMake(0, -[messageView height], [messageView width], [messageView height]);
  238. messageView.hidden = NO;
  239. [messageView setNeedsDisplay];
  240. UITapGestureRecognizer *gest = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(itemSelected:)];
  241. [messageView addGestureRecognizer:gest];
  242. if (messageView)
  243. {
  244. [self.messageBarQueue removeObject:messageView];
  245. [self messageBarViewController].statusBarStyle = messageView.statusBarStyle;
  246. [UIView animateWithDuration:kTWMessageBarManagerDismissAnimationDuration animations:^{
  247. [messageView setFrame:CGRectMake(messageView.frame.origin.x, messageView.frame.origin.y + [messageView height], [messageView width], [messageView height])]; // slide down
  248. }];
  249. [self performSelector:@selector(itemSelected:) withObject:messageView afterDelay:messageView.duration];
  250. [self generateAccessibleElementWithTitle:messageView.titleString description:messageView.descriptionString];
  251. }
  252. }
  253. }
  254. - (void)generateAccessibleElementWithTitle:(NSString *)title description:(NSString *)description
  255. {
  256. UIAccessibilityElement *textElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
  257. textElement.accessibilityLabel = [NSString stringWithFormat:@"%@\n%@", title, description];
  258. textElement.accessibilityTraits = UIAccessibilityTraitStaticText;
  259. self.accessibleElements = @[textElement];
  260. UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self); // notify the accessibility framework to read the message
  261. }
  262. #pragma mark - Gestures
  263. - (void)itemSelected:(id)sender
  264. {
  265. TWMessageView *messageView = nil;
  266. BOOL itemHit = NO;
  267. if ([sender isKindOfClass:[UIGestureRecognizer class]])
  268. {
  269. messageView = (TWMessageView *)((UIGestureRecognizer *)sender).view;
  270. itemHit = YES;
  271. }
  272. else if ([sender isKindOfClass:[TWMessageView class]])
  273. {
  274. messageView = (TWMessageView *)sender;
  275. }
  276. if (messageView && ![messageView isHit])
  277. {
  278. messageView.hit = YES;
  279. [UIView animateWithDuration:kTWMessageBarManagerDismissAnimationDuration animations:^{
  280. [messageView setFrame:CGRectMake(messageView.frame.origin.x, messageView.frame.origin.y - [messageView height], [messageView width], [messageView height])]; // slide back up
  281. } completion:^(BOOL finished) {
  282. if (itemHit)
  283. {
  284. if ([messageView.callbacks count] > 0)
  285. {
  286. id obj = [messageView.callbacks objectAtIndex:0];
  287. if (![obj isEqual:[NSNull null]])
  288. {
  289. ((void (^)())obj)();
  290. }
  291. }
  292. }
  293. self.messageVisible = NO;
  294. [messageView removeFromSuperview];
  295. //TWS
  296. prevMessage = nil;
  297. if([self.messageBarQueue count] > 0)
  298. {
  299. [self showNextMessage];
  300. }
  301. else
  302. {
  303. self.messageWindow.hidden = YES;
  304. self.messageWindow = nil;
  305. }
  306. }];
  307. }
  308. }
  309. #pragma mark - Getters
  310. - (UIView *)messageWindowView
  311. {
  312. return [self messageBarViewController].view;
  313. }
  314. - (TWMessageBarViewController *)messageBarViewController
  315. {
  316. if (!self.messageWindow)
  317. {
  318. self.messageWindow = [[TWMessageWindow alloc] init];
  319. self.messageWindow.frame = [UIApplication sharedApplication].keyWindow.frame;
  320. self.messageWindow.hidden = NO;
  321. self.messageWindow.windowLevel = UIWindowLevelNormal;
  322. self.messageWindow.backgroundColor = [UIColor clearColor];
  323. self.messageWindow.rootViewController = [[TWMessageBarViewController alloc] init];
  324. }
  325. return (TWMessageBarViewController *)self.messageWindow.rootViewController;
  326. }
  327. - (NSArray *)accessibleElements
  328. {
  329. if (_accessibleElements != nil)
  330. {
  331. return _accessibleElements;
  332. }
  333. _accessibleElements = [NSArray array];
  334. return _accessibleElements;
  335. }
  336. #pragma mark - Setters
  337. - (void)setStyleSheet:(NSObject<TWMessageBarStyleSheet> *)styleSheet
  338. {
  339. if (styleSheet != nil)
  340. {
  341. _styleSheet = styleSheet;
  342. }
  343. }
  344. #pragma mark - TWMessageViewDelegate
  345. - (NSObject<TWMessageBarStyleSheet> *)styleSheetForMessageView:(TWMessageView *)messageView
  346. {
  347. return self.styleSheet;
  348. }
  349. #pragma mark - UIAccessibilityContainer
  350. - (NSInteger)accessibilityElementCount
  351. {
  352. return (NSInteger)[self.accessibleElements count];
  353. }
  354. - (id)accessibilityElementAtIndex:(NSInteger)index
  355. {
  356. return [self.accessibleElements objectAtIndex:(NSUInteger)index];
  357. }
  358. - (NSInteger)indexOfAccessibilityElement:(id)element
  359. {
  360. return (NSInteger)[self.accessibleElements indexOfObject:element];
  361. }
  362. - (BOOL)isAccessibilityElement
  363. {
  364. return NO;
  365. }
  366. @end
  367. @implementation TWMessageView
  368. #pragma mark - Alloc/Init
  369. + (void)initialize
  370. {
  371. if (self == [TWMessageView class])
  372. {
  373. // Fonts
  374. kTWMessageViewTitleFont = [UIFont boldSystemFontOfSize:16.0];
  375. kTWMessageViewDescriptionFont = [UIFont systemFontOfSize:14.0];
  376. // Colors
  377. kTWMessageViewTitleColor = [UIColor colorWithWhite:1.0 alpha:1.0];
  378. kTWMessageViewDescriptionColor = [UIColor colorWithWhite:1.0 alpha:1.0];
  379. }
  380. }
  381. - (id)initWithTitle:(NSString *)title description:(NSString *)description type:(TWMessageBarMessageType)type
  382. {
  383. self = [super initWithFrame:CGRectZero];
  384. if (self)
  385. {
  386. self.backgroundColor = [UIColor clearColor];
  387. self.clipsToBounds = NO;
  388. self.userInteractionEnabled = YES;
  389. _titleString = title;
  390. _descriptionString = description;
  391. _messageType = type;
  392. _hasCallback = NO;
  393. _hit = NO;
  394. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeDeviceOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
  395. }
  396. return self;
  397. }
  398. #pragma mark - Memory Management
  399. - (void)dealloc
  400. {
  401. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
  402. }
  403. #pragma mark - Drawing
  404. - (void)drawRect:(CGRect)rect
  405. {
  406. CGContextRef context = UIGraphicsGetCurrentContext();
  407. if ([self.delegate respondsToSelector:@selector(styleSheetForMessageView:)])
  408. {
  409. id<TWMessageBarStyleSheet> styleSheet = [self.delegate styleSheetForMessageView:self];
  410. // background fill
  411. CGContextSaveGState(context);
  412. {
  413. if ([styleSheet respondsToSelector:@selector(backgroundColorForMessageType:)])
  414. {
  415. [[styleSheet backgroundColorForMessageType:self.messageType] set];
  416. CGContextFillRect(context, rect);
  417. }
  418. }
  419. CGContextRestoreGState(context);
  420. // bottom stroke
  421. CGContextSaveGState(context);
  422. {
  423. if ([styleSheet respondsToSelector:@selector(strokeColorForMessageType:)])
  424. {
  425. CGContextBeginPath(context);
  426. CGContextMoveToPoint(context, 0, rect.size.height);
  427. CGContextSetStrokeColorWithColor(context, [styleSheet strokeColorForMessageType:self.messageType].CGColor);
  428. CGContextSetLineWidth(context, 1.0);
  429. CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
  430. CGContextStrokePath(context);
  431. }
  432. }
  433. CGContextRestoreGState(context);
  434. CGFloat xOffset = kTWMessageViewBarPadding;
  435. CGFloat yOffset = kTWMessageViewBarPadding + [self statusBarOffset];
  436. // icon
  437. CGContextSaveGState(context);
  438. {
  439. if ([styleSheet respondsToSelector:@selector(iconImageForMessageType:)])
  440. {
  441. [[styleSheet iconImageForMessageType:self.messageType] drawInRect:CGRectMake(xOffset, yOffset, kTWMessageViewIconSize, kTWMessageViewIconSize)];
  442. }
  443. }
  444. CGContextRestoreGState(context);
  445. yOffset -= kTWMessageViewTextOffset;
  446. xOffset += kTWMessageViewIconSize + kTWMessageViewBarPadding;
  447. CGSize titleLabelSize = [self titleSize];
  448. CGSize descriptionLabelSize = [self descriptionSize];
  449. if (self.titleString && !self.descriptionString)
  450. {
  451. yOffset = ceil(rect.size.height * 0.5) - ceil(titleLabelSize.height * 0.5) - kTWMessageViewTextOffset;
  452. }
  453. if ([[UIDevice currentDevice] tw_isRunningiOS7OrLater])
  454. {
  455. NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
  456. paragraphStyle.alignment = NSTextAlignmentLeft;
  457. [[self titleColor] set];
  458. [self.titleString drawWithRect:CGRectMake(xOffset, yOffset, titleLabelSize.width, titleLabelSize.height)
  459. options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
  460. attributes:@{NSFontAttributeName:[self titleFont], NSForegroundColorAttributeName:[self titleColor], NSParagraphStyleAttributeName:paragraphStyle}
  461. context:nil];
  462. yOffset += titleLabelSize.height;
  463. [[self descriptionColor] set];
  464. [self.descriptionString drawWithRect:CGRectMake(xOffset, yOffset, descriptionLabelSize.width, descriptionLabelSize.height)
  465. options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
  466. attributes:@{NSFontAttributeName:[self descriptionFont], NSForegroundColorAttributeName:[self descriptionColor], NSParagraphStyleAttributeName:paragraphStyle}
  467. context:nil];
  468. }
  469. else
  470. {
  471. [[self titleColor] set];
  472. #pragma clang diagnostic push
  473. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  474. [self.titleString drawInRect:CGRectMake(xOffset, yOffset, titleLabelSize.width, titleLabelSize.height) withFont:[self titleFont] lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentLeft];
  475. #pragma clang diagnostic pop
  476. yOffset += titleLabelSize.height;
  477. [[self descriptionColor] set];
  478. #pragma clang diagnostic push
  479. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  480. [self.descriptionString drawInRect:CGRectMake(xOffset, yOffset, descriptionLabelSize.width, descriptionLabelSize.height) withFont:[self descriptionFont] lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentLeft];
  481. #pragma clang diagnostic pop
  482. }
  483. }
  484. }
  485. #pragma mark - Getters
  486. - (CGFloat)height
  487. {
  488. CGSize titleLabelSize = [self titleSize];
  489. CGSize descriptionLabelSize = [self descriptionSize];
  490. return MAX((kTWMessageViewBarPadding * 2) + titleLabelSize.height + descriptionLabelSize.height + [self statusBarOffset], (kTWMessageViewBarPadding * 2) + kTWMessageViewIconSize + [self statusBarOffset]);
  491. }
  492. - (CGFloat)width
  493. {
  494. return [self statusBarFrame].size.width;
  495. }
  496. - (CGFloat)statusBarOffset
  497. {
  498. return [[UIDevice currentDevice] tw_isRunningiOS7OrLater] ? [self statusBarFrame].size.height : 0.0;
  499. }
  500. - (CGFloat)availableWidth
  501. {
  502. return ([self width] - (kTWMessageViewBarPadding * 3) - kTWMessageViewIconSize);
  503. }
  504. - (CGSize)titleSize
  505. {
  506. CGSize boundedSize = CGSizeMake([self availableWidth], CGFLOAT_MAX);
  507. CGSize titleLabelSize;
  508. if ([[UIDevice currentDevice] tw_isRunningiOS7OrLater])
  509. {
  510. NSDictionary *titleStringAttributes = [NSDictionary dictionaryWithObject:[self titleFont] forKey: NSFontAttributeName];
  511. titleLabelSize = [self.titleString boundingRectWithSize:boundedSize
  512. options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin
  513. attributes:titleStringAttributes
  514. context:nil].size;
  515. }
  516. else
  517. {
  518. #pragma clang diagnostic push
  519. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  520. titleLabelSize = [_titleString sizeWithFont:[self titleFont] constrainedToSize:boundedSize lineBreakMode:NSLineBreakByTruncatingTail];
  521. #pragma clang diagnostic pop
  522. }
  523. return CGSizeMake(ceilf(titleLabelSize.width), ceilf(titleLabelSize.height));
  524. }
  525. - (CGSize)descriptionSize
  526. {
  527. CGSize boundedSize = CGSizeMake([self availableWidth], CGFLOAT_MAX);
  528. CGSize descriptionLabelSize;
  529. if ([[UIDevice currentDevice] tw_isRunningiOS7OrLater])
  530. {
  531. NSDictionary *descriptionStringAttributes = [NSDictionary dictionaryWithObject:[self descriptionFont] forKey: NSFontAttributeName];
  532. descriptionLabelSize = [self.descriptionString boundingRectWithSize:boundedSize
  533. options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin
  534. attributes:descriptionStringAttributes
  535. context:nil].size;
  536. }
  537. else
  538. {
  539. #pragma clang diagnostic push
  540. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  541. descriptionLabelSize = [_descriptionString sizeWithFont:[self descriptionFont] constrainedToSize:boundedSize lineBreakMode:NSLineBreakByTruncatingTail];
  542. #pragma clang diagnostic pop
  543. }
  544. return CGSizeMake(ceilf(descriptionLabelSize.width), ceilf(descriptionLabelSize.height));
  545. }
  546. - (CGRect)statusBarFrame
  547. {
  548. CGRect windowFrame = NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 ? [self orientFrame:[UIApplication sharedApplication].keyWindow.frame] : [UIApplication sharedApplication].keyWindow.frame;
  549. CGRect statusFrame = NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 ? [self orientFrame:[UIApplication sharedApplication].statusBarFrame] : [UIApplication sharedApplication].statusBarFrame;
  550. return CGRectMake(windowFrame.origin.x, windowFrame.origin.y, windowFrame.size.width, statusFrame.size.height);
  551. }
  552. - (UIFont *)titleFont
  553. {
  554. if ([self.delegate respondsToSelector:@selector(styleSheetForMessageView:)])
  555. {
  556. id<TWMessageBarStyleSheet> styleSheet = [self.delegate styleSheetForMessageView:self];
  557. if ([styleSheet respondsToSelector:@selector(titleFontForMessageType:)])
  558. {
  559. return [styleSheet titleFontForMessageType:self.messageType];
  560. }
  561. }
  562. return kTWMessageViewTitleFont;
  563. }
  564. - (UIFont *)descriptionFont
  565. {
  566. if ([self.delegate respondsToSelector:@selector(styleSheetForMessageView:)])
  567. {
  568. id<TWMessageBarStyleSheet> styleSheet = [self.delegate styleSheetForMessageView:self];
  569. if ([styleSheet respondsToSelector:@selector(descriptionFontForMessageType:)])
  570. {
  571. return [styleSheet descriptionFontForMessageType:self.messageType];
  572. }
  573. }
  574. return kTWMessageViewDescriptionFont;
  575. }
  576. - (UIColor *)titleColor
  577. {
  578. if ([self.delegate respondsToSelector:@selector(styleSheetForMessageView:)])
  579. {
  580. id<TWMessageBarStyleSheet> styleSheet = [self.delegate styleSheetForMessageView:self];
  581. if ([styleSheet respondsToSelector:@selector(titleColorForMessageType:)])
  582. {
  583. return [styleSheet titleColorForMessageType:self.messageType];
  584. }
  585. }
  586. return kTWMessageViewTitleColor;
  587. }
  588. - (UIColor *)descriptionColor
  589. {
  590. if ([self.delegate respondsToSelector:@selector(styleSheetForMessageView:)])
  591. {
  592. id<TWMessageBarStyleSheet> styleSheet = [self.delegate styleSheetForMessageView:self];
  593. if ([styleSheet respondsToSelector:@selector(descriptionColorForMessageType:)])
  594. {
  595. return [styleSheet descriptionColorForMessageType:self.messageType];
  596. }
  597. }
  598. return kTWMessageViewDescriptionColor;
  599. }
  600. #pragma mark - Helpers
  601. - (CGRect)orientFrame:(CGRect)frame
  602. {
  603. return frame;
  604. }
  605. #pragma mark - Notifications
  606. - (void)didChangeDeviceOrientation:(NSNotification *)notification
  607. {
  608. self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, [self statusBarFrame].size.width, self.frame.size.height);
  609. [self setNeedsDisplay];
  610. }
  611. @end
  612. @implementation TWDefaultMessageBarStyleSheet
  613. #pragma mark - Alloc/Init
  614. + (void)initialize
  615. {
  616. if (self == [TWDefaultMessageBarStyleSheet class])
  617. {
  618. // Colors (background)
  619. kTWDefaultMessageBarStyleSheetErrorBackgroundColor = [UIColor colorWithRed:1.0 green:0.611 blue:0.0 alpha:kTWMessageBarStyleSheetMessageBarAlpha]; // orange
  620. kTWDefaultMessageBarStyleSheetSuccessBackgroundColor = [UIColor colorWithRed:0.0f green:0.831f blue:0.176f alpha:kTWMessageBarStyleSheetMessageBarAlpha]; // green
  621. kTWDefaultMessageBarStyleSheetInfoBackgroundColor = [UIColor colorWithRed:0.0 green:0.482 blue:1.0 alpha:kTWMessageBarStyleSheetMessageBarAlpha]; // blue
  622. // Colors (stroke)
  623. kTWDefaultMessageBarStyleSheetErrorStrokeColor = [UIColor colorWithRed:0.949f green:0.580f blue:0.0f alpha:1.0f]; // orange
  624. kTWDefaultMessageBarStyleSheetSuccessStrokeColor = [UIColor colorWithRed:0.0f green:0.772f blue:0.164f alpha:1.0f]; // green
  625. kTWDefaultMessageBarStyleSheetInfoStrokeColor = [UIColor colorWithRed:0.0f green:0.415f blue:0.803f alpha:1.0f]; // blue
  626. }
  627. }
  628. + (TWDefaultMessageBarStyleSheet *)styleSheet
  629. {
  630. return [[TWDefaultMessageBarStyleSheet alloc] init];
  631. }
  632. #pragma mark - TWMessageBarStyleSheet
  633. - (nonnull UIColor *)backgroundColorForMessageType:(TWMessageBarMessageType)type
  634. {
  635. UIColor *backgroundColor = nil;
  636. switch (type)
  637. {
  638. case TWMessageBarMessageTypeError:
  639. backgroundColor = kTWDefaultMessageBarStyleSheetErrorBackgroundColor;
  640. break;
  641. case TWMessageBarMessageTypeSuccess:
  642. backgroundColor = kTWDefaultMessageBarStyleSheetSuccessBackgroundColor;
  643. break;
  644. case TWMessageBarMessageTypeInfo:
  645. backgroundColor = kTWDefaultMessageBarStyleSheetInfoBackgroundColor;
  646. break;
  647. }
  648. return backgroundColor;
  649. }
  650. - (nonnull UIColor *)strokeColorForMessageType:(TWMessageBarMessageType)type
  651. {
  652. UIColor *strokeColor = nil;
  653. switch (type)
  654. {
  655. case TWMessageBarMessageTypeError:
  656. strokeColor = kTWDefaultMessageBarStyleSheetErrorStrokeColor;
  657. break;
  658. case TWMessageBarMessageTypeSuccess:
  659. strokeColor = kTWDefaultMessageBarStyleSheetSuccessStrokeColor;
  660. break;
  661. case TWMessageBarMessageTypeInfo:
  662. strokeColor = kTWDefaultMessageBarStyleSheetInfoStrokeColor;
  663. break;
  664. }
  665. return strokeColor;
  666. }
  667. - (nonnull UIImage *)iconImageForMessageType:(TWMessageBarMessageType)type
  668. {
  669. UIImage *iconImage = nil;
  670. switch (type)
  671. {
  672. case TWMessageBarMessageTypeError:
  673. iconImage = [UIImage imageNamed:kTWMessageBarStyleSheetImageIconError];
  674. break;
  675. case TWMessageBarMessageTypeSuccess:
  676. iconImage = [UIImage imageNamed:kTWMessageBarStyleSheetImageIconSuccess];
  677. break;
  678. case TWMessageBarMessageTypeInfo:
  679. iconImage = [UIImage imageNamed:kTWMessageBarStyleSheetImageIconInfo];
  680. break;
  681. }
  682. return iconImage;
  683. }
  684. @end
  685. @implementation TWMessageWindow
  686. #pragma mark - Touches
  687. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
  688. {
  689. UIView *hitView = [super hitTest:point withEvent:event];
  690. /*
  691. * Pass touches through if they land on the rootViewController's view.
  692. * Allows notification interaction without blocking the window below.
  693. */
  694. if ([hitView isEqual: self.rootViewController.view])
  695. {
  696. hitView = nil;
  697. }
  698. return hitView;
  699. }
  700. @end
  701. @implementation UIDevice (Additions)
  702. #pragma mark - OS Helpers
  703. - (BOOL)tw_isRunningiOS7OrLater
  704. {
  705. NSString *systemVersion = self.systemVersion;
  706. NSUInteger systemInt = [systemVersion intValue];
  707. return systemInt >= kTWMessageViewiOS7Identifier;
  708. }
  709. @end
  710. @implementation TWMessageBarViewController
  711. - (UIInterfaceOrientationMask)supportedInterfaceOrientations
  712. {
  713. return [TWMessageBarManager sharedInstance].managerSupportedOrientationsMask;
  714. }
  715. #pragma mark - Setters
  716. - (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle
  717. {
  718. _statusBarStyle = statusBarStyle;
  719. if ([[UIDevice currentDevice] tw_isRunningiOS7OrLater])
  720. {
  721. [self setNeedsStatusBarAppearanceUpdate];
  722. }
  723. }
  724. - (void)setStatusBarHidden:(BOOL)statusBarHidden
  725. {
  726. _statusBarHidden = statusBarHidden;
  727. if ([[UIDevice currentDevice] tw_isRunningiOS7OrLater])
  728. {
  729. [self setNeedsStatusBarAppearanceUpdate];
  730. }
  731. }
  732. #pragma mark - Status Bar
  733. - (UIStatusBarStyle)preferredStatusBarStyle
  734. {
  735. return self.statusBarStyle;
  736. }
  737. - (BOOL)prefersStatusBarHidden
  738. {
  739. return self.statusBarHidden;
  740. }
  741. @end