STModal.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. //
  2. // STModal.m
  3. // STModalDemo
  4. //
  5. // Created by zhenlintie on 15/6/5.
  6. // Copyright (c) 2015年 sTeven. All rights reserved.
  7. //
  8. #import "STModal.h"
  9. /**
  10. * @discussion 公共窗口类
  11. */
  12. @interface STModalWindow : NSObject
  13. + (instancetype)sharedModalWindow;
  14. - (void)showModal:(STModal *)modal animated:(BOOL)animated duration:(CGFloat)duration completion:(void(^)())comletion;
  15. - (void)hideModal:(STModal *)modal animated:(BOOL)animated duration:(CGFloat)duration completion:(void(^)())comletion;
  16. @end
  17. @interface STModal ()
  18. @property (strong, nonatomic) UIView *containerView;
  19. @property (strong, nonatomic) UIView *backgroundTapView;
  20. @property (strong, nonatomic) UITapGestureRecognizer *tap;
  21. @property (assign, nonatomic) BOOL onShowing;
  22. @property (assign, nonatomic) BOOL onTop;
  23. @end
  24. @implementation STModal
  25. + (instancetype)modal{
  26. return [self new];
  27. }
  28. + (instancetype)modalWithContentView:(UIView *)contentView{
  29. STModal *modal = [self modal];
  30. [modal addModalContentView:contentView];
  31. return modal;;
  32. }
  33. - (instancetype)init{
  34. if (self = [super init]){
  35. _positionMode = STModelPositionCenter;
  36. _position = CGPointMake(CGRectGetWidth([UIScreen mainScreen].bounds)/2.0, CGRectGetHeight([UIScreen mainScreen].bounds)/2.0);
  37. _onShowing = NO;
  38. _onTop = NO;
  39. _hideWhenTouchOutside = NO;
  40. _animatedHideWhenTouchOutside = YES;
  41. _dimBackgroundWhenShow = YES;
  42. }
  43. return self;
  44. }
  45. - (void)addModalContentView:(UIView *)contentView{
  46. _contentView = contentView;
  47. }
  48. #pragma mark - setter / getter
  49. - (void)setPositionMode:(STModelPositionMode)positionMode{
  50. if (_positionMode != positionMode){
  51. _positionMode = positionMode;
  52. [self updateContentViewPosition];
  53. }
  54. }
  55. - (UIView *)containerView{
  56. if (!_containerView){
  57. CGSize size = [UIScreen mainScreen].bounds.size;
  58. _containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
  59. _containerView.backgroundColor = [UIColor clearColor];
  60. _backgroundTapView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
  61. _backgroundTapView.backgroundColor = [UIColor clearColor];
  62. [_containerView addSubview:_backgroundTapView];
  63. _tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped)];
  64. _tap.enabled = _hideWhenTouchOutside;
  65. [_backgroundTapView addGestureRecognizer:_tap];
  66. }
  67. return _containerView;
  68. }
  69. - (st_modal_animation)showAnimation{
  70. if (!_showAnimation){
  71. return ^CGFloat(){
  72. CGFloat d = 0.3;
  73. _containerView.alpha = 0;
  74. [UIView animateWithDuration:d
  75. animations:^{
  76. _containerView.alpha = 1;
  77. }];
  78. return d;
  79. };
  80. }
  81. return _showAnimation;
  82. }
  83. - (st_modal_animation)hideAnimation{
  84. if (!_hideAnimation){
  85. return ^CGFloat(){
  86. CGFloat d = 0.3;
  87. [UIView animateWithDuration:d
  88. animations:^{
  89. _containerView.alpha = 0;
  90. }];
  91. return d;
  92. };
  93. }
  94. return _hideAnimation;
  95. }
  96. - (BOOL)onShow{
  97. return _onShowing;
  98. }
  99. - (STModalWindow *)window{
  100. return [STModalWindow sharedModalWindow];
  101. }
  102. #pragma mark - action
  103. - (void)setHideWhenTouchOutside:(BOOL)hideWhenTouchOutside{
  104. _hideWhenTouchOutside = hideWhenTouchOutside;
  105. _tap.enabled = _hideWhenTouchOutside;
  106. }
  107. - (void)tapped{
  108. if (_hideWhenTouchOutside && !CGRectContainsPoint(_contentView.frame, [_tap locationInView:_containerView])){
  109. [self hide:_animatedHideWhenTouchOutside];
  110. }
  111. }
  112. #pragma mark - for show / hide
  113. - (void)updateContentViewPosition{
  114. switch (_positionMode) {
  115. case STModelPositionCenter:{
  116. _contentView.center = CGPointMake(CGRectGetMidX(_containerView.bounds), CGRectGetMidY(_containerView.bounds));
  117. break;
  118. }
  119. case STModelPositionCenterTop:{
  120. _contentView.center = CGPointMake(CGRectGetMidX(_containerView.bounds), CGRectGetHeight(_contentView.bounds)/2.0);
  121. break;
  122. }
  123. case STModelPositionCenterBottom:{
  124. _contentView.center = CGPointMake(CGRectGetMidX(_containerView.bounds), CGRectGetHeight(_containerView.bounds)-CGRectGetHeight(_contentView.bounds)/2.0);
  125. break;
  126. }
  127. case STModelPositionCustom:{
  128. _contentView.center = self.position;
  129. }
  130. default:
  131. break;
  132. }
  133. }
  134. - (void)prepareUIForShow{
  135. [self.containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
  136. [_containerView addSubview:_backgroundTapView];
  137. [self.containerView addSubview:_contentView];
  138. [self updateContentViewPosition];
  139. }
  140. - (void)showContentView:(UIView *)contentView animated:(BOOL)animated{
  141. NSAssert(contentView!=nil, @"没有可显示的视图");
  142. [self addModalContentView:contentView];
  143. [self show:animated];
  144. }
  145. - (void)show:(BOOL)animated{
  146. NSAssert(_contentView!=nil, @"没有可显示的视图");
  147. [self prepareUIForShow];
  148. [self.window showModal:self animated:animated duration:animated?self.showAnimation():0 completion:self.didShowHandler];
  149. }
  150. - (void)hide:(BOOL)animated{
  151. [self.window hideModal:self animated:animated duration:animated?self.hideAnimation():0 completion:self.didHideHandler];
  152. }
  153. @end
  154. #define STModalWindowDefaultBackgroundColor [UIColor colorWithWhite:0 alpha:0.55]
  155. @interface STModalWindow ()
  156. @property (strong, nonatomic) NSMutableArray *modalsStack;
  157. @property (strong, nonatomic) UIWindow *window;
  158. @property (strong, nonatomic) UIView *dimBackgroundView;
  159. @property (assign, nonatomic) BOOL shouldDimBackground;
  160. @end
  161. @implementation STModalWindow{
  162. BOOL _onShowing;
  163. }
  164. + (instancetype)sharedModalWindow{
  165. static dispatch_once_t onceToken;
  166. static STModalWindow *_sharedModalWindow = nil;
  167. dispatch_once(&onceToken, ^{
  168. _sharedModalWindow = [[self alloc] init];
  169. });
  170. return _sharedModalWindow;
  171. }
  172. - (instancetype)init{
  173. if (self = [super init]){
  174. _modalsStack = [[NSMutableArray alloc] init];
  175. _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  176. _window.windowLevel = UIWindowLevelAlert;
  177. _window.backgroundColor = [UIColor clearColor];
  178. _window.opaque = NO;
  179. _dimBackgroundView = [[UIView alloc] initWithFrame:_window.bounds];
  180. [_window addSubview:_dimBackgroundView];
  181. }
  182. return self;
  183. }
  184. #pragma mark - public modal operation
  185. - (void)showModal:(STModal *)modal animated:(BOOL)animated duration:(CGFloat)duration completion:(void(^)())comletion{
  186. STModal *topModal = [self topModal];
  187. [self pushModal:modal];
  188. [self reloadData];
  189. _window.hidden = NO;
  190. [self transitionFromModal:topModal
  191. toModal:modal
  192. animated:animated
  193. duration:duration
  194. completion:comletion];
  195. }
  196. - (void)hideModal:(STModal *)modal animated:(BOOL)animated duration:(CGFloat)duration completion:(void(^)())comletion{
  197. if ([self hasModal:modal]){
  198. if ([[self topModal] isEqual:modal]){
  199. STModal *toModal = (_modalsStack.count>1)?_modalsStack[_modalsStack.count-2]:nil;
  200. [self transitionFromModal:modal
  201. toModal:toModal
  202. animated:animated
  203. duration:duration
  204. completion:^{
  205. [self popModal:modal];
  206. [modal.containerView removeFromSuperview];
  207. BOOL isNoModal = nil == [self topModal];
  208. if (isNoModal){
  209. _window.hidden = YES;
  210. }
  211. else{
  212. [self reloadData];
  213. }
  214. if (comletion){
  215. comletion();
  216. }
  217. }];
  218. }
  219. else{
  220. [self popModal:modal];
  221. [modal.containerView removeFromSuperview];
  222. }
  223. }
  224. }
  225. #pragma mark - show or hide
  226. - (void)transitionFromModal:(STModal *)fromModal
  227. toModal:(STModal *)toModal
  228. animated:(BOOL)animated
  229. duration:(CGFloat)duration
  230. completion:(void(^)())completion{
  231. CGFloat fromA = 0, toA = 0;
  232. UIColor *fromColor = nil, *toColor = nil;
  233. BOOL(^colosIsEqual)(UIColor *, UIColor *) = ^BOOL(UIColor *color1, UIColor *color2){
  234. if (color1 && color2){
  235. CGFloat r1, r2, g1, g2, b1, b2, a1, a2;
  236. [color1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
  237. [color2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
  238. return (r1==r2)&&(g1==g2)&&(b1==b2)&&(a1==a2);
  239. }
  240. else if (!color1 && !color2){
  241. return YES;
  242. }
  243. return NO;
  244. };
  245. if (nil != fromModal){
  246. fromA = fromModal.dimBackgroundWhenShow?1:0;
  247. fromColor = fromModal.dimBackgroundColor?:STModalWindowDefaultBackgroundColor;
  248. }
  249. if (nil != toModal){
  250. toA = toModal.dimBackgroundWhenShow?1:0;
  251. toColor = toModal.dimBackgroundColor?:STModalWindowDefaultBackgroundColor;
  252. }
  253. _dimBackgroundView.alpha = fromA;
  254. _dimBackgroundView.backgroundColor = fromColor;
  255. if (fromA == toA && colosIsEqual(fromColor, toColor)){
  256. // 如果没有任何变化UIView动画会立即结束,采用以下方法进行回调
  257. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  258. if (completion){
  259. completion();
  260. }
  261. });
  262. }
  263. else{
  264. [UIView animateWithDuration:animated?duration:0
  265. animations:^{
  266. _dimBackgroundView.alpha = toA;
  267. _dimBackgroundView.backgroundColor = toColor;
  268. }
  269. completion:^(BOOL finished) {
  270. if (completion){
  271. completion();
  272. }
  273. }];
  274. }
  275. }
  276. - (void)reloadData{
  277. STModal *topModal = [self topModal];
  278. if (topModal){
  279. self.shouldDimBackground = topModal.dimBackgroundWhenShow;
  280. _dimBackgroundView.backgroundColor = topModal.dimBackgroundColor?:STModalWindowDefaultBackgroundColor;
  281. [topModal.containerView removeFromSuperview];
  282. [_window addSubview:topModal.containerView];
  283. [[_modalsStack valueForKey:@"containerView"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  284. [(UIView *)obj setHidden:(obj!=topModal.containerView)];
  285. }];
  286. }
  287. }
  288. - (void)setShouldDimBackground:(BOOL)shouldDimBackground{
  289. _shouldDimBackground = shouldDimBackground;
  290. _dimBackgroundView.alpha = _shouldDimBackground?1:0;
  291. }
  292. #pragma mark - stack operation
  293. - (void)pushModal:(STModal *)modal{
  294. if ([self hasModal:modal]){
  295. [self popModal:modal];
  296. }
  297. [_modalsStack addObject:modal];
  298. }
  299. - (STModal *)popModal:(STModal *)modal{
  300. if ([self hasModal:modal]){
  301. [_modalsStack removeObject:modal];
  302. }
  303. return [self topModal];
  304. }
  305. - (STModal *)topModal{
  306. if (_modalsStack.count > 0){
  307. return [_modalsStack lastObject];
  308. }
  309. return nil;
  310. }
  311. - (BOOL)hasModal:(STModal *)modal{
  312. return (nil!=modal)&&([_modalsStack indexOfObject:modal] != NSNotFound);
  313. }
  314. @end