├── YDChannelSelector ├── Assets │ ├── .gitkeep │ ├── selector_add.png │ ├── selector_delete.png │ └── selector_dismiss.png └── Classes │ ├── YDChannelSelectorHeader.swift │ ├── YDChannelSelectorCell.swift │ └── YDChannelSelector.swift ├── Assets ├── Screen_Shot1.png └── Screen_Shot2.png ├── YDChannelSelectorDemo ├── YDChannelSelectorDemo │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Base.lproj │ │ └── Main.storyboard │ ├── ViewController.swift │ ├── YDChannelSelector │ │ ├── YDChannelSelectorHeader.swift │ │ ├── YDChannelSelectorCell.swift │ │ └── YDChannelSelector.swift │ └── LaunchScreen.storyboard ├── YDChannelSelectorDemo.xcodeproj │ ├── xcuserdata │ │ └── yd.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── YDChannelSelectorDemoTests │ ├── Info.plist │ └── YDChannelSelectorDemoTests.swift └── YDChannelSelectorDemoUITests │ ├── Info.plist │ └── YDChannelSelectorDemoUITests.swift ├── YDChannelSelector.podspec ├── LICENSE └── README.md /YDChannelSelector/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Assets/Screen_Shot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yd2008/YDChannelSelector/HEAD/Assets/Screen_Shot1.png -------------------------------------------------------------------------------- /Assets/Screen_Shot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yd2008/YDChannelSelector/HEAD/Assets/Screen_Shot2.png -------------------------------------------------------------------------------- /YDChannelSelector/Assets/selector_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yd2008/YDChannelSelector/HEAD/YDChannelSelector/Assets/selector_add.png -------------------------------------------------------------------------------- /YDChannelSelector/Assets/selector_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yd2008/YDChannelSelector/HEAD/YDChannelSelector/Assets/selector_delete.png -------------------------------------------------------------------------------- /YDChannelSelector/Assets/selector_dismiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yd2008/YDChannelSelector/HEAD/YDChannelSelector/Assets/selector_dismiss.png -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo.xcodeproj/xcuserdata/yd.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo.xcodeproj/xcuserdata/yd.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YDChannelSelectorDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YDChannelSelectorDemo 4 | // 5 | // Created by yudai on 2018/11/22. 6 | // Copyright © 2018 yudai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /YDChannelSelector.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |spec| 3 | 4 | spec.name = "YDChannelSelector" 5 | spec.version = "1.3.0" 6 | spec.summary = "一款精仿网易新闻频道选择器组件" 7 | 8 | spec.description = "一款精仿网易新闻频道选择器组件. 界面样式细节基本达到 1:1 使用简单 维护容易 扩展性强" 9 | 10 | 11 | spec.homepage = "https://github.com/yd2008/YDChannelSelector" 12 | 13 | spec.license = "MIT" 14 | 15 | spec.author = { "yudai" => "332838156@qq.com" } 16 | 17 | spec.platform = :ios, "10.0" 18 | 19 | spec.source = { :git => "https://github.com/yd2008/YDChannelSelector.git", :tag => "#{spec.version}" } 20 | 21 | spec.source_files = "YDChannelSelector", "YDChannelSelector/Classes/*.swift" 22 | 23 | spec.resource_bundles = { 24 | 'YDChannelSelector' => ['YDChannelSelector/Assets/*.png'] 25 | } 26 | 27 | spec.framework = "UIKit" 28 | spec.requires_arc = true 29 | spec.swift_version = "4.2" 30 | 31 | 32 | end 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 yd2008 <332838156@qq.com> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemoTests/YDChannelSelectorDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorDemoTests.swift 3 | // YDChannelSelectorDemoTests 4 | // 5 | // Created by yudai on 2018/11/22. 6 | // Copyright © 2018 yudai. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import YDChannelSelectorDemo 11 | 12 | class YDChannelSelectorDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemoUITests/YDChannelSelectorDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorDemoUITests.swift 3 | // YDChannelSelectorDemoUITests 4 | // 5 | // Created by yudai on 2018/11/22. 6 | // Copyright © 2018 yudai. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class YDChannelSelectorDemoUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // 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. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // YDChannelSelectorDemo 4 | // 5 | // Created by yudai on 2018/11/22. 6 | // Copyright © 2018 yudai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let mockDataSource = [["头条","体育","数码","佛学","科技","娱乐","成都","二次元"],["独家","NBA","历史","军事","彩票","新闻学院","态度公开课","云课堂"]] 14 | 15 | private lazy var channelSelector: YDChannelSelector = { 16 | let cv = YDChannelSelector() 17 | cv.dataSource = self 18 | cv.delegate = self 19 | // 是否支持本地缓存用户功能 默认支持 20 | // cv.isCacheLastest = false 21 | return cv 22 | }() 23 | 24 | private lazy var showBtn: UIButton = { 25 | let sb = UIButton() 26 | sb.setTitle("弹出", for: .normal) 27 | sb.sizeToFit() 28 | sb.center = view.center 29 | sb.addTarget(self, action: #selector(presentSelector), for: .touchUpInside) 30 | sb.setTitleColor(UIColor.red, for: .normal) 31 | return sb 32 | }() 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | view.addSubview(showBtn) 37 | } 38 | 39 | @objc private func presentSelector() { 40 | // guard selectorDataSource != nil && selectorDataSource?.count != 0 else { 41 | // print("DataSources can not be empty!") 42 | // return 43 | // } 44 | // present(channelSelector, animated: true, completion: nil) 45 | channelSelector.show() 46 | } 47 | 48 | } 49 | 50 | // MARK: 数据源方法 51 | extension ViewController: YDChannelSelectorDataSource { 52 | 53 | func numberOfSections(in selector: YDChannelSelector) -> Int { 54 | return mockDataSource.count 55 | } 56 | 57 | func selector(_ selector: YDChannelSelector, numberOfItemsInSection section: Int) -> Int { 58 | return mockDataSource[section].count 59 | } 60 | 61 | func selector(_ selector: YDChannelSelector, itemAt indexPath: IndexPath) -> SelectorItem { 62 | let title = mockDataSource[indexPath.section][indexPath.row] 63 | // 默认头条为固定栏目 64 | let selectorItem = SelectorItem(channelTitle: title, isFixation: title == "头条", rawData: nil) 65 | return selectorItem 66 | } 67 | 68 | } 69 | 70 | // MARK: 代理方法 71 | extension ViewController: YDChannelSelectorDelegate { 72 | 73 | func selector(_ selector: YDChannelSelector, didChangeDS newDataSource: [[SelectorItem]]) { 74 | print(newDataSource.map { $0.map { $0.channelTitle! } }) 75 | } 76 | 77 | func selector(_ selector: YDChannelSelector, dismiss newDataSource: [[SelectorItem]]) { 78 | print(newDataSource.map { $0.map { $0.channelTitle! } }) 79 | } 80 | 81 | func selector(_ selector: YDChannelSelector, didSelectChannel channelItem: SelectorItem) { 82 | print(channelItem.channelTitle!) 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YDChannelSelector 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/YDChannelSelector.svg?style=flat)](https://cocoapods.org/pods/YDChannelSelector) 4 | [![License](https://img.shields.io/cocoapods/l/YDChannelSelector.svg?style=flat)](https://cocoapods.org/pods/YDChannelSelector) 5 | [![Platform](https://img.shields.io/cocoapods/p/YDChannelSelector.svg?style=flat)](https://cocoapods.org/pods/YDChannelSelector) 6 | 7 | 8 | ## Requirements 9 | - iOS 10.0 10 | - Swift 4.x 11 | - Xcode 10 12 | 13 | 14 | ## 记录了初版实现思路的文章 [掘金](https://juejin.im/post/5bfb91dee51d4548657d0265) 15 | 16 | 17 | ## Installation 18 | 19 | ###### cocoapods 20 | YDChannelSelector is available through [CocoaPods](https://cocoapods.org). To install 21 | it, simply add the following line to your Podfile: 22 | 23 | ```ruby 24 | pod 'YDChannelSelector' 25 | ``` 26 | 27 | ###### 非cocoapods 28 | 直接拖YDChannelSelector进入项目 29 | 30 | 31 | ## Features 32 | - [x] 支持本地缓存用户操作 33 | - [x] 界面逻辑基本1:1 还原网易新闻 扩展性强 自定义性强 34 | - [x] 纯净无任何依赖耦合 接口和tableView类似 容易上手 35 | - [x] 注释思路详尽 不管是自己定制还是参考思路或者直接使用都没问题 36 | - [x] 支持cocoapods或者直接拖入项目使用 37 | - [x] 网易后续界面逻辑同步更新 38 | 39 |
40 | 41 | 42 |
43 | 44 | 45 | # Usage 46 | 47 | ## 初始化 48 | 创建频道选择器只需要三个参数: 代理,是否缓存用户操作(默认缓存),数据源(网络获取到后再赋值即可) 49 | ```swift 50 | private lazy var channelSelector: YDChannelSelector = { 51 | let cv = YDChannelSelector() 52 | cv.dataSource = self 53 | cv.delegate = self 54 | // 是否支持本地缓存用户功能 55 | // cv.isCacheLastest = false 56 | return cv 57 | }() 58 | ``` 59 | 60 | ## 数据源 61 | SelectorItem中有三个属性 channelTitle即为频道标题 isFixation 是否是固定栏目 rawData ** 原始数据 模型或者字典皆可 ** 62 | ```swift 63 | public protocol YDChannelSelectorDataSource: class { 64 | func numberOfSections(in selector: YDChannelSelector) -> Int 65 | func selector(_ selector: YDChannelSelector, numberOfItemsInSection section: Int) -> Int 66 | func selector(_ selector: YDChannelSelector, itemAt indexPath: IndexPath) -> SelectorItem 67 | } 68 | ......... 69 | ``` 70 | 71 | ## 弹出 72 | 直接使用UIViewController的方法present即可 73 | ```swift 74 | present(channelSelector, animated: true, completion: nil) 75 | ``` 76 | 77 | ## 代理 78 | ```swift 79 | 80 | // 数据源发生变化 81 | func selector(_ selector: YDChannelSelector, didChangeDS newDataSource: [[SelectorItem]]) { 82 | print(newDataSource.map { $0.map { $0.channelTitle! } }) 83 | } 84 | 85 | // 用户退出操作时 86 | func selector(_ selector: YDChannelSelector, dismiss newDataSource: [[SelectorItem]]) { 87 | print(newDataSource.map { $0.map { $0.channelTitle! } }) 88 | } 89 | 90 | // 用户选中新频道时 91 | func selector(_ selector: YDChannelSelector, didSelectChannel channelItem: SelectorItem) { 92 | print(channelItem.channelTitle!) 93 | } 94 | 95 | ``` 96 | 97 | 98 | ## Author 99 | 100 | yd2008, 332838156@qq.com 101 | 102 | ## License 103 | 104 | YDChannelSelector is available under the MIT license. See the LICENSE file for more info. 105 | -------------------------------------------------------------------------------- /YDChannelSelector/Classes/YDChannelSelectorHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorHeader.swift 3 | // YDChannelSelectorViewDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let YDChannelSelectorHeaderID = "YDChannelSelectorHeaderID" 12 | /// header内部间距 13 | fileprivate let headerMargin: CGFloat = 10 14 | 15 | class YDChannelSelectorHeader: UICollectionReusableView { 16 | 17 | open var isHiddenEdit: Bool = true { 18 | didSet{ 19 | setNeedsLayout() 20 | layoutIfNeeded() 21 | } 22 | } 23 | 24 | open var isEdit: Bool = false { 25 | didSet{ 26 | editBtn.isSelected = isEdit 27 | } 28 | } 29 | 30 | open var title: String? { 31 | didSet{ 32 | titleLabel.text = title 33 | } 34 | } 35 | 36 | open var tips: String? { 37 | didSet{ 38 | tipsLabel.text = tips 39 | } 40 | } 41 | 42 | open var editStatusChangedBlock: ((_ isEdit: Bool) -> Void)? 43 | 44 | private lazy var titleLabel: UILabel = { 45 | let tl = UILabel() 46 | tl.textColor = UIColor.black 47 | tl.font = UIFont.systemFont(ofSize: 15) 48 | tl.textAlignment = .center 49 | tl.numberOfLines = 0 50 | return tl 51 | }() 52 | 53 | private lazy var tipsLabel: UILabel = { 54 | let tl = UILabel() 55 | tl.textColor = UIColor.lightGray 56 | tl.font = UIFont.systemFont(ofSize: 12.5) 57 | tl.textAlignment = .left 58 | tl.sizeToFit() 59 | return tl 60 | }() 61 | 62 | private lazy var editBtn: UIButton = { 63 | let eb = UIButton() 64 | eb.layer.borderColor = UIColor.red.cgColor 65 | eb.layer.masksToBounds = true 66 | eb.layer.borderWidth = 1 67 | eb.setTitle("编辑", for: .normal) 68 | eb.setTitle("完成", for: .selected) 69 | eb.titleLabel?.font = UIFont.systemFont(ofSize: 14) 70 | eb.setTitleColor(UIColor.red, for: .normal) 71 | eb.addTarget(self, action: #selector(btnDidClicked(btn:)), for: .touchDown) 72 | return eb 73 | }() 74 | 75 | override init(frame: CGRect) { 76 | super.init(frame: frame) 77 | initUI() 78 | } 79 | 80 | required init?(coder aDecoder: NSCoder) { 81 | super.init(coder: aDecoder) 82 | initUI() 83 | } 84 | 85 | override func layoutSubviews() { 86 | super.layoutSubviews() 87 | if isHiddenEdit == true { 88 | editBtn.isHidden = true 89 | } else { 90 | editBtn.isHidden = false 91 | } 92 | 93 | // headerMargin 94 | titleLabel.frame = CGRect(x: 0, y: 0, width: 65, height: bounds.height) 95 | 96 | tipsLabel.frame = CGRect(x: titleLabel.frame.maxX + headerMargin, y: 0, width: 80, height: bounds.height) 97 | 98 | editBtn.frame = CGRect(x: bounds.width - 60, y: headerMargin, width: 60, height: bounds.height - headerMargin*2) 99 | editBtn.layer.cornerRadius = 15 100 | } 101 | } 102 | 103 | extension YDChannelSelectorHeader { 104 | private func initUI() { 105 | addSubview(titleLabel) 106 | addSubview(tipsLabel) 107 | addSubview(editBtn) 108 | } 109 | 110 | @objc private func btnDidClicked(btn: UIButton) { 111 | btn.isSelected = !btn.isSelected 112 | editStatusChangedBlock?(btn.isSelected) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/YDChannelSelector/YDChannelSelectorHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorHeader.swift 3 | // YDChannelSelectorViewDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let YDChannelSelectorHeaderID = "YDChannelSelectorHeaderID" 12 | /// header内部间距 13 | fileprivate let headerMargin: CGFloat = 10 14 | 15 | class YDChannelSelectorHeader: UICollectionReusableView { 16 | 17 | open var isHiddenEdit: Bool = true { 18 | didSet{ 19 | setNeedsLayout() 20 | layoutIfNeeded() 21 | } 22 | } 23 | 24 | open var isEdit: Bool = false { 25 | didSet{ 26 | editBtn.isSelected = isEdit 27 | } 28 | } 29 | 30 | open var title: String? { 31 | didSet{ 32 | titleLabel.text = title 33 | } 34 | } 35 | 36 | open var tips: String? { 37 | didSet{ 38 | tipsLabel.text = tips 39 | } 40 | } 41 | 42 | open var editStatusChangedBlock: ((_ isEdit: Bool) -> Void)? 43 | 44 | private lazy var titleLabel: UILabel = { 45 | let tl = UILabel() 46 | tl.textColor = UIColor.black 47 | tl.font = UIFont.systemFont(ofSize: 15) 48 | tl.textAlignment = .center 49 | tl.numberOfLines = 0 50 | return tl 51 | }() 52 | 53 | private lazy var tipsLabel: UILabel = { 54 | let tl = UILabel() 55 | tl.textColor = UIColor.lightGray 56 | tl.font = UIFont.systemFont(ofSize: 12.5) 57 | tl.textAlignment = .left 58 | tl.sizeToFit() 59 | return tl 60 | }() 61 | 62 | private lazy var editBtn: UIButton = { 63 | let eb = UIButton() 64 | eb.layer.borderColor = UIColor.red.cgColor 65 | eb.layer.masksToBounds = true 66 | eb.layer.borderWidth = 1 67 | eb.setTitle("编辑", for: .normal) 68 | eb.setTitle("完成", for: .selected) 69 | eb.titleLabel?.font = UIFont.systemFont(ofSize: 14) 70 | eb.setTitleColor(UIColor.red, for: .normal) 71 | eb.addTarget(self, action: #selector(btnDidClicked(btn:)), for: .touchDown) 72 | return eb 73 | }() 74 | 75 | override init(frame: CGRect) { 76 | super.init(frame: frame) 77 | initUI() 78 | } 79 | 80 | required init?(coder aDecoder: NSCoder) { 81 | super.init(coder: aDecoder) 82 | initUI() 83 | } 84 | 85 | override func layoutSubviews() { 86 | super.layoutSubviews() 87 | if isHiddenEdit == true { 88 | editBtn.isHidden = true 89 | } else { 90 | editBtn.isHidden = false 91 | } 92 | 93 | // headerMargin 94 | titleLabel.frame = CGRect(x: 0, y: 0, width: 65, height: bounds.height) 95 | 96 | tipsLabel.frame = CGRect(x: titleLabel.frame.maxX + headerMargin, y: 0, width: 80, height: bounds.height) 97 | 98 | editBtn.frame = CGRect(x: bounds.width - 60, y: headerMargin, width: 60, height: bounds.height - headerMargin*2) 99 | editBtn.layer.cornerRadius = 15 100 | } 101 | } 102 | 103 | extension YDChannelSelectorHeader { 104 | private func initUI() { 105 | addSubview(titleLabel) 106 | addSubview(tipsLabel) 107 | addSubview(editBtn) 108 | } 109 | 110 | @objc private func btnDidClicked(btn: UIButton) { 111 | btn.isSelected = !btn.isSelected 112 | editStatusChangedBlock?(btn.isSelected) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/YDChannelSelector/YDChannelSelectorCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorCell.swift 3 | // YDChannelSelectorViewDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// cell id 12 | public let YDChannelSelectorCellID = "YDChannelSelectorCellID" 13 | /// cell 内部控件之间间距 14 | fileprivate let cellMargin: CGFloat = yd_Inch35() ? 8:yd_Inch40() ? 8:12 15 | 16 | enum YDChannelSelectorCellType { 17 | case add 18 | case delete 19 | } 20 | 21 | class YDChannelSelectorCell: UICollectionViewCell { 22 | 23 | /// 长按点击事件 24 | public var longPressAction: ((_ lpg: UILongPressGestureRecognizer) -> Void)? 25 | 26 | /// 数据源 27 | public var dataSource: String? { 28 | didSet{ 29 | titleLabel.text = dataSource 30 | } 31 | } 32 | 33 | /// 是否是固定栏目 34 | public var isFixation = false 35 | 36 | /// 是否是当前选中red 37 | 38 | public var isCurrentSelected = false { 39 | didSet { 40 | titleLabel.textColor = isCurrentSelected ? UIColor.red : UIColor.black 41 | } 42 | } 43 | 44 | /// 当前cell类型 45 | public var cellType: YDChannelSelectorCellType? { 46 | didSet{ 47 | switch cellType { 48 | case .add?: 49 | iconView.image = UIImage(named: "selector_add") 50 | case .delete?: 51 | if isFixation { 52 | iconView.image = nil 53 | } else { 54 | iconView.image = UIImage(named: "selector_delete") 55 | } 56 | default: break 57 | } 58 | } 59 | } 60 | 61 | /// 是否进入编辑状态 62 | open var isEdit: Bool = false { 63 | didSet{ 64 | if isFixation && isEdit { // 是固定栏目并且在编辑状态下 65 | contentView.layer.borderColor = UIColor.clear.cgColor 66 | } else { 67 | contentView.layer.borderColor = UIColor(white: 0.9, alpha: 1).cgColor 68 | } 69 | 70 | setNeedsLayout() 71 | layoutIfNeeded() 72 | } 73 | } 74 | 75 | /// 长按手势识别器 76 | open lazy var longPressGes: UILongPressGestureRecognizer = { 77 | let lpg = UILongPressGestureRecognizer(target: self, action: #selector(longPress(lpg:))) 78 | lpg.delegate = self 79 | return lpg 80 | }() 81 | 82 | /// 触碰动画 83 | open func touchAnimate() { 84 | contentView.backgroundColor = UIColor(white: 0, alpha: 0.1) 85 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.12) { [weak self] in 86 | self?.contentView.backgroundColor = UIColor.white 87 | } 88 | } 89 | 90 | private lazy var iconView: UIImageView = { 91 | let iv = UIImageView() 92 | return iv 93 | }() 94 | 95 | private lazy var titleLabel: UILabel = { 96 | let tl = UILabel() 97 | tl.font = UIFont.systemFont(ofSize: 14) 98 | tl.textAlignment = .center 99 | tl.adjustsFontSizeToFitWidth = true 100 | tl.baselineAdjustment = .alignCenters 101 | tl.backgroundColor = UIColor.clear 102 | return tl 103 | }() 104 | 105 | override init(frame: CGRect) { 106 | super.init(frame: frame) 107 | initUI() 108 | } 109 | 110 | required init?(coder aDecoder: NSCoder) { 111 | super.init(coder: aDecoder) 112 | initUI() 113 | } 114 | 115 | override func layoutSubviews() { 116 | super.layoutSubviews() 117 | 118 | contentView.layer.cornerRadius = contentView.bounds.height/2 119 | let cellW = contentView.bounds.width 120 | let cellH = contentView.bounds.height 121 | 122 | // 设置对应情况下内部控件frame 123 | if isEdit == true { // 编辑状态下 124 | let iconW: CGFloat = yd_Inch35() ? 8:yd_Inch40() ? 8:10 125 | let iconX: CGFloat = cellMargin 126 | let iconY: CGFloat = (cellH - iconW)/2 127 | let iconH: CGFloat = iconW 128 | iconView.frame = CGRect(x: iconX, y: iconY, width: iconW, height: iconH) 129 | 130 | let titleW: CGFloat = cellW - iconView.frame.maxY - (yd_Inch35() ? -4:yd_Inch40() ? -4:0) 131 | let titleX: CGFloat = iconView.frame.maxX + 2 132 | let titleY: CGFloat = 0 133 | let titleH: CGFloat = cellH 134 | titleLabel.frame = CGRect(x: titleX, y: titleY, width: titleW, height: titleH) 135 | titleLabel.frame.size.width -= 10 136 | } else { // 非编辑状态下 137 | // 隐藏iconView 138 | iconView.frame = CGRect.zero 139 | titleLabel.frame = contentView.bounds 140 | titleLabel.frame.size.width -= (yd_Inch35() ? 10:yd_Inch40() ? 10:yd_Inch47() ? 15:18) 141 | titleLabel.frame.origin.x = (bounds.width - titleLabel.frame.size.width)/2 142 | } 143 | } 144 | 145 | } 146 | 147 | extension YDChannelSelectorCell { 148 | private func initUI() { 149 | // 手势添加 150 | addGestureRecognizer(longPressGes) 151 | 152 | // UI 153 | contentView.layer.borderWidth = 1 154 | contentView.layer.masksToBounds = true 155 | 156 | contentView.addSubview(titleLabel) 157 | contentView.addSubview(iconView) 158 | } 159 | 160 | @objc private func longPress(lpg: UILongPressGestureRecognizer) { 161 | guard lpg.state == .began else { return } 162 | longPressAction?(lpg) 163 | } 164 | } 165 | 166 | extension YDChannelSelectorCell: UIGestureRecognizerDelegate { 167 | override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 168 | return !(isEdit && cellType != .add) 169 | } 170 | } 171 | 172 | // frame 相关常用函数 173 | func yd_Inch35() -> Bool { return UIScreen.main.bounds.height == 480.0 } 174 | func yd_Inch40() -> Bool { return UIScreen.main.bounds.height == 568.0 } 175 | func yd_Inch47() -> Bool { return UIScreen.main.bounds.height == 667.0 } 176 | func yd_Inch55() -> Bool { return UIScreen.main.bounds.height == 736.0 } 177 | func yd_Inch58() -> Bool { return UIScreen.main.bounds.height == 812.0 } 178 | func yd_Inch65or61() -> Bool { return UIScreen.main.bounds.height == 896.0 } 179 | -------------------------------------------------------------------------------- /YDChannelSelector/Classes/YDChannelSelectorCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelectorCell.swift 3 | // YDChannelSelectorViewDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// cell id 12 | public let YDChannelSelectorCellID = "YDChannelSelectorCellID" 13 | /// cell 内部控件之间间距 14 | fileprivate let cellMargin: CGFloat = yd_Inch35() ? 8:yd_Inch40() ? 8:12 15 | 16 | enum YDChannelSelectorCellType { 17 | case add 18 | case delete 19 | } 20 | 21 | class YDChannelSelectorCell: UICollectionViewCell { 22 | 23 | /// 长按点击事件 24 | public var longPressAction: ((_ lpg: UILongPressGestureRecognizer) -> Void)? 25 | 26 | /// 数据源 27 | public var dataSource: String? { 28 | didSet{ 29 | titleLabel.text = dataSource 30 | } 31 | } 32 | 33 | /// 是否是固定栏目 34 | public var isFixation = false 35 | 36 | /// 是否是当前选中red 37 | 38 | public var isCurrentSelected = false { 39 | didSet { 40 | titleLabel.textColor = isCurrentSelected ? UIColor.red : UIColor.black 41 | } 42 | } 43 | 44 | /// 当前cell类型 45 | public var cellType: YDChannelSelectorCellType? { 46 | didSet{ 47 | switch cellType { 48 | case .add?: 49 | let bundle = Bundle(for: YDChannelSelector.self) 50 | let imagePath = bundle.path(forResource: "selector_add.png", ofType: nil, inDirectory: "YDChannelSelector.bundle") 51 | let image = UIImage(contentsOfFile: imagePath!) 52 | iconView.image = image 53 | case .delete?: 54 | if isFixation { 55 | iconView.image = nil 56 | } else { 57 | let bundel = Bundle(for: YDChannelSelector.self) 58 | let imagePath = bundel.path(forResource: "selector_delete.png", ofType: nil, inDirectory: "YDChannelSelector.bundle") 59 | let image = UIImage(contentsOfFile: imagePath!) 60 | iconView.image = image 61 | } 62 | default: break 63 | } 64 | } 65 | } 66 | 67 | /// 是否进入编辑状态 68 | open var isEdit: Bool = false { 69 | didSet{ 70 | if isFixation && isEdit { // 是固定栏目并且在编辑状态下 71 | contentView.layer.borderColor = UIColor.clear.cgColor 72 | } else { 73 | contentView.layer.borderColor = UIColor(white: 0.9, alpha: 1).cgColor 74 | } 75 | 76 | setNeedsLayout() 77 | layoutIfNeeded() 78 | } 79 | } 80 | 81 | /// 长按手势识别器 82 | open lazy var longPressGes: UILongPressGestureRecognizer = { 83 | let lpg = UILongPressGestureRecognizer(target: self, action: #selector(longPress(lpg:))) 84 | lpg.delegate = self 85 | return lpg 86 | }() 87 | 88 | /// 触碰动画 89 | open func touchAnimate() { 90 | contentView.backgroundColor = UIColor(white: 0, alpha: 0.1) 91 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.12) { [weak self] in 92 | self?.contentView.backgroundColor = UIColor.white 93 | } 94 | } 95 | 96 | private lazy var iconView: UIImageView = { 97 | let iv = UIImageView() 98 | return iv 99 | }() 100 | 101 | private lazy var titleLabel: UILabel = { 102 | let tl = UILabel() 103 | tl.font = UIFont.systemFont(ofSize: 14) 104 | tl.textAlignment = .center 105 | tl.adjustsFontSizeToFitWidth = true 106 | tl.baselineAdjustment = .alignCenters 107 | tl.backgroundColor = UIColor.clear 108 | return tl 109 | }() 110 | 111 | override init(frame: CGRect) { 112 | super.init(frame: frame) 113 | initUI() 114 | } 115 | 116 | required init?(coder aDecoder: NSCoder) { 117 | super.init(coder: aDecoder) 118 | initUI() 119 | } 120 | 121 | override func layoutSubviews() { 122 | super.layoutSubviews() 123 | 124 | contentView.layer.cornerRadius = contentView.bounds.height/2 125 | let cellW = contentView.bounds.width 126 | let cellH = contentView.bounds.height 127 | 128 | // 设置对应情况下内部控件frame 129 | if isEdit == true { // 编辑状态下 130 | let iconW: CGFloat = yd_Inch35() ? 8:yd_Inch40() ? 8:10 131 | let iconX: CGFloat = cellMargin 132 | let iconY: CGFloat = (cellH - iconW)/2 133 | let iconH: CGFloat = iconW 134 | iconView.frame = CGRect(x: iconX, y: iconY, width: iconW, height: iconH) 135 | 136 | let titleW: CGFloat = cellW - iconView.frame.maxY - (yd_Inch35() ? -4:yd_Inch40() ? -4:0) 137 | let titleX: CGFloat = iconView.frame.maxX + 2 138 | let titleY: CGFloat = 0 139 | let titleH: CGFloat = cellH 140 | titleLabel.frame = CGRect(x: titleX, y: titleY, width: titleW, height: titleH) 141 | titleLabel.frame.size.width -= 10 142 | } else { // 非编辑状态下 143 | // 隐藏iconView 144 | iconView.frame = CGRect.zero 145 | titleLabel.frame = contentView.bounds 146 | titleLabel.frame.size.width -= (yd_Inch35() ? 10:yd_Inch40() ? 10:yd_Inch47() ? 15:18) 147 | titleLabel.frame.origin.x = (bounds.width - titleLabel.frame.size.width)/2 148 | } 149 | } 150 | 151 | } 152 | 153 | extension YDChannelSelectorCell { 154 | private func initUI() { 155 | // 手势添加 156 | addGestureRecognizer(longPressGes) 157 | 158 | // UI 159 | contentView.layer.borderWidth = 1 160 | contentView.layer.masksToBounds = true 161 | 162 | contentView.addSubview(titleLabel) 163 | contentView.addSubview(iconView) 164 | } 165 | 166 | @objc private func longPress(lpg: UILongPressGestureRecognizer) { 167 | guard lpg.state == .began else { return } 168 | longPressAction?(lpg) 169 | } 170 | } 171 | 172 | extension YDChannelSelectorCell: UIGestureRecognizerDelegate { 173 | override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 174 | return !(isEdit && cellType != .add) 175 | } 176 | } 177 | 178 | // frame 相关常用函数 179 | func yd_Inch35() -> Bool { return UIScreen.main.bounds.height == 480.0 } 180 | func yd_Inch40() -> Bool { return UIScreen.main.bounds.height == 568.0 } 181 | func yd_Inch47() -> Bool { return UIScreen.main.bounds.height == 667.0 } 182 | func yd_Inch55() -> Bool { return UIScreen.main.bounds.height == 736.0 } 183 | func yd_Inch58() -> Bool { return UIScreen.main.bounds.height == 812.0 } 184 | func yd_Inch65or61() -> Bool { return UIScreen.main.bounds.height == 896.0 } 185 | -------------------------------------------------------------------------------- /YDChannelSelector/Classes/YDChannelSelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelector.swift 3 | // YDChannelSelectorDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol YDChannelSelectorDataSource: class { 12 | func numberOfSections(in selector: YDChannelSelector) -> Int 13 | func selector(_ selector: YDChannelSelector, numberOfItemsInSection section: Int) -> Int 14 | func selector(_ selector: YDChannelSelector, itemAt indexPath: IndexPath) -> SelectorItem 15 | } 16 | 17 | public protocol YDChannelSelectorDelegate: class { 18 | /// 数据源发生变化 19 | func selector(_ selector: YDChannelSelector, didChangeDS newDataSource: [[SelectorItem]]) 20 | /// 点击了关闭按钮 21 | func selector(_ selector: YDChannelSelector, dismiss newDataSource: [[SelectorItem]]) 22 | /// 点击了某个频道 23 | func selector(_ selector: YDChannelSelector, didSelectChannel channelItem: SelectorItem) 24 | } 25 | 26 | /// item间隙 27 | fileprivate let itemMargin: CGFloat = yd_Inch35() ? 15:yd_Inch40() ? 15:20 28 | /// item宽度 29 | fileprivate let itemW: CGFloat = (UIScreen.main.bounds.width-5*itemMargin)/4 30 | /// item高度 31 | fileprivate let itemH: CGFloat = 35 32 | /// 初始数据源 33 | fileprivate let originDS = "originDS" 34 | /// 用户操纵后数据源 35 | fileprivate let operatedDS = "operatedDS" 36 | 37 | public class YDChannelSelector: UIViewController { 38 | 39 | // MARK: - 对外属性方法 - 40 | 41 | /** 42 | 是否存储上次用户最后一次操作后的数据源(默认存储) 43 | 业务逻辑: 上次原始原始数据源和这次一样,返回上次用户最后操纵数据源,如果原始数据源有变动则返回新数据源,之前用户操作重置 44 | */ 45 | public var isCacheLastest = true 46 | 47 | /// 数据源 48 | public weak var dataSource: YDChannelSelectorDataSource? 49 | 50 | /// 代理 51 | public weak var delegate: YDChannelSelectorDelegate? 52 | 53 | /// 当前选中频道 54 | public var currentSelected: SelectorItem! 55 | 56 | // MARK: - 私有属性方法 - 57 | 58 | /// 私有数据源 59 | private var _dataSource: [[SelectorItem]]? { 60 | didSet{ 61 | // 错误过滤 62 | guard _dataSource != nil && !(_dataSource?.isEmpty)! else { 63 | assert(false, "DataSources can not be empty!") 64 | return 65 | } 66 | 67 | // 第一次处理 68 | guard isFixFlag == false else { return } 69 | 70 | // 默认选中第一个 71 | currentSelected = _dataSource?.first?.first 72 | 73 | isFixFlag = true 74 | } 75 | } 76 | 77 | /// 初始化处理标识 78 | private var isFixFlag = false 79 | 80 | /// 是否进入编辑状态 81 | private var isEdit: Bool = false { 82 | didSet{ 83 | collectionView.reloadData() 84 | } 85 | } 86 | 87 | /// 最近删除频道 88 | private var latelyDeleteChannels = [SelectorItem]() 89 | 90 | private lazy var titleLabel: UILabel = { 91 | let tl = UILabel() 92 | tl.textAlignment = .center 93 | tl.font = UIFont(name: "Helvetica-Bold", size: 18) 94 | tl.textColor = UIColor.black 95 | tl.text = "所有栏目" 96 | return tl 97 | }() 98 | 99 | private lazy var dismissBtn: UIButton = { 100 | let db = UIButton() 101 | db.setImage(UIImage(named: "selector_dismiss"), for: .normal) 102 | db.setTitleColor(UIColor.black, for: .normal) 103 | db.addTarget(self, action: #selector(remove), for: .touchUpInside) 104 | db.imageEdgeInsets = UIEdgeInsets.init(top: 5, left: 5, bottom: 5, right: 5) 105 | return db 106 | }() 107 | 108 | private lazy var longPressGes: UILongPressGestureRecognizer = { 109 | let lpg = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(ges:))) 110 | return lpg 111 | }() 112 | 113 | private lazy var collectionView: UICollectionView = { 114 | let layout = UICollectionViewFlowLayout() 115 | layout.minimumLineSpacing = itemMargin 116 | layout.minimumInteritemSpacing = itemMargin 117 | layout.itemSize = CGSize(width: itemW, height: itemH) 118 | let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) 119 | cv.contentInset = UIEdgeInsets.init(top: 0, left: itemMargin, bottom: 0, right: itemMargin) 120 | cv.backgroundColor = UIColor.white 121 | cv.showsVerticalScrollIndicator = false 122 | cv.delegate = self 123 | cv.dataSource = self 124 | cv.register(YDChannelSelectorCell.self, forCellWithReuseIdentifier: YDChannelSelectorCellID) 125 | cv.register(YDChannelSelectorHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: YDChannelSelectorHeaderID) 126 | cv.addGestureRecognizer(longPressGes) 127 | return cv 128 | }() 129 | 130 | // MARK: life cycle 131 | public override func viewDidLoad() { 132 | super.viewDidLoad() 133 | initUI() 134 | } 135 | 136 | public override func viewWillLayoutSubviews() { 137 | super.viewWillLayoutSubviews() 138 | titleLabel.frame = CGRect(x: (UIScreen.main.bounds.width-72)/2, y: yd_Inch58() || yd_Inch65or61() ? 40:20, width: 72, height: 44) 139 | dismissBtn.frame = CGRect(x: UIScreen.main.bounds.width-25-20, y: 0, width: 25, height: 25) 140 | dismissBtn.center.y = titleLabel.center.y 141 | collectionView.frame = CGRect(x: 0, y: titleLabel.frame.maxY, width: view.bounds.width, height: view.bounds.height - titleLabel.frame.maxY) 142 | } 143 | 144 | public override func viewWillAppear(_ animated: Bool) { 145 | super.viewWillAppear(animated) 146 | 147 | var dataSource_t = [[SelectorItem]]() 148 | 149 | for i in 0..<(dataSource?.numberOfSections(in: self) ?? 0) { 150 | var section_t = [SelectorItem]() 151 | for j in 0..<(dataSource?.selector(self, numberOfItemsInSection: i) ?? 0) { 152 | let item = dataSource?.selector(self, itemAt: IndexPath(item: j, section: i)) ?? SelectorItem(channelTitle: "unknown", rawData: nil) 153 | section_t.append(item) 154 | } 155 | dataSource_t.append(section_t) 156 | } 157 | 158 | // 根据需求处理数据源 159 | if isCacheLastest && UserDefaults.standard.value(forKey: operatedDS) != nil { // 需要缓存之前数据 且用户操作有存储 160 | // 缓存原始数据源 161 | if isCacheLastest { cacheDataSource(dataSource: dataSource_t, isOrigin: true) } 162 | var bool = false 163 | let newTitlesArrs = dataSource_t.map { $0.map { $0.channelTitle! } } 164 | let orginTitlesArrs = UserDefaults.standard.value(forKey: originDS) as? [[String]] 165 | // 之前有存过原始数据源 166 | if orginTitlesArrs != nil { bool = newTitlesArrs == orginTitlesArrs! } 167 | if bool { // 和之前数据相等 -> 返回缓存数据源 168 | let cacheTitleArrs = UserDefaults.standard.value(forKey: operatedDS) as? [[String]] 169 | let flatArr = dataSource_t.flatMap { $0 } 170 | var cachedDataSource = cacheTitleArrs!.map { $0.map { SelectorItem(channelTitle: $0, rawData: nil) }} 171 | for (i,items) in cachedDataSource.enumerated() { 172 | for (j,item) in items.enumerated() { 173 | for originItem in flatArr { 174 | if originItem.channelTitle == item.channelTitle { 175 | cachedDataSource[i][j] = originItem 176 | } 177 | } 178 | } 179 | } 180 | dataSource_t = cachedDataSource 181 | } else { // 和之前数据不等 -> 返回新数据源(不处理) 182 | 183 | } 184 | } 185 | 186 | // 预处理数据源 187 | var dataSource_tt = dataSource_t 188 | dataSource_tt.insert(latelyDeleteChannels, at: 1) 189 | _dataSource = dataSource_tt 190 | collectionView.reloadData() 191 | } 192 | 193 | public override func viewDidDisappear(_ animated: Bool) { 194 | super.viewDidDisappear(animated) 195 | 196 | // 移除界面后的一些操作 197 | if _dataSource?.count == 2 { // 初始只有1个section 198 | _dataSource![0] = _dataSource![0] + (_dataSource?[1] ?? []) 199 | } else { // 初始2个或更多section 200 | _dataSource![2] = _dataSource![1] + (_dataSource?[2] ?? []) 201 | 202 | } 203 | _dataSource?.remove(at: 1) 204 | latelyDeleteChannels.removeAll() 205 | } 206 | 207 | deinit { 208 | delegate = nil 209 | dataSource = nil 210 | } 211 | } 212 | 213 | extension YDChannelSelector { 214 | private func initUI() { 215 | view = HitTestView() 216 | view.backgroundColor = UIColor.white 217 | 218 | view.addSubview(titleLabel) 219 | view.addSubview(collectionView) 220 | view.addSubview(dismissBtn) 221 | 222 | (view as! HitTestView).collectionView = collectionView 223 | } 224 | 225 | @objc private func remove() { 226 | // 通知代理 227 | delegate?.selector(self, dismiss: _dataSource!) 228 | // 移除控制器 229 | dismiss(animated: true, completion: nil) 230 | } 231 | 232 | @objc private func handleLongGesture(ges: UILongPressGestureRecognizer) { 233 | guard isEdit == true else { return } 234 | switch(ges.state) { 235 | case .began: 236 | guard let selectedIndexPath = collectionView.indexPathForItem(at: ges.location(in: collectionView)) else { break } 237 | collectionView.beginInteractiveMovementForItem(at: selectedIndexPath) 238 | case .changed: 239 | collectionView.updateInteractiveMovementTargetPosition(ges.location(in: ges.view!)) 240 | case .ended: 241 | collectionView.endInteractiveMovement() 242 | default: 243 | collectionView.cancelInteractiveMovement() 244 | } 245 | } 246 | 247 | private func handleDataSource(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) { 248 | let sourceStr = _dataSource![sourceIndexPath.section][sourceIndexPath.row] 249 | if sourceIndexPath.section == 0 && destinationIndexPath.section == 1 { // 我的栏目 -> 最近删除 250 | latelyDeleteChannels.append(sourceStr) 251 | } 252 | 253 | if sourceIndexPath.section == 1 && destinationIndexPath.section == 0 && !latelyDeleteChannels.isEmpty { // 最近删除 -> 我的栏目 254 | latelyDeleteChannels.remove(at: sourceIndexPath.row) 255 | } 256 | 257 | _dataSource![sourceIndexPath.section].remove(at: sourceIndexPath.row) 258 | _dataSource![destinationIndexPath.section].insert(sourceStr, at: destinationIndexPath.row) 259 | 260 | // 通知代理 261 | delegate?.selector(self, didChangeDS: _dataSource!) 262 | // 存储用户操作 263 | cacheDataSource(dataSource: _dataSource!) 264 | } 265 | 266 | private func cacheDataSource(dataSource: [[SelectorItem]], isOrigin: Bool = false) { 267 | guard isCacheLastest else { return } 268 | var titlesArrs = dataSource.map { $0.map { $0.channelTitle! } } 269 | if isOrigin { // 原始数据源 270 | UserDefaults.standard.set(titlesArrs, forKey: originDS) 271 | } else { // 最近删除 -> 更多栏目 272 | if dataSource.count > 2 { // 有更多栏目 273 | titlesArrs[2] = titlesArrs[1] + titlesArrs[2] 274 | titlesArrs.remove(at: 1) 275 | UserDefaults.standard.set(titlesArrs, forKey: operatedDS) 276 | } else { // 没有更多栏目 277 | UserDefaults.standard.set(titlesArrs, forKey: operatedDS) 278 | } 279 | } 280 | UserDefaults.standard.synchronize() 281 | } 282 | } 283 | 284 | extension YDChannelSelector: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 285 | 286 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 287 | return _dataSource?.count ?? 0 288 | } 289 | 290 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 291 | return _dataSource![section].count 292 | } 293 | 294 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 295 | 296 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: YDChannelSelectorCellID, for: indexPath) as! YDChannelSelectorCell 297 | 298 | let item = _dataSource![indexPath.section][indexPath.row] 299 | 300 | // 设置固定栏目 301 | cell.isFixation = item.isFixation 302 | // 设置选中 303 | cell.isCurrentSelected = item.channelTitle == currentSelected.channelTitle 304 | // 设置数据源 305 | cell.dataSource = item.channelTitle 306 | 307 | if indexPath.section == 0 { // 是我的栏目 308 | cell.cellType = .delete 309 | cell.isEdit = isEdit 310 | } else { // 不是我的栏目 311 | cell.cellType = .add 312 | cell.isEdit = true 313 | } 314 | 315 | cell.longPressAction = { [weak self] _ in 316 | // 激活编辑状态 317 | self?.isEdit = true 318 | } 319 | 320 | // 手势冲突解决 321 | longPressGes.require(toFail: cell.longPressGes) 322 | 323 | return cell 324 | } 325 | 326 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 327 | let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: YDChannelSelectorHeaderID, for: indexPath) as! YDChannelSelectorHeader 328 | header.editStatusChangedBlock = { [weak self] (isEdit) in 329 | self?.isEdit = isEdit 330 | } 331 | if indexPath.section == 0 { 332 | header.isHiddenEdit = false 333 | header.isEdit = isEdit 334 | header.title = "我的栏目" 335 | if isEdit == true { 336 | header.tips = "拖动排序" 337 | } else { 338 | header.tips = "点击进入栏目" 339 | } 340 | } else if indexPath.section == 1 && !latelyDeleteChannels.isEmpty { 341 | header.isHiddenEdit = true 342 | header.title = "最近删除" 343 | header.tips = "" 344 | } else { 345 | header.isHiddenEdit = true 346 | header.title = "更多栏目" 347 | header.tips = "点击添加栏目" 348 | } 349 | return header 350 | } 351 | 352 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 353 | if section == 0 { // 我的栏目 354 | return CGSize(width: 0, height: 50) 355 | } else if section == 1 && latelyDeleteChannels.isEmpty { // 没有最近删除 356 | return CGSize.zero 357 | } else if section == 1 && !latelyDeleteChannels.isEmpty { // 有最近删除 358 | return CGSize(width: 0, height: 50) 359 | } else { // 更多栏目 360 | return CGSize(width: 0, height: 50) 361 | } 362 | } 363 | 364 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 365 | let cell = collectionView.cellForItem(at: indexPath) as! YDChannelSelectorCell 366 | if indexPath.section == 0 && isEdit && !cell.isFixation { // 我的栏目 非固定栏目 编辑状态下 -> 最近删除 367 | let destinationIndexPath = IndexPath(item: 0, section: 1) 368 | handleDataSource(sourceIndexPath: indexPath, destinationIndexPath: destinationIndexPath) 369 | collectionView.moveItem(at: indexPath, to: destinationIndexPath) 370 | let destinationCell = collectionView.cellForItem(at: destinationIndexPath) as! YDChannelSelectorCell 371 | destinationCell.cellType = .add 372 | } else if indexPath.section == 0 && !isEdit { // 我的栏目 非编辑状态下 -> 事件传递 373 | let channelItem = _dataSource![indexPath.section][indexPath.row] 374 | // 通知代理 375 | delegate?.selector(self, didSelectChannel: channelItem) 376 | // dismiss 377 | dismiss(animated: true, completion: nil) 378 | // 当前选中切换 379 | currentSelected = channelItem 380 | } else if indexPath.section != 0 { // 更多栏目 -> 我的栏目 381 | let destinationIndexPath = IndexPath(item: _dataSource![0].count, section: 0) 382 | handleDataSource(sourceIndexPath: indexPath, destinationIndexPath: destinationIndexPath) 383 | collectionView.moveItem(at: indexPath, to: destinationIndexPath) 384 | let destinationCell = collectionView.cellForItem(at: destinationIndexPath) as! YDChannelSelectorCell 385 | destinationCell.cellType = .delete 386 | destinationCell.isEdit = isEdit 387 | } 388 | } 389 | 390 | public func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { 391 | let item = _dataSource![indexPath.section][indexPath.row] 392 | if indexPath.section > 0 || item.isFixation { // 不是我的栏目 或者是固定栏目 393 | return false 394 | } else { 395 | return true 396 | } 397 | } 398 | 399 | public func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 400 | handleDataSource(sourceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath) 401 | } 402 | 403 | /// 这个方法里面控制需要移动和最后移动到的IndexPath(开始移动时) 404 | /// - Returns: 当前期望移动到的位置 405 | public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath { 406 | let item = _dataSource![proposedIndexPath.section][proposedIndexPath.row] 407 | if proposedIndexPath.section > 0 || item.isFixation { // 不是我的栏目 或者是固定栏目 408 | return originalIndexPath // 操作还原 409 | } else { 410 | return proposedIndexPath // 操作完成 411 | } 412 | } 413 | 414 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 415 | if latelyDeleteChannels.isEmpty { // 没有最近删除 416 | if indexPath.section == 1 { 417 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.zero 418 | return CGSize.zero 419 | } else { 420 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 20, right: 0) 421 | return CGSize(width: itemW, height: itemH) 422 | } 423 | } else { // 有最近删除 424 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 20, right: 0) 425 | return CGSize(width: itemW, height: itemH) 426 | } 427 | } 428 | } 429 | 430 | public struct SelectorItem { 431 | 432 | /// 频道名称 433 | public var channelTitle: String! 434 | /// 是否是固定栏目 435 | public var isFixation: Bool! 436 | /// 频道对应初始字典或模型 437 | public var rawData: Any? 438 | 439 | public init(channelTitle: String, isFixation: Bool = false, rawData: Any?) { 440 | self.channelTitle = channelTitle 441 | self.isFixation = isFixation 442 | self.rawData = rawData 443 | } 444 | } 445 | 446 | fileprivate class HitTestView: UIView { 447 | 448 | open var collectionView: UICollectionView! 449 | 450 | /// 拦截系统触碰事件 451 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 452 | if let indexPath = collectionView.indexPathForItem(at: convert(point, to: collectionView)) { // 在某个cell上 453 | let cell = collectionView.cellForItem(at: indexPath) as! YDChannelSelectorCell 454 | cell.touchAnimate() 455 | } 456 | return super.hitTest(point, with: event) 457 | } 458 | } 459 | 460 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo/YDChannelSelector/YDChannelSelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDChannelSelector.swift 3 | // YDChannelSelectorDemo 4 | // 5 | // Created by yudai on 2018/3/12. 6 | // Copyright © 2018年 TRS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol YDChannelSelectorDataSource: class { 12 | func numberOfSections(in selector: YDChannelSelector) -> Int 13 | func selector(_ selector: YDChannelSelector, numberOfItemsInSection section: Int) -> Int 14 | func selector(_ selector: YDChannelSelector, itemAt indexPath: IndexPath) -> SelectorItem 15 | } 16 | 17 | public protocol YDChannelSelectorDelegate: class { 18 | /// 数据源发生变化 19 | func selector(_ selector: YDChannelSelector, didChangeDS newDataSource: [[SelectorItem]]) 20 | /// 点击了关闭按钮 21 | func selector(_ selector: YDChannelSelector, dismiss newDataSource: [[SelectorItem]]) 22 | /// 点击了某个频道 23 | func selector(_ selector: YDChannelSelector, didSelectChannel channelItem: SelectorItem) 24 | } 25 | 26 | /// item间隙 27 | fileprivate let itemMargin: CGFloat = yd_Inch35() ? 15:yd_Inch40() ? 15:20 28 | /// item宽度 29 | fileprivate let itemW: CGFloat = (UIScreen.main.bounds.width-5*itemMargin)/4 30 | /// item高度 31 | fileprivate let itemH: CGFloat = 35 32 | /// 初始数据源 33 | fileprivate let originDS = "originDS" 34 | /// 用户操纵后数据源 35 | fileprivate let operatedDS = "operatedDS" 36 | 37 | public class YDChannelSelector: UIViewController { 38 | 39 | // MARK: - 对外属性方法 - 40 | 41 | /** 42 | 是否存储上次用户最后一次操作后的数据源(默认存储) 43 | 业务逻辑: 上次原始原始数据源和这次一样,返回上次用户最后操纵数据源,如果原始数据源有变动则返回新数据源,之前用户操作重置 44 | */ 45 | public var isCacheLastest = true 46 | 47 | /// 数据源 48 | public weak var dataSource: YDChannelSelectorDataSource? 49 | 50 | /// 代理 51 | public weak var delegate: YDChannelSelectorDelegate? 52 | 53 | /// 当前选中频道 54 | public var currentSelected: SelectorItem! 55 | 56 | /// 弹出界面 57 | public func show() { 58 | getCurrentVC()?.present(self, animated: true, completion: nil) 59 | } 60 | 61 | /// 隐藏界面 62 | public func dismiss() { 63 | dismiss(animated: true, completion: nil) 64 | } 65 | 66 | // MARK: - 私有属性方法 - 67 | 68 | /// 私有数据源 69 | private var _dataSource: [[SelectorItem]]? { 70 | didSet{ 71 | // 错误过滤 72 | guard _dataSource != nil && !(_dataSource?.isEmpty)! else { 73 | assert(false, "DataSources can not be empty!") 74 | return 75 | } 76 | 77 | // 第一次处理 78 | guard isFixFlag == false else { return } 79 | 80 | // 默认选中第一个 81 | currentSelected = _dataSource?.first?.first 82 | 83 | isFixFlag = true 84 | } 85 | } 86 | 87 | /// 初始化处理标识 88 | private var isFixFlag = false 89 | 90 | /// 是否进入编辑状态 91 | private var isEdit: Bool = false { 92 | didSet{ 93 | collectionView.reloadData() 94 | } 95 | } 96 | 97 | /// 最近删除频道 98 | private var latelyDeleteChannels = [SelectorItem]() 99 | 100 | private lazy var titleLabel: UILabel = { 101 | let tl = UILabel() 102 | tl.textAlignment = .center 103 | tl.font = UIFont(name: "Helvetica-Bold", size: 18) 104 | tl.textColor = UIColor.black 105 | tl.text = "所有栏目" 106 | return tl 107 | }() 108 | 109 | private lazy var dismissBtn: UIButton = { 110 | let db = UIButton() 111 | db.setImage(UIImage(named: "selector_dismiss"), for: .normal) 112 | db.setTitleColor(UIColor.black, for: .normal) 113 | db.addTarget(self, action: #selector(remove), for: .touchUpInside) 114 | db.imageEdgeInsets = UIEdgeInsets.init(top: 5, left: 5, bottom: 5, right: 5) 115 | return db 116 | }() 117 | 118 | private lazy var longPressGes: UILongPressGestureRecognizer = { 119 | let lpg = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(ges:))) 120 | return lpg 121 | }() 122 | 123 | private lazy var collectionView: UICollectionView = { 124 | let layout = UICollectionViewFlowLayout() 125 | layout.minimumLineSpacing = itemMargin 126 | layout.minimumInteritemSpacing = itemMargin 127 | layout.itemSize = CGSize(width: itemW, height: itemH) 128 | let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) 129 | cv.contentInset = UIEdgeInsets.init(top: 0, left: itemMargin, bottom: 0, right: itemMargin) 130 | cv.backgroundColor = UIColor.white 131 | cv.showsVerticalScrollIndicator = false 132 | cv.delegate = self 133 | cv.dataSource = self 134 | cv.register(YDChannelSelectorCell.self, forCellWithReuseIdentifier: YDChannelSelectorCellID) 135 | cv.register(YDChannelSelectorHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: YDChannelSelectorHeaderID) 136 | cv.addGestureRecognizer(longPressGes) 137 | return cv 138 | }() 139 | 140 | // MARK: life cycle 141 | public override func viewDidLoad() { 142 | super.viewDidLoad() 143 | initUI() 144 | } 145 | 146 | public override func viewWillLayoutSubviews() { 147 | super.viewWillLayoutSubviews() 148 | titleLabel.frame = CGRect(x: (UIScreen.main.bounds.width-72)/2, y: yd_Inch58() || yd_Inch65or61() ? 40:20, width: 72, height: 44) 149 | dismissBtn.frame = CGRect(x: UIScreen.main.bounds.width-25-20, y: 0, width: 25, height: 25) 150 | dismissBtn.center.y = titleLabel.center.y 151 | collectionView.frame = CGRect(x: 0, y: titleLabel.frame.maxY, width: view.bounds.width, height: view.bounds.height - titleLabel.frame.maxY) 152 | } 153 | 154 | public override func viewWillAppear(_ animated: Bool) { 155 | super.viewWillAppear(animated) 156 | 157 | var dataSource_t = [[SelectorItem]]() 158 | 159 | for i in 0..<(dataSource?.numberOfSections(in: self) ?? 0) { 160 | var section_t = [SelectorItem]() 161 | for j in 0..<(dataSource?.selector(self, numberOfItemsInSection: i) ?? 0) { 162 | let item = dataSource?.selector(self, itemAt: IndexPath(item: j, section: i)) ?? SelectorItem(channelTitle: "unknown", rawData: nil) 163 | section_t.append(item) 164 | } 165 | dataSource_t.append(section_t) 166 | } 167 | 168 | // 根据需求处理数据源 169 | if isCacheLastest && UserDefaults.standard.value(forKey: operatedDS) != nil { // 需要缓存之前数据 且用户操作有存储 170 | // 缓存原始数据源 171 | if isCacheLastest { cacheDataSource(dataSource: dataSource_t, isOrigin: true) } 172 | var bool = false 173 | let newTitlesArrs = dataSource_t.map { $0.map { $0.channelTitle! } } 174 | let orginTitlesArrs = UserDefaults.standard.value(forKey: originDS) as? [[String]] 175 | // 之前有存过原始数据源 176 | if orginTitlesArrs != nil { bool = newTitlesArrs == orginTitlesArrs! } 177 | if bool { // 和之前数据相等 -> 返回缓存数据源 178 | let cacheTitleArrs = UserDefaults.standard.value(forKey: operatedDS) as? [[String]] 179 | let flatArr = dataSource_t.flatMap { $0 } 180 | var cachedDataSource = cacheTitleArrs!.map { $0.map { SelectorItem(channelTitle: $0, rawData: nil) }} 181 | for (i,items) in cachedDataSource.enumerated() { 182 | for (j,item) in items.enumerated() { 183 | for originItem in flatArr { 184 | if originItem.channelTitle == item.channelTitle { 185 | cachedDataSource[i][j] = originItem 186 | } 187 | } 188 | } 189 | } 190 | dataSource_t = cachedDataSource 191 | } else { // 和之前数据不等 -> 返回新数据源(不处理) 192 | 193 | } 194 | } 195 | 196 | // 预处理数据源 197 | var dataSource_tt = dataSource_t 198 | dataSource_tt.insert(latelyDeleteChannels, at: 1) 199 | _dataSource = dataSource_tt 200 | collectionView.reloadData() 201 | } 202 | 203 | public override func viewDidDisappear(_ animated: Bool) { 204 | 205 | super.viewDidDisappear(animated) 206 | // 移除界面后的一些操作 207 | if _dataSource?.count == 2 { // 初始只有1个section 208 | _dataSource![0] = _dataSource![0] + (_dataSource?[1] ?? []) 209 | } else { // 初始2个或更多section 210 | _dataSource![2] = _dataSource![1] + (_dataSource?[2] ?? []) 211 | } 212 | _dataSource?.remove(at: 1) 213 | latelyDeleteChannels.removeAll() 214 | 215 | } 216 | 217 | deinit { 218 | delegate = nil 219 | dataSource = nil 220 | } 221 | } 222 | 223 | extension YDChannelSelector { 224 | private func initUI() { 225 | view = HitTestView() 226 | view.backgroundColor = UIColor.white 227 | 228 | view.addSubview(titleLabel) 229 | view.addSubview(collectionView) 230 | view.addSubview(dismissBtn) 231 | 232 | (view as! HitTestView).collectionView = collectionView 233 | } 234 | 235 | @objc private func remove() { 236 | // 通知代理 237 | delegate?.selector(self, dismiss: _dataSource!) 238 | // 移除控制器 239 | dismiss(animated: true, completion: nil) 240 | } 241 | 242 | @objc private func handleLongGesture(ges: UILongPressGestureRecognizer) { 243 | guard isEdit == true else { return } 244 | switch(ges.state) { 245 | case .began: 246 | guard let selectedIndexPath = collectionView.indexPathForItem(at: ges.location(in: collectionView)) else { break } 247 | collectionView.beginInteractiveMovementForItem(at: selectedIndexPath) 248 | case .changed: 249 | collectionView.updateInteractiveMovementTargetPosition(ges.location(in: ges.view!)) 250 | case .ended: 251 | collectionView.endInteractiveMovement() 252 | default: 253 | collectionView.cancelInteractiveMovement() 254 | } 255 | } 256 | 257 | private func handleDataSource(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) { 258 | let sourceStr = _dataSource![sourceIndexPath.section][sourceIndexPath.row] 259 | if sourceIndexPath.section == 0 && destinationIndexPath.section == 1 { // 我的栏目 -> 最近删除 260 | latelyDeleteChannels.append(sourceStr) 261 | } 262 | 263 | if sourceIndexPath.section == 1 && destinationIndexPath.section == 0 && !latelyDeleteChannels.isEmpty { // 最近删除 -> 我的栏目 264 | latelyDeleteChannels.remove(at: sourceIndexPath.row) 265 | } 266 | 267 | _dataSource![sourceIndexPath.section].remove(at: sourceIndexPath.row) 268 | _dataSource![destinationIndexPath.section].insert(sourceStr, at: destinationIndexPath.row) 269 | 270 | // 通知代理 271 | delegate?.selector(self, didChangeDS: _dataSource!) 272 | // 存储用户操作 273 | cacheDataSource(dataSource: _dataSource!) 274 | } 275 | 276 | private func cacheDataSource(dataSource: [[SelectorItem]], isOrigin: Bool = false) { 277 | guard isCacheLastest else { return } 278 | var titlesArrs = dataSource.map { $0.map { $0.channelTitle! } } 279 | if isOrigin { // 原始数据源 280 | UserDefaults.standard.set(titlesArrs, forKey: originDS) 281 | } else { // 最近删除 -> 更多栏目 282 | if dataSource.count > 2 { // 有更多栏目 283 | titlesArrs[2] = titlesArrs[1] + titlesArrs[2] 284 | titlesArrs.remove(at: 1) 285 | UserDefaults.standard.set(titlesArrs, forKey: operatedDS) 286 | } else { // 没有更多栏目 287 | UserDefaults.standard.set(titlesArrs, forKey: operatedDS) 288 | } 289 | } 290 | UserDefaults.standard.synchronize() 291 | } 292 | } 293 | 294 | extension YDChannelSelector: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 295 | 296 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 297 | return _dataSource?.count ?? 0 298 | } 299 | 300 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 301 | return _dataSource![section].count 302 | } 303 | 304 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 305 | 306 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: YDChannelSelectorCellID, for: indexPath) as! YDChannelSelectorCell 307 | 308 | let item = _dataSource![indexPath.section][indexPath.row] 309 | 310 | // 设置固定栏目 311 | cell.isFixation = item.isFixation 312 | // 设置选中 313 | cell.isCurrentSelected = item.channelTitle == currentSelected.channelTitle 314 | // 设置数据源 315 | cell.dataSource = item.channelTitle 316 | 317 | if indexPath.section == 0 { // 是我的栏目 318 | cell.cellType = .delete 319 | cell.isEdit = isEdit 320 | } else { // 不是我的栏目 321 | cell.cellType = .add 322 | cell.isEdit = true 323 | } 324 | 325 | cell.longPressAction = { [weak self] _ in 326 | // 激活编辑状态 327 | self?.isEdit = true 328 | } 329 | 330 | // 手势冲突解决 331 | longPressGes.require(toFail: cell.longPressGes) 332 | 333 | return cell 334 | } 335 | 336 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 337 | let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: YDChannelSelectorHeaderID, for: indexPath) as! YDChannelSelectorHeader 338 | header.editStatusChangedBlock = { [weak self] (isEdit) in 339 | self?.isEdit = isEdit 340 | } 341 | if indexPath.section == 0 { 342 | header.isHiddenEdit = false 343 | header.isEdit = isEdit 344 | header.title = "我的栏目" 345 | if isEdit == true { 346 | header.tips = "拖动排序" 347 | } else { 348 | header.tips = "点击进入栏目" 349 | } 350 | } else if indexPath.section == 1 && !latelyDeleteChannels.isEmpty { 351 | header.isHiddenEdit = true 352 | header.title = "最近删除" 353 | header.tips = "" 354 | } else { 355 | header.isHiddenEdit = true 356 | header.title = "更多栏目" 357 | header.tips = "点击添加栏目" 358 | } 359 | return header 360 | } 361 | 362 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 363 | if section == 0 { // 我的栏目 364 | return CGSize(width: 0, height: 50) 365 | } else if section == 1 && latelyDeleteChannels.isEmpty { // 没有最近删除 366 | return CGSize.zero 367 | } else if section == 1 && !latelyDeleteChannels.isEmpty { // 有最近删除 368 | return CGSize(width: 0, height: 50) 369 | } else { // 更多栏目 370 | return CGSize(width: 0, height: 50) 371 | } 372 | } 373 | 374 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 375 | let cell = collectionView.cellForItem(at: indexPath) as! YDChannelSelectorCell 376 | if indexPath.section == 0 && isEdit && !cell.isFixation { // 我的栏目 非固定栏目 编辑状态下 -> 最近删除 377 | let destinationIndexPath = IndexPath(item: 0, section: 1) 378 | handleDataSource(sourceIndexPath: indexPath, destinationIndexPath: destinationIndexPath) 379 | collectionView.moveItem(at: indexPath, to: destinationIndexPath) 380 | let destinationCell = collectionView.cellForItem(at: destinationIndexPath) as! YDChannelSelectorCell 381 | destinationCell.cellType = .add 382 | } else if indexPath.section == 0 && !isEdit { // 我的栏目 非编辑状态下 -> 事件传递 383 | let channelItem = _dataSource![indexPath.section][indexPath.row] 384 | // 通知代理 385 | delegate?.selector(self, didSelectChannel: channelItem) 386 | // dismiss 387 | dismiss(animated: true, completion: nil) 388 | // 当前选中切换 389 | currentSelected = channelItem 390 | } else if indexPath.section != 0 { // 更多栏目 -> 我的栏目 391 | let destinationIndexPath = IndexPath(item: _dataSource![0].count, section: 0) 392 | handleDataSource(sourceIndexPath: indexPath, destinationIndexPath: destinationIndexPath) 393 | collectionView.moveItem(at: indexPath, to: destinationIndexPath) 394 | let destinationCell = collectionView.cellForItem(at: destinationIndexPath) as! YDChannelSelectorCell 395 | destinationCell.cellType = .delete 396 | destinationCell.isEdit = isEdit 397 | } 398 | } 399 | 400 | public func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { 401 | let item = _dataSource![indexPath.section][indexPath.row] 402 | if indexPath.section > 0 || item.isFixation { // 不是我的栏目 或者是固定栏目 403 | return false 404 | } else { 405 | return true 406 | } 407 | } 408 | 409 | public func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 410 | handleDataSource(sourceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath) 411 | } 412 | 413 | /// 这个方法里面控制需要移动和最后移动到的IndexPath(开始移动时) 414 | /// - Returns: 当前期望移动到的位置 415 | public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath { 416 | let item = _dataSource![proposedIndexPath.section][proposedIndexPath.row] 417 | if proposedIndexPath.section > 0 || item.isFixation { // 不是我的栏目 或者是固定栏目 418 | return originalIndexPath // 操作还原 419 | } else { 420 | return proposedIndexPath // 操作完成 421 | } 422 | } 423 | 424 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 425 | if latelyDeleteChannels.isEmpty { // 没有最近删除 426 | if indexPath.section == 1 { 427 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.zero 428 | return CGSize.zero 429 | } else { 430 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 20, right: 0) 431 | return CGSize(width: itemW, height: itemH) 432 | } 433 | } else { // 有最近删除 434 | (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 20, right: 0) 435 | return CGSize(width: itemW, height: itemH) 436 | } 437 | } 438 | } 439 | 440 | public struct SelectorItem { 441 | 442 | /// 频道名称 443 | public var channelTitle: String! 444 | /// 是否是固定栏目 445 | public var isFixation: Bool! 446 | /// 频道对应初始字典或模型 447 | public var rawData: Any? 448 | 449 | public init(channelTitle: String, isFixation: Bool = false, rawData: Any?) { 450 | self.channelTitle = channelTitle 451 | self.isFixation = isFixation 452 | self.rawData = rawData 453 | } 454 | } 455 | 456 | fileprivate class HitTestView: UIView { 457 | 458 | open var collectionView: UICollectionView! 459 | 460 | /// 拦截系统触碰事件 461 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 462 | if let indexPath = collectionView.indexPathForItem(at: convert(point, to: collectionView)) { // 在某个cell上 463 | let cell = collectionView.cellForItem(at: indexPath) as! YDChannelSelectorCell 464 | cell.touchAnimate() 465 | } 466 | 467 | return super.hitTest(point, with: event) 468 | } 469 | } 470 | 471 | private func getCurrentVC() -> UIViewController? { 472 | guard let window = UIApplication.shared.windows.first else { return nil } 473 | var tempView: UIView? 474 | for subview in window.subviews { 475 | if subview.classForCoder.description() == "UILayoutContainerView" { 476 | tempView = subview 477 | break 478 | } 479 | } 480 | 481 | if tempView == nil { 482 | tempView = window.subviews.last 483 | } 484 | 485 | var nextResponder = tempView?.next 486 | while (!(nextResponder?.isKind(of: UIViewController.self))!) || (nextResponder?.isKind(of: UINavigationController.self))! || (nextResponder?.isKind(of: UITabBarController.self))! { 487 | tempView = tempView?.subviews.first 488 | if tempView == nil { 489 | return nil 490 | } 491 | nextResponder = tempView?.next 492 | } 493 | return nextResponder as? UIViewController 494 | } 495 | -------------------------------------------------------------------------------- /YDChannelSelectorDemo/YDChannelSelectorDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 784640D521A6A556008EDE5D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784640D421A6A556008EDE5D /* AppDelegate.swift */; }; 11 | 784640D721A6A556008EDE5D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784640D621A6A556008EDE5D /* ViewController.swift */; }; 12 | 784640DA21A6A556008EDE5D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 784640D821A6A556008EDE5D /* Main.storyboard */; }; 13 | 784640DC21A6A558008EDE5D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 784640DB21A6A558008EDE5D /* Assets.xcassets */; }; 14 | 784640EA21A6A558008EDE5D /* YDChannelSelectorDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784640E921A6A558008EDE5D /* YDChannelSelectorDemoTests.swift */; }; 15 | 784640F521A6A558008EDE5D /* YDChannelSelectorDemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784640F421A6A558008EDE5D /* YDChannelSelectorDemoUITests.swift */; }; 16 | 7846410621A6A6D6008EDE5D /* YDChannelSelectorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7846410321A6A6D6008EDE5D /* YDChannelSelectorCell.swift */; }; 17 | 7846410721A6A6D6008EDE5D /* YDChannelSelectorHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7846410421A6A6D6008EDE5D /* YDChannelSelectorHeader.swift */; }; 18 | 7846410821A6A6D6008EDE5D /* YDChannelSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7846410521A6A6D6008EDE5D /* YDChannelSelector.swift */; }; 19 | 7846410D21A6A784008EDE5D /* selector_add.png in Resources */ = {isa = PBXBuildFile; fileRef = 7846410A21A6A784008EDE5D /* selector_add.png */; }; 20 | 7846410E21A6A784008EDE5D /* selector_delete.png in Resources */ = {isa = PBXBuildFile; fileRef = 7846410B21A6A784008EDE5D /* selector_delete.png */; }; 21 | 7846410F21A6A784008EDE5D /* selector_dismiss.png in Resources */ = {isa = PBXBuildFile; fileRef = 7846410C21A6A784008EDE5D /* selector_dismiss.png */; }; 22 | 7846413521A6A91C008EDE5D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7846413421A6A91C008EDE5D /* LaunchScreen.storyboard */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 784640E621A6A558008EDE5D /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 784640C921A6A556008EDE5D /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 784640D021A6A556008EDE5D; 31 | remoteInfo = YDChannelSelectorDemo; 32 | }; 33 | 784640F121A6A558008EDE5D /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 784640C921A6A556008EDE5D /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 784640D021A6A556008EDE5D; 38 | remoteInfo = YDChannelSelectorDemo; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 784640D121A6A556008EDE5D /* YDChannelSelectorDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YDChannelSelectorDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 784640D421A6A556008EDE5D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 784640D621A6A556008EDE5D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 46 | 784640D921A6A556008EDE5D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 784640DB21A6A558008EDE5D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 784640E021A6A558008EDE5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 784640E521A6A558008EDE5D /* YDChannelSelectorDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YDChannelSelectorDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 784640E921A6A558008EDE5D /* YDChannelSelectorDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YDChannelSelectorDemoTests.swift; sourceTree = ""; }; 51 | 784640EB21A6A558008EDE5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 784640F021A6A558008EDE5D /* YDChannelSelectorDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YDChannelSelectorDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 784640F421A6A558008EDE5D /* YDChannelSelectorDemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YDChannelSelectorDemoUITests.swift; sourceTree = ""; }; 54 | 784640F621A6A558008EDE5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 7846410321A6A6D6008EDE5D /* YDChannelSelectorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YDChannelSelectorCell.swift; sourceTree = ""; }; 56 | 7846410421A6A6D6008EDE5D /* YDChannelSelectorHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YDChannelSelectorHeader.swift; sourceTree = ""; }; 57 | 7846410521A6A6D6008EDE5D /* YDChannelSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YDChannelSelector.swift; sourceTree = ""; }; 58 | 7846410A21A6A784008EDE5D /* selector_add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = selector_add.png; path = ../../../../YDChannelSelector/Assets/selector_add.png; sourceTree = ""; }; 59 | 7846410B21A6A784008EDE5D /* selector_delete.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = selector_delete.png; path = ../../../../YDChannelSelector/Assets/selector_delete.png; sourceTree = ""; }; 60 | 7846410C21A6A784008EDE5D /* selector_dismiss.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = selector_dismiss.png; path = ../../../../YDChannelSelector/Assets/selector_dismiss.png; sourceTree = ""; }; 61 | 7846413421A6A91C008EDE5D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 784640CE21A6A556008EDE5D /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 784640E221A6A558008EDE5D /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 784640ED21A6A558008EDE5D /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 784640C821A6A556008EDE5D = { 90 | isa = PBXGroup; 91 | children = ( 92 | 784640D321A6A556008EDE5D /* YDChannelSelectorDemo */, 93 | 784640E821A6A558008EDE5D /* YDChannelSelectorDemoTests */, 94 | 784640F321A6A558008EDE5D /* YDChannelSelectorDemoUITests */, 95 | 784640D221A6A556008EDE5D /* Products */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 784640D221A6A556008EDE5D /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 784640D121A6A556008EDE5D /* YDChannelSelectorDemo.app */, 103 | 784640E521A6A558008EDE5D /* YDChannelSelectorDemoTests.xctest */, 104 | 784640F021A6A558008EDE5D /* YDChannelSelectorDemoUITests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 784640D321A6A556008EDE5D /* YDChannelSelectorDemo */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 7846410221A6A67D008EDE5D /* YDChannelSelector */, 113 | 784640D421A6A556008EDE5D /* AppDelegate.swift */, 114 | 784640D621A6A556008EDE5D /* ViewController.swift */, 115 | 784640D821A6A556008EDE5D /* Main.storyboard */, 116 | 784640DB21A6A558008EDE5D /* Assets.xcassets */, 117 | 7846413421A6A91C008EDE5D /* LaunchScreen.storyboard */, 118 | 784640E021A6A558008EDE5D /* Info.plist */, 119 | ); 120 | path = YDChannelSelectorDemo; 121 | sourceTree = ""; 122 | }; 123 | 784640E821A6A558008EDE5D /* YDChannelSelectorDemoTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 784640E921A6A558008EDE5D /* YDChannelSelectorDemoTests.swift */, 127 | 784640EB21A6A558008EDE5D /* Info.plist */, 128 | ); 129 | path = YDChannelSelectorDemoTests; 130 | sourceTree = ""; 131 | }; 132 | 784640F321A6A558008EDE5D /* YDChannelSelectorDemoUITests */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 784640F421A6A558008EDE5D /* YDChannelSelectorDemoUITests.swift */, 136 | 784640F621A6A558008EDE5D /* Info.plist */, 137 | ); 138 | path = YDChannelSelectorDemoUITests; 139 | sourceTree = ""; 140 | }; 141 | 7846410221A6A67D008EDE5D /* YDChannelSelector */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 7846410921A6A767008EDE5D /* Resource */, 145 | 7846410521A6A6D6008EDE5D /* YDChannelSelector.swift */, 146 | 7846410321A6A6D6008EDE5D /* YDChannelSelectorCell.swift */, 147 | 7846410421A6A6D6008EDE5D /* YDChannelSelectorHeader.swift */, 148 | ); 149 | path = YDChannelSelector; 150 | sourceTree = ""; 151 | }; 152 | 7846410921A6A767008EDE5D /* Resource */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 7846410A21A6A784008EDE5D /* selector_add.png */, 156 | 7846410B21A6A784008EDE5D /* selector_delete.png */, 157 | 7846410C21A6A784008EDE5D /* selector_dismiss.png */, 158 | ); 159 | path = Resource; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXGroup section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | 784640D021A6A556008EDE5D /* YDChannelSelectorDemo */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 784640F921A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemo" */; 168 | buildPhases = ( 169 | 784640CD21A6A556008EDE5D /* Sources */, 170 | 784640CE21A6A556008EDE5D /* Frameworks */, 171 | 784640CF21A6A556008EDE5D /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = YDChannelSelectorDemo; 178 | productName = YDChannelSelectorDemo; 179 | productReference = 784640D121A6A556008EDE5D /* YDChannelSelectorDemo.app */; 180 | productType = "com.apple.product-type.application"; 181 | }; 182 | 784640E421A6A558008EDE5D /* YDChannelSelectorDemoTests */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 784640FC21A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemoTests" */; 185 | buildPhases = ( 186 | 784640E121A6A558008EDE5D /* Sources */, 187 | 784640E221A6A558008EDE5D /* Frameworks */, 188 | 784640E321A6A558008EDE5D /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | 784640E721A6A558008EDE5D /* PBXTargetDependency */, 194 | ); 195 | name = YDChannelSelectorDemoTests; 196 | productName = YDChannelSelectorDemoTests; 197 | productReference = 784640E521A6A558008EDE5D /* YDChannelSelectorDemoTests.xctest */; 198 | productType = "com.apple.product-type.bundle.unit-test"; 199 | }; 200 | 784640EF21A6A558008EDE5D /* YDChannelSelectorDemoUITests */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 784640FF21A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemoUITests" */; 203 | buildPhases = ( 204 | 784640EC21A6A558008EDE5D /* Sources */, 205 | 784640ED21A6A558008EDE5D /* Frameworks */, 206 | 784640EE21A6A558008EDE5D /* Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | 784640F221A6A558008EDE5D /* PBXTargetDependency */, 212 | ); 213 | name = YDChannelSelectorDemoUITests; 214 | productName = YDChannelSelectorDemoUITests; 215 | productReference = 784640F021A6A558008EDE5D /* YDChannelSelectorDemoUITests.xctest */; 216 | productType = "com.apple.product-type.bundle.ui-testing"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | 784640C921A6A556008EDE5D /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastSwiftUpdateCheck = 1010; 225 | LastUpgradeCheck = 1010; 226 | ORGANIZATIONNAME = yudai; 227 | TargetAttributes = { 228 | 784640D021A6A556008EDE5D = { 229 | CreatedOnToolsVersion = 10.1; 230 | }; 231 | 784640E421A6A558008EDE5D = { 232 | CreatedOnToolsVersion = 10.1; 233 | TestTargetID = 784640D021A6A556008EDE5D; 234 | }; 235 | 784640EF21A6A558008EDE5D = { 236 | CreatedOnToolsVersion = 10.1; 237 | TestTargetID = 784640D021A6A556008EDE5D; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = 784640CC21A6A556008EDE5D /* Build configuration list for PBXProject "YDChannelSelectorDemo" */; 242 | compatibilityVersion = "Xcode 9.3"; 243 | developmentRegion = en; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | Base, 248 | ); 249 | mainGroup = 784640C821A6A556008EDE5D; 250 | productRefGroup = 784640D221A6A556008EDE5D /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | 784640D021A6A556008EDE5D /* YDChannelSelectorDemo */, 255 | 784640E421A6A558008EDE5D /* YDChannelSelectorDemoTests */, 256 | 784640EF21A6A558008EDE5D /* YDChannelSelectorDemoUITests */, 257 | ); 258 | }; 259 | /* End PBXProject section */ 260 | 261 | /* Begin PBXResourcesBuildPhase section */ 262 | 784640CF21A6A556008EDE5D /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 7846410D21A6A784008EDE5D /* selector_add.png in Resources */, 267 | 7846410E21A6A784008EDE5D /* selector_delete.png in Resources */, 268 | 784640DC21A6A558008EDE5D /* Assets.xcassets in Resources */, 269 | 7846413521A6A91C008EDE5D /* LaunchScreen.storyboard in Resources */, 270 | 784640DA21A6A556008EDE5D /* Main.storyboard in Resources */, 271 | 7846410F21A6A784008EDE5D /* selector_dismiss.png in Resources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | 784640E321A6A558008EDE5D /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | 784640EE21A6A558008EDE5D /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXResourcesBuildPhase section */ 290 | 291 | /* Begin PBXSourcesBuildPhase section */ 292 | 784640CD21A6A556008EDE5D /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | 7846410721A6A6D6008EDE5D /* YDChannelSelectorHeader.swift in Sources */, 297 | 7846410821A6A6D6008EDE5D /* YDChannelSelector.swift in Sources */, 298 | 784640D721A6A556008EDE5D /* ViewController.swift in Sources */, 299 | 784640D521A6A556008EDE5D /* AppDelegate.swift in Sources */, 300 | 7846410621A6A6D6008EDE5D /* YDChannelSelectorCell.swift in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 784640E121A6A558008EDE5D /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 784640EA21A6A558008EDE5D /* YDChannelSelectorDemoTests.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | 784640EC21A6A558008EDE5D /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | 784640F521A6A558008EDE5D /* YDChannelSelectorDemoUITests.swift in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXSourcesBuildPhase section */ 321 | 322 | /* Begin PBXTargetDependency section */ 323 | 784640E721A6A558008EDE5D /* PBXTargetDependency */ = { 324 | isa = PBXTargetDependency; 325 | target = 784640D021A6A556008EDE5D /* YDChannelSelectorDemo */; 326 | targetProxy = 784640E621A6A558008EDE5D /* PBXContainerItemProxy */; 327 | }; 328 | 784640F221A6A558008EDE5D /* PBXTargetDependency */ = { 329 | isa = PBXTargetDependency; 330 | target = 784640D021A6A556008EDE5D /* YDChannelSelectorDemo */; 331 | targetProxy = 784640F121A6A558008EDE5D /* PBXContainerItemProxy */; 332 | }; 333 | /* End PBXTargetDependency section */ 334 | 335 | /* Begin PBXVariantGroup section */ 336 | 784640D821A6A556008EDE5D /* Main.storyboard */ = { 337 | isa = PBXVariantGroup; 338 | children = ( 339 | 784640D921A6A556008EDE5D /* Base */, 340 | ); 341 | name = Main.storyboard; 342 | sourceTree = ""; 343 | }; 344 | /* End PBXVariantGroup section */ 345 | 346 | /* Begin XCBuildConfiguration section */ 347 | 784640F721A6A558008EDE5D /* Debug */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | ALWAYS_SEARCH_USER_PATHS = NO; 351 | CLANG_ANALYZER_NONNULL = YES; 352 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 353 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 354 | CLANG_CXX_LIBRARY = "libc++"; 355 | CLANG_ENABLE_MODULES = YES; 356 | CLANG_ENABLE_OBJC_ARC = YES; 357 | CLANG_ENABLE_OBJC_WEAK = YES; 358 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 359 | CLANG_WARN_BOOL_CONVERSION = YES; 360 | CLANG_WARN_COMMA = YES; 361 | CLANG_WARN_CONSTANT_CONVERSION = YES; 362 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 363 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 364 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 365 | CLANG_WARN_EMPTY_BODY = YES; 366 | CLANG_WARN_ENUM_CONVERSION = YES; 367 | CLANG_WARN_INFINITE_RECURSION = YES; 368 | CLANG_WARN_INT_CONVERSION = YES; 369 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 371 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 373 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 374 | CLANG_WARN_STRICT_PROTOTYPES = YES; 375 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 376 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | CODE_SIGN_IDENTITY = "iPhone Developer"; 380 | COPY_PHASE_STRIP = NO; 381 | DEBUG_INFORMATION_FORMAT = dwarf; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | ENABLE_TESTABILITY = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu11; 385 | GCC_DYNAMIC_NO_PIC = NO; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_OPTIMIZATION_LEVEL = 0; 388 | GCC_PREPROCESSOR_DEFINITIONS = ( 389 | "DEBUG=1", 390 | "$(inherited)", 391 | ); 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 399 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 400 | MTL_FAST_MATH = YES; 401 | ONLY_ACTIVE_ARCH = YES; 402 | SDKROOT = iphoneos; 403 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 404 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 405 | }; 406 | name = Debug; 407 | }; 408 | 784640F821A6A558008EDE5D /* Release */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ALWAYS_SEARCH_USER_PATHS = NO; 412 | CLANG_ANALYZER_NONNULL = YES; 413 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 414 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 415 | CLANG_CXX_LIBRARY = "libc++"; 416 | CLANG_ENABLE_MODULES = YES; 417 | CLANG_ENABLE_OBJC_ARC = YES; 418 | CLANG_ENABLE_OBJC_WEAK = YES; 419 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_COMMA = YES; 422 | CLANG_WARN_CONSTANT_CONVERSION = YES; 423 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 426 | CLANG_WARN_EMPTY_BODY = YES; 427 | CLANG_WARN_ENUM_CONVERSION = YES; 428 | CLANG_WARN_INFINITE_RECURSION = YES; 429 | CLANG_WARN_INT_CONVERSION = YES; 430 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 432 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 435 | CLANG_WARN_STRICT_PROTOTYPES = YES; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | CODE_SIGN_IDENTITY = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu11; 446 | GCC_NO_COMMON_BLOCKS = YES; 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 454 | MTL_ENABLE_DEBUG_INFO = NO; 455 | MTL_FAST_MATH = YES; 456 | SDKROOT = iphoneos; 457 | SWIFT_COMPILATION_MODE = wholemodule; 458 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 459 | VALIDATE_PRODUCT = YES; 460 | }; 461 | name = Release; 462 | }; 463 | 784640FA21A6A558008EDE5D /* Debug */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 467 | CODE_SIGN_STYLE = Automatic; 468 | DEVELOPMENT_TEAM = HG59X9KXU6; 469 | INFOPLIST_FILE = YDChannelSelectorDemo/Info.plist; 470 | LD_RUNPATH_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "@executable_path/Frameworks", 473 | ); 474 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemo; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_VERSION = 4.2; 477 | TARGETED_DEVICE_FAMILY = "1,2"; 478 | }; 479 | name = Debug; 480 | }; 481 | 784640FB21A6A558008EDE5D /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 485 | CODE_SIGN_STYLE = Automatic; 486 | DEVELOPMENT_TEAM = HG59X9KXU6; 487 | INFOPLIST_FILE = YDChannelSelectorDemo/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "@executable_path/Frameworks", 491 | ); 492 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemo; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SWIFT_VERSION = 4.2; 495 | TARGETED_DEVICE_FAMILY = "1,2"; 496 | }; 497 | name = Release; 498 | }; 499 | 784640FD21A6A558008EDE5D /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 503 | BUNDLE_LOADER = "$(TEST_HOST)"; 504 | CODE_SIGN_STYLE = Automatic; 505 | INFOPLIST_FILE = YDChannelSelectorDemoTests/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = ( 507 | "$(inherited)", 508 | "@executable_path/Frameworks", 509 | "@loader_path/Frameworks", 510 | ); 511 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemoTests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_VERSION = 4.2; 514 | TARGETED_DEVICE_FAMILY = "1,2"; 515 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YDChannelSelectorDemo.app/YDChannelSelectorDemo"; 516 | }; 517 | name = Debug; 518 | }; 519 | 784640FE21A6A558008EDE5D /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 523 | BUNDLE_LOADER = "$(TEST_HOST)"; 524 | CODE_SIGN_STYLE = Automatic; 525 | INFOPLIST_FILE = YDChannelSelectorDemoTests/Info.plist; 526 | LD_RUNPATH_SEARCH_PATHS = ( 527 | "$(inherited)", 528 | "@executable_path/Frameworks", 529 | "@loader_path/Frameworks", 530 | ); 531 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemoTests; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | SWIFT_VERSION = 4.2; 534 | TARGETED_DEVICE_FAMILY = "1,2"; 535 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YDChannelSelectorDemo.app/YDChannelSelectorDemo"; 536 | }; 537 | name = Release; 538 | }; 539 | 7846410021A6A558008EDE5D /* Debug */ = { 540 | isa = XCBuildConfiguration; 541 | buildSettings = { 542 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 543 | CODE_SIGN_STYLE = Automatic; 544 | INFOPLIST_FILE = YDChannelSelectorDemoUITests/Info.plist; 545 | LD_RUNPATH_SEARCH_PATHS = ( 546 | "$(inherited)", 547 | "@executable_path/Frameworks", 548 | "@loader_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemoUITests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_VERSION = 4.2; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | TEST_TARGET_NAME = YDChannelSelectorDemo; 555 | }; 556 | name = Debug; 557 | }; 558 | 7846410121A6A558008EDE5D /* Release */ = { 559 | isa = XCBuildConfiguration; 560 | buildSettings = { 561 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 562 | CODE_SIGN_STYLE = Automatic; 563 | INFOPLIST_FILE = YDChannelSelectorDemoUITests/Info.plist; 564 | LD_RUNPATH_SEARCH_PATHS = ( 565 | "$(inherited)", 566 | "@executable_path/Frameworks", 567 | "@loader_path/Frameworks", 568 | ); 569 | PRODUCT_BUNDLE_IDENTIFIER = com.yd.www.YDChannelSelectorDemoUITests; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | SWIFT_VERSION = 4.2; 572 | TARGETED_DEVICE_FAMILY = "1,2"; 573 | TEST_TARGET_NAME = YDChannelSelectorDemo; 574 | }; 575 | name = Release; 576 | }; 577 | /* End XCBuildConfiguration section */ 578 | 579 | /* Begin XCConfigurationList section */ 580 | 784640CC21A6A556008EDE5D /* Build configuration list for PBXProject "YDChannelSelectorDemo" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 784640F721A6A558008EDE5D /* Debug */, 584 | 784640F821A6A558008EDE5D /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | 784640F921A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemo" */ = { 590 | isa = XCConfigurationList; 591 | buildConfigurations = ( 592 | 784640FA21A6A558008EDE5D /* Debug */, 593 | 784640FB21A6A558008EDE5D /* Release */, 594 | ); 595 | defaultConfigurationIsVisible = 0; 596 | defaultConfigurationName = Release; 597 | }; 598 | 784640FC21A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemoTests" */ = { 599 | isa = XCConfigurationList; 600 | buildConfigurations = ( 601 | 784640FD21A6A558008EDE5D /* Debug */, 602 | 784640FE21A6A558008EDE5D /* Release */, 603 | ); 604 | defaultConfigurationIsVisible = 0; 605 | defaultConfigurationName = Release; 606 | }; 607 | 784640FF21A6A558008EDE5D /* Build configuration list for PBXNativeTarget "YDChannelSelectorDemoUITests" */ = { 608 | isa = XCConfigurationList; 609 | buildConfigurations = ( 610 | 7846410021A6A558008EDE5D /* Debug */, 611 | 7846410121A6A558008EDE5D /* Release */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | /* End XCConfigurationList section */ 617 | }; 618 | rootObject = 784640C921A6A556008EDE5D /* Project object */; 619 | } 620 | --------------------------------------------------------------------------------