123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- //
- // REMenu.m
- // REMenu
- //
- // Copyright (c) 2013 Roman Efimov (https://github.com/romaonthego)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- #import "REMenu.h"
- #import "REMenuItem.h"
- #import "REMenuItemView.h"
- @interface REMenuItem ()
- @property (assign, readwrite, nonatomic) REMenuItemView *itemView;
- @end
- @interface REMenu ()
- @property (strong, readwrite, nonatomic) UIView *menuWrapperView;
- @property (strong, readwrite, nonatomic) REMenuContainerView *containerView;
- @property (strong, readwrite, nonatomic) UIButton *backgroundButton;
- @property (assign, readwrite, nonatomic) BOOL isOpen;
- @property (assign, readwrite, nonatomic) BOOL isAnimating;
- @property (strong, readwrite, nonatomic) NSMutableArray *itemViews;
- @property (weak, readwrite, nonatomic) UINavigationBar *navigationBar;
- @property (strong, readwrite, nonatomic) UIToolbar *toolbar;
- @end
- @implementation REMenu
- - (id)init
- {
- self = [super init];
- if (self) {
- _imageAlignment = REMenuImageAlignmentLeft;
- _closeOnSelection = YES;
- _itemHeight = 48.0;
- _separatorHeight = 2.0;
- _separatorOffset = CGSizeMake(0.0, 0.0);
- _waitUntilAnimationIsComplete = YES;
-
- _textOffset = CGSizeMake(0, 0);
- _subtitleTextOffset = CGSizeMake(0, 0);
- _font = [UIFont boldSystemFontOfSize:21.0];
- _subtitleFont = [UIFont systemFontOfSize:14.0];
-
- _backgroundAlpha = 1.0;
- _backgroundColor = [UIColor colorWithRed:53/255.0 green:53/255.0 blue:52/255.0 alpha:1.0];
- _separatorColor = [UIColor colorWithPatternImage:self.separatorImage];
- _textColor = [UIColor colorWithRed:128/255.0 green:126/255.0 blue:124/255.0 alpha:1.0];
- _textShadowColor = [UIColor blackColor];
- _textShadowOffset = CGSizeMake(0, -1.0);
- _textAlignment = NSTextAlignmentCenter;
-
- _highlightedBackgroundColor = [UIColor colorWithRed:28/255.0 green:28/255.0 blue:27/255.0 alpha:1.0];
- _highlightedSeparatorColor = [UIColor colorWithRed:28/255.0 green:28/255.0 blue:27/255.0 alpha:1.0];
- _highlightedTextColor = [UIColor colorWithRed:128/255.0 green:126/255.0 blue:124/255.0 alpha:1.0];
- _highlightedTextShadowColor = [UIColor blackColor];
- _highlightedTextShadowOffset = CGSizeMake(0, -1.0);
-
- _subtitleTextColor = [UIColor colorWithWhite:0.425 alpha:1.000];
- _subtitleTextShadowColor = [UIColor blackColor];
- _subtitleTextShadowOffset = CGSizeMake(0, -1.0);
- _subtitleHighlightedTextColor = [UIColor colorWithRed:0.389 green:0.384 blue:0.379 alpha:1.000];
- _subtitleHighlightedTextShadowColor = [UIColor blackColor];
- _subtitleHighlightedTextShadowOffset = CGSizeMake(0, -1.0);
- _subtitleTextAlignment = NSTextAlignmentCenter;
-
- _borderWidth = 1.0;
- _borderColor = [UIColor colorWithRed:28/255.0 green:28/255.0 blue:27/255.0 alpha:1.0];
- _animationDuration = 0.3;
- _closeAnimationDuration = 0.2;
- _bounce = YES;
- _bounceAnimationDuration = 0.2;
-
- _appearsBehindNavigationBar = REUIKitIsFlatMode() ? YES : NO;
- }
- return self;
- }
- - (id)initWithItems:(NSArray *)items
- {
- self = [self init];
- if (self) {
- _items = items;
- }
- return self;
- }
- - (void)showFromRect:(CGRect)rect inView:(UIView *)view
- {
- if (self.isAnimating) {
- return;
- }
-
- self.isOpen = YES;
- self.isAnimating = YES;
-
- // Create views
- //
- self.containerView = ({
- REMenuContainerView *view = [[REMenuContainerView alloc] init];
- view.clipsToBounds = YES;
- view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
-
- if (self.backgroundView) {
- self.backgroundView.alpha = 0;
- [view addSubview:self.backgroundView];
- }
- view;
- });
-
- self.menuView = ({
- UIView *view = [[UIView alloc] init];
- if (!self.liveBlur || !REUIKitIsFlatMode()) {
- view.backgroundColor = self.backgroundColor;
- }
- view.layer.cornerRadius = self.cornerRadius;
- view.layer.borderColor = self.borderColor.CGColor;
- view.layer.borderWidth = self.borderWidth;
- view.layer.masksToBounds = YES;
- view.layer.shouldRasterize = YES;
- view.layer.rasterizationScale = [UIScreen mainScreen].scale;
- view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- view;
- });
-
- if (REUIKitIsFlatMode()) {
- self.toolbar = ({
- UIToolbar *toolbar = [[UIToolbar alloc] init];
- toolbar.barStyle = (UIBarStyle)self.liveBlurBackgroundStyle;
- if ([toolbar respondsToSelector:@selector(setBarTintColor:)])
- [toolbar performSelector:@selector(setBarTintColor:) withObject:self.liveBlurTintColor];
- toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- toolbar.layer.cornerRadius = self.cornerRadius;
- toolbar.layer.borderColor = self.borderColor.CGColor;
- toolbar.layer.borderWidth = self.borderWidth;
- toolbar.layer.masksToBounds = YES;
- toolbar;
- });
- }
-
- self.menuWrapperView = ({
- UIView *view = [[UIView alloc] init];
- view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- if (!self.liveBlur || !REUIKitIsFlatMode()) {
- view.layer.shadowColor = self.shadowColor.CGColor;
- view.layer.shadowOffset = self.shadowOffset;
- view.layer.shadowOpacity = self.shadowOpacity;
- view.layer.shadowRadius = self.shadowRadius;
- view.layer.shouldRasterize = YES;
- view.layer.rasterizationScale = [UIScreen mainScreen].scale;
- }
- view;
- });
-
- self.backgroundButton = ({
- UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
- button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- button.accessibilityLabel = NSLocalizedString(@"Menu background", @"Menu background");
- button.accessibilityHint = NSLocalizedString(@"Double tap to close", @"Double tap to close");
- [button addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
- button;
- });
-
- CGFloat navigationBarOffset = [self computeNavigationBarOffset];
-
- // Append new item views to REMenuView
- //
- for (REMenuItem *item in self.items) {
- NSInteger index = [self.items indexOfObject:item];
-
- CGFloat itemHeight = self.itemHeight;
- if (index == self.items.count - 1)
- itemHeight += self.cornerRadius;
-
- UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(self.separatorOffset.width,
- index * self.itemHeight + index * self.separatorHeight + 40.0 + navigationBarOffset + self.separatorOffset.height,
- rect.size.width - self.separatorOffset.width,
- self.separatorHeight)];
- separatorView.backgroundColor = self.separatorColor;
- separatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- [self.menuView addSubview:separatorView];
-
- REMenuItemView *itemView = [[REMenuItemView alloc] initWithFrame:CGRectMake(0,
- index * self.itemHeight + (index + 1.0) * self.separatorHeight + 40.0 + navigationBarOffset,
- rect.size.width,
- itemHeight)
- menu:self item:item
- hasSubtitle:item.subtitle.length > 0];
- itemView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- item.itemView = itemView;
- itemView.separatorView = separatorView;
- itemView.autoresizesSubviews = YES;
- if (item.customView) {
- item.customView.frame = itemView.bounds;
- [itemView addSubview:item.customView];
- }
- [self.menuView addSubview:itemView];
- }
-
- // Set up frames
- //
- self.menuWrapperView.frame = CGRectMake(0, -self.combinedHeight - navigationBarOffset, rect.size.width, self.combinedHeight + navigationBarOffset);
- self.menuView.frame = self.menuWrapperView.bounds;
- if (REUIKitIsFlatMode() && self.liveBlur) {
- self.toolbar.frame = self.menuWrapperView.bounds;
- }
- self.containerView.frame = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
- self.backgroundButton.frame = self.containerView.bounds;
-
- // Add subviews
- //
- if (REUIKitIsFlatMode() && self.liveBlur) {
- [self.menuWrapperView addSubview:self.toolbar];
- }
- [self.menuWrapperView addSubview:self.menuView];
- [self.containerView addSubview:self.backgroundButton];
- [self.containerView addSubview:self.menuWrapperView];
- [view addSubview:self.containerView];
-
- // Animate appearance
- //
- if (self.bounce) {
- self.isAnimating = YES;
- if ([UIView respondsToSelector:@selector(animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)]) {
- [UIView animateWithDuration:self.animationDuration+self.bounceAnimationDuration
- delay:0.0
- usingSpringWithDamping:0.6
- initialSpringVelocity:4.0
- options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
- animations:^{
- self.backgroundView.alpha = self.backgroundAlpha;
- CGRect frame = self.menuView.frame;
- frame.origin.y = -40.0 - self.separatorHeight;
- self.menuWrapperView.frame = frame;
- } completion:^(BOOL finished) {
- self.isAnimating = NO;
- }];
- } else {
- [UIView animateWithDuration:self.animationDuration
- delay:0.0
- options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
- animations:^{
- self.backgroundView.alpha = self.backgroundAlpha;
- CGRect frame = self.menuView.frame;
- frame.origin.y = -40.0 - self.separatorHeight;
- self.menuWrapperView.frame = frame;
- } completion:^(BOOL finished) {
- self.isAnimating = NO;
- }];
- }
- } else {
- [UIView animateWithDuration:self.animationDuration
- delay:0.0
- options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
- animations:^{
- self.backgroundView.alpha = self.backgroundAlpha;
- CGRect frame = self.menuView.frame;
- frame.origin.y = -40.0 - self.separatorHeight;
- self.menuWrapperView.frame = frame;
- } completion:^(BOOL finished) {
- self.isAnimating = NO;
- }];
- }
- }
- - (void)showInView:(UIView *)view
- {
- [self showFromRect:view.bounds inView:view];
- }
- -(void)showFromNavigationController:(UINavigationController *)navigationController
- {
- [self showFromNavigationController:navigationController offsetX:0 width:navigationController.navigationBar.frame.size.width];
- }
- - (void)showFromNavigationController:(UINavigationController *)navigationController offsetX:(CGFloat)offsetX width:(CGFloat)width
- {
- if (self.isAnimating) {
- return;
- }
-
- self.navigationBar = navigationController.navigationBar;
-
- [self showFromRect:CGRectMake(offsetX, 0, width, navigationController.view.frame.size.height) inView:navigationController.view];
- self.containerView.appearsBehindNavigationBar = self.appearsBehindNavigationBar;
- self.containerView.navigationBar = navigationController.navigationBar;
- if (self.appearsBehindNavigationBar) {
- [navigationController.view bringSubviewToFront:navigationController.navigationBar];
- }
-
- if(width != navigationController.navigationBar.frame.size.width)
- {
- self.containerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
- }
- }
- - (void)closeWithCompletion:(void (^)(void))completion
- {
- if (self.isAnimating) return;
-
- self.isAnimating = YES;
-
- CGFloat navigationBarOffset = [self computeNavigationBarOffset];
-
- void (^closeMenu)(void) = ^{
- [UIView animateWithDuration:self.closeAnimationDuration
- delay:0.0
- options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
- animations:^ {
- CGRect frame = self.menuView.frame;
- frame.origin.y = - self.combinedHeight - navigationBarOffset;
- self.menuWrapperView.frame = frame;
- self.backgroundView.alpha = 0;
- } completion:^(BOOL finished) {
- self.isOpen = NO;
- self.isAnimating = NO;
-
- [self.menuView removeFromSuperview];
- [self.menuWrapperView removeFromSuperview];
- [self.backgroundButton removeFromSuperview];
- [self.backgroundView removeFromSuperview];
- [self.containerView removeFromSuperview];
-
- if (completion) {
- completion();
- }
-
- if (self.closeCompletionHandler) {
- self.closeCompletionHandler();
- }
- }];
-
- };
-
- if (self.closePreparationBlock) {
- self.closePreparationBlock();
- }
-
- if (self.bounce) {
- [UIView animateWithDuration:self.bounceAnimationDuration animations:^{
- CGRect frame = self.menuView.frame;
- frame.origin.y = -20.0;
- self.menuWrapperView.frame = frame;
- } completion:^(BOOL finished) {
- closeMenu();
- }];
- } else {
- closeMenu();
- }
- }
- - (void)close
- {
- [self closeWithCompletion:nil];
- }
- - (CGFloat)combinedHeight
- {
- return self.items.count * self.itemHeight + self.items.count * self.separatorHeight + 40.0 + self.cornerRadius;
- }
- - (void)setNeedsLayout
- {
- [UIView animateWithDuration:0.35 animations:^{
- [self.containerView layoutSubviews];
- }];
- }
- #pragma mark -
- #pragma mark Setting style
- - (UIImage *)separatorImage
- {
- UIGraphicsBeginImageContext(CGSizeMake(1, 4.0));
- CGContextRef context = UIGraphicsGetCurrentContext();
- UIGraphicsPushContext(context);
- CGContextSetFillColorWithColor(context, [UIColor colorWithRed:28/255.0 green:28/255.0 blue:27/255.0 alpha:1.0].CGColor);
- CGContextFillRect(context, CGRectMake(0, 0, 1.0, 2.0));
- CGContextSetFillColorWithColor(context, [UIColor colorWithRed:79/255.0 green:79/255.0 blue:77/255.0 alpha:1.0].CGColor);
- CGContextFillRect(context, CGRectMake(0, 3.0, 1.0, 2.0));
- UIGraphicsPopContext();
- UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- return [UIImage imageWithCGImage:outputImage.CGImage scale:2.0 orientation:UIImageOrientationUp];
- }
- - (CGFloat)computeNavigationBarOffset
- {
- //TWS
- //CGFloat navigationBarOffset = self.appearsBehindNavigationBar && self.navigationBar ? ([UIApplication sharedApplication].statusBarHidden ? 44 : 64) : 0;
- CGFloat navigationBarOffset = self.appearsBehindNavigationBar && self.navigationBar ? ([UIApplication sharedApplication].statusBarHidden ? 64 : 64) : 0;
-
- return navigationBarOffset;
- }
- @end
|