AbstractActionSheetPicker.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. //
  2. //Copyright (c) 2011, Tim Cinel
  3. //All rights reserved.
  4. //
  5. //Redistribution and use in source and binary forms, with or without
  6. //modification, are permitted provided that the following conditions are met:
  7. //* Redistributions of source code must retain the above copyright
  8. //notice, this list of conditions and the following disclaimer.
  9. //* Redistributions in binary form must reproduce the above copyright
  10. //notice, this list of conditions and the following disclaimer in the
  11. //documentation and/or other materials provided with the distribution.
  12. //* Neither the name of the <organization> nor the
  13. //names of its contributors may be used to endorse or promote products
  14. //derived from this software without specific prior written permission.
  15. //
  16. //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. //DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  20. //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. //
  27. #import "AbstractActionSheetPicker.h"
  28. #import "SWActionSheet.h"
  29. #import <objc/message.h>
  30. #import <sys/utsname.h>
  31. CG_INLINE BOOL isIPhone4() {
  32. struct utsname systemInfo;
  33. uname(&systemInfo);
  34. NSString *modelName = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
  35. return ([modelName rangeOfString:@"iPhone3"].location != NSNotFound);
  36. }
  37. #define IS_WIDESCREEN ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
  38. #define IS_IPAD UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
  39. #define DEVICE_ORIENTATION [UIDevice currentDevice].orientation
  40. // UIInterfaceOrientationMask vs. UIInterfaceOrientation
  41. // As far as I know, a function like this isn't available in the API. I derived this from the enum def for
  42. // UIInterfaceOrientationMask.
  43. #define OrientationMaskSupportsOrientation(mask, orientation) ((mask & (1 << orientation)) != 0)
  44. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
  45. @interface MyPopoverController : UIPopoverController <UIAdaptivePresentationControllerDelegate>
  46. @end
  47. @implementation MyPopoverController
  48. + (BOOL)canShowPopover {
  49. if (IS_IPAD) {
  50. if ([UITraitCollection class]) {
  51. UITraitCollection *traits = [UIApplication sharedApplication].keyWindow.traitCollection;
  52. if (traits.horizontalSizeClass == UIUserInterfaceSizeClassCompact)
  53. return NO;
  54. }
  55. return YES;
  56. }
  57. return NO;
  58. }
  59. - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
  60. return UIModalPresentationNone;
  61. }
  62. @end
  63. #else
  64. @interface MyPopoverController:UIPopoverController
  65. @end
  66. @implementation MyPopoverController
  67. +(BOOL)canShowPopover {
  68. return IS_IPAD;
  69. }
  70. @end
  71. #endif
  72. @interface AbstractActionSheetPicker () <UIGestureRecognizerDelegate>
  73. @property(nonatomic, strong) UIBarButtonItem *barButtonItem;
  74. @property(nonatomic, strong) UIBarButtonItem *doneBarButtonItem;
  75. @property(nonatomic, strong) UIBarButtonItem *cancelBarButtonItem;
  76. @property(nonatomic, strong) UIView *containerView;
  77. @property(nonatomic, unsafe_unretained) id target;
  78. @property(nonatomic, assign) SEL successAction;
  79. @property(nonatomic, assign) SEL cancelAction;
  80. @property(nonatomic, strong) UIPopoverController *popOverController;
  81. @property(nonatomic, strong) CIFilter *filter;
  82. @property(nonatomic, strong) CIContext *context;
  83. @property(nonatomic, strong) NSObject *selfReference;
  84. - (void)presentPickerForView:(UIView *)aView;
  85. - (void)configureAndPresentPopoverForView:(UIView *)aView;
  86. - (void)configureAndPresentActionSheetForView:(UIView *)aView;
  87. - (void)presentActionSheet:(SWActionSheet *)actionSheet;
  88. - (void)presentPopover:(UIPopoverController *)popover;
  89. - (void)dismissPicker;
  90. - (BOOL)isViewPortrait;
  91. - (BOOL)isValidOrigin:(id)origin;
  92. - (id)storedOrigin;
  93. - (UIToolbar *)createPickerToolbarWithTitle:(NSString *)aTitle;
  94. - (UIBarButtonItem *)createButtonWithType:(UIBarButtonSystemItem)type target:(id)target action:(SEL)buttonAction;
  95. - (IBAction)actionPickerDone:(id)sender;
  96. - (IBAction)actionPickerCancel:(id)sender;
  97. @end
  98. @implementation AbstractActionSheetPicker
  99. #pragma mark - Abstract Implementation
  100. - (instancetype)init {
  101. self = [super init];
  102. if (self) {
  103. self.windowLevel = UIWindowLevelAlert;
  104. self.presentFromRect = CGRectZero;
  105. self.popoverBackgroundViewClass = nil;
  106. self.popoverDisabled = NO;
  107. #pragma clang diagnostic push
  108. #pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
  109. if ([UIApplication instancesRespondToSelector:@selector(supportedInterfaceOrientationsForWindow:)])
  110. self.supportedInterfaceOrientations = (UIInterfaceOrientationMask) [[UIApplication sharedApplication]
  111. supportedInterfaceOrientationsForWindow:
  112. [UIApplication sharedApplication].keyWindow];
  113. else {
  114. self.supportedInterfaceOrientations = UIInterfaceOrientationMaskAllButUpsideDown;
  115. if (IS_IPAD)
  116. self.supportedInterfaceOrientations |= (1 << UIInterfaceOrientationPortraitUpsideDown);
  117. }
  118. #pragma clang diagnostic pop
  119. UIBarButtonItem *sysDoneButton = [self createButtonWithType:UIBarButtonSystemItemDone target:self
  120. action:@selector(actionPickerDone:)];
  121. UIBarButtonItem *sysCancelButton = [self createButtonWithType:UIBarButtonSystemItemCancel target:self
  122. action:@selector(actionPickerCancel:)];
  123. [self setCancelBarButtonItem:sysCancelButton];
  124. [self setDoneBarButtonItem:sysDoneButton];
  125. self.tapDismissAction = TapActionNone;
  126. //allows us to use this without needing to store a reference in calling class
  127. self.selfReference = self;
  128. NSMutableParagraphStyle *labelParagraphStyle = [[NSMutableParagraphStyle alloc] init];
  129. labelParagraphStyle.alignment = NSTextAlignmentCenter;
  130. self.pickerTextAttributes = [@{NSParagraphStyleAttributeName : labelParagraphStyle} mutableCopy];
  131. self.context = [CIContext contextWithOptions:nil];
  132. self.filter = [CIFilter filterWithName:@"CIGaussianBlur"];
  133. }
  134. return self;
  135. }
  136. - (void)setTextColor:(UIColor *)textColor {
  137. if (self.pickerTextAttributes) {
  138. self.pickerTextAttributes[NSForegroundColorAttributeName] = textColor;
  139. } else {
  140. self.pickerTextAttributes = [@{NSForegroundColorAttributeName : [UIColor whiteColor]} mutableCopy];
  141. }
  142. }
  143. - (instancetype)initWithTarget:(id)target successAction:(SEL)successAction cancelAction:(SEL)cancelActionOrNil origin:(id)origin {
  144. self = [self init];
  145. if (self) {
  146. self.target = target;
  147. self.successAction = successAction;
  148. self.cancelAction = cancelActionOrNil;
  149. if ([origin isKindOfClass:[UIBarButtonItem class]])
  150. self.barButtonItem = origin;
  151. else if ([origin isKindOfClass:[UIView class]])
  152. self.containerView = origin;
  153. else
  154. NSAssert(NO, @"Invalid origin provided to ActionSheetPicker ( %@ )", origin);
  155. }
  156. return self;
  157. }
  158. - (void)dealloc {
  159. //need to clear picker delegates and datasources, otherwise they may call this object after it's gone
  160. if ([self.pickerView respondsToSelector:@selector(setDelegate:)])
  161. [self.pickerView performSelector:@selector(setDelegate:) withObject:nil];
  162. if ([self.pickerView respondsToSelector:@selector(setDataSource:)])
  163. [self.pickerView performSelector:@selector(setDataSource:) withObject:nil];
  164. if ([self.pickerView respondsToSelector:@selector(removeTarget:action:forControlEvents:)])
  165. [((UIControl *) self.pickerView) removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents];
  166. self.target = nil;
  167. [[NSNotificationCenter defaultCenter] removeObserver:self];
  168. }
  169. - (UIView *)configuredPickerView {
  170. NSAssert(NO, @"This is an abstract class, you must use a subclass of AbstractActionSheetPicker (like ActionSheetStringPicker)");
  171. return nil;
  172. }
  173. - (void)notifyTarget:(id)target didSucceedWithAction:(SEL)successAction origin:(id)origin {
  174. NSAssert(NO, @"This is an abstract class, you must use a subclass of AbstractActionSheetPicker (like ActionSheetStringPicker)");
  175. }
  176. - (void)notifyTarget:(id)target didCancelWithAction:(SEL)cancelAction origin:(id)origin {
  177. if (target && cancelAction && [target respondsToSelector:cancelAction]) {
  178. #pragma clang diagnostic push
  179. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  180. [target performSelector:cancelAction withObject:origin];
  181. #pragma clang diagnostic pop
  182. }
  183. }
  184. #pragma mark - Actions
  185. - (void)showActionSheetPicker {
  186. UIView *masterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.viewSize.width, 260)];
  187. // to fix bug, appeared only on iPhone 4 Device: https://github.com/skywinder/ActionSheetPicker-3.0/issues/5
  188. if (isIPhone4()) {
  189. masterView.backgroundColor = [UIColor colorWithRed:0.97 green:0.97 blue:0.97 alpha:1.0];
  190. }
  191. self.toolbar = [self createPickerToolbarWithTitle:self.title];
  192. [masterView addSubview:self.toolbar];
  193. //ios7 picker draws a darkened alpha-only region on the first and last 8 pixels horizontally, but blurs the rest of its background. To make the whole popup appear to be edge-to-edge, we have to add blurring to the remaining left and right edges.
  194. if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
  195. CGRect rect = CGRectMake(0, self.toolbar.frame.origin.y, _borderWidth, masterView.frame.size.height - self.toolbar.frame.origin.y);
  196. UIToolbar *leftEdge = [[UIToolbar alloc] initWithFrame:rect];
  197. rect.origin.x = masterView.frame.size.width - _borderWidth;
  198. UIToolbar *rightEdge = [[UIToolbar alloc] initWithFrame:rect];
  199. #pragma clang diagnostic push
  200. #pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
  201. leftEdge.barTintColor = rightEdge.barTintColor = self.toolbar.barTintColor;
  202. #pragma clang diagnostic pop
  203. [masterView insertSubview:leftEdge atIndex:0];
  204. [masterView insertSubview:rightEdge atIndex:0];
  205. }
  206. self.pickerView = [self configuredPickerView];
  207. NSAssert(_pickerView != NULL, @"Picker view failed to instantiate, perhaps you have invalid component data.");
  208. // toolbar hidden remove the toolbar frame and update pickerview frame
  209. if (self.toolbar.hidden) {
  210. int halfWidth = (int) (_borderWidth * 0.5f);
  211. masterView.frame = CGRectMake(0, 0, self.viewSize.width, 220);
  212. self.pickerView.frame = CGRectMake(0, halfWidth, self.viewSize.width, 220 - halfWidth);
  213. }
  214. [masterView addSubview:_pickerView];
  215. if ((![MyPopoverController canShowPopover] || self.popoverDisabled) && !self.pickerBackgroundColor && !self.toolbarBackgroundColor && [self.pickerBlurRadius intValue] > 0) {
  216. [self blurPickerBackground];
  217. } else {
  218. [self presentPickerForView:masterView];
  219. }
  220. #pragma clang diagnostic push
  221. #pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
  222. {
  223. switch (self.tapDismissAction) {
  224. case TapActionNone:
  225. break;
  226. case TapActionSuccess: {
  227. // add tap dismiss action
  228. self.actionSheet.window.userInteractionEnabled = YES;
  229. UITapGestureRecognizer *tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionPickerDone:)];
  230. tapAction.delegate = self;
  231. [self.actionSheet.window addGestureRecognizer:tapAction];
  232. break;
  233. }
  234. case TapActionCancel: {
  235. // add tap dismiss action
  236. self.actionSheet.window.userInteractionEnabled = YES;
  237. UITapGestureRecognizer *tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionPickerCancel:)];
  238. tapAction.delegate = self;
  239. [self.actionSheet.window addGestureRecognizer:tapAction];
  240. break;
  241. }
  242. };
  243. }
  244. #pragma clang diagnostic pop
  245. }
  246. - (IBAction)actionPickerDone:(id)sender {
  247. [self notifyTarget:self.target didSucceedWithAction:self.successAction origin:[self storedOrigin]];
  248. [self dismissPicker];
  249. }
  250. - (IBAction)actionPickerCancel:(id)sender {
  251. [self notifyTarget:self.target didCancelWithAction:self.cancelAction origin:[self storedOrigin]];
  252. [self dismissPicker];
  253. }
  254. - (void)dismissPicker {
  255. #if __IPHONE_4_1 <= __IPHONE_OS_VERSION_MAX_ALLOWED
  256. if (self.actionSheet)
  257. #else
  258. if (self.actionSheet && [self.actionSheet isVisible])
  259. #endif
  260. [_actionSheet dismissWithClickedButtonIndex:0 animated:YES];
  261. else if (self.popOverController && self.popOverController.popoverVisible)
  262. [_popOverController dismissPopoverAnimated:YES];
  263. self.actionSheet = nil;
  264. self.popOverController = nil;
  265. self.selfReference = nil;
  266. }
  267. #pragma mark - Custom Buttons
  268. - (NSMutableArray *)customButtons {
  269. if (!_customButtons) {
  270. _customButtons = [[NSMutableArray alloc] init];
  271. }
  272. return _customButtons;
  273. }
  274. - (void)addCustomButtonWithTitle:(NSString *)title value:(id)value {
  275. if (!title)
  276. title = @"";
  277. if (!value)
  278. value = @0;
  279. NSDictionary *buttonDetails = @{
  280. kButtonTitle : title,
  281. kActionType : @(ActionTypeValue),
  282. kButtonValue : value
  283. };
  284. [self.customButtons addObject:buttonDetails];
  285. }
  286. - (void)addCustomButtonWithTitle:(NSString *)title actionBlock:(ActionBlock)block {
  287. if (!title)
  288. title = @"";
  289. if (!block)
  290. block = (^{
  291. });
  292. NSDictionary *buttonDetails = @{
  293. kButtonTitle : title,
  294. kActionType : @(ActionTypeBlock),
  295. kButtonValue : [block copy]
  296. };
  297. [self.customButtons addObject:buttonDetails];
  298. }
  299. - (void)addCustomButtonWithTitle:(NSString *)title target:(id)target selector:(SEL)selector {
  300. if (!title)
  301. title = @"";
  302. if (!target)
  303. target = [NSNull null];
  304. NSDictionary *buttonDetails = @{
  305. kButtonTitle : title,
  306. kActionType : @(ActionTypeSelector),
  307. kActionTarget : target,
  308. kButtonValue : [NSValue valueWithPointer:selector]
  309. };
  310. [self.customButtons addObject:buttonDetails];
  311. }
  312. - (IBAction)customButtonPressed:(id)sender {
  313. UIBarButtonItem *button = (UIBarButtonItem *) sender;
  314. NSInteger index = button.tag;
  315. NSAssert((index >= 0 && index < self.customButtons.count), @"Bad custom button tag: %ld, custom button count: %lu", (long) index, (unsigned long) self.customButtons.count);
  316. NSDictionary *buttonDetails = (self.customButtons)[(NSUInteger) index];
  317. NSAssert(buttonDetails != NULL, @"Custom button dictionary is invalid");
  318. ActionType actionType = (ActionType) [buttonDetails[kActionType] integerValue];
  319. switch (actionType) {
  320. case ActionTypeValue: {
  321. NSAssert([self.pickerView respondsToSelector:@
  322. selector(selectRow:inComponent:animated:)], @"customButtonPressed not overridden, cannot interact with subclassed pickerView");
  323. NSInteger buttonValue = [buttonDetails[kButtonValue] integerValue];
  324. UIPickerView *picker = (UIPickerView *) self.pickerView;
  325. NSAssert(picker != NULL, @"PickerView is invalid");
  326. [picker selectRow:buttonValue inComponent:0 animated:YES];
  327. if ([self respondsToSelector:@selector(pickerView:didSelectRow:inComponent:)]) {
  328. void (*objc_msgSendTyped)(id target, SEL _cmd, id pickerView, NSInteger row, NSInteger component) = (void *) objc_msgSend; // sending Integers as params
  329. objc_msgSendTyped(self, @selector(pickerView:didSelectRow:inComponent:), picker, buttonValue, 0);
  330. }
  331. break;
  332. }
  333. case ActionTypeBlock: {
  334. ActionBlock actionBlock = buttonDetails[kButtonValue];
  335. [self dismissPicker];
  336. if (actionBlock)
  337. actionBlock();
  338. break;
  339. }
  340. case ActionTypeSelector: {
  341. SEL selector = [buttonDetails[kButtonValue] pointerValue];
  342. id target = buttonDetails[kActionTarget];
  343. [self dismissPicker];
  344. if (target && [target respondsToSelector:selector]) {
  345. SuppressPerformSelectorLeakWarning (
  346. [target performSelector:selector];
  347. );
  348. }
  349. break;
  350. }
  351. default:
  352. NSAssert(false, @"Unknown action type");
  353. break;
  354. }
  355. }
  356. // Allow the user to specify a custom cancel button
  357. - (void)setCancelButton:(UIBarButtonItem *)button {
  358. if (!button) {
  359. self.hideCancel = YES;
  360. return;
  361. }
  362. if ([button.customView isKindOfClass:[UIButton class]]) {
  363. UIButton *uiButton = (UIButton *) button.customView;
  364. [uiButton addTarget:self action:@selector(actionPickerCancel:) forControlEvents:UIControlEventTouchUpInside];
  365. }
  366. else {
  367. [button setTarget:self];
  368. [button setAction:@selector(actionPickerCancel:)];
  369. }
  370. self.cancelBarButtonItem = button;
  371. }
  372. // Allow the user to specify a custom done button
  373. - (void)setDoneButton:(UIBarButtonItem *)button {
  374. if ([button.customView isKindOfClass:[UIButton class]]) {
  375. UIButton *uiButton = (UIButton *) button.customView;
  376. [button setAction:@selector(actionPickerDone:)];
  377. [uiButton addTarget:self action:@selector(actionPickerDone:) forControlEvents:UIControlEventTouchUpInside];
  378. }
  379. else {
  380. [button setTarget:self];
  381. [button setAction:@selector(actionPickerDone:)];
  382. }
  383. [button setTarget:self];
  384. [button setAction:@selector(actionPickerDone:)];
  385. self.doneBarButtonItem = button;
  386. }
  387. - (void)hidePickerWithCancelAction {
  388. [self actionPickerCancel:nil];
  389. }
  390. - (UIToolbar *)createPickerToolbarWithTitle:(NSString *)title {
  391. CGRect frame = CGRectMake(0, 0, self.viewSize.width, 44);
  392. UIToolbar *pickerToolbar = [[UIToolbar alloc] initWithFrame:frame];
  393. pickerToolbar.barStyle = (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) ? UIBarStyleDefault : UIBarStyleBlackTranslucent;
  394. pickerToolbar.barTintColor = self.toolbarBackgroundColor;
  395. pickerToolbar.tintColor = self.toolbarButtonsColor;
  396. NSMutableArray *barItems = [[NSMutableArray alloc] init];
  397. if (!self.hideCancel) {
  398. [barItems addObject:self.cancelBarButtonItem];
  399. }
  400. NSInteger index = 0;
  401. for (NSDictionary *buttonDetails in self.customButtons) {
  402. NSString *buttonTitle = buttonDetails[kButtonTitle];
  403. UIBarButtonItem *button;
  404. if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
  405. button = [[UIBarButtonItem alloc] initWithTitle:buttonTitle style:UIBarButtonItemStylePlain
  406. target:self action:@selector(customButtonPressed:)];
  407. }
  408. else {
  409. #pragma clang diagnostic push
  410. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  411. button = [[UIBarButtonItem alloc] initWithTitle:buttonTitle style:UIBarButtonItemStyleBordered
  412. target:self action:@selector(customButtonPressed:)];
  413. #pragma clang diagnostic pop
  414. }
  415. button.tag = index;
  416. [barItems addObject:button];
  417. index++;
  418. }
  419. UIBarButtonItem *flexSpace = [self createButtonWithType:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
  420. [barItems addObject:flexSpace];
  421. if (title) {
  422. UIBarButtonItem *labelButton;
  423. labelButton = [self createToolbarLabelWithTitle:title titleTextAttributes:self.titleTextAttributes andAttributedTitle:self.attributedTitle];
  424. [barItems addObject:labelButton];
  425. [barItems addObject:flexSpace];
  426. }
  427. [barItems addObject:self.doneBarButtonItem];
  428. [pickerToolbar setItems:barItems animated:NO];
  429. [pickerToolbar layoutIfNeeded];
  430. return pickerToolbar;
  431. }
  432. - (UIBarButtonItem *)createToolbarLabelWithTitle:(NSString *)aTitle
  433. titleTextAttributes:(NSDictionary *)titleTextAttributes
  434. andAttributedTitle:(NSAttributedString *)attributedTitle {
  435. UILabel *toolBarItemLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 180, 30)];
  436. [toolBarItemLabel setTextAlignment:NSTextAlignmentCenter];
  437. [toolBarItemLabel setBackgroundColor:[UIColor clearColor]];
  438. CGFloat strikeWidth;
  439. CGSize textSize;
  440. if (titleTextAttributes) {
  441. toolBarItemLabel.attributedText = [[NSAttributedString alloc] initWithString:aTitle attributes:titleTextAttributes];
  442. textSize = toolBarItemLabel.attributedText.size;
  443. } else if (attributedTitle) {
  444. toolBarItemLabel.attributedText = attributedTitle;
  445. textSize = toolBarItemLabel.attributedText.size;
  446. }
  447. else {
  448. [toolBarItemLabel setTextColor:(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) ? [UIColor blackColor] : [UIColor whiteColor]];
  449. [toolBarItemLabel setFont:[UIFont boldSystemFontOfSize:16]];
  450. toolBarItemLabel.text = aTitle;
  451. if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
  452. #pragma clang diagnostic push
  453. #pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
  454. textSize = [[toolBarItemLabel text] sizeWithAttributes:@{NSFontAttributeName : [toolBarItemLabel font]}];
  455. #pragma clang diagnostic pop
  456. } else {
  457. #pragma clang diagnostic push
  458. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  459. textSize = [[toolBarItemLabel text] sizeWithFont:[toolBarItemLabel font]];
  460. #pragma clang diagnostic pop
  461. }
  462. }
  463. strikeWidth = textSize.width;
  464. if (strikeWidth < 180) {
  465. [toolBarItemLabel sizeToFit];
  466. }
  467. UIBarButtonItem *buttonLabel = [[UIBarButtonItem alloc] initWithCustomView:toolBarItemLabel];
  468. return buttonLabel;
  469. }
  470. - (UIBarButtonItem *)createButtonWithType:(UIBarButtonSystemItem)type target:(id)target action:(SEL)buttonAction {
  471. UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:type target:target
  472. action:buttonAction];
  473. return barButton;
  474. }
  475. #pragma mark - Custom Color
  476. - (void)setPickerBackgroundColor:(UIColor *)backgroundColor {
  477. _pickerBackgroundColor = backgroundColor;
  478. _actionSheet.bgView.backgroundColor = backgroundColor;
  479. }
  480. #pragma mark - Picker blur effect
  481. - (void)blurPickerBackground {
  482. UIWindow *window = [UIApplication sharedApplication].delegate.window;
  483. UIViewController *rootViewController = window.rootViewController;
  484. UIView *masterView = self.pickerView.superview;
  485. self.pickerView.backgroundColor = [UIColor clearColor];
  486. masterView.backgroundColor = [UIColor clearColor];
  487. // Get the snapshot
  488. UIGraphicsBeginImageContext(rootViewController.view.bounds.size);
  489. [rootViewController.view drawViewHierarchyInRect:rootViewController.view.bounds afterScreenUpdates:NO];
  490. UIImage *backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
  491. UIGraphicsEndImageContext();
  492. [self presentPickerForView:masterView];
  493. // Crop the snapshot to match picker frame
  494. CIImage *image = [CIImage imageWithCGImage:[backgroundImage CGImage]];
  495. [self.filter setValue:image forKey:kCIInputImageKey];
  496. [self.filter setValue:self.pickerBlurRadius forKey:kCIInputRadiusKey];
  497. CGRect blurFrame = [rootViewController.view convertRect:self.pickerView.frame fromView:masterView];
  498. // CoreImage coordinate system and UIKit coordinate system differs, so we need to adjust the frame
  499. blurFrame.origin.y = - (blurFrame.origin.y - rootViewController.view.frame.size.height) - blurFrame.size.height;
  500. CGImageRef imageRef = [self.context createCGImage:self.filter.outputImage fromRect:blurFrame];
  501. UIImageView *blurredImageView = [[UIImageView alloc] initWithFrame:self.pickerView.frame];
  502. blurredImageView.image = [UIImage imageWithCGImage:imageRef];
  503. blurredImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  504. [masterView addSubview:blurredImageView];
  505. [masterView sendSubviewToBack:blurredImageView];
  506. CGImageRelease(imageRef);
  507. }
  508. #pragma mark - Utilities and Accessors
  509. - (CGSize)viewSize {
  510. if (IS_IPAD) {
  511. if (!self.popoverDisabled && [MyPopoverController canShowPopover])
  512. return CGSizeMake(320, 320);
  513. return [UIApplication sharedApplication].keyWindow.bounds.size;
  514. }
  515. #if defined(__IPHONE_8_0)
  516. if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
  517. //iOS 7.1 or earlier
  518. if ([self isViewPortrait])
  519. return CGSizeMake(320, IS_WIDESCREEN ? 568 : 480);
  520. return CGSizeMake(IS_WIDESCREEN ? 568 : 480, 320);
  521. } else {
  522. //iOS 8 or later
  523. return [[UIScreen mainScreen] bounds].size;
  524. }
  525. #else
  526. if ( [self isViewPortrait] )
  527. return CGSizeMake(320 , IS_WIDESCREEN ? 568 : 480);
  528. return CGSizeMake(IS_WIDESCREEN ? 568 : 480, 320);
  529. #endif
  530. }
  531. - (BOOL)isViewPortrait {
  532. return UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation);
  533. }
  534. - (BOOL)isValidOrigin:(id)origin {
  535. if (!origin)
  536. return NO;
  537. BOOL isButton = [origin isKindOfClass:[UIBarButtonItem class]];
  538. BOOL isView = [origin isKindOfClass:[UIView class]];
  539. return (isButton || isView);
  540. }
  541. - (id)storedOrigin {
  542. if (self.barButtonItem)
  543. return self.barButtonItem;
  544. return self.containerView;
  545. }
  546. #pragma mark - Popovers and ActionSheets
  547. - (void)presentPickerForView:(UIView *)aView {
  548. self.presentFromRect = aView.frame;
  549. if (!self.popoverDisabled && [MyPopoverController canShowPopover])
  550. [self configureAndPresentPopoverForView:aView];
  551. else
  552. [self configureAndPresentActionSheetForView:aView];
  553. }
  554. - (void)configureAndPresentActionSheetForView:(UIView *)aView {
  555. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRotate:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
  556. _actionSheet = [[SWActionSheet alloc] initWithView:aView windowLevel:self.windowLevel];
  557. if (self.pickerBackgroundColor) {
  558. _actionSheet.bgView.backgroundColor = self.pickerBackgroundColor;
  559. }
  560. [self presentActionSheet:_actionSheet];
  561. // Use beginAnimations for a smoother popup animation, otherwise the UIActionSheet pops into view
  562. [UIView beginAnimations:nil context:nil];
  563. // _actionSheet.bounds = CGRectMake(0, 0, self.viewSize.width, sheetHeight);
  564. [UIView commitAnimations];
  565. }
  566. - (void)didRotate:(NSNotification *)notification {
  567. if (OrientationMaskSupportsOrientation(self.supportedInterfaceOrientations, DEVICE_ORIENTATION))
  568. [self dismissPicker];
  569. }
  570. - (void)presentActionSheet:(SWActionSheet *)actionSheet {
  571. NSParameterAssert(actionSheet != NULL);
  572. if (self.barButtonItem)
  573. [actionSheet showFromBarButtonItem:_barButtonItem animated:YES];
  574. else
  575. [actionSheet showInContainerView];
  576. }
  577. - (void)configureAndPresentPopoverForView:(UIView *)aView {
  578. UIViewController *viewController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
  579. viewController.view = aView;
  580. if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
  581. #pragma clang diagnostic push
  582. #pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
  583. viewController.preferredContentSize = aView.frame.size;
  584. #pragma clang diagnostic pop
  585. }
  586. else {
  587. #pragma clang diagnostic push
  588. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  589. viewController.contentSizeForViewInPopover = viewController.view.frame.size;
  590. #pragma clang diagnostic pop
  591. }
  592. _popOverController = [[MyPopoverController alloc] initWithContentViewController:viewController];
  593. _popOverController.delegate = self;
  594. if (self.pickerBackgroundColor) {
  595. self.popOverController.backgroundColor = self.pickerBackgroundColor;
  596. }
  597. if (self.popoverBackgroundViewClass) {
  598. [self.popOverController setPopoverBackgroundViewClass:self.popoverBackgroundViewClass];
  599. }
  600. [self presentPopover:_popOverController];
  601. }
  602. - (void)presentPopover:(UIPopoverController *)popover {
  603. NSParameterAssert(popover != NULL);
  604. if (self.barButtonItem) {
  605. if (_containerView != nil) {
  606. [popover presentPopoverFromRect:CGRectMake(_containerView.frame.size.width / 2.f, 0.f, 0, 0) inView:_containerView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  607. } else {
  608. [popover presentPopoverFromBarButtonItem:_barButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  609. }
  610. return;
  611. }
  612. else if ((self.containerView)) {
  613. dispatch_async(dispatch_get_main_queue(), ^{
  614. [popover presentPopoverFromRect:_containerView.bounds inView:_containerView
  615. permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  616. });
  617. return;
  618. }
  619. // Unfortunately, things go to hell whenever you try to present a popover from a table view cell. These are failsafes.
  620. UIView *origin = nil;
  621. CGRect presentRect = CGRectZero;
  622. @try {
  623. origin = (_containerView.superview ? _containerView.superview : _containerView);
  624. presentRect = origin.bounds;
  625. dispatch_async(dispatch_get_main_queue(), ^{
  626. [popover presentPopoverFromRect:presentRect inView:origin
  627. permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  628. });
  629. }
  630. @catch (NSException *exception) {
  631. origin = [[[[UIApplication sharedApplication] keyWindow] rootViewController] view];
  632. presentRect = CGRectMake(origin.center.x, origin.center.y, 1, 1);
  633. dispatch_async(dispatch_get_main_queue(), ^{
  634. [popover presentPopoverFromRect:presentRect inView:origin
  635. permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  636. });
  637. }
  638. }
  639. #pragma mark - Popoverdelegate
  640. - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
  641. switch (self.tapDismissAction) {
  642. case TapActionSuccess: {
  643. [self notifyTarget:self.target didSucceedWithAction:self.successAction origin:self.storedOrigin];
  644. break;
  645. }
  646. case TapActionNone:
  647. case TapActionCancel: {
  648. [self notifyTarget:self.target didCancelWithAction:self.cancelAction origin:self.storedOrigin];
  649. break;
  650. }
  651. };
  652. }
  653. #pragma mark UIGestureRecognizerDelegate
  654. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  655. CGPoint location = [gestureRecognizer locationInView:self.toolbar];
  656. return !CGRectContainsPoint(self.toolbar.bounds, location);
  657. }
  658. @end