├── screenshot01.png ├── screenshot02.png ├── QBPopupMenuDemo ├── QBPopupMenuDemo │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── background.jpg │ ├── Images.xcassets │ │ ├── clip.imageset │ │ │ ├── clip.png │ │ │ ├── clip@2x.png │ │ │ └── Contents.json │ │ ├── trash.imageset │ │ │ ├── trash.png │ │ │ ├── trash@2x.png │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── ViewController.h │ ├── AppDelegate.h │ ├── QBPopupMenuDemo-Prefix.pch │ ├── main.m │ ├── QBPopupMenuDemo-Info.plist │ ├── AppDelegate.m │ ├── ViewController.m │ └── Base.lproj │ │ └── Main.storyboard ├── QBPopupMenuDemoTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── QBPopupMenuDemoTests-Info.plist │ └── QBPopupMenuDemoTests.m └── QBPopupMenuDemo.xcodeproj │ └── project.pbxproj ├── .gitignore ├── QBPopupMenu ├── QBPopupMenuOverlayView.h ├── QBPopupMenuItemView.h ├── QBPopupMenuOverlayView.m ├── QBPlasticPopupMenu.h ├── QBPopupMenuItem.h ├── QBPopupMenuPagenatorView.h ├── QBPopupMenuItem.m ├── QBPopupMenu.h ├── QBPopupMenuItemView.m ├── QBPopupMenuPagenatorView.m ├── QBPlasticPopupMenu.m └── QBPopupMenu.m ├── LICENSE.txt └── README.md /screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/screenshot01.png -------------------------------------------------------------------------------- /screenshot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/screenshot02.png -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/QBPopupMenuDemo/QBPopupMenuDemo/background.jpg -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/clip.imageset/clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/clip.imageset/clip.png -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/trash.imageset/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/trash.imageset/trash.png -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/clip.imageset/clip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/clip.imageset/clip@2x.png -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/trash.imageset/trash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nek023/QBPopupMenu/HEAD/QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/trash.imageset/trash@2x.png -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // QBPopupMenuDemo 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // QBPopupMenuDemo 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by http://gitignore.io 2 | 3 | ### Objective-C ### 4 | # Xcode 5 | .DS_Store 6 | */build/* 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | *.xcworkspace 16 | !default.xcworkspace 17 | xcuserdata 18 | profile 19 | *.moved-aside 20 | DerivedData 21 | .idea/ 22 | *.hmap 23 | 24 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuOverlayView.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/24. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class QBPopupMenu; 12 | 13 | @interface QBPopupMenuOverlayView : UIView 14 | 15 | @property (nonatomic, weak) QBPopupMenu *popupMenu; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/clip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "clip.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "clip@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/trash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "trash.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "trash@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/QBPopupMenuDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // QBPopupMenuDemo 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuItemView.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class QBPopupMenu; 12 | @class QBPopupMenuItem; 13 | 14 | @interface QBPopupMenuItemView : UIView 15 | 16 | @property (nonatomic, weak) QBPopupMenu *popupMenu; 17 | 18 | @property (nonatomic, strong, readonly) UIButton *button; 19 | @property (nonatomic, strong) UIImage *image; 20 | @property (nonatomic, strong) UIImage *highlightedImage; 21 | 22 | @property (nonatomic, strong) QBPopupMenuItem *item; 23 | 24 | + (instancetype)itemViewWithItem:(QBPopupMenuItem *)item; 25 | - (instancetype)initWithItem:(QBPopupMenuItem *)item; 26 | 27 | - (void)performAction; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemoTests/QBPopupMenuDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | jp.questbeat.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemoTests/QBPopupMenuDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuDemoTests.m 3 | // QBPopupMenuDemoTests 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QBPopupMenuDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation QBPopupMenuDemoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuOverlayView.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/24. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenuOverlayView.h" 10 | 11 | #import "QBPopupMenu.h" 12 | 13 | @implementation QBPopupMenuOverlayView 14 | 15 | - (instancetype)initWithFrame:(CGRect)frame 16 | { 17 | self = [super initWithFrame:frame]; 18 | 19 | if (self) { 20 | self.backgroundColor = [UIColor clearColor]; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 27 | { 28 | UITouch *touch = [touches anyObject]; 29 | UIView *view = touch.view; 30 | 31 | if (view == self) { 32 | // Close popup menu 33 | [self.popupMenu dismissAnimated:YES]; 34 | } 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPlasticPopupMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPlasticPopupMenu.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/25. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenu.h" 10 | 11 | @interface QBPlasticPopupMenu : QBPopupMenu 12 | 13 | // NOTE: When subclassing this class, use these methods to customize the appearance. 14 | - (CGMutablePathRef)upperHeadPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 15 | - (CGMutablePathRef)lowerHeadPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 16 | - (CGMutablePathRef)upperTailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 17 | - (CGMutablePathRef)lowerTailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 18 | 19 | - (void)drawLeftSeparatorInRect:(CGRect)rect highlighted:(BOOL)highlighted; 20 | - (void)drawRightSeparatorInRect:(CGRect)rect highlighted:(BOOL)highlighted; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Katsuma Tanaka 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuItem.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QBPopupMenuItem : NSObject 12 | 13 | @property (nonatomic, weak, readonly) id target; 14 | @property (nonatomic, assign, readonly) SEL action; 15 | 16 | @property (nonatomic, copy, readonly) NSString *title; 17 | @property (nonatomic, copy, readonly) UIImage *image; 18 | 19 | + (instancetype)itemWithTitle:(NSString *)title target:(id)target action:(SEL)action; 20 | + (instancetype)itemWithImage:(UIImage *)image target:(id)target action:(SEL)action; 21 | + (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image target:(id)target action:(SEL)action; 22 | 23 | - (instancetype)initWithTitle:(NSString *)title target:(id)target action:(SEL)action; 24 | - (instancetype)initWithImage:(UIImage *)image target:(id)target action:(SEL)action; 25 | - (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image target:(id)target action:(SEL)action; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuPagenatorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuPagenatorView.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/23. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenuItemView.h" 10 | 11 | typedef NS_ENUM(NSUInteger, QBPopupMenuPagenatorDirection) { 12 | QBPopupMenuPagenatorDirectionLeft, 13 | QBPopupMenuPagenatorDirectionRight 14 | }; 15 | 16 | @interface QBPopupMenuPagenatorView : QBPopupMenuItemView 17 | 18 | @property (nonatomic, weak) id target; 19 | @property (nonatomic, assign) SEL action; 20 | 21 | + (CGFloat)pagenatorWidth; 22 | 23 | + (instancetype)leftPagenatorViewWithTarget:(id)target action:(SEL)action; 24 | + (instancetype)rightPagenatorViewWithTarget:(id)target action:(SEL)action; 25 | 26 | - (instancetype)initWithArrowDirection:(QBPopupMenuPagenatorDirection)arrowDirection target:(id)target action:(SEL)action; 27 | 28 | // NOTE: When subclassing this class, use these methods to customize the appearance. 29 | - (CGMutablePathRef)arrowPathInRect:(CGRect)rect direction:(QBPopupMenuPagenatorDirection)direction CF_RETURNS_RETAINED; 30 | - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuPagenatorDirection)direction highlighted:(BOOL)highlighted; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/QBPopupMenuDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | jp.questbeat.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QBPopupMenu 2 | Customizable popup menu for iOS. 3 | 4 | **QBPopupMenu version 2.0 is now available.** 5 | Its appearance is similar to `UIMenuController` of iOS 7, and it has several new features. 6 | 7 | 8 | ## Screenshot 9 | ![screenshot01.png](screenshot01.png) 10 | 11 | ![screenshot02.png](screenshot02.png) 12 | 13 | 14 | ## Installation 15 | QBPopupMenu is available in CocoaPods. 16 | 17 | pod 'QBPopupMenu' 18 | 19 | If you want to install manually, download this repository and copy files in QBPopupMenu directory to your project. 20 | 21 | 22 | ## Feature 23 | ### Customizable Appearance 24 | QBPopupMenu is highly customizable, so you can create your own popup menu. 25 | 26 | The simple way to customize is set `color` and `highlightcolor` property of `QBPopupMenu`. 27 | If you want to customize deeply, you should create a subclass of `QBPopupMenu` and override some drawing methods. 28 | 29 | `QBPlasticPopupMenu` class in this repository is a good example of subclassing. 30 | 31 | ### Auto Pagenation 32 | If you add many items to `QBPopupMenu`, it create pages and pagenator automatically. 33 | 34 | ### Auto Bounding 35 | `QBPopupMenu` automatically adjust its frame depending on the frame of target view. 36 | 37 | 38 | ## Example 39 | QBPopupMenuItem *item = [QBPopupMenuItem itemWithTitle:@"Text" target:self action:@selector(action:)]; 40 | QBPopupMenuItem *item2 = [QBPopupMenuItem itemWithImage:[UIImage imageNamed:@"image"] target:self action:@selector(action:)]; 41 | 42 | QBPopupMenu *popupMenu = [[QBPopupMenu alloc] initWithItems:@[item, item2]]; 43 | 44 | [popupMenu showInView:self.view targetRect:... animated:YES]; 45 | 46 | 47 | ## License 48 | *QBPopupMenu* is released under the **MIT License**, see *LICENSE.txt*. 49 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuItem.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenuItem.h" 10 | 11 | @interface QBPopupMenuItem () 12 | 13 | @property (nonatomic, weak, readwrite) id target; 14 | @property (nonatomic, assign, readwrite) SEL action; 15 | 16 | @property (nonatomic, copy, readwrite) NSString *title; 17 | @property (nonatomic, copy, readwrite) UIImage *image; 18 | 19 | @end 20 | 21 | @implementation QBPopupMenuItem 22 | 23 | + (instancetype)itemWithTitle:(NSString *)title target:(id)target action:(SEL)action 24 | { 25 | return [[self alloc] initWithTitle:title target:target action:action]; 26 | } 27 | 28 | + (instancetype)itemWithImage:(UIImage *)image target:(id)target action:(SEL)action 29 | { 30 | return [[self alloc] initWithImage:image target:target action:action]; 31 | } 32 | 33 | + (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image target:(id)target action:(SEL)action 34 | { 35 | return [[self alloc] initWithTitle:title image:image target:target action:action]; 36 | } 37 | 38 | - (instancetype)initWithTitle:(NSString *)title target:(id)target action:(SEL)action 39 | { 40 | return [self initWithTitle:title image:nil target:target action:action]; 41 | } 42 | 43 | - (instancetype)initWithImage:(UIImage *)image target:(id)target action:(SEL)action 44 | { 45 | return [self initWithTitle:nil image:image target:target action:action]; 46 | } 47 | 48 | - (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image target:(id)target action:(SEL)action 49 | { 50 | self = [super init]; 51 | 52 | if (self) { 53 | self.target = target; 54 | self.action = action; 55 | 56 | self.title = title; 57 | self.image = image; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // QBPopupMenuDemo 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // QBPopupMenuDemo 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "QBPopupMenu.h" 12 | #import "QBPlasticPopupMenu.h" 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, strong) QBPopupMenu *popupMenu; 17 | @property (nonatomic, strong) QBPlasticPopupMenu *plasticPopupMenu; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | 27 | QBPopupMenuItem *item = [QBPopupMenuItem itemWithTitle:@"Hello" target:self action:@selector(action)]; 28 | QBPopupMenuItem *item2 = [QBPopupMenuItem itemWithTitle:@"Cut" target:self action:@selector(action)]; 29 | QBPopupMenuItem *item3 = [QBPopupMenuItem itemWithTitle:@"Copy" target:self action:@selector(action)]; 30 | QBPopupMenuItem *item4 = [QBPopupMenuItem itemWithTitle:@"Delete" target:self action:@selector(action)]; 31 | QBPopupMenuItem *item5 = [QBPopupMenuItem itemWithImage:[UIImage imageNamed:@"clip"] target:self action:@selector(action)]; 32 | QBPopupMenuItem *item6 = [QBPopupMenuItem itemWithTitle:@"Delete" image:[UIImage imageNamed:@"trash"] target:self action:@selector(action)]; 33 | NSArray *items = @[item, item2, item3, item4, item5, item6]; 34 | 35 | QBPopupMenu *popupMenu = [[QBPopupMenu alloc] initWithItems:items]; 36 | popupMenu.highlightedColor = [[UIColor colorWithRed:0 green:0.478 blue:1.0 alpha:1.0] colorWithAlphaComponent:0.8]; 37 | self.popupMenu = popupMenu; 38 | 39 | QBPlasticPopupMenu *plasticPopupMenu = [[QBPlasticPopupMenu alloc] initWithItems:items]; 40 | plasticPopupMenu.height = 40; 41 | self.plasticPopupMenu = plasticPopupMenu; 42 | } 43 | 44 | 45 | - (IBAction)showPopupMenu:(id)sender 46 | { 47 | UIButton *button = (UIButton *)sender; 48 | [self.popupMenu showInView:self.view targetRect:button.frame animated:YES]; 49 | } 50 | 51 | - (IBAction)showPlasticPopupMenu:(id)sender 52 | { 53 | UIButton *button = (UIButton *)sender; 54 | [self.plasticPopupMenu showInView:self.view targetRect:button.frame animated:YES]; 55 | } 56 | 57 | - (void)action 58 | { 59 | NSLog(@"*** action"); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenu.h 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QBPopupMenuItem.h" 12 | 13 | @class QBPopupMenu; 14 | @class QBPopupMenuItemView; 15 | @class QBPopupMenuPagenatorView; 16 | 17 | @protocol QBPopupMenuDelegate 18 | 19 | @optional 20 | - (void)popupMenuWillAppear:(QBPopupMenu *)popupMenu; 21 | - (void)popupMenuDidAppear:(QBPopupMenu *)popupMenu; 22 | - (void)popupMenuWillDisappear:(QBPopupMenu *)popupMenu; 23 | - (void)popupMenuDidDisappear:(QBPopupMenu *)popupMenu; 24 | 25 | @end 26 | 27 | typedef NS_ENUM(NSUInteger, QBPopupMenuArrowDirection) { 28 | QBPopupMenuArrowDirectionDefault, 29 | QBPopupMenuArrowDirectionUp, 30 | QBPopupMenuArrowDirectionDown, 31 | QBPopupMenuArrowDirectionLeft, 32 | QBPopupMenuArrowDirectionRight 33 | }; 34 | 35 | @interface QBPopupMenu : UIView 36 | 37 | @property (nonatomic, weak) id delegate; 38 | 39 | @property (nonatomic, assign, getter = isVisible, readonly) BOOL visible; 40 | @property (nonatomic, copy) NSArray *items; 41 | @property (nonatomic, assign) CGFloat height; 42 | @property (nonatomic, assign) CGFloat cornerRadius; 43 | @property (nonatomic, assign) CGFloat arrowSize; 44 | @property (nonatomic, assign) QBPopupMenuArrowDirection arrowDirection; 45 | @property (nonatomic, assign) UIEdgeInsets popupMenuInsets; 46 | @property (nonatomic, assign) CGFloat margin; 47 | 48 | @property (nonatomic, strong) UIColor *color; 49 | @property (nonatomic, strong) UIColor *highlightedColor; 50 | 51 | + (instancetype)popupMenuWithItems:(NSArray *)items; 52 | - (instancetype)initWithItems:(NSArray *)items; 53 | 54 | - (void)showInView:(UIView *)view targetRect:(CGRect)targetRect animated:(BOOL)animated; 55 | - (void)dismissAnimated:(BOOL)animated; 56 | - (void)updateWithTargetRect:(CGRect)targetRect; 57 | 58 | // NOTE: When subclassing this class, use these methods to customize the appearance. 59 | + (Class)itemViewClass; 60 | + (Class)pagenatorViewClass; 61 | 62 | - (CGMutablePathRef)arrowPathInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction CF_RETURNS_RETAINED; 63 | - (CGMutablePathRef)headPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 64 | - (CGMutablePathRef)tailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius CF_RETURNS_RETAINED; 65 | - (CGMutablePathRef)bodyPathInRect:(CGRect)rect CF_RETURNS_RETAINED; 66 | 67 | - (void)drawArrowAtPoint:(CGPoint)point arrowSize:(CGFloat)arrowSize arrowDirection:(QBPopupMenuArrowDirection)arrowDirection highlighted:(BOOL)highlighted; 68 | - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction highlighted:(BOOL)highlighted; 69 | - (void)drawHeadInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted; 70 | - (void)drawTailInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted; 71 | - (void)drawBodyInRect:(CGRect)rect firstItem:(BOOL)firstItem lastItem:(BOOL)lastItem highlighted:(BOOL)highlighted; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuItemView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuItemView.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenuItemView.h" 10 | 11 | #import "QBPopupMenu.h" 12 | #import "QBPopupMenuItem.h" 13 | 14 | @interface QBPopupMenuItemView () 15 | 16 | @property (nonatomic, strong, readwrite) UIButton *button; 17 | 18 | @end 19 | 20 | @implementation QBPopupMenuItemView 21 | 22 | + (instancetype)itemViewWithItem:(QBPopupMenuItem *)item 23 | { 24 | return [[self alloc] initWithItem:item]; 25 | } 26 | 27 | - (instancetype)initWithItem:(QBPopupMenuItem *)item 28 | { 29 | self = [super initWithFrame:CGRectZero]; 30 | 31 | if (self) { 32 | // View settings 33 | self.opaque = NO; 34 | self.backgroundColor = [UIColor clearColor]; 35 | self.clipsToBounds = YES; 36 | 37 | // Create button 38 | self.button = ({ 39 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 40 | button.frame = self.bounds; 41 | button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 42 | [button addTarget:self action:@selector(performAction) forControlEvents:UIControlEventTouchUpInside]; 43 | 44 | // Set style 45 | button.contentMode = UIViewContentModeScaleAspectFit; 46 | button.titleLabel.font = [UIFont systemFontOfSize:14.0]; 47 | button.imageView.contentMode = UIViewContentModeScaleAspectFit; 48 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 49 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; 50 | 51 | button; 52 | }); 53 | [self addSubview:self.button]; 54 | 55 | // Property settings 56 | self.item = item; 57 | } 58 | 59 | return self; 60 | } 61 | 62 | 63 | #pragma mark - Accessors 64 | 65 | - (void)setImage:(UIImage *)image 66 | { 67 | [self.button setBackgroundImage:image forState:UIControlStateNormal]; 68 | } 69 | 70 | - (UIImage *)image 71 | { 72 | return [self.button backgroundImageForState:UIControlStateNormal]; 73 | } 74 | 75 | - (void)setHighlightedImage:(UIImage *)highlightedImage 76 | { 77 | [self.button setBackgroundImage:highlightedImage forState:UIControlStateHighlighted]; 78 | } 79 | 80 | - (UIImage *)highlightedImage 81 | { 82 | return [self.button backgroundImageForState:UIControlStateHighlighted]; 83 | } 84 | 85 | - (void)setItem:(QBPopupMenuItem *)item 86 | { 87 | _item = item; 88 | 89 | // Update 90 | [self configureButton]; 91 | } 92 | 93 | 94 | #pragma mark - Actions 95 | 96 | - (void)performAction 97 | { 98 | if (self.item.target && self.item.action) { 99 | [self.item.target performSelector:self.item.action withObject:nil afterDelay:0]; 100 | } 101 | 102 | // Close popup menu 103 | [self.popupMenu dismissAnimated:YES]; 104 | } 105 | 106 | 107 | #pragma mark - Updating the View 108 | 109 | - (void)sizeToFit 110 | { 111 | CGSize size = [self sizeThatFits:CGSizeZero]; 112 | 113 | CGRect frame = self.frame; 114 | frame.size = size; 115 | self.frame = frame; 116 | } 117 | 118 | - (CGSize)sizeThatFits:(CGSize)size 119 | { 120 | CGSize buttonSize = [self.button sizeThatFits:CGSizeZero]; 121 | buttonSize.width += 10 * 2; 122 | 123 | return buttonSize; 124 | } 125 | 126 | - (void)configureButton 127 | { 128 | // Title 129 | [self.button setTitle:self.item.title forState:UIControlStateNormal]; 130 | 131 | // Image 132 | [self.button setImage:self.item.image forState:UIControlStateNormal]; 133 | [self.button setImage:self.item.image forState:UIControlStateHighlighted]; 134 | 135 | // Content edge insets 136 | if (self.item.title && self.item.image) { 137 | self.button.titleEdgeInsets = UIEdgeInsetsMake(0, 6, 0, 0); 138 | self.button.imageEdgeInsets = UIEdgeInsetsMake(0, -3, 0, 0); 139 | } else { 140 | self.button.titleEdgeInsets = UIEdgeInsetsZero; 141 | self.button.imageEdgeInsets = UIEdgeInsetsZero; 142 | } 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenuPagenatorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenuPagenatorView.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/23. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenuPagenatorView.h" 10 | 11 | @implementation QBPopupMenuPagenatorView 12 | 13 | + (CGFloat)pagenatorWidth 14 | { 15 | return 10 + 10 * 2; 16 | } 17 | 18 | + (instancetype)leftPagenatorViewWithTarget:(id)target action:(SEL)action 19 | { 20 | return [[self alloc] initWithArrowDirection:QBPopupMenuPagenatorDirectionLeft target:target action:action]; 21 | } 22 | 23 | + (instancetype)rightPagenatorViewWithTarget:(id)target action:(SEL)action 24 | { 25 | return [[self alloc] initWithArrowDirection:QBPopupMenuPagenatorDirectionRight target:target action:action]; 26 | } 27 | 28 | - (instancetype)initWithArrowDirection:(QBPopupMenuPagenatorDirection)arrowDirection target:(id)target action:(SEL)action 29 | { 30 | self = [super initWithItem:nil]; 31 | 32 | if (self) { 33 | // Property settings 34 | self.target = target; 35 | self.action = action; 36 | 37 | // Set arrow image 38 | UIImage *arrowImage = [self arrowImageWithSize:CGSizeMake(10, 10) 39 | direction:arrowDirection 40 | highlighted:NO]; 41 | [self.button setImage:arrowImage forState:UIControlStateNormal]; 42 | 43 | UIImage *highlightedArrowImage = [self arrowImageWithSize:CGSizeMake(10, 10) 44 | direction:arrowDirection 45 | highlighted:YES]; 46 | [self.button setImage:highlightedArrowImage forState:UIControlStateHighlighted]; 47 | } 48 | 49 | return self; 50 | } 51 | 52 | 53 | #pragma mark - Actions 54 | 55 | - (void)performAction 56 | { 57 | if (self.target && self.action) { 58 | [self.target performSelector:self.action withObject:nil afterDelay:0]; 59 | } 60 | } 61 | 62 | 63 | #pragma mark - Updating the View 64 | 65 | - (CGSize)sizeThatFits:(CGSize)size 66 | { 67 | CGSize buttonSize = [self.button sizeThatFits:CGSizeZero]; 68 | buttonSize.width = [[self class] pagenatorWidth]; 69 | 70 | return buttonSize; 71 | } 72 | 73 | - (UIImage *)arrowImageWithSize:(CGSize)size direction:(QBPopupMenuPagenatorDirection)direction highlighted:(BOOL)highlighted 74 | { 75 | UIGraphicsBeginImageContextWithOptions(size, NO, 0); 76 | 77 | // Draw arrow 78 | [self drawArrowInRect:CGRectMake(0, 0, size.width, size.height) direction:direction highlighted:highlighted]; 79 | 80 | // Create image from buffer 81 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 82 | 83 | UIGraphicsEndImageContext(); 84 | 85 | return image; 86 | } 87 | 88 | 89 | #pragma mark - Creating Paths 90 | 91 | - (CGMutablePathRef)arrowPathInRect:(CGRect)rect direction:(QBPopupMenuPagenatorDirection)direction 92 | { 93 | CGMutablePathRef path = CGPathCreateMutable(); 94 | 95 | switch (direction) { 96 | case QBPopupMenuPagenatorDirectionLeft: 97 | { 98 | CGPathMoveToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 99 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 100 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height / 2.0); 101 | CGPathCloseSubpath(path); 102 | } 103 | break; 104 | 105 | case QBPopupMenuPagenatorDirectionRight: 106 | { 107 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 108 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 109 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height / 2.0); 110 | CGPathCloseSubpath(path); 111 | } 112 | break; 113 | 114 | default: 115 | break; 116 | } 117 | 118 | return path; 119 | } 120 | 121 | 122 | #pragma mark - Drawing 123 | 124 | - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuPagenatorDirection)direction highlighted:(BOOL)highlighted 125 | { 126 | CGContextRef context = UIGraphicsGetCurrentContext(); 127 | CGContextSaveGState(context); 128 | 129 | CGMutablePathRef path = [self arrowPathInRect:rect direction:direction]; 130 | CGContextAddPath(context, path); 131 | 132 | CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); 133 | CGContextFillPath(context); 134 | 135 | CGPathRelease(path); 136 | 137 | CGContextRestoreGState(context); 138 | } 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 35 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /QBPopupMenuDemo/QBPopupMenuDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA7C7DAE184C791F00CF859B /* QBPlasticPopupMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DA3184C791F00CF859B /* QBPlasticPopupMenu.m */; }; 11 | AA7C7DAF184C791F00CF859B /* QBPopupMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DA5184C791F00CF859B /* QBPopupMenu.m */; }; 12 | AA7C7DB0184C791F00CF859B /* QBPopupMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DA7184C791F00CF859B /* QBPopupMenuItem.m */; }; 13 | AA7C7DB1184C791F00CF859B /* QBPopupMenuItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DA9184C791F00CF859B /* QBPopupMenuItemView.m */; }; 14 | AA7C7DB2184C791F00CF859B /* QBPopupMenuOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DAB184C791F00CF859B /* QBPopupMenuOverlayView.m */; }; 15 | AA7C7DB3184C791F00CF859B /* QBPopupMenuPagenatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA7C7DAD184C791F00CF859B /* QBPopupMenuPagenatorView.m */; }; 16 | AA7FA185184321600044F5D7 /* background.jpg in Resources */ = {isa = PBXBuildFile; fileRef = AA7FA183184321600044F5D7 /* background.jpg */; }; 17 | AAD226E5183F91C300C27616 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD226E4183F91C300C27616 /* Foundation.framework */; }; 18 | AAD226E7183F91C300C27616 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD226E6183F91C300C27616 /* CoreGraphics.framework */; }; 19 | AAD226E9183F91C300C27616 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD226E8183F91C300C27616 /* UIKit.framework */; }; 20 | AAD226EF183F91C300C27616 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AAD226ED183F91C300C27616 /* InfoPlist.strings */; }; 21 | AAD226F1183F91C300C27616 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD226F0183F91C300C27616 /* main.m */; }; 22 | AAD226F5183F91C300C27616 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD226F4183F91C300C27616 /* AppDelegate.m */; }; 23 | AAD226F8183F91C300C27616 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAD226F6183F91C300C27616 /* Main.storyboard */; }; 24 | AAD226FB183F91C300C27616 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD226FA183F91C300C27616 /* ViewController.m */; }; 25 | AAD226FD183F91C300C27616 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AAD226FC183F91C300C27616 /* Images.xcassets */; }; 26 | AAD22704183F91C300C27616 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD22703183F91C300C27616 /* XCTest.framework */; }; 27 | AAD22705183F91C300C27616 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD226E4183F91C300C27616 /* Foundation.framework */; }; 28 | AAD22706183F91C300C27616 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD226E8183F91C300C27616 /* UIKit.framework */; }; 29 | AAD2270E183F91C300C27616 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AAD2270C183F91C300C27616 /* InfoPlist.strings */; }; 30 | AAD22710183F91C300C27616 /* QBPopupMenuDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AAD2270F183F91C300C27616 /* QBPopupMenuDemoTests.m */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | AAD22707183F91C300C27616 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = AAD226D9183F91C300C27616 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = AAD226E0183F91C300C27616; 39 | remoteInfo = QBPopupMenuDemo; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | AA7C7DA2184C791F00CF859B /* QBPlasticPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPlasticPopupMenu.h; sourceTree = ""; }; 45 | AA7C7DA3184C791F00CF859B /* QBPlasticPopupMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPlasticPopupMenu.m; sourceTree = ""; }; 46 | AA7C7DA4184C791F00CF859B /* QBPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPopupMenu.h; sourceTree = ""; }; 47 | AA7C7DA5184C791F00CF859B /* QBPopupMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenu.m; sourceTree = ""; }; 48 | AA7C7DA6184C791F00CF859B /* QBPopupMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPopupMenuItem.h; sourceTree = ""; }; 49 | AA7C7DA7184C791F00CF859B /* QBPopupMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenuItem.m; sourceTree = ""; }; 50 | AA7C7DA8184C791F00CF859B /* QBPopupMenuItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPopupMenuItemView.h; sourceTree = ""; }; 51 | AA7C7DA9184C791F00CF859B /* QBPopupMenuItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenuItemView.m; sourceTree = ""; }; 52 | AA7C7DAA184C791F00CF859B /* QBPopupMenuOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPopupMenuOverlayView.h; sourceTree = ""; }; 53 | AA7C7DAB184C791F00CF859B /* QBPopupMenuOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenuOverlayView.m; sourceTree = ""; }; 54 | AA7C7DAC184C791F00CF859B /* QBPopupMenuPagenatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QBPopupMenuPagenatorView.h; sourceTree = ""; }; 55 | AA7C7DAD184C791F00CF859B /* QBPopupMenuPagenatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenuPagenatorView.m; sourceTree = ""; }; 56 | AA7FA183184321600044F5D7 /* background.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = background.jpg; sourceTree = ""; }; 57 | AAD226E1183F91C300C27616 /* QBPopupMenuDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QBPopupMenuDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | AAD226E4183F91C300C27616 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 59 | AAD226E6183F91C300C27616 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 60 | AAD226E8183F91C300C27616 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 61 | AAD226EC183F91C300C27616 /* QBPopupMenuDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "QBPopupMenuDemo-Info.plist"; sourceTree = ""; }; 62 | AAD226EE183F91C300C27616 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 63 | AAD226F0183F91C300C27616 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 64 | AAD226F2183F91C300C27616 /* QBPopupMenuDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "QBPopupMenuDemo-Prefix.pch"; sourceTree = ""; }; 65 | AAD226F3183F91C300C27616 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 66 | AAD226F4183F91C300C27616 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 67 | AAD226F7183F91C300C27616 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 68 | AAD226F9183F91C300C27616 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 69 | AAD226FA183F91C300C27616 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 70 | AAD226FC183F91C300C27616 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 71 | AAD22702183F91C300C27616 /* QBPopupMenuDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QBPopupMenuDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | AAD22703183F91C300C27616 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 73 | AAD2270B183F91C300C27616 /* QBPopupMenuDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "QBPopupMenuDemoTests-Info.plist"; sourceTree = ""; }; 74 | AAD2270D183F91C300C27616 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 75 | AAD2270F183F91C300C27616 /* QBPopupMenuDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QBPopupMenuDemoTests.m; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | AAD226DE183F91C300C27616 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | AAD226E7183F91C300C27616 /* CoreGraphics.framework in Frameworks */, 84 | AAD226E9183F91C300C27616 /* UIKit.framework in Frameworks */, 85 | AAD226E5183F91C300C27616 /* Foundation.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | AAD226FF183F91C300C27616 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | AAD22704183F91C300C27616 /* XCTest.framework in Frameworks */, 94 | AAD22706183F91C300C27616 /* UIKit.framework in Frameworks */, 95 | AAD22705183F91C300C27616 /* Foundation.framework in Frameworks */, 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | /* End PBXFrameworksBuildPhase section */ 100 | 101 | /* Begin PBXGroup section */ 102 | AA7C7DA1184C791F00CF859B /* QBPopupMenu */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | AA7C7DA4184C791F00CF859B /* QBPopupMenu.h */, 106 | AA7C7DA5184C791F00CF859B /* QBPopupMenu.m */, 107 | AA7C7DAA184C791F00CF859B /* QBPopupMenuOverlayView.h */, 108 | AA7C7DAB184C791F00CF859B /* QBPopupMenuOverlayView.m */, 109 | AA7C7DA6184C791F00CF859B /* QBPopupMenuItem.h */, 110 | AA7C7DA7184C791F00CF859B /* QBPopupMenuItem.m */, 111 | AA7C7DA8184C791F00CF859B /* QBPopupMenuItemView.h */, 112 | AA7C7DA9184C791F00CF859B /* QBPopupMenuItemView.m */, 113 | AA7C7DAC184C791F00CF859B /* QBPopupMenuPagenatorView.h */, 114 | AA7C7DAD184C791F00CF859B /* QBPopupMenuPagenatorView.m */, 115 | AA7C7DA2184C791F00CF859B /* QBPlasticPopupMenu.h */, 116 | AA7C7DA3184C791F00CF859B /* QBPlasticPopupMenu.m */, 117 | ); 118 | name = QBPopupMenu; 119 | path = ../../QBPopupMenu; 120 | sourceTree = ""; 121 | }; 122 | AAD226D8183F91C300C27616 = { 123 | isa = PBXGroup; 124 | children = ( 125 | AAD226EA183F91C300C27616 /* QBPopupMenuDemo */, 126 | AAD22709183F91C300C27616 /* QBPopupMenuDemoTests */, 127 | AAD226E3183F91C300C27616 /* Frameworks */, 128 | AAD226E2183F91C300C27616 /* Products */, 129 | ); 130 | sourceTree = ""; 131 | }; 132 | AAD226E2183F91C300C27616 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | AAD226E1183F91C300C27616 /* QBPopupMenuDemo.app */, 136 | AAD22702183F91C300C27616 /* QBPopupMenuDemoTests.xctest */, 137 | ); 138 | name = Products; 139 | sourceTree = ""; 140 | }; 141 | AAD226E3183F91C300C27616 /* Frameworks */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | AAD226E4183F91C300C27616 /* Foundation.framework */, 145 | AAD226E6183F91C300C27616 /* CoreGraphics.framework */, 146 | AAD226E8183F91C300C27616 /* UIKit.framework */, 147 | AAD22703183F91C300C27616 /* XCTest.framework */, 148 | ); 149 | name = Frameworks; 150 | sourceTree = ""; 151 | }; 152 | AAD226EA183F91C300C27616 /* QBPopupMenuDemo */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | AA7C7DA1184C791F00CF859B /* QBPopupMenu */, 156 | AAD226F3183F91C300C27616 /* AppDelegate.h */, 157 | AAD226F4183F91C300C27616 /* AppDelegate.m */, 158 | AAD226F9183F91C300C27616 /* ViewController.h */, 159 | AAD226FA183F91C300C27616 /* ViewController.m */, 160 | AAD22719183F91CC00C27616 /* Resources */, 161 | AAD226EB183F91C300C27616 /* Supporting Files */, 162 | ); 163 | path = QBPopupMenuDemo; 164 | sourceTree = ""; 165 | }; 166 | AAD226EB183F91C300C27616 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | AAD226EC183F91C300C27616 /* QBPopupMenuDemo-Info.plist */, 170 | AAD226ED183F91C300C27616 /* InfoPlist.strings */, 171 | AAD226F0183F91C300C27616 /* main.m */, 172 | AAD226F2183F91C300C27616 /* QBPopupMenuDemo-Prefix.pch */, 173 | ); 174 | name = "Supporting Files"; 175 | sourceTree = ""; 176 | }; 177 | AAD22709183F91C300C27616 /* QBPopupMenuDemoTests */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | AAD2270F183F91C300C27616 /* QBPopupMenuDemoTests.m */, 181 | AAD2270A183F91C300C27616 /* Supporting Files */, 182 | ); 183 | path = QBPopupMenuDemoTests; 184 | sourceTree = ""; 185 | }; 186 | AAD2270A183F91C300C27616 /* Supporting Files */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | AAD2270B183F91C300C27616 /* QBPopupMenuDemoTests-Info.plist */, 190 | AAD2270C183F91C300C27616 /* InfoPlist.strings */, 191 | ); 192 | name = "Supporting Files"; 193 | sourceTree = ""; 194 | }; 195 | AAD22719183F91CC00C27616 /* Resources */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | AA7FA183184321600044F5D7 /* background.jpg */, 199 | AAD226F6183F91C300C27616 /* Main.storyboard */, 200 | AAD226FC183F91C300C27616 /* Images.xcassets */, 201 | ); 202 | name = Resources; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXGroup section */ 206 | 207 | /* Begin PBXNativeTarget section */ 208 | AAD226E0183F91C300C27616 /* QBPopupMenuDemo */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = AAD22713183F91C300C27616 /* Build configuration list for PBXNativeTarget "QBPopupMenuDemo" */; 211 | buildPhases = ( 212 | AAD226DD183F91C300C27616 /* Sources */, 213 | AAD226DE183F91C300C27616 /* Frameworks */, 214 | AAD226DF183F91C300C27616 /* Resources */, 215 | ); 216 | buildRules = ( 217 | ); 218 | dependencies = ( 219 | ); 220 | name = QBPopupMenuDemo; 221 | productName = QBPopupMenuDemo; 222 | productReference = AAD226E1183F91C300C27616 /* QBPopupMenuDemo.app */; 223 | productType = "com.apple.product-type.application"; 224 | }; 225 | AAD22701183F91C300C27616 /* QBPopupMenuDemoTests */ = { 226 | isa = PBXNativeTarget; 227 | buildConfigurationList = AAD22716183F91C300C27616 /* Build configuration list for PBXNativeTarget "QBPopupMenuDemoTests" */; 228 | buildPhases = ( 229 | AAD226FE183F91C300C27616 /* Sources */, 230 | AAD226FF183F91C300C27616 /* Frameworks */, 231 | AAD22700183F91C300C27616 /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | AAD22708183F91C300C27616 /* PBXTargetDependency */, 237 | ); 238 | name = QBPopupMenuDemoTests; 239 | productName = QBPopupMenuDemoTests; 240 | productReference = AAD22702183F91C300C27616 /* QBPopupMenuDemoTests.xctest */; 241 | productType = "com.apple.product-type.bundle.unit-test"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | AAD226D9183F91C300C27616 /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastUpgradeCheck = 0500; 250 | ORGANIZATIONNAME = "Katsuma Tanaka"; 251 | TargetAttributes = { 252 | AAD22701183F91C300C27616 = { 253 | TestTargetID = AAD226E0183F91C300C27616; 254 | }; 255 | }; 256 | }; 257 | buildConfigurationList = AAD226DC183F91C300C27616 /* Build configuration list for PBXProject "QBPopupMenuDemo" */; 258 | compatibilityVersion = "Xcode 3.2"; 259 | developmentRegion = English; 260 | hasScannedForEncodings = 0; 261 | knownRegions = ( 262 | en, 263 | Base, 264 | ); 265 | mainGroup = AAD226D8183F91C300C27616; 266 | productRefGroup = AAD226E2183F91C300C27616 /* Products */; 267 | projectDirPath = ""; 268 | projectRoot = ""; 269 | targets = ( 270 | AAD226E0183F91C300C27616 /* QBPopupMenuDemo */, 271 | AAD22701183F91C300C27616 /* QBPopupMenuDemoTests */, 272 | ); 273 | }; 274 | /* End PBXProject section */ 275 | 276 | /* Begin PBXResourcesBuildPhase section */ 277 | AAD226DF183F91C300C27616 /* Resources */ = { 278 | isa = PBXResourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | AAD226FD183F91C300C27616 /* Images.xcassets in Resources */, 282 | AAD226EF183F91C300C27616 /* InfoPlist.strings in Resources */, 283 | AA7FA185184321600044F5D7 /* background.jpg in Resources */, 284 | AAD226F8183F91C300C27616 /* Main.storyboard in Resources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | AAD22700183F91C300C27616 /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | AAD2270E183F91C300C27616 /* InfoPlist.strings in Resources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXResourcesBuildPhase section */ 297 | 298 | /* Begin PBXSourcesBuildPhase section */ 299 | AAD226DD183F91C300C27616 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | AAD226FB183F91C300C27616 /* ViewController.m in Sources */, 304 | AAD226F5183F91C300C27616 /* AppDelegate.m in Sources */, 305 | AA7C7DAF184C791F00CF859B /* QBPopupMenu.m in Sources */, 306 | AA7C7DB0184C791F00CF859B /* QBPopupMenuItem.m in Sources */, 307 | AA7C7DB3184C791F00CF859B /* QBPopupMenuPagenatorView.m in Sources */, 308 | AA7C7DB2184C791F00CF859B /* QBPopupMenuOverlayView.m in Sources */, 309 | AAD226F1183F91C300C27616 /* main.m in Sources */, 310 | AA7C7DAE184C791F00CF859B /* QBPlasticPopupMenu.m in Sources */, 311 | AA7C7DB1184C791F00CF859B /* QBPopupMenuItemView.m in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | AAD226FE183F91C300C27616 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | AAD22710183F91C300C27616 /* QBPopupMenuDemoTests.m in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXTargetDependency section */ 326 | AAD22708183F91C300C27616 /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = AAD226E0183F91C300C27616 /* QBPopupMenuDemo */; 329 | targetProxy = AAD22707183F91C300C27616 /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin PBXVariantGroup section */ 334 | AAD226ED183F91C300C27616 /* InfoPlist.strings */ = { 335 | isa = PBXVariantGroup; 336 | children = ( 337 | AAD226EE183F91C300C27616 /* en */, 338 | ); 339 | name = InfoPlist.strings; 340 | sourceTree = ""; 341 | }; 342 | AAD226F6183F91C300C27616 /* Main.storyboard */ = { 343 | isa = PBXVariantGroup; 344 | children = ( 345 | AAD226F7183F91C300C27616 /* Base */, 346 | ); 347 | name = Main.storyboard; 348 | sourceTree = ""; 349 | }; 350 | AAD2270C183F91C300C27616 /* InfoPlist.strings */ = { 351 | isa = PBXVariantGroup; 352 | children = ( 353 | AAD2270D183F91C300C27616 /* en */, 354 | ); 355 | name = InfoPlist.strings; 356 | sourceTree = ""; 357 | }; 358 | /* End PBXVariantGroup section */ 359 | 360 | /* Begin XCBuildConfiguration section */ 361 | AAD22711183F91C300C27616 /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 366 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 367 | CLANG_CXX_LIBRARY = "libc++"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 379 | COPY_PHASE_STRIP = NO; 380 | GCC_C_LANGUAGE_STANDARD = gnu99; 381 | GCC_DYNAMIC_NO_PIC = NO; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = iphoneos; 397 | }; 398 | name = Debug; 399 | }; 400 | AAD22712183F91C300C27616 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 406 | CLANG_CXX_LIBRARY = "libc++"; 407 | CLANG_ENABLE_MODULES = YES; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_CONSTANT_CONVERSION = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_EMPTY_BODY = YES; 413 | CLANG_WARN_ENUM_CONVERSION = YES; 414 | CLANG_WARN_INT_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 417 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 418 | COPY_PHASE_STRIP = YES; 419 | ENABLE_NS_ASSERTIONS = NO; 420 | GCC_C_LANGUAGE_STANDARD = gnu99; 421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 423 | GCC_WARN_UNDECLARED_SELECTOR = YES; 424 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 425 | GCC_WARN_UNUSED_FUNCTION = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 428 | SDKROOT = iphoneos; 429 | VALIDATE_PRODUCT = YES; 430 | }; 431 | name = Release; 432 | }; 433 | AAD22714183F91C300C27616 /* Debug */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 438 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 439 | GCC_PREFIX_HEADER = "QBPopupMenuDemo/QBPopupMenuDemo-Prefix.pch"; 440 | INFOPLIST_FILE = "QBPopupMenuDemo/QBPopupMenuDemo-Info.plist"; 441 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | WRAPPER_EXTENSION = app; 444 | }; 445 | name = Debug; 446 | }; 447 | AAD22715183F91C300C27616 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 452 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 453 | GCC_PREFIX_HEADER = "QBPopupMenuDemo/QBPopupMenuDemo-Prefix.pch"; 454 | INFOPLIST_FILE = "QBPopupMenuDemo/QBPopupMenuDemo-Info.plist"; 455 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | WRAPPER_EXTENSION = app; 458 | }; 459 | name = Release; 460 | }; 461 | AAD22717183F91C300C27616 /* Debug */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 465 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/QBPopupMenuDemo.app/QBPopupMenuDemo"; 466 | FRAMEWORK_SEARCH_PATHS = ( 467 | "$(SDKROOT)/Developer/Library/Frameworks", 468 | "$(inherited)", 469 | "$(DEVELOPER_FRAMEWORKS_DIR)", 470 | ); 471 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 472 | GCC_PREFIX_HEADER = "QBPopupMenuDemo/QBPopupMenuDemo-Prefix.pch"; 473 | GCC_PREPROCESSOR_DEFINITIONS = ( 474 | "DEBUG=1", 475 | "$(inherited)", 476 | ); 477 | INFOPLIST_FILE = "QBPopupMenuDemoTests/QBPopupMenuDemoTests-Info.plist"; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | TEST_HOST = "$(BUNDLE_LOADER)"; 480 | WRAPPER_EXTENSION = xctest; 481 | }; 482 | name = Debug; 483 | }; 484 | AAD22718183F91C300C27616 /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 488 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/QBPopupMenuDemo.app/QBPopupMenuDemo"; 489 | FRAMEWORK_SEARCH_PATHS = ( 490 | "$(SDKROOT)/Developer/Library/Frameworks", 491 | "$(inherited)", 492 | "$(DEVELOPER_FRAMEWORKS_DIR)", 493 | ); 494 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 495 | GCC_PREFIX_HEADER = "QBPopupMenuDemo/QBPopupMenuDemo-Prefix.pch"; 496 | INFOPLIST_FILE = "QBPopupMenuDemoTests/QBPopupMenuDemoTests-Info.plist"; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | TEST_HOST = "$(BUNDLE_LOADER)"; 499 | WRAPPER_EXTENSION = xctest; 500 | }; 501 | name = Release; 502 | }; 503 | /* End XCBuildConfiguration section */ 504 | 505 | /* Begin XCConfigurationList section */ 506 | AAD226DC183F91C300C27616 /* Build configuration list for PBXProject "QBPopupMenuDemo" */ = { 507 | isa = XCConfigurationList; 508 | buildConfigurations = ( 509 | AAD22711183F91C300C27616 /* Debug */, 510 | AAD22712183F91C300C27616 /* Release */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | AAD22713183F91C300C27616 /* Build configuration list for PBXNativeTarget "QBPopupMenuDemo" */ = { 516 | isa = XCConfigurationList; 517 | buildConfigurations = ( 518 | AAD22714183F91C300C27616 /* Debug */, 519 | AAD22715183F91C300C27616 /* Release */, 520 | ); 521 | defaultConfigurationIsVisible = 0; 522 | defaultConfigurationName = Release; 523 | }; 524 | AAD22716183F91C300C27616 /* Build configuration list for PBXNativeTarget "QBPopupMenuDemoTests" */ = { 525 | isa = XCConfigurationList; 526 | buildConfigurations = ( 527 | AAD22717183F91C300C27616 /* Debug */, 528 | AAD22718183F91C300C27616 /* Release */, 529 | ); 530 | defaultConfigurationIsVisible = 0; 531 | defaultConfigurationName = Release; 532 | }; 533 | /* End XCConfigurationList section */ 534 | }; 535 | rootObject = AAD226D9183F91C300C27616 /* Project object */; 536 | } 537 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPlasticPopupMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPlasticPopupMenu.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/25. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPlasticPopupMenu.h" 10 | 11 | @implementation QBPlasticPopupMenu 12 | 13 | #pragma mark - Creating Paths 14 | 15 | - (CGMutablePathRef)upperHeadPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 16 | { 17 | CGMutablePathRef path = CGPathCreateMutable(); 18 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + cornerRadius); 19 | CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y, rect.origin.x + cornerRadius, rect.origin.y, cornerRadius); 20 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 21 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 22 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 23 | CGPathCloseSubpath(path); 24 | 25 | return path; 26 | } 27 | 28 | - (CGMutablePathRef)lowerHeadPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 29 | { 30 | CGMutablePathRef path = CGPathCreateMutable(); 31 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 32 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 33 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 34 | CGPathAddLineToPoint(path, NULL, rect.origin.x + cornerRadius, rect.origin.y + rect.size.height); 35 | CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height, rect.origin.x, rect.origin.y + rect.size.height - cornerRadius, cornerRadius); 36 | CGPathCloseSubpath(path); 37 | 38 | return path; 39 | } 40 | 41 | - (CGMutablePathRef)upperTailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 42 | { 43 | CGMutablePathRef path = CGPathCreateMutable(); 44 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 45 | CGPathMoveToPoint(path, NULL, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y); 46 | CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y + cornerRadius, cornerRadius); 47 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 48 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 49 | CGPathCloseSubpath(path); 50 | 51 | return path; 52 | } 53 | 54 | - (CGMutablePathRef)lowerTailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 55 | { 56 | CGMutablePathRef path = CGPathCreateMutable(); 57 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 58 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 59 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - cornerRadius); 60 | CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y + rect.size.height, cornerRadius); 61 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 62 | CGPathCloseSubpath(path); 63 | 64 | return path; 65 | } 66 | 67 | 68 | #pragma mark - Drawing Popup Menu Image 69 | 70 | - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction highlighted:(BOOL)highlighted 71 | { 72 | CGContextRef context = UIGraphicsGetCurrentContext(); 73 | 74 | // Border 75 | CGContextSaveGState(context); { 76 | CGRect arrowRect = CGRectZero; 77 | switch (direction) { 78 | case QBPopupMenuArrowDirectionDown: 79 | arrowRect = CGRectMake(rect.origin.x, rect.origin.y - 0.6, rect.size.width, rect.size.height); 80 | break; 81 | 82 | case QBPopupMenuArrowDirectionUp: 83 | arrowRect = CGRectMake(rect.origin.x, rect.origin.y + 0.6, rect.size.width, rect.size.height); 84 | break; 85 | 86 | case QBPopupMenuArrowDirectionLeft: 87 | arrowRect = CGRectMake(rect.origin.x + 0.6, rect.origin.y - 0.5, rect.size.width, rect.size.height); 88 | break; 89 | 90 | case QBPopupMenuArrowDirectionRight: 91 | arrowRect = CGRectMake(rect.origin.x - 0.6, rect.origin.y - 0.5, rect.size.width, rect.size.height); 92 | break; 93 | 94 | default: 95 | break; 96 | } 97 | 98 | CGMutablePathRef path = [self arrowPathInRect:arrowRect direction:direction]; 99 | CGContextAddPath(context, path); 100 | 101 | CGContextSetRGBFillColor(context, 0, 0, 0, 1.0); 102 | CGContextFillPath(context); 103 | 104 | CGPathRelease(path); 105 | } CGContextRestoreGState(context); 106 | 107 | // Highlight 108 | CGContextSaveGState(context); { 109 | CGRect arrowRect = CGRectZero; 110 | switch (direction) { 111 | case QBPopupMenuArrowDirectionUp: 112 | arrowRect = CGRectMake(rect.origin.x, rect.origin.y + 2, rect.size.width, rect.size.height); 113 | break; 114 | 115 | case QBPopupMenuArrowDirectionLeft: 116 | arrowRect = CGRectMake(rect.origin.x + 2, rect.origin.y - 0.5 + 1, rect.size.width - 1, rect.size.height - 2); 117 | break; 118 | 119 | case QBPopupMenuArrowDirectionRight: 120 | arrowRect = CGRectMake(rect.origin.x - 1, rect.origin.y - 0.5 + 1, rect.size.width - 1, rect.size.height - 2); 121 | break; 122 | 123 | default: 124 | break; 125 | } 126 | 127 | CGMutablePathRef path = [self arrowPathInRect:arrowRect direction:direction]; 128 | CGContextAddPath(context, path); 129 | 130 | if (highlighted) { 131 | CGContextSetRGBFillColor(context, 0.384, 0.608, 0.906, 1.0); 132 | } else { 133 | CGContextSetRGBFillColor(context, 0.471, 0.471, 0.471, 1.0); 134 | } 135 | CGContextFillPath(context); 136 | 137 | CGPathRelease(path); 138 | } CGContextRestoreGState(context); 139 | 140 | // Body 141 | CGContextSaveGState(context); { 142 | switch (direction) { 143 | case QBPopupMenuArrowDirectionDown: 144 | { 145 | if (highlighted) { 146 | CGMutablePathRef path = [self arrowPathInRect:CGRectMake(rect.origin.x, rect.origin.y - 2, rect.size.width, rect.size.height) direction:direction]; 147 | CGContextAddPath(context, path); 148 | CGContextClip(context); 149 | 150 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 151 | 152 | CGFloat components[8]; 153 | components[0] = 0.027; components[1] = 0.169; components[2] = 0.733; components[3] = 1; 154 | components[4] = 0.020; components[5] = 0.114; components[6] = 0.675; components[7] = 1; 155 | 156 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 157 | 158 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y - 2); 159 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 160 | 161 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 162 | 163 | CGGradientRelease(gradient); 164 | CGColorSpaceRelease(colorSpace); 165 | CGPathRelease(path); 166 | } 167 | } 168 | break; 169 | 170 | case QBPopupMenuArrowDirectionUp: 171 | { 172 | CGMutablePathRef path = [self arrowPathInRect:CGRectMake(rect.origin.x + 1.4, rect.origin.y + 2 + 1.4, rect.size.width - 2.8, rect.size.height - 1.4) direction:direction]; 173 | CGContextAddPath(context, path); 174 | CGContextClip(context); 175 | 176 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 177 | 178 | CGFloat components[8]; 179 | if (highlighted) { 180 | components[0] = 0.290; components[1] = 0.580; components[2] = 1.000; components[3] = 1; 181 | components[4] = 0.216; components[5] = 0.471; components[6] = 0.871; components[7] = 1; 182 | } else { 183 | components[0] = 0.401; components[1] = 0.401; components[2] = 0.401; components[3] = 1; 184 | components[4] = 0.314; components[5] = 0.314; components[6] = 0.314; components[7] = 1; 185 | } 186 | 187 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 188 | 189 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + 2); 190 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 191 | 192 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 193 | 194 | CGGradientRelease(gradient); 195 | CGColorSpaceRelease(colorSpace); 196 | CGPathRelease(path); 197 | } 198 | break; 199 | 200 | case QBPopupMenuArrowDirectionLeft: 201 | { 202 | CGMutablePathRef path = [self arrowPathInRect:CGRectMake(rect.origin.x + 2, rect.origin.y - 0.5 + 2, rect.size.width - 1, rect.size.height - 2) direction:direction]; 203 | CGContextAddPath(context, path); 204 | CGContextClip(context); 205 | 206 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 207 | 208 | CGFloat components[16]; 209 | if (highlighted) { 210 | components[0] = 0.082; components[1] = 0.376; components[2] = 0.859; components[3] = 1; 211 | components[4] = 0.004; components[5] = 0.333; components[6] = 0.851; components[7] = 1; 212 | components[8] = 0.000; components[9] = 0.282; components[10] = 0.839; components[11] = 1; 213 | components[12] = 0.000; components[13] = 0.216; components[14] = 0.796; components[15] = 1; 214 | } else { 215 | components[0] = 0.216; components[1] = 0.216; components[2] = 0.216; components[3] = 1; 216 | components[4] = 0.165; components[5] = 0.165; components[6] = 0.165; components[7] = 1; 217 | components[8] = 0.102; components[9] = 0.102; components[10] = 0.102; components[11] = 1; 218 | components[12] = 0.051; components[13] = 0.051; components[14] = 0.051; components[15] = 1; 219 | } 220 | 221 | CGFloat locations[4] = { 0.0, 0.5, 0.5, 1.0 }; 222 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 4); 223 | 224 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y - 1); 225 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 226 | 227 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 228 | 229 | CGGradientRelease(gradient); 230 | CGColorSpaceRelease(colorSpace); 231 | CGPathRelease(path); 232 | } 233 | break; 234 | 235 | case QBPopupMenuArrowDirectionRight: 236 | { 237 | CGMutablePathRef path = [self arrowPathInRect:CGRectMake(rect.origin.x - 1, rect.origin.y - 0.5 + 2, rect.size.width - 1, rect.size.height - 2) direction:direction]; 238 | CGContextAddPath(context, path); 239 | CGContextClip(context); 240 | 241 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 242 | 243 | CGFloat components[16]; 244 | if (highlighted) { 245 | components[0] = 0.082; components[1] = 0.376; components[2] = 0.859; components[3] = 1; 246 | components[4] = 0.004; components[5] = 0.333; components[6] = 0.851; components[7] = 1; 247 | components[8] = 0.000; components[9] = 0.282; components[10] = 0.839; components[11] = 1; 248 | components[12] = 0.000; components[13] = 0.216; components[14] = 0.796; components[15] = 1; 249 | } else { 250 | components[0] = 0.216; components[1] = 0.216; components[2] = 0.216; components[3] = 1; 251 | components[4] = 0.165; components[5] = 0.165; components[6] = 0.165; components[7] = 1; 252 | components[8] = 0.102; components[9] = 0.102; components[10] = 0.102; components[11] = 1; 253 | components[12] = 0.051; components[13] = 0.051; components[14] = 0.051; components[15] = 1; 254 | } 255 | 256 | CGFloat locations[4] = { 0.0, 0.5, 0.5, 1.0 }; 257 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 4); 258 | 259 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y - 1); 260 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 261 | 262 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 263 | 264 | CGGradientRelease(gradient); 265 | CGColorSpaceRelease(colorSpace); 266 | CGPathRelease(path); 267 | } 268 | break; 269 | 270 | default: 271 | break; 272 | } 273 | } CGContextRestoreGState(context); 274 | } 275 | 276 | - (void)drawHeadInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted 277 | { 278 | CGContextRef context = UIGraphicsGetCurrentContext(); 279 | 280 | // Border 281 | CGContextSaveGState(context); { 282 | CGMutablePathRef path = [self headPathInRect:rect cornerRadius:cornerRadius]; 283 | CGContextAddPath(context, path); 284 | 285 | CGContextSetRGBFillColor(context, 0, 0, 0, 1.0); 286 | CGContextFillPath(context); 287 | 288 | CGPathRelease(path); 289 | } CGContextRestoreGState(context); 290 | 291 | // Highlight 292 | CGContextSaveGState(context); { 293 | CGMutablePathRef path = [self headPathInRect:CGRectMake(rect.origin.x + 1, rect.origin.y + 1, rect.size.width - 1, rect.size.height - 2) 294 | cornerRadius:cornerRadius - 1]; 295 | CGContextAddPath(context, path); 296 | 297 | if (highlighted) { 298 | CGContextSetRGBFillColor(context, 0.384, 0.608, 0.906, 1.0); 299 | } else { 300 | CGContextSetRGBFillColor(context, 0.471, 0.471, 0.471, 1.0); 301 | } 302 | CGContextFillPath(context); 303 | 304 | CGPathRelease(path); 305 | } CGContextRestoreGState(context); 306 | 307 | // Upper head 308 | CGContextSaveGState(context); { 309 | CGMutablePathRef path = [self upperHeadPathInRect:CGRectMake(rect.origin.x + 1, rect.origin.y + 2, rect.size.width - 1, rect.size.height / 2 - 2) cornerRadius:cornerRadius - 1]; 310 | CGContextAddPath(context, path); 311 | CGContextClip(context); 312 | 313 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 314 | CGFloat components[8]; 315 | if (highlighted) { 316 | components[0] = 0.216; components[1] = 0.471; components[2] = 0.871; components[3] = 1; 317 | components[4] = 0.059; components[5] = 0.353; components[6] = 0.839; components[7] = 1; 318 | } else { 319 | components[0] = 0.314; components[1] = 0.314; components[2] = 0.314; components[3] = 1; 320 | components[4] = 0.165; components[5] = 0.165; components[6] = 0.165; components[7] = 1; 321 | } 322 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 323 | 324 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + 2); 325 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2 - 2); 326 | 327 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 328 | 329 | CGGradientRelease(gradient); 330 | CGColorSpaceRelease(colorSpace); 331 | CGPathRelease(path); 332 | } CGContextRestoreGState(context); 333 | 334 | // Lower head 335 | CGContextSaveGState(context); { 336 | CGMutablePathRef path = [self lowerHeadPathInRect:CGRectMake(rect.origin.x + 1, rect.origin.y + rect.size.height / 2, rect.size.width - 1, rect.size.height / 2 - 1) cornerRadius:cornerRadius - 1]; 337 | CGContextAddPath(context, path); 338 | CGContextClip(context); 339 | 340 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 341 | CGFloat components[8]; 342 | if (highlighted) { 343 | components[0] = 0.047; components[1] = 0.306; components[2] = 0.827; components[3] = 1; 344 | components[4] = 0.027; components[5] = 0.176; components[6] = 0.737; components[7] = 1; 345 | } else { 346 | components[0] = 0.102; components[1] = 0.102; components[2] = 0.102; components[3] = 1; 347 | components[4] = 0; components[5] = 0; components[6] = 0; components[7] = 1; 348 | } 349 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 350 | 351 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2); 352 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height - 1); 353 | 354 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 355 | 356 | CGGradientRelease(gradient); 357 | CGColorSpaceRelease(colorSpace); 358 | CGPathRelease(path); 359 | } CGContextRestoreGState(context); 360 | } 361 | 362 | - (void)drawTailInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted 363 | { 364 | CGContextRef context = UIGraphicsGetCurrentContext(); 365 | 366 | // Border 367 | CGContextSaveGState(context); { 368 | CGMutablePathRef path = [self tailPathInRect:rect cornerRadius:cornerRadius]; 369 | CGContextAddPath(context, path); 370 | 371 | CGContextSetRGBFillColor(context, 0, 0, 0, 1.0); 372 | CGContextFillPath(context); 373 | 374 | CGPathRelease(path); 375 | } CGContextRestoreGState(context); 376 | 377 | // Highlight 378 | CGContextSaveGState(context); { 379 | CGMutablePathRef path = [self tailPathInRect:CGRectMake(rect.origin.x, rect.origin.y + 1, rect.size.width - 1, rect.size.height - 2) cornerRadius:cornerRadius - 1]; 380 | CGContextAddPath(context, path); 381 | 382 | if (highlighted) { 383 | CGContextSetRGBFillColor(context, 0.384, 0.608, 0.906, 1.0); 384 | } else { 385 | CGContextSetRGBFillColor(context, 0.471, 0.471, 0.471, 1.0); 386 | } 387 | CGContextFillPath(context); 388 | 389 | CGPathRelease(path); 390 | } CGContextRestoreGState(context); 391 | 392 | // Upper body 393 | CGContextSaveGState(context); { 394 | CGMutablePathRef path = [self upperTailPathInRect:CGRectMake(rect.origin.x, rect.origin.y + 2, rect.size.width - 1, rect.size.height / 2 - 2) cornerRadius:cornerRadius - 1]; 395 | CGContextAddPath(context, path); 396 | CGContextClip(context); 397 | 398 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 399 | CGFloat components[8]; 400 | if (highlighted) { 401 | components[0] = 0.216; components[1] = 0.471; components[2] = 0.871; components[3] = 1; 402 | components[4] = 0.059; components[5] = 0.353; components[6] = 0.839; components[7] = 1; 403 | } else { 404 | components[0] = 0.314; components[1] = 0.314; components[2] = 0.314; components[3] = 1; 405 | components[4] = 0.165; components[5] = 0.165; components[6] = 0.165; components[7] = 1; 406 | } 407 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 408 | 409 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + 2); 410 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2 - 2); 411 | 412 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 413 | 414 | CGGradientRelease(gradient); 415 | CGColorSpaceRelease(colorSpace); 416 | CGPathRelease(path); 417 | } CGContextRestoreGState(context); 418 | 419 | // Lower body 420 | CGContextSaveGState(context); { 421 | CGMutablePathRef path = [self lowerTailPathInRect:CGRectMake(rect.origin.x, rect.origin.y + rect.size.height / 2, rect.size.width - 1, rect.size.height / 2 - 1) cornerRadius:cornerRadius - 1]; 422 | CGContextAddPath(context, path); 423 | CGContextClip(context); 424 | 425 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 426 | CGFloat components[8]; 427 | if (highlighted) { 428 | components[0] = 0.047; components[1] = 0.306; components[2] = 0.827; components[3] = 1; 429 | components[4] = 0.027; components[5] = 0.176; components[6] = 0.737; components[7] = 1; 430 | } else { 431 | components[0] = 0.102; components[1] = 0.102; components[2] = 0.102; components[3] = 1; 432 | components[4] = 0; components[5] = 0; components[6] = 0; components[7] = 1; 433 | } 434 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 435 | 436 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2); 437 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height - 1); 438 | 439 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 440 | 441 | CGGradientRelease(gradient); 442 | CGColorSpaceRelease(colorSpace); 443 | CGPathRelease(path); 444 | } CGContextRestoreGState(context); 445 | } 446 | 447 | - (void)drawBodyInRect:(CGRect)rect firstItem:(BOOL)firstItem lastItem:(BOOL)lastItem highlighted:(BOOL)highlighted 448 | { 449 | CGContextRef context = UIGraphicsGetCurrentContext(); 450 | 451 | // Border 452 | CGContextSaveGState(context); { 453 | CGMutablePathRef path = [self bodyPathInRect:rect]; 454 | CGContextAddPath(context, path); 455 | 456 | CGContextSetRGBFillColor(context, 0, 0, 0, 1.0); 457 | CGContextFillPath(context); 458 | 459 | CGPathRelease(path); 460 | } CGContextRestoreGState(context); 461 | 462 | // Highlight 463 | CGContextSaveGState(context); { 464 | CGMutablePathRef path = [self bodyPathInRect:CGRectMake(rect.origin.x, rect.origin.y + 1, rect.size.width, rect.size.height - 2)]; 465 | CGContextAddPath(context, path); 466 | 467 | if (highlighted) { 468 | CGContextSetRGBFillColor(context, 0.384, 0.608, 0.906, 1.0); 469 | } else { 470 | CGContextSetRGBFillColor(context, 0.471, 0.471, 0.471, 1.0); 471 | } 472 | CGContextFillPath(context); 473 | 474 | CGPathRelease(path); 475 | } CGContextRestoreGState(context); 476 | 477 | // Upper body 478 | CGContextSaveGState(context); { 479 | CGMutablePathRef path = [self bodyPathInRect:CGRectMake(rect.origin.x, rect.origin.y + 2, rect.size.width, rect.size.height / 2 - 2)]; 480 | CGContextAddPath(context, path); 481 | CGContextClip(context); 482 | 483 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 484 | CGFloat components[8]; 485 | if (highlighted) { 486 | components[0] = 0.216; components[1] = 0.471; components[2] = 0.871; components[3] = 1; 487 | components[4] = 0.059; components[5] = 0.353; components[6] = 0.839; components[7] = 1; 488 | } else { 489 | components[0] = 0.314; components[1] = 0.314; components[2] = 0.314; components[3] = 1; 490 | components[4] = 0.165; components[5] = 0.165; components[6] = 0.165; components[7] = 1; 491 | } 492 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 493 | 494 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + 2); 495 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2 - 2); 496 | 497 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 498 | 499 | CGGradientRelease(gradient); 500 | CGColorSpaceRelease(colorSpace); 501 | CGPathRelease(path); 502 | } CGContextRestoreGState(context); 503 | 504 | // Lower body 505 | CGContextSaveGState(context); { 506 | CGMutablePathRef path = [self bodyPathInRect:CGRectMake(rect.origin.x, rect.origin.y + rect.size.height / 2, rect.size.width, rect.size.height / 2 - 1)]; 507 | CGContextAddPath(context, path); 508 | CGContextClip(context); 509 | 510 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 511 | CGFloat components[8]; 512 | if (highlighted) { 513 | components[0] = 0.047; components[1] = 0.306; components[2] = 0.827; components[3] = 1; 514 | components[4] = 0.027; components[5] = 0.176; components[6] = 0.737; components[7] = 1; 515 | } else { 516 | components[0] = 0.102; components[1] = 0.102; components[2] = 0.102; components[3] = 1; 517 | components[4] = 0; components[5] = 0; components[6] = 0; components[7] = 1; 518 | } 519 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 2); 520 | 521 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height / 2); 522 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height - 1); 523 | 524 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 525 | 526 | CGGradientRelease(gradient); 527 | CGColorSpaceRelease(colorSpace); 528 | CGPathRelease(path); 529 | } CGContextRestoreGState(context); 530 | 531 | // Draw separator 532 | if (!firstItem) { 533 | [self drawLeftSeparatorInRect:CGRectMake(rect.origin.x, rect.origin.y + 2, 1, rect.size.height - 3) highlighted:highlighted]; 534 | } 535 | if (!lastItem) { 536 | [self drawRightSeparatorInRect:CGRectMake(rect.origin.x + rect.size.width - 1, rect.origin.y + 2, 1, rect.size.height - 3) highlighted:highlighted]; 537 | } 538 | } 539 | 540 | - (void)drawLeftSeparatorInRect:(CGRect)rect highlighted:(BOOL)highlighted 541 | { 542 | CGContextRef context = UIGraphicsGetCurrentContext(); 543 | 544 | // Left separator 545 | CGContextSaveGState(context); { 546 | CGContextAddRect(context, rect); 547 | CGContextClip(context); 548 | 549 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 550 | CGFloat components[16]; 551 | if (highlighted) { 552 | components[0] = 0.22; components[1] = 0.47; components[2] = 0.87; components[3] = 1; 553 | components[4] = 0.12; components[5] = 0.50; components[6] = 0.89; components[7] = 1; 554 | components[8] = 0.09; components[9] = 0.47; components[10] = 0.88; components[11] = 1; 555 | components[12] = 0.03; components[13] = 0.18; components[14] = 0.74; components[15] = 1; 556 | } else { 557 | components[0] = 0.31; components[1] = 0.31; components[2] = 0.31; components[3] = 1; 558 | components[4] = 0.31; components[5] = 0.31; components[6] = 0.31; components[7] = 1; 559 | components[8] = 0.24; components[9] = 0.24; components[10] = 0.24; components[11] = 1; 560 | components[12] = 0.05; components[13] = 0.05; components[14] = 0.05; components[15] = 1; 561 | } 562 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 4); 563 | 564 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y); 565 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 566 | 567 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 568 | 569 | CGGradientRelease(gradient); 570 | CGColorSpaceRelease(colorSpace); 571 | } CGContextRestoreGState(context); 572 | } 573 | 574 | - (void)drawRightSeparatorInRect:(CGRect)rect highlighted:(BOOL)highlighted 575 | { 576 | CGContextRef context = UIGraphicsGetCurrentContext(); 577 | 578 | // Right separator 579 | CGContextSaveGState(context); { 580 | CGContextAddRect(context, rect); 581 | CGContextClip(context); 582 | 583 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 584 | CGFloat components[16]; 585 | if (highlighted) { 586 | components[0] = 0.22; components[1] = 0.47; components[2] = 0.87; components[3] = 1; 587 | components[4] = 0.03; components[5] = 0.18; components[6] = 0.72; components[7] = 1; 588 | components[8] = 0.02; components[9] = 0.15; components[10] = 0.73; components[11] = 1; 589 | components[12] = 0.03; components[13] = 0.17; components[14] = 0.72; components[15] = 1; 590 | } else { 591 | components[0] = 0.31; components[1] = 0.31; components[2] = 0.31; components[3] = 1; 592 | components[4] = 0.06; components[5] = 0.06; components[6] = 0.06; components[7] = 1; 593 | components[8] = 0.04; components[9] = 0.04; components[10] = 0.04; components[11] = 1; 594 | components[12] = 0; components[13] = 0; components[14] = 0; components[15] = 1; 595 | } 596 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, NULL, 4); 597 | 598 | CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y); 599 | CGPoint endPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height); 600 | 601 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsAfterEndLocation); 602 | 603 | CGGradientRelease(gradient); 604 | CGColorSpaceRelease(colorSpace); 605 | } CGContextRestoreGState(context); 606 | } 607 | 608 | @end 609 | -------------------------------------------------------------------------------- /QBPopupMenu/QBPopupMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBPopupMenu.m 3 | // QBPopupMenu 4 | // 5 | // Created by Tanaka Katsuma on 2013/11/22. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBPopupMenu.h" 10 | 11 | #import "QBPopupMenuOverlayView.h" 12 | #import "QBPopupMenuItemView.h" 13 | #import "QBPopupMenuPagenatorView.h" 14 | 15 | static const NSTimeInterval kQBPopupMenuAnimationDuration = 0.2; 16 | 17 | @interface QBPopupMenu () 18 | 19 | @property (nonatomic, assign, getter = isVisible, readwrite) BOOL visible; 20 | @property (nonatomic, strong) QBPopupMenuOverlayView *overlayView; 21 | 22 | @property (nonatomic, weak) UIView *view; 23 | @property (nonatomic, assign) CGRect targetRect; 24 | 25 | @property (nonatomic, assign) NSUInteger page; 26 | @property (nonatomic, assign) QBPopupMenuArrowDirection actualArrorDirection; 27 | @property (nonatomic, assign) CGPoint arrowPoint; 28 | 29 | @property (nonatomic, strong) NSMutableArray *itemViews; 30 | @property (nonatomic, strong) NSMutableArray *groupedItemViews; 31 | @property (nonatomic, strong) NSMutableArray *visibleItemViews; 32 | 33 | @end 34 | 35 | @implementation QBPopupMenu 36 | 37 | + (Class)itemViewClass 38 | { 39 | return [QBPopupMenuItemView class]; 40 | } 41 | 42 | + (Class)pagenatorViewClass 43 | { 44 | return [QBPopupMenuPagenatorView class]; 45 | } 46 | 47 | + (instancetype)popupMenuWithItems:(NSArray *)items 48 | { 49 | return [[self alloc] initWithItems:items]; 50 | } 51 | 52 | - (instancetype)initWithItems:(NSArray *)items 53 | { 54 | self = [super initWithFrame:CGRectZero]; 55 | 56 | if (self) { 57 | // View settings 58 | self.opaque = NO; 59 | self.backgroundColor = [UIColor clearColor]; 60 | self.clipsToBounds = YES; 61 | 62 | // Property settings 63 | self.items = items; 64 | self.height = 36; 65 | self.cornerRadius = 8; 66 | self.arrowSize = 9; 67 | self.arrowDirection = QBPopupMenuArrowDirectionDefault; 68 | self.popupMenuInsets = UIEdgeInsetsMake(10, 10, 10, 10); 69 | self.margin = 2; 70 | 71 | self.color = [[UIColor blackColor] colorWithAlphaComponent:0.8]; 72 | self.highlightedColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.8]; 73 | } 74 | 75 | return self; 76 | } 77 | 78 | 79 | #pragma mark - Accessors 80 | 81 | - (void)setItems:(NSArray *)items 82 | { 83 | _items = items; 84 | 85 | // Create item views 86 | [self createItemViews]; 87 | } 88 | 89 | - (void)setHeight:(CGFloat)height 90 | { 91 | _height = height; 92 | 93 | // Update view 94 | CGRect frame = self.frame; 95 | frame.size.height = height; 96 | self.frame = frame; 97 | } 98 | 99 | 100 | #pragma mark - Managing Popup Menu 101 | 102 | - (void)showInView:(UIView *)view targetRect:(CGRect)targetRect animated:(BOOL)animated 103 | { 104 | if ([self isVisible]) { 105 | return; 106 | } 107 | 108 | self.view = view; 109 | self.targetRect = targetRect; 110 | 111 | // Decide arrow direction 112 | QBPopupMenuArrowDirection arrowDirection = self.arrowDirection; 113 | 114 | if (arrowDirection == QBPopupMenuArrowDirectionDefault) { 115 | if ((targetRect.origin.y - (self.height + self.arrowSize)) >= self.popupMenuInsets.top) { 116 | arrowDirection = QBPopupMenuArrowDirectionDown; 117 | } 118 | else if ((targetRect.origin.y + targetRect.size.height + (self.height + self.arrowSize)) < (view.bounds.size.height - self.popupMenuInsets.bottom)) { 119 | arrowDirection = QBPopupMenuArrowDirectionUp; 120 | } 121 | else { 122 | CGFloat left = targetRect.origin.x - self.popupMenuInsets.left; 123 | CGFloat right = view.bounds.size.width - (targetRect.origin.x + targetRect.size.width + self.popupMenuInsets.right); 124 | 125 | arrowDirection = (left > right) ? QBPopupMenuArrowDirectionLeft : QBPopupMenuArrowDirectionRight; 126 | } 127 | } 128 | 129 | self.actualArrorDirection = arrowDirection; 130 | 131 | // Calculate width 132 | CGFloat maximumWidth = 0; 133 | CGFloat minimumWidth = 40; 134 | 135 | switch (arrowDirection) { 136 | case QBPopupMenuArrowDirectionDown: 137 | case QBPopupMenuArrowDirectionUp: 138 | maximumWidth = view.bounds.size.width - (self.popupMenuInsets.left + self.popupMenuInsets.right); 139 | if (maximumWidth < minimumWidth) maximumWidth = minimumWidth; 140 | break; 141 | 142 | case QBPopupMenuArrowDirectionLeft: 143 | maximumWidth = targetRect.origin.x - self.popupMenuInsets.left; 144 | break; 145 | 146 | case QBPopupMenuArrowDirectionRight: 147 | maximumWidth = view.bounds.size.width - (targetRect.origin.x + targetRect.size.width + self.popupMenuInsets.right); 148 | break; 149 | 150 | default: 151 | break; 152 | } 153 | 154 | // Layout item views 155 | [self groupItemViewsWithMaximumWidth:maximumWidth]; 156 | 157 | // Show page 158 | [self showPage:0]; 159 | 160 | // Create overlay view 161 | self.overlayView = ({ 162 | QBPopupMenuOverlayView *overlayView = [[QBPopupMenuOverlayView alloc] initWithFrame:view.bounds]; 163 | overlayView.popupMenu = self; 164 | 165 | overlayView; 166 | }); 167 | 168 | // Delegate 169 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuWillAppear:)]) { 170 | [self.delegate popupMenuWillAppear:self]; 171 | } 172 | 173 | // Show 174 | [view addSubview:self.overlayView]; 175 | 176 | if (animated) { 177 | self.alpha = 0; 178 | [self.overlayView addSubview:self]; 179 | 180 | [UIView animateWithDuration:kQBPopupMenuAnimationDuration animations:^(void) { 181 | self.alpha = 1.0; 182 | } completion:^(BOOL finished) { 183 | self.visible = YES; 184 | 185 | // Delegate 186 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidAppear:)]) { 187 | [self.delegate popupMenuDidAppear:self]; 188 | } 189 | }]; 190 | } else { 191 | [self.overlayView addSubview:self]; 192 | 193 | self.visible = YES; 194 | 195 | // Delegate 196 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidAppear:)]) { 197 | [self.delegate popupMenuDidAppear:self]; 198 | } 199 | } 200 | } 201 | 202 | - (void)dismissAnimated:(BOOL)animated 203 | { 204 | if (![self isVisible]) { 205 | return; 206 | } 207 | 208 | // Delegate 209 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuWillDisappear:)]) { 210 | [self.delegate popupMenuWillDisappear:self]; 211 | } 212 | 213 | if (animated) { 214 | [UIView animateWithDuration:kQBPopupMenuAnimationDuration animations:^{ 215 | self.alpha = 0; 216 | } completion:^(BOOL finished) { 217 | [self removeFromSuperview]; 218 | [self.overlayView removeFromSuperview]; 219 | 220 | self.visible = NO; 221 | 222 | // Delegate 223 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidDisappear:)]) { 224 | [self.delegate popupMenuDidDisappear:self]; 225 | } 226 | }]; 227 | } else { 228 | [self removeFromSuperview]; 229 | [self.overlayView removeFromSuperview]; 230 | 231 | self.visible = NO; 232 | 233 | // Delegate 234 | if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidDisappear:)]) { 235 | [self.delegate popupMenuDidDisappear:self]; 236 | } 237 | } 238 | } 239 | 240 | - (void)updateWithTargetRect:(CGRect)targetRect 241 | { 242 | self.targetRect = targetRect; 243 | 244 | [self updatePopupMenuFrameAndArrowPosition]; 245 | [self updatePopupMenuImage]; 246 | } 247 | 248 | - (void)showPreviousPage 249 | { 250 | [self showPage:(self.page - 1)]; 251 | } 252 | 253 | - (void)showNextPage 254 | { 255 | [self showPage:(self.page + 1)]; 256 | } 257 | 258 | - (void)showPage:(NSUInteger)page 259 | { 260 | self.page = page; 261 | 262 | [self updateVisibleItemViewsWithPage:page]; 263 | [self layoutVisibleItemViews]; 264 | [self updatePopupMenuFrameAndArrowPosition]; 265 | [self updatePopupMenuImage]; 266 | } 267 | 268 | 269 | #pragma mark - Updating Content 270 | 271 | - (void)createItemViews 272 | { 273 | NSMutableArray *itemViews = [NSMutableArray array]; 274 | 275 | for (QBPopupMenuItem *item in self.items) { 276 | QBPopupMenuItemView *itemView = [[[self class] itemViewClass] itemViewWithItem:item]; 277 | itemView.popupMenu = self; 278 | 279 | [itemViews addObject:itemView]; 280 | } 281 | 282 | self.itemViews = itemViews; 283 | } 284 | 285 | - (void)resetItemViewState:(QBPopupMenuItemView *)itemView 286 | { 287 | // NOTE: Reset properties related to the size of the button before colling sizeThatFits: of item view, 288 | // or the size of the view will change from the second time. 289 | itemView.button.contentEdgeInsets = UIEdgeInsetsZero; 290 | itemView.image = nil; 291 | itemView.highlightedImage = nil; 292 | } 293 | 294 | - (void)groupItemViewsWithMaximumWidth:(CGFloat)maximumWidth 295 | { 296 | NSMutableArray *groupedItemViews = [NSMutableArray array]; 297 | 298 | CGFloat pagenatorWidth = [QBPopupMenuPagenatorView pagenatorWidth]; 299 | 300 | // Create new array 301 | NSMutableArray *itemViews = [NSMutableArray array]; 302 | CGFloat width = 0; 303 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft || self.actualArrorDirection == QBPopupMenuArrowDirectionRight) { 304 | width += self.arrowSize; 305 | } 306 | 307 | for (QBPopupMenuItemView *itemView in self.itemViews) { 308 | // Clear state 309 | [self resetItemViewState:itemView]; 310 | 311 | CGSize itemViewSize = [itemView sizeThatFits:CGSizeZero]; 312 | 313 | if (itemViews.count > 0 && width + itemViewSize.width + pagenatorWidth > maximumWidth) { 314 | [groupedItemViews addObject:[itemViews copy]]; 315 | 316 | // Create new array 317 | itemViews = [NSMutableArray array]; 318 | width = pagenatorWidth; 319 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft || self.actualArrorDirection == QBPopupMenuArrowDirectionRight) { 320 | width += self.arrowSize; 321 | } 322 | } 323 | 324 | [itemViews addObject:itemView]; 325 | width += itemViewSize.width; 326 | } 327 | 328 | if (itemViews.count > 0) { 329 | [groupedItemViews addObject:[itemViews copy]]; 330 | } 331 | 332 | self.groupedItemViews = groupedItemViews; 333 | } 334 | 335 | - (void)updateVisibleItemViewsWithPage:(NSUInteger)page 336 | { 337 | // Remove all visible item views 338 | for (UIView *view in self.visibleItemViews) { 339 | [view removeFromSuperview]; 340 | } 341 | 342 | // Add item views 343 | NSMutableArray *visibleItemViews = [NSMutableArray array]; 344 | NSUInteger numberOfPages = self.groupedItemViews.count; 345 | 346 | if (numberOfPages > 1 && page != 0) { 347 | QBPopupMenuPagenatorView *leftPagenatorView = [[[self class] pagenatorViewClass] leftPagenatorViewWithTarget:self action:@selector(showPreviousPage)]; 348 | 349 | [self addSubview:leftPagenatorView]; 350 | [visibleItemViews addObject:leftPagenatorView]; 351 | } 352 | 353 | NSArray *itemViews = [self.groupedItemViews objectAtIndex:page]; 354 | 355 | for (QBPopupMenuItemView *itemView in itemViews) { 356 | [self addSubview:itemView]; 357 | [visibleItemViews addObject:itemView]; 358 | } 359 | 360 | if (numberOfPages > 1 && page != numberOfPages - 1) { 361 | QBPopupMenuPagenatorView *rightPagenatorView = [[[self class] pagenatorViewClass] rightPagenatorViewWithTarget:self action:@selector(showNextPage)]; 362 | 363 | [self addSubview:rightPagenatorView]; 364 | [visibleItemViews addObject:rightPagenatorView]; 365 | } 366 | 367 | self.visibleItemViews = visibleItemViews; 368 | } 369 | 370 | - (void)layoutVisibleItemViews 371 | { 372 | CGFloat height = self.height; 373 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionDown || self.actualArrorDirection == QBPopupMenuArrowDirectionUp) { 374 | height += self.arrowSize; 375 | } 376 | 377 | CGFloat offset = 0; 378 | for (NSInteger i = 0; i < self.visibleItemViews.count; i++) { 379 | QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i]; 380 | 381 | // Clear state 382 | [self resetItemViewState:itemView]; 383 | 384 | // Set item view insets 385 | if (i == 0 && self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) { 386 | itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, self.arrowSize, 0, 0); 387 | } 388 | else if (i == self.visibleItemViews.count - 1 && self.actualArrorDirection == QBPopupMenuArrowDirectionRight) { 389 | itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, self.arrowSize); 390 | } 391 | else if (self.actualArrorDirection == QBPopupMenuArrowDirectionDown) { 392 | itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, self.arrowSize, 0); 393 | } 394 | else if (self.actualArrorDirection == QBPopupMenuArrowDirectionUp) { 395 | itemView.button.contentEdgeInsets = UIEdgeInsetsMake(self.arrowSize, 0, 0, 0); 396 | } 397 | 398 | // Set item view frame 399 | CGSize size = [itemView sizeThatFits:CGSizeZero]; 400 | CGFloat width = size.width; 401 | 402 | if ((i == 0 && self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) || 403 | (i == self.visibleItemViews.count - 1 && self.actualArrorDirection == QBPopupMenuArrowDirectionRight)) { 404 | width += self.arrowSize; 405 | } 406 | 407 | itemView.frame = CGRectMake(offset, 0, width, height); 408 | 409 | offset += width; 410 | } 411 | } 412 | 413 | - (void)updatePopupMenuFrameAndArrowPosition 414 | { 415 | // Calculate popup frame 416 | CGRect popupMenuFrame = CGRectZero; 417 | CGPoint arrowPoint = CGPointZero; 418 | 419 | UIView *itemView = [self.visibleItemViews lastObject]; 420 | CGFloat width = itemView.frame.origin.x + itemView.frame.size.width; 421 | CGFloat height = itemView.frame.origin.y + itemView.frame.size.height; 422 | 423 | switch (self.actualArrorDirection) { 424 | case QBPopupMenuArrowDirectionDown: 425 | { 426 | popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width - width) / 2.0, 427 | self.targetRect.origin.y - (height + self.margin), 428 | width, 429 | height); 430 | 431 | if (popupMenuFrame.origin.x + popupMenuFrame.size.width > self.view.frame.size.width - self.popupMenuInsets.right) { 432 | popupMenuFrame.origin.x = self.view.frame.size.width - self.popupMenuInsets.right - popupMenuFrame.size.width; 433 | } 434 | if (popupMenuFrame.origin.x < self.popupMenuInsets.left) { 435 | popupMenuFrame.origin.x = self.popupMenuInsets.left; 436 | } 437 | 438 | CGFloat centerOfTargetRect = self.targetRect.origin.x + self.targetRect.size.width / 2.0; 439 | arrowPoint = CGPointMake(MAX(self.cornerRadius, MIN(popupMenuFrame.size.width - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.x)), 440 | popupMenuFrame.size.height); 441 | } 442 | break; 443 | 444 | case QBPopupMenuArrowDirectionUp: 445 | { 446 | popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width - width) / 2.0, 447 | self.targetRect.origin.y + (self.targetRect.size.height + self.margin), 448 | width, 449 | height); 450 | 451 | if (popupMenuFrame.origin.x + popupMenuFrame.size.width > self.view.frame.size.width - self.popupMenuInsets.right) { 452 | popupMenuFrame.origin.x = self.view.frame.size.width - self.popupMenuInsets.right - popupMenuFrame.size.width; 453 | } 454 | if (popupMenuFrame.origin.x < self.popupMenuInsets.left) { 455 | popupMenuFrame.origin.x = self.popupMenuInsets.left; 456 | } 457 | 458 | CGFloat centerOfTargetRect = self.targetRect.origin.x + self.targetRect.size.width / 2.0; 459 | arrowPoint = CGPointMake(MAX(self.cornerRadius, MIN(popupMenuFrame.size.width - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.x)), 460 | 0); 461 | } 462 | break; 463 | 464 | case QBPopupMenuArrowDirectionLeft: 465 | { 466 | popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width + self.margin), 467 | self.targetRect.origin.y + (self.targetRect.size.height - height) / 2.0, 468 | width, 469 | height); 470 | 471 | if (popupMenuFrame.origin.y + popupMenuFrame.size.height > self.view.frame.size.height - self.popupMenuInsets.bottom) { 472 | popupMenuFrame.origin.y = self.view.frame.size.height - self.popupMenuInsets.bottom - popupMenuFrame.size.height; 473 | } 474 | if (popupMenuFrame.origin.y < self.popupMenuInsets.top) { 475 | popupMenuFrame.origin.y = self.popupMenuInsets.top; 476 | } 477 | 478 | CGFloat centerOfTargetRect = self.targetRect.origin.y + self.targetRect.size.height / 2.0; 479 | arrowPoint = CGPointMake(0, 480 | MAX(self.cornerRadius, MIN(popupMenuFrame.size.height - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.y))); 481 | } 482 | break; 483 | 484 | case QBPopupMenuArrowDirectionRight: 485 | { 486 | popupMenuFrame = CGRectMake(self.targetRect.origin.x - (width + self.margin), 487 | self.targetRect.origin.y + (self.targetRect.size.height - height) / 2.0, 488 | width, 489 | height); 490 | 491 | if (popupMenuFrame.origin.y + popupMenuFrame.size.height > self.view.frame.size.height - self.popupMenuInsets.bottom) { 492 | popupMenuFrame.origin.y = self.view.frame.size.height - self.popupMenuInsets.bottom - popupMenuFrame.size.height; 493 | } 494 | if (popupMenuFrame.origin.y < self.popupMenuInsets.top) { 495 | popupMenuFrame.origin.y = self.popupMenuInsets.top; 496 | } 497 | 498 | CGFloat centerOfTargetRect = self.targetRect.origin.y + self.targetRect.size.height / 2.0; 499 | arrowPoint = CGPointMake(popupMenuFrame.size.width, 500 | MAX(self.cornerRadius, MIN(popupMenuFrame.size.height - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.y))); 501 | } 502 | break; 503 | 504 | default: 505 | break; 506 | } 507 | 508 | // Round coordinates 509 | popupMenuFrame = CGRectMake(round(popupMenuFrame.origin.x), 510 | round(popupMenuFrame.origin.y), 511 | round(popupMenuFrame.size.width), 512 | round(popupMenuFrame.size.height)); 513 | arrowPoint = CGPointMake(round(arrowPoint.x), 514 | round(arrowPoint.y)); 515 | 516 | self.frame = popupMenuFrame; 517 | self.arrowPoint = arrowPoint; 518 | } 519 | 520 | - (void)updatePopupMenuImage 521 | { 522 | UIImage *popupMenuImage = [self popupMenuImageWithHighlighted:NO]; 523 | UIImage *popupMenuHighlightedImage = [self popupMenuImageWithHighlighted:YES]; 524 | 525 | for (NSInteger i = 0; i < self.visibleItemViews.count; i++) { 526 | QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i]; 527 | 528 | UIImage *image = [self cropImageFromImage:popupMenuImage inRect:itemView.frame]; 529 | UIImage *highlightedImage = [self cropImageFromImage:popupMenuHighlightedImage inRect:itemView.frame]; 530 | 531 | itemView.image = image; 532 | itemView.highlightedImage = highlightedImage; 533 | } 534 | } 535 | 536 | 537 | #pragma mark - Creating Popup Menu Image 538 | 539 | - (UIImage *)cropImageFromImage:(UIImage *)image inRect:(CGRect)rect 540 | { 541 | CGFloat scale = [[UIScreen mainScreen] scale]; 542 | CGRect scaledRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale); 543 | 544 | CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, scaledRect); 545 | UIImage *croppedImage = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; 546 | CGImageRelease(imageRef); 547 | 548 | return croppedImage; 549 | } 550 | 551 | - (UIImage *)popupMenuImageWithHighlighted:(BOOL)highlighted 552 | { 553 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); 554 | 555 | // Draw body 556 | CGFloat y = (self.actualArrorDirection == QBPopupMenuArrowDirectionUp) ? self.arrowSize : 0; 557 | CGFloat height = self.height; 558 | 559 | for (NSInteger i = 0; i < self.visibleItemViews.count; i++) { 560 | QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i]; 561 | CGRect frame = itemView.frame; 562 | 563 | if (i == 0) { 564 | if (self.visibleItemViews.count == 1) { 565 | CGRect headRect; 566 | CGRect bodyRect; 567 | CGRect tailRect; 568 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) { 569 | headRect = CGRectMake(self.arrowSize, y, self.cornerRadius, height); 570 | bodyRect = CGRectMake(self.arrowSize + self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius * 2.0), height); 571 | tailRect = CGRectMake(frame.size.width - self.cornerRadius, y, self.cornerRadius, height); 572 | } 573 | else if (self.actualArrorDirection == QBPopupMenuArrowDirectionRight) { 574 | headRect = CGRectMake(0, y, self.cornerRadius, height); 575 | bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius * 2.0), height); 576 | tailRect = CGRectMake(frame.size.width - (self.arrowSize + self.cornerRadius), y, self.cornerRadius, height); 577 | } 578 | else { 579 | headRect = CGRectMake(0, y, self.cornerRadius, height); 580 | bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - self.cornerRadius * 2.0, height); 581 | tailRect = CGRectMake(frame.size.width - self.cornerRadius, y, self.cornerRadius, height); 582 | } 583 | 584 | // Draw head 585 | [self drawHeadInRect:headRect cornerRadius:self.cornerRadius highlighted:highlighted]; 586 | 587 | // Draw body 588 | [self drawBodyInRect:bodyRect firstItem:YES lastItem:YES highlighted:highlighted]; 589 | 590 | // Draw tail 591 | [self drawTailInRect:tailRect cornerRadius:self.cornerRadius highlighted:highlighted]; 592 | } else { 593 | CGRect headRect; 594 | CGRect bodyRect; 595 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) { 596 | headRect = CGRectMake(self.arrowSize, y, self.cornerRadius, height); 597 | bodyRect = CGRectMake(self.arrowSize + self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius), height); 598 | } else { 599 | headRect = CGRectMake(0, y, self.cornerRadius, height); 600 | bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - self.cornerRadius, height); 601 | } 602 | 603 | // Draw head 604 | [self drawHeadInRect:headRect cornerRadius:self.cornerRadius highlighted:highlighted]; 605 | 606 | // Draw body 607 | [self drawBodyInRect:bodyRect firstItem:YES lastItem:NO highlighted:highlighted]; 608 | } 609 | } 610 | else if (i == self.visibleItemViews.count - 1) { 611 | CGRect bodyRect; 612 | CGRect tailRect; 613 | if (self.actualArrorDirection == QBPopupMenuArrowDirectionRight) { 614 | bodyRect = CGRectMake(frame.origin.x, y, frame.size.width - (self.cornerRadius + self.arrowSize), height); 615 | tailRect = CGRectMake(frame.origin.x + frame.size.width - (self.cornerRadius + self.arrowSize), y, self.cornerRadius, height); 616 | } else { 617 | bodyRect = CGRectMake(frame.origin.x, y, frame.size.width - self.cornerRadius, height); 618 | tailRect = CGRectMake(frame.origin.x + frame.size.width - self.cornerRadius, y, self.cornerRadius, height); 619 | } 620 | 621 | // Draw body 622 | [self drawBodyInRect:bodyRect firstItem:NO lastItem:YES highlighted:highlighted]; 623 | 624 | // Draw tail 625 | [self drawTailInRect:tailRect cornerRadius:self.cornerRadius highlighted:highlighted]; 626 | } 627 | else { 628 | // Draw body 629 | CGRect bodyRect = CGRectMake(frame.origin.x, y, frame.size.width, height); 630 | [self drawBodyInRect:bodyRect firstItem:NO lastItem:NO highlighted:highlighted]; 631 | } 632 | } 633 | 634 | // Draw arrow 635 | [self drawArrowAtPoint:self.arrowPoint arrowSize:self.arrowSize arrowDirection:self.actualArrorDirection highlighted:highlighted]; 636 | 637 | // Create image from buffer 638 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 639 | 640 | UIGraphicsEndImageContext(); 641 | 642 | return image; 643 | } 644 | 645 | 646 | #pragma mark - Creating Paths 647 | 648 | - (CGMutablePathRef)arrowPathInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction 649 | { 650 | // Create arrow path 651 | CGMutablePathRef path = CGPathCreateMutable(); 652 | 653 | switch (direction) { 654 | case QBPopupMenuArrowDirectionDown: 655 | { 656 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 657 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 658 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width / 2.0, rect.origin.y + rect.size.height); 659 | CGPathCloseSubpath(path); 660 | } 661 | break; 662 | 663 | case QBPopupMenuArrowDirectionUp: 664 | { 665 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 666 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 667 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width / 2.0, rect.origin.y); 668 | CGPathCloseSubpath(path); 669 | } 670 | break; 671 | 672 | case QBPopupMenuArrowDirectionLeft: 673 | { 674 | CGPathMoveToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 675 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 676 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height / 2.0); 677 | CGPathCloseSubpath(path); 678 | } 679 | break; 680 | 681 | case QBPopupMenuArrowDirectionRight: 682 | { 683 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 684 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 685 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height / 2.0); 686 | CGPathCloseSubpath(path); 687 | } 688 | break; 689 | 690 | default: 691 | break; 692 | } 693 | 694 | return path; 695 | } 696 | 697 | - (CGMutablePathRef)headPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 698 | { 699 | CGMutablePathRef path = CGPathCreateMutable(); 700 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + cornerRadius); 701 | CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y, rect.origin.x + cornerRadius, rect.origin.y, cornerRadius); 702 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 703 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 704 | CGPathAddLineToPoint(path, NULL, rect.origin.x + cornerRadius, rect.origin.y + rect.size.height); 705 | CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height, rect.origin.x, rect.origin.y + rect.size.height - cornerRadius, cornerRadius); 706 | CGPathCloseSubpath(path); 707 | 708 | return path; 709 | } 710 | 711 | - (CGMutablePathRef)tailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius 712 | { 713 | CGMutablePathRef path = CGPathCreateMutable(); 714 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 715 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y); 716 | CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y + cornerRadius, cornerRadius); 717 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - cornerRadius); 718 | CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y + rect.size.height, cornerRadius); 719 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 720 | CGPathCloseSubpath(path); 721 | 722 | return path; 723 | } 724 | 725 | - (CGMutablePathRef)bodyPathInRect:(CGRect)rect 726 | { 727 | CGMutablePathRef path = CGPathCreateMutable(); 728 | CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); 729 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); 730 | CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); 731 | CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); 732 | CGPathCloseSubpath(path); 733 | 734 | return path; 735 | } 736 | 737 | 738 | #pragma mark - Drawing 739 | 740 | - (void)drawArrowAtPoint:(CGPoint)point arrowSize:(CGFloat)arrowSize arrowDirection:(QBPopupMenuArrowDirection)arrowDirection highlighted:(BOOL)highlighted 741 | { 742 | CGRect arrowRect = CGRectZero; 743 | 744 | switch (arrowDirection) { 745 | case QBPopupMenuArrowDirectionDown: 746 | { 747 | arrowRect = CGRectMake(point.x - arrowSize + 1.0, 748 | point.y - arrowSize, 749 | arrowSize * 2.0 - 1.0, 750 | arrowSize); 751 | 752 | arrowRect.origin.x = MIN(MAX(arrowRect.origin.x, self.cornerRadius), 753 | self.frame.size.width - self.cornerRadius - arrowRect.size.width); 754 | } 755 | break; 756 | 757 | case QBPopupMenuArrowDirectionUp: 758 | { 759 | arrowRect = CGRectMake(point.x - arrowSize + 1.0, 760 | 0, 761 | arrowSize * 2.0 - 1.0, 762 | arrowSize); 763 | 764 | arrowRect.origin.x = MIN(MAX(arrowRect.origin.x, self.cornerRadius), 765 | self.frame.size.width - self.cornerRadius - arrowRect.size.width); 766 | } 767 | break; 768 | 769 | case QBPopupMenuArrowDirectionLeft: 770 | { 771 | arrowRect = CGRectMake(0, 772 | point.y - arrowSize + 1.0, 773 | arrowSize, 774 | arrowSize * 2.0 - 1.0); 775 | } 776 | break; 777 | 778 | case QBPopupMenuArrowDirectionRight: 779 | { 780 | arrowRect = CGRectMake(point.x - arrowSize, 781 | point.y - arrowSize + 1.0, 782 | arrowSize, 783 | arrowSize * 2.0 - 1.0); 784 | } 785 | break; 786 | 787 | default: 788 | break; 789 | } 790 | 791 | [self drawArrowInRect:arrowRect direction:arrowDirection highlighted:highlighted]; 792 | } 793 | 794 | - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction highlighted:(BOOL)highlighted 795 | { 796 | CGContextRef context = UIGraphicsGetCurrentContext(); 797 | 798 | // Arrow 799 | CGContextSaveGState(context); { 800 | CGMutablePathRef path = [self arrowPathInRect:rect direction:direction]; 801 | CGContextAddPath(context, path); 802 | 803 | UIColor *color = highlighted ? self.highlightedColor : self.color; 804 | CGContextSetFillColorWithColor(context, [color CGColor]); 805 | CGContextFillPath(context); 806 | 807 | CGPathRelease(path); 808 | } CGContextRestoreGState(context); 809 | 810 | // Separator 811 | if (direction == QBPopupMenuArrowDirectionDown || direction == QBPopupMenuArrowDirectionUp) { 812 | for (QBPopupMenuItemView *itemView in self.visibleItemViews) { 813 | [self drawSeparatorInRect:CGRectMake(itemView.frame.origin.x + itemView.frame.size.width - 1, rect.origin.y, 1, rect.size.height)]; 814 | } 815 | } 816 | } 817 | 818 | - (void)drawHeadInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted 819 | { 820 | CGContextRef context = UIGraphicsGetCurrentContext(); 821 | 822 | // Head 823 | CGContextSaveGState(context); { 824 | CGMutablePathRef path = [self headPathInRect:rect cornerRadius:cornerRadius]; 825 | CGContextAddPath(context, path); 826 | 827 | UIColor *color = highlighted ? self.highlightedColor : self.color; 828 | CGContextSetFillColorWithColor(context, [color CGColor]); 829 | CGContextFillPath(context); 830 | 831 | CGPathRelease(path); 832 | } CGContextRestoreGState(context); 833 | } 834 | 835 | - (void)drawTailInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted 836 | { 837 | CGContextRef context = UIGraphicsGetCurrentContext(); 838 | 839 | // Tail 840 | CGContextSaveGState(context); { 841 | CGMutablePathRef path = [self tailPathInRect:rect cornerRadius:cornerRadius]; 842 | CGContextAddPath(context, path); 843 | 844 | UIColor *color = highlighted ? self.highlightedColor : self.color; 845 | CGContextSetFillColorWithColor(context, [color CGColor]); 846 | CGContextFillPath(context); 847 | 848 | CGPathRelease(path); 849 | } CGContextRestoreGState(context); 850 | } 851 | 852 | - (void)drawBodyInRect:(CGRect)rect firstItem:(BOOL)firstItem lastItem:(BOOL)lastItem highlighted:(BOOL)highlighted 853 | { 854 | CGContextRef context = UIGraphicsGetCurrentContext(); 855 | 856 | // Body 857 | CGContextSaveGState(context); { 858 | CGMutablePathRef path = [self bodyPathInRect:rect]; 859 | CGContextAddPath(context, path); 860 | 861 | UIColor *color = highlighted ? self.highlightedColor : self.color; 862 | CGContextSetFillColorWithColor(context, [color CGColor]); 863 | CGContextFillPath(context); 864 | 865 | CGPathRelease(path); 866 | } CGContextRestoreGState(context); 867 | 868 | // Separator 869 | if (!lastItem) { 870 | [self drawSeparatorInRect:CGRectMake(rect.origin.x + rect.size.width - 1, rect.origin.y, 1, rect.size.height)]; 871 | } 872 | } 873 | 874 | - (void)drawSeparatorInRect:(CGRect)rect 875 | { 876 | CGContextRef context = UIGraphicsGetCurrentContext(); 877 | 878 | // Separator 879 | CGContextSaveGState(context); { 880 | CGContextClearRect(context, rect); 881 | } CGContextRestoreGState(context); 882 | } 883 | 884 | @end 885 | --------------------------------------------------------------------------------