TWMessageBarManager.m 32 KB

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