AHKActionSheet.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. //
  2. // AHKActionSheet.m
  3. // AHKActionSheetExample
  4. //
  5. // Created by Arkadiusz on 08-04-14.
  6. // Copyright (c) 2014 Arkadiusz Holko. All rights reserved.
  7. //
  8. #import <QuartzCore/QuartzCore.h>
  9. #import "AHKActionSheet.h"
  10. #import "AHKActionSheetViewController.h"
  11. #import "UIImage+AHKAdditions.h"
  12. #import "UIWindow+AHKAdditions.h"
  13. static const NSTimeInterval kDefaultAnimationDuration = 0.5f;
  14. // Length of the range at which the blurred background is being hidden when the user scrolls the tableView to the top.
  15. static const CGFloat kBlurFadeRangeSize = 200.0f;
  16. static NSString * const kCellIdentifier = @"Cell";
  17. // How much user has to scroll beyond the top of the tableView for the view to dismiss automatically.
  18. static const CGFloat kAutoDismissOffset = 80.0f;
  19. // Offset at which there's a check if the user is flicking the tableView down.
  20. static const CGFloat kFlickDownHandlingOffset = 20.0f;
  21. static const CGFloat kFlickDownMinVelocity = 2000.0f;
  22. // How much free space to leave at the top (above the tableView's contents) when there's a lot of elements. It makes this control look similar to the UIActionSheet.
  23. static const CGFloat kTopSpaceMarginFraction = 0.0; //TWS 0.333f;
  24. // cancelButton's shadow height as the ratio to the cancelButton's height
  25. static const CGFloat kCancelButtonShadowHeightRatio = 0.333f;
  26. /// Used for storing button configuration.
  27. @interface AHKActionSheetItem : NSObject
  28. @property (copy, nonatomic) NSString *title;
  29. @property (strong, nonatomic) UIImage *image;
  30. @property (nonatomic) AHKActionSheetButtonType type;
  31. @property (strong, nonatomic) AHKActionSheetHandler handler;
  32. @end
  33. @implementation AHKActionSheetItem
  34. @end
  35. @interface AHKActionSheet() <UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate>
  36. @property (strong, nonatomic) NSMutableArray *items;
  37. @property (weak, nonatomic, readwrite) UIWindow *previousKeyWindow;
  38. @property (strong, nonatomic) UIWindow *window;
  39. @property (weak, nonatomic) UIImageView *blurredBackgroundView;
  40. @property (weak, nonatomic) UITableView *tableView;
  41. @property (weak, nonatomic) UIButton *cancelButton;
  42. @property (weak, nonatomic) UIView *cancelButtonShadowView;
  43. @end
  44. @implementation AHKActionSheet
  45. #pragma mark - Init
  46. + (void)initialize
  47. {
  48. if (self != [AHKActionSheet class]) {
  49. return;
  50. }
  51. AHKActionSheet *appearance = [self appearance];
  52. [appearance setBlurRadius:16.0f];
  53. [appearance setBlurTintColor:[UIColor colorWithWhite:1.0f alpha:0.5f]];
  54. [appearance setBlurSaturationDeltaFactor:1.8f];
  55. [appearance setButtonHeight:60.0f];
  56. [appearance setCancelButtonHeight:44.0f];
  57. [appearance setAutomaticallyTintButtonImages:@YES];
  58. [appearance setSelectedBackgroundColor:[UIColor colorWithWhite:0.1f alpha:0.2f]];
  59. [appearance setCancelButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f],
  60. NSForegroundColorAttributeName : [UIColor darkGrayColor] }];
  61. [appearance setButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f]}];
  62. [appearance setDisabledButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14.0f],
  63. NSForegroundColorAttributeName : [UIColor colorWithWhite:0.6f alpha:1.0] }];
  64. [appearance setDestructiveButtonTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:17.0f],
  65. NSForegroundColorAttributeName : [UIColor redColor] }];
  66. [appearance setTitleTextAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:14.0f],
  67. NSForegroundColorAttributeName : [UIColor grayColor] }];
  68. [appearance setCancelOnPanGestureEnabled:@(YES)];
  69. [appearance setCancelOnTapEmptyAreaEnabled:@(NO)];
  70. [appearance setAnimationDuration:kDefaultAnimationDuration];
  71. }
  72. - (instancetype)initWithTitle:(NSString *)title
  73. {
  74. self = [super init];
  75. if (self) {
  76. _title = [title copy];
  77. _cancelButtonTitle = @"Cancel";
  78. }
  79. return self;
  80. }
  81. - (instancetype)initWithView:(UIView *)view title:(NSString *)title
  82. {
  83. self = [super init];
  84. if (self) {
  85. _title = [title copy];
  86. _cancelButtonTitle = @"Cancel";
  87. _view = view;
  88. }
  89. return self;
  90. }
  91. - (instancetype)init
  92. {
  93. return [self initWithTitle:nil];
  94. }
  95. - (void)dealloc
  96. {
  97. self.tableView.dataSource = nil;
  98. self.tableView.delegate = nil;
  99. }
  100. #pragma mark - UITableViewDataSource
  101. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  102. {
  103. return 1;
  104. }
  105. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  106. {
  107. return (NSInteger)[self.items count];
  108. }
  109. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  110. {
  111. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
  112. //TWS
  113. cell.separatorInset = UIEdgeInsetsMake(0.f, 55.f, 0.f, 0.f);
  114. AHKActionSheetItem *item = self.items[(NSUInteger)indexPath.row];
  115. NSDictionary *attributes = nil;
  116. switch (item.type)
  117. {
  118. case AHKActionSheetButtonTypeDefault:
  119. attributes = self.buttonTextAttributes;
  120. break;
  121. case AHKActionSheetButtonTypeDisabled:
  122. attributes = self.disabledButtonTextAttributes;
  123. cell.selectionStyle = UITableViewCellSelectionStyleNone;
  124. break;
  125. case AHKActionSheetButtonTypeDestructive:
  126. attributes = self.destructiveButtonTextAttributes;
  127. break;
  128. case AHKActionSheetButtonTypeCrypto:
  129. attributes = self.cryptoButtonTextAttributes;
  130. break;
  131. }
  132. NSAttributedString *attrTitle = [[NSAttributedString alloc] initWithString:item.title attributes:attributes];
  133. cell.textLabel.attributedText = attrTitle;
  134. cell.textLabel.textAlignment = [self.buttonTextCenteringEnabled boolValue] ? NSTextAlignmentCenter : NSTextAlignmentLeft;
  135. // Use image with template mode with color the same as the text (when enabled).
  136. BOOL useTemplateMode = [UIImage instancesRespondToSelector:@selector(imageWithRenderingMode:)] && [self.automaticallyTintButtonImages boolValue];
  137. cell.imageView.image = useTemplateMode ? [item.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] : item.image;
  138. if ([UIImageView instancesRespondToSelector:@selector(tintColor)]){
  139. cell.imageView.tintColor = attributes[NSForegroundColorAttributeName] ? attributes[NSForegroundColorAttributeName] : [UIColor blackColor];
  140. }
  141. //TWS
  142. cell.backgroundColor = [UIColor whiteColor];
  143. if (self.selectedBackgroundColor && ![cell.selectedBackgroundView.backgroundColor isEqual:self.selectedBackgroundColor]) {
  144. cell.selectedBackgroundView = [[UIView alloc] init];
  145. cell.selectedBackgroundView.backgroundColor = self.selectedBackgroundColor;
  146. }
  147. return cell;
  148. }
  149. #pragma mark - UITableViewDelegate
  150. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  151. {
  152. AHKActionSheetItem *item = self.items[(NSUInteger)indexPath.row];
  153. if (item.type != AHKActionSheetButtonTypeDisabled) {
  154. [self dismissAnimated:YES duration:self.animationDuration completion:item.handler];
  155. }
  156. }
  157. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  158. {
  159. return self.buttonHeight;
  160. }
  161. //TWS
  162. /*
  163. -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  164. {
  165. // Remove separator inset as described here: http://stackoverflow.com/a/25877725/783960
  166. if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
  167. [cell setSeparatorInset:UIEdgeInsetsZero];
  168. }
  169. // Prevent the cell from inheriting the Table View's margin settings
  170. if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
  171. [cell setPreservesSuperviewLayoutMargins:NO];
  172. }
  173. // Explictly set your cell's layout margins
  174. if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
  175. [cell setLayoutMargins:UIEdgeInsetsZero];
  176. }
  177. }
  178. */
  179. #pragma mark - UIScrollViewDelegate
  180. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  181. {
  182. if (![self.cancelOnPanGestureEnabled boolValue]) {
  183. return;
  184. }
  185. [self fadeBlursOnScrollToTop];
  186. }
  187. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  188. {
  189. if (![self.cancelOnPanGestureEnabled boolValue]) {
  190. return;
  191. }
  192. CGPoint scrollVelocity = [scrollView.panGestureRecognizer velocityInView:self];
  193. BOOL viewWasFlickedDown = scrollVelocity.y > kFlickDownMinVelocity && scrollView.contentOffset.y < -self.tableView.contentInset.top - kFlickDownHandlingOffset;
  194. BOOL shouldSlideDown = scrollView.contentOffset.y < -self.tableView.contentInset.top - kAutoDismissOffset;
  195. if (viewWasFlickedDown) {
  196. // use a shorter duration for a flick down animation
  197. static const NSTimeInterval duration = 0.2f;
  198. [self dismissAnimated:YES duration:duration completion:self.cancelHandler];
  199. } else if (shouldSlideDown) {
  200. [self dismissAnimated:YES duration:self.animationDuration completion:self.cancelHandler];
  201. }
  202. }
  203. #pragma mark - Properties
  204. - (NSMutableArray *)items
  205. {
  206. if (!_items) {
  207. _items = [NSMutableArray array];
  208. }
  209. return _items;
  210. }
  211. #pragma mark - Actions
  212. - (void)cancelButtonTapped:(id)sender
  213. {
  214. [self dismissAnimated:YES duration:self.animationDuration completion:self.cancelHandler];
  215. }
  216. #pragma mark - Public
  217. - (void)addButtonWithTitle:(NSString *)title type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler
  218. {
  219. [self addButtonWithTitle:title image:nil type:type handler:handler];
  220. }
  221. - (void)addButtonWithTitle:(NSString *)title image:(UIImage *)image type:(AHKActionSheetButtonType)type handler:(AHKActionSheetHandler)handler
  222. {
  223. AHKActionSheetItem *item = [[AHKActionSheetItem alloc] init];
  224. item.title = title;
  225. item.image = image;
  226. item.type = type;
  227. item.handler = handler;
  228. [self.items addObject:item];
  229. }
  230. - (void)show
  231. {
  232. NSAssert([self.items count] > 0, @"Please add some buttons before calling -show.");
  233. if ([self isVisible]) {
  234. return;
  235. }
  236. self.previousKeyWindow = [UIApplication sharedApplication].keyWindow;
  237. UIImage *previousKeyWindowSnapshot = [self.previousKeyWindow ahk_snapshot];
  238. [self setUpNewWindow];
  239. [self setUpBlurredBackgroundWithSnapshot:previousKeyWindowSnapshot];
  240. [self setUpCancelButton];
  241. [self setUpTableView];
  242. if (self.cancelOnPanGestureEnabled.boolValue) {
  243. [self setUpCancelTapGestureForView:self.tableView];
  244. }
  245. CGFloat slideDownMinOffset = (CGFloat)fmin(CGRectGetHeight(self.frame) + self.tableView.contentOffset.y, CGRectGetHeight(self.frame));
  246. self.tableView.transform = CGAffineTransformMakeTranslation(0, slideDownMinOffset);
  247. void(^immediateAnimations)(void) = ^(void) {
  248. self.blurredBackgroundView.alpha = 1.0f;
  249. };
  250. void(^delayedAnimations)(void) = ^(void) {
  251. self.cancelButton.frame = CGRectMake(0,
  252. CGRectGetMaxY(self.bounds) - self.cancelButtonHeight,
  253. CGRectGetWidth(_view.bounds),
  254. self.cancelButtonHeight);
  255. //TWS
  256. self.cancelButton.backgroundColor = [UIColor whiteColor];
  257. self.tableView.transform = CGAffineTransformMakeTranslation(0, 0);
  258. // manual calculation of table's contentSize.height
  259. CGFloat tableContentHeight = [self.items count] * self.buttonHeight + CGRectGetHeight(self.tableView.tableHeaderView.frame);
  260. CGFloat topInset;
  261. BOOL buttonsFitInWithoutScrolling = tableContentHeight < CGRectGetHeight(self.tableView.frame) * (1.0 - kTopSpaceMarginFraction);
  262. if (buttonsFitInWithoutScrolling) {
  263. // show all buttons if there isn't many
  264. topInset = CGRectGetHeight(self.tableView.frame) - tableContentHeight;
  265. } else {
  266. // leave an empty space on the top to make the control look similar to UIActionSheet
  267. topInset = (CGFloat)round(CGRectGetHeight(self.tableView.frame) * kTopSpaceMarginFraction);
  268. }
  269. self.tableView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
  270. self.tableView.bounces = [self.cancelOnPanGestureEnabled boolValue] || !buttonsFitInWithoutScrolling;
  271. };
  272. if ([UIView respondsToSelector:@selector(animateKeyframesWithDuration:delay:options:animations:completion:)]){
  273. // Animate sliding in tableView and cancel button with keyframe animation for a nicer effect.
  274. [UIView animateKeyframesWithDuration:self.animationDuration delay:0 options:0 animations:^{
  275. immediateAnimations();
  276. [UIView addKeyframeWithRelativeStartTime:0.3f relativeDuration:0.7f animations:^{
  277. delayedAnimations();
  278. }];
  279. } completion:nil];
  280. } else {
  281. [UIView animateWithDuration:self.animationDuration animations:^{
  282. immediateAnimations();
  283. delayedAnimations();
  284. }];
  285. }
  286. }
  287. - (void)dismissAnimated:(BOOL)animated
  288. {
  289. [self dismissAnimated:animated duration:self.animationDuration completion:self.cancelHandler];
  290. }
  291. #pragma mark - Private
  292. - (BOOL)isVisible
  293. {
  294. // action sheet is visible iff it's associated with a window
  295. return !!self.window;
  296. }
  297. - (void)dismissAnimated:(BOOL)animated duration:(NSTimeInterval)duration completion:(AHKActionSheetHandler)completionHandler
  298. {
  299. if (![self isVisible]) {
  300. return;
  301. }
  302. // delegate isn't needed anymore because tableView will be hidden (and we don't want delegate methods to be called now)
  303. self.tableView.delegate = nil;
  304. self.tableView.userInteractionEnabled = NO;
  305. // keep the table from scrolling back up
  306. self.tableView.contentInset = UIEdgeInsetsMake(-self.tableView.contentOffset.y, 0, 0, 0);
  307. void(^tearDownView)(void) = ^(void) {
  308. // remove the views because it's easiest to just recreate them if the action sheet is shown again
  309. for (UIView *view in @[self.tableView, self.cancelButton, self.blurredBackgroundView, self.window]) {
  310. [view removeFromSuperview];
  311. }
  312. self.window = nil;
  313. [self.previousKeyWindow makeKeyAndVisible];
  314. if (completionHandler) {
  315. completionHandler(self);
  316. }
  317. };
  318. if (animated) {
  319. // animate sliding down tableView and cancelButton.
  320. [UIView animateWithDuration:duration animations:^{
  321. self.blurredBackgroundView.alpha = 0.0f;
  322. self.cancelButton.transform = CGAffineTransformTranslate(self.cancelButton.transform, 0, self.cancelButtonHeight);
  323. self.cancelButtonShadowView.alpha = 0.0f;
  324. // Shortest shift of position sufficient to hide all tableView contents below the bottom margin.
  325. // contentInset isn't used here (unlike in -show) because it caused weird problems with animations in some cases.
  326. CGFloat slideDownMinOffset = (CGFloat)fmin(CGRectGetHeight(self.frame) + self.tableView.contentOffset.y, CGRectGetHeight(self.frame));
  327. self.tableView.transform = CGAffineTransformMakeTranslation(0, slideDownMinOffset);
  328. } completion:^(BOOL finished) {
  329. tearDownView();
  330. }];
  331. } else {
  332. tearDownView();
  333. }
  334. }
  335. - (void)setUpNewWindow
  336. {
  337. AHKActionSheetViewController *actionSheetVC = [[AHKActionSheetViewController alloc] initWithNibName:nil bundle:nil];
  338. actionSheetVC.actionSheet = self;
  339. self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  340. self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  341. self.window.opaque = NO;
  342. self.window.rootViewController = actionSheetVC;
  343. [self.window makeKeyAndVisible];
  344. }
  345. - (void)setUpBlurredBackgroundWithSnapshot:(UIImage *)previousKeyWindowSnapshot
  346. {
  347. UIImage *blurredViewSnapshot = [previousKeyWindowSnapshot
  348. ahk_applyBlurWithRadius:self.blurRadius
  349. tintColor:self.blurTintColor
  350. saturationDeltaFactor:self.blurSaturationDeltaFactor
  351. maskImage:nil];
  352. UIImageView *backgroundView = [[UIImageView alloc] initWithImage:blurredViewSnapshot];
  353. backgroundView.frame = self.bounds;
  354. backgroundView.alpha = 0.0f;
  355. [self addSubview:backgroundView];
  356. self.blurredBackgroundView = backgroundView;
  357. }
  358. - (void)setUpCancelTapGestureForView:(UIView*)view {
  359. UITapGestureRecognizer *cancelTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cancelButtonTapped:)];
  360. cancelTap.delegate = self;
  361. [view addGestureRecognizer:cancelTap];
  362. }
  363. - (void)setUpCancelButton
  364. {
  365. UIButton *cancelButton;
  366. // It's hard to check if UIButtonTypeSystem enumeration exists, so we're checking existence of another method that was introduced in iOS 7.
  367. if ([UIView instancesRespondToSelector:@selector(tintAdjustmentMode)]) {
  368. cancelButton= [UIButton buttonWithType:UIButtonTypeSystem];
  369. } else {
  370. cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
  371. }
  372. NSAttributedString *attrTitle = [[NSAttributedString alloc] initWithString:self.cancelButtonTitle
  373. attributes:self.cancelButtonTextAttributes];
  374. [cancelButton setAttributedTitle:attrTitle forState:UIControlStateNormal];
  375. [cancelButton addTarget:self action:@selector(cancelButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
  376. cancelButton.frame = CGRectMake(0,
  377. CGRectGetMaxY(self.bounds) - self.cancelButtonHeight,
  378. CGRectGetWidth(self.bounds),
  379. self.cancelButtonHeight);
  380. // move the button below the screen (ready to be animated -show)
  381. cancelButton.transform = CGAffineTransformMakeTranslation(0, self.cancelButtonHeight);
  382. cancelButton.clipsToBounds = YES;
  383. [self addSubview:cancelButton];
  384. self.cancelButton = cancelButton;
  385. // add a small shadow/glow above the button
  386. if (self.cancelButtonShadowColor) {
  387. self.cancelButton.clipsToBounds = NO;
  388. CGFloat gradientHeight = (CGFloat)round(self.cancelButtonHeight * kCancelButtonShadowHeightRatio);
  389. UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, -gradientHeight, CGRectGetWidth(self.bounds), gradientHeight)];
  390. CAGradientLayer *gradient = [CAGradientLayer layer];
  391. gradient.frame = view.bounds;
  392. gradient.colors = @[ (id)[UIColor colorWithWhite:0.0 alpha:0.0].CGColor, (id)[self.blurTintColor colorWithAlphaComponent:0.1f].CGColor ];
  393. [view.layer insertSublayer:gradient atIndex:0];
  394. [self.cancelButton addSubview:view];
  395. self.cancelButtonShadowView = view;
  396. }
  397. }
  398. - (void)setUpTableView
  399. {
  400. CGRect statusBarViewRect = [self convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil];
  401. CGFloat statusBarHeight = CGRectGetHeight(statusBarViewRect);
  402. CGRect frame = CGRectMake(0,
  403. statusBarHeight,
  404. CGRectGetWidth(_view.bounds),
  405. CGRectGetHeight(self.bounds) - statusBarHeight - self.cancelButtonHeight);
  406. UITableView *tableView = [[UITableView alloc] initWithFrame:frame];
  407. tableView.backgroundColor = [UIColor clearColor];
  408. tableView.showsVerticalScrollIndicator = NO;
  409. if (self.separatorColor) {
  410. tableView.separatorColor = self.separatorColor;
  411. }
  412. tableView.delegate = self;
  413. tableView.dataSource = self;
  414. [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier];
  415. [self insertSubview:tableView aboveSubview:self.blurredBackgroundView];
  416. // move the content below the screen, ready to be animated in -show
  417. tableView.contentInset = UIEdgeInsetsMake(CGRectGetHeight(self.bounds), 0, 0, 0);
  418. // removes separators below the footer (between empty cells)
  419. tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
  420. self.tableView = tableView;
  421. [self setUpTableViewHeader];
  422. }
  423. - (void)setUpTableViewHeader
  424. {
  425. if (self.title) {
  426. // paddings similar to those in the UITableViewCell
  427. static const CGFloat leftRightPadding = 15.0f;
  428. static const CGFloat topBottomPadding = 8.0f;
  429. CGFloat labelWidth = CGRectGetWidth(self.bounds) - 2*leftRightPadding;
  430. NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:self.title attributes:self.titleTextAttributes];
  431. // create a label and calculate its size
  432. UILabel *label = [[UILabel alloc] init];
  433. label.numberOfLines = 0;
  434. [label setAttributedText:attrText];
  435. CGSize labelSize = [label sizeThatFits:CGSizeMake(labelWidth, MAXFLOAT)];
  436. label.frame = CGRectMake(leftRightPadding, topBottomPadding, labelWidth, labelSize.height);
  437. label.backgroundColor = [UIColor clearColor];
  438. // create and add a header consisting of the label
  439. UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), labelSize.height + 2*topBottomPadding)];
  440. [headerView addSubview:label];
  441. self.tableView.tableHeaderView = headerView;
  442. } else if (self.headerView) {
  443. self.tableView.tableHeaderView = self.headerView;
  444. }
  445. // add a separator between the tableHeaderView and a first row (technically at the bottom of the tableHeaderView)
  446. if (self.tableView.tableHeaderView && self.tableView.separatorStyle != UITableViewCellSeparatorStyleNone) {
  447. CGFloat separatorHeight = 1.0f / [UIScreen mainScreen].scale;
  448. CGRect separatorFrame = CGRectMake(0,
  449. CGRectGetHeight(self.tableView.tableHeaderView.frame) - separatorHeight,
  450. CGRectGetWidth(self.tableView.tableHeaderView.frame),
  451. separatorHeight);
  452. UIView *separator = [[UIView alloc] initWithFrame:separatorFrame];
  453. separator.backgroundColor = self.tableView.separatorColor;
  454. //TWS
  455. UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.tableView.tableHeaderView.frame), 1 / UIScreen.mainScreen.scale)];
  456. line.backgroundColor = self.tableView.separatorColor;
  457. [self.tableView.tableHeaderView addSubview:separator];
  458. [self.tableView.tableHeaderView addSubview:line];
  459. }
  460. }
  461. - (void)fadeBlursOnScrollToTop
  462. {
  463. if (self.tableView.isDragging || self.tableView.isDecelerating) {
  464. CGFloat alphaWithoutBounds = 1.0f - ( -(self.tableView.contentInset.top + self.tableView.contentOffset.y) / kBlurFadeRangeSize);
  465. // limit alpha to the interval [0, 1]
  466. CGFloat alpha = (CGFloat)fmax(fmin(alphaWithoutBounds, 1.0f), 0.0f);
  467. self.blurredBackgroundView.alpha = alpha;
  468. self.cancelButtonShadowView.alpha = alpha;
  469. }
  470. }
  471. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
  472. // If the view that is touched is not the view associated with this view's table view, but
  473. // is one of the sub-views, we should not recognize the touch.
  474. // Original source: http://stackoverflow.com/questions/10755566/how-to-know-uitableview-is-pressed-when-empty
  475. if (touch.view != self.tableView && [touch.view isDescendantOfView:self.tableView]) {
  476. return NO;
  477. }
  478. return YES;
  479. }
  480. @end