├── .swift-version ├── YLTagsChooser ├── YLTagsChooser │ ├── zh-Hans.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── ViewController.h │ ├── AppDelegate.h │ ├── TagsChooser │ │ ├── NSArray+YLBoundsCheck.h │ │ ├── YLCollectionReusableView.h │ │ ├── NSArray+YLBoundsCheck.m │ │ ├── YLTag.h │ │ ├── YLTag.m │ │ ├── YLWaterFlowLayout.h │ │ ├── YLCollectionReusableView.m │ │ ├── YLTagsChooser.h │ │ ├── YLWaterFlowLayout.m │ │ └── YLTagsChooser.m │ ├── main.m │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.m │ └── ViewController.m ├── .DS_Store ├── YLTagsChooser.xcodeproj │ ├── xcuserdata │ │ ├── TK-001289.xcuserdatad │ │ │ ├── xcdebugger │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ │ ├── xcschememanagement.plist │ │ │ │ └── YLTagsChooser.xcscheme │ │ ├── lumin.xcuserdatad │ │ │ ├── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── xcdebugger │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── Lambert.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── YLTagsChooser.xcscheme │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ ├── lumin.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ ├── Lambert.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── TK-001289.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj ├── YLTagsChooserTests │ ├── Info.plist │ └── YLTagsChooserTests.m └── YLTagsChooserUITests │ ├── Info.plist │ └── YLTagsChooserUITests.m ├── .DS_Store ├── .gitignore ├── fix_bug3.gif ├── YLTagsChooser.gif ├── ReappearBugCode ├── 说明.md ├── YLWaterFlowLayout_Bug.h └── YLWaterFlowLayout_Bug.m ├── YLTagsChooser.podspec ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | .DS_Store 4 | .DS_Store 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /fix_bug3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/fix_bug3.gif -------------------------------------------------------------------------------- /YLTagsChooser.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/YLTagsChooser.gif -------------------------------------------------------------------------------- /ReappearBugCode/说明.md: -------------------------------------------------------------------------------- 1 | 使用这里的YLWaterFlowLayout_Bug替代项目中的YLWaterFlowLayout,即可复现在滑动过程中有些cell一会显示一会消失的效果。 2 | -------------------------------------------------------------------------------- /YLTagsChooser/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/YLTagsChooser/.DS_Store -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UIButton"; normalTitle = "选择标签"; ObjectID = "9Nb-dT-wv5"; */ 3 | "9Nb-dT-wv5.normalTitle" = "选择标签"; 4 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/TK-001289.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/lumin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/lumin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/Lambert.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/Lambert.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/TK-001289.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApesTalk/YLTagsChooser/HEAD/YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcuserdata/TK-001289.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | @property (weak, nonatomic) IBOutlet UILabel *textLabel; 13 | 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. 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 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/NSArray+YLBoundsCheck.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+YLBoundsCheck.h 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/1/20. 6 | // Copyright (c) 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSArray (YLBoundsCheck) 12 | 13 | /**对数组边界进行检查,避免数组越界*/ 14 | - (id)yl_objectAtIndex:(NSUInteger)index; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. 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 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLCollectionReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YLCollectionReusableView.h 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 2016/10/31. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // test UICollectionView section header 12 | @interface YLCollectionReusableView : UICollectionReusableView 13 | - (void)setTitle:(NSString *)title; 14 | @end 15 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/NSArray+YLBoundsCheck.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+YLBoundsCheck.m 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/1/20. 6 | // Copyright (c) 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "NSArray+YLBoundsCheck.h" 10 | 11 | @implementation NSArray (YLBoundsCheck) 12 | - (id)yl_objectAtIndex:(NSUInteger)index 13 | { 14 | if(index 10 | 11 | @interface YLTag : NSObject 12 | @property(nonatomic,assign)NSInteger objId; 13 | @property(nonatomic,copy)NSString *name; 14 | @property(nonatomic,assign)BOOL selected;///< default is NO 15 | 16 | - (instancetype)initWithId:(NSInteger)oId name:(NSString *)name; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/lumin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YLTagsChooser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | YLTagsChooser.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /YLTagsChooser.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "YLTagsChooser" 5 | s.version = "1.0" 6 | s.summary = "用UICollectionView实现的兴趣标签选择器 (A interest tag selector implemented with the UICollectionView.)" 7 | s.homepage = "https://github.com/lqcjdx/YLTagsChooser" 8 | s.license = "MIT" 9 | s.author = { "lgcjdx" => "lgcjdx@163.com" } 10 | s.platform = :ios, '7.0' 11 | s.source = { :git => "https://github.com/lqcjdx/YLTagsChooser.git", :tag => "#{s.version}" } 12 | s.source_files = "YLTagsChooser/YLTagsChooser/TagsChooser/*.{h,m}" 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/lumin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/Lambert.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ReappearBugCode/YLWaterFlowLayout_Bug.h: -------------------------------------------------------------------------------- 1 | // 2 | // YLWaterFlowLayout.h 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/5/10. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class YLWaterFlowLayout_Bug; 12 | @protocol YLWaterFlowLayoutDelegate 13 | /**通过代理获得每个cell的宽度*/ 14 | - (CGFloat)waterFlowLayout:(YLWaterFlowLayout *)layout widthAtIndexPath:(NSIndexPath *)indexPath; 15 | 16 | @end 17 | 18 | @interface YLWaterFlowLayout_Bug : UICollectionViewFlowLayout 19 | @property (nonatomic,assign) id delegate; 20 | @property(nonatomic,assign)CGFloat rowHeight;///< 固定行高 21 | 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLTag.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLTag.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 2017/6/15. 6 | // Copyright © 2017年 YL. All rights reserved. 7 | // 8 | 9 | #import "YLTag.h" 10 | 11 | @implementation YLTag 12 | - (instancetype)initWithId:(NSInteger)oId name:(NSString *)name 13 | { 14 | if(self = [super init]){ 15 | self.objId = oId; 16 | self.name = name; 17 | } 18 | return self; 19 | } 20 | 21 | - (BOOL)isEqual:(id)object 22 | { 23 | if(!object || ![object isKindOfClass:[self class]]){ 24 | return NO; 25 | } 26 | YLTag *otherTag = (YLTag *)object; 27 | return self.objId == otherTag.objId && [self.name isEqualToString:otherTag.name]; 28 | } 29 | 30 | - (NSString *)description 31 | { 32 | return self.name; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooserTests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooserUITests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/Lambert.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YLTagsChooser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4570422C1D0E580400128041 16 | 17 | primary 18 | 19 | 20 | 457042451D0E580400128041 21 | 22 | primary 23 | 24 | 25 | 457042501D0E580400128041 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/TK-001289.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YLTagsChooser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4570422C1D0E580400128041 16 | 17 | primary 18 | 19 | 20 | 457042451D0E580400128041 21 | 22 | primary 23 | 24 | 25 | 457042501D0E580400128041 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLWaterFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // YLWaterFlowLayout.h 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/5/10. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class YLWaterFlowLayout; 12 | @protocol YLWaterFlowLayoutDelegate 13 | /**通过代理获得每个cell的宽度*/ 14 | - (CGFloat)waterFlowLayout:(YLWaterFlowLayout *)layout widthAtIndexPath:(NSIndexPath *)indexPath; 15 | 16 | @end 17 | 18 | @interface YLWaterFlowLayout : UICollectionViewFlowLayout 19 | @property (nonatomic, weak) id delegate; 20 | @property (nonatomic, assign) CGFloat rowHeight;///< 固定行高 21 | 22 | /** 23 | 如果你的数据源是会改变的,你应该在数据源发生改变后,先调用该方法刷新frames再调用reloadData。 24 | Call this method when your datasource changed and then call UICollectionView's reloadData. 25 | */ 26 | - (void)reCalculateFrames; 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLCollectionReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLCollectionReusableView.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 2016/10/31. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "YLCollectionReusableView.h" 10 | 11 | @interface YLCollectionReusableView () 12 | @property(nonatomic,strong)UILabel *textLabel; 13 | @end 14 | 15 | @implementation YLCollectionReusableView 16 | - (void)prepareForReuse 17 | { 18 | 19 | } 20 | 21 | -(instancetype)initWithFrame:(CGRect)frame 22 | { 23 | if(self = [super initWithFrame:frame]){ 24 | self.backgroundColor = [UIColor whiteColor]; 25 | _textLabel = [[UILabel alloc]initWithFrame:self.bounds]; 26 | _textLabel.textAlignment = NSTextAlignmentCenter; 27 | _textLabel.text = @"This is a section header/footer"; 28 | [self addSubview:_textLabel]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setTitle:(NSString *)title 34 | { 35 | _textLabel.text = title; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooserTests/YLTagsChooserTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLTagsChooserTests.m 3 | // YLTagsChooserTests 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YLTagsChooserTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation YLTagsChooserTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mr Lu 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 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooserUITests/YLTagsChooserUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLTagsChooserUITests.m 3 | // YLTagsChooserUITests 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YLTagsChooserUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation YLTagsChooserUITests 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 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLTagsChooser.h: -------------------------------------------------------------------------------- 1 | // 2 | // YLTagsChooser.h 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol YLTagsChooserDelegate; 12 | 13 | @interface YLTagsChooser : UIView 14 | @property(nonatomic,weak)iddelegate; 15 | @property(nonatomic,assign) CGFloat bottomHeight; ///< 指定白色背景高度 16 | @property(nonatomic,assign) NSInteger maxSelectCount; ///< 最多可选择数量 17 | 18 | /** 19 | 必须调用的初始化方法 20 | you must call this init method 21 | */ 22 | -(instancetype)initWithBottomHeight:(CGFloat)bHeight 23 | maxSelectCount:(CGFloat)maxCount 24 | delegate:(id)aDelegate; 25 | 26 | /** 27 | 刷新 28 | 29 | @param tags 原数据。包含多个数组对象的数组,决定有几个section 30 | @param selectedTags 已选择的数据 31 | */ 32 | -(void)refreshWithTags:(NSArray *)tags selectedTags:(NSArray *)selectedTags; 33 | - (void)showInView:(UIView *)view; 34 | - (void)dismiss; 35 | 36 | @end 37 | 38 | 39 | //代理 40 | @protocol YLTagsChooserDelegate 41 | 42 | - (void)tagsChooser:(YLTagsChooser *)sheet selectedTags:(NSArray *)sTags; 43 | 44 | 45 | @end 46 | 47 | #pragma mark---标签cell 48 | @interface YLTagsCollectionViewCell : UICollectionViewCell 49 | @property(nonatomic,strong)UILabel *textLabel; 50 | 51 | - (void)refreshWithObject:(NSObject *)obj; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/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.6 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1510 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/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 | 29 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/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" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. 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 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "YLTagsChooser.h" 11 | #import "YLTag.h" 12 | 13 | @interface ViewController () 14 | { 15 | NSMutableArray *selectedTags; 16 | } 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | selectedTags = [NSMutableArray array]; 25 | } 26 | 27 | - (void)didReceiveMemoryWarning { 28 | [super didReceiveMemoryWarning]; 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | - (IBAction)chooseTags:(id)sender { 33 | _textLabel.text = nil; 34 | YLTagsChooser *chooser = [[YLTagsChooser alloc]initWithBottomHeight:500 maxSelectCount:8 delegate:self]; 35 | NSMutableArray *orignDataArray = [NSMutableArray array]; 36 | NSArray *tags = @[@"篮球", 37 | @"足球", 38 | @"羽毛球", 39 | @"乒乓球", 40 | @"排球", 41 | @"网球", 42 | @"高尔夫球", 43 | @"冰球", 44 | @"沙滩排球", 45 | @"棒球", 46 | @"垒球", 47 | @"藤球", 48 | @"毽球", 49 | @"台球", 50 | @"鞠蹴", 51 | @"板球", 52 | @"壁球", 53 | @"沙壶", 54 | @"克郎球", 55 | @"橄榄球", 56 | @"曲棍球", 57 | @"水球", 58 | @"马球", 59 | @"保龄球", 60 | @"健身球", 61 | @"门球", 62 | @"弹球" 63 | ]; 64 | NSInteger index = tags.count; 65 | NSMutableArray *testTags0 = [NSMutableArray arrayWithCapacity:tags.count]; 66 | for(NSInteger i = 0; i < index; i++){ 67 | YLTag *tag = [[YLTag alloc]initWithId:i name:tags[i]]; 68 | [testTags0 addObject:tag]; 69 | } 70 | [orignDataArray addObject:testTags0]; 71 | 72 | //一个section 73 | // [chooser showInView:self.view]; 74 | // [chooser refreshWithTags:orignDataArray selectedTags:selectedTags]; 75 | // return; 76 | 77 | NSMutableArray *testTags1 = [NSMutableArray arrayWithCapacity:40]; 78 | for(NSInteger i = index; i < index + 40; i++){ 79 | NSString *name; 80 | if(i % 4 == 0){ 81 | name = [NSString stringWithFormat:@"Remember%li",(long)i]; 82 | }else if (i % 4 == 1){ 83 | name = [NSString stringWithFormat:@"Remember Give%li",(long)i]; 84 | }else if(i % 4 == 2){ 85 | name = [NSString stringWithFormat:@"Remember Give Me a@%li",(long)i]; 86 | }else{ 87 | name = [NSString stringWithFormat:@"Remember Give Me a Star@%li",(long)i]; 88 | } 89 | YLTag *tag = [[YLTag alloc]initWithId:i name:name]; 90 | [testTags1 addObject:tag]; 91 | } 92 | [orignDataArray addObject:testTags1]; 93 | 94 | 95 | index += 40; 96 | NSMutableArray *testTags2 = [NSMutableArray arrayWithCapacity:20]; 97 | for(NSInteger i = index; i < index + 20; i++){ 98 | NSString *name; 99 | if(i % 3 == 0){ 100 | name = [NSString stringWithFormat:@"标签选择器%li",(long)i]; 101 | }else if (i % 3 == 1){ 102 | name = [NSString stringWithFormat:@"Lambert%li",(long)i]; 103 | }else{ 104 | name = [NSString stringWithFormat:@"CodeNinja%li",(long)i]; 105 | } 106 | YLTag *tag = [[YLTag alloc]initWithId:i name:name]; 107 | [testTags2 addObject:tag]; 108 | } 109 | [orignDataArray addObject:testTags2]; 110 | 111 | //多个section 112 | [chooser showInView:self.view]; 113 | [chooser refreshWithTags:orignDataArray selectedTags:selectedTags]; 114 | } 115 | 116 | 117 | #pragma mark---YLTagsChooserDelegate 118 | - (void)tagsChooser:(YLTagsChooser *)sheet selectedTags:(NSArray *)sTags 119 | { 120 | [selectedTags removeAllObjects]; 121 | [selectedTags addObjectsFromArray:sTags]; 122 | NSString *tagStr = [sTags componentsJoinedByString:@"\n"]; 123 | NSString *result = [NSString stringWithFormat:@"你选择了以下标签:\n%@",tagStr]; 124 | _textLabel.text = result; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/Lambert.xcuserdatad/xcschemes/YLTagsChooser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/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 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/xcuserdata/TK-001289.xcuserdatad/xcschemes/YLTagsChooser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 89 | 90 | 91 | 92 | 93 | 94 | 100 | 102 | 108 | 109 | 110 | 111 | 113 | 114 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YLTagsChooser 2 | 用UICollectionView实现的兴趣标签选择器,可传入最多可选择标签数量和上一次选中的标签。 3 | 4 | **注意:UICollectionView在cell较多的情况下,在滑动的过程中会出现某些cell一会显示一会不显示的问题已解决,欢迎star和提出优化方案。** 5 | 6 | 在这里我用UICollectionView实现了高度固定,宽度不固定,每行显示的标签个数不定的瀑布流效果。本示例只简单展示了类似UIActionSheet弹出效果。可以模仿实现其他类似效果。 7 | 8 | 核心思想就是在- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath方法中通过计算上一个cell的frame和当前cell的frame来判断将当前cell显示在什么位置。具体实现见代码。 9 | 10 | --- 11 | A interest tag selector implemented with the UICollectionView, which allows you to pass in the maximum number of selectable tags and the last selected tags. 12 | 13 | Here I use UICollectionView to achieve a high degree of fixed, the width is not fixed, each row shows the number of labels on the waterfall flow effect. This example only shows a similar UIActionSheet pop-up effect. It also Can be imitated to achieve other similar effects. 14 | 15 | 16 | ## 效果图 Demonstration : 17 | 18 | ![](https://github.com/lqcjdx/YLTagsChooser/blob/master/YLTagsChooser.gif) 19 | 20 | 21 | ## 2016-11-1 update 22 | 23 | 一、修复数据源较多的情况下部分cell不显示的bug:[标签很多的时候,中间cell会不显示,而且会出现很多空白的地方 #1](https://github.com/lqcjdx/YLTagsChooser/issues/1) (fixed bug #1) 24 | 25 | 二、实现可添加sectionheader的效果。(Show how to add a UICollectionReusableView.) 26 | 27 | 因为有人反映自定义UICollectionViewFlowLayout的情况下,方法 28 | 29 | ```OBJC 30 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView 31 | viewForSupplementaryElementOfKind:(NSString *)kind 32 | atIndexPath:(NSIndexPath *)indexPath 33 | ``` 34 | 35 | 不调用的问题。我也做了尝试,确实是很容易就造成这个方法不被调用。 36 | 37 | 实现步骤总结如下: 38 | 39 | 1、设置自定义UICollectionViewFlowLayout对象的headerReferenceSize属性或者实现UICollectionViewDelegateFlowLayout的 40 | 41 | ```OBJC 42 | - (CGSize)collectionView:(UICollectionView *)collectionView 43 | layout:(UICollectionViewLayout*)collectionViewLayout 44 | referenceSizeForHeaderInSection:(NSInteger)section 45 | ``` 46 | 47 | 方法返回sectionheader的高度; 48 | 49 | 2、注册sectionheader: 50 | 51 | ```OBJC 52 | [_myCollectionView registerClass:[YLCollectionReusableView class] 53 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 54 | withReuseIdentifier:@"YLCollectionReusableView”] 55 | ``` 56 | 57 | 58 | 3、在方法 59 | 60 | ```OBJC 61 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView 62 | viewForSupplementaryElementOfKind:(NSString *)kind 63 | atIndexPath:(NSIndexPath *)indexPath 64 | ``` 65 | 66 | 中返回自定义sectionheader; 67 | 68 | 69 | 需要注意的是在``- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect``方法中应该加上判断当前UICollectionViewLayoutAttributes的representedElementCategory属性是否是UICollectionElementCategoryCell类型的,不能在这里改变了sectionheader的高度导致显示异常。因为所有view和cell的属性都会调用该方法。 70 | 71 | ## 2017-06-15 update 72 | 73 | 一、新增默认选中的标签,下次进入页面会默认显示上次选中的标签。[Can we retain selected tag ?](https://github.com/lqcjdx/YLTagsChooser/issues/2)(fixed bug #2) 74 | 75 | 可能同一个APP内会有多个页面会使用到标签选择器,并且每次选择的标签类型不太一样,所以建议将标签封装为一个对象:YLTag,每次显示YLTagsChooser的时候传入上次选中的值。 76 | 77 | Maybe you have multiple pages in your APP will use the tags selector, and each time you select the tags, the type is not the same, it is recommended to package the tag as an object: YLTag, each time you display YLTagsChooser, you can pass the last selected The value to it. 78 | 79 | 80 | ## 2017-09-07 update 81 | 82 | 一、支持多Section,每个section一个header,解决高度计算问题。使用方法: 83 | 将每个分区的数据单独放在一个数组中,最后这些数组都放在orignDataArray里面,调用以下方法传入数据即可。 84 | 85 | ```OBJC 86 | [chooser refreshWithTags:orignDataArray selectedTags:selectedTags]; 87 | ``` 88 | 89 | ## 2017-09-08 update 90 | 91 | 一、解决UICollectionView在滑动过程中有些cell一会显示一会不显示的问题。[UICollectionView的重大bug?真相竟然是......](http://www.jianshu.com/p/c07e28d3563d) 92 | 93 | 搜索``UICollectionView some cell not appear``或``UICollectionViewCell not appear``可见相关问题。 94 | 95 | 这个bug主要是cell较多并且cell的frame属性自定义的情况下很容易发生。 96 | 97 | 因为我们的每个item的属性都是完全自定义的,所以在``layoutAttributesForElementsInRect:``方法如果直接调用``[super layoutAttributesForElementsInRect: rect]``,拿到的这个集合其实是不对的,所以或导致有些cell在滑动过程中一会消失一会显示。 98 | 99 | 100 | ### 下面简单讲一下解决思路: 101 | 102 | 自定义UICollectionViewLayout必须覆盖的方法且执行顺序如下: 103 | 104 | ```OBJC 105 | - (void)prepareLayout; 106 | - (CGSize)collectionViewContentSize; 107 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; 108 | ``` 109 | 110 | - prepareLayout 111 | 112 | 布局快要生效的时候被调用,在这里将每个item的frame属性计算出来并缓存起来。 113 | 114 | - collectionViewContentSize 115 | 116 | UICollectionView通过它获取可滑动范围,在这里根据上面计算的值获取最大可滚动范围。 117 | 118 | - layoutAttributesForElementsInRect: 119 | 120 | 返回指定区域内的item的布局属性(UICollectionViewLayoutAttributes对象)集合。 121 | 122 | 解决这个bug的要点是在``layoutAttributesForElementsInRect:``方法中先获取UICollectionView当前的可见范围,然后根据第一步计算并缓存起来的item属性来判断当前可见范围内有哪些item应该是可见的,把这些item对应的UICollectionViewLayoutAttributes对象放到一个数组中返回。 123 | 124 | ## 2018-2-7 update 125 | 126 | Fix bug3, support set UICollectionView's contentInset and also support set setionHeader and sectionFooter at the same time. 127 | 128 | 修复bug:[设置 collectionView.contentInset 时候,就会有问题 129 | #3](https://github.com/lqcjdx/YLTagsChooser/issues/3) (fixed bug #3) 130 | 131 | - 新增支持设置UICollectionview的contentInset。 132 | 133 | 举个栗子: 134 | 135 | 设置了``_myCollectionView.contentInset = UIEdgeInsetsMake(30, 20, 30, 20);``之后,需要在YLWaterFlowLayout中的如下方法返回UICollectionView的可滚动范围,否则你会发现它竟然可以左右滚动。 136 | 137 | ```OBJC 138 | #pragma mark - CollectionView的滚动范围 139 | //step2 140 | - (CGSize)collectionViewContentSize 141 | { 142 | //support set collectionview's contentInset 143 | CGFloat width = self.collectionView.frame.size.width - self.collectionView.contentInset.left - self.collectionView.contentInset.right; 144 | return CGSizeMake(width, _contentHeight); 145 | } 146 | ``` 147 | 148 | - 新增支持UICollectionview同时设置sectionHeader和sectionFooter。 149 | 150 | 已优化YLWaterFlowLayout的代码,支持同时设置UICollectionView的contentInset、layout的sectionInset、layout的headerReferenceSize和layout的footerReferenceSize,也可以正常地全部展示header、footer和cell。 151 | 152 | 效果图如下: 153 | 154 | ![](https://github.com/lqcjdx/YLTagsChooser/blob/master/fix_bug3.gif) 155 | 156 | 157 | 158 | 当然,这里只是提供一种解决UICollectionView滚动过程中部分cell一会显示一会不显示的问题思路。如果你需要实现其他效果,你可以自己尝试用我这种思路去实现,或者提出来,大家一起讨论一下怎么实现。如果帮到了你,请留下一个Star表示支持,谢谢! 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /ReappearBugCode/YLWaterFlowLayout_Bug.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLWaterFlowLayout.m 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/5/10. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "YLWaterFlowLayout_Bug.h" 10 | #import "NSArray+YLBoundsCheck.h" 11 | 12 | @interface YLWaterFlowLayout_Bug() 13 | @property(nonatomic,strong)NSMutableArray *framesArray; 14 | @end 15 | 16 | 17 | @implementation YLWaterFlowLayout_Bug 18 | #pragma mark - 初始化属性 19 | - (instancetype)init { 20 | self = [super init]; 21 | if (self) { 22 | [self setupLayout]; 23 | _framesArray = [NSMutableArray array]; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)setupLayout 29 | { 30 | self.minimumInteritemSpacing = 10;//同一行不同cell间距 31 | self.minimumLineSpacing = 10;//行间距 32 | self.headerReferenceSize = CGSizeMake(0, 50);//设置section header 固定高度,如果需要的话 33 | self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 34 | self.scrollDirection = UICollectionViewScrollDirectionVertical; 35 | } 36 | 37 | #pragma mark - 重写父类的方法,实现瀑布流布局 38 | #pragma mark - 当尺寸有所变化时,重新刷新 39 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { 40 | return YES; 41 | } 42 | 43 | - (void)prepareLayout { 44 | [super prepareLayout]; 45 | } 46 | 47 | #pragma mark - CollectionView的滚动范围 48 | - (CGSize)collectionViewContentSize 49 | { 50 | CGFloat width = self.collectionView.frame.size.width; 51 | CGFloat maxY = [self maxOrignYInSection:_framesArray.count - 1]; 52 | return CGSizeMake(width, maxY + _rowHeight + self.sectionInset.bottom); 53 | } 54 | 55 | #pragma mark - 所有cell和view的布局属性 56 | //sectionheader sectionfooter decorationview collectionviewcell的属性都会走这个方法 57 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 58 | { 59 | NSArray *tmpArray = [super layoutAttributesForElementsInRect:rect]; 60 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:tmpArray.count]; 61 | for(NSInteger i = 0; i < tmpArray.count; i++){ 62 | UICollectionViewLayoutAttributes *attrs = [tmpArray objectAtIndex:i]; 63 | UICollectionElementCategory category = attrs.representedElementCategory; 64 | if(category == UICollectionElementCategoryCell){ 65 | [array addObject:[self layoutAttributesForItemAtIndexPath:attrs.indexPath]]; 66 | }else if (category == UICollectionElementCategorySupplementaryView){ 67 | UICollectionViewLayoutAttributes *theAttrs = [self layoutAttributesForSupplementaryViewOfKind:attrs.representedElementKind atIndexPath:attrs.indexPath]; 68 | [array addObject:theAttrs]; 69 | } 70 | } 71 | return array; 72 | } 73 | 74 | #pragma mark - 指定cell的布局属性 75 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath 76 | { 77 | UICollectionViewLayoutAttributes *attrs = [[super layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath] copy]; 78 | NSInteger section = attrs.indexPath.section; 79 | if(section > 0 && _framesArray.count > section - 1){ 80 | CGFloat y = [self maxOrignYInSection:section - 1] + _rowHeight + self.sectionInset.bottom; 81 | CGRect frame = attrs.frame; 82 | frame.origin.y = y; 83 | attrs.frame = frame; 84 | } 85 | return attrs; 86 | } 87 | 88 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 89 | { 90 | NSInteger section = indexPath.section; 91 | NSInteger row = indexPath.row; 92 | UICollectionViewLayoutAttributes *attrs = [[super layoutAttributesForItemAtIndexPath:indexPath] copy]; 93 | //已经计算过了 94 | if(_framesArray.count > section){ 95 | NSArray *sectionFramesArray = _framesArray[section]; 96 | if(sectionFramesArray.count > row){ 97 | attrs.frame = [sectionFramesArray[row] CGRectValue]; 98 | return attrs; 99 | } 100 | } 101 | 102 | //计算新的 103 | CGFloat x = self.sectionInset.left; 104 | //如果有sectionheader需要加上sectionheader高度 105 | CGFloat y = self.headerReferenceSize.height + self.sectionInset.top; 106 | 107 | if(section > 0 && _framesArray.count > section - 1){ 108 | y = [self maxOrignYInSection:section - 1] + _rowHeight + self.sectionInset.bottom + self.headerReferenceSize.height + self.sectionInset.top; 109 | } 110 | 111 | NSMutableArray *sectionFramesArray = [_framesArray yl_objectAtIndex:section]; 112 | if(!sectionFramesArray){ 113 | sectionFramesArray = [NSMutableArray array]; 114 | _framesArray[section] = sectionFramesArray; 115 | } 116 | 117 | //判断获得前一个cell的frame 118 | NSInteger preRow = row - 1; 119 | if(preRow >= 0 && sectionFramesArray.count > preRow){ 120 | CGRect preCellFrame = [sectionFramesArray[preRow] CGRectValue]; 121 | x = preCellFrame.origin.x + preCellFrame.size.width + self.minimumInteritemSpacing; 122 | y = preCellFrame.origin.y; 123 | } 124 | 125 | CGFloat currentWidth = [self.delegate waterFlowLayout:self widthAtIndexPath:indexPath]; 126 | //保证一个cell不超过最大宽度 127 | currentWidth = MIN(currentWidth, self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right); 128 | if(x + currentWidth > self.collectionView.frame.size.width - self.sectionInset.right){ 129 | //超出范围,换行 130 | x = self.sectionInset.left; 131 | y += _rowHeight + self.minimumLineSpacing; 132 | } 133 | // 创建属性 134 | CGRect currentCellFrame = CGRectMake(x, y, currentWidth, _rowHeight); 135 | attrs.frame = currentCellFrame; 136 | sectionFramesArray[row] = [NSValue valueWithCGRect:currentCellFrame]; 137 | return attrs; 138 | } 139 | 140 | #pragma mark---other methods 141 | - (CGFloat)maxOrignYInSection:(NSInteger)section 142 | { 143 | __block CGFloat maxY = 0; 144 | if(section >= 0 && _framesArray.count > section){ 145 | NSArray *sectionYArray = [_framesArray yl_objectAtIndex:section]; 146 | [sectionYArray enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger idx, BOOL * _Nonnull stop) { 147 | CGRect frame = [value CGRectValue]; 148 | CGFloat y = frame.origin.y; 149 | if (y > maxY) { 150 | maxY = y; 151 | } 152 | }]; 153 | } 154 | return maxY; 155 | } 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLWaterFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLWaterFlowLayout.m 3 | // YLLanJiQuan 4 | // 5 | // Created by TK-001289 on 16/5/10. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "YLWaterFlowLayout.h" 10 | #import "NSArray+YLBoundsCheck.h" 11 | #import 12 | 13 | @interface YLWaterFlowLayout() 14 | @property(nonatomic,strong)NSMutableArray *framesArray; 15 | @property(nonatomic,assign)CGFloat contentHeight; 16 | @end 17 | 18 | 19 | @implementation YLWaterFlowLayout 20 | #pragma mark - 初始化属性 21 | - (instancetype)init { 22 | self = [super init]; 23 | if (self) { 24 | [self setupLayout]; 25 | _framesArray = [NSMutableArray array]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)setupLayout 31 | { 32 | self.minimumInteritemSpacing = 10;//同一行不同cell间距 33 | self.minimumLineSpacing = 10;//行间距 34 | //support set sectionheader and sectionfooter at the same time 35 | self.headerReferenceSize = CGSizeMake(CGRectGetWidth(self.collectionView.frame), 50);//设置section header 固定高度,如果需要的话 36 | self.footerReferenceSize = CGSizeMake(CGRectGetWidth(self.collectionView.frame), 30);//设置section footer 固定高度,如果需要的话 37 | self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 38 | self.scrollDirection = UICollectionViewScrollDirectionVertical; 39 | } 40 | 41 | #pragma mark - 重写父类的方法,实现瀑布流布局 42 | //step1 43 | - (void)prepareLayout { 44 | [super prepareLayout]; 45 | [self calculateFrames]; 46 | } 47 | 48 | #pragma mark - CollectionView的滚动范围 49 | //step2 50 | - (CGSize)collectionViewContentSize 51 | { 52 | //support set collectionview's contentInset 53 | CGFloat width = self.collectionView.frame.size.width - self.collectionView.contentInset.left - self.collectionView.contentInset.right; 54 | return CGSizeMake(width, _contentHeight); 55 | } 56 | 57 | #pragma mark - 当尺寸有所变化时,重新刷新 58 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { 59 | return YES; 60 | } 61 | 62 | #pragma mark - 所有cell和view的布局属性 63 | //sectionheader sectionfooter decorationview collectionviewcell的属性都会走这个方法 64 | //step3 65 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 66 | { 67 | NSMutableArray *attributesArray = [NSMutableArray array]; 68 | CGPoint offset = self.collectionView.contentOffset; 69 | CGRect visibleRect = CGRectMake(0, offset.y, CGRectGetWidth(self.collectionView.frame), CGRectGetHeight(self.collectionView.frame)); 70 | for(NSInteger section = 0; section < _framesArray.count; section++){ 71 | CGFloat currentSectionHeight = [self contentHeightInSection:section]; 72 | CGFloat preSectionHeight = 0; 73 | if(section > 0){ 74 | preSectionHeight = [self contentHeightInSection:section - 1]; 75 | } 76 | 77 | NSArray *currentSectionFrames = _framesArray[section]; 78 | if(currentSectionFrames.count == 0){ 79 | //section header should show 80 | CGFloat y = section == 0 ? self.collectionView.contentInset.top : preSectionHeight; 81 | UICollectionViewLayoutAttributes *ha = [self headerAttri:[NSIndexPath indexPathForRow:0 inSection:section] withY:y]; 82 | if(ha){ 83 | [attributesArray addObject:ha]; 84 | } 85 | 86 | //current section footer should show 87 | NSIndexPath *footerIndexPath = [NSIndexPath indexPathForRow:0 inSection:section]; 88 | y = currentSectionHeight - self.footerReferenceSize.height; 89 | UICollectionViewLayoutAttributes *fa = [self footerAttri:footerIndexPath withY:y]; 90 | if(fa){ 91 | [attributesArray addObject:fa]; 92 | } 93 | 94 | if(section > 0 && section + 1 < _framesArray.count && 95 | y + self.footerReferenceSize.height <= visibleRect.origin.y + visibleRect.size.height){ 96 | //next section header should show 97 | UICollectionViewLayoutAttributes *ha = [self headerAttri:[NSIndexPath indexPathForRow:0 inSection:section + 1] withY:y + self.footerReferenceSize.height]; 98 | if(ha){ 99 | [attributesArray addObject:ha]; 100 | } 101 | } 102 | }else{ 103 | for(NSInteger row = 0; row < currentSectionFrames.count; row++){ 104 | CGRect currentFrame = [currentSectionFrames[row] CGRectValue]; 105 | NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; 106 | if(currentFrame.origin.y + currentFrame.size.height >= visibleRect.origin.y && 107 | currentFrame.origin.y <= visibleRect.origin.y + visibleRect.size.height){ 108 | //when first row appear 109 | if(row == 0){ 110 | //section header should show 111 | CGFloat y = section == 0 ? self.collectionView.contentInset.top : preSectionHeight; 112 | UICollectionViewLayoutAttributes *fa = [self headerAttri:currentIndexPath withY:y]; 113 | if(fa){ 114 | [attributesArray addObject:fa]; 115 | } 116 | 117 | if(section > 0){ 118 | //pre section's footer should show 119 | NSInteger preSection = section - 1; 120 | NSArray *preSectionFrames = _framesArray[preSection]; 121 | NSIndexPath *footerIndexPath = [NSIndexPath indexPathForRow:preSectionFrames.count inSection:preSection]; 122 | CGFloat y = preSectionHeight - self.footerReferenceSize.height; 123 | UICollectionViewLayoutAttributes *fa = [self footerAttri:footerIndexPath withY:y]; 124 | if(fa){ 125 | [attributesArray addObject:fa]; 126 | } 127 | } 128 | } 129 | 130 | //cells should show 131 | UICollectionViewLayoutAttributes *cellAttrs = [[self layoutAttributesForItemAtIndexPath:currentIndexPath] copy]; 132 | cellAttrs.frame = currentFrame; 133 | [attributesArray addObject:cellAttrs]; 134 | 135 | //when last cell appear 136 | if(row == currentSectionFrames.count - 1 && 137 | currentFrame.origin.y + currentFrame.size.height + self.sectionInset.bottom <= visibleRect.origin.y + visibleRect.size.height){ 138 | //current section footer should show 139 | NSIndexPath *footerIndexPath = [NSIndexPath indexPathForRow:currentSectionFrames.count inSection:section]; 140 | CGFloat y = currentSectionHeight - self.footerReferenceSize.height; 141 | UICollectionViewLayoutAttributes *fa = [self footerAttri:footerIndexPath withY:y]; 142 | if(fa){ 143 | [attributesArray addObject:fa]; 144 | } 145 | 146 | //next section header should show 147 | NSInteger nextSection = section + 1; 148 | if(nextSection < _framesArray.count){ 149 | NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:nextSection]; 150 | UICollectionViewLayoutAttributes *ha = [self headerAttri:headerIndexPath withY:currentSectionHeight]; 151 | if(ha){ 152 | [attributesArray addObject:ha]; 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | return attributesArray; 161 | } 162 | 163 | #pragma mark---other methods 164 | - (UICollectionViewLayoutAttributes *)headerAttri:(NSIndexPath *)indexPath withY:(CGFloat)y 165 | { 166 | UICollectionViewLayoutAttributes *headerAttr = [[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 167 | atIndexPath:indexPath] copy]; 168 | CGRect frame = headerAttr.frame; 169 | frame.origin.y = y; 170 | headerAttr.frame = frame; 171 | return headerAttr; 172 | } 173 | 174 | - (UICollectionViewLayoutAttributes *)footerAttri:(NSIndexPath *)indexPath withY:(CGFloat)y 175 | { 176 | UICollectionViewLayoutAttributes *footerAttr = [[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter 177 | atIndexPath:indexPath] copy]; 178 | CGRect frame = footerAttr.frame; 179 | frame.origin.y = y; 180 | footerAttr.frame = frame; 181 | return footerAttr; 182 | } 183 | 184 | - (void)reCalculateFrames 185 | { 186 | [_framesArray removeAllObjects]; 187 | [self calculateFrames]; 188 | } 189 | 190 | - (void)calculateFrames 191 | { 192 | if(_framesArray.count > 0){ 193 | return; 194 | } 195 | 196 | NSInteger sectionCount = self.collectionView.numberOfSections; 197 | for(NSInteger section = 0; section < sectionCount; section++){ 198 | CGFloat preSectionHeight = 0; 199 | if(section > 0){ 200 | preSectionHeight = [self contentHeightInSection:section - 1]; 201 | } 202 | 203 | NSMutableArray *currentSectionFrames = [NSMutableArray arrayWithCapacity:sectionCount]; 204 | [_framesArray addObject:currentSectionFrames]; 205 | 206 | NSInteger numberOfRows = [self.collectionView numberOfItemsInSection:section]; 207 | for(NSInteger row = 0; row < numberOfRows; row++){ 208 | CGFloat x = self.sectionInset.left; 209 | //如果有sectionheader需要加上sectionheader高度 210 | CGFloat y = self.headerReferenceSize.height + self.sectionInset.top + self.collectionView.contentInset.top; 211 | 212 | if(section > 0 && _framesArray.count > section - 1){ 213 | y = preSectionHeight + self.headerReferenceSize.height + self.sectionInset.top; 214 | } 215 | //判断获得前一个cell的frame 216 | NSInteger preRow = row - 1; 217 | if(preRow >= 0 && currentSectionFrames.count > preRow){ 218 | CGRect preCellFrame = [currentSectionFrames[preRow] CGRectValue]; 219 | x = preCellFrame.origin.x + preCellFrame.size.width + self.minimumInteritemSpacing; 220 | y = preCellFrame.origin.y; 221 | } 222 | 223 | NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; 224 | CGFloat currentWidth = [self.delegate waterFlowLayout:self widthAtIndexPath:currentIndexPath]; 225 | //保证一个cell不超过最大宽度 226 | CGFloat maxCellWidth = self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - self.collectionView.contentInset.left - self.collectionView.contentInset.right; 227 | currentWidth = MIN(currentWidth, maxCellWidth); 228 | if(x + currentWidth > self.collectionView.frame.size.width - self.sectionInset.right - self.collectionView.contentInset.right){ 229 | //超出范围,换行 230 | x = self.sectionInset.left; 231 | y += _rowHeight + self.minimumLineSpacing; 232 | } 233 | // 创建属性 234 | CGRect currentCellFrame = CGRectMake(x, y, currentWidth, _rowHeight); 235 | [currentSectionFrames addObject:[NSValue valueWithCGRect:currentCellFrame]]; 236 | } 237 | } 238 | _contentHeight = [self contentHeightInSection:_framesArray.count - 1] + self.collectionView.contentInset.bottom; 239 | } 240 | 241 | - (CGFloat)contentHeightInSection:(NSInteger)section 242 | { 243 | __block CGFloat height = 0; 244 | if(section >= 0 && _framesArray.count > section){ 245 | NSArray *sectionYArray = [_framesArray objectAtIndex:section]; 246 | if(sectionYArray.count == 0){ 247 | height = self.headerReferenceSize.height + self.sectionInset.top + self.sectionInset.bottom + self.footerReferenceSize.height; 248 | if(section == 0){ 249 | height += self.collectionView.contentInset.top; 250 | }else{ 251 | height += [self contentHeightInSection:section - 1]; 252 | } 253 | }else{ 254 | [sectionYArray enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger idx, BOOL * _Nonnull stop) { 255 | CGRect frame = [value CGRectValue]; 256 | CGFloat y = frame.origin.y; 257 | if (y > height) { 258 | height = y; 259 | } 260 | }]; 261 | height += _rowHeight + self.sectionInset.bottom + self.footerReferenceSize.height; 262 | } 263 | } 264 | return height; 265 | } 266 | 267 | @end 268 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser/TagsChooser/YLTagsChooser.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLTagsChooser.m 3 | // YLTagsChooser 4 | // 5 | // Created by TK-001289 on 16/6/13. 6 | // Copyright © 2016年 YL. All rights reserved. 7 | // 8 | 9 | #import "YLTagsChooser.h" 10 | #import "NSArray+YLBoundsCheck.h" 11 | #import "YLWaterFlowLayout.h" 12 | #import "YLCollectionReusableView.h" 13 | #import "YLTag.h" 14 | 15 | #define HEXCOLOR(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 16 | 17 | // 屏幕尺寸 18 | #define kFrameWidth [UIScreen mainScreen].bounds.size.width 19 | #define kFrameHeight [UIScreen mainScreen].bounds.size.height 20 | 21 | static NSString *headerIdentifier = @"YLCollectionReusableView"; 22 | static NSString *footerIdentifier = @"YLCollectionReusableView"; 23 | static NSString *cellIdentifier = @"YLTagsCollectionViewCell"; 24 | 25 | static NSTimeInterval const kSheetAnimationDuration = 0.25; 26 | static CGFloat const kBottomBtnHeight = 44.f; 27 | static CGFloat const kBottomGap = 24.f; 28 | static CGFloat const kYGap = 10.f; 29 | 30 | @interface YLTagsChooser() 31 | { 32 | 33 | } 34 | @property (nonatomic,strong) UIView *bottomView; 35 | @property (nonatomic,strong) UICollectionView *myCollectionView; 36 | @property (nonatomic,strong) UIButton *ensureBtn; 37 | @property (nonatomic,strong) NSMutableArray *orignalTags; 38 | @property (nonatomic,strong) NSMutableArray *selectedTags; 39 | 40 | @end 41 | 42 | 43 | @implementation YLTagsChooser 44 | -(instancetype)initWithBottomHeight:(CGFloat)bHeight 45 | maxSelectCount:(CGFloat)maxCount 46 | delegate:(id)aDelegate 47 | { 48 | if(self = [super initWithFrame:CGRectMake(0, 0, kFrameWidth, kFrameHeight)]){ 49 | _orignalTags = [NSMutableArray array]; 50 | _selectedTags = [NSMutableArray array]; 51 | self.alpha = 0.f; 52 | self.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.5]; 53 | 54 | self.bottomHeight = bHeight; 55 | self.maxSelectCount = maxCount; 56 | self.delegate = aDelegate; 57 | 58 | [self.bottomView addSubview:self.myCollectionView]; 59 | [self.bottomView addSubview:self.ensureBtn]; 60 | [self addSubview:self.bottomView]; 61 | } 62 | return self; 63 | } 64 | 65 | -(void)refreshWithTags:(NSArray *)tags selectedTags:(NSArray *)selectedTags 66 | { 67 | [_orignalTags removeAllObjects]; 68 | [_orignalTags addObjectsFromArray:tags]; 69 | 70 | [_selectedTags removeAllObjects]; 71 | [_selectedTags addObjectsFromArray:selectedTags]; 72 | 73 | for(NSArray *array in _orignalTags){ 74 | for(YLTag *tag in array){ 75 | tag.selected = [_selectedTags containsObject:tag]; 76 | } 77 | } 78 | [self.myCollectionView reloadData]; 79 | } 80 | 81 | -(UIView *)bottomView 82 | { 83 | if(!_bottomView){ 84 | _bottomView = [[UIView alloc]init]; 85 | _bottomView.backgroundColor = [UIColor groupTableViewBackgroundColor]; 86 | _bottomView.frame = CGRectMake(0, kFrameHeight, kFrameWidth, _bottomHeight); 87 | } 88 | return _bottomView; 89 | } 90 | 91 | -(UICollectionView *)myCollectionView 92 | { 93 | if(!_myCollectionView){ 94 | YLWaterFlowLayout *layout = [[YLWaterFlowLayout alloc]init]; 95 | layout.rowHeight = 30.f; 96 | layout.delegate = self; 97 | 98 | _myCollectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, kYGap, kFrameWidth, _bottomHeight - 2 * kYGap - kBottomGap - kBottomBtnHeight) 99 | collectionViewLayout:layout]; 100 | _myCollectionView.backgroundColor = [UIColor clearColor]; 101 | [_myCollectionView registerClass:[YLCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:headerIdentifier]; 102 | [_myCollectionView registerClass:[YLCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:footerIdentifier]; 103 | [_myCollectionView registerClass:[YLTagsCollectionViewCell class] forCellWithReuseIdentifier:cellIdentifier]; 104 | _myCollectionView.dataSource = self; 105 | _myCollectionView.delegate = self; 106 | _myCollectionView.showsHorizontalScrollIndicator = NO; 107 | //support set collectionview's contentInset 108 | // _myCollectionView.contentInset = UIEdgeInsetsMake(30, 20, 30, 20); 109 | } 110 | return _myCollectionView; 111 | } 112 | 113 | -(UIButton *)ensureBtn 114 | { 115 | if(!_ensureBtn){ 116 | _ensureBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 117 | _ensureBtn.backgroundColor = HEXCOLOR(0x25c5b6); 118 | _ensureBtn.layer.cornerRadius = 5; 119 | _ensureBtn.layer.masksToBounds = YES; 120 | _ensureBtn.titleLabel.font = [UIFont systemFontOfSize:16]; 121 | [_ensureBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 122 | [_ensureBtn setTitleColor:[[UIColor whiteColor]colorWithAlphaComponent:0.6] forState:UIControlStateHighlighted]; 123 | [_ensureBtn setTitle:@"确定" forState:UIControlStateNormal]; 124 | [_ensureBtn addTarget:self action:@selector(ensureAction) forControlEvents:UIControlEventTouchUpInside]; 125 | _ensureBtn.frame = CGRectMake(10, _bottomHeight - kBottomGap - kBottomBtnHeight, kFrameWidth - 20, kBottomBtnHeight); 126 | } 127 | return _ensureBtn; 128 | } 129 | 130 | #pragma mark---UICollectionViewDataSource 131 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 132 | { 133 | return _orignalTags.count; 134 | } 135 | 136 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 137 | { 138 | NSArray *sectionData = [_orignalTags yl_objectAtIndex:section]; 139 | if([sectionData isKindOfClass:[NSArray class]]){ 140 | return sectionData.count; 141 | } 142 | return 0; 143 | } 144 | 145 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 146 | { 147 | YLTagsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier 148 | forIndexPath:indexPath]; 149 | NSArray *sectionData = [_orignalTags yl_objectAtIndex:indexPath.section]; 150 | YLTag *tag = [sectionData yl_objectAtIndex:indexPath.row]; 151 | [cell refreshWithObject:tag]; 152 | return cell; 153 | } 154 | 155 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 156 | { 157 | if([kind isEqualToString:UICollectionElementKindSectionHeader]){ 158 | YLCollectionReusableView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:headerIdentifier forIndexPath:indexPath]; 159 | [header setTitle:[NSString stringWithFormat:@"Section Header %li",(long)indexPath.section]]; 160 | return header; 161 | }else{ 162 | YLCollectionReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:footerIdentifier forIndexPath:indexPath]; 163 | [footer setTitle:[NSString stringWithFormat:@"Section Footer %li",(long)indexPath.section]]; 164 | return footer; 165 | } 166 | } 167 | 168 | #pragma mark---YLWaterFlowLayoutDelegate 169 | - (CGFloat)waterFlowLayout:(YLWaterFlowLayout *)layout widthAtIndexPath:(NSIndexPath *)indexPath 170 | { 171 | NSArray *sectionData = [_orignalTags yl_objectAtIndex:indexPath.section]; 172 | YLTag *tag = [sectionData yl_objectAtIndex:indexPath.row]; 173 | CGSize size = CGSizeMake(kFrameWidth - layout.sectionInset.left - layout.sectionInset.right,CGFLOAT_MAX); 174 | CGRect textRect = [tag.name 175 | boundingRectWithSize:size 176 | options:NSStringDrawingUsesLineFragmentOrigin 177 | attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} 178 | context:nil]; 179 | CGFloat width = textRect.size.width + 15; 180 | return width; 181 | } 182 | 183 | #pragma mark---UICollectionViewDelegate 184 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath 185 | { 186 | return YES; 187 | } 188 | 189 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 190 | { 191 | NSArray *sectionData = [_orignalTags yl_objectAtIndex:indexPath.section]; 192 | YLTag *tag = [sectionData yl_objectAtIndex:indexPath.row]; 193 | if(![_selectedTags containsObject:tag]){ 194 | if(_selectedTags.count >= _maxSelectCount){ 195 | //提示用户 196 | UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" 197 | message:[NSString stringWithFormat:@"最多选择%li个",(long)_maxSelectCount] 198 | delegate:nil 199 | cancelButtonTitle:@"确定" 200 | otherButtonTitles:nil, nil]; 201 | [alert show]; 202 | }else{ 203 | tag.selected = YES; 204 | [_selectedTags addObject:tag]; 205 | } 206 | }else{ 207 | tag.selected = NO; 208 | [_selectedTags removeObject:tag]; 209 | } 210 | 211 | [collectionView reloadData]; 212 | //!!!!:Don't use this method! Should be optimized 213 | // [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 214 | } 215 | 216 | #pragma mark---touch 217 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 218 | { 219 | [super touchesBegan:touches withEvent:event]; 220 | UITouch *lastTouch = [touches anyObject]; 221 | CGPoint point = [lastTouch locationInView:self]; 222 | if(!CGRectContainsPoint(self.bottomView.frame, point)){ 223 | [self dismiss]; 224 | } 225 | } 226 | 227 | #pragma mark---animation method 228 | - (void)showInView:(UIView *)view 229 | { 230 | [view addSubview:self]; 231 | 232 | CGRect frame = self.bottomView.frame; 233 | frame.origin.y = kFrameHeight - _bottomHeight; 234 | [UIView animateWithDuration:kSheetAnimationDuration 235 | delay:0.f 236 | options:UIViewAnimationOptionCurveEaseInOut 237 | animations:^{ 238 | self.bottomView.frame = frame; 239 | self.alpha = 1.f; 240 | } completion:^(BOOL finished) { 241 | 242 | }]; 243 | } 244 | 245 | - (void)dismiss 246 | { 247 | CGRect frame = self.bottomView.frame; 248 | frame.origin.y = kFrameHeight; 249 | [UIView animateWithDuration:kSheetAnimationDuration 250 | delay:0.f 251 | options:UIViewAnimationOptionCurveEaseInOut 252 | animations:^{ 253 | self.bottomView.frame = frame; 254 | self.alpha = 0; 255 | } completion:^(BOOL finished) { 256 | [self removeFromSuperview]; 257 | }]; 258 | } 259 | 260 | 261 | #pragma mark---other methods 262 | -(void)ensureAction 263 | { 264 | if([_delegate respondsToSelector:@selector(tagsChooser:selectedTags:)]){ 265 | [_delegate tagsChooser:self selectedTags:_selectedTags]; 266 | } 267 | [self dismiss]; 268 | } 269 | 270 | @end 271 | 272 | #pragma mark---标签cell 273 | @implementation YLTagsCollectionViewCell 274 | -(instancetype)initWithFrame:(CGRect)frame 275 | { 276 | if(self = [super initWithFrame:frame]){ 277 | self.backgroundColor = [UIColor clearColor]; 278 | _textLabel = [[UILabel alloc]init]; 279 | //此处可以根据需要自己使用自动布局代码实现 280 | _textLabel.frame = CGRectMake(0, 0, frame.size.width, frame.size.height); 281 | _textLabel.backgroundColor = [UIColor whiteColor]; 282 | _textLabel.font = [UIFont systemFontOfSize:14]; 283 | _textLabel.layer.borderWidth = 1.f; 284 | _textLabel.layer.cornerRadius = frame.size.height * 0.5; 285 | _textLabel.layer.masksToBounds = YES; 286 | _textLabel.textColor = HEXCOLOR(0x666666); 287 | _textLabel.textAlignment = NSTextAlignmentCenter; 288 | _textLabel.layer.borderColor = HEXCOLOR(0xdddddd).CGColor; 289 | [self.contentView addSubview:_textLabel]; 290 | } 291 | return self; 292 | } 293 | 294 | - (void)refreshWithObject:(NSObject *)obj 295 | { 296 | if([obj isKindOfClass:[YLTag class]]){ 297 | YLTag *tag = (YLTag *)obj; 298 | UIColor *borderColor = tag.selected ? HEXCOLOR(0xffb400) : HEXCOLOR(0xdddddd); 299 | UIColor *titleColor = tag.selected ? HEXCOLOR(0xffb400) : HEXCOLOR(0x666666); 300 | _textLabel.layer.borderColor = borderColor.CGColor; 301 | _textLabel.textColor = titleColor; 302 | _textLabel.text = tag.name; 303 | } 304 | } 305 | 306 | -(void)layoutSubviews 307 | { 308 | [super layoutSubviews]; 309 | _textLabel.frame = self.bounds; 310 | } 311 | 312 | -(void)setSelected:(BOOL)selected 313 | { 314 | [super setSelected:selected]; 315 | UIColor *borderColor = selected ? HEXCOLOR(0xffb400) : HEXCOLOR(0xdddddd); 316 | UIColor *titleColor = selected ? HEXCOLOR(0xffb400) : HEXCOLOR(0x666666); 317 | _textLabel.layer.borderColor = borderColor.CGColor; 318 | _textLabel.textColor = titleColor; 319 | } 320 | 321 | -(void)setHighlighted:(BOOL)highlighted 322 | { 323 | [super setHighlighted:highlighted]; 324 | UIColor *borderColor = highlighted ? HEXCOLOR(0xffb400) : HEXCOLOR(0xdddddd); 325 | UIColor *titleColor = highlighted ? HEXCOLOR(0xffb400) : HEXCOLOR(0x666666); 326 | _textLabel.layer.borderColor = borderColor.CGColor; 327 | _textLabel.textColor = titleColor; 328 | } 329 | 330 | @end 331 | -------------------------------------------------------------------------------- /YLTagsChooser/YLTagsChooser.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 457042321D0E580400128041 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 457042311D0E580400128041 /* main.m */; }; 11 | 457042351D0E580400128041 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 457042341D0E580400128041 /* AppDelegate.m */; }; 12 | 457042381D0E580400128041 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 457042371D0E580400128041 /* ViewController.m */; }; 13 | 4570423B1D0E580400128041 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 457042391D0E580400128041 /* Main.storyboard */; }; 14 | 4570423D1D0E580400128041 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4570423C1D0E580400128041 /* Assets.xcassets */; }; 15 | 457042401D0E580400128041 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4570423E1D0E580400128041 /* LaunchScreen.storyboard */; }; 16 | 4570424B1D0E580400128041 /* YLTagsChooserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4570424A1D0E580400128041 /* YLTagsChooserTests.m */; }; 17 | 457042561D0E580400128041 /* YLTagsChooserUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 457042551D0E580400128041 /* YLTagsChooserUITests.m */; }; 18 | 457042771D0E62B900128041 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 457042761D0E62B900128041 /* CoreGraphics.framework */; }; 19 | 457042791D0E633300128041 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 457042781D0E633300128041 /* UIKit.framework */; }; 20 | 6D3855B62023503900A15073 /* NSArray+YLBoundsCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3855AD2023503900A15073 /* NSArray+YLBoundsCheck.m */; }; 21 | 6D3855B72023503900A15073 /* YLCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3855AF2023503900A15073 /* YLCollectionReusableView.m */; }; 22 | 6D3855B82023503900A15073 /* YLTag.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3855B12023503900A15073 /* YLTag.m */; }; 23 | 6D3855B92023503900A15073 /* YLTagsChooser.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3855B32023503900A15073 /* YLTagsChooser.m */; }; 24 | 6D3855BA2023503900A15073 /* YLWaterFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3855B52023503900A15073 /* YLWaterFlowLayout.m */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 457042471D0E580400128041 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 457042251D0E580400128041 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 4570422C1D0E580400128041; 33 | remoteInfo = YLTagsChooser; 34 | }; 35 | 457042521D0E580400128041 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 457042251D0E580400128041 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 4570422C1D0E580400128041; 40 | remoteInfo = YLTagsChooser; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 456324F41EF239DA000AD9AF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; 46 | 456324F51EF239DA000AD9AF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 47 | 4570422D1D0E580400128041 /* YLTagsChooser.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YLTagsChooser.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 457042311D0E580400128041 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | 457042331D0E580400128041 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | 457042341D0E580400128041 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | 457042361D0E580400128041 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 52 | 457042371D0E580400128041 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 53 | 4570423A1D0E580400128041 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 4570423C1D0E580400128041 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 4570423F1D0E580400128041 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 457042411D0E580400128041 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 457042461D0E580400128041 /* YLTagsChooserTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YLTagsChooserTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 4570424A1D0E580400128041 /* YLTagsChooserTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YLTagsChooserTests.m; sourceTree = ""; }; 59 | 4570424C1D0E580400128041 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 457042511D0E580400128041 /* YLTagsChooserUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YLTagsChooserUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 457042551D0E580400128041 /* YLTagsChooserUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YLTagsChooserUITests.m; sourceTree = ""; }; 62 | 457042571D0E580400128041 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 457042761D0E62B900128041 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 64 | 457042781D0E633300128041 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 65 | 6D3855AC2023503900A15073 /* NSArray+YLBoundsCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+YLBoundsCheck.h"; sourceTree = ""; }; 66 | 6D3855AD2023503900A15073 /* NSArray+YLBoundsCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+YLBoundsCheck.m"; sourceTree = ""; }; 67 | 6D3855AE2023503900A15073 /* YLCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YLCollectionReusableView.h; sourceTree = ""; }; 68 | 6D3855AF2023503900A15073 /* YLCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YLCollectionReusableView.m; sourceTree = ""; }; 69 | 6D3855B02023503900A15073 /* YLTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YLTag.h; sourceTree = ""; }; 70 | 6D3855B12023503900A15073 /* YLTag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YLTag.m; sourceTree = ""; }; 71 | 6D3855B22023503900A15073 /* YLTagsChooser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YLTagsChooser.h; sourceTree = ""; }; 72 | 6D3855B32023503900A15073 /* YLTagsChooser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YLTagsChooser.m; sourceTree = ""; }; 73 | 6D3855B42023503900A15073 /* YLWaterFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YLWaterFlowLayout.h; sourceTree = ""; }; 74 | 6D3855B52023503900A15073 /* YLWaterFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YLWaterFlowLayout.m; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 4570422A1D0E580400128041 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 457042791D0E633300128041 /* UIKit.framework in Frameworks */, 83 | 457042771D0E62B900128041 /* CoreGraphics.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 457042431D0E580400128041 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | 4570424E1D0E580400128041 /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 457042241D0E580400128041 = { 105 | isa = PBXGroup; 106 | children = ( 107 | 457042781D0E633300128041 /* UIKit.framework */, 108 | 457042761D0E62B900128041 /* CoreGraphics.framework */, 109 | 4570422F1D0E580400128041 /* YLTagsChooser */, 110 | 457042491D0E580400128041 /* YLTagsChooserTests */, 111 | 457042541D0E580400128041 /* YLTagsChooserUITests */, 112 | 4570422E1D0E580400128041 /* Products */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 4570422E1D0E580400128041 /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 4570422D1D0E580400128041 /* YLTagsChooser.app */, 120 | 457042461D0E580400128041 /* YLTagsChooserTests.xctest */, 121 | 457042511D0E580400128041 /* YLTagsChooserUITests.xctest */, 122 | ); 123 | name = Products; 124 | sourceTree = ""; 125 | }; 126 | 4570422F1D0E580400128041 /* YLTagsChooser */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 6D3855AB2023503900A15073 /* TagsChooser */, 130 | 457042331D0E580400128041 /* AppDelegate.h */, 131 | 457042341D0E580400128041 /* AppDelegate.m */, 132 | 457042361D0E580400128041 /* ViewController.h */, 133 | 457042371D0E580400128041 /* ViewController.m */, 134 | 457042391D0E580400128041 /* Main.storyboard */, 135 | 4570423C1D0E580400128041 /* Assets.xcassets */, 136 | 4570423E1D0E580400128041 /* LaunchScreen.storyboard */, 137 | 457042411D0E580400128041 /* Info.plist */, 138 | 457042301D0E580400128041 /* Supporting Files */, 139 | ); 140 | path = YLTagsChooser; 141 | sourceTree = ""; 142 | }; 143 | 457042301D0E580400128041 /* Supporting Files */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 457042311D0E580400128041 /* main.m */, 147 | ); 148 | name = "Supporting Files"; 149 | sourceTree = ""; 150 | }; 151 | 457042491D0E580400128041 /* YLTagsChooserTests */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 4570424A1D0E580400128041 /* YLTagsChooserTests.m */, 155 | 4570424C1D0E580400128041 /* Info.plist */, 156 | ); 157 | path = YLTagsChooserTests; 158 | sourceTree = ""; 159 | }; 160 | 457042541D0E580400128041 /* YLTagsChooserUITests */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 457042551D0E580400128041 /* YLTagsChooserUITests.m */, 164 | 457042571D0E580400128041 /* Info.plist */, 165 | ); 166 | path = YLTagsChooserUITests; 167 | sourceTree = ""; 168 | }; 169 | 6D3855AB2023503900A15073 /* TagsChooser */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 6D3855AC2023503900A15073 /* NSArray+YLBoundsCheck.h */, 173 | 6D3855AD2023503900A15073 /* NSArray+YLBoundsCheck.m */, 174 | 6D3855AE2023503900A15073 /* YLCollectionReusableView.h */, 175 | 6D3855AF2023503900A15073 /* YLCollectionReusableView.m */, 176 | 6D3855B02023503900A15073 /* YLTag.h */, 177 | 6D3855B12023503900A15073 /* YLTag.m */, 178 | 6D3855B22023503900A15073 /* YLTagsChooser.h */, 179 | 6D3855B32023503900A15073 /* YLTagsChooser.m */, 180 | 6D3855B42023503900A15073 /* YLWaterFlowLayout.h */, 181 | 6D3855B52023503900A15073 /* YLWaterFlowLayout.m */, 182 | ); 183 | path = TagsChooser; 184 | sourceTree = ""; 185 | }; 186 | /* End PBXGroup section */ 187 | 188 | /* Begin PBXNativeTarget section */ 189 | 4570422C1D0E580400128041 /* YLTagsChooser */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 4570425A1D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooser" */; 192 | buildPhases = ( 193 | 457042291D0E580400128041 /* Sources */, 194 | 4570422A1D0E580400128041 /* Frameworks */, 195 | 4570422B1D0E580400128041 /* Resources */, 196 | ); 197 | buildRules = ( 198 | ); 199 | dependencies = ( 200 | ); 201 | name = YLTagsChooser; 202 | productName = YLTagsChooser; 203 | productReference = 4570422D1D0E580400128041 /* YLTagsChooser.app */; 204 | productType = "com.apple.product-type.application"; 205 | }; 206 | 457042451D0E580400128041 /* YLTagsChooserTests */ = { 207 | isa = PBXNativeTarget; 208 | buildConfigurationList = 4570425D1D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooserTests" */; 209 | buildPhases = ( 210 | 457042421D0E580400128041 /* Sources */, 211 | 457042431D0E580400128041 /* Frameworks */, 212 | 457042441D0E580400128041 /* Resources */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | 457042481D0E580400128041 /* PBXTargetDependency */, 218 | ); 219 | name = YLTagsChooserTests; 220 | productName = YLTagsChooserTests; 221 | productReference = 457042461D0E580400128041 /* YLTagsChooserTests.xctest */; 222 | productType = "com.apple.product-type.bundle.unit-test"; 223 | }; 224 | 457042501D0E580400128041 /* YLTagsChooserUITests */ = { 225 | isa = PBXNativeTarget; 226 | buildConfigurationList = 457042601D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooserUITests" */; 227 | buildPhases = ( 228 | 4570424D1D0E580400128041 /* Sources */, 229 | 4570424E1D0E580400128041 /* Frameworks */, 230 | 4570424F1D0E580400128041 /* Resources */, 231 | ); 232 | buildRules = ( 233 | ); 234 | dependencies = ( 235 | 457042531D0E580400128041 /* PBXTargetDependency */, 236 | ); 237 | name = YLTagsChooserUITests; 238 | productName = YLTagsChooserUITests; 239 | productReference = 457042511D0E580400128041 /* YLTagsChooserUITests.xctest */; 240 | productType = "com.apple.product-type.bundle.ui-testing"; 241 | }; 242 | /* End PBXNativeTarget section */ 243 | 244 | /* Begin PBXProject section */ 245 | 457042251D0E580400128041 /* Project object */ = { 246 | isa = PBXProject; 247 | attributes = { 248 | LastUpgradeCheck = 0730; 249 | ORGANIZATIONNAME = YL; 250 | TargetAttributes = { 251 | 4570422C1D0E580400128041 = { 252 | CreatedOnToolsVersion = 7.3.1; 253 | DevelopmentTeam = M3Y7JKHK66; 254 | }; 255 | 457042451D0E580400128041 = { 256 | CreatedOnToolsVersion = 7.3.1; 257 | TestTargetID = 4570422C1D0E580400128041; 258 | }; 259 | 457042501D0E580400128041 = { 260 | CreatedOnToolsVersion = 7.3.1; 261 | TestTargetID = 4570422C1D0E580400128041; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = 457042281D0E580400128041 /* Build configuration list for PBXProject "YLTagsChooser" */; 266 | compatibilityVersion = "Xcode 3.2"; 267 | developmentRegion = English; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | English, 271 | en, 272 | Base, 273 | "zh-Hans", 274 | ); 275 | mainGroup = 457042241D0E580400128041; 276 | productRefGroup = 4570422E1D0E580400128041 /* Products */; 277 | projectDirPath = ""; 278 | projectRoot = ""; 279 | targets = ( 280 | 4570422C1D0E580400128041 /* YLTagsChooser */, 281 | 457042451D0E580400128041 /* YLTagsChooserTests */, 282 | 457042501D0E580400128041 /* YLTagsChooserUITests */, 283 | ); 284 | }; 285 | /* End PBXProject section */ 286 | 287 | /* Begin PBXResourcesBuildPhase section */ 288 | 4570422B1D0E580400128041 /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 457042401D0E580400128041 /* LaunchScreen.storyboard in Resources */, 293 | 4570423D1D0E580400128041 /* Assets.xcassets in Resources */, 294 | 4570423B1D0E580400128041 /* Main.storyboard in Resources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | 457042441D0E580400128041 /* Resources */ = { 299 | isa = PBXResourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | 4570424F1D0E580400128041 /* Resources */ = { 306 | isa = PBXResourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXResourcesBuildPhase section */ 313 | 314 | /* Begin PBXSourcesBuildPhase section */ 315 | 457042291D0E580400128041 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 457042381D0E580400128041 /* ViewController.m in Sources */, 320 | 457042351D0E580400128041 /* AppDelegate.m in Sources */, 321 | 457042321D0E580400128041 /* main.m in Sources */, 322 | 6D3855B82023503900A15073 /* YLTag.m in Sources */, 323 | 6D3855BA2023503900A15073 /* YLWaterFlowLayout.m in Sources */, 324 | 6D3855B62023503900A15073 /* NSArray+YLBoundsCheck.m in Sources */, 325 | 6D3855B72023503900A15073 /* YLCollectionReusableView.m in Sources */, 326 | 6D3855B92023503900A15073 /* YLTagsChooser.m in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | 457042421D0E580400128041 /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 4570424B1D0E580400128041 /* YLTagsChooserTests.m in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 4570424D1D0E580400128041 /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 457042561D0E580400128041 /* YLTagsChooserUITests.m in Sources */, 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | }; 346 | /* End PBXSourcesBuildPhase section */ 347 | 348 | /* Begin PBXTargetDependency section */ 349 | 457042481D0E580400128041 /* PBXTargetDependency */ = { 350 | isa = PBXTargetDependency; 351 | target = 4570422C1D0E580400128041 /* YLTagsChooser */; 352 | targetProxy = 457042471D0E580400128041 /* PBXContainerItemProxy */; 353 | }; 354 | 457042531D0E580400128041 /* PBXTargetDependency */ = { 355 | isa = PBXTargetDependency; 356 | target = 4570422C1D0E580400128041 /* YLTagsChooser */; 357 | targetProxy = 457042521D0E580400128041 /* PBXContainerItemProxy */; 358 | }; 359 | /* End PBXTargetDependency section */ 360 | 361 | /* Begin PBXVariantGroup section */ 362 | 457042391D0E580400128041 /* Main.storyboard */ = { 363 | isa = PBXVariantGroup; 364 | children = ( 365 | 4570423A1D0E580400128041 /* Base */, 366 | 456324F41EF239DA000AD9AF /* zh-Hans */, 367 | ); 368 | name = Main.storyboard; 369 | sourceTree = ""; 370 | }; 371 | 4570423E1D0E580400128041 /* LaunchScreen.storyboard */ = { 372 | isa = PBXVariantGroup; 373 | children = ( 374 | 4570423F1D0E580400128041 /* Base */, 375 | 456324F51EF239DA000AD9AF /* zh-Hans */, 376 | ); 377 | name = LaunchScreen.storyboard; 378 | sourceTree = ""; 379 | }; 380 | /* End PBXVariantGroup section */ 381 | 382 | /* Begin XCBuildConfiguration section */ 383 | 457042581D0E580400128041 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ALWAYS_SEARCH_USER_PATHS = NO; 387 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 388 | CLANG_ANALYZER_NONNULL = YES; 389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 390 | CLANG_CXX_LIBRARY = "libc++"; 391 | CLANG_ENABLE_MODULES = YES; 392 | CLANG_ENABLE_OBJC_ARC = YES; 393 | CLANG_WARN_BOOL_CONVERSION = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_EMPTY_BODY = YES; 397 | CLANG_WARN_ENUM_CONVERSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 400 | CLANG_WARN_UNREACHABLE_CODE = YES; 401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 403 | COPY_PHASE_STRIP = NO; 404 | DEBUG_INFORMATION_FORMAT = dwarf; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | ENABLE_TESTABILITY = YES; 407 | GCC_C_LANGUAGE_STANDARD = gnu99; 408 | GCC_DYNAMIC_NO_PIC = NO; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_OPTIMIZATION_LEVEL = 0; 411 | GCC_PREPROCESSOR_DEFINITIONS = ( 412 | "DEBUG=1", 413 | "$(inherited)", 414 | ); 415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 417 | GCC_WARN_UNDECLARED_SELECTOR = YES; 418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 419 | GCC_WARN_UNUSED_FUNCTION = YES; 420 | GCC_WARN_UNUSED_VARIABLE = YES; 421 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 422 | MTL_ENABLE_DEBUG_INFO = YES; 423 | ONLY_ACTIVE_ARCH = YES; 424 | SDKROOT = iphoneos; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | }; 427 | name = Debug; 428 | }; 429 | 457042591D0E580400128041 /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ALWAYS_SEARCH_USER_PATHS = NO; 433 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 434 | CLANG_ANALYZER_NONNULL = YES; 435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 436 | CLANG_CXX_LIBRARY = "libc++"; 437 | CLANG_ENABLE_MODULES = YES; 438 | CLANG_ENABLE_OBJC_ARC = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INT_CONVERSION = YES; 445 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 446 | CLANG_WARN_UNREACHABLE_CODE = YES; 447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 448 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 449 | COPY_PHASE_STRIP = NO; 450 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 451 | ENABLE_NS_ASSERTIONS = NO; 452 | ENABLE_STRICT_OBJC_MSGSEND = YES; 453 | GCC_C_LANGUAGE_STANDARD = gnu99; 454 | GCC_NO_COMMON_BLOCKS = YES; 455 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 456 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 457 | GCC_WARN_UNDECLARED_SELECTOR = YES; 458 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 459 | GCC_WARN_UNUSED_FUNCTION = YES; 460 | GCC_WARN_UNUSED_VARIABLE = YES; 461 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 462 | MTL_ENABLE_DEBUG_INFO = NO; 463 | SDKROOT = iphoneos; 464 | TARGETED_DEVICE_FAMILY = "1,2"; 465 | VALIDATE_PRODUCT = YES; 466 | }; 467 | name = Release; 468 | }; 469 | 4570425B1D0E580400128041 /* Debug */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | DEVELOPMENT_TEAM = M3Y7JKHK66; 474 | INFOPLIST_FILE = YLTagsChooser/Info.plist; 475 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 476 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 477 | PRODUCT_BUNDLE_IDENTIFIER = com.Lambert.YLTagsChooser; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | }; 480 | name = Debug; 481 | }; 482 | 4570425C1D0E580400128041 /* Release */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 486 | DEVELOPMENT_TEAM = M3Y7JKHK66; 487 | INFOPLIST_FILE = YLTagsChooser/Info.plist; 488 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 489 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 490 | PRODUCT_BUNDLE_IDENTIFIER = com.Lambert.YLTagsChooser; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | }; 493 | name = Release; 494 | }; 495 | 4570425E1D0E580400128041 /* Debug */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | BUNDLE_LOADER = "$(TEST_HOST)"; 499 | INFOPLIST_FILE = YLTagsChooserTests/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 501 | PRODUCT_BUNDLE_IDENTIFIER = yl.YLTagsChooserTests; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YLTagsChooser.app/YLTagsChooser"; 504 | }; 505 | name = Debug; 506 | }; 507 | 4570425F1D0E580400128041 /* Release */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | BUNDLE_LOADER = "$(TEST_HOST)"; 511 | INFOPLIST_FILE = YLTagsChooserTests/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 513 | PRODUCT_BUNDLE_IDENTIFIER = yl.YLTagsChooserTests; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YLTagsChooser.app/YLTagsChooser"; 516 | }; 517 | name = Release; 518 | }; 519 | 457042611D0E580400128041 /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | INFOPLIST_FILE = YLTagsChooserUITests/Info.plist; 523 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 524 | PRODUCT_BUNDLE_IDENTIFIER = yl.YLTagsChooserUITests; 525 | PRODUCT_NAME = "$(TARGET_NAME)"; 526 | TEST_TARGET_NAME = YLTagsChooser; 527 | }; 528 | name = Debug; 529 | }; 530 | 457042621D0E580400128041 /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | INFOPLIST_FILE = YLTagsChooserUITests/Info.plist; 534 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 535 | PRODUCT_BUNDLE_IDENTIFIER = yl.YLTagsChooserUITests; 536 | PRODUCT_NAME = "$(TARGET_NAME)"; 537 | TEST_TARGET_NAME = YLTagsChooser; 538 | }; 539 | name = Release; 540 | }; 541 | /* End XCBuildConfiguration section */ 542 | 543 | /* Begin XCConfigurationList section */ 544 | 457042281D0E580400128041 /* Build configuration list for PBXProject "YLTagsChooser" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 457042581D0E580400128041 /* Debug */, 548 | 457042591D0E580400128041 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 4570425A1D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooser" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 4570425B1D0E580400128041 /* Debug */, 557 | 4570425C1D0E580400128041 /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 4570425D1D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooserTests" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 4570425E1D0E580400128041 /* Debug */, 566 | 4570425F1D0E580400128041 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 457042601D0E580400128041 /* Build configuration list for PBXNativeTarget "YLTagsChooserUITests" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 457042611D0E580400128041 /* Debug */, 575 | 457042621D0E580400128041 /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | /* End XCConfigurationList section */ 581 | }; 582 | rootObject = 457042251D0E580400128041 /* Project object */; 583 | } 584 | --------------------------------------------------------------------------------