├── .gitignore ├── .idea ├── .gitignore ├── YBPopupMenu.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.swift ├── README.md ├── Tests ├── LinuxMain.swift └── YBPopupMenuTests │ ├── XCTestManifests.swift │ └── YBPopupMenuTests.swift ├── YBPopupMenu.podspec ├── YBPopupMenu ├── PrivacyInfo.xcprivacy ├── YBPopupMenu.h ├── YBPopupMenu.m ├── YBPopupMenuAnimationManager.h ├── YBPopupMenuAnimationManager.m ├── YBPopupMenuContainerView.h ├── YBPopupMenuContainerView.m ├── YBPopupMenuDeviceOrientationManager.h ├── YBPopupMenuDeviceOrientationManager.m ├── YBPopupMenuPath.h └── YBPopupMenuPath.m ├── YBPopupMenuDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── LYB.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── LYB.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── YBPopupMenuDemo.xcscheme │ └── xcschememanagement.plist ├── YBPopupMenuDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── delete.imageset │ │ ├── Contents.json │ │ └── delete-1.png │ ├── motify.imageset │ │ ├── Contents.json │ │ └── motify-1.png │ ├── pay.imageset │ │ ├── Contents.json │ │ └── pay-1.png │ └── saoyisao.imageset │ │ ├── Contents.json │ │ └── saoyisao-1.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CustomTestCell.h ├── CustomTestCell.m ├── CustomTestCell.xib ├── Info.plist ├── ViewController.h ├── ViewController.m ├── YBPopupMenu │ ├── YBPopupMenu.h │ ├── YBPopupMenu.m │ ├── YBPopupMenuAnimationManager.h │ ├── YBPopupMenuAnimationManager.m │ ├── YBPopupMenuContainerView.h │ ├── YBPopupMenuContainerView.m │ ├── YBPopupMenuDeviceOrientationManager.h │ ├── YBPopupMenuDeviceOrientationManager.m │ ├── YBPopupMenuPath.h │ └── YBPopupMenuPath.m └── main.m ├── YBPopupMenuDemoUITests ├── Info.plist └── YBPopupMenuDemoUITests.m └── YBPopupMenuGif.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/YBPopupMenu.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 yuanbo li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "YBPopupMenu", 8 | platforms: [.iOS(.v9)], 9 | products: [ 10 | .library( 11 | name: "YBPopupMenu", 12 | targets: ["YBPopupMenu"]), 13 | ], 14 | targets: [ 15 | .target( 16 | name: "YBPopupMenu", 17 | dependencies: [], 18 | path: "YBPopupMenu", 19 | resources: [.copy("PrivacyInfo.xcprivacy")] 20 | ) 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YBPopupMenu 2 | * 快速集成popupMenu 3 | 4 | # 效果图 5 | ![(演示效果)](https://lyb5834.github.io/Images/YBPopupMenuGif.gif) 6 | 7 | # cocoapods支持 8 | * 只需在`Podfile`中加入`pod 'YBPopupMenu'`后`pod install`即可 9 | 10 | # SwiftPackageManager(SPM)支持 11 | 12 | # 最近更新 13 | * 新增箭头样式 14 | 15 | | 样式 | 图例 | 16 | |------|------| 17 | | `YBPopupMenuArrowStyleCurve` **默认** | ![curve](https://lyb5834.github.io/Images/curve_arrow.png) | 18 | | `YBPopupMenuArrowStyleStraight` | ![straight](https://lyb5834.github.io/Images/straight_arrow.png) | 19 | 20 | * 增加了高斯模糊视图 21 | 22 | # 之前更新 23 | * 修复了快速点击屏幕动画重复执行的问题 24 | * 增加了`dismissAllPopupMenu` 方法,方便一键隐藏所有popupMenu 25 | * 删除老版初始化方法,避免频繁调用`[self updateUI]`方法 26 | * 增加横竖屏适配 27 | * 增加可选择的动画,暂时只有`YBPopupMenuAnimationStyleScale` 和 `YBPopupMenuAnimationStyleFade`两种,可自定义 28 | * 代码全部重构,不过完全兼容原先的API接口 29 | * 增加了`YBPopupMenuPriorityDirection`属性,可以设置箭头的第一优先级方向,当将要超过屏幕时会自动反转方向 30 | * 增加了`rectCorner`属性,可以自定义圆角(当反转时会自动镜像的反转圆角) 31 | * 可以设置边框颜色,边框粗细等 32 | * 支持传入`NSAttributedString` 33 | * 开放部分私有属性,如`titles`,`images`,`tableView`,`minSpace`等等 34 | * 点击回调方法有更新(旧的还可以用) 35 | * 增加可自定义Cell的回调,遇到奇葩需求可以自定义设置(具体使用方法可参考demo) 36 | 37 | # 注意 38 | 1. 当箭头优先级是`YBPopupMenuPriorityDirectionLeft`/`YBPopupMenuPriorityDirectionRight`/`YBPopupMenuPriorityDirectionNone`时需手动设置`arrowPosition`来设置箭头在该行的位置 39 | 2. 边框宽度不宜过粗,影响美观 40 | 41 | # 使用方法 42 | * `#import "YBPopupMenu.h"` 43 | 44 | ``` 45 | [YBPopupMenu showAtPoint:p titles:TITLES icons:nil menuWidth:110 otherSettings:^(YBPopupMenu *popupMenu) { 46 | popupMenu.dismissOnSelected = NO; 47 | popupMenu.showShadow = YES; 48 | popupMenu.delegate = self; 49 | popupMenu.offset = 10; 50 | popupMenu.type = YBPopupMenuTypeDark; 51 | popupMenu.rectCorner = UIRectCornerBottomLeft | UIRectCornerBottomRight; 52 | popupMenu...; 53 | }]; 54 | ``` 55 | 56 | 57 | # 版本支持 58 | * `xcode7.0+` 59 | 60 | * 如果您在使用本库的过程中发现任何bug或者有更好建议,欢迎 [@issues](https://github.com/lyb5834/YBPopupMenu/issues) 我或联系本人email lyb5834@126.com 61 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import YBPopupMenuTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += YBPopupMenuTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/YBPopupMenuTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(YBPopupMenuTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/YBPopupMenuTests/YBPopupMenuTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import YBPopupMenu 3 | 4 | final class YBPopupMenuTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(YBPopupMenu().text, "Hello, World!") 10 | 11 | } 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /YBPopupMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "YBPopupMenu" 3 | s.version = "1.3.1" 4 | s.summary = "快速集成popupMenu" 5 | s.description = "Code created and updated by Lyb." 6 | s.homepage = "https://github.com/lyb5834/YBPopupMenu.git" 7 | s.license = "MIT" 8 | s.author = { "lyb" => "lyb5834@126.com" } 9 | s.source = { :git => "https://github.com/lyb5834/YBPopupMenu.git", :tag => s.version.to_s } 10 | s.source_files = "YBPopupMenu/*.{h,m}" 11 | s.resource_bundles = {"YBPopupMenu" => ["YBPopupMenu/PrivacyInfo.xcprivacy"]} 12 | s.requires_arc = true 13 | s.platform = :ios, '9.0' 14 | end 15 | -------------------------------------------------------------------------------- /YBPopupMenu/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | NSPrivacyTrackingDomains 8 | 9 | NSPrivacyCollectedDataTypes 10 | 11 | NSPrivacyTracking 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenu.h 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/10. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YBPopupMenuPath.h" 11 | #import "YBPopupMenuDeviceOrientationManager.h" 12 | #import "YBPopupMenuAnimationManager.h" 13 | #import "YBPopupMenuContainerView.h" 14 | 15 | // 过期提醒 16 | #define YBDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 17 | 18 | typedef NS_ENUM(NSInteger , YBPopupMenuType) { 19 | YBPopupMenuTypeDefault = 0, 20 | YBPopupMenuTypeDark 21 | }; 22 | 23 | /** 24 | 箭头方向优先级 25 | 26 | 当控件超出屏幕时会自动调整成反方向 27 | */ 28 | typedef NS_ENUM(NSInteger , YBPopupMenuPriorityDirection) { 29 | YBPopupMenuPriorityDirectionTop = 0, //Default 30 | YBPopupMenuPriorityDirectionBottom, 31 | YBPopupMenuPriorityDirectionLeft, 32 | YBPopupMenuPriorityDirectionRight, 33 | YBPopupMenuPriorityDirectionNone //不自动调整 34 | }; 35 | 36 | @class YBPopupMenu; 37 | @protocol YBPopupMenuDelegate 38 | 39 | @optional 40 | 41 | - (void)ybPopupMenuBeganDismiss:(YBPopupMenu *)ybPopupMenu; 42 | - (void)ybPopupMenuDidDismiss:(YBPopupMenu *)ybPopupMenu; 43 | - (void)ybPopupMenuBeganShow:(YBPopupMenu *)ybPopupMenu; 44 | - (void)ybPopupMenuDidShow:(YBPopupMenu *)ybPopupMenu; 45 | 46 | /** 47 | 点击事件回调 48 | */ 49 | - (void)ybPopupMenu:(YBPopupMenu *)ybPopupMenu didSelectedAtIndex:(NSInteger)index; 50 | 51 | /** 52 | 自定义cell 53 | 54 | 可以自定义cell,设置后会忽略 fontSize textColor backColor type 属性 55 | cell 的高度是根据 itemHeight 的,直接设置无效 56 | 建议cell 背景色设置为透明色,不然切的圆角显示不出来 57 | */ 58 | - (UITableViewCell *)ybPopupMenu:(YBPopupMenu *)ybPopupMenu cellForRowAtIndex:(NSInteger)index; 59 | 60 | @end 61 | 62 | @interface YBPopupMenu : UIView 63 | 64 | /** 65 | 标题数组 只读属性 66 | */ 67 | @property (nonatomic, strong, readonly) NSArray * titles; 68 | 69 | /** 70 | 图片数组 只读属性 71 | */ 72 | @property (nonatomic, strong, readonly) NSArray * images; 73 | 74 | /** 75 | tableView Default separatorStyle is UITableViewCellSeparatorStyleNone 76 | */ 77 | @property (nonatomic, strong, readonly) UITableView * tableView; 78 | 79 | 80 | @property (nonatomic, strong, readonly) YBPopupMenuContainerView * containerView; 81 | 82 | /** 83 | 高斯模糊视图 backColor 有 alpha时生效 84 | 85 | exp: popupMenu.backColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; 86 | */ 87 | @property (nonatomic, strong, readonly) UIVisualEffectView * effectView; 88 | 89 | /** 90 | 圆角半径 Default is 5.0 91 | */ 92 | @property (nonatomic, assign) CGFloat cornerRadius; 93 | 94 | /** 95 | 自定义圆角 Default is UIRectCornerAllCorners 96 | 97 | 当自动调整方向时corner会自动转换至镜像方向 98 | */ 99 | @property (nonatomic, assign) UIRectCorner rectCorner; 100 | 101 | /** 102 | 是否显示阴影 Default is YES 103 | */ 104 | @property (nonatomic, assign , getter=isShowShadow) BOOL showShadow; 105 | 106 | /** 107 | 是否显示灰色覆盖层 Default is YES 108 | */ 109 | @property (nonatomic, assign) BOOL showMaskView; 110 | 111 | /** 112 | 选择菜单项后消失 Default is YES 113 | */ 114 | @property (nonatomic, assign) BOOL dismissOnSelected; 115 | 116 | /** 117 | 点击菜单外消失 Default is YES 118 | */ 119 | @property (nonatomic, assign) BOOL dismissOnTouchOutside; 120 | 121 | /** 122 | 设置字体大小 自定义cell时忽略 Default is 15 123 | */ 124 | @property (nonatomic, assign) CGFloat fontSize; 125 | 126 | /** 127 | 设置字体 设置时忽略fontSize Default is nil 128 | */ 129 | @property (nonatomic, strong) UIFont * font; 130 | 131 | /** 132 | 设置字体颜色 自定义cell时忽略 Default is [UIColor blackColor] 133 | */ 134 | @property (nonatomic, strong) UIColor * textColor; 135 | 136 | /** 137 | 设置偏移距离 (>= 0) Default is 0.0 138 | */ 139 | @property (nonatomic, assign) CGFloat offset; 140 | 141 | /** 142 | 边框宽度 Default is 0.0 143 | 144 | 设置边框需 > 0 145 | */ 146 | @property (nonatomic, assign) CGFloat borderWidth; 147 | 148 | /** 149 | 边框颜色 Default is LightGrayColor 150 | 151 | borderWidth <= 0 无效 152 | */ 153 | @property (nonatomic, strong) UIColor * borderColor; 154 | 155 | /** 156 | 箭头宽度 Default is 20 157 | */ 158 | @property (nonatomic, assign) CGFloat arrowWidth; 159 | 160 | /** 161 | 箭头高度 Default is 8 162 | */ 163 | @property (nonatomic, assign) CGFloat arrowHeight; 164 | 165 | /** 166 | 箭头位置 Default is center 167 | 168 | 只有箭头优先级是YBPopupMenuPriorityDirectionLeft/YBPopupMenuPriorityDirectionRight/YBPopupMenuPriorityDirectionNone时需要设置 169 | */ 170 | @property (nonatomic, assign) CGFloat arrowPosition; 171 | 172 | /** 173 | 箭头方向 Default is YBPopupMenuArrowDirectionTop 174 | */ 175 | @property (nonatomic, assign) YBPopupMenuArrowDirection arrowDirection; 176 | 177 | /** 178 | 箭头类型 Default is YBPopupMenuArrowStyleCurve 179 | */ 180 | @property (nonatomic, assign) YBPopupMenuArrowStyle arrowStyle; 181 | 182 | /** 183 | 箭头优先方向 Default is YBPopupMenuPriorityDirectionTop 184 | 185 | 当控件超出屏幕时会自动调整箭头位置 186 | */ 187 | @property (nonatomic, assign) YBPopupMenuPriorityDirection priorityDirection; 188 | 189 | /** 190 | 可见的最大行数 Default is 5; 191 | */ 192 | @property (nonatomic, assign) NSInteger maxVisibleCount; 193 | 194 | /** 195 | menu背景色 如果color有alpha就会显示effectView Default is WhiteColor 196 | */ 197 | @property (nonatomic, strong) UIColor * backColor; 198 | 199 | /** 200 | item的高度 Default is 44; 201 | */ 202 | @property (nonatomic, assign) CGFloat itemHeight; 203 | 204 | /** 205 | popupMenu距离最近的Screen的距离 Default is 10 206 | */ 207 | @property (nonatomic, assign) CGFloat minSpace; 208 | 209 | /** 210 | 设置显示模式 自定义cell时忽略 Default is YBPopupMenuTypeDefault 211 | */ 212 | @property (nonatomic, assign) YBPopupMenuType type; 213 | 214 | /** 215 | 屏幕旋转管理 216 | */ 217 | @property (nonatomic, strong) id orientationManager; 218 | 219 | /** 220 | 动画管理 221 | */ 222 | @property (nonatomic, strong) id animationManager; 223 | 224 | /** 225 | 代理 226 | */ 227 | @property (nonatomic, weak) id delegate; 228 | 229 | /** 230 | 在指定位置弹出 231 | 232 | @param point 弹出的位置 233 | @param titles 标题数组 数组里是NSString/NSAttributedString 234 | @param icons 图标数组 数组里是NSString/UIImage 235 | @param itemWidth 菜单宽度 236 | @param otherSetting 其他设置 237 | */ 238 | + (YBPopupMenu *)showAtPoint:(CGPoint)point 239 | titles:(NSArray *)titles 240 | icons:(NSArray *)icons 241 | menuWidth:(CGFloat)itemWidth 242 | otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting; 243 | 244 | /** 245 | 依赖指定view弹出 246 | 247 | @param titles 标题数组 数组里是NSString/NSAttributedString 248 | @param icons 图标数组 数组里是NSString/UIImage 249 | @param itemWidth 菜单宽度 250 | @param otherSetting 其他设置 251 | */ 252 | + (YBPopupMenu *)showRelyOnView:(UIView *)view 253 | titles:(NSArray *)titles 254 | icons:(NSArray *)icons 255 | menuWidth:(CGFloat)itemWidth 256 | otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting; 257 | 258 | /** 259 | 隐藏 260 | */ 261 | - (void)dismiss; 262 | 263 | /** 264 | 隐藏window上所有的popupMenu 265 | */ 266 | + (void)dismissAllPopupMenu; 267 | 268 | @end 269 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenu.m 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/10. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenu.h" 10 | #import "YBPopupMenuPath.h" 11 | 12 | #define YBScreenWidth [UIScreen mainScreen].bounds.size.width 13 | #define YBScreenHeight [UIScreen mainScreen].bounds.size.height 14 | #define YBMainWindow [UIApplication sharedApplication].keyWindow 15 | #define YB_SAFE_BLOCK(BlockName, ...) ({ !BlockName ? nil : BlockName(__VA_ARGS__); }) 16 | 17 | #pragma mark - ///////////// 18 | #pragma mark - private cell 19 | 20 | @interface YBPopupMenuCell : UITableViewCell 21 | @property (nonatomic, assign) BOOL isShowSeparator; 22 | @property (nonatomic, strong) UIColor * separatorColor; 23 | @end 24 | 25 | @implementation YBPopupMenuCell 26 | 27 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 28 | { 29 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 30 | if (self) { 31 | _isShowSeparator = YES; 32 | _separatorColor = [UIColor lightGrayColor]; 33 | [self setNeedsDisplay]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)setIsShowSeparator:(BOOL)isShowSeparator 39 | { 40 | _isShowSeparator = isShowSeparator; 41 | [self setNeedsDisplay]; 42 | } 43 | 44 | - (void)setSeparatorColor:(UIColor *)separatorColor 45 | { 46 | _separatorColor = separatorColor; 47 | [self setNeedsDisplay]; 48 | } 49 | 50 | - (void)layoutSubviews 51 | { 52 | [super layoutSubviews]; 53 | self.contentView.frame = self.bounds; 54 | self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 55 | } 56 | 57 | - (void)drawRect:(CGRect)rect 58 | { 59 | if (!_isShowSeparator) return; 60 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, rect.size.height - 0.5, rect.size.width, 0.5)]; 61 | [_separatorColor setFill]; 62 | [bezierPath fillWithBlendMode:kCGBlendModeNormal alpha:1]; 63 | [bezierPath closePath]; 64 | } 65 | 66 | @end 67 | 68 | 69 | 70 | @interface YBPopupMenu () 71 | < 72 | UITableViewDelegate, 73 | UITableViewDataSource 74 | > 75 | 76 | @property (nonatomic, strong) UIView * menuBackView; 77 | @property (nonatomic, strong) YBPopupMenuContainerView * containerView; 78 | @property (nonatomic, strong) UITableView * tableView; 79 | @property (nonatomic, strong) UIVisualEffectView * effectView; 80 | @property (nonatomic) CGRect relyRect; 81 | @property (nonatomic, assign) CGFloat itemWidth; 82 | @property (nonatomic) CGPoint point; 83 | @property (nonatomic, assign) BOOL isCornerChanged; 84 | @property (nonatomic, strong) UIColor * separatorColor; 85 | @property (nonatomic, assign) BOOL isChangeDirection; 86 | @property (nonatomic, strong) UIView * relyView; 87 | @end 88 | 89 | @implementation YBPopupMenu 90 | 91 | - (instancetype)init 92 | { 93 | self = [super init]; 94 | if (self) { 95 | [self setDefaultSettings]; 96 | } 97 | return self; 98 | } 99 | 100 | #pragma mark - publics 101 | + (YBPopupMenu *)showAtPoint:(CGPoint)point titles:(NSArray *)titles icons:(NSArray *)icons menuWidth:(CGFloat)itemWidth otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting 102 | { 103 | YBPopupMenu *popupMenu = [[YBPopupMenu alloc] init]; 104 | popupMenu.point = point; 105 | popupMenu.titles = titles; 106 | popupMenu.images = icons; 107 | popupMenu.itemWidth = itemWidth; 108 | YB_SAFE_BLOCK(otherSetting,popupMenu); 109 | [popupMenu show]; 110 | return popupMenu; 111 | } 112 | 113 | + (YBPopupMenu *)showRelyOnView:(UIView *)view titles:(NSArray *)titles icons:(NSArray *)icons menuWidth:(CGFloat)itemWidth otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting 114 | { 115 | YBPopupMenu *popupMenu = [[YBPopupMenu alloc] init]; 116 | popupMenu.relyView = view; 117 | popupMenu.titles = titles; 118 | popupMenu.images = icons; 119 | popupMenu.itemWidth = itemWidth; 120 | YB_SAFE_BLOCK(otherSetting,popupMenu); 121 | [popupMenu show]; 122 | return popupMenu; 123 | } 124 | 125 | - (void)dismiss 126 | { 127 | [self.orientationManager endMonitorDeviceOrientation]; 128 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuBeganDismiss:)]) { 129 | [self.delegate ybPopupMenuBeganDismiss:self]; 130 | } 131 | __weak typeof(self) weakSelf = self; 132 | [self.animationManager displayDismissAnimationCompletion:^{ 133 | __strong typeof(weakSelf)self = weakSelf; 134 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuDidDismiss:)]) { 135 | [self.delegate ybPopupMenuDidDismiss:self]; 136 | } 137 | self.delegate = nil; 138 | [self removeFromSuperview]; 139 | [self.menuBackView removeFromSuperview]; 140 | }]; 141 | } 142 | 143 | + (void)dismissAllPopupMenu 144 | { 145 | for (UIView * subView in YBMainWindow.subviews) { 146 | if ([subView isKindOfClass:[YBPopupMenu class]]) { 147 | YBPopupMenu * popupMenu = (YBPopupMenu *)subView; 148 | [popupMenu dismiss]; 149 | } 150 | } 151 | } 152 | 153 | #pragma mark tableViewDelegate & dataSource 154 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 155 | { 156 | return _titles.count; 157 | } 158 | 159 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 160 | { 161 | UITableViewCell * tableViewCell = nil; 162 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenu:cellForRowAtIndex:)]) { 163 | tableViewCell = [self.delegate ybPopupMenu:self cellForRowAtIndex:indexPath.row]; 164 | } 165 | 166 | if (tableViewCell) { 167 | return tableViewCell; 168 | } 169 | 170 | static NSString * identifier = @"ybPopupMenu"; 171 | YBPopupMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 172 | if (!cell) { 173 | cell = [[YBPopupMenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 174 | cell.textLabel.numberOfLines = 0; 175 | } 176 | cell.backgroundColor = [UIColor clearColor]; 177 | cell.textLabel.textColor = _textColor; 178 | if (_font) { 179 | cell.textLabel.font = _font; 180 | }else { 181 | cell.textLabel.font = [UIFont systemFontOfSize:_fontSize]; 182 | } 183 | if ([_titles[indexPath.row] isKindOfClass:[NSAttributedString class]]) { 184 | cell.textLabel.attributedText = _titles[indexPath.row]; 185 | }else if ([_titles[indexPath.row] isKindOfClass:[NSString class]]) { 186 | cell.textLabel.text = _titles[indexPath.row]; 187 | }else { 188 | cell.textLabel.text = nil; 189 | } 190 | cell.separatorColor = _separatorColor; 191 | if (_images.count >= indexPath.row + 1) { 192 | if ([_images[indexPath.row] isKindOfClass:[NSString class]]) { 193 | cell.imageView.image = [UIImage imageNamed:_images[indexPath.row]]; 194 | }else if ([_images[indexPath.row] isKindOfClass:[UIImage class]]){ 195 | cell.imageView.image = _images[indexPath.row]; 196 | }else { 197 | cell.imageView.image = nil; 198 | } 199 | }else { 200 | cell.imageView.image = nil; 201 | } 202 | return cell; 203 | } 204 | 205 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 206 | { 207 | return _itemHeight; 208 | } 209 | 210 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 211 | { 212 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 213 | if (_dismissOnSelected) [self dismiss]; 214 | 215 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenu:didSelectedAtIndex:)]) { 216 | [self.delegate ybPopupMenu:self didSelectedAtIndex:indexPath.row]; 217 | } 218 | } 219 | 220 | #pragma mark - scrollViewDelegate 221 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 222 | { 223 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 224 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 225 | cell.isShowSeparator = YES; 226 | } 227 | } 228 | 229 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 230 | { 231 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 232 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 233 | cell.isShowSeparator = NO; 234 | } 235 | } 236 | 237 | - (YBPopupMenuCell *)getLastVisibleCell 238 | { 239 | NSArray *indexPaths = [self.tableView indexPathsForVisibleRows]; 240 | indexPaths = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath * _Nonnull obj1, NSIndexPath * _Nonnull obj2) { 241 | return obj1.row < obj2.row; 242 | }]; 243 | NSIndexPath *indexPath = indexPaths.firstObject; 244 | return [self.tableView cellForRowAtIndexPath:indexPath]; 245 | } 246 | 247 | #pragma mark - privates 248 | - (void)show 249 | { 250 | [self.orientationManager startMonitorDeviceOrientation]; 251 | [self updateUI]; 252 | [YBMainWindow addSubview:_menuBackView]; 253 | [YBMainWindow addSubview:self]; 254 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuBeganShow:)]) { 255 | [self.delegate ybPopupMenuBeganShow:self]; 256 | } 257 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 258 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 259 | cell.isShowSeparator = NO; 260 | } 261 | __weak typeof(self) weakSelf = self; 262 | [self.animationManager displayShowAnimationCompletion:^{ 263 | __strong typeof(weakSelf)self = weakSelf; 264 | 265 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuDidShow:)]) { 266 | [self.delegate ybPopupMenuDidShow:self]; 267 | } 268 | }]; 269 | } 270 | 271 | - (void)setDefaultSettings 272 | { 273 | _cornerRadius = 5.0; 274 | _rectCorner = UIRectCornerAllCorners; 275 | self.showShadow = YES; 276 | _dismissOnSelected = YES; 277 | _dismissOnTouchOutside = YES; 278 | _fontSize = 15; 279 | _textColor = [UIColor blackColor]; 280 | _offset = 0.0; 281 | _relyRect = CGRectZero; 282 | _point = CGPointZero; 283 | _borderWidth = 0.0; 284 | _borderColor = [UIColor lightGrayColor]; 285 | _arrowWidth = 20.0; 286 | _arrowHeight = 8.0; 287 | _backColor = [UIColor whiteColor]; 288 | _type = YBPopupMenuTypeDefault; 289 | _arrowStyle = YBPopupMenuArrowStyleCurve; 290 | _arrowDirection = YBPopupMenuArrowDirectionTop; 291 | _priorityDirection = YBPopupMenuPriorityDirectionTop; 292 | _minSpace = 10.0; 293 | _maxVisibleCount = 5; 294 | _itemHeight = 44; 295 | _isCornerChanged = NO; 296 | _showMaskView = YES; 297 | _orientationManager = [YBPopupMenuDeviceOrientationManager manager]; 298 | _animationManager = [YBPopupMenuAnimationManager manager]; 299 | _animationManager.animationView = self; 300 | _menuBackView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, YBScreenWidth, YBScreenHeight)]; 301 | _menuBackView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.1]; 302 | _menuBackView.alpha = 1; 303 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(touchOutSide)]; 304 | [_menuBackView addGestureRecognizer: tap]; 305 | self.alpha = 1; 306 | self.backgroundColor = [UIColor clearColor]; 307 | [self addSubview:self.effectView]; 308 | [self addSubview:self.containerView]; 309 | [self.containerView addSubview:self.tableView]; 310 | 311 | __weak typeof(self) weakSelf = self; 312 | [_orientationManager setDeviceOrientDidChangeHandle:^(UIInterfaceOrientation orientation) { 313 | __strong typeof(weakSelf)self = weakSelf; 314 | if (orientation == UIInterfaceOrientationPortrait || 315 | orientation == UIInterfaceOrientationLandscapeLeft || 316 | orientation == UIInterfaceOrientationLandscapeRight) 317 | { 318 | if (self.relyView) { 319 | //依赖view 320 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 321 | //需要延迟加载才可以获取真实的frame,这里先做个标记,若有更合适的方法再替换 322 | [self calculateRealPointIfNeed]; 323 | [self updateUI]; 324 | }); 325 | }else { 326 | //依赖point 327 | [self updateUI]; 328 | } 329 | } 330 | }]; 331 | } 332 | 333 | - (void)configSettings 334 | { 335 | self.containerView.rectCorner = _rectCorner; 336 | self.containerView.cornerRadius = _cornerRadius; 337 | self.containerView.borderWidth = _borderWidth; 338 | self.containerView.borderColor = _borderColor; 339 | self.containerView.backColor = _backColor; 340 | self.containerView.arrowWidth = _arrowWidth; 341 | self.containerView.arrowHeight = _arrowHeight; 342 | self.containerView.arrowPosition = _arrowPosition; 343 | self.containerView.arrowDirection = _arrowDirection; 344 | self.containerView.arrowStyle = _arrowStyle; 345 | [self.containerView setNeedsDisplay]; 346 | 347 | self.effectView.layer.mask = [YBPopupMenuPath yb_maskLayerWithRect:self.effectView.frame rectCorner:_rectCorner cornerRadius:_cornerRadius arrowWidth:_arrowWidth arrowHeight:_arrowHeight arrowPosition:_arrowPosition arrowDirection:_arrowDirection arrowStyle:_arrowStyle]; 348 | } 349 | 350 | - (void)touchOutSide 351 | { 352 | if (_dismissOnTouchOutside) { 353 | [self dismiss]; 354 | } 355 | } 356 | 357 | - (void)setShowShadow:(BOOL)showShadow 358 | { 359 | _showShadow = showShadow; 360 | self.layer.shadowColor = [UIColor lightGrayColor].CGColor; 361 | self.layer.shadowOpacity = showShadow ? 0.5 : 0; 362 | self.layer.shadowOffset = CGSizeMake(0, 0); 363 | self.layer.shadowRadius = showShadow ? 4.0 : 0; 364 | } 365 | 366 | - (void)setRelyView:(UIView *)relyView 367 | { 368 | _relyView = relyView; 369 | [self calculateRealPointIfNeed]; 370 | } 371 | 372 | - (void)calculateRealPointIfNeed 373 | { 374 | CGRect absoluteRect = [_relyView convertRect:_relyView.bounds toView:YBMainWindow]; 375 | CGPoint relyPoint = CGPointMake(absoluteRect.origin.x + absoluteRect.size.width / 2, absoluteRect.origin.y + absoluteRect.size.height); 376 | self.relyRect = absoluteRect; 377 | self.point = relyPoint; 378 | } 379 | 380 | - (void)setShowMaskView:(BOOL)showMaskView 381 | { 382 | _showMaskView = showMaskView; 383 | _menuBackView.backgroundColor = showMaskView ? [[UIColor blackColor] colorWithAlphaComponent:0.1] : [UIColor clearColor]; 384 | } 385 | 386 | - (void)setType:(YBPopupMenuType)type 387 | { 388 | _type = type; 389 | switch (type) { 390 | case YBPopupMenuTypeDark: 391 | { 392 | _textColor = [UIColor lightGrayColor]; 393 | _backColor = [UIColor colorWithRed:0.25 green:0.27 blue:0.29 alpha:1]; 394 | _separatorColor = [UIColor lightGrayColor]; 395 | } 396 | break; 397 | 398 | default: 399 | { 400 | _textColor = [UIColor blackColor]; 401 | _backColor = [UIColor whiteColor]; 402 | _separatorColor = [UIColor lightGrayColor]; 403 | } 404 | break; 405 | } 406 | } 407 | 408 | - (void)setTitles:(NSArray *)titles 409 | { 410 | _titles = titles; 411 | } 412 | 413 | - (void)setImages:(NSArray *)images 414 | { 415 | _images = images; 416 | } 417 | 418 | - (void)updateUI 419 | { 420 | _menuBackView.frame = CGRectMake(0, 0, YBScreenWidth, YBScreenHeight); 421 | CGFloat height; 422 | if (_titles.count > _maxVisibleCount) { 423 | height = _itemHeight * _maxVisibleCount + _borderWidth * 2; 424 | self.tableView.bounces = YES; 425 | }else { 426 | height = _itemHeight * _titles.count + _borderWidth * 2; 427 | self.tableView.bounces = NO; 428 | } 429 | _isChangeDirection = NO; 430 | if (_priorityDirection == YBPopupMenuPriorityDirectionTop) { 431 | if (_point.y + height + _arrowHeight > YBScreenHeight - _minSpace) { 432 | _arrowDirection = YBPopupMenuArrowDirectionBottom; 433 | _isChangeDirection = YES; 434 | }else { 435 | _arrowDirection = YBPopupMenuArrowDirectionTop; 436 | _isChangeDirection = NO; 437 | } 438 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionBottom) { 439 | if (_point.y - height - _arrowHeight < _minSpace) { 440 | _arrowDirection = YBPopupMenuArrowDirectionTop; 441 | _isChangeDirection = YES; 442 | }else { 443 | _arrowDirection = YBPopupMenuArrowDirectionBottom; 444 | _isChangeDirection = NO; 445 | } 446 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionLeft) { 447 | if (_point.x + _itemWidth + _arrowHeight > YBScreenWidth - _minSpace) { 448 | _arrowDirection = YBPopupMenuArrowDirectionRight; 449 | _isChangeDirection = YES; 450 | }else { 451 | _arrowDirection = YBPopupMenuArrowDirectionLeft; 452 | _isChangeDirection = NO; 453 | } 454 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionRight) { 455 | if (_point.x - _itemWidth - _arrowHeight < _minSpace) { 456 | _arrowDirection = YBPopupMenuArrowDirectionLeft; 457 | _isChangeDirection = YES; 458 | }else { 459 | _arrowDirection = YBPopupMenuArrowDirectionRight; 460 | _isChangeDirection = NO; 461 | } 462 | } 463 | [self setArrowPosition]; 464 | [self setRelyRect]; 465 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 466 | CGFloat y = _isChangeDirection ? _point.y : _point.y; 467 | if (_arrowPosition > _itemWidth / 2) { 468 | self.frame = CGRectMake(YBScreenWidth - _minSpace - _itemWidth, y, _itemWidth, height + _arrowHeight); 469 | }else if (_arrowPosition < _itemWidth / 2) { 470 | self.frame = CGRectMake(_minSpace, y, _itemWidth, height + _arrowHeight); 471 | }else { 472 | self.frame = CGRectMake(_point.x - _itemWidth / 2, y, _itemWidth, height + _arrowHeight); 473 | } 474 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 475 | CGFloat y = _isChangeDirection ? _point.y - _arrowHeight - height : _point.y - _arrowHeight - height; 476 | if (_arrowPosition > _itemWidth / 2) { 477 | self.frame = CGRectMake(YBScreenWidth - _minSpace - _itemWidth, y, _itemWidth, height + _arrowHeight); 478 | }else if (_arrowPosition < _itemWidth / 2) { 479 | self.frame = CGRectMake(_minSpace, y, _itemWidth, height + _arrowHeight); 480 | }else { 481 | self.frame = CGRectMake(_point.x - _itemWidth / 2, y, _itemWidth, height + _arrowHeight); 482 | } 483 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 484 | CGFloat x = _isChangeDirection ? _point.x : _point.x; 485 | if (_arrowPosition < _itemHeight / 2) { 486 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 487 | }else if (_arrowPosition > _itemHeight / 2) { 488 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 489 | }else { 490 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 491 | } 492 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 493 | CGFloat x = _isChangeDirection ? _point.x - _itemWidth - _arrowHeight : _point.x - _itemWidth - _arrowHeight; 494 | if (_arrowPosition < _itemHeight / 2) { 495 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 496 | }else if (_arrowPosition > _itemHeight / 2) { 497 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 498 | }else { 499 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 500 | } 501 | }else if (_arrowDirection == YBPopupMenuArrowDirectionNone) { 502 | 503 | } 504 | 505 | if (_isChangeDirection) { 506 | [self changeRectCorner]; 507 | } 508 | [self setAnchorPoint]; 509 | [self setOffset]; 510 | [self.tableView reloadData]; 511 | [self configSettings]; 512 | } 513 | 514 | - (void)setRelyRect 515 | { 516 | if (CGRectEqualToRect(_relyRect, CGRectZero)) { 517 | return; 518 | } 519 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 520 | _point.y = _relyRect.size.height + _relyRect.origin.y; 521 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 522 | _point.y = _relyRect.origin.y; 523 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 524 | _point = CGPointMake(_relyRect.origin.x + _relyRect.size.width, _relyRect.origin.y + _relyRect.size.height / 2); 525 | }else { 526 | _point = CGPointMake(_relyRect.origin.x, _relyRect.origin.y + _relyRect.size.height / 2); 527 | } 528 | } 529 | 530 | 531 | - (void)setFrame:(CGRect)frame 532 | { 533 | [super setFrame:frame]; 534 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 535 | self.tableView.frame = CGRectMake(_borderWidth, _borderWidth + _arrowHeight, frame.size.width - _borderWidth * 2, frame.size.height - _arrowHeight); 536 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 537 | self.tableView.frame = CGRectMake(_borderWidth, _borderWidth, frame.size.width - _borderWidth * 2, frame.size.height - _arrowHeight); 538 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 539 | self.tableView.frame = CGRectMake(_borderWidth + _arrowHeight, _borderWidth , frame.size.width - _borderWidth * 2 - _arrowHeight, frame.size.height); 540 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 541 | self.tableView.frame = CGRectMake(_borderWidth , _borderWidth , frame.size.width - _borderWidth * 2 - _arrowHeight, frame.size.height); 542 | } 543 | self.effectView.frame = self.bounds; 544 | self.containerView.frame = self.bounds; 545 | } 546 | 547 | - (void)changeRectCorner 548 | { 549 | if (_isCornerChanged || _rectCorner == UIRectCornerAllCorners) { 550 | return; 551 | } 552 | BOOL haveTopLeftCorner = NO, haveTopRightCorner = NO, haveBottomLeftCorner = NO, haveBottomRightCorner = NO; 553 | if (_rectCorner & UIRectCornerTopLeft) { 554 | haveTopLeftCorner = YES; 555 | } 556 | if (_rectCorner & UIRectCornerTopRight) { 557 | haveTopRightCorner = YES; 558 | } 559 | if (_rectCorner & UIRectCornerBottomLeft) { 560 | haveBottomLeftCorner = YES; 561 | } 562 | if (_rectCorner & UIRectCornerBottomRight) { 563 | haveBottomRightCorner = YES; 564 | } 565 | 566 | if (_arrowDirection == YBPopupMenuArrowDirectionTop || _arrowDirection == YBPopupMenuArrowDirectionBottom) { 567 | 568 | if (haveTopLeftCorner) { 569 | _rectCorner = _rectCorner | UIRectCornerBottomLeft; 570 | }else { 571 | _rectCorner = _rectCorner & (~UIRectCornerBottomLeft); 572 | } 573 | if (haveTopRightCorner) { 574 | _rectCorner = _rectCorner | UIRectCornerBottomRight; 575 | }else { 576 | _rectCorner = _rectCorner & (~UIRectCornerBottomRight); 577 | } 578 | if (haveBottomLeftCorner) { 579 | _rectCorner = _rectCorner | UIRectCornerTopLeft; 580 | }else { 581 | _rectCorner = _rectCorner & (~UIRectCornerTopLeft); 582 | } 583 | if (haveBottomRightCorner) { 584 | _rectCorner = _rectCorner | UIRectCornerTopRight; 585 | }else { 586 | _rectCorner = _rectCorner & (~UIRectCornerTopRight); 587 | } 588 | 589 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft || _arrowDirection == YBPopupMenuArrowDirectionRight) { 590 | if (haveTopLeftCorner) { 591 | _rectCorner = _rectCorner | UIRectCornerTopRight; 592 | }else { 593 | _rectCorner = _rectCorner & (~UIRectCornerTopRight); 594 | } 595 | if (haveTopRightCorner) { 596 | _rectCorner = _rectCorner | UIRectCornerTopLeft; 597 | }else { 598 | _rectCorner = _rectCorner & (~UIRectCornerTopLeft); 599 | } 600 | if (haveBottomLeftCorner) { 601 | _rectCorner = _rectCorner | UIRectCornerBottomRight; 602 | }else { 603 | _rectCorner = _rectCorner & (~UIRectCornerBottomRight); 604 | } 605 | if (haveBottomRightCorner) { 606 | _rectCorner = _rectCorner | UIRectCornerBottomLeft; 607 | }else { 608 | _rectCorner = _rectCorner & (~UIRectCornerBottomLeft); 609 | } 610 | } 611 | 612 | _isCornerChanged = YES; 613 | } 614 | 615 | - (void)setOffset 616 | { 617 | if (_itemWidth == 0) return; 618 | 619 | CGRect originRect = self.frame; 620 | 621 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 622 | originRect.origin.y += _offset; 623 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 624 | originRect.origin.y -= _offset; 625 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 626 | originRect.origin.x += _offset; 627 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 628 | originRect.origin.x -= _offset; 629 | } 630 | self.frame = originRect; 631 | } 632 | 633 | - (void)setAnchorPoint 634 | { 635 | if (_itemWidth == 0) return; 636 | 637 | CGFloat menuHeight = [self getMenuTotalHeight]; 638 | 639 | CGPoint point = CGPointMake(0.5, 0.5); 640 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 641 | point = CGPointMake(_arrowPosition / _itemWidth, 0); 642 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 643 | point = CGPointMake(_arrowPosition / _itemWidth, 1); 644 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 645 | point = CGPointMake(0, _arrowPosition / menuHeight); 646 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 647 | point = CGPointMake(1, _arrowPosition / menuHeight); 648 | } 649 | CGRect originRect = self.frame; 650 | self.layer.anchorPoint = point; 651 | self.frame = originRect; 652 | } 653 | 654 | - (void)setArrowPosition 655 | { 656 | if (_priorityDirection == YBPopupMenuPriorityDirectionNone) { 657 | return; 658 | } 659 | 660 | if (_arrowDirection == YBPopupMenuArrowDirectionTop || _arrowDirection == YBPopupMenuArrowDirectionBottom) { 661 | if (_point.x + _itemWidth / 2 > YBScreenWidth - _minSpace) { 662 | _arrowPosition = _itemWidth - (YBScreenWidth - _minSpace - _point.x); 663 | }else if (_point.x < _itemWidth / 2 + _minSpace) { 664 | _arrowPosition = _point.x - _minSpace; 665 | }else { 666 | _arrowPosition = _itemWidth / 2; 667 | } 668 | 669 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft || _arrowDirection == YBPopupMenuArrowDirectionRight) { 670 | } 671 | } 672 | 673 | - (CGFloat)getMenuTotalHeight 674 | { 675 | CGFloat menuHeight = 0; 676 | if (_titles.count > _maxVisibleCount) { 677 | menuHeight = _itemHeight * _maxVisibleCount + _borderWidth * 2; 678 | }else { 679 | menuHeight = _itemHeight * _titles.count + _borderWidth * 2; 680 | } 681 | return menuHeight; 682 | } 683 | 684 | #pragma mark - lazyloads 685 | - (UITableView *)tableView 686 | { 687 | if (!_tableView) { 688 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 689 | _tableView.backgroundColor = [UIColor clearColor]; 690 | _tableView.tableFooterView = [UIView new]; 691 | _tableView.delegate = self; 692 | _tableView.dataSource = self; 693 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 694 | } 695 | return _tableView; 696 | } 697 | 698 | - (YBPopupMenuContainerView *)containerView 699 | { 700 | if (!_containerView) { 701 | _containerView = [[YBPopupMenuContainerView alloc] init]; 702 | _containerView.backgroundColor = [UIColor clearColor]; 703 | } 704 | return _containerView; 705 | } 706 | 707 | - (UIVisualEffectView *)effectView 708 | { 709 | if (!_effectView) { 710 | UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 711 | _effectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 712 | } 713 | return _effectView; 714 | } 715 | 716 | @end 717 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuAnimationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuAnimationManager.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger,YBPopupMenuAnimationStyle) { 15 | YBPopupMenuAnimationStyleScale = 0, //scale动画 Default 16 | YBPopupMenuAnimationStyleFade, //alpha 0~1 17 | YBPopupMenuAnimationStyleNone, //没有动画 18 | YBPopupMenuAnimationStyleCustom //自定义 19 | }; 20 | 21 | @protocol YBPopupMenuAnimationManager 22 | 23 | /** 24 | 动画类型,默认YBPopupMenuAnimationStyleScale 25 | */ 26 | @property (nonatomic, assign) YBPopupMenuAnimationStyle style; 27 | 28 | /** 29 | 显示动画,自定义可用 30 | */ 31 | @property (nonatomic, strong) CAAnimation * showAnimation; 32 | 33 | /** 34 | 隐藏动画,自定义可用 35 | */ 36 | @property (nonatomic, strong) CAAnimation * dismissAnimation; 37 | 38 | /** 39 | 弹出和隐藏动画的时间,Default is 0.25 40 | */ 41 | @property CFTimeInterval duration; 42 | 43 | @property (nonatomic, weak) UIView * animationView; 44 | 45 | + (id )manager; 46 | 47 | - (void)displayShowAnimationCompletion:(void (^) (void))completion; 48 | 49 | - (void)displayDismissAnimationCompletion:(void (^) (void))completion; 50 | 51 | @end 52 | 53 | @interface YBPopupMenuAnimationManager : NSObject 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuAnimationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuAnimationManager.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuAnimationManager.h" 10 | 11 | static NSString * const YBShowAnimationKey = @"showAnimation"; 12 | static NSString * const YBDismissAnimationKey = @"dismissAnimation"; 13 | @interface YBPopupMenuAnimationManager () 14 | < 15 | CAAnimationDelegate 16 | > 17 | @property (nonatomic, copy) void (^showAnimationHandle) (void); 18 | 19 | @property (nonatomic, copy) void (^dismissAnimationHandle) (void); 20 | 21 | @property (nonatomic, assign) BOOL isAnimating; 22 | 23 | @end 24 | 25 | @implementation YBPopupMenuAnimationManager 26 | @synthesize style = _style; 27 | @synthesize showAnimation = _showAnimation; 28 | @synthesize dismissAnimation = _dismissAnimation; 29 | @synthesize duration = _duration; 30 | @synthesize animationView = _animationView; 31 | 32 | + (id)manager 33 | { 34 | YBPopupMenuAnimationManager * manager = [[YBPopupMenuAnimationManager alloc] init]; 35 | manager.style = YBPopupMenuAnimationStyleScale; 36 | manager.duration = 0.25; 37 | return manager; 38 | } 39 | 40 | - (void)configAnimation 41 | { 42 | CABasicAnimation * showAnimation; 43 | CABasicAnimation * dismissAnimation; 44 | switch (_style) { 45 | case YBPopupMenuAnimationStyleFade: 46 | { 47 | _showAnimation = _dismissAnimation = nil; 48 | //show 49 | showAnimation = [self getBasicAnimationWithKeyPath:@"opacity"]; 50 | showAnimation.fillMode = kCAFillModeBackwards; 51 | showAnimation.fromValue = @(0); 52 | showAnimation.toValue = @(1); 53 | _showAnimation = showAnimation; 54 | //dismiss 55 | dismissAnimation = [self getBasicAnimationWithKeyPath:@"opacity"]; 56 | dismissAnimation.fillMode = kCAFillModeForwards; 57 | dismissAnimation.fromValue = @(1); 58 | dismissAnimation.toValue = @(0); 59 | _dismissAnimation = dismissAnimation; 60 | } 61 | break; 62 | case YBPopupMenuAnimationStyleCustom: 63 | break; 64 | case YBPopupMenuAnimationStyleNone: 65 | { 66 | _showAnimation = _dismissAnimation = nil; 67 | } 68 | break; 69 | default: 70 | { 71 | _showAnimation = _dismissAnimation = nil; 72 | //show 73 | showAnimation = [self getBasicAnimationWithKeyPath:@"transform.scale"]; 74 | showAnimation.fillMode = kCAFillModeBackwards; 75 | showAnimation.fromValue = @(0.1); 76 | showAnimation.toValue = @(1); 77 | _showAnimation = showAnimation; 78 | //dismiss 79 | dismissAnimation = [self getBasicAnimationWithKeyPath:@"transform.scale"]; 80 | dismissAnimation.fillMode = kCAFillModeForwards; 81 | dismissAnimation.fromValue = @(1); 82 | dismissAnimation.toValue = @(0.1); 83 | _dismissAnimation = dismissAnimation; 84 | } 85 | break; 86 | } 87 | } 88 | 89 | - (void)setStyle:(YBPopupMenuAnimationStyle)style 90 | { 91 | _style = style; 92 | [self configAnimation]; 93 | } 94 | 95 | - (void)setDuration:(CFTimeInterval)duration 96 | { 97 | _duration = duration; 98 | [self configAnimation]; 99 | } 100 | 101 | - (void)setShowAnimation:(CAAnimation *)showAnimation 102 | { 103 | _showAnimation = showAnimation; 104 | [self configAnimation]; 105 | } 106 | 107 | - (void)setDismissAnimation:(CAAnimation *)dismissAnimation 108 | { 109 | _dismissAnimation = dismissAnimation; 110 | [self configAnimation]; 111 | } 112 | 113 | - (CABasicAnimation *)getBasicAnimationWithKeyPath:(NSString *)keyPath 114 | { 115 | CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:keyPath]; 116 | animation.removedOnCompletion = NO; 117 | animation.duration = _duration; 118 | return animation; 119 | } 120 | 121 | - (void)displayShowAnimationCompletion:(void (^)(void))completion 122 | { 123 | _showAnimationHandle = completion; 124 | if (!_showAnimation) { 125 | if (_showAnimationHandle) { 126 | _showAnimationHandle(); 127 | } 128 | return; 129 | } 130 | if (self.isAnimating) return; 131 | self.isAnimating = YES; 132 | _showAnimation.delegate = self; 133 | [_animationView.layer addAnimation:_showAnimation forKey:YBShowAnimationKey]; 134 | } 135 | 136 | - (void)displayDismissAnimationCompletion:(void (^)(void))completion 137 | { 138 | _dismissAnimationHandle = completion; 139 | if (!_dismissAnimation) { 140 | if (_dismissAnimationHandle) { 141 | _dismissAnimationHandle(); 142 | } 143 | return; 144 | } 145 | if (self.isAnimating) return; 146 | self.isAnimating = YES; 147 | _dismissAnimation.delegate = self; 148 | [_animationView.layer addAnimation:_dismissAnimation forKey:YBDismissAnimationKey]; 149 | } 150 | 151 | #pragma mark - CAAnimationDelegate 152 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 153 | { 154 | if ([_animationView.layer animationForKey:YBShowAnimationKey] == anim) { 155 | [_animationView.layer removeAnimationForKey:YBShowAnimationKey]; 156 | _showAnimation.delegate = nil; 157 | _showAnimation = nil; 158 | _isAnimating = NO; 159 | if (_showAnimationHandle) { 160 | _showAnimationHandle(); 161 | } 162 | }else if ([_animationView.layer animationForKey:YBDismissAnimationKey] == anim) { 163 | [_animationView.layer removeAnimationForKey:YBDismissAnimationKey]; 164 | _dismissAnimation.delegate = nil; 165 | _dismissAnimation = nil; 166 | _isAnimating = NO; 167 | if (_dismissAnimationHandle) { 168 | _dismissAnimationHandle(); 169 | } 170 | } 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuContainerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuContainerView.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2023/8/16. 6 | // Copyright © 2023 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YBPopupMenuPath.h" 11 | 12 | @interface YBPopupMenuContainerView : UIView 13 | 14 | @property (nonatomic, assign) CGFloat cornerRadius; 15 | 16 | @property (nonatomic, assign) UIRectCorner rectCorner; 17 | 18 | @property (nonatomic, assign) CGFloat borderWidth; 19 | 20 | @property (nonatomic, strong) UIColor * borderColor; 21 | 22 | @property (nonatomic, assign) CGFloat arrowWidth; 23 | 24 | @property (nonatomic, assign) CGFloat arrowHeight; 25 | 26 | @property (nonatomic, assign) CGFloat arrowPosition; 27 | 28 | @property (nonatomic, assign) YBPopupMenuArrowDirection arrowDirection; 29 | 30 | @property (nonatomic, assign) YBPopupMenuArrowStyle arrowStyle; 31 | 32 | @property (nonatomic, strong) UIColor * backColor; 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuContainerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuContainerView.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2023/8/16. 6 | // Copyright © 2023 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuContainerView.h" 10 | 11 | @implementation YBPopupMenuContainerView 12 | 13 | - (void)drawRect:(CGRect)rect 14 | { 15 | UIBezierPath *bezierPath = [YBPopupMenuPath yb_bezierPathWithRect:rect rectCorner:_rectCorner cornerRadius:_cornerRadius borderWidth:_borderWidth borderColor:_borderColor backgroundColor:_backColor arrowWidth:_arrowWidth arrowHeight:_arrowHeight arrowPosition:_arrowPosition arrowDirection:_arrowDirection arrowStyle:_arrowStyle]; 16 | [bezierPath fill]; 17 | [bezierPath stroke]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuDeviceOrientationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuDeviceOrientationManager.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @protocol YBPopupMenuDeviceOrientationManager 17 | 18 | /** 19 | 根据屏幕旋转方向自动旋转 Default is YES 20 | */ 21 | @property (nonatomic, assign) BOOL autoRotateWhenDeviceOrientationChanged; 22 | 23 | @property (nonatomic, copy) void (^deviceOrientDidChangeHandle) (UIInterfaceOrientation orientation); 24 | 25 | + (id )manager; 26 | 27 | /** 28 | 开始监听 29 | */ 30 | - (void)startMonitorDeviceOrientation; 31 | 32 | /** 33 | 结束监听 34 | */ 35 | - (void)endMonitorDeviceOrientation; 36 | 37 | @end 38 | 39 | @interface YBPopupMenuDeviceOrientationManager : NSObject 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuDeviceOrientationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuDeviceOrientationManager.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuDeviceOrientationManager.h" 10 | 11 | @implementation YBPopupMenuDeviceOrientationManager 12 | @synthesize autoRotateWhenDeviceOrientationChanged = _autoRotateWhenDeviceOrientationChanged; 13 | @synthesize deviceOrientDidChangeHandle = _deviceOrientDidChangeHandle; 14 | 15 | + (id)manager 16 | { 17 | YBPopupMenuDeviceOrientationManager * manager = [[YBPopupMenuDeviceOrientationManager alloc] init]; 18 | manager.autoRotateWhenDeviceOrientationChanged = YES; 19 | return manager; 20 | } 21 | 22 | - (void)startMonitorDeviceOrientation 23 | { 24 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationDidChangedNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 26 | } 27 | 28 | - (void)endMonitorDeviceOrientation 29 | { 30 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 31 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 32 | } 33 | 34 | #pragma mark - notify 35 | - (void)onDeviceOrientationDidChangedNotification:(NSNotification *)notify 36 | { 37 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 38 | UIInterfaceOrientation orientation = [notify.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue]; 39 | if (_deviceOrientDidChangeHandle) { 40 | _deviceOrientDidChangeHandle(orientation); 41 | } 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuPath.h 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/9. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, YBPopupMenuArrowDirection) { 12 | YBPopupMenuArrowDirectionTop = 0, //箭头朝上 13 | YBPopupMenuArrowDirectionBottom, //箭头朝下 14 | YBPopupMenuArrowDirectionLeft, //箭头朝左 15 | YBPopupMenuArrowDirectionRight, //箭头朝右 16 | YBPopupMenuArrowDirectionNone //没有箭头 17 | }; 18 | 19 | typedef NS_ENUM(NSInteger, YBPopupMenuArrowStyle) { 20 | YBPopupMenuArrowStyleCurve = 0, //曲线箭头 21 | YBPopupMenuArrowStyleStraight, //直线箭头 22 | }; 23 | 24 | @interface YBPopupMenuPath : NSObject 25 | 26 | + (CAShapeLayer *)yb_maskLayerWithRect:(CGRect)rect 27 | rectCorner:(UIRectCorner)rectCorner 28 | cornerRadius:(CGFloat)cornerRadius 29 | arrowWidth:(CGFloat)arrowWidth 30 | arrowHeight:(CGFloat)arrowHeight 31 | arrowPosition:(CGFloat)arrowPosition 32 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 33 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle; 34 | 35 | + (UIBezierPath *)yb_bezierPathWithRect:(CGRect)rect 36 | rectCorner:(UIRectCorner)rectCorner 37 | cornerRadius:(CGFloat)cornerRadius 38 | borderWidth:(CGFloat)borderWidth 39 | borderColor:(UIColor *)borderColor 40 | backgroundColor:(UIColor *)backgroundColor 41 | arrowWidth:(CGFloat)arrowWidth 42 | arrowHeight:(CGFloat)arrowHeight 43 | arrowPosition:(CGFloat)arrowPosition 44 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 45 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle; 46 | @end 47 | -------------------------------------------------------------------------------- /YBPopupMenu/YBPopupMenuPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuPath.m 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/9. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuPath.h" 10 | 11 | @implementation YBPopupMenuPath 12 | 13 | + (CAShapeLayer *)yb_maskLayerWithRect:(CGRect)rect 14 | rectCorner:(UIRectCorner)rectCorner 15 | cornerRadius:(CGFloat)cornerRadius 16 | arrowWidth:(CGFloat)arrowWidth 17 | arrowHeight:(CGFloat)arrowHeight 18 | arrowPosition:(CGFloat)arrowPosition 19 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 20 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle 21 | { 22 | CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 23 | shapeLayer.path = [self yb_bezierPathWithRect:rect rectCorner:rectCorner cornerRadius:cornerRadius borderWidth:0 borderColor:nil backgroundColor:nil arrowWidth:arrowWidth arrowHeight:arrowHeight arrowPosition:arrowPosition arrowDirection:arrowDirection arrowStyle:arrowStyle].CGPath; 24 | return shapeLayer; 25 | } 26 | 27 | 28 | + (UIBezierPath *)yb_bezierPathWithRect:(CGRect)rect 29 | rectCorner:(UIRectCorner)rectCorner 30 | cornerRadius:(CGFloat)cornerRadius 31 | borderWidth:(CGFloat)borderWidth 32 | borderColor:(UIColor *)borderColor 33 | backgroundColor:(UIColor *)backgroundColor 34 | arrowWidth:(CGFloat)arrowWidth 35 | arrowHeight:(CGFloat)arrowHeight 36 | arrowPosition:(CGFloat)arrowPosition 37 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 38 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle 39 | { 40 | UIBezierPath *bezierPath = [UIBezierPath bezierPath]; 41 | if (borderColor) { 42 | [borderColor setStroke]; 43 | } 44 | if (backgroundColor) { 45 | [backgroundColor setFill]; 46 | } 47 | bezierPath.lineWidth = borderWidth; 48 | rect = CGRectMake(borderWidth / 2, borderWidth / 2, CGRectGetWidth(rect) - borderWidth, CGRectGetHeight(rect) - borderWidth); 49 | CGFloat topRightRadius = 0,topLeftRadius = 0,bottomRightRadius = 0,bottomLeftRadius = 0; 50 | CGPoint topRightArcCenter,topLeftArcCenter,bottomRightArcCenter,bottomLeftArcCenter; 51 | 52 | if (rectCorner & UIRectCornerTopLeft) { 53 | topLeftRadius = cornerRadius; 54 | } 55 | if (rectCorner & UIRectCornerTopRight) { 56 | topRightRadius = cornerRadius; 57 | } 58 | if (rectCorner & UIRectCornerBottomLeft) { 59 | bottomLeftRadius = cornerRadius; 60 | } 61 | if (rectCorner & UIRectCornerBottomRight) { 62 | bottomRightRadius = cornerRadius; 63 | } 64 | 65 | if (arrowDirection == YBPopupMenuArrowDirectionTop) { 66 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect)); 67 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), arrowHeight + topRightRadius + CGRectGetMinX(rect)); 68 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 69 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 70 | if (arrowPosition < topLeftRadius + arrowWidth / 2) { 71 | arrowPosition = topLeftRadius + arrowWidth / 2; 72 | }else if (arrowPosition > CGRectGetWidth(rect) - topRightRadius - arrowWidth / 2) { 73 | arrowPosition = CGRectGetWidth(rect) - topRightRadius - arrowWidth / 2; 74 | } 75 | [bezierPath moveToPoint:CGPointMake(arrowPosition - arrowWidth / 2, arrowHeight + CGRectGetMinX(rect))]; 76 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 77 | [bezierPath addLineToPoint:CGPointMake(arrowPosition, CGRectGetMinY(rect) + CGRectGetMinX(rect))]; 78 | [bezierPath addLineToPoint:CGPointMake(arrowPosition + arrowWidth / 2, arrowHeight + CGRectGetMinX(rect))]; 79 | } else { 80 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition, CGRectGetMinY(rect) + CGRectGetMinX(rect)) 81 | controlPoint1:CGPointMake(arrowPosition - arrowWidth / 2 + arrowWidth / 4, arrowHeight + CGRectGetMinX(rect)) 82 | controlPoint2:CGPointMake(arrowPosition - arrowWidth / 8, CGRectGetMinY(rect) + CGRectGetMinX(rect))]; 83 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition + arrowWidth / 2, arrowHeight + CGRectGetMinX(rect)) 84 | controlPoint1:CGPointMake(arrowPosition + arrowWidth / 8, CGRectGetMinY(rect) + CGRectGetMinX(rect)) 85 | controlPoint2:CGPointMake(arrowPosition + arrowWidth / 2 - arrowWidth / 4, arrowHeight + CGRectGetMinX(rect))]; 86 | } 87 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, arrowHeight + CGRectGetMinX(rect))]; 88 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 89 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 90 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 91 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 92 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 93 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 94 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 95 | 96 | }else if (arrowDirection == YBPopupMenuArrowDirectionBottom) { 97 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect),topLeftRadius + CGRectGetMinX(rect)); 98 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 99 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect) - arrowHeight); 100 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect) - arrowHeight); 101 | if (arrowPosition < bottomLeftRadius + arrowWidth / 2) { 102 | arrowPosition = bottomLeftRadius + arrowWidth / 2; 103 | }else if (arrowPosition > CGRectGetWidth(rect) - bottomRightRadius - arrowWidth / 2) { 104 | arrowPosition = CGRectGetWidth(rect) - bottomRightRadius - arrowWidth / 2; 105 | } 106 | [bezierPath moveToPoint:CGPointMake(arrowPosition + arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 107 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 108 | [bezierPath addLineToPoint:CGPointMake(arrowPosition, CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 109 | [bezierPath addLineToPoint:CGPointMake(arrowPosition - arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 110 | } else { 111 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition, CGRectGetHeight(rect) + CGRectGetMinX(rect)) 112 | controlPoint1:CGPointMake(arrowPosition + arrowWidth / 2 - arrowWidth / 4, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect)) 113 | controlPoint2:CGPointMake(arrowPosition + arrowWidth / 8, CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 114 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition - arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect)) 115 | controlPoint1:CGPointMake(arrowPosition - arrowWidth / 8, CGRectGetHeight(rect) + CGRectGetMinX(rect)) 116 | controlPoint2:CGPointMake(arrowPosition - arrowWidth / 2 + arrowWidth / 4, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 117 | } 118 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 119 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 120 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect))]; 121 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 122 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), CGRectGetMinX(rect))]; 123 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 124 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect) - arrowHeight)]; 125 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 126 | 127 | }else if (arrowDirection == YBPopupMenuArrowDirectionLeft) { 128 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect) + arrowHeight,topLeftRadius + CGRectGetMinX(rect)); 129 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 130 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect) + arrowHeight, CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 131 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 132 | if (arrowPosition < topLeftRadius + arrowWidth / 2) { 133 | arrowPosition = topLeftRadius + arrowWidth / 2; 134 | }else if (arrowPosition > CGRectGetHeight(rect) - bottomLeftRadius - arrowWidth / 2) { 135 | arrowPosition = CGRectGetHeight(rect) - bottomLeftRadius - arrowWidth / 2; 136 | } 137 | [bezierPath moveToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2)]; 138 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 139 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowPosition)]; 140 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2)]; 141 | } else { 142 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetMinX(rect), arrowPosition) 143 | controlPoint1:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2 - arrowWidth / 4) 144 | controlPoint2:CGPointMake(CGRectGetMinX(rect), arrowPosition + arrowWidth / 8)]; 145 | [bezierPath addCurveToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2) 146 | controlPoint1:CGPointMake(CGRectGetMinX(rect), arrowPosition - arrowWidth / 8) 147 | controlPoint2:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2 + arrowWidth / 4)]; 148 | } 149 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect))]; 150 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 151 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, CGRectGetMinX(rect))]; 152 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 153 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 154 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 155 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 156 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 157 | 158 | }else if (arrowDirection == YBPopupMenuArrowDirectionRight) { 159 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect),topLeftRadius + CGRectGetMinX(rect)); 160 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect) - arrowHeight, topRightRadius + CGRectGetMinX(rect)); 161 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 162 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect) - arrowHeight, CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 163 | if (arrowPosition < topRightRadius + arrowWidth / 2) { 164 | arrowPosition = topRightRadius + arrowWidth / 2; 165 | }else if (arrowPosition > CGRectGetHeight(rect) - bottomRightRadius - arrowWidth / 2) { 166 | arrowPosition = CGRectGetHeight(rect) - bottomRightRadius - arrowWidth / 2; 167 | } 168 | [bezierPath moveToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2)]; 169 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 170 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition)]; 171 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2)]; 172 | } else { 173 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition) 174 | controlPoint1:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2 + arrowWidth / 4) 175 | controlPoint2:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition - arrowWidth / 8)]; 176 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2) 177 | controlPoint1:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition + arrowWidth / 8) 178 | controlPoint2:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2 - arrowWidth / 4)]; 179 | } 180 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 181 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 182 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 183 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 184 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 185 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 186 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect) - arrowHeight, CGRectGetMinX(rect))]; 187 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 188 | 189 | }else if (arrowDirection == YBPopupMenuArrowDirectionNone) { 190 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect)); 191 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 192 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 193 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 194 | [bezierPath moveToPoint:CGPointMake(topLeftRadius + CGRectGetMinX(rect), CGRectGetMinX(rect))]; 195 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, CGRectGetMinX(rect))]; 196 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 197 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 198 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 199 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 200 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 201 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 202 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 203 | } 204 | 205 | [bezierPath closePath]; 206 | return bezierPath; 207 | } 208 | 209 | @end 210 | -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0C660A231FEA39E7000BD9A5 /* CustomTestCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C660A211FEA39E7000BD9A5 /* CustomTestCell.m */; }; 11 | 0C660A241FEA39E7000BD9A5 /* CustomTestCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C660A221FEA39E7000BD9A5 /* CustomTestCell.xib */; }; 12 | 0C8A9ACF1EC555BE002F22E1 /* YBPopupMenuPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8A9ACC1EC555BE002F22E1 /* YBPopupMenuPath.m */; }; 13 | 0C8E6EC21DD1A893009EEF02 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8E6EC11DD1A893009EEF02 /* main.m */; }; 14 | 0C8E6EC51DD1A893009EEF02 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8E6EC41DD1A893009EEF02 /* AppDelegate.m */; }; 15 | 0C8E6EC81DD1A893009EEF02 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8E6EC71DD1A893009EEF02 /* ViewController.m */; }; 16 | 0C8E6ECB1DD1A893009EEF02 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C8E6EC91DD1A893009EEF02 /* Main.storyboard */; }; 17 | 0C8E6ECD1DD1A893009EEF02 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0C8E6ECC1DD1A893009EEF02 /* Assets.xcassets */; }; 18 | 0C8E6ED01DD1A893009EEF02 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C8E6ECE1DD1A893009EEF02 /* LaunchScreen.storyboard */; }; 19 | 0C8E6EDB1DD1A893009EEF02 /* YBPopupMenuDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8E6EDA1DD1A893009EEF02 /* YBPopupMenuDemoUITests.m */; }; 20 | 0C8E6EE81DD1A8CF009EEF02 /* YBPopupMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C8E6EE71DD1A8CF009EEF02 /* YBPopupMenu.m */; }; 21 | 4A07B8382A8CBF1B00B0AF85 /* YBPopupMenuContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A07B8372A8CBF1B00B0AF85 /* YBPopupMenuContainerView.m */; }; 22 | 4AD7633C23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD7633B23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.m */; }; 23 | 4AD7634223D46E77006AEBE5 /* YBPopupMenuAnimationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD7634123D46E77006AEBE5 /* YBPopupMenuAnimationManager.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 0C8E6ED71DD1A893009EEF02 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 0C8E6EB51DD1A893009EEF02 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 0C8E6EBC1DD1A893009EEF02; 32 | remoteInfo = YBPopupMenuDemo; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 0C660A201FEA39E7000BD9A5 /* CustomTestCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomTestCell.h; sourceTree = ""; }; 38 | 0C660A211FEA39E7000BD9A5 /* CustomTestCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomTestCell.m; sourceTree = ""; }; 39 | 0C660A221FEA39E7000BD9A5 /* CustomTestCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomTestCell.xib; sourceTree = ""; }; 40 | 0C8A9ACB1EC555BE002F22E1 /* YBPopupMenuPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YBPopupMenuPath.h; sourceTree = ""; }; 41 | 0C8A9ACC1EC555BE002F22E1 /* YBPopupMenuPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenuPath.m; sourceTree = ""; }; 42 | 0C8E6EBD1DD1A893009EEF02 /* YBPopupMenuDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YBPopupMenuDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 0C8E6EC11DD1A893009EEF02 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 44 | 0C8E6EC31DD1A893009EEF02 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 45 | 0C8E6EC41DD1A893009EEF02 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 46 | 0C8E6EC61DD1A893009EEF02 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 47 | 0C8E6EC71DD1A893009EEF02 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 48 | 0C8E6ECA1DD1A893009EEF02 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 0C8E6ECC1DD1A893009EEF02 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 0C8E6ECF1DD1A893009EEF02 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 0C8E6ED11DD1A893009EEF02 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 0C8E6ED61DD1A893009EEF02 /* YBPopupMenuDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YBPopupMenuDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 0C8E6EDA1DD1A893009EEF02 /* YBPopupMenuDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenuDemoUITests.m; sourceTree = ""; }; 54 | 0C8E6EDC1DD1A893009EEF02 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 0C8E6EE61DD1A8CF009EEF02 /* YBPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YBPopupMenu.h; sourceTree = ""; }; 56 | 0C8E6EE71DD1A8CF009EEF02 /* YBPopupMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenu.m; sourceTree = ""; }; 57 | 4A07B8362A8CBF1B00B0AF85 /* YBPopupMenuContainerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YBPopupMenuContainerView.h; sourceTree = ""; }; 58 | 4A07B8372A8CBF1B00B0AF85 /* YBPopupMenuContainerView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenuContainerView.m; sourceTree = ""; }; 59 | 4AD7633A23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YBPopupMenuDeviceOrientationManager.h; sourceTree = ""; }; 60 | 4AD7633B23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenuDeviceOrientationManager.m; sourceTree = ""; }; 61 | 4AD7634023D46E77006AEBE5 /* YBPopupMenuAnimationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YBPopupMenuAnimationManager.h; sourceTree = ""; }; 62 | 4AD7634123D46E77006AEBE5 /* YBPopupMenuAnimationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YBPopupMenuAnimationManager.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 0C8E6EBA1DD1A893009EEF02 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | 0C8E6ED31DD1A893009EEF02 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 0C8E6EB41DD1A893009EEF02 = { 84 | isa = PBXGroup; 85 | children = ( 86 | 0C8E6EBF1DD1A893009EEF02 /* YBPopupMenuDemo */, 87 | 0C8E6ED91DD1A893009EEF02 /* YBPopupMenuDemoUITests */, 88 | 0C8E6EBE1DD1A893009EEF02 /* Products */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | 0C8E6EBE1DD1A893009EEF02 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 0C8E6EBD1DD1A893009EEF02 /* YBPopupMenuDemo.app */, 96 | 0C8E6ED61DD1A893009EEF02 /* YBPopupMenuDemoUITests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 0C8E6EBF1DD1A893009EEF02 /* YBPopupMenuDemo */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 0C8E6EE51DD1A8C7009EEF02 /* YBPopupMenu */, 105 | 0C8E6EC31DD1A893009EEF02 /* AppDelegate.h */, 106 | 0C8E6EC41DD1A893009EEF02 /* AppDelegate.m */, 107 | 0C8E6EC61DD1A893009EEF02 /* ViewController.h */, 108 | 0C8E6EC71DD1A893009EEF02 /* ViewController.m */, 109 | 0C660A201FEA39E7000BD9A5 /* CustomTestCell.h */, 110 | 0C660A211FEA39E7000BD9A5 /* CustomTestCell.m */, 111 | 0C660A221FEA39E7000BD9A5 /* CustomTestCell.xib */, 112 | 0C8E6EC91DD1A893009EEF02 /* Main.storyboard */, 113 | 0C8E6ECC1DD1A893009EEF02 /* Assets.xcassets */, 114 | 0C8E6ECE1DD1A893009EEF02 /* LaunchScreen.storyboard */, 115 | 0C8E6ED11DD1A893009EEF02 /* Info.plist */, 116 | 0C8E6EC01DD1A893009EEF02 /* Supporting Files */, 117 | ); 118 | path = YBPopupMenuDemo; 119 | sourceTree = ""; 120 | }; 121 | 0C8E6EC01DD1A893009EEF02 /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 0C8E6EC11DD1A893009EEF02 /* main.m */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | 0C8E6ED91DD1A893009EEF02 /* YBPopupMenuDemoUITests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 0C8E6EDA1DD1A893009EEF02 /* YBPopupMenuDemoUITests.m */, 133 | 0C8E6EDC1DD1A893009EEF02 /* Info.plist */, 134 | ); 135 | path = YBPopupMenuDemoUITests; 136 | sourceTree = ""; 137 | }; 138 | 0C8E6EE51DD1A8C7009EEF02 /* YBPopupMenu */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 0C8E6EE61DD1A8CF009EEF02 /* YBPopupMenu.h */, 142 | 0C8E6EE71DD1A8CF009EEF02 /* YBPopupMenu.m */, 143 | 4AD7634023D46E77006AEBE5 /* YBPopupMenuAnimationManager.h */, 144 | 4AD7634123D46E77006AEBE5 /* YBPopupMenuAnimationManager.m */, 145 | 4A07B8362A8CBF1B00B0AF85 /* YBPopupMenuContainerView.h */, 146 | 4A07B8372A8CBF1B00B0AF85 /* YBPopupMenuContainerView.m */, 147 | 4AD7633A23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.h */, 148 | 4AD7633B23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.m */, 149 | 0C8A9ACB1EC555BE002F22E1 /* YBPopupMenuPath.h */, 150 | 0C8A9ACC1EC555BE002F22E1 /* YBPopupMenuPath.m */, 151 | ); 152 | path = YBPopupMenu; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXNativeTarget section */ 158 | 0C8E6EBC1DD1A893009EEF02 /* YBPopupMenuDemo */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = 0C8E6EDF1DD1A893009EEF02 /* Build configuration list for PBXNativeTarget "YBPopupMenuDemo" */; 161 | buildPhases = ( 162 | 0C8E6EB91DD1A893009EEF02 /* Sources */, 163 | 0C8E6EBA1DD1A893009EEF02 /* Frameworks */, 164 | 0C8E6EBB1DD1A893009EEF02 /* Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | ); 170 | name = YBPopupMenuDemo; 171 | productName = YBPopupMenuDemo; 172 | productReference = 0C8E6EBD1DD1A893009EEF02 /* YBPopupMenuDemo.app */; 173 | productType = "com.apple.product-type.application"; 174 | }; 175 | 0C8E6ED51DD1A893009EEF02 /* YBPopupMenuDemoUITests */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = 0C8E6EE21DD1A893009EEF02 /* Build configuration list for PBXNativeTarget "YBPopupMenuDemoUITests" */; 178 | buildPhases = ( 179 | 0C8E6ED21DD1A893009EEF02 /* Sources */, 180 | 0C8E6ED31DD1A893009EEF02 /* Frameworks */, 181 | 0C8E6ED41DD1A893009EEF02 /* Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | 0C8E6ED81DD1A893009EEF02 /* PBXTargetDependency */, 187 | ); 188 | name = YBPopupMenuDemoUITests; 189 | productName = YBPopupMenuDemoUITests; 190 | productReference = 0C8E6ED61DD1A893009EEF02 /* YBPopupMenuDemoUITests.xctest */; 191 | productType = "com.apple.product-type.bundle.ui-testing"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | 0C8E6EB51DD1A893009EEF02 /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastUpgradeCheck = 0800; 200 | ORGANIZATIONNAME = LYB; 201 | TargetAttributes = { 202 | 0C8E6EBC1DD1A893009EEF02 = { 203 | CreatedOnToolsVersion = 8.0; 204 | DevelopmentTeam = 8CSXJZUPG2; 205 | ProvisioningStyle = Automatic; 206 | }; 207 | 0C8E6ED51DD1A893009EEF02 = { 208 | CreatedOnToolsVersion = 8.0; 209 | DevelopmentTeam = 76NVUL9XC6; 210 | ProvisioningStyle = Automatic; 211 | TestTargetID = 0C8E6EBC1DD1A893009EEF02; 212 | }; 213 | }; 214 | }; 215 | buildConfigurationList = 0C8E6EB81DD1A893009EEF02 /* Build configuration list for PBXProject "YBPopupMenuDemo" */; 216 | compatibilityVersion = "Xcode 3.2"; 217 | developmentRegion = English; 218 | hasScannedForEncodings = 0; 219 | knownRegions = ( 220 | English, 221 | en, 222 | Base, 223 | ); 224 | mainGroup = 0C8E6EB41DD1A893009EEF02; 225 | productRefGroup = 0C8E6EBE1DD1A893009EEF02 /* Products */; 226 | projectDirPath = ""; 227 | projectRoot = ""; 228 | targets = ( 229 | 0C8E6EBC1DD1A893009EEF02 /* YBPopupMenuDemo */, 230 | 0C8E6ED51DD1A893009EEF02 /* YBPopupMenuDemoUITests */, 231 | ); 232 | }; 233 | /* End PBXProject section */ 234 | 235 | /* Begin PBXResourcesBuildPhase section */ 236 | 0C8E6EBB1DD1A893009EEF02 /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 0C8E6ED01DD1A893009EEF02 /* LaunchScreen.storyboard in Resources */, 241 | 0C8E6ECD1DD1A893009EEF02 /* Assets.xcassets in Resources */, 242 | 0C660A241FEA39E7000BD9A5 /* CustomTestCell.xib in Resources */, 243 | 0C8E6ECB1DD1A893009EEF02 /* Main.storyboard in Resources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | 0C8E6ED41DD1A893009EEF02 /* Resources */ = { 248 | isa = PBXResourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | /* End PBXResourcesBuildPhase section */ 255 | 256 | /* Begin PBXSourcesBuildPhase section */ 257 | 0C8E6EB91DD1A893009EEF02 /* Sources */ = { 258 | isa = PBXSourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 0C8E6EE81DD1A8CF009EEF02 /* YBPopupMenu.m in Sources */, 262 | 0C8A9ACF1EC555BE002F22E1 /* YBPopupMenuPath.m in Sources */, 263 | 0C660A231FEA39E7000BD9A5 /* CustomTestCell.m in Sources */, 264 | 4A07B8382A8CBF1B00B0AF85 /* YBPopupMenuContainerView.m in Sources */, 265 | 4AD7633C23D42170006AEBE5 /* YBPopupMenuDeviceOrientationManager.m in Sources */, 266 | 4AD7634223D46E77006AEBE5 /* YBPopupMenuAnimationManager.m in Sources */, 267 | 0C8E6EC81DD1A893009EEF02 /* ViewController.m in Sources */, 268 | 0C8E6EC51DD1A893009EEF02 /* AppDelegate.m in Sources */, 269 | 0C8E6EC21DD1A893009EEF02 /* main.m in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | 0C8E6ED21DD1A893009EEF02 /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 0C8E6EDB1DD1A893009EEF02 /* YBPopupMenuDemoUITests.m in Sources */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXSourcesBuildPhase section */ 282 | 283 | /* Begin PBXTargetDependency section */ 284 | 0C8E6ED81DD1A893009EEF02 /* PBXTargetDependency */ = { 285 | isa = PBXTargetDependency; 286 | target = 0C8E6EBC1DD1A893009EEF02 /* YBPopupMenuDemo */; 287 | targetProxy = 0C8E6ED71DD1A893009EEF02 /* PBXContainerItemProxy */; 288 | }; 289 | /* End PBXTargetDependency section */ 290 | 291 | /* Begin PBXVariantGroup section */ 292 | 0C8E6EC91DD1A893009EEF02 /* Main.storyboard */ = { 293 | isa = PBXVariantGroup; 294 | children = ( 295 | 0C8E6ECA1DD1A893009EEF02 /* Base */, 296 | ); 297 | name = Main.storyboard; 298 | sourceTree = ""; 299 | }; 300 | 0C8E6ECE1DD1A893009EEF02 /* LaunchScreen.storyboard */ = { 301 | isa = PBXVariantGroup; 302 | children = ( 303 | 0C8E6ECF1DD1A893009EEF02 /* Base */, 304 | ); 305 | name = LaunchScreen.storyboard; 306 | sourceTree = ""; 307 | }; 308 | /* End PBXVariantGroup section */ 309 | 310 | /* Begin XCBuildConfiguration section */ 311 | 0C8E6EDD1DD1A893009EEF02 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_ANALYZER_NONNULL = YES; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 323 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 330 | CLANG_WARN_UNREACHABLE_CODE = YES; 331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 333 | COPY_PHASE_STRIP = NO; 334 | DEBUG_INFORMATION_FORMAT = dwarf; 335 | ENABLE_STRICT_OBJC_MSGSEND = YES; 336 | ENABLE_TESTABILITY = YES; 337 | GCC_C_LANGUAGE_STANDARD = gnu99; 338 | GCC_DYNAMIC_NO_PIC = NO; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_OPTIMIZATION_LEVEL = 0; 341 | GCC_PREPROCESSOR_DEFINITIONS = ( 342 | "DEBUG=1", 343 | "$(inherited)", 344 | ); 345 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 347 | GCC_WARN_UNDECLARED_SELECTOR = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 349 | GCC_WARN_UNUSED_FUNCTION = YES; 350 | GCC_WARN_UNUSED_VARIABLE = YES; 351 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 352 | MTL_ENABLE_DEBUG_INFO = YES; 353 | ONLY_ACTIVE_ARCH = YES; 354 | SDKROOT = iphoneos; 355 | }; 356 | name = Debug; 357 | }; 358 | 0C8E6EDE1DD1A893009EEF02 /* Release */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ALWAYS_SEARCH_USER_PATHS = NO; 362 | CLANG_ANALYZER_NONNULL = YES; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_CONSTANT_CONVERSION = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 376 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 380 | COPY_PHASE_STRIP = NO; 381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 382 | ENABLE_NS_ASSERTIONS = NO; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu99; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 387 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 388 | GCC_WARN_UNDECLARED_SELECTOR = YES; 389 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 390 | GCC_WARN_UNUSED_FUNCTION = YES; 391 | GCC_WARN_UNUSED_VARIABLE = YES; 392 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 393 | MTL_ENABLE_DEBUG_INFO = NO; 394 | SDKROOT = iphoneos; 395 | VALIDATE_PRODUCT = YES; 396 | }; 397 | name = Release; 398 | }; 399 | 0C8E6EE01DD1A893009EEF02 /* Debug */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | DEVELOPMENT_TEAM = 8CSXJZUPG2; 404 | INFOPLIST_FILE = YBPopupMenuDemo/Info.plist; 405 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 407 | PRODUCT_BUNDLE_IDENTIFIER = com.codelee.YBPopupMenuDemo; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | }; 410 | name = Debug; 411 | }; 412 | 0C8E6EE11DD1A893009EEF02 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | DEVELOPMENT_TEAM = 8CSXJZUPG2; 417 | INFOPLIST_FILE = YBPopupMenuDemo/Info.plist; 418 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 419 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.codelee.YBPopupMenuDemo; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | }; 423 | name = Release; 424 | }; 425 | 0C8E6EE31DD1A893009EEF02 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | DEVELOPMENT_TEAM = 76NVUL9XC6; 429 | INFOPLIST_FILE = YBPopupMenuDemoUITests/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = com.Lee.YBPopupMenuDemoUITests; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | TEST_TARGET_NAME = YBPopupMenuDemo; 434 | }; 435 | name = Debug; 436 | }; 437 | 0C8E6EE41DD1A893009EEF02 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | DEVELOPMENT_TEAM = 76NVUL9XC6; 441 | INFOPLIST_FILE = YBPopupMenuDemoUITests/Info.plist; 442 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 443 | PRODUCT_BUNDLE_IDENTIFIER = com.Lee.YBPopupMenuDemoUITests; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | TEST_TARGET_NAME = YBPopupMenuDemo; 446 | }; 447 | name = Release; 448 | }; 449 | /* End XCBuildConfiguration section */ 450 | 451 | /* Begin XCConfigurationList section */ 452 | 0C8E6EB81DD1A893009EEF02 /* Build configuration list for PBXProject "YBPopupMenuDemo" */ = { 453 | isa = XCConfigurationList; 454 | buildConfigurations = ( 455 | 0C8E6EDD1DD1A893009EEF02 /* Debug */, 456 | 0C8E6EDE1DD1A893009EEF02 /* Release */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 0C8E6EDF1DD1A893009EEF02 /* Build configuration list for PBXNativeTarget "YBPopupMenuDemo" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 0C8E6EE01DD1A893009EEF02 /* Debug */, 465 | 0C8E6EE11DD1A893009EEF02 /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 0C8E6EE21DD1A893009EEF02 /* Build configuration list for PBXNativeTarget "YBPopupMenuDemoUITests" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 0C8E6EE31DD1A893009EEF02 /* Debug */, 474 | 0C8E6EE41DD1A893009EEF02 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | /* End XCConfigurationList section */ 480 | }; 481 | rootObject = 0C8E6EB51DD1A893009EEF02 /* Project object */; 482 | } 483 | -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/project.xcworkspace/xcuserdata/LYB.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuDemo.xcodeproj/project.xcworkspace/xcuserdata/LYB.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/xcuserdata/LYB.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/xcuserdata/LYB.xcuserdatad/xcschemes/YBPopupMenuDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /YBPopupMenuDemo.xcodeproj/xcuserdata/LYB.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YBPopupMenuDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 0C8E6EBC1DD1A893009EEF02 16 | 17 | primary 18 | 19 | 20 | 0C8E6ED51DD1A893009EEF02 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "delete-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/delete.imageset/delete-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuDemo/Assets.xcassets/delete.imageset/delete-1.png -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/motify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "motify-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/motify.imageset/motify-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuDemo/Assets.xcassets/motify.imageset/motify-1.png -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/pay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "pay-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/pay.imageset/pay-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuDemo/Assets.xcassets/pay.imageset/pay-1.png -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/saoyisao.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "saoyisao-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YBPopupMenuDemo/Assets.xcassets/saoyisao.imageset/saoyisao-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuDemo/Assets.xcassets/saoyisao.imageset/saoyisao-1.png -------------------------------------------------------------------------------- /YBPopupMenuDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/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 | 33 | 43 | 53 | 63 | 64 | 65 | 66 | 80 | 94 | 108 | 114 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/CustomTestCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTestCell.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by lyb on 2017/12/20. 6 | // Copyright © 2017年 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CustomTestCell : UITableViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UIImageView *iconImageView; 14 | @property (weak, nonatomic) IBOutlet UIView *badge; 15 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 16 | @property (weak, nonatomic) IBOutlet UILabel *statusLabel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/CustomTestCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTestCell.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by lyb on 2017/12/20. 6 | // Copyright © 2017年 LYB. All rights reserved. 7 | // 8 | 9 | #import "CustomTestCell.h" 10 | 11 | @implementation CustomTestCell 12 | 13 | - (void)awakeFromNib { 14 | [super awakeFromNib]; 15 | 16 | } 17 | 18 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 19 | [super setSelected:selected animated:animated]; 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/CustomTestCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 37 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "YBPopupMenu.h" 11 | #import "CustomTestCell.h" 12 | 13 | #define TITLES @[@"修改", @"删除", @"扫一扫",@"付款"] 14 | #define ICONS @[@"motify",@"delete",@"saoyisao",@"pay"] 15 | @interface ViewController () 16 | @property (weak, nonatomic) IBOutlet UITextField *textField; 17 | @property (weak, nonatomic) IBOutlet UILabel *customCellView; 18 | 19 | @property (nonatomic, strong) YBPopupMenu *popupMenu; 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | 27 | } 28 | 29 | - (IBAction)onPopupClick:(UIButton *)sender { 30 | [YBPopupMenu showRelyOnView:sender titles:TITLES icons:ICONS menuWidth:120 otherSettings:^(YBPopupMenu *popupMenu) { 31 | popupMenu.delegate = self; 32 | }]; 33 | } 34 | 35 | - (IBAction)onTestClick:(UIButton *)sender { 36 | [YBPopupMenu showRelyOnView:sender titles:@[@"111",@"222",@"333",@"444",@"555",@"666",@"777",@"888"] icons:nil menuWidth:100 otherSettings:^(YBPopupMenu *popupMenu) { 37 | popupMenu.priorityDirection = sender.tag - 100; 38 | popupMenu.borderWidth = 1; 39 | popupMenu.borderColor = [[UIColor redColor] colorWithAlphaComponent:0.7]; 40 | popupMenu.arrowPosition = 22; 41 | // 要显示高斯模糊效果,backColor 要设置透明色或者带alpha的颜色 42 | popupMenu.backColor = [UIColor clearColor]; 43 | }]; 44 | //test dismiss all popupMenus 45 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 46 | // [YBPopupMenu dismissAllPopupMenu]; 47 | // }); 48 | } 49 | 50 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 51 | { 52 | UITouch *t = touches.anyObject; 53 | CGPoint p = [t locationInView: self.view]; 54 | 55 | if (CGRectContainsPoint(self.customCellView.frame, p)) { 56 | [self showCustomPopupMenuWithPoint:p]; 57 | }else { 58 | [self showDarkPopupMenuWithPoint:p]; 59 | } 60 | } 61 | 62 | - (void)showDarkPopupMenuWithPoint:(CGPoint)point 63 | { 64 | [YBPopupMenu showAtPoint:point titles:TITLES icons:nil menuWidth:110 otherSettings:^(YBPopupMenu *popupMenu) { 65 | popupMenu.dismissOnSelected = NO; 66 | popupMenu.showShadow = YES; 67 | popupMenu.delegate = self; 68 | popupMenu.offset = 10; 69 | popupMenu.type = YBPopupMenuTypeDark; 70 | popupMenu.arrowStyle = YBPopupMenuArrowStyleStraight; 71 | popupMenu.arrowWidth = 15; 72 | popupMenu.arrowHeight = 10; 73 | popupMenu.backColor = [UIColor clearColor]; 74 | popupMenu.effectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 75 | popupMenu.animationManager.style = YBPopupMenuAnimationStyleNone; 76 | popupMenu.rectCorner = UIRectCornerBottomLeft | UIRectCornerBottomRight; 77 | }]; 78 | } 79 | 80 | - (void)showCustomPopupMenuWithPoint:(CGPoint)point 81 | { 82 | [YBPopupMenu showAtPoint:point titles:TITLES icons:nil menuWidth:110 otherSettings:^(YBPopupMenu *popupMenu) { 83 | popupMenu.dismissOnSelected = YES; 84 | popupMenu.showShadow = YES; 85 | popupMenu.delegate = self; 86 | popupMenu.type = YBPopupMenuTypeDefault; 87 | popupMenu.cornerRadius = 8; 88 | popupMenu.rectCorner = UIRectCornerTopLeft| UIRectCornerTopRight; 89 | popupMenu.backColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; 90 | popupMenu.tag = 100; 91 | //如果不加这句默认是 UITableViewCellSeparatorStyleNone 的 92 | popupMenu.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 93 | 94 | }]; 95 | } 96 | 97 | #pragma mark - YBPopupMenuDelegate 98 | - (void)ybPopupMenu:(YBPopupMenu *)ybPopupMenu didSelectedAtIndex:(NSInteger)index 99 | { 100 | //推荐回调 101 | NSLog(@"点击了 %@ 选项",ybPopupMenu.titles[index]); 102 | } 103 | 104 | - (void)ybPopupMenuBeganDismiss:(YBPopupMenu *)ybPopupMenu 105 | { 106 | if (self.textField.isFirstResponder) { 107 | [self.textField resignFirstResponder]; 108 | } 109 | } 110 | 111 | - (UITableViewCell *)ybPopupMenu:(YBPopupMenu *)ybPopupMenu cellForRowAtIndex:(NSInteger)index 112 | { 113 | if (ybPopupMenu.tag != 100) { 114 | return nil; 115 | } 116 | static NSString * identifier = @"customCell"; 117 | CustomTestCell * cell = [ybPopupMenu.tableView dequeueReusableCellWithIdentifier:identifier]; 118 | if (!cell) { 119 | cell = [[[NSBundle mainBundle] loadNibNamed:@"CustomTestCell" owner:self options:nil] firstObject]; 120 | } 121 | 122 | cell.titleLabel.text = TITLES[index]; 123 | cell.iconImageView.image = [UIImage imageNamed:ICONS[index]]; 124 | 125 | switch (index) { 126 | case 0: 127 | cell.statusLabel.hidden = NO; 128 | cell.badge.hidden = YES; 129 | break; 130 | case 2: 131 | cell.statusLabel.hidden = YES; 132 | cell.badge.hidden = NO; 133 | break; 134 | default: 135 | cell.statusLabel.hidden = YES; 136 | cell.badge.hidden = YES; 137 | break; 138 | } 139 | 140 | return cell; 141 | } 142 | 143 | #pragma mark - UITextFieldDelegate 144 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField 145 | { 146 | _popupMenu = [YBPopupMenu showRelyOnView:textField titles:@[@"密码必须为数字、大写字母、小写字母和特殊字符中至少三种的组合,长度不少于8且不大于20"] icons:nil menuWidth:textField.bounds.size.width otherSettings:^(YBPopupMenu *popupMenu) { 147 | popupMenu.delegate = self; 148 | popupMenu.showMaskView = NO; 149 | popupMenu.priorityDirection = YBPopupMenuPriorityDirectionBottom; 150 | popupMenu.maxVisibleCount = 1; 151 | popupMenu.itemHeight = 60; 152 | popupMenu.borderWidth = 1; 153 | popupMenu.fontSize = 12; 154 | popupMenu.dismissOnTouchOutside = YES; 155 | popupMenu.dismissOnSelected = NO; 156 | popupMenu.borderColor = [UIColor brownColor]; 157 | popupMenu.textColor = [UIColor brownColor]; 158 | popupMenu.animationManager.style = YBPopupMenuAnimationStyleNone; 159 | popupMenu.animationManager.duration = 0.25; 160 | }]; 161 | return YES; 162 | } 163 | 164 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 165 | { 166 | [_popupMenu dismiss]; 167 | return YES; 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenu.h 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/10. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YBPopupMenuPath.h" 11 | #import "YBPopupMenuDeviceOrientationManager.h" 12 | #import "YBPopupMenuAnimationManager.h" 13 | #import "YBPopupMenuContainerView.h" 14 | 15 | // 过期提醒 16 | #define YBDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 17 | 18 | typedef NS_ENUM(NSInteger , YBPopupMenuType) { 19 | YBPopupMenuTypeDefault = 0, 20 | YBPopupMenuTypeDark 21 | }; 22 | 23 | /** 24 | 箭头方向优先级 25 | 26 | 当控件超出屏幕时会自动调整成反方向 27 | */ 28 | typedef NS_ENUM(NSInteger , YBPopupMenuPriorityDirection) { 29 | YBPopupMenuPriorityDirectionTop = 0, //Default 30 | YBPopupMenuPriorityDirectionBottom, 31 | YBPopupMenuPriorityDirectionLeft, 32 | YBPopupMenuPriorityDirectionRight, 33 | YBPopupMenuPriorityDirectionNone //不自动调整 34 | }; 35 | 36 | @class YBPopupMenu; 37 | @protocol YBPopupMenuDelegate 38 | 39 | @optional 40 | 41 | - (void)ybPopupMenuBeganDismiss:(YBPopupMenu *)ybPopupMenu; 42 | - (void)ybPopupMenuDidDismiss:(YBPopupMenu *)ybPopupMenu; 43 | - (void)ybPopupMenuBeganShow:(YBPopupMenu *)ybPopupMenu; 44 | - (void)ybPopupMenuDidShow:(YBPopupMenu *)ybPopupMenu; 45 | 46 | /** 47 | 点击事件回调 48 | */ 49 | - (void)ybPopupMenu:(YBPopupMenu *)ybPopupMenu didSelectedAtIndex:(NSInteger)index; 50 | 51 | /** 52 | 自定义cell 53 | 54 | 可以自定义cell,设置后会忽略 fontSize textColor backColor type 属性 55 | cell 的高度是根据 itemHeight 的,直接设置无效 56 | 建议cell 背景色设置为透明色,不然切的圆角显示不出来 57 | */ 58 | - (UITableViewCell *)ybPopupMenu:(YBPopupMenu *)ybPopupMenu cellForRowAtIndex:(NSInteger)index; 59 | 60 | @end 61 | 62 | @interface YBPopupMenu : UIView 63 | 64 | /** 65 | 标题数组 只读属性 66 | */ 67 | @property (nonatomic, strong, readonly) NSArray * titles; 68 | 69 | /** 70 | 图片数组 只读属性 71 | */ 72 | @property (nonatomic, strong, readonly) NSArray * images; 73 | 74 | /** 75 | tableView Default separatorStyle is UITableViewCellSeparatorStyleNone 76 | */ 77 | @property (nonatomic, strong, readonly) UITableView * tableView; 78 | 79 | 80 | @property (nonatomic, strong, readonly) YBPopupMenuContainerView * containerView; 81 | 82 | /** 83 | 高斯模糊视图 backColor 有 alpha时生效 84 | 85 | exp: popupMenu.backColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; 86 | */ 87 | @property (nonatomic, strong, readonly) UIVisualEffectView * effectView; 88 | 89 | /** 90 | 圆角半径 Default is 5.0 91 | */ 92 | @property (nonatomic, assign) CGFloat cornerRadius; 93 | 94 | /** 95 | 自定义圆角 Default is UIRectCornerAllCorners 96 | 97 | 当自动调整方向时corner会自动转换至镜像方向 98 | */ 99 | @property (nonatomic, assign) UIRectCorner rectCorner; 100 | 101 | /** 102 | 是否显示阴影 Default is YES 103 | */ 104 | @property (nonatomic, assign , getter=isShowShadow) BOOL showShadow; 105 | 106 | /** 107 | 是否显示灰色覆盖层 Default is YES 108 | */ 109 | @property (nonatomic, assign) BOOL showMaskView; 110 | 111 | /** 112 | 选择菜单项后消失 Default is YES 113 | */ 114 | @property (nonatomic, assign) BOOL dismissOnSelected; 115 | 116 | /** 117 | 点击菜单外消失 Default is YES 118 | */ 119 | @property (nonatomic, assign) BOOL dismissOnTouchOutside; 120 | 121 | /** 122 | 设置字体大小 自定义cell时忽略 Default is 15 123 | */ 124 | @property (nonatomic, assign) CGFloat fontSize; 125 | 126 | /** 127 | 设置字体 设置时忽略fontSize Default is nil 128 | */ 129 | @property (nonatomic, strong) UIFont * font; 130 | 131 | /** 132 | 设置字体颜色 自定义cell时忽略 Default is [UIColor blackColor] 133 | */ 134 | @property (nonatomic, strong) UIColor * textColor; 135 | 136 | /** 137 | 设置偏移距离 (>= 0) Default is 0.0 138 | */ 139 | @property (nonatomic, assign) CGFloat offset; 140 | 141 | /** 142 | 边框宽度 Default is 0.0 143 | 144 | 设置边框需 > 0 145 | */ 146 | @property (nonatomic, assign) CGFloat borderWidth; 147 | 148 | /** 149 | 边框颜色 Default is LightGrayColor 150 | 151 | borderWidth <= 0 无效 152 | */ 153 | @property (nonatomic, strong) UIColor * borderColor; 154 | 155 | /** 156 | 箭头宽度 Default is 20 157 | */ 158 | @property (nonatomic, assign) CGFloat arrowWidth; 159 | 160 | /** 161 | 箭头高度 Default is 8 162 | */ 163 | @property (nonatomic, assign) CGFloat arrowHeight; 164 | 165 | /** 166 | 箭头位置 Default is center 167 | 168 | 只有箭头优先级是YBPopupMenuPriorityDirectionLeft/YBPopupMenuPriorityDirectionRight/YBPopupMenuPriorityDirectionNone时需要设置 169 | */ 170 | @property (nonatomic, assign) CGFloat arrowPosition; 171 | 172 | /** 173 | 箭头方向 Default is YBPopupMenuArrowDirectionTop 174 | */ 175 | @property (nonatomic, assign) YBPopupMenuArrowDirection arrowDirection; 176 | 177 | /** 178 | 箭头类型 Default is YBPopupMenuArrowStyleCurve 179 | */ 180 | @property (nonatomic, assign) YBPopupMenuArrowStyle arrowStyle; 181 | 182 | /** 183 | 箭头优先方向 Default is YBPopupMenuPriorityDirectionTop 184 | 185 | 当控件超出屏幕时会自动调整箭头位置 186 | */ 187 | @property (nonatomic, assign) YBPopupMenuPriorityDirection priorityDirection; 188 | 189 | /** 190 | 可见的最大行数 Default is 5; 191 | */ 192 | @property (nonatomic, assign) NSInteger maxVisibleCount; 193 | 194 | /** 195 | menu背景色 如果color有alpha就会显示effectView Default is WhiteColor 196 | */ 197 | @property (nonatomic, strong) UIColor * backColor; 198 | 199 | /** 200 | item的高度 Default is 44; 201 | */ 202 | @property (nonatomic, assign) CGFloat itemHeight; 203 | 204 | /** 205 | popupMenu距离最近的Screen的距离 Default is 10 206 | */ 207 | @property (nonatomic, assign) CGFloat minSpace; 208 | 209 | /** 210 | 设置显示模式 自定义cell时忽略 Default is YBPopupMenuTypeDefault 211 | */ 212 | @property (nonatomic, assign) YBPopupMenuType type; 213 | 214 | /** 215 | 屏幕旋转管理 216 | */ 217 | @property (nonatomic, strong) id orientationManager; 218 | 219 | /** 220 | 动画管理 221 | */ 222 | @property (nonatomic, strong) id animationManager; 223 | 224 | /** 225 | 代理 226 | */ 227 | @property (nonatomic, weak) id delegate; 228 | 229 | /** 230 | 在指定位置弹出 231 | 232 | @param point 弹出的位置 233 | @param titles 标题数组 数组里是NSString/NSAttributedString 234 | @param icons 图标数组 数组里是NSString/UIImage 235 | @param itemWidth 菜单宽度 236 | @param otherSetting 其他设置 237 | */ 238 | + (YBPopupMenu *)showAtPoint:(CGPoint)point 239 | titles:(NSArray *)titles 240 | icons:(NSArray *)icons 241 | menuWidth:(CGFloat)itemWidth 242 | otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting; 243 | 244 | /** 245 | 依赖指定view弹出 246 | 247 | @param titles 标题数组 数组里是NSString/NSAttributedString 248 | @param icons 图标数组 数组里是NSString/UIImage 249 | @param itemWidth 菜单宽度 250 | @param otherSetting 其他设置 251 | */ 252 | + (YBPopupMenu *)showRelyOnView:(UIView *)view 253 | titles:(NSArray *)titles 254 | icons:(NSArray *)icons 255 | menuWidth:(CGFloat)itemWidth 256 | otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting; 257 | 258 | /** 259 | 隐藏 260 | */ 261 | - (void)dismiss; 262 | 263 | /** 264 | 隐藏window上所有的popupMenu 265 | */ 266 | + (void)dismissAllPopupMenu; 267 | 268 | @end 269 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenu.m 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/10. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenu.h" 10 | #import "YBPopupMenuPath.h" 11 | 12 | #define YBScreenWidth [UIScreen mainScreen].bounds.size.width 13 | #define YBScreenHeight [UIScreen mainScreen].bounds.size.height 14 | #define YBMainWindow [UIApplication sharedApplication].keyWindow 15 | #define YB_SAFE_BLOCK(BlockName, ...) ({ !BlockName ? nil : BlockName(__VA_ARGS__); }) 16 | 17 | #pragma mark - ///////////// 18 | #pragma mark - private cell 19 | 20 | @interface YBPopupMenuCell : UITableViewCell 21 | @property (nonatomic, assign) BOOL isShowSeparator; 22 | @property (nonatomic, strong) UIColor * separatorColor; 23 | @end 24 | 25 | @implementation YBPopupMenuCell 26 | 27 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 28 | { 29 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 30 | if (self) { 31 | _isShowSeparator = YES; 32 | _separatorColor = [UIColor lightGrayColor]; 33 | [self setNeedsDisplay]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)setIsShowSeparator:(BOOL)isShowSeparator 39 | { 40 | _isShowSeparator = isShowSeparator; 41 | [self setNeedsDisplay]; 42 | } 43 | 44 | - (void)setSeparatorColor:(UIColor *)separatorColor 45 | { 46 | _separatorColor = separatorColor; 47 | [self setNeedsDisplay]; 48 | } 49 | 50 | - (void)layoutSubviews 51 | { 52 | [super layoutSubviews]; 53 | self.contentView.frame = self.bounds; 54 | self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 55 | } 56 | 57 | - (void)drawRect:(CGRect)rect 58 | { 59 | if (!_isShowSeparator) return; 60 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, rect.size.height - 0.5, rect.size.width, 0.5)]; 61 | [_separatorColor setFill]; 62 | [bezierPath fillWithBlendMode:kCGBlendModeNormal alpha:1]; 63 | [bezierPath closePath]; 64 | } 65 | 66 | @end 67 | 68 | 69 | 70 | @interface YBPopupMenu () 71 | < 72 | UITableViewDelegate, 73 | UITableViewDataSource 74 | > 75 | 76 | @property (nonatomic, strong) UIView * menuBackView; 77 | @property (nonatomic, strong) YBPopupMenuContainerView * containerView; 78 | @property (nonatomic, strong) UITableView * tableView; 79 | @property (nonatomic, strong) UIVisualEffectView * effectView; 80 | @property (nonatomic) CGRect relyRect; 81 | @property (nonatomic, assign) CGFloat itemWidth; 82 | @property (nonatomic) CGPoint point; 83 | @property (nonatomic, assign) BOOL isCornerChanged; 84 | @property (nonatomic, strong) UIColor * separatorColor; 85 | @property (nonatomic, assign) BOOL isChangeDirection; 86 | @property (nonatomic, strong) UIView * relyView; 87 | @end 88 | 89 | @implementation YBPopupMenu 90 | 91 | - (instancetype)init 92 | { 93 | self = [super init]; 94 | if (self) { 95 | [self setDefaultSettings]; 96 | } 97 | return self; 98 | } 99 | 100 | #pragma mark - publics 101 | + (YBPopupMenu *)showAtPoint:(CGPoint)point titles:(NSArray *)titles icons:(NSArray *)icons menuWidth:(CGFloat)itemWidth otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting 102 | { 103 | YBPopupMenu *popupMenu = [[YBPopupMenu alloc] init]; 104 | popupMenu.point = point; 105 | popupMenu.titles = titles; 106 | popupMenu.images = icons; 107 | popupMenu.itemWidth = itemWidth; 108 | YB_SAFE_BLOCK(otherSetting,popupMenu); 109 | [popupMenu show]; 110 | return popupMenu; 111 | } 112 | 113 | + (YBPopupMenu *)showRelyOnView:(UIView *)view titles:(NSArray *)titles icons:(NSArray *)icons menuWidth:(CGFloat)itemWidth otherSettings:(void (^) (YBPopupMenu * popupMenu))otherSetting 114 | { 115 | YBPopupMenu *popupMenu = [[YBPopupMenu alloc] init]; 116 | popupMenu.relyView = view; 117 | popupMenu.titles = titles; 118 | popupMenu.images = icons; 119 | popupMenu.itemWidth = itemWidth; 120 | YB_SAFE_BLOCK(otherSetting,popupMenu); 121 | [popupMenu show]; 122 | return popupMenu; 123 | } 124 | 125 | - (void)dismiss 126 | { 127 | [self.orientationManager endMonitorDeviceOrientation]; 128 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuBeganDismiss:)]) { 129 | [self.delegate ybPopupMenuBeganDismiss:self]; 130 | } 131 | __weak typeof(self) weakSelf = self; 132 | [self.animationManager displayDismissAnimationCompletion:^{ 133 | __strong typeof(weakSelf)self = weakSelf; 134 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuDidDismiss:)]) { 135 | [self.delegate ybPopupMenuDidDismiss:self]; 136 | } 137 | self.delegate = nil; 138 | [self removeFromSuperview]; 139 | [self.menuBackView removeFromSuperview]; 140 | }]; 141 | } 142 | 143 | + (void)dismissAllPopupMenu 144 | { 145 | for (UIView * subView in YBMainWindow.subviews) { 146 | if ([subView isKindOfClass:[YBPopupMenu class]]) { 147 | YBPopupMenu * popupMenu = (YBPopupMenu *)subView; 148 | [popupMenu dismiss]; 149 | } 150 | } 151 | } 152 | 153 | #pragma mark tableViewDelegate & dataSource 154 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 155 | { 156 | return _titles.count; 157 | } 158 | 159 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 160 | { 161 | UITableViewCell * tableViewCell = nil; 162 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenu:cellForRowAtIndex:)]) { 163 | tableViewCell = [self.delegate ybPopupMenu:self cellForRowAtIndex:indexPath.row]; 164 | } 165 | 166 | if (tableViewCell) { 167 | return tableViewCell; 168 | } 169 | 170 | static NSString * identifier = @"ybPopupMenu"; 171 | YBPopupMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 172 | if (!cell) { 173 | cell = [[YBPopupMenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 174 | cell.textLabel.numberOfLines = 0; 175 | } 176 | cell.backgroundColor = [UIColor clearColor]; 177 | cell.textLabel.textColor = _textColor; 178 | if (_font) { 179 | cell.textLabel.font = _font; 180 | }else { 181 | cell.textLabel.font = [UIFont systemFontOfSize:_fontSize]; 182 | } 183 | if ([_titles[indexPath.row] isKindOfClass:[NSAttributedString class]]) { 184 | cell.textLabel.attributedText = _titles[indexPath.row]; 185 | }else if ([_titles[indexPath.row] isKindOfClass:[NSString class]]) { 186 | cell.textLabel.text = _titles[indexPath.row]; 187 | }else { 188 | cell.textLabel.text = nil; 189 | } 190 | cell.separatorColor = _separatorColor; 191 | if (_images.count >= indexPath.row + 1) { 192 | if ([_images[indexPath.row] isKindOfClass:[NSString class]]) { 193 | cell.imageView.image = [UIImage imageNamed:_images[indexPath.row]]; 194 | }else if ([_images[indexPath.row] isKindOfClass:[UIImage class]]){ 195 | cell.imageView.image = _images[indexPath.row]; 196 | }else { 197 | cell.imageView.image = nil; 198 | } 199 | }else { 200 | cell.imageView.image = nil; 201 | } 202 | return cell; 203 | } 204 | 205 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 206 | { 207 | return _itemHeight; 208 | } 209 | 210 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 211 | { 212 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 213 | if (_dismissOnSelected) [self dismiss]; 214 | 215 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenu:didSelectedAtIndex:)]) { 216 | [self.delegate ybPopupMenu:self didSelectedAtIndex:indexPath.row]; 217 | } 218 | } 219 | 220 | #pragma mark - scrollViewDelegate 221 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 222 | { 223 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 224 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 225 | cell.isShowSeparator = YES; 226 | } 227 | } 228 | 229 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 230 | { 231 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 232 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 233 | cell.isShowSeparator = NO; 234 | } 235 | } 236 | 237 | - (YBPopupMenuCell *)getLastVisibleCell 238 | { 239 | NSArray *indexPaths = [self.tableView indexPathsForVisibleRows]; 240 | indexPaths = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath * _Nonnull obj1, NSIndexPath * _Nonnull obj2) { 241 | return obj1.row < obj2.row; 242 | }]; 243 | NSIndexPath *indexPath = indexPaths.firstObject; 244 | return [self.tableView cellForRowAtIndexPath:indexPath]; 245 | } 246 | 247 | #pragma mark - privates 248 | - (void)show 249 | { 250 | [self.orientationManager startMonitorDeviceOrientation]; 251 | [self updateUI]; 252 | [YBMainWindow addSubview:_menuBackView]; 253 | [YBMainWindow addSubview:self]; 254 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuBeganShow:)]) { 255 | [self.delegate ybPopupMenuBeganShow:self]; 256 | } 257 | if ([[self getLastVisibleCell] isKindOfClass:[YBPopupMenuCell class]]) { 258 | YBPopupMenuCell *cell = [self getLastVisibleCell]; 259 | cell.isShowSeparator = NO; 260 | } 261 | __weak typeof(self) weakSelf = self; 262 | [self.animationManager displayShowAnimationCompletion:^{ 263 | __strong typeof(weakSelf)self = weakSelf; 264 | 265 | if (self.delegate && [self.delegate respondsToSelector:@selector(ybPopupMenuDidShow:)]) { 266 | [self.delegate ybPopupMenuDidShow:self]; 267 | } 268 | }]; 269 | } 270 | 271 | - (void)setDefaultSettings 272 | { 273 | _cornerRadius = 5.0; 274 | _rectCorner = UIRectCornerAllCorners; 275 | self.showShadow = YES; 276 | _dismissOnSelected = YES; 277 | _dismissOnTouchOutside = YES; 278 | _fontSize = 15; 279 | _textColor = [UIColor blackColor]; 280 | _offset = 0.0; 281 | _relyRect = CGRectZero; 282 | _point = CGPointZero; 283 | _borderWidth = 0.0; 284 | _borderColor = [UIColor lightGrayColor]; 285 | _arrowWidth = 20.0; 286 | _arrowHeight = 8.0; 287 | _backColor = [UIColor whiteColor]; 288 | _type = YBPopupMenuTypeDefault; 289 | _arrowStyle = YBPopupMenuArrowStyleCurve; 290 | _arrowDirection = YBPopupMenuArrowDirectionTop; 291 | _priorityDirection = YBPopupMenuPriorityDirectionTop; 292 | _minSpace = 10.0; 293 | _maxVisibleCount = 5; 294 | _itemHeight = 44; 295 | _isCornerChanged = NO; 296 | _showMaskView = YES; 297 | _orientationManager = [YBPopupMenuDeviceOrientationManager manager]; 298 | _animationManager = [YBPopupMenuAnimationManager manager]; 299 | _animationManager.animationView = self; 300 | _menuBackView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, YBScreenWidth, YBScreenHeight)]; 301 | _menuBackView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.1]; 302 | _menuBackView.alpha = 1; 303 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(touchOutSide)]; 304 | [_menuBackView addGestureRecognizer: tap]; 305 | self.alpha = 1; 306 | self.backgroundColor = [UIColor clearColor]; 307 | [self addSubview:self.effectView]; 308 | [self addSubview:self.containerView]; 309 | [self.containerView addSubview:self.tableView]; 310 | 311 | __weak typeof(self) weakSelf = self; 312 | [_orientationManager setDeviceOrientDidChangeHandle:^(UIInterfaceOrientation orientation) { 313 | __strong typeof(weakSelf)self = weakSelf; 314 | if (orientation == UIInterfaceOrientationPortrait || 315 | orientation == UIInterfaceOrientationLandscapeLeft || 316 | orientation == UIInterfaceOrientationLandscapeRight) 317 | { 318 | if (self.relyView) { 319 | //依赖view 320 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 321 | //需要延迟加载才可以获取真实的frame,这里先做个标记,若有更合适的方法再替换 322 | [self calculateRealPointIfNeed]; 323 | [self updateUI]; 324 | }); 325 | }else { 326 | //依赖point 327 | [self updateUI]; 328 | } 329 | } 330 | }]; 331 | } 332 | 333 | - (void)configSettings 334 | { 335 | self.containerView.rectCorner = _rectCorner; 336 | self.containerView.cornerRadius = _cornerRadius; 337 | self.containerView.borderWidth = _borderWidth; 338 | self.containerView.borderColor = _borderColor; 339 | self.containerView.backColor = _backColor; 340 | self.containerView.arrowWidth = _arrowWidth; 341 | self.containerView.arrowHeight = _arrowHeight; 342 | self.containerView.arrowPosition = _arrowPosition; 343 | self.containerView.arrowDirection = _arrowDirection; 344 | self.containerView.arrowStyle = _arrowStyle; 345 | [self.containerView setNeedsDisplay]; 346 | 347 | self.effectView.layer.mask = [YBPopupMenuPath yb_maskLayerWithRect:self.effectView.frame rectCorner:_rectCorner cornerRadius:_cornerRadius arrowWidth:_arrowWidth arrowHeight:_arrowHeight arrowPosition:_arrowPosition arrowDirection:_arrowDirection arrowStyle:_arrowStyle]; 348 | } 349 | 350 | - (void)touchOutSide 351 | { 352 | if (_dismissOnTouchOutside) { 353 | [self dismiss]; 354 | } 355 | } 356 | 357 | - (void)setShowShadow:(BOOL)showShadow 358 | { 359 | _showShadow = showShadow; 360 | self.layer.shadowColor = [UIColor lightGrayColor].CGColor; 361 | self.layer.shadowOpacity = showShadow ? 0.5 : 0; 362 | self.layer.shadowOffset = CGSizeMake(0, 0); 363 | self.layer.shadowRadius = showShadow ? 4.0 : 0; 364 | } 365 | 366 | - (void)setRelyView:(UIView *)relyView 367 | { 368 | _relyView = relyView; 369 | [self calculateRealPointIfNeed]; 370 | } 371 | 372 | - (void)calculateRealPointIfNeed 373 | { 374 | CGRect absoluteRect = [_relyView convertRect:_relyView.bounds toView:YBMainWindow]; 375 | CGPoint relyPoint = CGPointMake(absoluteRect.origin.x + absoluteRect.size.width / 2, absoluteRect.origin.y + absoluteRect.size.height); 376 | self.relyRect = absoluteRect; 377 | self.point = relyPoint; 378 | } 379 | 380 | - (void)setShowMaskView:(BOOL)showMaskView 381 | { 382 | _showMaskView = showMaskView; 383 | _menuBackView.backgroundColor = showMaskView ? [[UIColor blackColor] colorWithAlphaComponent:0.1] : [UIColor clearColor]; 384 | } 385 | 386 | - (void)setType:(YBPopupMenuType)type 387 | { 388 | _type = type; 389 | switch (type) { 390 | case YBPopupMenuTypeDark: 391 | { 392 | _textColor = [UIColor lightGrayColor]; 393 | _backColor = [UIColor colorWithRed:0.25 green:0.27 blue:0.29 alpha:1]; 394 | _separatorColor = [UIColor lightGrayColor]; 395 | } 396 | break; 397 | 398 | default: 399 | { 400 | _textColor = [UIColor blackColor]; 401 | _backColor = [UIColor whiteColor]; 402 | _separatorColor = [UIColor lightGrayColor]; 403 | } 404 | break; 405 | } 406 | } 407 | 408 | - (void)setTitles:(NSArray *)titles 409 | { 410 | _titles = titles; 411 | } 412 | 413 | - (void)setImages:(NSArray *)images 414 | { 415 | _images = images; 416 | } 417 | 418 | - (void)updateUI 419 | { 420 | _menuBackView.frame = CGRectMake(0, 0, YBScreenWidth, YBScreenHeight); 421 | CGFloat height; 422 | if (_titles.count > _maxVisibleCount) { 423 | height = _itemHeight * _maxVisibleCount + _borderWidth * 2; 424 | self.tableView.bounces = YES; 425 | }else { 426 | height = _itemHeight * _titles.count + _borderWidth * 2; 427 | self.tableView.bounces = NO; 428 | } 429 | _isChangeDirection = NO; 430 | if (_priorityDirection == YBPopupMenuPriorityDirectionTop) { 431 | if (_point.y + height + _arrowHeight > YBScreenHeight - _minSpace) { 432 | _arrowDirection = YBPopupMenuArrowDirectionBottom; 433 | _isChangeDirection = YES; 434 | }else { 435 | _arrowDirection = YBPopupMenuArrowDirectionTop; 436 | _isChangeDirection = NO; 437 | } 438 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionBottom) { 439 | if (_point.y - height - _arrowHeight < _minSpace) { 440 | _arrowDirection = YBPopupMenuArrowDirectionTop; 441 | _isChangeDirection = YES; 442 | }else { 443 | _arrowDirection = YBPopupMenuArrowDirectionBottom; 444 | _isChangeDirection = NO; 445 | } 446 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionLeft) { 447 | if (_point.x + _itemWidth + _arrowHeight > YBScreenWidth - _minSpace) { 448 | _arrowDirection = YBPopupMenuArrowDirectionRight; 449 | _isChangeDirection = YES; 450 | }else { 451 | _arrowDirection = YBPopupMenuArrowDirectionLeft; 452 | _isChangeDirection = NO; 453 | } 454 | }else if (_priorityDirection == YBPopupMenuPriorityDirectionRight) { 455 | if (_point.x - _itemWidth - _arrowHeight < _minSpace) { 456 | _arrowDirection = YBPopupMenuArrowDirectionLeft; 457 | _isChangeDirection = YES; 458 | }else { 459 | _arrowDirection = YBPopupMenuArrowDirectionRight; 460 | _isChangeDirection = NO; 461 | } 462 | } 463 | [self setArrowPosition]; 464 | [self setRelyRect]; 465 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 466 | CGFloat y = _isChangeDirection ? _point.y : _point.y; 467 | if (_arrowPosition > _itemWidth / 2) { 468 | self.frame = CGRectMake(YBScreenWidth - _minSpace - _itemWidth, y, _itemWidth, height + _arrowHeight); 469 | }else if (_arrowPosition < _itemWidth / 2) { 470 | self.frame = CGRectMake(_minSpace, y, _itemWidth, height + _arrowHeight); 471 | }else { 472 | self.frame = CGRectMake(_point.x - _itemWidth / 2, y, _itemWidth, height + _arrowHeight); 473 | } 474 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 475 | CGFloat y = _isChangeDirection ? _point.y - _arrowHeight - height : _point.y - _arrowHeight - height; 476 | if (_arrowPosition > _itemWidth / 2) { 477 | self.frame = CGRectMake(YBScreenWidth - _minSpace - _itemWidth, y, _itemWidth, height + _arrowHeight); 478 | }else if (_arrowPosition < _itemWidth / 2) { 479 | self.frame = CGRectMake(_minSpace, y, _itemWidth, height + _arrowHeight); 480 | }else { 481 | self.frame = CGRectMake(_point.x - _itemWidth / 2, y, _itemWidth, height + _arrowHeight); 482 | } 483 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 484 | CGFloat x = _isChangeDirection ? _point.x : _point.x; 485 | if (_arrowPosition < _itemHeight / 2) { 486 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 487 | }else if (_arrowPosition > _itemHeight / 2) { 488 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 489 | }else { 490 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 491 | } 492 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 493 | CGFloat x = _isChangeDirection ? _point.x - _itemWidth - _arrowHeight : _point.x - _itemWidth - _arrowHeight; 494 | if (_arrowPosition < _itemHeight / 2) { 495 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 496 | }else if (_arrowPosition > _itemHeight / 2) { 497 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 498 | }else { 499 | self.frame = CGRectMake(x, _point.y - _arrowPosition, _itemWidth + _arrowHeight, height); 500 | } 501 | }else if (_arrowDirection == YBPopupMenuArrowDirectionNone) { 502 | 503 | } 504 | 505 | if (_isChangeDirection) { 506 | [self changeRectCorner]; 507 | } 508 | [self setAnchorPoint]; 509 | [self setOffset]; 510 | [self.tableView reloadData]; 511 | [self configSettings]; 512 | } 513 | 514 | - (void)setRelyRect 515 | { 516 | if (CGRectEqualToRect(_relyRect, CGRectZero)) { 517 | return; 518 | } 519 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 520 | _point.y = _relyRect.size.height + _relyRect.origin.y; 521 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 522 | _point.y = _relyRect.origin.y; 523 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 524 | _point = CGPointMake(_relyRect.origin.x + _relyRect.size.width, _relyRect.origin.y + _relyRect.size.height / 2); 525 | }else { 526 | _point = CGPointMake(_relyRect.origin.x, _relyRect.origin.y + _relyRect.size.height / 2); 527 | } 528 | } 529 | 530 | 531 | - (void)setFrame:(CGRect)frame 532 | { 533 | [super setFrame:frame]; 534 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 535 | self.tableView.frame = CGRectMake(_borderWidth, _borderWidth + _arrowHeight, frame.size.width - _borderWidth * 2, frame.size.height - _arrowHeight); 536 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 537 | self.tableView.frame = CGRectMake(_borderWidth, _borderWidth, frame.size.width - _borderWidth * 2, frame.size.height - _arrowHeight); 538 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 539 | self.tableView.frame = CGRectMake(_borderWidth + _arrowHeight, _borderWidth , frame.size.width - _borderWidth * 2 - _arrowHeight, frame.size.height); 540 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 541 | self.tableView.frame = CGRectMake(_borderWidth , _borderWidth , frame.size.width - _borderWidth * 2 - _arrowHeight, frame.size.height); 542 | } 543 | self.effectView.frame = self.bounds; 544 | self.containerView.frame = self.bounds; 545 | } 546 | 547 | - (void)changeRectCorner 548 | { 549 | if (_isCornerChanged || _rectCorner == UIRectCornerAllCorners) { 550 | return; 551 | } 552 | BOOL haveTopLeftCorner = NO, haveTopRightCorner = NO, haveBottomLeftCorner = NO, haveBottomRightCorner = NO; 553 | if (_rectCorner & UIRectCornerTopLeft) { 554 | haveTopLeftCorner = YES; 555 | } 556 | if (_rectCorner & UIRectCornerTopRight) { 557 | haveTopRightCorner = YES; 558 | } 559 | if (_rectCorner & UIRectCornerBottomLeft) { 560 | haveBottomLeftCorner = YES; 561 | } 562 | if (_rectCorner & UIRectCornerBottomRight) { 563 | haveBottomRightCorner = YES; 564 | } 565 | 566 | if (_arrowDirection == YBPopupMenuArrowDirectionTop || _arrowDirection == YBPopupMenuArrowDirectionBottom) { 567 | 568 | if (haveTopLeftCorner) { 569 | _rectCorner = _rectCorner | UIRectCornerBottomLeft; 570 | }else { 571 | _rectCorner = _rectCorner & (~UIRectCornerBottomLeft); 572 | } 573 | if (haveTopRightCorner) { 574 | _rectCorner = _rectCorner | UIRectCornerBottomRight; 575 | }else { 576 | _rectCorner = _rectCorner & (~UIRectCornerBottomRight); 577 | } 578 | if (haveBottomLeftCorner) { 579 | _rectCorner = _rectCorner | UIRectCornerTopLeft; 580 | }else { 581 | _rectCorner = _rectCorner & (~UIRectCornerTopLeft); 582 | } 583 | if (haveBottomRightCorner) { 584 | _rectCorner = _rectCorner | UIRectCornerTopRight; 585 | }else { 586 | _rectCorner = _rectCorner & (~UIRectCornerTopRight); 587 | } 588 | 589 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft || _arrowDirection == YBPopupMenuArrowDirectionRight) { 590 | if (haveTopLeftCorner) { 591 | _rectCorner = _rectCorner | UIRectCornerTopRight; 592 | }else { 593 | _rectCorner = _rectCorner & (~UIRectCornerTopRight); 594 | } 595 | if (haveTopRightCorner) { 596 | _rectCorner = _rectCorner | UIRectCornerTopLeft; 597 | }else { 598 | _rectCorner = _rectCorner & (~UIRectCornerTopLeft); 599 | } 600 | if (haveBottomLeftCorner) { 601 | _rectCorner = _rectCorner | UIRectCornerBottomRight; 602 | }else { 603 | _rectCorner = _rectCorner & (~UIRectCornerBottomRight); 604 | } 605 | if (haveBottomRightCorner) { 606 | _rectCorner = _rectCorner | UIRectCornerBottomLeft; 607 | }else { 608 | _rectCorner = _rectCorner & (~UIRectCornerBottomLeft); 609 | } 610 | } 611 | 612 | _isCornerChanged = YES; 613 | } 614 | 615 | - (void)setOffset 616 | { 617 | if (_itemWidth == 0) return; 618 | 619 | CGRect originRect = self.frame; 620 | 621 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 622 | originRect.origin.y += _offset; 623 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 624 | originRect.origin.y -= _offset; 625 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 626 | originRect.origin.x += _offset; 627 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 628 | originRect.origin.x -= _offset; 629 | } 630 | self.frame = originRect; 631 | } 632 | 633 | - (void)setAnchorPoint 634 | { 635 | if (_itemWidth == 0) return; 636 | 637 | CGFloat menuHeight = [self getMenuTotalHeight]; 638 | 639 | CGPoint point = CGPointMake(0.5, 0.5); 640 | if (_arrowDirection == YBPopupMenuArrowDirectionTop) { 641 | point = CGPointMake(_arrowPosition / _itemWidth, 0); 642 | }else if (_arrowDirection == YBPopupMenuArrowDirectionBottom) { 643 | point = CGPointMake(_arrowPosition / _itemWidth, 1); 644 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft) { 645 | point = CGPointMake(0, _arrowPosition / menuHeight); 646 | }else if (_arrowDirection == YBPopupMenuArrowDirectionRight) { 647 | point = CGPointMake(1, _arrowPosition / menuHeight); 648 | } 649 | CGRect originRect = self.frame; 650 | self.layer.anchorPoint = point; 651 | self.frame = originRect; 652 | } 653 | 654 | - (void)setArrowPosition 655 | { 656 | if (_priorityDirection == YBPopupMenuPriorityDirectionNone) { 657 | return; 658 | } 659 | 660 | if (_arrowDirection == YBPopupMenuArrowDirectionTop || _arrowDirection == YBPopupMenuArrowDirectionBottom) { 661 | if (_point.x + _itemWidth / 2 > YBScreenWidth - _minSpace) { 662 | _arrowPosition = _itemWidth - (YBScreenWidth - _minSpace - _point.x); 663 | }else if (_point.x < _itemWidth / 2 + _minSpace) { 664 | _arrowPosition = _point.x - _minSpace; 665 | }else { 666 | _arrowPosition = _itemWidth / 2; 667 | } 668 | 669 | }else if (_arrowDirection == YBPopupMenuArrowDirectionLeft || _arrowDirection == YBPopupMenuArrowDirectionRight) { 670 | } 671 | } 672 | 673 | - (CGFloat)getMenuTotalHeight 674 | { 675 | CGFloat menuHeight = 0; 676 | if (_titles.count > _maxVisibleCount) { 677 | menuHeight = _itemHeight * _maxVisibleCount + _borderWidth * 2; 678 | }else { 679 | menuHeight = _itemHeight * _titles.count + _borderWidth * 2; 680 | } 681 | return menuHeight; 682 | } 683 | 684 | #pragma mark - lazyloads 685 | - (UITableView *)tableView 686 | { 687 | if (!_tableView) { 688 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 689 | _tableView.backgroundColor = [UIColor clearColor]; 690 | _tableView.tableFooterView = [UIView new]; 691 | _tableView.delegate = self; 692 | _tableView.dataSource = self; 693 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 694 | } 695 | return _tableView; 696 | } 697 | 698 | - (YBPopupMenuContainerView *)containerView 699 | { 700 | if (!_containerView) { 701 | _containerView = [[YBPopupMenuContainerView alloc] init]; 702 | _containerView.backgroundColor = [UIColor clearColor]; 703 | } 704 | return _containerView; 705 | } 706 | 707 | - (UIVisualEffectView *)effectView 708 | { 709 | if (!_effectView) { 710 | UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 711 | _effectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 712 | } 713 | return _effectView; 714 | } 715 | 716 | @end 717 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuAnimationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuAnimationManager.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger,YBPopupMenuAnimationStyle) { 15 | YBPopupMenuAnimationStyleScale = 0, //scale动画 Default 16 | YBPopupMenuAnimationStyleFade, //alpha 0~1 17 | YBPopupMenuAnimationStyleNone, //没有动画 18 | YBPopupMenuAnimationStyleCustom //自定义 19 | }; 20 | 21 | @protocol YBPopupMenuAnimationManager 22 | 23 | /** 24 | 动画类型,默认YBPopupMenuAnimationStyleScale 25 | */ 26 | @property (nonatomic, assign) YBPopupMenuAnimationStyle style; 27 | 28 | /** 29 | 显示动画,自定义可用 30 | */ 31 | @property (nonatomic, strong) CAAnimation * showAnimation; 32 | 33 | /** 34 | 隐藏动画,自定义可用 35 | */ 36 | @property (nonatomic, strong) CAAnimation * dismissAnimation; 37 | 38 | /** 39 | 弹出和隐藏动画的时间,Default is 0.25 40 | */ 41 | @property CFTimeInterval duration; 42 | 43 | @property (nonatomic, weak) UIView * animationView; 44 | 45 | + (id )manager; 46 | 47 | - (void)displayShowAnimationCompletion:(void (^) (void))completion; 48 | 49 | - (void)displayDismissAnimationCompletion:(void (^) (void))completion; 50 | 51 | @end 52 | 53 | @interface YBPopupMenuAnimationManager : NSObject 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuAnimationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuAnimationManager.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuAnimationManager.h" 10 | 11 | static NSString * const YBShowAnimationKey = @"showAnimation"; 12 | static NSString * const YBDismissAnimationKey = @"dismissAnimation"; 13 | @interface YBPopupMenuAnimationManager () 14 | < 15 | CAAnimationDelegate 16 | > 17 | @property (nonatomic, copy) void (^showAnimationHandle) (void); 18 | 19 | @property (nonatomic, copy) void (^dismissAnimationHandle) (void); 20 | 21 | @property (nonatomic, assign) BOOL isAnimating; 22 | 23 | @end 24 | 25 | @implementation YBPopupMenuAnimationManager 26 | @synthesize style = _style; 27 | @synthesize showAnimation = _showAnimation; 28 | @synthesize dismissAnimation = _dismissAnimation; 29 | @synthesize duration = _duration; 30 | @synthesize animationView = _animationView; 31 | 32 | + (id)manager 33 | { 34 | YBPopupMenuAnimationManager * manager = [[YBPopupMenuAnimationManager alloc] init]; 35 | manager.style = YBPopupMenuAnimationStyleScale; 36 | manager.duration = 0.25; 37 | return manager; 38 | } 39 | 40 | - (void)configAnimation 41 | { 42 | CABasicAnimation * showAnimation; 43 | CABasicAnimation * dismissAnimation; 44 | switch (_style) { 45 | case YBPopupMenuAnimationStyleFade: 46 | { 47 | _showAnimation = _dismissAnimation = nil; 48 | //show 49 | showAnimation = [self getBasicAnimationWithKeyPath:@"opacity"]; 50 | showAnimation.fillMode = kCAFillModeBackwards; 51 | showAnimation.fromValue = @(0); 52 | showAnimation.toValue = @(1); 53 | _showAnimation = showAnimation; 54 | //dismiss 55 | dismissAnimation = [self getBasicAnimationWithKeyPath:@"opacity"]; 56 | dismissAnimation.fillMode = kCAFillModeForwards; 57 | dismissAnimation.fromValue = @(1); 58 | dismissAnimation.toValue = @(0); 59 | _dismissAnimation = dismissAnimation; 60 | } 61 | break; 62 | case YBPopupMenuAnimationStyleCustom: 63 | break; 64 | case YBPopupMenuAnimationStyleNone: 65 | { 66 | _showAnimation = _dismissAnimation = nil; 67 | } 68 | break; 69 | default: 70 | { 71 | _showAnimation = _dismissAnimation = nil; 72 | //show 73 | showAnimation = [self getBasicAnimationWithKeyPath:@"transform.scale"]; 74 | showAnimation.fillMode = kCAFillModeBackwards; 75 | showAnimation.fromValue = @(0.1); 76 | showAnimation.toValue = @(1); 77 | _showAnimation = showAnimation; 78 | //dismiss 79 | dismissAnimation = [self getBasicAnimationWithKeyPath:@"transform.scale"]; 80 | dismissAnimation.fillMode = kCAFillModeForwards; 81 | dismissAnimation.fromValue = @(1); 82 | dismissAnimation.toValue = @(0.1); 83 | _dismissAnimation = dismissAnimation; 84 | } 85 | break; 86 | } 87 | } 88 | 89 | - (void)setStyle:(YBPopupMenuAnimationStyle)style 90 | { 91 | _style = style; 92 | [self configAnimation]; 93 | } 94 | 95 | - (void)setDuration:(CFTimeInterval)duration 96 | { 97 | _duration = duration; 98 | [self configAnimation]; 99 | } 100 | 101 | - (void)setShowAnimation:(CAAnimation *)showAnimation 102 | { 103 | _showAnimation = showAnimation; 104 | [self configAnimation]; 105 | } 106 | 107 | - (void)setDismissAnimation:(CAAnimation *)dismissAnimation 108 | { 109 | _dismissAnimation = dismissAnimation; 110 | [self configAnimation]; 111 | } 112 | 113 | - (CABasicAnimation *)getBasicAnimationWithKeyPath:(NSString *)keyPath 114 | { 115 | CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:keyPath]; 116 | animation.removedOnCompletion = NO; 117 | animation.duration = _duration; 118 | return animation; 119 | } 120 | 121 | - (void)displayShowAnimationCompletion:(void (^)(void))completion 122 | { 123 | _showAnimationHandle = completion; 124 | if (!_showAnimation) { 125 | if (_showAnimationHandle) { 126 | _showAnimationHandle(); 127 | } 128 | return; 129 | } 130 | if (self.isAnimating) return; 131 | self.isAnimating = YES; 132 | _showAnimation.delegate = self; 133 | [_animationView.layer addAnimation:_showAnimation forKey:YBShowAnimationKey]; 134 | } 135 | 136 | - (void)displayDismissAnimationCompletion:(void (^)(void))completion 137 | { 138 | _dismissAnimationHandle = completion; 139 | if (!_dismissAnimation) { 140 | if (_dismissAnimationHandle) { 141 | _dismissAnimationHandle(); 142 | } 143 | return; 144 | } 145 | if (self.isAnimating) return; 146 | self.isAnimating = YES; 147 | _dismissAnimation.delegate = self; 148 | [_animationView.layer addAnimation:_dismissAnimation forKey:YBDismissAnimationKey]; 149 | } 150 | 151 | #pragma mark - CAAnimationDelegate 152 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 153 | { 154 | if ([_animationView.layer animationForKey:YBShowAnimationKey] == anim) { 155 | [_animationView.layer removeAnimationForKey:YBShowAnimationKey]; 156 | _showAnimation.delegate = nil; 157 | _showAnimation = nil; 158 | _isAnimating = NO; 159 | if (_showAnimationHandle) { 160 | _showAnimationHandle(); 161 | } 162 | }else if ([_animationView.layer animationForKey:YBDismissAnimationKey] == anim) { 163 | [_animationView.layer removeAnimationForKey:YBDismissAnimationKey]; 164 | _dismissAnimation.delegate = nil; 165 | _dismissAnimation = nil; 166 | _isAnimating = NO; 167 | if (_dismissAnimationHandle) { 168 | _dismissAnimationHandle(); 169 | } 170 | } 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuContainerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuContainerView.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2023/8/16. 6 | // Copyright © 2023 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YBPopupMenuPath.h" 11 | 12 | @interface YBPopupMenuContainerView : UIView 13 | 14 | @property (nonatomic, assign) CGFloat cornerRadius; 15 | 16 | @property (nonatomic, assign) UIRectCorner rectCorner; 17 | 18 | @property (nonatomic, assign) CGFloat borderWidth; 19 | 20 | @property (nonatomic, strong) UIColor * borderColor; 21 | 22 | @property (nonatomic, assign) CGFloat arrowWidth; 23 | 24 | @property (nonatomic, assign) CGFloat arrowHeight; 25 | 26 | @property (nonatomic, assign) CGFloat arrowPosition; 27 | 28 | @property (nonatomic, assign) YBPopupMenuArrowDirection arrowDirection; 29 | 30 | @property (nonatomic, assign) YBPopupMenuArrowStyle arrowStyle; 31 | 32 | @property (nonatomic, strong) UIColor * backColor; 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuContainerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuContainerView.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2023/8/16. 6 | // Copyright © 2023 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuContainerView.h" 10 | 11 | @implementation YBPopupMenuContainerView 12 | 13 | - (void)drawRect:(CGRect)rect 14 | { 15 | UIBezierPath *bezierPath = [YBPopupMenuPath yb_bezierPathWithRect:rect rectCorner:_rectCorner cornerRadius:_cornerRadius borderWidth:_borderWidth borderColor:_borderColor backgroundColor:_backColor arrowWidth:_arrowWidth arrowHeight:_arrowHeight arrowPosition:_arrowPosition arrowDirection:_arrowDirection arrowStyle:_arrowStyle]; 16 | [bezierPath fill]; 17 | [bezierPath stroke]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuDeviceOrientationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuDeviceOrientationManager.h 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @protocol YBPopupMenuDeviceOrientationManager 17 | 18 | /** 19 | 根据屏幕旋转方向自动旋转 Default is YES 20 | */ 21 | @property (nonatomic, assign) BOOL autoRotateWhenDeviceOrientationChanged; 22 | 23 | @property (nonatomic, copy) void (^deviceOrientDidChangeHandle) (UIInterfaceOrientation orientation); 24 | 25 | + (id )manager; 26 | 27 | /** 28 | 开始监听 29 | */ 30 | - (void)startMonitorDeviceOrientation; 31 | 32 | /** 33 | 结束监听 34 | */ 35 | - (void)endMonitorDeviceOrientation; 36 | 37 | @end 38 | 39 | @interface YBPopupMenuDeviceOrientationManager : NSObject 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuDeviceOrientationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuDeviceOrientationManager.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by liyuanbo on 2020/1/19. 6 | // Copyright © 2020 LYB. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuDeviceOrientationManager.h" 10 | 11 | @implementation YBPopupMenuDeviceOrientationManager 12 | @synthesize autoRotateWhenDeviceOrientationChanged = _autoRotateWhenDeviceOrientationChanged; 13 | @synthesize deviceOrientDidChangeHandle = _deviceOrientDidChangeHandle; 14 | 15 | + (id)manager 16 | { 17 | YBPopupMenuDeviceOrientationManager * manager = [[YBPopupMenuDeviceOrientationManager alloc] init]; 18 | manager.autoRotateWhenDeviceOrientationChanged = YES; 19 | return manager; 20 | } 21 | 22 | - (void)startMonitorDeviceOrientation 23 | { 24 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationDidChangedNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 26 | } 27 | 28 | - (void)endMonitorDeviceOrientation 29 | { 30 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 31 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 32 | } 33 | 34 | #pragma mark - notify 35 | - (void)onDeviceOrientationDidChangedNotification:(NSNotification *)notify 36 | { 37 | if (!self.autoRotateWhenDeviceOrientationChanged) return; 38 | UIInterfaceOrientation orientation = [notify.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue]; 39 | if (_deviceOrientDidChangeHandle) { 40 | _deviceOrientDidChangeHandle(orientation); 41 | } 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuPath.h 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/9. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, YBPopupMenuArrowDirection) { 12 | YBPopupMenuArrowDirectionTop = 0, //箭头朝上 13 | YBPopupMenuArrowDirectionBottom, //箭头朝下 14 | YBPopupMenuArrowDirectionLeft, //箭头朝左 15 | YBPopupMenuArrowDirectionRight, //箭头朝右 16 | YBPopupMenuArrowDirectionNone //没有箭头 17 | }; 18 | 19 | typedef NS_ENUM(NSInteger, YBPopupMenuArrowStyle) { 20 | YBPopupMenuArrowStyleCurve = 0, //曲线箭头 21 | YBPopupMenuArrowStyleStraight, //直线箭头 22 | }; 23 | 24 | @interface YBPopupMenuPath : NSObject 25 | 26 | + (CAShapeLayer *)yb_maskLayerWithRect:(CGRect)rect 27 | rectCorner:(UIRectCorner)rectCorner 28 | cornerRadius:(CGFloat)cornerRadius 29 | arrowWidth:(CGFloat)arrowWidth 30 | arrowHeight:(CGFloat)arrowHeight 31 | arrowPosition:(CGFloat)arrowPosition 32 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 33 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle; 34 | 35 | + (UIBezierPath *)yb_bezierPathWithRect:(CGRect)rect 36 | rectCorner:(UIRectCorner)rectCorner 37 | cornerRadius:(CGFloat)cornerRadius 38 | borderWidth:(CGFloat)borderWidth 39 | borderColor:(UIColor *)borderColor 40 | backgroundColor:(UIColor *)backgroundColor 41 | arrowWidth:(CGFloat)arrowWidth 42 | arrowHeight:(CGFloat)arrowHeight 43 | arrowPosition:(CGFloat)arrowPosition 44 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 45 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle; 46 | @end 47 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/YBPopupMenu/YBPopupMenuPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuPath.m 3 | // YBPopupMenu 4 | // 5 | // Created by lyb on 2017/5/9. 6 | // Copyright © 2017年 lyb. All rights reserved. 7 | // 8 | 9 | #import "YBPopupMenuPath.h" 10 | 11 | @implementation YBPopupMenuPath 12 | 13 | + (CAShapeLayer *)yb_maskLayerWithRect:(CGRect)rect 14 | rectCorner:(UIRectCorner)rectCorner 15 | cornerRadius:(CGFloat)cornerRadius 16 | arrowWidth:(CGFloat)arrowWidth 17 | arrowHeight:(CGFloat)arrowHeight 18 | arrowPosition:(CGFloat)arrowPosition 19 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 20 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle 21 | { 22 | CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 23 | shapeLayer.path = [self yb_bezierPathWithRect:rect rectCorner:rectCorner cornerRadius:cornerRadius borderWidth:0 borderColor:nil backgroundColor:nil arrowWidth:arrowWidth arrowHeight:arrowHeight arrowPosition:arrowPosition arrowDirection:arrowDirection arrowStyle:arrowStyle].CGPath; 24 | return shapeLayer; 25 | } 26 | 27 | 28 | + (UIBezierPath *)yb_bezierPathWithRect:(CGRect)rect 29 | rectCorner:(UIRectCorner)rectCorner 30 | cornerRadius:(CGFloat)cornerRadius 31 | borderWidth:(CGFloat)borderWidth 32 | borderColor:(UIColor *)borderColor 33 | backgroundColor:(UIColor *)backgroundColor 34 | arrowWidth:(CGFloat)arrowWidth 35 | arrowHeight:(CGFloat)arrowHeight 36 | arrowPosition:(CGFloat)arrowPosition 37 | arrowDirection:(YBPopupMenuArrowDirection)arrowDirection 38 | arrowStyle:(YBPopupMenuArrowStyle)arrowStyle 39 | { 40 | UIBezierPath *bezierPath = [UIBezierPath bezierPath]; 41 | if (borderColor) { 42 | [borderColor setStroke]; 43 | } 44 | if (backgroundColor) { 45 | [backgroundColor setFill]; 46 | } 47 | bezierPath.lineWidth = borderWidth; 48 | rect = CGRectMake(borderWidth / 2, borderWidth / 2, CGRectGetWidth(rect) - borderWidth, CGRectGetHeight(rect) - borderWidth); 49 | CGFloat topRightRadius = 0,topLeftRadius = 0,bottomRightRadius = 0,bottomLeftRadius = 0; 50 | CGPoint topRightArcCenter,topLeftArcCenter,bottomRightArcCenter,bottomLeftArcCenter; 51 | 52 | if (rectCorner & UIRectCornerTopLeft) { 53 | topLeftRadius = cornerRadius; 54 | } 55 | if (rectCorner & UIRectCornerTopRight) { 56 | topRightRadius = cornerRadius; 57 | } 58 | if (rectCorner & UIRectCornerBottomLeft) { 59 | bottomLeftRadius = cornerRadius; 60 | } 61 | if (rectCorner & UIRectCornerBottomRight) { 62 | bottomRightRadius = cornerRadius; 63 | } 64 | 65 | if (arrowDirection == YBPopupMenuArrowDirectionTop) { 66 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect)); 67 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), arrowHeight + topRightRadius + CGRectGetMinX(rect)); 68 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 69 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 70 | if (arrowPosition < topLeftRadius + arrowWidth / 2) { 71 | arrowPosition = topLeftRadius + arrowWidth / 2; 72 | }else if (arrowPosition > CGRectGetWidth(rect) - topRightRadius - arrowWidth / 2) { 73 | arrowPosition = CGRectGetWidth(rect) - topRightRadius - arrowWidth / 2; 74 | } 75 | [bezierPath moveToPoint:CGPointMake(arrowPosition - arrowWidth / 2, arrowHeight + CGRectGetMinX(rect))]; 76 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 77 | [bezierPath addLineToPoint:CGPointMake(arrowPosition, CGRectGetMinY(rect) + CGRectGetMinX(rect))]; 78 | [bezierPath addLineToPoint:CGPointMake(arrowPosition + arrowWidth / 2, arrowHeight + CGRectGetMinX(rect))]; 79 | } else { 80 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition, CGRectGetMinY(rect) + CGRectGetMinX(rect)) 81 | controlPoint1:CGPointMake(arrowPosition - arrowWidth / 2 + arrowWidth / 4, arrowHeight + CGRectGetMinX(rect)) 82 | controlPoint2:CGPointMake(arrowPosition - arrowWidth / 8, CGRectGetMinY(rect) + CGRectGetMinX(rect))]; 83 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition + arrowWidth / 2, arrowHeight + CGRectGetMinX(rect)) 84 | controlPoint1:CGPointMake(arrowPosition + arrowWidth / 8, CGRectGetMinY(rect) + CGRectGetMinX(rect)) 85 | controlPoint2:CGPointMake(arrowPosition + arrowWidth / 2 - arrowWidth / 4, arrowHeight + CGRectGetMinX(rect))]; 86 | } 87 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, arrowHeight + CGRectGetMinX(rect))]; 88 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 89 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 90 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 91 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 92 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 93 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 94 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 95 | 96 | }else if (arrowDirection == YBPopupMenuArrowDirectionBottom) { 97 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect),topLeftRadius + CGRectGetMinX(rect)); 98 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 99 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect) - arrowHeight); 100 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect) - arrowHeight); 101 | if (arrowPosition < bottomLeftRadius + arrowWidth / 2) { 102 | arrowPosition = bottomLeftRadius + arrowWidth / 2; 103 | }else if (arrowPosition > CGRectGetWidth(rect) - bottomRightRadius - arrowWidth / 2) { 104 | arrowPosition = CGRectGetWidth(rect) - bottomRightRadius - arrowWidth / 2; 105 | } 106 | [bezierPath moveToPoint:CGPointMake(arrowPosition + arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 107 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 108 | [bezierPath addLineToPoint:CGPointMake(arrowPosition, CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 109 | [bezierPath addLineToPoint:CGPointMake(arrowPosition - arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 110 | } else { 111 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition, CGRectGetHeight(rect) + CGRectGetMinX(rect)) 112 | controlPoint1:CGPointMake(arrowPosition + arrowWidth / 2 - arrowWidth / 4, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect)) 113 | controlPoint2:CGPointMake(arrowPosition + arrowWidth / 8, CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 114 | [bezierPath addCurveToPoint:CGPointMake(arrowPosition - arrowWidth / 2, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect)) 115 | controlPoint1:CGPointMake(arrowPosition - arrowWidth / 8, CGRectGetHeight(rect) + CGRectGetMinX(rect)) 116 | controlPoint2:CGPointMake(arrowPosition - arrowWidth / 2 + arrowWidth / 4, CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 117 | } 118 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - arrowHeight + CGRectGetMinX(rect))]; 119 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 120 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect))]; 121 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 122 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), CGRectGetMinX(rect))]; 123 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 124 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect) - arrowHeight)]; 125 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 126 | 127 | }else if (arrowDirection == YBPopupMenuArrowDirectionLeft) { 128 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect) + arrowHeight,topLeftRadius + CGRectGetMinX(rect)); 129 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 130 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect) + arrowHeight, CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 131 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 132 | if (arrowPosition < topLeftRadius + arrowWidth / 2) { 133 | arrowPosition = topLeftRadius + arrowWidth / 2; 134 | }else if (arrowPosition > CGRectGetHeight(rect) - bottomLeftRadius - arrowWidth / 2) { 135 | arrowPosition = CGRectGetHeight(rect) - bottomLeftRadius - arrowWidth / 2; 136 | } 137 | [bezierPath moveToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2)]; 138 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 139 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowPosition)]; 140 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2)]; 141 | } else { 142 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetMinX(rect), arrowPosition) 143 | controlPoint1:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2 - arrowWidth / 4) 144 | controlPoint2:CGPointMake(CGRectGetMinX(rect), arrowPosition + arrowWidth / 8)]; 145 | [bezierPath addCurveToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2) 146 | controlPoint1:CGPointMake(CGRectGetMinX(rect), arrowPosition - arrowWidth / 8) 147 | controlPoint2:CGPointMake(arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2 + arrowWidth / 4)]; 148 | } 149 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect))]; 150 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 151 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, CGRectGetMinX(rect))]; 152 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 153 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 154 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 155 | [bezierPath addLineToPoint:CGPointMake(arrowHeight + bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 156 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 157 | 158 | }else if (arrowDirection == YBPopupMenuArrowDirectionRight) { 159 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect),topLeftRadius + CGRectGetMinX(rect)); 160 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect) - arrowHeight, topRightRadius + CGRectGetMinX(rect)); 161 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 162 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect) - arrowHeight, CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 163 | if (arrowPosition < topRightRadius + arrowWidth / 2) { 164 | arrowPosition = topRightRadius + arrowWidth / 2; 165 | }else if (arrowPosition > CGRectGetHeight(rect) - bottomRightRadius - arrowWidth / 2) { 166 | arrowPosition = CGRectGetHeight(rect) - bottomRightRadius - arrowWidth / 2; 167 | } 168 | [bezierPath moveToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2)]; 169 | if (arrowStyle == YBPopupMenuArrowStyleStraight) { 170 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition)]; 171 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2)]; 172 | } else { 173 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition) 174 | controlPoint1:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition - arrowWidth / 2 + arrowWidth / 4) 175 | controlPoint2:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition - arrowWidth / 8)]; 176 | [bezierPath addCurveToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2) 177 | controlPoint1:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), arrowPosition + arrowWidth / 8) 178 | controlPoint2:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), arrowPosition + arrowWidth / 2 - arrowWidth / 4)]; 179 | } 180 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - arrowHeight + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 181 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 182 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 183 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 184 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 185 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 186 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect) - arrowHeight, CGRectGetMinX(rect))]; 187 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 188 | 189 | }else if (arrowDirection == YBPopupMenuArrowDirectionNone) { 190 | topLeftArcCenter = CGPointMake(topLeftRadius + CGRectGetMinX(rect), topLeftRadius + CGRectGetMinX(rect)); 191 | topRightArcCenter = CGPointMake(CGRectGetWidth(rect) - topRightRadius + CGRectGetMinX(rect), topRightRadius + CGRectGetMinX(rect)); 192 | bottomLeftArcCenter = CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomLeftRadius + CGRectGetMinX(rect)); 193 | bottomRightArcCenter = CGPointMake(CGRectGetWidth(rect) - bottomRightRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius + CGRectGetMinX(rect)); 194 | [bezierPath moveToPoint:CGPointMake(topLeftRadius + CGRectGetMinX(rect), CGRectGetMinX(rect))]; 195 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) - topRightRadius, CGRectGetMinX(rect))]; 196 | [bezierPath addArcWithCenter:topRightArcCenter radius:topRightRadius startAngle:M_PI * 3 / 2 endAngle:2 * M_PI clockwise:YES]; 197 | [bezierPath addLineToPoint:CGPointMake(CGRectGetWidth(rect) + CGRectGetMinX(rect), CGRectGetHeight(rect) - bottomRightRadius - CGRectGetMinX(rect))]; 198 | [bezierPath addArcWithCenter:bottomRightArcCenter radius:bottomRightRadius startAngle:0 endAngle:M_PI_2 clockwise:YES]; 199 | [bezierPath addLineToPoint:CGPointMake(bottomLeftRadius + CGRectGetMinX(rect), CGRectGetHeight(rect) + CGRectGetMinX(rect))]; 200 | [bezierPath addArcWithCenter:bottomLeftArcCenter radius:bottomLeftRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES]; 201 | [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(rect), arrowHeight + topLeftRadius + CGRectGetMinX(rect))]; 202 | [bezierPath addArcWithCenter:topLeftArcCenter radius:topLeftRadius startAngle:M_PI endAngle:M_PI * 3 / 2 clockwise:YES]; 203 | } 204 | 205 | [bezierPath closePath]; 206 | return bezierPath; 207 | } 208 | 209 | @end 210 | -------------------------------------------------------------------------------- /YBPopupMenuDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YBPopupMenuDemo 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /YBPopupMenuDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /YBPopupMenuDemoUITests/YBPopupMenuDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // YBPopupMenuDemoUITests.m 3 | // YBPopupMenuDemoUITests 4 | // 5 | // Created by LYB on 16/11/8. 6 | // Copyright © 2016年 LYB. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YBPopupMenuDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation YBPopupMenuDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /YBPopupMenuGif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyb5834/YBPopupMenu/f7cc2e63194a4fb567eef76b6fff7846d7b60d86/YBPopupMenuGif.gif --------------------------------------------------------------------------------