// // 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 offsetY = 0; if (@available(iOS 11, *)) { UIEdgeInsets insets = [UIApplication sharedApplication].delegate.window.safeAreaInsets; if (insets.bottom > 0) offsetY = 20; } CGFloat navigationBarOffset = self.appearsBehindNavigationBar && self.navigationBar ? ([UIApplication sharedApplication].statusBarHidden ? 44+offsetY : 64+offsetY) : 0; return navigationBarOffset; } @end