├── 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 | [](https://cocoapods.org/pods/YDChannelSelector)
4 | [](https://cocoapods.org/pods/YDChannelSelector)
5 | [](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 |
--------------------------------------------------------------------------------