You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

456 lines
16 KiB

  1. //
  2. // TSMessage.m
  3. // Felix Krause
  4. //
  5. // Created by Felix Krause on 24.08.12.
  6. // Copyright (c) 2012 Felix Krause. All rights reserved.
  7. //
  8. #import "TSMessage.h"
  9. #import "TSMessageView.h"
  10. #define kTSMessageDisplayTime 1.5
  11. #define kTSMessageExtraDisplayTimePerPixel 0.04
  12. #define kTSMessageAnimationDuration 0.3
  13. @interface TSMessage ()
  14. /** The queued messages (TSMessageView objects) */
  15. @property (nonatomic, strong) NSMutableArray *messages;
  16. @end
  17. @implementation TSMessage
  18. static TSMessage *sharedMessage;
  19. static BOOL notificationActive;
  20. static BOOL _useiOS7Style;
  21. __weak static UIViewController *_defaultViewController;
  22. + (TSMessage *)sharedMessage
  23. {
  24. if (!sharedMessage)
  25. {
  26. sharedMessage = [[[self class] alloc] init];
  27. }
  28. return sharedMessage;
  29. }
  30. #pragma mark Public methods for setting up the notification
  31. + (void)showNotificationWithTitle:(NSString *)title
  32. type:(TSMessageNotificationType)type
  33. {
  34. [self showNotificationWithTitle:title
  35. subtitle:nil
  36. type:type];
  37. }
  38. + (void)showNotificationWithTitle:(NSString *)title
  39. subtitle:(NSString *)subtitle
  40. type:(TSMessageNotificationType)type
  41. {
  42. [self showNotificationInViewController:[self defaultViewController]
  43. title:title
  44. subtitle:subtitle
  45. type:type];
  46. }
  47. + (void)showNotificationInViewController:(UIViewController *)viewController
  48. title:(NSString *)title
  49. subtitle:(NSString *)subtitle
  50. type:(TSMessageNotificationType)type
  51. duration:(NSTimeInterval)duration
  52. {
  53. [self showNotificationInViewController:viewController
  54. title:title
  55. subtitle:subtitle
  56. image:nil
  57. type:type
  58. duration:duration
  59. callback:nil
  60. buttonTitle:nil
  61. buttonCallback:nil
  62. atPosition:TSMessageNotificationPositionTop
  63. canBeDismissedByUser:YES];
  64. }
  65. + (void)showNotificationInViewController:(UIViewController *)viewController
  66. title:(NSString *)title
  67. subtitle:(NSString *)subtitle
  68. type:(TSMessageNotificationType)type
  69. duration:(NSTimeInterval)duration
  70. canBeDismissedByUser:(BOOL)dismissingEnabled
  71. {
  72. [self showNotificationInViewController:viewController
  73. title:title
  74. subtitle:subtitle
  75. image:nil
  76. type:type
  77. duration:duration
  78. callback:nil
  79. buttonTitle:nil
  80. buttonCallback:nil
  81. atPosition:TSMessageNotificationPositionTop
  82. canBeDismissedByUser:dismissingEnabled];
  83. }
  84. + (void)showNotificationInViewController:(UIViewController *)viewController
  85. title:(NSString *)title
  86. subtitle:(NSString *)subtitle
  87. type:(TSMessageNotificationType)type
  88. {
  89. [self showNotificationInViewController:viewController
  90. title:title
  91. subtitle:subtitle
  92. image:nil
  93. type:type
  94. duration:TSMessageNotificationDurationAutomatic
  95. callback:nil
  96. buttonTitle:nil
  97. buttonCallback:nil
  98. atPosition:TSMessageNotificationPositionTop
  99. canBeDismissedByUser:YES];
  100. }
  101. + (void)showNotificationInViewController:(UIViewController *)viewController
  102. title:(NSString *)title
  103. subtitle:(NSString *)subtitle
  104. image:(UIImage *)image
  105. type:(TSMessageNotificationType)type
  106. duration:(NSTimeInterval)duration
  107. callback:(void (^)())callback
  108. buttonTitle:(NSString *)buttonTitle
  109. buttonCallback:(void (^)())buttonCallback
  110. atPosition:(TSMessageNotificationPosition)messagePosition
  111. canBeDismissedByUser:(BOOL)dismissingEnabled
  112. {
  113. // Create the TSMessageView
  114. TSMessageView *v = [[TSMessageView alloc] initWithTitle:title
  115. subtitle:subtitle
  116. image:image
  117. type:type
  118. duration:duration
  119. inViewController:viewController
  120. callback:callback
  121. buttonTitle:buttonTitle
  122. buttonCallback:buttonCallback
  123. atPosition:messagePosition
  124. canBeDismissedByUser:dismissingEnabled];
  125. [self prepareNotificationToBeShown:v];
  126. }
  127. + (void)prepareNotificationToBeShown:(TSMessageView *)messageView
  128. {
  129. NSString *title = messageView.title;
  130. NSString *subtitle = messageView.subtitle;
  131. for (TSMessageView *n in [TSMessage sharedMessage].messages)
  132. {
  133. if (([n.title isEqualToString:title] || (!n.title && !title)) && ([n.subtitle isEqualToString:subtitle] || (!n.subtitle && !subtitle)))
  134. {
  135. return; // avoid showing the same messages twice in a row
  136. }
  137. }
  138. [[TSMessage sharedMessage].messages addObject:messageView];
  139. if (!notificationActive)
  140. {
  141. [[TSMessage sharedMessage] fadeInCurrentNotification];
  142. }
  143. }
  144. #pragma mark Fading in/out the message view
  145. - (id)init
  146. {
  147. if ((self = [super init]))
  148. {
  149. _messages = [[NSMutableArray alloc] init];
  150. }
  151. return self;
  152. }
  153. - (void)fadeInCurrentNotification
  154. {
  155. if ([self.messages count] == 0) return;
  156. notificationActive = YES;
  157. TSMessageView *currentView = [self.messages objectAtIndex:0];
  158. __block CGFloat verticalOffset = 0.0f;
  159. void (^addStatusBarHeightToVerticalOffset)() = ^void() {
  160. if (currentView.messagePosition == TSMessageNotificationPositionNavBarOverlay){
  161. return;
  162. }
  163. CGSize statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
  164. verticalOffset += MIN(statusBarSize.width, statusBarSize.height);
  165. };
  166. if ([currentView.viewController isKindOfClass:[UINavigationController class]] || [currentView.viewController.parentViewController isKindOfClass:[UINavigationController class]])
  167. {
  168. UINavigationController *currentNavigationController;
  169. if([currentView.viewController isKindOfClass:[UINavigationController class]])
  170. currentNavigationController = (UINavigationController *)currentView.viewController;
  171. else
  172. currentNavigationController = (UINavigationController *)currentView.viewController.parentViewController;
  173. BOOL isViewIsUnderStatusBar = [[[currentNavigationController childViewControllers] firstObject] wantsFullScreenLayout];
  174. if (!isViewIsUnderStatusBar && currentNavigationController.parentViewController == nil) {
  175. isViewIsUnderStatusBar = ![TSMessage isNavigationBarInNavigationControllerHidden:currentNavigationController]; // strange but true
  176. }
  177. if (![TSMessage isNavigationBarInNavigationControllerHidden:currentNavigationController] && currentView.messagePosition != TSMessageNotificationPositionNavBarOverlay)
  178. {
  179. [currentNavigationController.view insertSubview:currentView
  180. belowSubview:[currentNavigationController navigationBar]];
  181. verticalOffset = [currentNavigationController navigationBar].bounds.size.height;
  182. if ([TSMessage iOS7StyleEnabled] || isViewIsUnderStatusBar) {
  183. addStatusBarHeightToVerticalOffset();
  184. }
  185. }
  186. else
  187. {
  188. [currentView.viewController.view addSubview:currentView];
  189. if ([TSMessage iOS7StyleEnabled] || isViewIsUnderStatusBar) {
  190. addStatusBarHeightToVerticalOffset();
  191. }
  192. }
  193. }
  194. else
  195. {
  196. [currentView.viewController.view addSubview:currentView];
  197. if ([TSMessage iOS7StyleEnabled]) {
  198. addStatusBarHeightToVerticalOffset();
  199. }
  200. }
  201. CGPoint toPoint;
  202. if (currentView.messagePosition != TSMessageNotificationPositionBottom)
  203. {
  204. CGFloat navigationbarBottomOfViewController = 0;
  205. if (self.delegate && [self.delegate respondsToSelector:@selector(messageLocationOfMessageView:)])
  206. {
  207. navigationbarBottomOfViewController = [self.delegate messageLocationOfMessageView:currentView];
  208. }
  209. toPoint = CGPointMake(currentView.center.x,
  210. navigationbarBottomOfViewController + verticalOffset + CGRectGetHeight(currentView.frame) / 2.0);
  211. }
  212. else
  213. {
  214. CGFloat y = currentView.viewController.view.bounds.size.height - CGRectGetHeight(currentView.frame) / 2.0;
  215. if (!currentView.viewController.navigationController.isToolbarHidden)
  216. {
  217. y -= CGRectGetHeight(currentView.viewController.navigationController.toolbar.bounds);
  218. }
  219. toPoint = CGPointMake(currentView.center.x, y);
  220. }
  221. if (self.delegate && [self.delegate respondsToSelector:@selector(customizeMessageView:)])
  222. {
  223. [self.delegate customizeMessageView:currentView];
  224. }
  225. dispatch_block_t animationBlock = ^{
  226. currentView.center = toPoint;
  227. if (![TSMessage iOS7StyleEnabled]) {
  228. currentView.alpha = TSMessageViewAlpha;
  229. }
  230. };
  231. void(^completionBlock)(BOOL) = ^(BOOL finished) {
  232. currentView.messageIsFullyDisplayed = YES;
  233. };
  234. if (![TSMessage iOS7StyleEnabled]) {
  235. [UIView animateWithDuration:kTSMessageAnimationDuration
  236. delay:0.0
  237. options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
  238. animations:animationBlock
  239. completion:completionBlock];
  240. } else {
  241. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
  242. [UIView animateWithDuration:kTSMessageAnimationDuration + 0.1
  243. delay:0
  244. usingSpringWithDamping:0.8
  245. initialSpringVelocity:0.f
  246. options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
  247. animations:animationBlock
  248. completion:completionBlock];
  249. #endif
  250. }
  251. if (currentView.duration == TSMessageNotificationDurationAutomatic)
  252. {
  253. currentView.duration = kTSMessageAnimationDuration + kTSMessageDisplayTime + currentView.frame.size.height * kTSMessageExtraDisplayTimePerPixel;
  254. }
  255. if (currentView.duration != TSMessageNotificationDurationEndless)
  256. {
  257. dispatch_async(dispatch_get_main_queue(), ^
  258. {
  259. [self performSelector:@selector(fadeOutNotification:)
  260. withObject:currentView
  261. afterDelay:currentView.duration];
  262. });
  263. }
  264. }
  265. + (BOOL)isNavigationBarInNavigationControllerHidden:(UINavigationController *)navController
  266. {
  267. if([navController isNavigationBarHidden]) {
  268. return YES;
  269. } else if ([[navController navigationBar] isHidden]) {
  270. return YES;
  271. } else {
  272. return NO;
  273. }
  274. }
  275. - (void)fadeOutNotification:(TSMessageView *)currentView
  276. {
  277. [self fadeOutNotification:currentView animationFinishedBlock:nil];
  278. }
  279. - (void)fadeOutNotification:(TSMessageView *)currentView animationFinishedBlock:(void (^)())animationFinished
  280. {
  281. currentView.messageIsFullyDisplayed = NO;
  282. [NSObject cancelPreviousPerformRequestsWithTarget:self
  283. selector:@selector(fadeOutNotification:)
  284. object:currentView];
  285. CGPoint fadeOutToPoint;
  286. if (currentView.messagePosition != TSMessageNotificationPositionBottom)
  287. {
  288. fadeOutToPoint = CGPointMake(currentView.center.x, -CGRectGetHeight(currentView.frame)/2.f);
  289. }
  290. else
  291. {
  292. fadeOutToPoint = CGPointMake(currentView.center.x,
  293. currentView.viewController.view.bounds.size.height + CGRectGetHeight(currentView.frame)/2.f);
  294. }
  295. [UIView animateWithDuration:kTSMessageAnimationDuration animations:^
  296. {
  297. currentView.center = fadeOutToPoint;
  298. if (![TSMessage iOS7StyleEnabled]) {
  299. currentView.alpha = 0.f;
  300. }
  301. } completion:^(BOOL finished)
  302. {
  303. [currentView removeFromSuperview];
  304. if ([self.messages count] > 0)
  305. {
  306. [self.messages removeObjectAtIndex:0];
  307. }
  308. notificationActive = NO;
  309. if ([self.messages count] > 0)
  310. {
  311. [self fadeInCurrentNotification];
  312. }
  313. if(animationFinished) {
  314. animationFinished();
  315. }
  316. }];
  317. }
  318. + (BOOL)dismissActiveNotification
  319. {
  320. return [self dismissActiveNotificationWithCompletion:nil];
  321. }
  322. + (BOOL)dismissActiveNotificationWithCompletion:(void (^)())completion
  323. {
  324. if ([[TSMessage sharedMessage].messages count] == 0) return NO;
  325. dispatch_async(dispatch_get_main_queue(), ^
  326. {
  327. if ([[TSMessage sharedMessage].messages count] == 0) return;
  328. TSMessageView *currentMessage = [[TSMessage sharedMessage].messages objectAtIndex:0];
  329. if (currentMessage.messageIsFullyDisplayed)
  330. {
  331. [[TSMessage sharedMessage] fadeOutNotification:currentMessage animationFinishedBlock:completion];
  332. }
  333. });
  334. return YES;
  335. }
  336. #pragma mark Customizing TSMessages
  337. + (void)setDefaultViewController:(UIViewController *)defaultViewController
  338. {
  339. _defaultViewController = defaultViewController;
  340. }
  341. + (void)setDelegate:(id<TSMessageViewProtocol>)delegate
  342. {
  343. [TSMessage sharedMessage].delegate = delegate;
  344. }
  345. + (void)addCustomDesignFromFileWithName:(NSString *)fileName
  346. {
  347. [TSMessageView addNotificationDesignFromFile:fileName];
  348. }
  349. #pragma mark Other methods
  350. + (BOOL)isNotificationActive
  351. {
  352. return notificationActive;
  353. }
  354. + (NSArray *)queuedMessages
  355. {
  356. return [TSMessage sharedMessage].messages;
  357. }
  358. + (UIViewController *)defaultViewController
  359. {
  360. __strong UIViewController *defaultViewController = _defaultViewController;
  361. if (!defaultViewController) {
  362. defaultViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  363. }
  364. return defaultViewController;
  365. }
  366. + (BOOL)iOS7StyleEnabled
  367. {
  368. static dispatch_once_t onceToken;
  369. dispatch_once(&onceToken, ^{
  370. // Decide wheter to use iOS 7 style or not based on the running device and the base sdk
  371. BOOL iOS7SDK = NO;
  372. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
  373. iOS7SDK = YES;
  374. #endif
  375. _useiOS7Style = ! (TS_SYSTEM_VERSION_LESS_THAN(@"7.0") || !iOS7SDK);
  376. });
  377. return _useiOS7Style;
  378. }
  379. @end