├── .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 | 
6 |
7 | # cocoapods支持
8 | * 只需在`Podfile`中加入`pod 'YBPopupMenu'`后`pod install`即可
9 |
10 | # SwiftPackageManager(SPM)支持
11 |
12 | # 最近更新
13 | * 新增箭头样式
14 |
15 | | 样式 | 图例 |
16 | |------|------|
17 | | `YBPopupMenuArrowStyleCurve` **默认** |  |
18 | | `YBPopupMenuArrowStyleStraight` |  |
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
--------------------------------------------------------------------------------