WYPopoverController.m 105 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949
  1. /*
  2. Version 0.3.9
  3. WYPopoverController is available under the MIT license.
  4. Copyright © 2013 Nicolas CHENG
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included
  12. in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  14. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  15. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  16. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  17. OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  18. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19. */
  20. #import "WYPopoverController.h"
  21. #import <objc/runtime.h>
  22. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
  23. #define WY_BASE_SDK_7_ENABLED
  24. #endif
  25. #ifdef DEBUG
  26. #define WY_LOG(fmt, ...) NSLog((@"%s (%d) : " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
  27. #else
  28. #define WY_LOG(...)
  29. #endif
  30. #define WY_IS_IOS_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
  31. #define WY_IS_IOS_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
  32. #define WY_IS_IOS_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
  33. #define WY_IS_IOS_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
  34. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  35. @interface WYKeyboardListener : NSObject
  36. + (BOOL)isVisible;
  37. + (CGRect)rect;
  38. @end
  39. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  40. @implementation WYKeyboardListener
  41. static BOOL _isVisible;
  42. static CGRect _keyboardRect;
  43. + (void)load {
  44. @autoreleasepool {
  45. _keyboardRect = CGRectZero;
  46. _isVisible = NO;
  47. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
  48. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
  49. }
  50. }
  51. + (void)keyboardWillShow:(NSNotification *)notification {
  52. NSDictionary *info = [notification userInfo];
  53. _keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  54. _isVisible = YES;
  55. }
  56. + (void)keyboardWillHide {
  57. _keyboardRect = CGRectZero;
  58. _isVisible = NO;
  59. }
  60. + (BOOL)isVisible {
  61. return _isVisible;
  62. }
  63. + (CGRect)rect {
  64. return _keyboardRect;
  65. }
  66. @end
  67. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  68. @interface UIColor (WYPopover)
  69. - (BOOL)wy_getValueOfRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)apha;
  70. - (NSString *)wy_hexString;
  71. - (UIColor *)wy_colorByLighten:(float)d;
  72. - (UIColor *)wy_colorByDarken:(float)d;
  73. @end
  74. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  75. @implementation UIColor (WYPopover)
  76. - (BOOL)wy_getValueOfRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
  77. // model: kCGColorSpaceModelRGB, num_comps: 4
  78. // model: kCGColorSpaceModelMonochrome, num_comps: 2
  79. CGColorSpaceRef colorSpace = CGColorSpaceRetain(CGColorGetColorSpace([self CGColor]));
  80. CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
  81. CGColorSpaceRelease(colorSpace);
  82. CGFloat rFloat = 0.0, gFloat = 0.0, bFloat = 0.0, aFloat = 0.0;
  83. BOOL result = NO;
  84. if (colorSpaceModel == kCGColorSpaceModelRGB) {
  85. result = [self getRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
  86. } else if (colorSpaceModel == kCGColorSpaceModelMonochrome) {
  87. result = [self getWhite:&rFloat alpha:&aFloat];
  88. gFloat = rFloat;
  89. bFloat = rFloat;
  90. }
  91. if (red) *red = rFloat;
  92. if (green) *green = gFloat;
  93. if (blue) *blue = bFloat;
  94. if (alpha) *alpha = aFloat;
  95. return result;
  96. }
  97. - (NSString *)wy_hexString {
  98. CGFloat rFloat, gFloat, bFloat, aFloat;
  99. int r, g, b, a;
  100. [self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
  101. r = (int)(255.0 * rFloat);
  102. g = (int)(255.0 * gFloat);
  103. b = (int)(255.0 * bFloat);
  104. a = (int)(255.0 * aFloat);
  105. return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a];
  106. }
  107. - (UIColor *)wy_colorByLighten:(float)d {
  108. CGFloat rFloat, gFloat, bFloat, aFloat;
  109. [self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
  110. return [UIColor colorWithRed:MIN(rFloat + d, 1.0)
  111. green:MIN(gFloat + d, 1.0)
  112. blue:MIN(bFloat + d, 1.0)
  113. alpha:1.0];
  114. }
  115. - (UIColor *)wy_colorByDarken:(float)d {
  116. CGFloat rFloat, gFloat, bFloat, aFloat;
  117. [self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
  118. return [UIColor colorWithRed:MAX(rFloat - d, 0.0)
  119. green:MAX(gFloat - d, 0.0)
  120. blue:MAX(bFloat - d, 0.0)
  121. alpha:1.0];
  122. }
  123. @end
  124. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  125. @interface UINavigationController (WYPopover)
  126. @property(nonatomic, assign, getter = wy_isEmbedInPopover) BOOL wy_embedInPopover;
  127. @end
  128. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  129. @implementation UINavigationController (WYPopover)
  130. static char const * const UINavigationControllerEmbedInPopoverTagKey = "UINavigationControllerEmbedInPopoverTagKey";
  131. @dynamic wy_embedInPopover;
  132. + (void)load {
  133. Method original, swizzle;
  134. original = class_getInstanceMethod(self, @selector(pushViewController:animated:));
  135. swizzle = class_getInstanceMethod(self, @selector(sizzled_pushViewController:animated:));
  136. method_exchangeImplementations(original, swizzle);
  137. original = class_getInstanceMethod(self, @selector(setViewControllers:animated:));
  138. swizzle = class_getInstanceMethod(self, @selector(sizzled_setViewControllers:animated:));
  139. method_exchangeImplementations(original, swizzle);
  140. }
  141. - (BOOL)wy_isEmbedInPopover {
  142. BOOL result = NO;
  143. NSNumber *value = objc_getAssociatedObject(self, UINavigationControllerEmbedInPopoverTagKey);
  144. if (value) {
  145. result = [value boolValue];
  146. }
  147. return result;
  148. }
  149. - (void)setWy_embedInPopover:(BOOL)value
  150. {
  151. objc_setAssociatedObject(self, UINavigationControllerEmbedInPopoverTagKey, [NSNumber numberWithBool:value], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  152. }
  153. - (CGSize)contentSize:(UIViewController *)aViewController {
  154. CGSize result = CGSizeZero;
  155. #pragma clang diagnostic push
  156. #pragma GCC diagnostic ignored "-Wdeprecated"
  157. if ([aViewController respondsToSelector:@selector(contentSizeForViewInPopover)]) {
  158. result = aViewController.contentSizeForViewInPopover;
  159. }
  160. #pragma clang diagnostic pop
  161. #ifdef WY_BASE_SDK_7_ENABLED
  162. if ([aViewController respondsToSelector:@selector(preferredContentSize)]) {
  163. result = aViewController.preferredContentSize;
  164. }
  165. #endif
  166. return result;
  167. }
  168. - (void)setContentSize:(CGSize)aContentSize {
  169. #pragma clang diagnostic push
  170. #pragma GCC diagnostic ignored "-Wdeprecated"
  171. [self setContentSizeForViewInPopover:aContentSize];
  172. #pragma clang diagnostic pop
  173. #ifdef WY_BASE_SDK_7_ENABLED
  174. if ([self respondsToSelector:@selector(setPreferredContentSize:)]) {
  175. [self setPreferredContentSize:aContentSize];
  176. }
  177. #endif
  178. }
  179. - (void)sizzled_pushViewController:(UIViewController *)aViewController animated:(BOOL)aAnimated {
  180. if (self.wy_isEmbedInPopover) {
  181. #ifdef WY_BASE_SDK_7_ENABLED
  182. if ([aViewController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
  183. aViewController.edgesForExtendedLayout = UIRectEdgeNone;
  184. }
  185. #endif
  186. CGSize contentSize = [self contentSize:aViewController];
  187. [self setContentSize:contentSize];
  188. }
  189. [self sizzled_pushViewController:aViewController animated:aAnimated];
  190. if (self.wy_isEmbedInPopover) {
  191. CGSize contentSize = [self contentSize:aViewController];
  192. [self setContentSize:contentSize];
  193. }
  194. }
  195. - (void)sizzled_setViewControllers:(NSArray *)aViewControllers animated:(BOOL)aAnimated {
  196. NSUInteger count = [aViewControllers count];
  197. #ifdef WY_BASE_SDK_7_ENABLED
  198. if (self.wy_isEmbedInPopover && count > 0) {
  199. for (UIViewController *viewController in aViewControllers) {
  200. if ([viewController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
  201. viewController.edgesForExtendedLayout = UIRectEdgeNone;
  202. }
  203. }
  204. }
  205. #endif
  206. [self sizzled_setViewControllers:aViewControllers animated:aAnimated];
  207. if (self.wy_isEmbedInPopover && count > 0) {
  208. UIViewController *topViewController = [aViewControllers objectAtIndex:(count - 1)];
  209. CGSize contentSize = [self contentSize:topViewController];
  210. [self setContentSize:contentSize];
  211. }
  212. }
  213. @end
  214. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  215. @interface UIViewController (WYPopover)
  216. @end
  217. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  218. @implementation UIViewController (WYPopover)
  219. + (void)load {
  220. Method original, swizzle;
  221. #pragma clang diagnostic push
  222. #pragma GCC diagnostic ignored "-Wdeprecated"
  223. original = class_getInstanceMethod(self, @selector(setContentSizeForViewInPopover:));
  224. swizzle = class_getInstanceMethod(self, @selector(sizzled_setContentSizeForViewInPopover:));
  225. method_exchangeImplementations(original, swizzle);
  226. #pragma clang diagnostic pop
  227. #ifdef WY_BASE_SDK_7_ENABLED
  228. original = class_getInstanceMethod(self, @selector(setPreferredContentSize:));
  229. swizzle = class_getInstanceMethod(self, @selector(sizzled_setPreferredContentSize:));
  230. if (original != NULL) {
  231. method_exchangeImplementations(original, swizzle);
  232. }
  233. #endif
  234. }
  235. - (void)sizzled_setContentSizeForViewInPopover:(CGSize)aSize {
  236. [self sizzled_setContentSizeForViewInPopover:aSize];
  237. if ([self isKindOfClass:[UINavigationController class]] == NO && self.navigationController != nil) {
  238. #pragma clang diagnostic push
  239. #pragma GCC diagnostic ignored "-Wdeprecated"
  240. [self.navigationController setContentSizeForViewInPopover:aSize];
  241. #pragma clang diagnostic pop
  242. }
  243. }
  244. - (void)sizzled_setPreferredContentSize:(CGSize)aSize {
  245. [self sizzled_setPreferredContentSize:aSize];
  246. if ([self isKindOfClass:[UINavigationController class]] == NO && self.navigationController != nil)
  247. {
  248. #ifdef WY_BASE_SDK_7_ENABLED
  249. if ([self.navigationController wy_isEmbedInPopover] == NO) {
  250. return;
  251. } else if ([self respondsToSelector:@selector(setPreferredContentSize:)]) {
  252. [self.navigationController setPreferredContentSize:aSize];
  253. }
  254. #endif
  255. }
  256. }
  257. @end
  258. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  259. @interface WYPopoverArea : NSObject
  260. @property (nonatomic, assign) WYPopoverArrowDirection arrowDirection;
  261. @property (nonatomic, assign) CGSize areaSize;
  262. @property (nonatomic, assign, readonly) float value;
  263. @end
  264. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  265. #pragma mark - WYPopoverArea
  266. @implementation WYPopoverArea
  267. - (NSString*)description {
  268. const NSDictionary *directionMap = @{@(WYPopoverArrowDirectionUp) : @"UP",
  269. @(WYPopoverArrowDirectionDown) : @"DOWN",
  270. @(WYPopoverArrowDirectionLeft) : @"LEFT",
  271. @(WYPopoverArrowDirectionRight) : @"RIGHT",
  272. @(WYPopoverArrowDirectionNone) : @"NONE"};
  273. NSString *direction = directionMap[@(_arrowDirection)];
  274. return [NSString stringWithFormat:@"%@ [ %f x %f ]", direction, _areaSize.width, _areaSize.height];
  275. }
  276. - (float)value {
  277. float result = 0;
  278. if (_areaSize.width > 0 && _areaSize.height > 0) {
  279. float w1 = ceilf(_areaSize.width / 10.0);
  280. float h1 = ceilf(_areaSize.height / 10.0);
  281. result = (w1 * h1);
  282. }
  283. return result;
  284. }
  285. @end
  286. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  287. @interface WYPopoverTheme ()
  288. - (NSArray *)observableKeypaths;
  289. @end
  290. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  291. @implementation WYPopoverTheme
  292. + (id)theme {
  293. WYPopoverTheme *result = nil;
  294. if (WY_IS_IOS_LESS_THAN(@"7.0")) {
  295. result = [WYPopoverTheme themeForIOS6];
  296. } else {
  297. result = [WYPopoverTheme themeForIOS7];
  298. }
  299. return result;
  300. }
  301. + (id)themeForIOS6 {
  302. WYPopoverTheme *result = [[WYPopoverTheme alloc] init];
  303. result.usesRoundedArrow = NO;
  304. result.dimsBackgroundViewsTintColor = YES;
  305. result.tintColor = [UIColor colorWithRed:55./255. green:63./255. blue:71./255. alpha:1.0];
  306. result.outerStrokeColor = nil;
  307. result.innerStrokeColor = nil;
  308. result.fillTopColor = result.tintColor;
  309. result.fillBottomColor = [result.tintColor wy_colorByDarken:0.4];
  310. result.glossShadowColor = nil;
  311. result.glossShadowOffset = CGSizeMake(0, 1.5);
  312. result.glossShadowBlurRadius = 0;
  313. result.borderWidth = 6;
  314. result.arrowBase = 42;
  315. result.arrowHeight = 18;
  316. result.outerShadowColor = [UIColor colorWithWhite:0 alpha:0.75];
  317. result.outerShadowBlurRadius = 8;
  318. result.outerShadowOffset = CGSizeMake(0, 2);
  319. result.outerCornerRadius = 8;
  320. result.minOuterCornerRadius = 0;
  321. result.innerShadowColor = [UIColor colorWithWhite:0 alpha:0.75];
  322. result.innerShadowBlurRadius = 2;
  323. result.innerShadowOffset = CGSizeMake(0, 1);
  324. result.innerCornerRadius = 6;
  325. result.viewContentInsets = UIEdgeInsetsMake(3, 0, 0, 0);
  326. result.overlayColor = [UIColor clearColor];
  327. result.preferredAlpha = 1.0f;
  328. return result;
  329. }
  330. + (id)themeForIOS7 {
  331. WYPopoverTheme *result = [[WYPopoverTheme alloc] init];
  332. result.usesRoundedArrow = YES;
  333. result.dimsBackgroundViewsTintColor = YES;
  334. result.tintColor = [UIColor colorWithRed:244./255. green:244./255. blue:244./255. alpha:1.0];
  335. result.outerStrokeColor = [UIColor clearColor];
  336. result.innerStrokeColor = [UIColor clearColor];
  337. result.fillTopColor = nil;
  338. result.fillBottomColor = nil;
  339. result.glossShadowColor = nil;
  340. result.glossShadowOffset = CGSizeZero;
  341. result.glossShadowBlurRadius = 0;
  342. result.borderWidth = 0;
  343. result.arrowBase = 25;
  344. result.arrowHeight = 13;
  345. result.outerShadowColor = [UIColor clearColor];
  346. result.outerShadowBlurRadius = 0;
  347. result.outerShadowOffset = CGSizeZero;
  348. result.outerCornerRadius = 5;
  349. result.minOuterCornerRadius = 0;
  350. result.innerShadowColor = [UIColor clearColor];
  351. result.innerShadowBlurRadius = 0;
  352. result.innerShadowOffset = CGSizeZero;
  353. result.innerCornerRadius = 0;
  354. result.viewContentInsets = UIEdgeInsetsZero;
  355. result.overlayColor = [UIColor colorWithWhite:0 alpha:0.15];
  356. result.preferredAlpha = 1.0f;
  357. return result;
  358. }
  359. - (NSUInteger)innerCornerRadius {
  360. float result = _innerCornerRadius;
  361. if (_borderWidth == 0) {
  362. result = 0;
  363. if (_outerCornerRadius > 0) {
  364. result = _outerCornerRadius;
  365. }
  366. }
  367. return result;
  368. }
  369. - (CGSize)outerShadowOffset {
  370. CGSize result = _outerShadowOffset;
  371. result.width = MIN(result.width, _outerShadowBlurRadius);
  372. result.height = MIN(result.height, _outerShadowBlurRadius);
  373. return result;
  374. }
  375. - (UIColor *)innerStrokeColor {
  376. return _innerStrokeColor?: [self.fillTopColor wy_colorByDarken:0.6];
  377. }
  378. - (UIColor *)outerStrokeColor {
  379. return _outerStrokeColor?: [self.fillTopColor wy_colorByDarken:0.6];
  380. }
  381. - (UIColor *)glossShadowColor {
  382. return _glossShadowColor?: [self.fillTopColor wy_colorByLighten:0.2];
  383. }
  384. - (UIColor *)fillTopColor {
  385. return _fillTopColor?: _tintColor;
  386. }
  387. - (UIColor *)fillBottomColor {
  388. return _fillBottomColor?: self.fillTopColor;
  389. }
  390. - (NSArray *)observableKeypaths {
  391. return [NSArray arrayWithObjects:@"tintColor", @"outerStrokeColor", @"innerStrokeColor", @"fillTopColor", @"fillBottomColor", @"glossShadowColor", @"glossShadowOffset", @"glossShadowBlurRadius", @"borderWidth", @"arrowBase", @"arrowHeight", @"outerShadowColor", @"outerShadowBlurRadius", @"outerShadowOffset", @"outerCornerRadius", @"innerShadowColor", @"innerShadowBlurRadius", @"innerShadowOffset", @"innerCornerRadius", @"viewContentInsets", @"overlayColor", nil];
  392. }
  393. @end
  394. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  395. @interface UIImage (WYPopover)
  396. + (UIImage *)wy_imageWithColor:(UIColor *)color;
  397. @end
  398. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  399. #pragma mark - UIImage (WYPopover)
  400. @implementation UIImage (WYPopover)
  401. static float edgeSizeFromCornerRadius(float cornerRadius) {
  402. return cornerRadius * 2 + 1;
  403. }
  404. + (UIImage *)wy_imageWithColor:(UIColor *)color {
  405. return [self imageWithColor:color size:CGSizeMake(8, 8) cornerRadius:0];
  406. }
  407. + (UIImage *)imageWithColor:(UIColor *)color
  408. cornerRadius:(float)cornerRadius {
  409. float min = edgeSizeFromCornerRadius(cornerRadius);
  410. CGSize minSize = CGSizeMake(min, min);
  411. return [self imageWithColor:color size:minSize cornerRadius:cornerRadius];
  412. }
  413. + (UIImage *)imageWithColor:(UIColor *)color
  414. size:(CGSize)aSize
  415. cornerRadius:(float)cornerRadius {
  416. CGRect rect = CGRectMake(0, 0, aSize.width, aSize.height);
  417. UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
  418. roundedRect.lineWidth = 0;
  419. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);
  420. [color setFill];
  421. [roundedRect fill];
  422. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  423. UIGraphicsEndImageContext();
  424. return [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius)];
  425. }
  426. @end
  427. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  428. @interface WYPopoverBackgroundInnerView : UIView
  429. @property (nonatomic, strong) UIColor *innerStrokeColor;
  430. @property (nonatomic, strong) UIColor *gradientTopColor;
  431. @property (nonatomic, strong) UIColor *gradientBottomColor;
  432. @property (nonatomic, assign) float gradientHeight;
  433. @property (nonatomic, assign) float gradientTopPosition;
  434. @property (nonatomic, strong) UIColor *innerShadowColor;
  435. @property (nonatomic, assign) CGSize innerShadowOffset;
  436. @property (nonatomic, assign) float innerShadowBlurRadius;
  437. @property (nonatomic, assign) float innerCornerRadius;
  438. @property (nonatomic, assign) float navigationBarHeight;
  439. @property (nonatomic, assign) BOOL wantsDefaultContentAppearance;
  440. @property (nonatomic, assign) float borderWidth;
  441. @end
  442. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  443. #pragma mark - WYPopoverInnerView
  444. @implementation WYPopoverBackgroundInnerView
  445. - (id)initWithFrame:(CGRect)frame {
  446. self = [super initWithFrame:frame];
  447. if (self) {
  448. self.backgroundColor = [UIColor clearColor];
  449. self.userInteractionEnabled = NO;
  450. }
  451. return self;
  452. }
  453. - (void)drawRect:(CGRect)rect {
  454. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  455. CGContextRef context = UIGraphicsGetCurrentContext();
  456. //// Gradient Declarations
  457. NSArray* fillGradientColors = [NSArray arrayWithObjects:
  458. (id)_gradientTopColor.CGColor,
  459. (id)_gradientBottomColor.CGColor, nil];
  460. CGFloat fillGradientLocations[2] = { 0, 1 };
  461. CGGradientRef fillGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)fillGradientColors, fillGradientLocations);
  462. //// innerRect Drawing
  463. float barHeight = (_wantsDefaultContentAppearance == NO) ? _navigationBarHeight : 0;
  464. float cornerRadius = (_wantsDefaultContentAppearance == NO) ? _innerCornerRadius : 0;
  465. CGRect innerRect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect) + barHeight, CGRectGetWidth(rect) , CGRectGetHeight(rect) - barHeight);
  466. UIBezierPath* rectPath = [UIBezierPath bezierPathWithRect:innerRect];
  467. UIBezierPath* roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:cornerRadius + 1];
  468. if (_wantsDefaultContentAppearance == NO && _borderWidth > 0) {
  469. CGContextSaveGState(context);
  470. {
  471. [rectPath appendPath:roundedRectPath];
  472. rectPath.usesEvenOddFillRule = YES;
  473. [rectPath addClip];
  474. CGContextDrawLinearGradient(context, fillGradient,
  475. CGPointMake(0, -_gradientTopPosition),
  476. CGPointMake(0, -_gradientTopPosition + _gradientHeight),
  477. 0);
  478. }
  479. CGContextRestoreGState(context);
  480. }
  481. CGContextSaveGState(context);
  482. {
  483. if (_wantsDefaultContentAppearance == NO && _borderWidth > 0) {
  484. [roundedRectPath addClip];
  485. CGContextSetShadowWithColor(context, _innerShadowOffset, _innerShadowBlurRadius, _innerShadowColor.CGColor);
  486. }
  487. UIBezierPath* inRoundedRectPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(innerRect, 0.5, 0.5) cornerRadius:cornerRadius];
  488. if (_borderWidth == 0) {
  489. inRoundedRectPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(innerRect, 0.5, 0.5) byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
  490. }
  491. [self.innerStrokeColor setStroke];
  492. inRoundedRectPath.lineWidth = 1;
  493. [inRoundedRectPath stroke];
  494. }
  495. CGContextRestoreGState(context);
  496. CGGradientRelease(fillGradient);
  497. CGColorSpaceRelease(colorSpace);
  498. }
  499. - (void)dealloc
  500. {
  501. _innerShadowColor = nil;
  502. _innerStrokeColor = nil;
  503. _gradientTopColor = nil;
  504. _gradientBottomColor = nil;
  505. }
  506. @end
  507. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  508. @protocol WYPopoverOverlayViewDelegate;
  509. @interface WYPopoverOverlayView : UIView {
  510. BOOL _testHits;
  511. }
  512. @property(nonatomic, assign) id <WYPopoverOverlayViewDelegate> delegate;
  513. @property(nonatomic, unsafe_unretained) NSArray *passthroughViews;
  514. @end
  515. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  516. #pragma mark - WYPopoverOverlayViewDelegate
  517. @protocol WYPopoverOverlayViewDelegate <NSObject>
  518. @optional
  519. - (BOOL)dismissOnPassthroughViewTap;
  520. - (void)popoverOverlayViewDidTouch:(WYPopoverOverlayView *)overlayView;
  521. @end
  522. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  523. #pragma mark - WYPopoverOverlayView
  524. @implementation WYPopoverOverlayView
  525. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  526. if (_testHits) {
  527. return nil;
  528. }
  529. UIView *view = [super hitTest:point withEvent:event];
  530. if (view == self) {
  531. _testHits = YES;
  532. UIView *superHitView = [self.superview hitTest:point withEvent:event];
  533. _testHits = NO;
  534. if ([self isPassthroughView:superHitView]) {
  535. if ([self.delegate dismissOnPassthroughViewTap]) {
  536. dispatch_async(dispatch_get_main_queue(), ^ {
  537. if ([self.delegate respondsToSelector:@selector(popoverOverlayViewDidTouch:)]) {
  538. [self.delegate popoverOverlayViewDidTouch:self];
  539. }
  540. });
  541. }
  542. return superHitView;
  543. }
  544. }
  545. return view;
  546. }
  547. - (BOOL)isPassthroughView:(UIView *)view {
  548. if (view == nil) {
  549. return NO;
  550. }
  551. if ([self.passthroughViews containsObject:view]) {
  552. return YES;
  553. }
  554. return [self isPassthroughView:view.superview];
  555. }
  556. /**
  557. * @note This empty method is meaningful.
  558. * If the method is not defined, touch event isn't capture in iOS6.
  559. */
  560. - (void)drawRect:(CGRect)rect {}
  561. #pragma mark - UIAccessibility
  562. - (void)accessibilityElementDidBecomeFocused {
  563. self.accessibilityLabel = NSLocalizedString(@"Double-tap to dismiss pop-up window.", nil);
  564. }
  565. @end
  566. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  567. #pragma mark - WYPopoverBackgroundViewDelegate
  568. @protocol WYPopoverBackgroundViewDelegate <NSObject>
  569. @optional
  570. - (void)popoverBackgroundViewDidTouchOutside:(WYPopoverBackgroundView *)backgroundView;
  571. @end
  572. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  573. @interface WYPopoverBackgroundView () {
  574. WYPopoverBackgroundInnerView *_innerView;
  575. CGSize _contentSize;
  576. }
  577. @property(nonatomic, assign) id <WYPopoverBackgroundViewDelegate> delegate;
  578. @property (nonatomic, assign) WYPopoverArrowDirection arrowDirection;
  579. @property (nonatomic, strong, readonly) UIView *contentView;
  580. @property (nonatomic, assign, readonly) float navigationBarHeight;
  581. @property (nonatomic, assign, readonly) UIEdgeInsets outerShadowInsets;
  582. @property (nonatomic, assign) float arrowOffset;
  583. @property (nonatomic, assign) BOOL wantsDefaultContentAppearance;
  584. @property (nonatomic, assign, getter = isAppearing) BOOL appearing;
  585. - (void)tapOut;
  586. - (void)setViewController:(UIViewController *)viewController;
  587. - (CGRect)outerRect;
  588. - (CGRect)innerRect;
  589. - (CGRect)arrowRect;
  590. - (CGRect)outerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
  591. - (CGRect)innerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
  592. - (CGRect)arrowRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
  593. - (id)initWithContentSize:(CGSize)contentSize;
  594. @end
  595. ////////////////////////////////////////////////////////////////////////////////////////////////////////
  596. #pragma mark - WYPopoverBackgroundView
  597. @implementation WYPopoverBackgroundView
  598. - (id)initWithContentSize:(CGSize)aContentSize {
  599. self = [super initWithFrame:CGRectMake(0, 0, aContentSize.width, aContentSize.height)];
  600. if (self != nil) {
  601. _contentSize = aContentSize;
  602. self.autoresizesSubviews = NO;
  603. self.backgroundColor = [UIColor clearColor];
  604. self.arrowDirection = WYPopoverArrowDirectionDown;
  605. self.arrowOffset = 0;
  606. self.layer.name = @"parent";
  607. if (WY_IS_IOS_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
  608. self.layer.drawsAsynchronously = YES;
  609. }
  610. self.layer.contentsScale = [UIScreen mainScreen].scale;
  611. //self.layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;
  612. self.layer.delegate = self;
  613. }
  614. return self;
  615. }
  616. - (void)tapOut {
  617. [self.delegate popoverBackgroundViewDidTouchOutside:self];
  618. }
  619. - (UIEdgeInsets)outerShadowInsets {
  620. UIEdgeInsets result = UIEdgeInsetsMake(_outerShadowBlurRadius, _outerShadowBlurRadius, _outerShadowBlurRadius, _outerShadowBlurRadius);
  621. result.top -= self.outerShadowOffset.height;
  622. result.bottom += self.outerShadowOffset.height;
  623. result.left -= self.outerShadowOffset.width;
  624. result.right += self.outerShadowOffset.width;
  625. return result;
  626. }
  627. - (void)setArrowOffset:(float)value {
  628. float coef = 1;
  629. if (value != 0) {
  630. coef = value / ABS(value);
  631. value = ABS(value);
  632. CGRect outerRect = [self outerRect];
  633. float delta = self.arrowBase / 2. + .5;
  634. delta += MIN(_minOuterCornerRadius, _outerCornerRadius);
  635. outerRect = CGRectInset(outerRect, delta, delta);
  636. if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
  637. value += coef * self.outerShadowOffset.width;
  638. value = MIN(value, CGRectGetWidth(outerRect) / 2);
  639. }
  640. if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  641. value += coef * self.outerShadowOffset.height;
  642. value = MIN(value, CGRectGetHeight(outerRect) / 2);
  643. }
  644. } else {
  645. if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
  646. value += self.outerShadowOffset.width;
  647. }
  648. if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  649. value += self.outerShadowOffset.height;
  650. }
  651. }
  652. _arrowOffset = value * coef;
  653. }
  654. - (void)setViewController:(UIViewController *)viewController {
  655. _contentView = viewController.view;
  656. _contentView.frame = CGRectIntegral(CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
  657. [self addSubview:_contentView];
  658. _navigationBarHeight = 0;
  659. if ([viewController isKindOfClass:[UINavigationController class]]) {
  660. UINavigationController* navigationController = (UINavigationController*)viewController;
  661. _navigationBarHeight = navigationController.navigationBarHidden? 0 : navigationController.navigationBar.bounds.size.height;
  662. }
  663. _contentView.frame = CGRectIntegral([self innerRect]);
  664. if (_innerView == nil) {
  665. _innerView = [[WYPopoverBackgroundInnerView alloc] initWithFrame:_contentView.frame];
  666. _innerView.userInteractionEnabled = NO;
  667. _innerView.gradientTopColor = self.fillTopColor;
  668. _innerView.gradientBottomColor = self.fillBottomColor;
  669. _innerView.innerShadowColor = _innerShadowColor;
  670. _innerView.innerStrokeColor = self.innerStrokeColor;
  671. _innerView.innerShadowOffset = _innerShadowOffset;
  672. _innerView.innerCornerRadius = self.innerCornerRadius;
  673. _innerView.innerShadowBlurRadius = _innerShadowBlurRadius;
  674. _innerView.borderWidth = self.borderWidth;
  675. }
  676. _innerView.navigationBarHeight = _navigationBarHeight;
  677. _innerView.gradientHeight = self.frame.size.height - 2 * _outerShadowBlurRadius;
  678. _innerView.gradientTopPosition = _contentView.frame.origin.y - self.outerShadowInsets.top;
  679. _innerView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
  680. [self insertSubview:_innerView aboveSubview:_contentView];
  681. _innerView.frame = CGRectIntegral(_contentView.frame);
  682. [self.layer setNeedsDisplay];
  683. }
  684. - (CGSize)sizeThatFits:(CGSize)size {
  685. CGSize result = size;
  686. result.width += 2 * (_borderWidth + _outerShadowBlurRadius);
  687. result.height += _borderWidth + 2 * _outerShadowBlurRadius;
  688. if (_navigationBarHeight == 0) {
  689. result.height += _borderWidth;
  690. }
  691. if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
  692. result.height += _arrowHeight;
  693. }
  694. if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  695. result.width += _arrowHeight;
  696. }
  697. return result;
  698. }
  699. - (void)sizeToFit {
  700. CGSize size = [self sizeThatFits:_contentSize];
  701. self.bounds = CGRectMake(0, 0, size.width, size.height);
  702. }
  703. #pragma mark Drawing
  704. - (void)setNeedsDisplay {
  705. [super setNeedsDisplay];
  706. [self.layer setNeedsDisplay];
  707. self.alpha = self.preferredAlpha;
  708. if (_innerView) {
  709. _innerView.gradientTopColor = self.fillTopColor;
  710. _innerView.gradientBottomColor = self.fillBottomColor;
  711. _innerView.innerShadowColor = _innerShadowColor;
  712. _innerView.innerStrokeColor = self.innerStrokeColor;
  713. _innerView.innerShadowOffset = _innerShadowOffset;
  714. _innerView.innerCornerRadius = self.innerCornerRadius;
  715. _innerView.innerShadowBlurRadius = _innerShadowBlurRadius;
  716. _innerView.borderWidth = self.borderWidth;
  717. _innerView.navigationBarHeight = _navigationBarHeight;
  718. _innerView.gradientHeight = self.frame.size.height - 2 * _outerShadowBlurRadius;
  719. _innerView.gradientTopPosition = _contentView.frame.origin.y - self.outerShadowInsets.top;
  720. _innerView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
  721. [_innerView setNeedsDisplay];
  722. }
  723. }
  724. #pragma mark CALayerDelegate
  725. - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
  726. if ([layer.name isEqualToString:@"parent"]) {
  727. UIGraphicsPushContext(context);
  728. //CGContextSetShouldAntialias(context, YES);
  729. //CGContextSetAllowsAntialiasing(context, YES);
  730. //// General Declarations
  731. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  732. //// Gradient Declarations
  733. NSArray* fillGradientColors = [NSArray arrayWithObjects:
  734. (id)self.fillTopColor.CGColor,
  735. (id)self.fillBottomColor.CGColor, nil];
  736. CGFloat fillGradientLocations[2] = {0, 1};
  737. CGGradientRef fillGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)fillGradientColors, fillGradientLocations);
  738. // Frames
  739. CGRect rect = self.bounds;
  740. CGRect outerRect = [self outerRect:rect arrowDirection:self.arrowDirection];
  741. CGRect insetRect = CGRectInset(outerRect, 0.5, 0.5);
  742. if (!CGRectIsEmpty(insetRect) && !CGRectIsInfinite(insetRect)) {
  743. outerRect = insetRect;
  744. }
  745. // Inner Path
  746. CGMutablePathRef outerPathRef = CGPathCreateMutable();
  747. CGPoint arrowTipPoint = CGPointZero;
  748. CGPoint arrowBasePointA = CGPointZero;
  749. CGPoint arrowBasePointB = CGPointZero;
  750. float reducedOuterCornerRadius = 0;
  751. if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
  752. if (_arrowOffset >= 0) {
  753. reducedOuterCornerRadius = CGRectGetMaxX(outerRect) - (CGRectGetMidX(outerRect) + _arrowOffset + _arrowBase / 2);
  754. } else {
  755. reducedOuterCornerRadius = (CGRectGetMidX(outerRect) + _arrowOffset - _arrowBase / 2) - CGRectGetMinX(outerRect);
  756. }
  757. } else if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  758. if (_arrowOffset >= 0) {
  759. reducedOuterCornerRadius = CGRectGetMaxY(outerRect) - (CGRectGetMidY(outerRect) + _arrowOffset + _arrowBase / 2);
  760. } else {
  761. reducedOuterCornerRadius = (CGRectGetMidY(outerRect) + _arrowOffset - _arrowBase / 2) - CGRectGetMinY(outerRect);
  762. }
  763. }
  764. reducedOuterCornerRadius = MIN(reducedOuterCornerRadius, _outerCornerRadius);
  765. CGFloat roundedArrowControlLength = _arrowBase / 5.0f;
  766. if (_arrowDirection == WYPopoverArrowDirectionUp) {
  767. arrowTipPoint = CGPointMake(CGRectGetMidX(outerRect) + _arrowOffset,
  768. CGRectGetMinY(outerRect) - _arrowHeight);
  769. arrowBasePointA = CGPointMake(arrowTipPoint.x - _arrowBase / 2,
  770. arrowTipPoint.y + _arrowHeight);
  771. arrowBasePointB = CGPointMake(arrowTipPoint.x + _arrowBase / 2,
  772. arrowTipPoint.y + _arrowHeight);
  773. CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  774. if (self.usesRoundedArrow) {
  775. CGPathAddCurveToPoint(outerPathRef, NULL,
  776. arrowBasePointA.x + roundedArrowControlLength, arrowBasePointA.y,
  777. arrowTipPoint.x - (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
  778. arrowTipPoint.x, arrowTipPoint.y);
  779. CGPathAddCurveToPoint(outerPathRef, NULL,
  780. arrowTipPoint.x + (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
  781. arrowBasePointB.x - roundedArrowControlLength, arrowBasePointB.y,
  782. arrowBasePointB.x, arrowBasePointB.y);
  783. } else {
  784. CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
  785. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
  786. }
  787. CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  788. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  789. (_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  790. CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  791. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  792. _outerCornerRadius);
  793. CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  794. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  795. _outerCornerRadius);
  796. CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  797. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  798. (_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  799. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  800. } else if (_arrowDirection == WYPopoverArrowDirectionDown) {
  801. arrowTipPoint = CGPointMake(CGRectGetMidX(outerRect) + _arrowOffset,
  802. CGRectGetMaxY(outerRect) + _arrowHeight);
  803. arrowBasePointA = CGPointMake(arrowTipPoint.x + _arrowBase / 2,
  804. arrowTipPoint.y - _arrowHeight);
  805. arrowBasePointB = CGPointMake(arrowTipPoint.x - _arrowBase / 2,
  806. arrowTipPoint.y - _arrowHeight);
  807. CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  808. if (self.usesRoundedArrow) {
  809. CGPathAddCurveToPoint(outerPathRef, NULL,
  810. arrowBasePointA.x - roundedArrowControlLength, arrowBasePointA.y,
  811. arrowTipPoint.x + (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
  812. arrowTipPoint.x, arrowTipPoint.y);
  813. CGPathAddCurveToPoint(outerPathRef, NULL,
  814. arrowTipPoint.x - (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
  815. arrowBasePointB.x + roundedArrowControlLength, arrowBasePointA.y,
  816. arrowBasePointB.x, arrowBasePointB.y);
  817. } else {
  818. CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
  819. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
  820. }
  821. CGPathAddArcToPoint(outerPathRef, NULL,
  822. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  823. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  824. (_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  825. CGPathAddArcToPoint(outerPathRef, NULL,
  826. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  827. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  828. _outerCornerRadius);
  829. CGPathAddArcToPoint(outerPathRef, NULL,
  830. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  831. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  832. _outerCornerRadius);
  833. CGPathAddArcToPoint(outerPathRef, NULL,
  834. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  835. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  836. (_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  837. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  838. } else if (_arrowDirection == WYPopoverArrowDirectionLeft) {
  839. arrowTipPoint = CGPointMake(CGRectGetMinX(outerRect) - _arrowHeight,
  840. CGRectGetMidY(outerRect) + _arrowOffset);
  841. arrowBasePointA = CGPointMake(arrowTipPoint.x + _arrowHeight,
  842. arrowTipPoint.y + _arrowBase / 2);
  843. arrowBasePointB = CGPointMake(arrowTipPoint.x + _arrowHeight,
  844. arrowTipPoint.y - _arrowBase / 2);
  845. CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  846. if (self.usesRoundedArrow) {
  847. CGPathAddCurveToPoint(outerPathRef, NULL,
  848. arrowBasePointA.x, arrowBasePointA.y - roundedArrowControlLength,
  849. arrowTipPoint.x, arrowTipPoint.y + (roundedArrowControlLength * 0.75f),
  850. arrowTipPoint.x, arrowTipPoint.y);
  851. CGPathAddCurveToPoint(outerPathRef, NULL,
  852. arrowTipPoint.x, arrowTipPoint.y - (roundedArrowControlLength * 0.75f),
  853. arrowBasePointB.x, arrowBasePointB.y + roundedArrowControlLength,
  854. arrowBasePointB.x, arrowBasePointB.y);
  855. } else {
  856. CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
  857. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
  858. }
  859. CGPathAddArcToPoint(outerPathRef, NULL,
  860. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  861. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  862. (_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  863. CGPathAddArcToPoint(outerPathRef, NULL,
  864. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  865. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  866. _outerCornerRadius);
  867. CGPathAddArcToPoint(outerPathRef, NULL,
  868. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  869. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  870. _outerCornerRadius);
  871. CGPathAddArcToPoint(outerPathRef, NULL,
  872. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  873. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  874. (_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  875. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  876. } else if (_arrowDirection == WYPopoverArrowDirectionRight) {
  877. arrowTipPoint = CGPointMake(CGRectGetMaxX(outerRect) + _arrowHeight,
  878. CGRectGetMidY(outerRect) + _arrowOffset);
  879. arrowBasePointA = CGPointMake(arrowTipPoint.x - _arrowHeight,
  880. arrowTipPoint.y - _arrowBase / 2);
  881. arrowBasePointB = CGPointMake(arrowTipPoint.x - _arrowHeight,
  882. arrowTipPoint.y + _arrowBase / 2);
  883. CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  884. if (self.usesRoundedArrow) {
  885. CGPathAddCurveToPoint(outerPathRef, NULL,
  886. arrowBasePointA.x, arrowBasePointA.y + roundedArrowControlLength,
  887. arrowTipPoint.x, arrowTipPoint.y - (roundedArrowControlLength * 0.75f),
  888. arrowTipPoint.x, arrowTipPoint.y);
  889. CGPathAddCurveToPoint(outerPathRef, NULL,
  890. arrowTipPoint.x, arrowTipPoint.y + (roundedArrowControlLength * 0.75f),
  891. arrowBasePointB.x, arrowBasePointB.y - roundedArrowControlLength,
  892. arrowBasePointB.x, arrowBasePointB.y);
  893. } else {
  894. CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
  895. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
  896. }
  897. CGPathAddArcToPoint(outerPathRef, NULL,
  898. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  899. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  900. (_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  901. CGPathAddArcToPoint(outerPathRef, NULL,
  902. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  903. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  904. _outerCornerRadius);
  905. CGPathAddArcToPoint(outerPathRef, NULL,
  906. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  907. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  908. _outerCornerRadius);
  909. CGPathAddArcToPoint(outerPathRef, NULL,
  910. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  911. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  912. (_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
  913. CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
  914. } else if (_arrowDirection == WYPopoverArrowDirectionNone) {
  915. CGPoint origin = CGPointMake(CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
  916. CGPathMoveToPoint(outerPathRef, NULL, origin.x, origin.y);
  917. CGPathAddLineToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
  918. CGPathAddLineToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
  919. CGPathAddArcToPoint(outerPathRef, NULL,
  920. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  921. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  922. _outerCornerRadius);
  923. CGPathAddArcToPoint(outerPathRef, NULL,
  924. CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
  925. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  926. _outerCornerRadius);
  927. CGPathAddArcToPoint(outerPathRef, NULL,
  928. CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
  929. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  930. _outerCornerRadius);
  931. CGPathAddArcToPoint(outerPathRef, NULL,
  932. CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
  933. CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
  934. _outerCornerRadius);
  935. CGPathAddLineToPoint(outerPathRef, NULL, origin.x, origin.y);
  936. }
  937. CGPathCloseSubpath(outerPathRef);
  938. UIBezierPath* outerRectPath = [UIBezierPath bezierPathWithCGPath:outerPathRef];
  939. CGContextSaveGState(context);
  940. {
  941. CGContextSetShadowWithColor(context, self.outerShadowOffset, _outerShadowBlurRadius, _outerShadowColor.CGColor);
  942. CGContextBeginTransparencyLayer(context, NULL);
  943. [outerRectPath addClip];
  944. CGRect outerRectBounds = CGPathGetPathBoundingBox(outerRectPath.CGPath);
  945. CGContextDrawLinearGradient(context, fillGradient,
  946. CGPointMake(CGRectGetMidX(outerRectBounds), CGRectGetMinY(outerRectBounds)),
  947. CGPointMake(CGRectGetMidX(outerRectBounds), CGRectGetMaxY(outerRectBounds)),
  948. 0);
  949. CGContextEndTransparencyLayer(context);
  950. }
  951. CGContextRestoreGState(context);
  952. ////// outerRect Inner Shadow
  953. CGRect outerRectBorderRect = CGRectInset([outerRectPath bounds], -_glossShadowBlurRadius, -_glossShadowBlurRadius);
  954. outerRectBorderRect = CGRectOffset(outerRectBorderRect, -_glossShadowOffset.width, -_glossShadowOffset.height);
  955. outerRectBorderRect = CGRectInset(CGRectUnion(outerRectBorderRect, [outerRectPath bounds]), -1, -1);
  956. UIBezierPath* outerRectNegativePath = [UIBezierPath bezierPathWithRect: outerRectBorderRect];
  957. [outerRectNegativePath appendPath: outerRectPath];
  958. outerRectNegativePath.usesEvenOddFillRule = YES;
  959. CGContextSaveGState(context);
  960. {
  961. float xOffset = _glossShadowOffset.width + round(outerRectBorderRect.size.width);
  962. float yOffset = _glossShadowOffset.height;
  963. CGContextSetShadowWithColor(context,
  964. CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
  965. _glossShadowBlurRadius,
  966. self.glossShadowColor.CGColor);
  967. [outerRectPath addClip];
  968. CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(outerRectBorderRect.size.width), 0);
  969. [outerRectNegativePath applyTransform: transform];
  970. [[UIColor grayColor] setFill];
  971. [outerRectNegativePath fill];
  972. }
  973. CGContextRestoreGState(context);
  974. [self.outerStrokeColor setStroke];
  975. outerRectPath.lineWidth = 1;
  976. [outerRectPath stroke];
  977. //// Cleanup
  978. CFRelease(outerPathRef);
  979. CGGradientRelease(fillGradient);
  980. CGColorSpaceRelease(colorSpace);
  981. UIGraphicsPopContext();
  982. }
  983. }
  984. #pragma mark Private
  985. - (CGRect)outerRect {
  986. return [self outerRect:self.bounds arrowDirection:self.arrowDirection];
  987. }
  988. - (CGRect)innerRect {
  989. return [self innerRect:self.bounds arrowDirection:self.arrowDirection];
  990. }
  991. - (CGRect)arrowRect {
  992. return [self arrowRect:self.bounds arrowDirection:self.arrowDirection];
  993. }
  994. - (CGRect)outerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection{
  995. CGRect result = rect;
  996. if (aArrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
  997. result.size.height -= _arrowHeight;
  998. if (aArrowDirection == WYPopoverArrowDirectionUp) {
  999. result = CGRectOffset(result, 0, _arrowHeight);
  1000. }
  1001. }
  1002. if (aArrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  1003. result.size.width -= _arrowHeight;
  1004. if (aArrowDirection == WYPopoverArrowDirectionLeft) {
  1005. result = CGRectOffset(result, _arrowHeight, 0);
  1006. }
  1007. }
  1008. result = CGRectInset(result, _outerShadowBlurRadius, _outerShadowBlurRadius);
  1009. result.origin.x -= self.outerShadowOffset.width;
  1010. result.origin.y -= self.outerShadowOffset.height;
  1011. return result;
  1012. }
  1013. - (CGRect)innerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection {
  1014. CGRect result = [self outerRect:rect arrowDirection:aArrowDirection];
  1015. result.origin.x += _borderWidth;
  1016. result.origin.y += 0;
  1017. result.size.width -= 2 * _borderWidth;
  1018. result.size.height -= _borderWidth;
  1019. if (_navigationBarHeight == 0 || _wantsDefaultContentAppearance) {
  1020. result.origin.y += _borderWidth;
  1021. result.size.height -= _borderWidth;
  1022. }
  1023. result.origin.x += _viewContentInsets.left;
  1024. result.origin.y += _viewContentInsets.top;
  1025. result.size.width = result.size.width - _viewContentInsets.left - _viewContentInsets.right;
  1026. result.size.height = result.size.height - _viewContentInsets.top - _viewContentInsets.bottom;
  1027. if (_borderWidth > 0) {
  1028. result = CGRectInset(result, -1, -1);
  1029. }
  1030. return result;
  1031. }
  1032. - (CGRect)arrowRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection {
  1033. CGRect result = CGRectZero;
  1034. if (_arrowHeight > 0) {
  1035. result.size = CGSizeMake(_arrowBase, _arrowHeight);
  1036. if (aArrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
  1037. result.size = CGSizeMake(_arrowHeight, _arrowBase);
  1038. }
  1039. CGRect outerRect = [self outerRect:rect arrowDirection:aArrowDirection];
  1040. if (aArrowDirection == WYPopoverArrowDirectionDown) {
  1041. result.origin.x = CGRectGetMidX(outerRect) - result.size.width / 2 + _arrowOffset;
  1042. result.origin.y = CGRectGetMaxY(outerRect);
  1043. }
  1044. if (aArrowDirection == WYPopoverArrowDirectionUp) {
  1045. result.origin.x = CGRectGetMidX(outerRect) - result.size.width / 2 + _arrowOffset;
  1046. result.origin.y = CGRectGetMinY(outerRect) - result.size.height;
  1047. }
  1048. if (aArrowDirection == WYPopoverArrowDirectionRight) {
  1049. result.origin.x = CGRectGetMaxX(outerRect);
  1050. result.origin.y = CGRectGetMidY(outerRect) - result.size.height / 2 + _arrowOffset;
  1051. }
  1052. if (aArrowDirection == WYPopoverArrowDirectionLeft) {
  1053. result.origin.x = CGRectGetMinX(outerRect) - result.size.width;
  1054. result.origin.y = CGRectGetMidY(outerRect) - result.size.height / 2 + _arrowOffset;
  1055. }
  1056. }
  1057. return result;
  1058. }
  1059. #pragma mark Memory Management
  1060. - (void)dealloc {
  1061. _contentView = nil;
  1062. _innerView = nil;
  1063. _tintColor = nil;
  1064. _outerStrokeColor = nil;
  1065. _innerStrokeColor = nil;
  1066. _fillTopColor = nil;
  1067. _fillBottomColor = nil;
  1068. _glossShadowColor = nil;
  1069. _outerShadowColor = nil;
  1070. _innerShadowColor = nil;
  1071. }
  1072. @end
  1073. ////////////////////////////////////////////////////////////////////////////
  1074. @interface WYPopoverController () <WYPopoverOverlayViewDelegate, WYPopoverBackgroundViewDelegate> {
  1075. UIViewController *_viewController;
  1076. CGRect _rect;
  1077. UIView *_inView;
  1078. WYPopoverOverlayView *_overlayView;
  1079. WYPopoverBackgroundView *_backgroundView;
  1080. WYPopoverArrowDirection _permittedArrowDirections;
  1081. BOOL _animated;
  1082. BOOL _isListeningNotifications;
  1083. BOOL _isObserverAdded;
  1084. BOOL _isInterfaceOrientationChanging;
  1085. BOOL _ignoreOrientation;
  1086. __weak UIBarButtonItem *_barButtonItem;
  1087. WYPopoverAnimationOptions options;
  1088. BOOL themeUpdatesEnabled;
  1089. BOOL themeIsUpdating;
  1090. }
  1091. - (void)dismissPopoverAnimated:(BOOL)aAnimated
  1092. options:(WYPopoverAnimationOptions)aAptions
  1093. completion:(void (^)(void))aCompletion
  1094. callDelegate:(BOOL)aCallDelegate;
  1095. - (WYPopoverArrowDirection)arrowDirectionForRect:(CGRect)aRect
  1096. inView:(UIView*)aView
  1097. contentSize:(CGSize)aContentSize
  1098. arrowHeight:(float)aArrowHeight
  1099. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections;
  1100. - (CGSize)sizeForRect:(CGRect)aRect
  1101. inView:(UIView *)aView
  1102. arrowHeight:(float)aArrowHeight
  1103. arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
  1104. - (void)registerTheme;
  1105. - (void)unregisterTheme;
  1106. - (void)updateThemeUI;
  1107. - (CGSize)topViewControllerContentSize;
  1108. @end
  1109. ////////////////////////////////////////////////////////////////////////////
  1110. #pragma mark - WYPopoverController
  1111. @implementation WYPopoverController
  1112. static WYPopoverTheme *defaultTheme_ = nil;
  1113. @synthesize popoverContentSize = popoverContentSize_;
  1114. + (void)setDefaultTheme:(WYPopoverTheme *)aTheme {
  1115. defaultTheme_ = aTheme;
  1116. @autoreleasepool {
  1117. WYPopoverBackgroundView *appearance = [WYPopoverBackgroundView appearance];
  1118. appearance.usesRoundedArrow = aTheme.usesRoundedArrow;
  1119. appearance.dimsBackgroundViewsTintColor = aTheme.dimsBackgroundViewsTintColor;
  1120. appearance.tintColor = aTheme.tintColor;
  1121. appearance.outerStrokeColor = aTheme.outerStrokeColor;
  1122. appearance.innerStrokeColor = aTheme.innerStrokeColor;
  1123. appearance.fillTopColor = aTheme.fillTopColor;
  1124. appearance.fillBottomColor = aTheme.fillBottomColor;
  1125. appearance.glossShadowColor = aTheme.glossShadowColor;
  1126. appearance.glossShadowOffset = aTheme.glossShadowOffset;
  1127. appearance.glossShadowBlurRadius = aTheme.glossShadowBlurRadius;
  1128. appearance.borderWidth = aTheme.borderWidth;
  1129. appearance.arrowBase = aTheme.arrowBase;
  1130. appearance.arrowHeight = aTheme.arrowHeight;
  1131. appearance.outerShadowColor = aTheme.outerShadowColor;
  1132. appearance.outerShadowBlurRadius = aTheme.outerShadowBlurRadius;
  1133. appearance.outerShadowOffset = aTheme.outerShadowOffset;
  1134. appearance.outerCornerRadius = aTheme.outerCornerRadius;
  1135. appearance.minOuterCornerRadius = aTheme.minOuterCornerRadius;
  1136. appearance.innerShadowColor = aTheme.innerShadowColor;
  1137. appearance.innerShadowBlurRadius = aTheme.innerShadowBlurRadius;
  1138. appearance.innerShadowOffset = aTheme.innerShadowOffset;
  1139. appearance.innerCornerRadius = aTheme.innerCornerRadius;
  1140. appearance.viewContentInsets = aTheme.viewContentInsets;
  1141. appearance.overlayColor = aTheme.overlayColor;
  1142. appearance.preferredAlpha = aTheme.preferredAlpha;
  1143. }
  1144. }
  1145. + (WYPopoverTheme *)defaultTheme {
  1146. return defaultTheme_;
  1147. }
  1148. + (void)load {
  1149. [WYPopoverController setDefaultTheme:[WYPopoverTheme theme]];
  1150. }
  1151. - (id)init {
  1152. self = [super init];
  1153. if (self) {
  1154. // ignore orientation in iOS8
  1155. _ignoreOrientation = (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]);
  1156. _popoverLayoutMargins = UIEdgeInsetsMake(10, 10, 10, 10);
  1157. _animationDuration = WY_POPOVER_DEFAULT_ANIMATION_DURATION;
  1158. themeUpdatesEnabled = NO;
  1159. [self setTheme:[WYPopoverController defaultTheme]];
  1160. themeIsUpdating = YES;
  1161. WYPopoverBackgroundView *appearance = [WYPopoverBackgroundView appearance];
  1162. _theme.usesRoundedArrow = appearance.usesRoundedArrow;
  1163. _theme.dimsBackgroundViewsTintColor = appearance.dimsBackgroundViewsTintColor;
  1164. _theme.tintColor = appearance.tintColor;
  1165. _theme.outerStrokeColor = appearance.outerStrokeColor;
  1166. _theme.innerStrokeColor = appearance.innerStrokeColor;
  1167. _theme.fillTopColor = appearance.fillTopColor;
  1168. _theme.fillBottomColor = appearance.fillBottomColor;
  1169. _theme.glossShadowColor = appearance.glossShadowColor;
  1170. _theme.glossShadowOffset = appearance.glossShadowOffset;
  1171. _theme.glossShadowBlurRadius = appearance.glossShadowBlurRadius;
  1172. _theme.borderWidth = appearance.borderWidth;
  1173. _theme.arrowBase = appearance.arrowBase;
  1174. _theme.arrowHeight = appearance.arrowHeight;
  1175. _theme.outerShadowColor = appearance.outerShadowColor;
  1176. _theme.outerShadowBlurRadius = appearance.outerShadowBlurRadius;
  1177. _theme.outerShadowOffset = appearance.outerShadowOffset;
  1178. _theme.outerCornerRadius = appearance.outerCornerRadius;
  1179. _theme.minOuterCornerRadius = appearance.minOuterCornerRadius;
  1180. _theme.innerShadowColor = appearance.innerShadowColor;
  1181. _theme.innerShadowBlurRadius = appearance.innerShadowBlurRadius;
  1182. _theme.innerShadowOffset = appearance.innerShadowOffset;
  1183. _theme.innerCornerRadius = appearance.innerCornerRadius;
  1184. _theme.viewContentInsets = appearance.viewContentInsets;
  1185. _theme.overlayColor = appearance.overlayColor;
  1186. _theme.preferredAlpha = appearance.preferredAlpha;
  1187. themeIsUpdating = NO;
  1188. themeUpdatesEnabled = YES;
  1189. popoverContentSize_ = CGSizeZero;
  1190. }
  1191. return self;
  1192. }
  1193. - (id)initWithContentViewController:(UIViewController *)aViewController {
  1194. self = [self init];
  1195. if (self) {
  1196. _viewController = aViewController;
  1197. }
  1198. return self;
  1199. }
  1200. - (void)setTheme:(WYPopoverTheme *)value {
  1201. [self unregisterTheme];
  1202. _theme = value;
  1203. [self registerTheme];
  1204. [self updateThemeUI];
  1205. themeIsUpdating = NO;
  1206. }
  1207. - (void)registerTheme {
  1208. if (_theme == nil) return;
  1209. NSArray *keypaths = [_theme observableKeypaths];
  1210. for (NSString *keypath in keypaths) {
  1211. [_theme addObserver:self forKeyPath:keypath options:NSKeyValueObservingOptionNew context:NULL];
  1212. }
  1213. }
  1214. - (void)unregisterTheme {
  1215. if (_theme == nil) return;
  1216. @try {
  1217. NSArray *keypaths = [_theme observableKeypaths];
  1218. for (NSString *keypath in keypaths) {
  1219. [_theme removeObserver:self forKeyPath:keypath];
  1220. }
  1221. }
  1222. @catch (NSException * __unused exception) {}
  1223. }
  1224. - (void)updateThemeUI {
  1225. if (_theme == nil || themeUpdatesEnabled == NO || themeIsUpdating == YES) return;
  1226. if (_backgroundView != nil) {
  1227. _backgroundView.usesRoundedArrow = _theme.usesRoundedArrow;
  1228. _backgroundView.dimsBackgroundViewsTintColor = _theme.dimsBackgroundViewsTintColor;
  1229. _backgroundView.tintColor = _theme.tintColor;
  1230. _backgroundView.outerStrokeColor = _theme.outerStrokeColor;
  1231. _backgroundView.innerStrokeColor = _theme.innerStrokeColor;
  1232. _backgroundView.fillTopColor = _theme.fillTopColor;
  1233. _backgroundView.fillBottomColor = _theme.fillBottomColor;
  1234. _backgroundView.glossShadowColor = _theme.glossShadowColor;
  1235. _backgroundView.glossShadowOffset = _theme.glossShadowOffset;
  1236. _backgroundView.glossShadowBlurRadius = _theme.glossShadowBlurRadius;
  1237. _backgroundView.borderWidth = _theme.borderWidth;
  1238. _backgroundView.arrowBase = _theme.arrowBase;
  1239. _backgroundView.arrowHeight = _theme.arrowHeight;
  1240. _backgroundView.outerShadowColor = _theme.outerShadowColor;
  1241. _backgroundView.outerShadowBlurRadius = _theme.outerShadowBlurRadius;
  1242. _backgroundView.outerShadowOffset = _theme.outerShadowOffset;
  1243. _backgroundView.outerCornerRadius = _theme.outerCornerRadius;
  1244. _backgroundView.minOuterCornerRadius = _theme.minOuterCornerRadius;
  1245. _backgroundView.innerShadowColor = _theme.innerShadowColor;
  1246. _backgroundView.innerShadowBlurRadius = _theme.innerShadowBlurRadius;
  1247. _backgroundView.innerShadowOffset = _theme.innerShadowOffset;
  1248. _backgroundView.innerCornerRadius = _theme.innerCornerRadius;
  1249. _backgroundView.viewContentInsets = _theme.viewContentInsets;
  1250. _backgroundView.preferredAlpha = _theme.preferredAlpha;
  1251. [_backgroundView setNeedsDisplay];
  1252. }
  1253. if (_overlayView != nil) {
  1254. _overlayView.backgroundColor = _theme.overlayColor;
  1255. }
  1256. [self positionPopover:NO];
  1257. [self setPopoverNavigationBarBackgroundImage];
  1258. }
  1259. - (void)beginThemeUpdates {
  1260. themeIsUpdating = YES;
  1261. }
  1262. - (void)endThemeUpdates {
  1263. themeIsUpdating = NO;
  1264. [self updateThemeUI];
  1265. }
  1266. - (BOOL)isPopoverVisible {
  1267. BOOL result = (_overlayView != nil);
  1268. return result;
  1269. }
  1270. - (UIViewController *)contentViewController {
  1271. return _viewController;
  1272. }
  1273. - (CGSize)topViewControllerContentSize {
  1274. CGSize result = CGSizeZero;
  1275. UIViewController *topViewController = _viewController;
  1276. if ([_viewController isKindOfClass:[UINavigationController class]] == YES) {
  1277. UINavigationController *navigationController = (UINavigationController *)_viewController;
  1278. topViewController = [navigationController topViewController];
  1279. }
  1280. #ifdef WY_BASE_SDK_7_ENABLED
  1281. if ([topViewController respondsToSelector:@selector(preferredContentSize)]) {
  1282. result = topViewController.preferredContentSize;
  1283. }
  1284. #endif
  1285. if (CGSizeEqualToSize(result, CGSizeZero)) {
  1286. #pragma clang diagnostic push
  1287. #pragma GCC diagnostic ignored "-Wdeprecated"
  1288. result = topViewController.contentSizeForViewInPopover;
  1289. #pragma clang diagnostic pop
  1290. }
  1291. if (CGSizeEqualToSize(result, CGSizeZero)) {
  1292. CGSize windowSize = [[UIApplication sharedApplication] keyWindow].bounds.size;
  1293. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  1294. result = CGSizeMake(UIInterfaceOrientationIsPortrait(orientation) ? windowSize.width : windowSize.height, UIInterfaceOrientationIsLandscape(orientation) ? windowSize.width : windowSize.height);
  1295. }
  1296. return result;
  1297. }
  1298. - (CGSize)popoverContentSize {
  1299. CGSize result = popoverContentSize_;
  1300. if (CGSizeEqualToSize(result, CGSizeZero)) {
  1301. result = [self topViewControllerContentSize];
  1302. }
  1303. return result;
  1304. }
  1305. - (void)setPopoverContentSize:(CGSize)size {
  1306. popoverContentSize_ = size;
  1307. [self positionPopover:YES];
  1308. }
  1309. - (void)setPopoverContentSize:(CGSize)size animated:(BOOL)animated {
  1310. popoverContentSize_ = size;
  1311. [self positionPopover:animated];
  1312. }
  1313. - (void)performWithoutAnimation:(void (^)(void))aBlock {
  1314. if (aBlock) {
  1315. self.implicitAnimationsDisabled = YES;
  1316. aBlock();
  1317. self.implicitAnimationsDisabled = NO;
  1318. }
  1319. }
  1320. - (void)presentPopoverFromRect:(CGRect)aRect
  1321. inView:(UIView *)aView
  1322. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1323. animated:(BOOL)aAnimated {
  1324. [self presentPopoverFromRect:aRect
  1325. inView:aView
  1326. permittedArrowDirections:aArrowDirections
  1327. animated:aAnimated
  1328. completion:nil];
  1329. }
  1330. - (void)presentPopoverFromRect:(CGRect)aRect
  1331. inView:(UIView *)aView
  1332. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1333. animated:(BOOL)aAnimated
  1334. completion:(void (^)(void))completion {
  1335. [self presentPopoverFromRect:aRect
  1336. inView:aView
  1337. permittedArrowDirections:aArrowDirections
  1338. animated:aAnimated
  1339. options:WYPopoverAnimationOptionFade
  1340. completion:completion];
  1341. }
  1342. - (void)presentPopoverFromRect:(CGRect)aRect
  1343. inView:(UIView *)aView
  1344. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1345. animated:(BOOL)aAnimated
  1346. options:(WYPopoverAnimationOptions)aOptions {
  1347. [self presentPopoverFromRect:aRect
  1348. inView:aView
  1349. permittedArrowDirections:aArrowDirections
  1350. animated:aAnimated
  1351. options:aOptions
  1352. completion:nil];
  1353. }
  1354. - (void)presentPopoverFromRect:(CGRect)aRect
  1355. inView:(UIView *)aView
  1356. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1357. animated:(BOOL)aAnimated
  1358. options:(WYPopoverAnimationOptions)aOptions
  1359. completion:(void (^)(void))completion {
  1360. NSAssert((aArrowDirections != WYPopoverArrowDirectionUnknown), @"WYPopoverArrowDirection must not be UNKNOWN");
  1361. _rect = aRect;
  1362. _inView = aView;
  1363. _permittedArrowDirections = aArrowDirections;
  1364. _animated = aAnimated;
  1365. options = aOptions;
  1366. if (!_inView) {
  1367. _inView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
  1368. if (CGRectIsEmpty(_rect)) {
  1369. _rect = CGRectMake((int)_inView.bounds.size.width / 2 - 5, (int)_inView.bounds.size.height / 2 - 5, 10, 10);
  1370. }
  1371. }
  1372. CGSize contentViewSize = self.popoverContentSize;
  1373. if (_overlayView == nil) {
  1374. _overlayView = [[WYPopoverOverlayView alloc] initWithFrame:_inView.window.bounds];
  1375. _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1376. _overlayView.autoresizesSubviews = NO;
  1377. _overlayView.delegate = self;
  1378. _overlayView.passthroughViews = _passthroughViews;
  1379. _backgroundView = [[WYPopoverBackgroundView alloc] initWithContentSize:contentViewSize];
  1380. _backgroundView.appearing = YES;
  1381. _backgroundView.delegate = self;
  1382. _backgroundView.hidden = YES;
  1383. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:_backgroundView action:@selector(tapOut)];
  1384. tap.cancelsTouchesInView = NO;
  1385. [_overlayView addGestureRecognizer:tap];
  1386. if (self.dismissOnTap) {
  1387. tap = [[UITapGestureRecognizer alloc] initWithTarget:_backgroundView action:@selector(tapOut)];
  1388. tap.cancelsTouchesInView = NO;
  1389. [_backgroundView addGestureRecognizer:tap];
  1390. }
  1391. [_inView.window addSubview:_backgroundView];
  1392. [_inView.window insertSubview:_overlayView belowSubview:_backgroundView];
  1393. }
  1394. [self updateThemeUI];
  1395. __weak __typeof__(self) weakSelf = self;
  1396. void (^completionBlock)(BOOL) = ^(BOOL animated) {
  1397. __typeof__(self) strongSelf = weakSelf;
  1398. if (strongSelf) {
  1399. if (_isObserverAdded == NO) {
  1400. _isObserverAdded = YES;
  1401. if ([strongSelf->_viewController respondsToSelector:@selector(preferredContentSize)]) {
  1402. [strongSelf->_viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize)) options:0 context:nil];
  1403. } else {
  1404. [strongSelf->_viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover)) options:0 context:nil];
  1405. }
  1406. }
  1407. strongSelf->_backgroundView.appearing = NO;
  1408. }
  1409. if (completion) {
  1410. completion();
  1411. } else if (strongSelf && strongSelf->_delegate && [strongSelf->_delegate respondsToSelector:@selector(popoverControllerDidPresentPopover:)]) {
  1412. [strongSelf->_delegate popoverControllerDidPresentPopover:strongSelf];
  1413. }
  1414. };
  1415. void (^adjustTintDimmed)() = ^() {
  1416. #ifdef WY_BASE_SDK_7_ENABLED
  1417. if (_backgroundView.dimsBackgroundViewsTintColor && [_inView.window respondsToSelector:@selector(setTintAdjustmentMode:)]) {
  1418. for (UIView *subview in _inView.window.subviews) {
  1419. if (subview != _backgroundView) {
  1420. [subview setTintAdjustmentMode:UIViewTintAdjustmentModeDimmed];
  1421. }
  1422. }
  1423. }
  1424. #endif
  1425. };
  1426. _backgroundView.hidden = NO;
  1427. if (_animated) {
  1428. if ((options & WYPopoverAnimationOptionFade) == WYPopoverAnimationOptionFade) {
  1429. _overlayView.alpha = 0;
  1430. _backgroundView.alpha = 0;
  1431. }
  1432. CGAffineTransform endTransform = _backgroundView.transform;
  1433. if ((options & WYPopoverAnimationOptionScale) == WYPopoverAnimationOptionScale) {
  1434. CGAffineTransform startTransform = [self transformForArrowDirection:_backgroundView.arrowDirection];
  1435. _backgroundView.transform = startTransform;
  1436. }
  1437. [UIView animateWithDuration:_animationDuration animations:^{
  1438. __typeof__(self) strongSelf = weakSelf;
  1439. if (strongSelf) {
  1440. strongSelf->_overlayView.alpha = 1;
  1441. strongSelf->_backgroundView.alpha = strongSelf->_backgroundView.preferredAlpha;
  1442. strongSelf->_backgroundView.transform = endTransform;
  1443. }
  1444. adjustTintDimmed();
  1445. } completion:^(BOOL finished) {
  1446. completionBlock(YES);
  1447. }];
  1448. } else {
  1449. adjustTintDimmed();
  1450. completionBlock(NO);
  1451. }
  1452. if (_isListeningNotifications == NO) {
  1453. _isListeningNotifications = YES;
  1454. [[NSNotificationCenter defaultCenter] addObserver:self
  1455. selector:@selector(didChangeStatusBarOrientation:)
  1456. name:UIApplicationDidChangeStatusBarOrientationNotification
  1457. object:nil];
  1458. [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  1459. [[NSNotificationCenter defaultCenter] addObserver:self
  1460. selector:@selector(didChangeDeviceOrientation:)
  1461. name:UIDeviceOrientationDidChangeNotification
  1462. object:nil];
  1463. [[NSNotificationCenter defaultCenter] addObserver:self
  1464. selector:@selector(keyboardWillShow:)
  1465. name:UIKeyboardWillShowNotification object:nil];
  1466. [[NSNotificationCenter defaultCenter] addObserver:self
  1467. selector:@selector(keyboardWillHide:)
  1468. name:UIKeyboardWillHideNotification object:nil];
  1469. }
  1470. }
  1471. - (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
  1472. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1473. animated:(BOOL)aAnimated {
  1474. [self presentPopoverFromBarButtonItem:aItem
  1475. permittedArrowDirections:aArrowDirections
  1476. animated:aAnimated
  1477. completion:nil];
  1478. }
  1479. - (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
  1480. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1481. animated:(BOOL)aAnimated
  1482. completion:(void (^)(void))completion {
  1483. [self presentPopoverFromBarButtonItem:aItem
  1484. permittedArrowDirections:aArrowDirections
  1485. animated:aAnimated
  1486. options:WYPopoverAnimationOptionFade
  1487. completion:completion];
  1488. }
  1489. - (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
  1490. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1491. animated:(BOOL)aAnimated
  1492. options:(WYPopoverAnimationOptions)aOptions {
  1493. [self presentPopoverFromBarButtonItem:aItem
  1494. permittedArrowDirections:aArrowDirections
  1495. animated:aAnimated
  1496. options:aOptions
  1497. completion:nil];
  1498. }
  1499. - (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
  1500. permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
  1501. animated:(BOOL)aAnimated
  1502. options:(WYPopoverAnimationOptions)aOptions
  1503. completion:(void (^)(void))completion {
  1504. _barButtonItem = aItem;
  1505. UIView *itemView = [_barButtonItem valueForKey:@"view"];
  1506. aArrowDirections = WYPopoverArrowDirectionDown | WYPopoverArrowDirectionUp;
  1507. [self presentPopoverFromRect:itemView.bounds
  1508. inView:itemView
  1509. permittedArrowDirections:aArrowDirections
  1510. animated:aAnimated
  1511. options:aOptions
  1512. completion:completion];
  1513. }
  1514. - (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated {
  1515. [self presentPopoverAsDialogAnimated:aAnimated
  1516. completion:nil];
  1517. }
  1518. - (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
  1519. completion:(void (^)(void))completion {
  1520. [self presentPopoverAsDialogAnimated:aAnimated
  1521. options:WYPopoverAnimationOptionFade
  1522. completion:completion];
  1523. }
  1524. - (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
  1525. options:(WYPopoverAnimationOptions)aOptions {
  1526. [self presentPopoverAsDialogAnimated:aAnimated
  1527. options:aOptions
  1528. completion:nil];
  1529. }
  1530. - (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
  1531. options:(WYPopoverAnimationOptions)aOptions
  1532. completion:(void (^)(void))completion {
  1533. [self presentPopoverFromRect:CGRectZero
  1534. inView:nil
  1535. permittedArrowDirections:WYPopoverArrowDirectionNone
  1536. animated:aAnimated
  1537. options:aOptions
  1538. completion:completion];
  1539. }
  1540. - (CGAffineTransform)transformForArrowDirection:(WYPopoverArrowDirection)arrowDirection {
  1541. CGAffineTransform transform = _backgroundView.transform;
  1542. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  1543. CGSize containerViewSize = _backgroundView.frame.size;
  1544. if (_backgroundView.arrowHeight > 0) {
  1545. if (UIInterfaceOrientationIsLandscape(orientation)) {
  1546. containerViewSize.width = _backgroundView.frame.size.height;
  1547. containerViewSize.height = _backgroundView.frame.size.width;
  1548. }
  1549. //WY_LOG(@"containerView.arrowOffset = %f", containerView.arrowOffset);
  1550. //WY_LOG(@"containerViewSize = %@", NSStringFromCGSize(containerViewSize));
  1551. //WY_LOG(@"orientation = %@", WYStringFromOrientation(orientation));
  1552. if (arrowDirection == WYPopoverArrowDirectionDown) {
  1553. transform = CGAffineTransformTranslate(transform, _backgroundView.arrowOffset, containerViewSize.height / 2);
  1554. }
  1555. if (arrowDirection == WYPopoverArrowDirectionUp) {
  1556. transform = CGAffineTransformTranslate(transform, _backgroundView.arrowOffset, -containerViewSize.height / 2);
  1557. }
  1558. if (arrowDirection == WYPopoverArrowDirectionRight) {
  1559. transform = CGAffineTransformTranslate(transform, containerViewSize.width / 2, _backgroundView.arrowOffset);
  1560. }
  1561. if (arrowDirection == WYPopoverArrowDirectionLeft) {
  1562. transform = CGAffineTransformTranslate(transform, -containerViewSize.width / 2, _backgroundView.arrowOffset);
  1563. }
  1564. }
  1565. transform = CGAffineTransformScale(transform, 0.01, 0.01);
  1566. return transform;
  1567. }
  1568. - (void)setPopoverNavigationBarBackgroundImage {
  1569. if ([_viewController isKindOfClass:[UINavigationController class]] == YES) {
  1570. UINavigationController *navigationController = (UINavigationController *)_viewController;
  1571. navigationController.wy_embedInPopover = YES;
  1572. #ifdef WY_BASE_SDK_7_ENABLED
  1573. if ([navigationController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
  1574. UIViewController *topViewController = [navigationController topViewController];
  1575. [topViewController setEdgesForExtendedLayout:UIRectEdgeNone];
  1576. }
  1577. #endif
  1578. if (_wantsDefaultContentAppearance == NO) {
  1579. [navigationController.navigationBar setBackgroundImage:[UIImage wy_imageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault];
  1580. }
  1581. }
  1582. _viewController.view.clipsToBounds = YES;
  1583. if (_backgroundView.borderWidth == 0) {
  1584. _viewController.view.layer.cornerRadius = _backgroundView.outerCornerRadius;
  1585. }
  1586. }
  1587. - (void)positionPopover:(BOOL)aAnimated {
  1588. CGRect savedContainerFrame = _backgroundView.frame;
  1589. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  1590. CGSize contentViewSize = self.popoverContentSize;
  1591. CGSize minContainerSize = WY_POPOVER_MIN_SIZE;
  1592. CGRect viewFrame;
  1593. CGRect containerFrame = CGRectZero;
  1594. float minX, maxX, minY, maxY, offset = 0;
  1595. CGSize containerViewSize = CGSizeZero;
  1596. float overlayWidth;
  1597. float overlayHeight;
  1598. float keyboardHeight;
  1599. if (_ignoreOrientation) {
  1600. overlayWidth = _overlayView.window.frame.size.width;
  1601. overlayHeight = _overlayView.window.frame.size.height;
  1602. CGRect convertedFrame = [_overlayView.window convertRect:WYKeyboardListener.rect toView:_overlayView];
  1603. keyboardHeight = convertedFrame.size.height;
  1604. } else {
  1605. overlayWidth = UIInterfaceOrientationIsPortrait(orientation) ? _overlayView.bounds.size.width : _overlayView.bounds.size.height;
  1606. overlayHeight = UIInterfaceOrientationIsPortrait(orientation) ? _overlayView.bounds.size.height : _overlayView.bounds.size.width;
  1607. keyboardHeight = UIInterfaceOrientationIsPortrait(orientation) ? WYKeyboardListener.rect.size.height : WYKeyboardListener.rect.size.width;
  1608. }
  1609. if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
  1610. BOOL shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
  1611. if (shouldIgnore) {
  1612. keyboardHeight = 0;
  1613. }
  1614. }
  1615. WYPopoverArrowDirection arrowDirection = _permittedArrowDirections;
  1616. _overlayView.bounds = _inView.window.bounds;
  1617. _backgroundView.transform = CGAffineTransformIdentity;
  1618. viewFrame = [_inView convertRect:_rect toView:nil];
  1619. viewFrame = WYRectInWindowBounds(viewFrame, orientation);
  1620. minX = _popoverLayoutMargins.left;
  1621. maxX = overlayWidth - _popoverLayoutMargins.right;
  1622. minY = WYStatusBarHeight() + _popoverLayoutMargins.top;
  1623. maxY = overlayHeight - _popoverLayoutMargins.bottom - keyboardHeight;
  1624. // Which direction ?
  1625. //
  1626. arrowDirection = [self arrowDirectionForRect:_rect
  1627. inView:_inView
  1628. contentSize:contentViewSize
  1629. arrowHeight:_backgroundView.arrowHeight
  1630. permittedArrowDirections:arrowDirection];
  1631. // Position of the popover
  1632. //
  1633. minX -= _backgroundView.outerShadowInsets.left;
  1634. maxX += _backgroundView.outerShadowInsets.right;
  1635. minY -= _backgroundView.outerShadowInsets.top;
  1636. maxY += _backgroundView.outerShadowInsets.bottom;
  1637. if (arrowDirection == WYPopoverArrowDirectionDown) {
  1638. _backgroundView.arrowDirection = WYPopoverArrowDirectionDown;
  1639. containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
  1640. containerFrame = CGRectZero;
  1641. containerFrame.size = containerViewSize;
  1642. containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
  1643. containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
  1644. _backgroundView.frame = CGRectIntegral(containerFrame);
  1645. _backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
  1646. containerFrame = _backgroundView.frame;
  1647. offset = 0;
  1648. if (containerFrame.origin.x < minX) {
  1649. offset = minX - containerFrame.origin.x;
  1650. containerFrame.origin.x = minX;
  1651. offset = -offset;
  1652. } else if (containerFrame.origin.x + containerFrame.size.width > maxX) {
  1653. offset = (_backgroundView.frame.origin.x + _backgroundView.frame.size.width) - maxX;
  1654. containerFrame.origin.x -= offset;
  1655. }
  1656. _backgroundView.arrowOffset = offset;
  1657. offset = _backgroundView.frame.size.height / 2 + viewFrame.size.height / 2 - _backgroundView.outerShadowInsets.bottom;
  1658. containerFrame.origin.y -= offset;
  1659. if (containerFrame.origin.y < minY) {
  1660. offset = minY - containerFrame.origin.y;
  1661. containerFrame.size.height -= offset;
  1662. if (containerFrame.size.height < minContainerSize.height) {
  1663. // popover is overflowing
  1664. offset -= (minContainerSize.height - containerFrame.size.height);
  1665. containerFrame.size.height = minContainerSize.height;
  1666. }
  1667. containerFrame.origin.y += offset;
  1668. }
  1669. }
  1670. if (arrowDirection == WYPopoverArrowDirectionUp) {
  1671. _backgroundView.arrowDirection = WYPopoverArrowDirectionUp;
  1672. containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
  1673. containerFrame = CGRectZero;
  1674. containerFrame.size = containerViewSize;
  1675. containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
  1676. containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
  1677. _backgroundView.frame = containerFrame;
  1678. _backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
  1679. containerFrame = _backgroundView.frame;
  1680. offset = 0;
  1681. if (containerFrame.origin.x < minX) {
  1682. offset = minX - containerFrame.origin.x;
  1683. containerFrame.origin.x = minX;
  1684. offset = -offset;
  1685. }
  1686. else if (containerFrame.origin.x + containerFrame.size.width > maxX) {
  1687. offset = (_backgroundView.frame.origin.x + _backgroundView.frame.size.width) - maxX;
  1688. containerFrame.origin.x -= offset;
  1689. }
  1690. _backgroundView.arrowOffset = offset;
  1691. offset = _backgroundView.frame.size.height / 2 + viewFrame.size.height / 2 - _backgroundView.outerShadowInsets.top;
  1692. containerFrame.origin.y += offset;
  1693. if (containerFrame.origin.y + containerFrame.size.height > maxY) {
  1694. offset = (containerFrame.origin.y + containerFrame.size.height) - maxY;
  1695. containerFrame.size.height -= offset;
  1696. if (containerFrame.size.height < minContainerSize.height) {
  1697. // popover is overflowing
  1698. containerFrame.size.height = minContainerSize.height;
  1699. }
  1700. }
  1701. }
  1702. if (arrowDirection == WYPopoverArrowDirectionRight) {
  1703. _backgroundView.arrowDirection = WYPopoverArrowDirectionRight;
  1704. containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
  1705. containerFrame = CGRectZero;
  1706. containerFrame.size = containerViewSize;
  1707. containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
  1708. containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
  1709. _backgroundView.frame = CGRectIntegral(containerFrame);
  1710. _backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
  1711. containerFrame = _backgroundView.frame;
  1712. offset = _backgroundView.frame.size.width / 2 + viewFrame.size.width / 2 - _backgroundView.outerShadowInsets.right;
  1713. containerFrame.origin.x -= offset;
  1714. if (containerFrame.origin.x < minX) {
  1715. offset = minX - containerFrame.origin.x;
  1716. containerFrame.size.width -= offset;
  1717. if (containerFrame.size.width < minContainerSize.width) {
  1718. // popover is overflowing
  1719. offset -= (minContainerSize.width - containerFrame.size.width);
  1720. containerFrame.size.width = minContainerSize.width;
  1721. }
  1722. containerFrame.origin.x += offset;
  1723. }
  1724. offset = 0;
  1725. if (containerFrame.origin.y < minY) {
  1726. offset = minY - containerFrame.origin.y;
  1727. containerFrame.origin.y = minY;
  1728. offset = -offset;
  1729. } else if (containerFrame.origin.y + containerFrame.size.height > maxY) {
  1730. offset = (_backgroundView.frame.origin.y + _backgroundView.frame.size.height) - maxY;
  1731. containerFrame.origin.y -= offset;
  1732. }
  1733. _backgroundView.arrowOffset = offset;
  1734. }
  1735. if (arrowDirection == WYPopoverArrowDirectionLeft) {
  1736. _backgroundView.arrowDirection = WYPopoverArrowDirectionLeft;
  1737. containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
  1738. containerFrame = CGRectZero;
  1739. containerFrame.size = containerViewSize;
  1740. containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
  1741. containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
  1742. _backgroundView.frame = containerFrame;
  1743. _backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
  1744. containerFrame = CGRectIntegral(_backgroundView.frame);
  1745. offset = _backgroundView.frame.size.width / 2 + viewFrame.size.width / 2 - _backgroundView.outerShadowInsets.left;
  1746. containerFrame.origin.x += offset;
  1747. if (containerFrame.origin.x + containerFrame.size.width > maxX) {
  1748. offset = (containerFrame.origin.x + containerFrame.size.width) - maxX;
  1749. containerFrame.size.width -= offset;
  1750. if (containerFrame.size.width < minContainerSize.width) {
  1751. // popover is overflowing
  1752. containerFrame.size.width = minContainerSize.width;
  1753. }
  1754. }
  1755. offset = 0;
  1756. if (containerFrame.origin.y < minY) {
  1757. offset = minY - containerFrame.origin.y;
  1758. containerFrame.origin.y = minY;
  1759. offset = -offset;
  1760. } else if (containerFrame.origin.y + containerFrame.size.height > maxY) {
  1761. offset = (_backgroundView.frame.origin.y + _backgroundView.frame.size.height) - maxY;
  1762. containerFrame.origin.y -= offset;
  1763. }
  1764. _backgroundView.arrowOffset = offset;
  1765. }
  1766. if (arrowDirection == WYPopoverArrowDirectionNone) {
  1767. _backgroundView.arrowDirection = WYPopoverArrowDirectionNone;
  1768. containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
  1769. containerFrame = CGRectZero;
  1770. containerFrame.size = containerViewSize;
  1771. containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
  1772. containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
  1773. _backgroundView.frame = CGRectIntegral(containerFrame);
  1774. _backgroundView.center = CGPointMake(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2);
  1775. containerFrame = _backgroundView.frame;
  1776. _backgroundView.arrowOffset = offset;
  1777. }
  1778. containerFrame = CGRectIntegral(containerFrame);
  1779. _backgroundView.frame = containerFrame;
  1780. _backgroundView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
  1781. [_backgroundView setViewController:_viewController];
  1782. // keyboard support
  1783. if (keyboardHeight > 0) {
  1784. float keyboardY = overlayHeight - keyboardHeight;
  1785. float yOffset = containerFrame.origin.y + containerFrame.size.height - keyboardY;
  1786. if (yOffset > 0) {
  1787. if (containerFrame.origin.y - yOffset < minY) {
  1788. yOffset -= minY - (containerFrame.origin.y - yOffset);
  1789. }
  1790. if ([_delegate respondsToSelector:@selector(popoverController:willTranslatePopoverWithYOffset:)]) {
  1791. [_delegate popoverController:self willTranslatePopoverWithYOffset:&yOffset];
  1792. }
  1793. containerFrame.origin.y -= yOffset;
  1794. }
  1795. }
  1796. CGPoint containerOrigin = containerFrame.origin;
  1797. _backgroundView.transform = CGAffineTransformMakeRotation(WYInterfaceOrientationAngleOfOrientation(orientation));
  1798. containerFrame = _backgroundView.frame;
  1799. containerFrame.origin = WYPointRelativeToOrientation(containerOrigin, containerFrame.size, orientation);
  1800. if (aAnimated == YES && !self.implicitAnimationsDisabled) {
  1801. _backgroundView.frame = savedContainerFrame;
  1802. __weak __typeof__(self) weakSelf = self;
  1803. [UIView animateWithDuration:0.10f animations:^{
  1804. __typeof__(self) strongSelf = weakSelf;
  1805. strongSelf->_backgroundView.frame = containerFrame;
  1806. }];
  1807. } else {
  1808. _backgroundView.frame = containerFrame;
  1809. }
  1810. [_backgroundView setNeedsDisplay];
  1811. // WY_LOG(@"popoverContainerView.frame = %@", NSStringFromCGRect(_backgroundView.frame));
  1812. }
  1813. - (void)dismissPopoverAnimated:(BOOL)aAnimated {
  1814. [self dismissPopoverAnimated:aAnimated
  1815. options:options
  1816. completion:nil];
  1817. }
  1818. - (void)dismissPopoverAnimated:(BOOL)aAnimated
  1819. completion:(void (^)(void))completion {
  1820. [self dismissPopoverAnimated:aAnimated
  1821. options:options
  1822. completion:completion];
  1823. }
  1824. - (void)dismissPopoverAnimated:(BOOL)aAnimated
  1825. options:(WYPopoverAnimationOptions)aOptions {
  1826. [self dismissPopoverAnimated:aAnimated
  1827. options:aOptions
  1828. completion:nil];
  1829. }
  1830. - (void)dismissPopoverAnimated:(BOOL)aAnimated
  1831. options:(WYPopoverAnimationOptions)aOptions
  1832. completion:(void (^)(void))completion {
  1833. [self dismissPopoverAnimated:aAnimated
  1834. options:aOptions
  1835. completion:completion
  1836. callDelegate:NO];
  1837. }
  1838. - (void)dismissPopoverAnimated:(BOOL)aAnimated
  1839. options:(WYPopoverAnimationOptions)aOptions
  1840. completion:(void (^)(void))completion
  1841. callDelegate:(BOOL)callDelegate {
  1842. float duration = self.animationDuration;
  1843. WYPopoverAnimationOptions style = aOptions;
  1844. __weak __typeof__(self) weakSelf = self;
  1845. void (^adjustTintAutomatic)() = ^() {
  1846. #ifdef WY_BASE_SDK_7_ENABLED
  1847. if ([_inView.window respondsToSelector:@selector(setTintAdjustmentMode:)]) {
  1848. for (UIView *subview in _inView.window.subviews) {
  1849. if (subview != _backgroundView) {
  1850. [subview setTintAdjustmentMode:UIViewTintAdjustmentModeAutomatic];
  1851. }
  1852. }
  1853. }
  1854. #endif
  1855. };
  1856. void (^completionBlock)() = ^() {
  1857. __typeof__(self) strongSelf = weakSelf;
  1858. if (strongSelf) {
  1859. [strongSelf->_backgroundView removeFromSuperview];
  1860. strongSelf->_backgroundView = nil;
  1861. [strongSelf->_overlayView removeFromSuperview];
  1862. strongSelf->_overlayView = nil;
  1863. // inView is captured strongly in presentPopoverInRect:... method, so it needs to be released in dismiss method to avoid potential retain cycles
  1864. strongSelf->_inView = nil;
  1865. }
  1866. if (completion) {
  1867. completion();
  1868. }
  1869. else if (callDelegate && strongSelf && strongSelf->_delegate && [strongSelf->_delegate respondsToSelector:@selector(popoverControllerDidDismissPopover:)]) {
  1870. [strongSelf->_delegate popoverControllerDidDismissPopover:strongSelf];
  1871. }
  1872. if (self.dismissCompletionBlock) {
  1873. self.dismissCompletionBlock(strongSelf);
  1874. }
  1875. };
  1876. if (_isListeningNotifications == YES) {
  1877. _isListeningNotifications = NO;
  1878. [[NSNotificationCenter defaultCenter] removeObserver:self
  1879. name:UIApplicationDidChangeStatusBarOrientationNotification
  1880. object:nil];
  1881. [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
  1882. [[NSNotificationCenter defaultCenter] removeObserver:self
  1883. name:UIDeviceOrientationDidChangeNotification
  1884. object:nil];
  1885. [[NSNotificationCenter defaultCenter] removeObserver:self
  1886. name:UIKeyboardWillShowNotification
  1887. object:nil];
  1888. [[NSNotificationCenter defaultCenter] removeObserver:self
  1889. name:UIKeyboardWillHideNotification
  1890. object:nil];
  1891. }
  1892. @try {
  1893. if (_isObserverAdded == YES) {
  1894. _isObserverAdded = NO;
  1895. if ([_viewController respondsToSelector:@selector(preferredContentSize)]) {
  1896. [_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize))];
  1897. } else {
  1898. [_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover))];
  1899. }
  1900. }
  1901. }
  1902. @catch (NSException * __unused exception) {}
  1903. if (aAnimated && !self.implicitAnimationsDisabled) {
  1904. [UIView animateWithDuration:duration animations:^{
  1905. __typeof__(self) strongSelf = weakSelf;
  1906. if (strongSelf) {
  1907. if ((style & WYPopoverAnimationOptionFade) == WYPopoverAnimationOptionFade) {
  1908. strongSelf->_backgroundView.alpha = 0;
  1909. }
  1910. if ((style & WYPopoverAnimationOptionScale) == WYPopoverAnimationOptionScale) {
  1911. CGAffineTransform endTransform = [self transformForArrowDirection:strongSelf->_backgroundView.arrowDirection];
  1912. strongSelf->_backgroundView.transform = endTransform;
  1913. }
  1914. strongSelf->_overlayView.alpha = 0;
  1915. }
  1916. adjustTintAutomatic();
  1917. } completion:^(BOOL finished) {
  1918. completionBlock();
  1919. }];
  1920. } else {
  1921. adjustTintAutomatic();
  1922. completionBlock();
  1923. }
  1924. }
  1925. #pragma mark KVO
  1926. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  1927. if (object == _viewController) {
  1928. if ([keyPath isEqualToString:NSStringFromSelector(@selector(preferredContentSize))]
  1929. || [keyPath isEqualToString:NSStringFromSelector(@selector(contentSizeForViewInPopover))]) {
  1930. CGSize contentSize = [self topViewControllerContentSize];
  1931. [self setPopoverContentSize:contentSize];
  1932. }
  1933. } else if (object == _theme) {
  1934. [self updateThemeUI];
  1935. }
  1936. }
  1937. #pragma mark WYPopoverOverlayViewDelegate
  1938. - (void)popoverOverlayViewDidTouch:(WYPopoverOverlayView *)aOverlayView {
  1939. BOOL shouldDismiss = !_viewController.modalInPopover;
  1940. if (shouldDismiss && _delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldDismissPopover:)]) {
  1941. shouldDismiss = [_delegate popoverControllerShouldDismissPopover:self];
  1942. }
  1943. if (shouldDismiss) {
  1944. [self dismissPopoverAnimated:_animated options:options completion:nil callDelegate:YES];
  1945. }
  1946. }
  1947. #pragma mark WYPopoverBackgroundViewDelegate
  1948. - (void)popoverBackgroundViewDidTouchOutside:(WYPopoverBackgroundView *)aBackgroundView {
  1949. [self popoverOverlayViewDidTouch:nil];
  1950. }
  1951. #pragma mark Private
  1952. - (WYPopoverArrowDirection)arrowDirectionForRect:(CGRect)aRect
  1953. inView:(UIView *)aView
  1954. contentSize:(CGSize)contentSize
  1955. arrowHeight:(float)arrowHeight
  1956. permittedArrowDirections:(WYPopoverArrowDirection)arrowDirections {
  1957. WYPopoverArrowDirection arrowDirection = WYPopoverArrowDirectionUnknown;
  1958. NSMutableArray *areas = [NSMutableArray arrayWithCapacity:0];
  1959. WYPopoverArea *area;
  1960. if ((arrowDirections & WYPopoverArrowDirectionDown) == WYPopoverArrowDirectionDown) {
  1961. area = [[WYPopoverArea alloc] init];
  1962. area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionDown];
  1963. area.arrowDirection = WYPopoverArrowDirectionDown;
  1964. [areas addObject:area];
  1965. }
  1966. if ((arrowDirections & WYPopoverArrowDirectionUp) == WYPopoverArrowDirectionUp) {
  1967. area = [[WYPopoverArea alloc] init];
  1968. area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionUp];
  1969. area.arrowDirection = WYPopoverArrowDirectionUp;
  1970. [areas addObject:area];
  1971. }
  1972. if ((arrowDirections & WYPopoverArrowDirectionLeft) == WYPopoverArrowDirectionLeft) {
  1973. area = [[WYPopoverArea alloc] init];
  1974. area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionLeft];
  1975. area.arrowDirection = WYPopoverArrowDirectionLeft;
  1976. [areas addObject:area];
  1977. }
  1978. if ((arrowDirections & WYPopoverArrowDirectionRight) == WYPopoverArrowDirectionRight) {
  1979. area = [[WYPopoverArea alloc] init];
  1980. area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionRight];
  1981. area.arrowDirection = WYPopoverArrowDirectionRight;
  1982. [areas addObject:area];
  1983. }
  1984. if ((arrowDirections & WYPopoverArrowDirectionNone) == WYPopoverArrowDirectionNone) {
  1985. area = [[WYPopoverArea alloc] init];
  1986. area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionNone];
  1987. area.arrowDirection = WYPopoverArrowDirectionNone;
  1988. [areas addObject:area];
  1989. }
  1990. if ([areas count] > 1) {
  1991. NSIndexSet* indexes = [areas indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
  1992. WYPopoverArea* popoverArea = (WYPopoverArea*)obj;
  1993. BOOL result = (popoverArea.areaSize.width > 0 && popoverArea.areaSize.height > 0);
  1994. return result;
  1995. }];
  1996. areas = [NSMutableArray arrayWithArray:[areas objectsAtIndexes:indexes]];
  1997. }
  1998. [areas sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
  1999. WYPopoverArea *area1 = (WYPopoverArea *)obj1;
  2000. WYPopoverArea *area2 = (WYPopoverArea *)obj2;
  2001. float val1 = area1.value;
  2002. float val2 = area2.value;
  2003. NSComparisonResult result = NSOrderedSame;
  2004. if (val1 > val2) {
  2005. result = NSOrderedAscending;
  2006. } else if (val1 < val2) {
  2007. result = NSOrderedDescending;
  2008. }
  2009. return result;
  2010. }];
  2011. for (NSUInteger i = 0; i < [areas count]; i++) {
  2012. WYPopoverArea *popoverArea = (WYPopoverArea *)[areas objectAtIndex:i];
  2013. if (popoverArea.areaSize.width >= contentSize.width) {
  2014. arrowDirection = popoverArea.arrowDirection;
  2015. break;
  2016. }
  2017. }
  2018. if (arrowDirection == WYPopoverArrowDirectionUnknown) {
  2019. if ([areas count] > 0) {
  2020. arrowDirection = ((WYPopoverArea *)[areas objectAtIndex:0]).arrowDirection;
  2021. } else {
  2022. if ((arrowDirections & WYPopoverArrowDirectionDown) == WYPopoverArrowDirectionDown) {
  2023. arrowDirection = WYPopoverArrowDirectionDown;
  2024. } else if ((arrowDirections & WYPopoverArrowDirectionUp) == WYPopoverArrowDirectionUp) {
  2025. arrowDirection = WYPopoverArrowDirectionUp;
  2026. } else if ((arrowDirections & WYPopoverArrowDirectionLeft) == WYPopoverArrowDirectionLeft) {
  2027. arrowDirection = WYPopoverArrowDirectionLeft;
  2028. } else {
  2029. arrowDirection = WYPopoverArrowDirectionRight;
  2030. }
  2031. }
  2032. }
  2033. return arrowDirection;
  2034. }
  2035. - (CGSize)sizeForRect:(CGRect)aRect
  2036. inView:(UIView *)aView
  2037. arrowHeight:(float)arrowHeight
  2038. arrowDirection:(WYPopoverArrowDirection)arrowDirection {
  2039. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  2040. CGRect viewFrame = [aView convertRect:aRect toView:nil];
  2041. viewFrame = WYRectInWindowBounds(viewFrame, orientation);
  2042. float minX, maxX, minY, maxY = 0;
  2043. float keyboardHeight = WYKeyboardListener.rect.size.height;
  2044. float overlayWidth = _overlayView.bounds.size.width;
  2045. float overlayHeight = _overlayView.bounds.size.height;
  2046. if (!_ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation)) {
  2047. keyboardHeight = WYKeyboardListener.rect.size.width;
  2048. overlayWidth = _overlayView.bounds.size.height;
  2049. overlayHeight = _overlayView.bounds.size.width;
  2050. }
  2051. if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
  2052. BOOL shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
  2053. if (shouldIgnore) {
  2054. keyboardHeight = 0;
  2055. }
  2056. }
  2057. minX = _popoverLayoutMargins.left;
  2058. maxX = overlayWidth - _popoverLayoutMargins.right;
  2059. minY = WYStatusBarHeight() + _popoverLayoutMargins.top;
  2060. maxY = overlayHeight - _popoverLayoutMargins.bottom - keyboardHeight;
  2061. CGSize result = CGSizeZero;
  2062. if (arrowDirection == WYPopoverArrowDirectionLeft) {
  2063. result.width = maxX - (viewFrame.origin.x + viewFrame.size.width);
  2064. result.width -= arrowHeight;
  2065. result.height = maxY - minY;
  2066. } else if (arrowDirection == WYPopoverArrowDirectionRight) {
  2067. result.width = viewFrame.origin.x - minX;
  2068. result.width -= arrowHeight;
  2069. result.height = maxY - minY;
  2070. } else if (arrowDirection == WYPopoverArrowDirectionDown) {
  2071. result.width = maxX - minX;
  2072. result.height = viewFrame.origin.y - minY;
  2073. result.height -= arrowHeight;
  2074. } else if (arrowDirection == WYPopoverArrowDirectionUp) {
  2075. result.width = maxX - minX;
  2076. result.height = maxY - (viewFrame.origin.y + viewFrame.size.height);
  2077. result.height -= arrowHeight;
  2078. } else if (arrowDirection == WYPopoverArrowDirectionNone) {
  2079. result.width = maxX - minX;
  2080. result.height = maxY - minY;
  2081. }
  2082. return result;
  2083. }
  2084. #pragma mark Inline functions
  2085. static BOOL compileUsingIOS8SDK() {
  2086. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
  2087. return YES;
  2088. #endif
  2089. return NO;
  2090. }
  2091. __unused static NSString* WYStringFromOrientation(NSInteger orientation) {
  2092. NSString *result = @"Unknown";
  2093. switch (orientation) {
  2094. case UIInterfaceOrientationPortrait:
  2095. result = @"Portrait";
  2096. break;
  2097. case UIInterfaceOrientationPortraitUpsideDown:
  2098. result = @"Portrait UpsideDown";
  2099. break;
  2100. case UIInterfaceOrientationLandscapeLeft:
  2101. result = @"Landscape Left";
  2102. break;
  2103. case UIInterfaceOrientationLandscapeRight:
  2104. result = @"Landscape Right";
  2105. break;
  2106. default:
  2107. break;
  2108. }
  2109. return result;
  2110. }
  2111. static float WYStatusBarHeight() {
  2112. if (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
  2113. CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
  2114. return statusBarFrame.size.height;
  2115. } else {
  2116. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  2117. float statusBarHeight = 0;
  2118. CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
  2119. statusBarHeight = statusBarFrame.size.height;
  2120. if (UIInterfaceOrientationIsLandscape(orientation))
  2121. {
  2122. statusBarHeight = statusBarFrame.size.width;
  2123. }
  2124. return statusBarHeight;
  2125. }
  2126. }
  2127. static float WYInterfaceOrientationAngleOfOrientation(UIInterfaceOrientation orientation) {
  2128. float angle;
  2129. // no transformation needed in iOS 8
  2130. if (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
  2131. angle = 0.0;
  2132. } else {
  2133. switch (orientation) {
  2134. case UIInterfaceOrientationPortraitUpsideDown:
  2135. angle = M_PI;
  2136. break;
  2137. case UIInterfaceOrientationLandscapeLeft:
  2138. angle = -M_PI_2;
  2139. break;
  2140. case UIInterfaceOrientationLandscapeRight:
  2141. angle = M_PI_2;
  2142. break;
  2143. default:
  2144. angle = 0.0;
  2145. break;
  2146. }
  2147. }
  2148. return angle;
  2149. }
  2150. static CGRect WYRectInWindowBounds(CGRect rect, UIInterfaceOrientation orientation) {
  2151. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
  2152. float windowWidth = keyWindow.bounds.size.width;
  2153. float windowHeight = keyWindow.bounds.size.height;
  2154. CGRect result = rect;
  2155. if (!(compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])) {
  2156. if (orientation == UIInterfaceOrientationLandscapeRight) {
  2157. result.origin.x = rect.origin.y;
  2158. result.origin.y = windowWidth - rect.origin.x - rect.size.width;
  2159. result.size.width = rect.size.height;
  2160. result.size.height = rect.size.width;
  2161. }
  2162. if (orientation == UIInterfaceOrientationLandscapeLeft) {
  2163. result.origin.x = windowHeight - rect.origin.y - rect.size.height;
  2164. result.origin.y = rect.origin.x;
  2165. result.size.width = rect.size.height;
  2166. result.size.height = rect.size.width;
  2167. }
  2168. if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
  2169. result.origin.x = windowWidth - rect.origin.x - rect.size.width;
  2170. result.origin.y = windowHeight - rect.origin.y - rect.size.height;
  2171. }
  2172. }
  2173. return result;
  2174. }
  2175. static CGPoint WYPointRelativeToOrientation(CGPoint origin, CGSize size, UIInterfaceOrientation orientation) {
  2176. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
  2177. float windowWidth = keyWindow.bounds.size.width;
  2178. float windowHeight = keyWindow.bounds.size.height;
  2179. CGPoint result = origin;
  2180. if (!(compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])) {
  2181. if (orientation == UIInterfaceOrientationLandscapeRight) {
  2182. result.x = windowWidth - origin.y - size.width;
  2183. result.y = origin.x;
  2184. }
  2185. if (orientation == UIInterfaceOrientationLandscapeLeft) {
  2186. result.x = origin.y;
  2187. result.y = windowHeight - origin.x - size.height;
  2188. }
  2189. if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
  2190. result.x = windowWidth - origin.x - size.width;
  2191. result.y = windowHeight - origin.y - size.height;
  2192. }
  2193. }
  2194. return result;
  2195. }
  2196. #pragma mark Selectors
  2197. - (void)didChangeStatusBarOrientation:(NSNotification *)notification {
  2198. _isInterfaceOrientationChanging = YES;
  2199. }
  2200. - (void)didChangeDeviceOrientation:(NSNotification *)notification {
  2201. if (_isInterfaceOrientationChanging == NO) return;
  2202. _isInterfaceOrientationChanging = NO;
  2203. if ([_viewController isKindOfClass:[UINavigationController class]]) {
  2204. UINavigationController* navigationController = (UINavigationController*)_viewController;
  2205. if (navigationController.navigationBarHidden == NO) {
  2206. navigationController.navigationBarHidden = YES;
  2207. navigationController.navigationBarHidden = NO;
  2208. }
  2209. }
  2210. if (_barButtonItem) {
  2211. _inView = [_barButtonItem valueForKey:@"view"];
  2212. _rect = _inView.bounds;
  2213. } else if ([_delegate respondsToSelector:@selector(popoverController:willRepositionPopoverToRect:inView:)]) {
  2214. CGRect anotherRect;
  2215. UIView *anotherInView;
  2216. [_delegate popoverController:self willRepositionPopoverToRect:&anotherRect inView:&anotherInView];
  2217. #pragma clang diagnostic push
  2218. #pragma GCC diagnostic ignored "-Wtautological-pointer-compare"
  2219. if (&anotherRect != NULL) {
  2220. _rect = anotherRect;
  2221. }
  2222. if (&anotherInView != NULL) {
  2223. _inView = anotherInView;
  2224. }
  2225. #pragma GCC diagnostic pop
  2226. }
  2227. [self positionPopover:NO];
  2228. }
  2229. - (void)keyboardWillShow:(NSNotification *)notification {
  2230. //UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  2231. //WY_LOG(@"orientation = %@", WYStringFromOrientation(orientation));
  2232. //WY_LOG(@"WYKeyboardListener.rect = %@", NSStringFromCGRect(WYKeyboardListener.rect));
  2233. BOOL shouldIgnore = NO;
  2234. if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
  2235. shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
  2236. }
  2237. if (shouldIgnore == NO) {
  2238. [self positionPopover:YES];
  2239. }
  2240. }
  2241. - (void)keyboardWillHide:(NSNotification *)notification {
  2242. BOOL shouldIgnore = NO;
  2243. if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
  2244. shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
  2245. }
  2246. if (shouldIgnore == NO) {
  2247. [self positionPopover:YES];
  2248. }
  2249. }
  2250. #pragma mark Memory management
  2251. - (void)dealloc {
  2252. [[NSNotificationCenter defaultCenter] removeObserver:self];
  2253. [_backgroundView removeFromSuperview];
  2254. [_backgroundView setDelegate:nil];
  2255. [_overlayView removeFromSuperview];
  2256. [_overlayView setDelegate:nil];
  2257. @try {
  2258. if (_isObserverAdded == YES) {
  2259. _isObserverAdded = NO;
  2260. if ([_viewController respondsToSelector:@selector(preferredContentSize)]) {
  2261. [_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize))];
  2262. } else {
  2263. [_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover))];
  2264. }
  2265. }
  2266. }
  2267. @catch (NSException *exception) {
  2268. }
  2269. @finally {
  2270. _viewController = nil;
  2271. }
  2272. [self unregisterTheme];
  2273. _barButtonItem = nil;
  2274. _passthroughViews = nil;
  2275. _inView = nil;
  2276. _overlayView = nil;
  2277. _backgroundView = nil;
  2278. _theme = nil;
  2279. }
  2280. @end