UIViewController+KNSemiModal.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. //
  2. // KNSemiModalViewController.m
  3. // KNSemiModalViewController
  4. //
  5. // Created by Kent Nguyen on 2/5/12.
  6. // Copyright (c) 2012 Kent Nguyen. All rights reserved.
  7. //
  8. #import "UIViewController+KNSemiModal.h"
  9. #import <QuartzCore/QuartzCore.h>
  10. #import <objc/runtime.h>
  11. const struct KNSemiModalOption KNSemiModalOptionKeys = {
  12. .traverseParentHierarchy = @"KNSemiModalOptionTraverseParentHierarchy",
  13. .pushParentBack = @"KNSemiModalOptionPushParentBack",
  14. .animationDuration = @"KNSemiModalOptionAnimationDuration",
  15. .parentAlpha = @"KNSemiModalOptionParentAlpha",
  16. .parentScale = @"KNSemiModalOptionParentScale",
  17. .shadowOpacity = @"KNSemiModalOptionShadowOpacity",
  18. .transitionStyle = @"KNSemiModalTransitionStyle",
  19. .disableCancel = @"KNSemiModalOptionDisableCancel",
  20. .backgroundView = @"KNSemiModelOptionBackgroundView",
  21. .disableRotation = @"KNSemiModalOptionDisableRotation",
  22. };
  23. #define kSemiModalViewController @"PaPQC93kjgzUanz"
  24. #define kSemiModalDismissBlock @"l27h7RU2dzVfPoQ"
  25. #define kSemiModalPresentingViewController @"QKWuTQjUkWaO1Xr"
  26. #define kSemiModalOverlayTag 10001
  27. #define kSemiModalScreenshotTag 10002
  28. #define kSemiModalModalViewTag 10003
  29. #define kSemiModalDismissButtonTag 10004
  30. @interface UIViewController (KNSemiModalInternal)
  31. -(UIView*)parentTarget;
  32. -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward;
  33. @end
  34. @implementation UIViewController (KNSemiModalInternal)
  35. -(UIViewController*)kn_parentTargetViewController {
  36. UIViewController * target = self;
  37. // cover UINav & UITabbar as well
  38. while (target.parentViewController != nil) {
  39. target = target.parentViewController;
  40. }
  41. if ([[target ym_optionOrDefaultForKey:KNSemiModalOptionKeys.traverseParentHierarchy] boolValue]) {
  42. return target;
  43. }
  44. return self;
  45. }
  46. -(UIView*)parentTarget {
  47. return [self kn_parentTargetViewController].view;
  48. }
  49. - (UIViewController*)kn_targetToStoreValues {
  50. UIViewController * target = self;
  51. // cover UINav & UITabbar as well
  52. while (target.parentViewController != nil) {
  53. target = target.parentViewController;
  54. }
  55. return target;
  56. }
  57. #pragma mark Options and defaults
  58. -(void)kn_registerDefaultsAndOptions:(NSDictionary*)options {
  59. [[self kn_targetToStoreValues] ym_registerOptions:options defaults:@{
  60. KNSemiModalOptionKeys.traverseParentHierarchy : @(YES),
  61. KNSemiModalOptionKeys.pushParentBack : @(YES),
  62. KNSemiModalOptionKeys.animationDuration : @(0.5),
  63. KNSemiModalOptionKeys.parentAlpha : @(0.5),
  64. KNSemiModalOptionKeys.parentScale : @(0.8),
  65. KNSemiModalOptionKeys.shadowOpacity : @(0.8),
  66. KNSemiModalOptionKeys.transitionStyle : @(KNSemiModalTransitionStyleSlideUp),
  67. KNSemiModalOptionKeys.disableCancel : @(NO),
  68. KNSemiModalOptionKeys.disableRotation : @(NO),
  69. }];
  70. }
  71. #pragma mark Push-back animation group
  72. -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward {
  73. // Create animation keys, forwards and backwards
  74. CATransform3D t1 = CATransform3DIdentity;
  75. t1.m34 = 1.0/-900;
  76. t1 = CATransform3DScale(t1, 0.95, 0.95, 1);
  77. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
  78. // The rotation angle is minor as the view is nearer
  79. t1 = CATransform3DRotate(t1, 7.5f*M_PI/180.0f, 1, 0, 0);
  80. } else {
  81. t1 = CATransform3DRotate(t1, 15.0f*M_PI/180.0f, 1, 0, 0);
  82. }
  83. CATransform3D t2 = CATransform3DIdentity;
  84. t2.m34 = t1.m34;
  85. double scale = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.parentScale] doubleValue];
  86. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
  87. // Minor shift to mantai perspective
  88. t2 = CATransform3DTranslate(t2, 0, [self parentTarget].frame.size.height*-0.04, 0);
  89. t2 = CATransform3DScale(t2, scale, scale, 1);
  90. } else {
  91. t2 = CATransform3DTranslate(t2, 0, [self parentTarget].frame.size.height*-0.08, 0);
  92. t2 = CATransform3DScale(t2, scale, scale, 1);
  93. }
  94. CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
  95. animation.toValue = [NSValue valueWithCATransform3D:t1];
  96. CFTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
  97. animation.duration = duration/2;
  98. animation.fillMode = kCAFillModeForwards;
  99. animation.removedOnCompletion = NO;
  100. [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
  101. CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
  102. animation2.toValue = [NSValue valueWithCATransform3D:(_forward?t2:CATransform3DIdentity)];
  103. animation2.beginTime = animation.duration;
  104. animation2.duration = animation.duration;
  105. animation2.fillMode = kCAFillModeForwards;
  106. animation2.removedOnCompletion = NO;
  107. CAAnimationGroup *group = [CAAnimationGroup animation];
  108. group.fillMode = kCAFillModeForwards;
  109. group.removedOnCompletion = NO;
  110. [group setDuration:animation.duration*2];
  111. [group setAnimations:[NSArray arrayWithObjects:animation,animation2, nil]];
  112. return group;
  113. }
  114. -(void)kn_interfaceOrientationDidChange:(NSNotification*)notification {
  115. if(![[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.disableRotation] boolValue]) {
  116. UIView *overlay = [[self parentTarget] viewWithTag:kSemiModalOverlayTag];
  117. [self kn_addOrUpdateParentScreenshotInView:overlay];
  118. }
  119. }
  120. -(UIImageView*)kn_addOrUpdateParentScreenshotInView:(UIView*)screenshotContainer {
  121. UIView *target = [self parentTarget];
  122. UIView *semiView = [target viewWithTag:kSemiModalModalViewTag];
  123. screenshotContainer.hidden = YES; // screenshot without the overlay!
  124. semiView.hidden = YES;
  125. UIGraphicsBeginImageContextWithOptions(target.bounds.size, YES, [[UIScreen mainScreen] scale]);
  126. if ([target respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
  127. [target drawViewHierarchyInRect:target.bounds afterScreenUpdates:YES];
  128. } else {
  129. [target.layer renderInContext:UIGraphicsGetCurrentContext()];
  130. }
  131. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  132. UIGraphicsEndImageContext();
  133. screenshotContainer.hidden = NO;
  134. semiView.hidden = NO;
  135. UIImageView* screenshot = (id) [screenshotContainer viewWithTag:kSemiModalScreenshotTag];
  136. if (screenshot) {
  137. screenshot.image = image;
  138. }
  139. else {
  140. screenshot = [[UIImageView alloc] initWithImage:image];
  141. screenshot.tag = kSemiModalScreenshotTag;
  142. screenshot.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  143. [screenshotContainer addSubview:screenshot];
  144. }
  145. return screenshot;
  146. }
  147. @end
  148. @implementation UIViewController (KNSemiModal)
  149. -(void)presentSemiViewController:(UIViewController*)vc {
  150. [self presentSemiViewController:vc withOptions:nil completion:nil dismissBlock:nil];
  151. }
  152. -(void)presentSemiViewController:(UIViewController*)vc
  153. withOptions:(NSDictionary*)options {
  154. [self presentSemiViewController:vc withOptions:options completion:nil dismissBlock:nil];
  155. }
  156. -(void)presentSemiViewController:(UIViewController*)vc
  157. withOptions:(NSDictionary*)options
  158. completion:(KNTransitionCompletionBlock)completion
  159. dismissBlock:(KNTransitionCompletionBlock)dismissBlock {
  160. [self kn_registerDefaultsAndOptions:options]; // re-registering is OK
  161. UIViewController *targetParentVC = [self kn_parentTargetViewController];
  162. // implement view controller containment for the semi-modal view controller
  163. [targetParentVC addChildViewController:vc];
  164. if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) {
  165. [vc beginAppearanceTransition:YES animated:YES]; // iOS 6
  166. }
  167. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController, vc, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  168. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock, dismissBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
  169. [self presentSemiView:vc.view withOptions:options completion:^{
  170. [vc didMoveToParentViewController:targetParentVC];
  171. if ([vc respondsToSelector:@selector(endAppearanceTransition)]) {
  172. [vc endAppearanceTransition]; // iOS 6
  173. }
  174. if (completion) {
  175. completion();
  176. }
  177. }];
  178. }
  179. -(void)presentSemiView:(UIView*)view {
  180. [self presentSemiView:view withOptions:nil completion:nil];
  181. }
  182. -(void)presentSemiView:(UIView*)view withOptions:(NSDictionary*)options {
  183. [self presentSemiView:view withOptions:options completion:nil];
  184. }
  185. -(void)presentSemiView:(UIView*)view
  186. withOptions:(NSDictionary*)options
  187. completion:(KNTransitionCompletionBlock)completion {
  188. [self kn_registerDefaultsAndOptions:options]; // re-registering is OK
  189. UIView * target = [self parentTarget];
  190. if (![target.subviews containsObject:view]) {
  191. // Set associative object
  192. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  193. // Register for orientation changes, so we can update the presenting controller screenshot
  194. [[NSNotificationCenter defaultCenter] addObserver:self
  195. selector:@selector(kn_interfaceOrientationDidChange:)
  196. name:UIDeviceOrientationDidChangeNotification
  197. object:nil];
  198. // Get transition style
  199. NSUInteger transitionStyle = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue];
  200. // Calulate all frames
  201. CGFloat semiViewHeight = view.frame.size.height;
  202. CGRect vf = target.bounds;
  203. CGRect semiViewFrame;
  204. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
  205. // We center the view and mantain aspect ration
  206. semiViewFrame = CGRectMake((vf.size.width - view.frame.size.width) / 2.0, vf.size.height-semiViewHeight, view.frame.size.width, semiViewHeight);
  207. } else {
  208. semiViewFrame = CGRectMake(0, vf.size.height-semiViewHeight, vf.size.width, semiViewHeight);
  209. }
  210. CGRect overlayFrame = CGRectMake(0, 0, vf.size.width, vf.size.height-semiViewHeight);
  211. // Add semi overlay
  212. UIView *overlay;
  213. UIView *backgroundView = [[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.backgroundView];
  214. if (backgroundView) {
  215. overlay = backgroundView;
  216. } else {
  217. overlay = [[UIView alloc] init];
  218. }
  219. overlay.frame = target.bounds;
  220. overlay.backgroundColor = [UIColor blackColor];
  221. overlay.userInteractionEnabled = YES;
  222. overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  223. overlay.tag = kSemiModalOverlayTag;
  224. // Take screenshot and scale
  225. UIImageView *ss = [self kn_addOrUpdateParentScreenshotInView:overlay];
  226. [target addSubview:overlay];
  227. // Dismiss button (if allow)
  228. if(![[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.disableCancel] boolValue]) {
  229. // Don't use UITapGestureRecognizer to avoid complex handling
  230. UIButton * dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
  231. [dismissButton addTarget:self action:@selector(dismissSemiModalView) forControlEvents:UIControlEventTouchUpInside];
  232. dismissButton.backgroundColor = [UIColor clearColor];
  233. dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  234. dismissButton.frame = overlayFrame;
  235. dismissButton.tag = kSemiModalDismissButtonTag;
  236. [overlay addSubview:dismissButton];
  237. }
  238. // Begin overlay animation
  239. if ([[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) {
  240. [ss.layer addAnimation:[self animationGroupForward:YES] forKey:@"pushedBackAnimation"];
  241. }
  242. NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
  243. [UIView animateWithDuration:duration animations:^{
  244. ss.alpha = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.parentAlpha] floatValue];
  245. }];
  246. // Present view animated
  247. view.frame = (transitionStyle == KNSemiModalTransitionStyleSlideUp
  248. ? CGRectOffset(semiViewFrame, 0, +semiViewHeight)
  249. : semiViewFrame);
  250. if (transitionStyle == KNSemiModalTransitionStyleFadeIn || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
  251. view.alpha = 0.0;
  252. }
  253. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
  254. // Don't resize the view width on rotating
  255. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
  256. } else {
  257. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
  258. }
  259. view.tag = kSemiModalModalViewTag;
  260. [target addSubview:view];
  261. view.layer.shadowColor = [[UIColor blackColor] CGColor];
  262. view.layer.shadowOffset = CGSizeMake(0, -2);
  263. view.layer.shadowRadius = 5.0;
  264. view.layer.shadowOpacity = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.shadowOpacity] floatValue];
  265. view.layer.shouldRasterize = YES;
  266. view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
  267. [UIView animateWithDuration:duration animations:^{
  268. if (transitionStyle == KNSemiModalTransitionStyleSlideUp) {
  269. view.frame = semiViewFrame;
  270. } else if (transitionStyle == KNSemiModalTransitionStyleFadeIn || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
  271. view.alpha = 1.0;
  272. }
  273. } completion:^(BOOL finished) {
  274. if (!finished) return;
  275. [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalDidShowNotification
  276. object:self];
  277. if (completion) {
  278. completion();
  279. }
  280. }];
  281. }
  282. }
  283. -(void)updateBackground{
  284. UIView * target = [self parentTarget];
  285. UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
  286. [self kn_addOrUpdateParentScreenshotInView:overlay];
  287. }
  288. -(void)dismissSemiModalView {
  289. [self dismissSemiModalViewWithCompletion:nil];
  290. }
  291. -(void)dismissSemiModalViewWithCompletion:(void (^)(void))completion {
  292. // Look for presenting controller if available
  293. UIViewController * presentingController = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController);
  294. if (presentingController) {
  295. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  296. [presentingController dismissSemiModalViewWithCompletion:completion];
  297. return;
  298. }
  299. // Correct target for dismissal
  300. UIView * target = [self parentTarget];
  301. UIView * modal = [target viewWithTag:kSemiModalModalViewTag];
  302. UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
  303. NSUInteger transitionStyle = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue];
  304. NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
  305. UIViewController *vc = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController);
  306. KNTransitionCompletionBlock dismissBlock = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock);
  307. // Child controller containment
  308. [vc willMoveToParentViewController:nil];
  309. if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) {
  310. [vc beginAppearanceTransition:NO animated:YES]; // iOS 6
  311. }
  312. [UIView animateWithDuration:duration animations:^{
  313. if (transitionStyle == KNSemiModalTransitionStyleSlideUp) {
  314. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
  315. // As the view is centered, we perform a vertical translation
  316. modal.frame = CGRectMake((target.bounds.size.width - modal.frame.size.width) / 2.0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
  317. } else {
  318. modal.frame = CGRectMake(0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
  319. }
  320. } else if (transitionStyle == KNSemiModalTransitionStyleFadeOut || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
  321. modal.alpha = 0.0;
  322. }
  323. } completion:^(BOOL finished) {
  324. [overlay removeFromSuperview];
  325. [modal removeFromSuperview];
  326. // Child controller containment
  327. [vc removeFromParentViewController];
  328. if ([vc respondsToSelector:@selector(endAppearanceTransition)]) {
  329. [vc endAppearanceTransition];
  330. }
  331. if (dismissBlock) {
  332. dismissBlock();
  333. }
  334. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
  335. objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  336. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
  337. }];
  338. // Begin overlay animation
  339. UIImageView * ss = (UIImageView*)[overlay.subviews objectAtIndex:0];
  340. if ([[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) {
  341. [ss.layer addAnimation:[self animationGroupForward:NO] forKey:@"bringForwardAnimation"];
  342. }
  343. [UIView animateWithDuration:duration animations:^{
  344. ss.alpha = 1;
  345. } completion:^(BOOL finished) {
  346. if(finished){
  347. [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalDidHideNotification
  348. object:self];
  349. if (completion) {
  350. completion();
  351. }
  352. }
  353. }];
  354. }
  355. - (void)resizeSemiView:(CGSize)newSize {
  356. UIView * target = [self parentTarget];
  357. UIView * modal = [target viewWithTag:kSemiModalModalViewTag];
  358. CGRect mf = modal.frame;
  359. mf.size.width = newSize.width;
  360. mf.size.height = newSize.height;
  361. mf.origin.y = target.frame.size.height - mf.size.height;
  362. UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
  363. UIButton * button = (UIButton*)[overlay viewWithTag:kSemiModalDismissButtonTag];
  364. CGRect bf = button.frame;
  365. bf.size.height = overlay.frame.size.height - newSize.height;
  366. NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
  367. [UIView animateWithDuration:duration animations:^{
  368. modal.frame = mf;
  369. button.frame = bf;
  370. } completion:^(BOOL finished) {
  371. if(finished){
  372. [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalWasResizedNotification
  373. object:self];
  374. }
  375. }];
  376. }
  377. @end
  378. #pragma mark - NSObject (YMOptionsAndDefaults)
  379. // NSObject+YMOptionsAndDefaults
  380. // Created by YangMeyer on 08.10.12.
  381. // Copyright (c) 2012 Yang Meyer. All rights reserved.
  382. #import <objc/runtime.h>
  383. @implementation NSObject (YMOptionsAndDefaults)
  384. static char const * const kYMStandardOptionsTableName = "YMStandardOptionsTableName";
  385. static char const * const kYMStandardDefaultsTableName = "YMStandardDefaultsTableName";
  386. - (void)ym_registerOptions:(NSDictionary *)options
  387. defaults:(NSDictionary *)defaults
  388. {
  389. objc_setAssociatedObject(self, kYMStandardOptionsTableName, options, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  390. objc_setAssociatedObject(self, kYMStandardDefaultsTableName, defaults, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  391. }
  392. - (id)ym_optionOrDefaultForKey:(NSString*)optionKey
  393. {
  394. NSDictionary *options = objc_getAssociatedObject(self, kYMStandardOptionsTableName);
  395. NSDictionary *defaults = objc_getAssociatedObject(self, kYMStandardDefaultsTableName);
  396. NSAssert(defaults, @"Defaults must have been set when accessing options.");
  397. return options[optionKey] ?: defaults[optionKey];
  398. }
  399. @end
  400. #pragma mark - UIView (FindUIViewController)
  401. // Convenient category method to find actual ViewController that contains a view
  402. // Adapted from: http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone
  403. @implementation UIView (FindUIViewController)
  404. - (UIViewController *) containingViewController {
  405. UIView * target = self.superview ? self.superview : self;
  406. return (UIViewController *)[target traverseResponderChainForUIViewController];
  407. }
  408. - (id) traverseResponderChainForUIViewController {
  409. id nextResponder = [self nextResponder];
  410. BOOL isViewController = [nextResponder isKindOfClass:[UIViewController class]];
  411. BOOL isTabBarController = [nextResponder isKindOfClass:[UITabBarController class]];
  412. if (isViewController && !isTabBarController) {
  413. return nextResponder;
  414. } else if(isTabBarController){
  415. UITabBarController *tabBarController = nextResponder;
  416. return [tabBarController selectedViewController];
  417. } else if ([nextResponder isKindOfClass:[UIView class]]) {
  418. return [nextResponder traverseResponderChainForUIViewController];
  419. } else {
  420. return nil;
  421. }
  422. }
  423. @end