├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiftTableViewGroup │ ├── DataContentView.swift │ ├── DataNode.swift │ ├── DataNodeBuilder.swift │ ├── Group.swift │ ├── UICollectionView │ ├── CollectionBuilder.swift │ ├── CollectionCell.swift │ ├── CollectionHeaderFooter.swift │ ├── CollectionSection.swift │ ├── CollectionSectionGroup.swift │ ├── CollectionView.swift │ ├── CollectionViewDelegate.swift │ └── CollectionViewRegiterGroup.swift │ ├── UIScrollView+Delegate.swift │ ├── UITableView │ ├── TableBuilder.swift │ ├── TableCell.swift │ ├── TableHeaderFooter.swift │ ├── TableSection.swift │ ├── TableSectionGroup.swift │ ├── TableView.swift │ ├── TableViewDelegate.swift │ └── TableViewRegiterGroup.swift │ └── ViewRegister.swift ├── SwiftTableViewGroup.podspec ├── Tests ├── LinuxMain.swift └── SwiftTableViewGroupTests │ ├── SwiftTableViewGroupTests.swift │ └── XCTestManifests.swift └── images ├── 2019-07-26-063607.png ├── 2019-07-26-063634.png ├── 2019-07-29-022957.png ├── Simulator Screen Shot - iPhone Xʀ - 2019-07-27 at 21.43.43.png ├── Snipaste_2019-07-29_14-11-01-4381583.png ├── Snipaste_2019-07-29_14-11-01.png ├── Snipaste_2019-07-29_14-13-30.png ├── Snipaste_2019-07-29_14-18-03.png ├── Snipaste_2019-07-29_14-19-23.png ├── image-20190726143356551.png ├── image-20190726143714253.png ├── image-20190726145544399.png ├── image-20190726145605726.png ├── image-20190729103132510.png ├── image-20190729103652825.png └── image-20190729105419637.png /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftTableViewGroup", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftTableViewGroup", 12 | targets: ["SwiftTableViewGroup"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "SwiftTableViewGroup", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SwiftTableViewGroupTests", 26 | dependencies: ["SwiftTableViewGroup"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | typora-copy-images-to: ../SwiftTableViewGroup/images/ 3 | typora-root-url: ../SwiftTableViewGroup 4 | --- 5 | 6 | # SwiftTableViewGroup 7 | 8 | > ❇️`SwiftTableViewGroup`is developed using the syntax of the latest Swift5.1``@_functionBuilder` combined with the latest `SwiftUI` design pattern. 9 | > 10 | > ❇️`SwiftTableViewGroup`是利用最新的`Swift.1`语法`@_functionBuilder`符合`SwiftUI`设计的数据驱动。 11 | 12 | [TOC] 13 | 14 | ## 代码例子 15 | 16 | ![Snipaste_2019-07-29_14-11-01](/images/Snipaste_2019-07-29_14-11-01-4381583.png) 17 | 18 | ![Snipaste_2019-07-29_14-13-30](/images/Snipaste_2019-07-29_14-13-30.png) 19 | 20 | ![Snipaste_2019-07-29_14-18-03](/images/Snipaste_2019-07-29_14-18-03.png) 21 | 22 | 23 | 24 | ![Snipaste_2019-07-29_14-19-23](/images/Snipaste_2019-07-29_14-19-23.png) 25 | 26 | ## ChangeLog(更新记录) 27 | 28 | ### v2.0.0 29 | 30 | - Support `UICollectionView` data driver(支持`UICollectionView`数据驱动) 31 | - Changing `Api` is easier to use(更改`Api`使用更简单) 32 | - Support for custom height or size(支持自定义高度或者大小) 33 | - Safer to use(使用更加安全) 34 | 35 | ## 安装 36 | 37 | ### Swift Package Manager(Xcode 11) 38 | 39 | ```ruby 40 | https://github.com/josercc/SwiftTableViewGroup 41 | ``` 42 | 43 | ### CocoaPods 44 | 45 | ```swift 46 | pod 'SwiftTableViewGroup' 47 | ``` 48 | 49 | ### Carthage 50 | 51 | ```ruby 52 | github "josercc/SwiftTableViewGroup" 53 | ``` 54 | 55 | ## Claim(要求) 56 | 57 | - `Xcode11` 58 | - `Swift5.1` 59 | 60 | ## How to use(怎么使用) 61 | 62 | ### UITableView 63 | 64 | #### Fake code(伪代码) 65 | 66 | ```swift 67 | let tableView = UITableView() 68 | var dataSource = TableView(tableView:tableView) 69 | /// setup configuration(初始化配置) 70 | dataSource.setup { 71 | /// Add Header 72 | TableViewHeaderFooterView 73 | /// Add Cell 74 | TableViewCell 75 | /// Add More Cell 76 | ... 77 | /// Add Footer 78 | TableViewHeaderFooterView 79 | } 80 | /// Perform registration and refresh(执行注册和刷新) 81 | dataSource.reloadData 82 | ``` 83 | 84 | #### Create a static text list(创建一个简单的列表) 85 | 86 | ```swift 87 | TableCell { content, contentCell in 88 | /// Create a configured block(创建配置的 Block) 89 | content.configuration(UITableViewCell.self) { (cell, index) in 90 | cell.textLabel?.text = self.source[index] 91 | cell.accessoryType = .disclosureIndicator 92 | } 93 | /// Create a clickback call block(创建点击回调 Block) 94 | content.didSelectRow(UITableViewCell.self) { (cell, index) in 95 | } 96 | } 97 | .number(self.source.count) 98 | .height(45) 99 | } 100 | self.dataSource.reloadData() 101 | ``` 102 | 103 | ![image-20190726143607274](/images/2019-07-26-063607.png) 104 | 105 | #### Create complex TableView(创建复杂的表格) 106 | 107 | ```swift 108 | let settingDataSource = TableView(tableView: tableView) 109 | settingDataSource.setup { 110 | /// Create `SettingHeaderView` Header(创建自定义`SettingHeaderView`Header) 111 | TableHeaderView(SettingHeaderView.self, { content,contentHeader in 112 | content.configuration(SettingHeaderView.self) { (view, section) in 113 | view.textLabel?.text = "Header" 114 | } 115 | }) 116 | .height(49) 117 | /// Create `IntrinsicContentTextLabelCell` Cell(创建`IntrinsicContentTextLabelCell`Cell) 118 | TableCell(IntrinsicContentTextLabelCell.self) 119 | /// Create Dynamic change number Cell(创建动态更改数量的 Cell) 120 | TableCell { content,contentCell in 121 | content.configuration(UITableViewCell.self) { (cell, index) in 122 | cell.textLabel?.text = "\(index) 点击我会增加哦" 123 | } 124 | content.didSelectRow(UITableViewCell.self) { (cell, index) in 125 | let number = contentCell.number + 1; 126 | contentCell.number(number) 127 | settingDataSource.reloadData() 128 | } 129 | } 130 | .height(44) 131 | /// Create Dynamic change height Cell)(创建动态更改高度的 Cell) 132 | TableCell { content,contentCell in 133 | content.configuration(UITableViewCell.self) { (cell, index) in 134 | cell.textLabel?.text = "点击我改变高度" 135 | } 136 | content.didSelectRow(UITableViewCell.self) { (cell, index) in 137 | let height = contentCell.height == 44 ? 100 : 44; 138 | contentCell.height(CGFloat(height)) 139 | settingDataSource.reloadData() 140 | } 141 | } 142 | .height(44) 143 | } 144 | ``` 145 | 146 | ![image-20190726143714253](/images/image-20190726143714253.png) 147 | 148 | ##### Dynamic change quantity(动态更改数量) 149 | 150 | ![image-20190726145544399](/images/image-20190726145544399.png) 151 | 152 | ##### Dynamic height(动态修改高度) 153 | 154 | ![image-20190726145605726](/images/image-20190726145605726.png) 155 | 156 | ### UICollectionView 157 | 158 | ```swift 159 | self.dataSource.setup { 160 | /// Create normal class `UICollectionViewCell` cell(创建默认为`UITableViewCell`类的 Cell) 161 | CollectionCell { content, cellContent in 162 | content.configuration(UICollectionViewCell.self) { (cell, index) in 163 | cell.backgroundColor = index % 2 == 0 ? UIColor.red : UIColor.blue 164 | } 165 | content.didSelectRow(UICollectionViewCell.self) { (cell, index) in 166 | cell.backgroundColor = cell.backgroundColor == UIColor.red ? UIColor.blue : UIColor.red 167 | } 168 | } 169 | .number(20) 170 | .size(CGSize(width: 100, height: 200)) 171 | 172 | } 173 | .inset(UIEdgeInsets(top: 20, left: 10, bottom: 20, right: 10)) 174 | self.dataSource.reloadData() 175 | ``` 176 | 177 | ![image-20190729105419637](/images//image-20190729105419637.png) 178 | 179 | ## Problem 180 | 181 | ### ❓How to dynamically insert or delete some elements(怎么动态的插入或者删除一组元素) 182 | 183 | > You can change the element properties of the opposite side of the TableView's Sections array and then call `reloadData`.(您可以更改TableView的Sections数组的另一侧的元素属性,然后调用`reloadData`。) 184 | 185 | ### ❓How to listen to other agents of `UIScrollView`(怎么监听`UIScrollView`其他代理方法) 186 | 187 | ```swift 188 | public struct ScrollViewDelegate { 189 | public var scrollViewDidScroll:((_ scrollView: UIScrollView) -> Void)? 190 | public var scrollViewWillBeginDragging:((_ scrollView: UIScrollView) -> Void)? 191 | public var scrollViewWillEndDragging:((_ scrollView: UIScrollView, _ velocity: CGPoint, _ targetContentOffset: UnsafeMutablePointer) -> Void)? 192 | public var scrollViewDidEndDragging:((_ scrollView: UIScrollView, _ decelerate: Bool) -> Void)? 193 | public var scrollViewWillBeginDecelerating:((_ scrollView: UIScrollView) -> Void)? 194 | public var scrollViewDidEndDecelerating:((_ scrollView: UIScrollView) -> Void)? 195 | } 196 | 197 | ``` 198 | 199 | > Can implement the above proxy method of `UITableView`(可以实现`UITableView的上述代理方法) 200 | 201 | Example 202 | 203 | ```swift 204 | tableView.scrollDelegate?.scrollViewDidScroll = { scrollView in 205 | } 206 | ``` 207 | 208 | ### I feel that there are too few supported features.(我觉得支持的功能太少了。) 209 | 210 | > Can submit PR or commit ISSUSE(可以提交PR或提ISSUSE) 211 | 212 | ## Api Document(Api 文档) 213 | 214 | - Height or size setting priority (高度或者大小的设置优先级) 215 | - height( UITableViewCell/UITableHeaderFooterView)(高度( UITableViewCell/UITableHeaderFooterView)) 216 | - custom > setting > auto(sizeToFit)(自定义 > 设置 > 自动获取(sizeToFit)) 217 | - size(UICollectionViewCell/UICollectionReusableView)(大小(UICollectionViewCell/UICollectionReusableView)) 218 | - custom > setting > FlowLayout(自定义 > 设置 > FlowLayout) 219 | 220 | ## contact me 221 | 222 | - Email: josercc@163.com 223 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/DataContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableContentView.swift 3 | // 4 | // 5 | // Created by zhang hang on 2019/7/26. 6 | // 7 | 8 | import UIKit.UIView 9 | 10 | public class BlockContent { 11 | public enum BlockType { 12 | case configuration 13 | case didSelectRow 14 | case customHeight 15 | case customSize 16 | } 17 | public var view:UIView? 18 | public var index:Int 19 | public var blockType:BlockType 20 | public var customHeight:CGFloat = 0 21 | public var customSize:CGSize = .zero 22 | init(view:UIView? = nil, index:Int, blockType:BlockType) { 23 | self.view = view 24 | self.index = index 25 | self.blockType = blockType 26 | } 27 | public func configuration(_ type:V.Type = V.self, _ block:(V,Int) -> Void) { 28 | guard let contentView = self.view as? V, self.blockType == .configuration else { 29 | return 30 | } 31 | block(contentView,self.index) 32 | } 33 | public func didSelectRow(_ type:V.Type = V.self, _ block:(V,Int) -> Void) { 34 | guard let contentView = self.view as? V, self.blockType == .didSelectRow else { 35 | return 36 | } 37 | block(contentView,self.index) 38 | } 39 | 40 | public func customHeight(_ type:V.Type = V.self, _ block:(V,Int) -> CGFloat) { 41 | guard let contentView = self.view as? V, self.blockType == .customHeight else { 42 | return 43 | } 44 | self.customHeight = block(contentView,self.index) 45 | } 46 | 47 | public func customSize(_ block:(Int) -> CGSize) { 48 | guard self.blockType == .customSize else { 49 | return 50 | } 51 | self.customSize = block(self.index) 52 | } 53 | } 54 | 55 | 56 | public protocol ContentView { 57 | associatedtype View:UIView 58 | typealias MakeTypeBlock = (BlockContent,Self) -> Void 59 | var makeTypeBlock:MakeTypeBlock? { get set } 60 | func makeConfig(view:UIView, index:Int) 61 | func makeDidSelectRow(view:UIView, index:Int) 62 | func makeCustomHeight(view:UIView, index:Int) -> CGFloat 63 | func makeCustomSize(index:Int) -> CGSize 64 | } 65 | 66 | extension ContentView { 67 | public func makeConfig(view:UIView, index:Int) { 68 | self.makeTypeBlock?(BlockContent(view: view, index: index, blockType: .configuration),self) 69 | } 70 | 71 | public func makeDidSelectRow(view:UIView, index:Int) { 72 | self.makeTypeBlock?(BlockContent(view: view, index: index, blockType: .didSelectRow),self) 73 | } 74 | 75 | public func makeCustomHeight(view:UIView, index:Int) -> CGFloat { 76 | let content = BlockContent(view: view, index: index, blockType: .customHeight) 77 | self.makeTypeBlock?(content,self) 78 | return content.customHeight 79 | } 80 | public func makeCustomSize(index:Int) -> CGSize { 81 | let content = BlockContent(index: index, blockType: .customSize) 82 | self.makeTypeBlock?(content,self) 83 | return content.customSize 84 | } 85 | } 86 | 87 | public class DataContentView : ViewRegister { 88 | public var anyClass: AnyClass 89 | public var identifier: String 90 | public init(anyClass:AnyClass) { 91 | self.anyClass = anyClass 92 | self.identifier = "\(anyClass)" 93 | } 94 | } 95 | 96 | public func realValue(zero:V, custom:() -> V, setting:() -> V, layout:() -> V, normal:() -> V) -> V { 97 | let customValue = custom() 98 | let settingValue = setting() 99 | let layoutSize = layout() 100 | let normalSize = normal() 101 | if customValue != zero { 102 | return customValue 103 | } else if settingValue != zero { 104 | return settingValue 105 | } else if layoutSize != zero { 106 | return layoutSize 107 | } else { 108 | return normalSize 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/DataNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataNode.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | /// 数据源协议 9 | public protocol DataNode {} 10 | 11 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/DataNodeBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataNodeBuilder.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/16. 6 | // 7 | 8 | /// 数据源组装对象 9 | @_functionBuilder 10 | public struct DataNodeBuilder { 11 | public static func buildBlock(_ groups:DataNode...) -> DataNode { 12 | return Group(nodes: groups) 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/Group.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Group.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /// 数据源组 11 | public struct Group : DataNode { 12 | public let nodes:[DataNode] 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionBuilder.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UICollectionView 9 | 10 | @_functionBuilder 11 | public struct CollectionBuilder { 12 | public static func buildBlock(_ nodes:DataNode...) -> DataNode { 13 | var group = CollectionSectionGroup() 14 | if let _ = nodes.first as? CollectionSection { 15 | for node in nodes { 16 | if let section = node as? CollectionSection { 17 | group.sections.append(section) 18 | } 19 | } 20 | } else { 21 | let section = CollectionSection { 22 | CollectionSectionBuilder.collectionSection(nodes: nodes) 23 | } 24 | group.sections.append(section) 25 | } 26 | return group 27 | } 28 | 29 | 30 | } 31 | 32 | 33 | @_functionBuilder 34 | public struct CollectionSectionBuilder { 35 | public static func buildBlock(_ contents:Content...) -> DataNode { 36 | return self.collectionSection(nodes: contents) 37 | } 38 | 39 | public static func collectionSection(nodes:[DataNode]) -> CollectionViewRegiterGroup { 40 | var header:CollectionHeaderView? 41 | var footer:CollectionFooterView? 42 | var cells:[CollectionCell] = [CollectionCell]() 43 | for node in nodes { 44 | if let _header = node as? CollectionHeaderView { 45 | header = _header 46 | } else if let _footer = node as? CollectionFooterView { 47 | footer = _footer 48 | } else if let cell = node as? CollectionCell { 49 | cells.append(cell) 50 | } 51 | } 52 | 53 | return CollectionViewRegiterGroup(header: header, footer: footer, cells: cells) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionCell.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UICollectionViewCell 9 | 10 | 11 | public final class CollectionCell: DataContentView, ContentView, CollectionContentView { 12 | public var makeTypeBlock: ((BlockContent, CollectionCell) -> Void)? 13 | public typealias View = UICollectionViewCell 14 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 15 | super.init(anyClass: V.self) 16 | self.makeTypeBlock = block 17 | } 18 | } 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionHeaderFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionHeaderFooter.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UICollectionView 9 | 10 | public final class CollectionHeaderView : DataContentView, ContentView, CollectionContentView { 11 | public var makeTypeBlock: ((BlockContent, CollectionHeaderView) -> Void)? 12 | public typealias View = UICollectionReusableView 13 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 14 | super.init(anyClass: V.self) 15 | self.makeTypeBlock = block 16 | } 17 | } 18 | 19 | public final class CollectionFooterView : DataContentView, ContentView, CollectionContentView { 20 | public var makeTypeBlock: ((BlockContent, CollectionFooterView) -> Void)? 21 | public typealias View = UICollectionReusableView 22 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 23 | super.init(anyClass: V.self) 24 | self.makeTypeBlock = block 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionSection.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UICollectionViewCell 9 | 10 | public struct CollectionSection : DataNode { 11 | public var header:CollectionHeaderView? 12 | public var footer:CollectionFooterView? 13 | public var cells:[CollectionCell] = [CollectionCell]() 14 | public init(@CollectionSectionBuilder _ block:() -> DataNode) { 15 | if let group = block() as? CollectionViewRegiterGroup { 16 | self.header = group.header 17 | self.footer = group.footer 18 | self.cells = group.cells 19 | } 20 | } 21 | public func number() -> Int { 22 | var count = 0 23 | for cell in cells { 24 | count += cell.number 25 | } 26 | return count 27 | } 28 | 29 | /// 1 2 3 30 | public func cell(index:Int) -> (cell:CollectionCell, index:Int) { 31 | var count = 0 32 | for cell in cells { 33 | let countIndex = count + cell.number 34 | if index < countIndex { 35 | return (cell, index - count) 36 | } 37 | count = countIndex 38 | } 39 | return (cells[0],0) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionSectionGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionSectionGroup.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | public struct CollectionSectionGroup : DataNode { 9 | public var sections:[CollectionSection] = [] 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionView.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import Foundation 9 | import UIKit.UICollectionView 10 | 11 | public class CollectionView : DataNode { 12 | public var sections:[CollectionSection] = [] 13 | public let collectionView:UICollectionView 14 | public lazy var delegate:CollectionViewDelegate = { 15 | CollectionViewDelegate(dataSource: self) 16 | }() 17 | public var isAutoDelegate = true 18 | public var inset:UIEdgeInsets = .zero 19 | public var insetBlock:((Int) -> UIEdgeInsets)? 20 | public var minimumLineSpacingBlock:((Int) -> CGFloat)? 21 | public var minimumLineSpacing:CGFloat = 0 22 | public var minimumInteritemSpacingBlock:((Int) -> CGFloat)? 23 | public var minimumInteritemSpacing:CGFloat = 0 24 | public init(collectionView:UICollectionView) { 25 | self.collectionView = collectionView 26 | } 27 | @discardableResult 28 | public func setup(@CollectionBuilder _ block:() -> DataNode) -> CollectionView { 29 | let node = block() 30 | if let group = node as? CollectionSectionGroup { 31 | sections = group.sections 32 | } else { 33 | let section = CollectionSection { 34 | CollectionSectionBuilder.collectionSection(nodes: [node]) 35 | } 36 | sections = [section] 37 | } 38 | return self 39 | } 40 | @discardableResult 41 | public func customInset(_ block:@escaping ((Int) -> UIEdgeInsets)) -> CollectionView { 42 | self.insetBlock = block 43 | return self 44 | } 45 | @discardableResult 46 | public func inset(_ inset:UIEdgeInsets) -> CollectionView { 47 | self.inset = inset 48 | return self 49 | } 50 | @discardableResult 51 | public func customMinimumLineSpacing(_ block:@escaping ((Int) -> CGFloat)) -> CollectionView { 52 | self.minimumLineSpacingBlock = block 53 | return self 54 | } 55 | @discardableResult 56 | public func minimumLineSpacing(_ minimumLineSpacing:CGFloat) -> CollectionView { 57 | self.minimumLineSpacing = minimumLineSpacing 58 | return self 59 | } 60 | @discardableResult 61 | public func customMinimumInteritemSpacing(_ block:@escaping ((Int) -> CGFloat)) -> CollectionView { 62 | self.minimumInteritemSpacingBlock = block 63 | return self 64 | } 65 | @discardableResult 66 | public func minimumInteritemSpacing(_ minimumInteritemSpacing:CGFloat) -> CollectionView { 67 | self.minimumInteritemSpacing = minimumInteritemSpacing 68 | return self 69 | } 70 | 71 | public func reloadData() { 72 | self.registerClass() 73 | /// 检测到表格的代理还没有设置 74 | if self.collectionView.dataSource == nil { 75 | /// 如果用户关闭了自动代理 则抱错提示用户 76 | if !self.isAutoDelegate { 77 | assertionFailure("如果自己设置 UICollectionView 代理则必须设置才能执行reloadData") 78 | } else { 79 | self.collectionView.dataSource = self.delegate 80 | } 81 | } 82 | /// 如果检测到表格没有设置方法代理 则不必要强制用户去设置 83 | if self.collectionView.delegate == nil && self.isAutoDelegate { 84 | self.collectionView.delegate = self.delegate 85 | } 86 | self.collectionView.reloadData() 87 | } 88 | 89 | public func registerClass() { 90 | for section in sections { 91 | if let header = section.header, header.identifier.count > 0 { 92 | self.collectionView.register(header.anyClass, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: header.identifier) 93 | } 94 | if let footer = section.footer, footer.identifier.count > 0 { 95 | self.collectionView.register(footer.anyClass, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footer.identifier) 96 | } 97 | for cell in section.cells { 98 | self.collectionView.register(cell.anyClass, forCellWithReuseIdentifier: cell.identifier) 99 | } 100 | } 101 | } 102 | 103 | 104 | } 105 | 106 | public protocol CollectionContentView { 107 | var size:CGSize { get set } 108 | @discardableResult 109 | func size(_ size:CGSize) -> Self 110 | } 111 | 112 | struct CollectionContentViewKey { 113 | static var size = "size" 114 | } 115 | 116 | extension CollectionContentView { 117 | public var size:CGSize { 118 | get { 119 | objc_getAssociatedObject(self, &CollectionContentViewKey.size) as? CGSize ?? CGSize(width: -1, height: -1) 120 | } 121 | set { 122 | objc_setAssociatedObject(self, &CollectionContentViewKey.size, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 123 | } 124 | } 125 | @discardableResult 126 | public func size(_ size:CGSize) -> Self { 127 | var view = self 128 | view.size = size 129 | return view 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewDelegate.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/16. 6 | // 7 | 8 | import UIKit 9 | 10 | public class CollectionViewDelegate : NSObject { 11 | public var dataSource:CollectionView 12 | init(dataSource:CollectionView) { 13 | self.dataSource = dataSource 14 | } 15 | } 16 | 17 | extension CollectionViewDelegate : UICollectionViewDataSource { 18 | 19 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 20 | let section = self.dataSource.sections[section] 21 | return section.number(); 22 | } 23 | 24 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 25 | let section = self.dataSource.sections[indexPath.section] 26 | let cellTuple = section.cell(index: indexPath.row) 27 | let collectionCell = cellTuple.cell 28 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionCell.identifier, for: indexPath) 29 | collectionCell.makeConfig(view: cell, index: cellTuple.index) 30 | return cell 31 | } 32 | 33 | 34 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 35 | return self.dataSource.sections.count 36 | } 37 | 38 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 39 | let collectionSection = self.dataSource.sections[indexPath.section] 40 | if kind == UICollectionView.elementKindSectionHeader, let header = collectionSection.header { 41 | return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: header.identifier, for: indexPath) 42 | } else if kind == UICollectionView.elementKindSectionFooter, let footer = collectionSection.footer { 43 | return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footer.identifier, for: indexPath) 44 | } 45 | return UICollectionReusableView() 46 | } 47 | } 48 | 49 | extension CollectionViewDelegate : UICollectionViewDelegate { 50 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 51 | let collectionSection = self.dataSource.sections[indexPath.section] 52 | let cellTuple = collectionSection.cell(index: indexPath.row) 53 | let collectionCell = cellTuple.cell 54 | guard let cell = collectionView.cellForItem(at: indexPath) else { 55 | return 56 | } 57 | collectionCell.makeDidSelectRow(view: cell, index: cellTuple.index) 58 | collectionView.deselectItem(at: indexPath, animated: true) 59 | } 60 | 61 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 62 | scrollView.scrollDelegate?.scrollViewDidScroll?(scrollView) 63 | } 64 | 65 | public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 66 | scrollView.scrollDelegate?.scrollViewWillBeginDragging?(scrollView) 67 | } 68 | 69 | public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 70 | scrollView.scrollDelegate?.scrollViewWillEndDragging?(scrollView,velocity, targetContentOffset) 71 | } 72 | 73 | public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 74 | scrollView.scrollDelegate?.scrollViewDidEndDragging?(scrollView, decelerate) 75 | } 76 | 77 | public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { 78 | scrollView.scrollDelegate?.scrollViewWillBeginDecelerating?(scrollView) 79 | } 80 | 81 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 82 | scrollView.scrollDelegate?.scrollViewDidEndDecelerating?(scrollView) 83 | } 84 | } 85 | 86 | extension CollectionViewDelegate : UICollectionViewDelegateFlowLayout { 87 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 88 | let collectionSection = self.dataSource.sections[indexPath.section] 89 | let cellTuple = collectionSection.cell(index: indexPath.row) 90 | let collectionCell = cellTuple.cell 91 | return realValue(zero: CGSize.zero, 92 | custom: { () -> CGSize in 93 | return collectionCell.makeCustomSize(index: cellTuple.index) 94 | }, setting: { () -> CGSize in 95 | return collectionCell.size 96 | }, layout: { () -> CGSize in 97 | let size = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.itemSize 98 | return size ?? CGSize.zero 99 | }) { () -> CGSize in 100 | return CGSize.zero 101 | } 102 | } 103 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 104 | return realValue(zero: UIEdgeInsets.zero, 105 | custom: { () -> UIEdgeInsets in 106 | return self.dataSource.insetBlock?(section) ?? UIEdgeInsets.zero 107 | }, setting: { () -> UIEdgeInsets in 108 | return self.dataSource.inset 109 | }, layout: { () -> UIEdgeInsets in 110 | return (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? UIEdgeInsets.zero 111 | }) { () -> UIEdgeInsets in 112 | return UIEdgeInsets.zero 113 | } 114 | } 115 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 116 | return realValue(zero: CGFloat(0), 117 | custom: { () -> CGFloat in 118 | return self.dataSource.minimumLineSpacingBlock?(section) ?? CGFloat(0) 119 | }, setting: { () -> CGFloat in 120 | return self.dataSource.minimumLineSpacing 121 | }, layout: { () -> CGFloat in 122 | return (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing ?? CGFloat(0) 123 | }) { () -> CGFloat in 124 | return CGFloat(0) 125 | } 126 | } 127 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 128 | return realValue(zero: CGFloat(0), 129 | custom: { () -> CGFloat in 130 | return self.dataSource.minimumInteritemSpacingBlock?(section) ?? CGFloat(0) 131 | }, setting: { () -> CGFloat in 132 | return self.dataSource.minimumInteritemSpacing 133 | }, layout: { () -> CGFloat in 134 | return (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing ?? CGFloat(0) 135 | }) { () -> CGFloat in 136 | return CGFloat(0) 137 | } 138 | } 139 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 140 | let collectionSection = self.dataSource.sections[section] 141 | guard let header = collectionSection.header else { 142 | return CGSize.zero 143 | } 144 | return realValue(zero: CGSize.zero, 145 | custom: { () -> CGSize in 146 | return header.makeCustomSize(index: section) 147 | }, setting: { () -> CGSize in 148 | return header.size 149 | }, layout: { () -> CGSize in 150 | return (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.headerReferenceSize ?? CGSize.zero 151 | }) { () -> CGSize in 152 | return CGSize.zero 153 | } 154 | } 155 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { 156 | let collectionSection = self.dataSource.sections[section] 157 | guard let footer = collectionSection.footer else { 158 | return CGSize.zero 159 | } 160 | return realValue(zero: CGSize.zero, 161 | custom: { () -> CGSize in 162 | return footer.makeCustomSize(index: section) 163 | }, setting: { () -> CGSize in 164 | return footer.size 165 | }, layout: { () -> CGSize in 166 | return (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.headerReferenceSize ?? CGSize.zero 167 | }) { () -> CGSize in 168 | return CGSize.zero 169 | } 170 | } 171 | 172 | 173 | } 174 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UICollectionView/CollectionViewRegiterGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewRegiterGroup.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UICollectionView 9 | 10 | public struct CollectionViewRegiterGroup : DataNode { 11 | public var header:CollectionHeaderView? 12 | public var footer:CollectionFooterView? 13 | public var cells:[CollectionCell] 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UIScrollView+Delegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+Delegate.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UIScrollView 9 | 10 | extension UIScrollView { 11 | public var scrollDelegate:ScrollViewDelegate? { 12 | get { 13 | return objc_getAssociatedObject(self, "scrollDelegate") as? ScrollViewDelegate 14 | } 15 | set { 16 | objc_setAssociatedObject(self, "scrollDelegate", newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 17 | } 18 | } 19 | } 20 | 21 | public struct ScrollViewDelegate { 22 | public var scrollViewDidScroll:((_ scrollView: UIScrollView) -> Void)? 23 | public var scrollViewWillBeginDragging:((_ scrollView: UIScrollView) -> Void)? 24 | public var scrollViewWillEndDragging:((_ scrollView: UIScrollView, _ velocity: CGPoint, _ targetContentOffset: UnsafeMutablePointer) -> Void)? 25 | public var scrollViewDidEndDragging:((_ scrollView: UIScrollView, _ decelerate: Bool) -> Void)? 26 | public var scrollViewWillBeginDecelerating:((_ scrollView: UIScrollView) -> Void)? 27 | public var scrollViewDidEndDecelerating:((_ scrollView: UIScrollView) -> Void)? 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableBuilder.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UITableViewHeaderFooterView 9 | 10 | @_functionBuilder 11 | public struct TableBuilder { 12 | public static func buildBlock(_ nodes:DataNode...) -> DataNode { 13 | var group = TableSectionGroup() 14 | if let _ = nodes.first as? TableSection { 15 | for node in nodes { 16 | if let section = node as? TableSection { 17 | group.sections.append(section) 18 | } 19 | } 20 | } else { 21 | let section = TableSection { 22 | TableSectionBuilder.tableSection(nodes: nodes) 23 | } 24 | group.sections.append(section) 25 | } 26 | return group 27 | } 28 | 29 | 30 | } 31 | 32 | 33 | @_functionBuilder 34 | public struct TableSectionBuilder { 35 | public static func buildBlock(_ contents:Content...) -> DataNode { 36 | return self.tableSection(nodes: contents) 37 | } 38 | 39 | public static func tableSection(nodes:[DataNode]) -> TableViewRegiterGroup { 40 | var header:TableHeaderView? 41 | var footer:TableFooterView? 42 | var cells:[TableCell] = [TableCell]() 43 | for node in nodes { 44 | if let _header = node as? TableHeaderView { 45 | header = _header 46 | } else if let _footer = node as? TableFooterView { 47 | footer = _footer 48 | } else if let cell = node as? TableCell { 49 | cells.append(cell) 50 | } 51 | } 52 | 53 | return TableViewRegiterGroup(header: header, footer: footer, cells: cells) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableCell.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UITableViewCell 9 | 10 | 11 | public final class TableCell: DataContentView, ContentView, TableContentView { 12 | public var makeTypeBlock: ((BlockContent, TableCell) -> Void)? 13 | public typealias View = UITableViewCell 14 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 15 | super.init(anyClass: V.self) 16 | self.makeTypeBlock = block 17 | } 18 | } 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableHeaderFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableHeaderFooter.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UITableViewHeaderFooterView 9 | 10 | public final class TableHeaderView : DataContentView, ContentView, TableContentView { 11 | public var makeTypeBlock: ((BlockContent, TableHeaderView) -> Void)? 12 | public typealias View = UITableViewHeaderFooterView 13 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 14 | super.init(anyClass: V.self) 15 | self.makeTypeBlock = block 16 | } 17 | } 18 | 19 | public final class TableFooterView : DataContentView, ContentView, TableContentView { 20 | public var makeTypeBlock: ((BlockContent, TableFooterView) -> Void)? 21 | public typealias View = UITableViewHeaderFooterView 22 | public init(_ type: V.Type = V.self, _ block: MakeTypeBlock? = nil) { 23 | super.init(anyClass: V.self) 24 | self.makeTypeBlock = block 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableSection.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UITableViewCell 9 | 10 | public struct TableSection : DataNode { 11 | public var header:TableHeaderView? 12 | public var footer:TableFooterView? 13 | public var cells:[TableCell] = [TableCell]() 14 | public init(@TableSectionBuilder _ block:() -> DataNode) { 15 | if let group = block() as? TableViewRegiterGroup { 16 | self.header = group.header 17 | self.footer = group.footer 18 | self.cells = group.cells 19 | } 20 | } 21 | public func number() -> Int { 22 | var count = 0 23 | for cell in cells { 24 | count += cell.number 25 | } 26 | return count 27 | } 28 | 29 | /// 1 2 3 30 | public func cell(index:Int) -> (cell:TableCell, index:Int) { 31 | var count = 0 32 | for cell in cells { 33 | let countIndex = count + cell.number 34 | if index < countIndex { 35 | return (cell, index - count) 36 | } 37 | count = countIndex 38 | } 39 | return (cells[0],0) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableSectionGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableSectionGroup.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | public struct TableSectionGroup : DataNode { 9 | public var sections:[TableSection] = [] 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableView.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import Foundation 9 | import UIKit.UITableView 10 | 11 | public class TableView : DataNode { 12 | public var sections:[TableSection] = [] 13 | public let tableView:UITableView 14 | public lazy var delegate:TableViewDelegate = { 15 | TableViewDelegate(dataSource: self) 16 | }() 17 | public var isAutoDelegate = true 18 | public init(tableView:UITableView) { 19 | self.tableView = tableView 20 | } 21 | 22 | public func setup(@TableBuilder _ block:() -> DataNode) { 23 | let node = block() 24 | if let group = node as? TableSectionGroup { 25 | sections = group.sections 26 | } else { 27 | let section = TableSection { 28 | TableSectionBuilder.tableSection(nodes: [node]) 29 | } 30 | sections = [section] 31 | } 32 | } 33 | 34 | public func reloadData() { 35 | self.registerClass() 36 | /// 检测到表格的代理还没有设置 37 | if self.tableView.dataSource == nil { 38 | /// 如果用户关闭了自动代理 则抱错提示用户 39 | if !self.isAutoDelegate { 40 | assertionFailure("如果自己设置 UITableView 代理则必须设置才能执行reloadData") 41 | } else { 42 | self.tableView.dataSource = self.delegate 43 | } 44 | } 45 | /// 如果检测到表格没有设置方法代理 则不必要强制用户去设置 46 | if self.tableView.delegate == nil && self.isAutoDelegate { 47 | self.tableView.delegate = self.delegate 48 | } 49 | self.tableView.reloadData() 50 | } 51 | 52 | public func registerClass() { 53 | for section in sections { 54 | if let header = section.header, header.identifier.count > 0 { 55 | self.tableView.register(header.anyClass, forHeaderFooterViewReuseIdentifier: header.identifier) 56 | } 57 | if let footer = section.footer, footer.identifier.count > 0 { 58 | self.tableView.register(footer.anyClass, forHeaderFooterViewReuseIdentifier: footer.identifier) 59 | } 60 | for cell in section.cells { 61 | self.tableView.register(cell.anyClass, forCellReuseIdentifier: cell.identifier) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public protocol TableContentView { 68 | var height:CGFloat { get set} 69 | @discardableResult 70 | func height(_ height:CGFloat) -> Self 71 | } 72 | 73 | public struct TableContentViewKey { 74 | static var height = "height" 75 | } 76 | 77 | extension TableContentView { 78 | public var height:CGFloat { 79 | get { 80 | objc_getAssociatedObject(self, &TableContentViewKey.height) as? CGFloat ?? -1 81 | } 82 | set { 83 | objc_setAssociatedObject(self, &TableContentViewKey.height, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 84 | } 85 | } 86 | @discardableResult 87 | public func height(_ height:CGFloat) -> Self { 88 | var view = self 89 | view.height = height 90 | return view 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewDelegate.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/16. 6 | // 7 | 8 | import UIKit 9 | 10 | public class TableViewDelegate : NSObject { 11 | public let dataSource:TableView 12 | init(dataSource:TableView) { 13 | self.dataSource = dataSource 14 | } 15 | } 16 | 17 | extension TableViewDelegate : UITableViewDataSource { 18 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 19 | let section = self.dataSource.sections[section] 20 | return section.number(); 21 | } 22 | 23 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 24 | let section = self.dataSource.sections[indexPath.section] 25 | let cellTuple = section.cell(index: indexPath.row) 26 | let tableCell = cellTuple.cell 27 | let cell = tableView.dequeueReusableCell(withIdentifier: tableCell.identifier, for: indexPath) 28 | tableCell.makeConfig(view: cell, index: cellTuple.index) 29 | return cell 30 | } 31 | 32 | 33 | public func numberOfSections(in tableView: UITableView) -> Int { 34 | return self.dataSource.sections.count 35 | } 36 | } 37 | 38 | extension TableViewDelegate : UITableViewDelegate { 39 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 40 | let section = self.dataSource.sections[indexPath.section] 41 | let cellTuple = section.cell(index: indexPath.row) 42 | let tableCell = cellTuple.cell 43 | guard let cell = tableView.dequeueReusableCell(withIdentifier: tableCell.identifier) else { 44 | return 0 45 | } 46 | return realValue(zero: CGFloat(0), custom: { () -> CGFloat in 47 | return tableCell.makeCustomHeight(view: cell, index: cellTuple.index) 48 | }, setting: { () -> CGFloat in 49 | return tableCell.height 50 | }, layout: { () -> CGFloat in 51 | tableCell.makeConfig(view: cell, index: cellTuple.index) 52 | return cell.sizeThatFits(CGSize(width: tableView.frame.width, height: 0)).height 53 | }) { () -> CGFloat in 54 | return 0 55 | } 56 | } 57 | 58 | public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 59 | let tableSection = self.dataSource.sections[section] 60 | guard let header = tableSection.header, let tableHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: header.identifier) else { 61 | return 0 62 | } 63 | return realValue(zero: CGFloat(0), custom: { () -> CGFloat in 64 | header.makeCustomHeight(view: tableHeader, index: section) 65 | }, setting: { () -> CGFloat in 66 | header.height 67 | }, layout: { () -> CGFloat in 68 | header.makeConfig(view: tableHeader, index: section) 69 | return tableHeader.sizeThatFits(CGSize(width: tableView.frame.width, height: 0)).height 70 | }) { () -> CGFloat in 71 | CGFloat(0) 72 | } 73 | } 74 | 75 | public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 76 | let tableSection = self.dataSource.sections[section] 77 | guard let footer = tableSection.footer, let tableFooter = tableView.dequeueReusableHeaderFooterView(withIdentifier: footer.identifier) else { 78 | return 0 79 | } 80 | return realValue(zero: CGFloat(0), custom: { () -> CGFloat in 81 | footer.makeCustomHeight(view: tableFooter, index: section) 82 | }, setting: { () -> CGFloat in 83 | footer.height 84 | }, layout: { () -> CGFloat in 85 | footer.makeConfig(view: tableFooter, index: section) 86 | return tableFooter.sizeThatFits(CGSize(width: tableView.frame.width, height: 0)).height 87 | }) { () -> CGFloat in 88 | CGFloat(0) 89 | } 90 | } 91 | 92 | public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 93 | let tableSection = self.dataSource.sections[section] 94 | guard let header = tableSection.header else { 95 | return nil 96 | } 97 | guard let tableHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: header.identifier) else { 98 | return nil 99 | } 100 | header.makeConfig(view: tableHeader, index: section) 101 | return tableHeader 102 | } 103 | 104 | public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 105 | let tableSection = self.dataSource.sections[section] 106 | guard let footer = tableSection.footer else { 107 | return nil 108 | } 109 | guard let tableFooter = tableView.dequeueReusableHeaderFooterView(withIdentifier: footer.identifier) else { 110 | return nil 111 | } 112 | footer.makeConfig(view: tableFooter, index: section) 113 | return tableFooter 114 | } 115 | 116 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 117 | let tableSection = self.dataSource.sections[indexPath.section] 118 | let cellTuple = tableSection.cell(index: indexPath.row) 119 | let tableCell = cellTuple.cell 120 | guard let cell = tableView.cellForRow(at: indexPath) else { 121 | return 122 | } 123 | tableCell.makeDidSelectRow(view: cell, index: cellTuple.index) 124 | tableView.deselectRow(at: indexPath, animated: true) 125 | } 126 | 127 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 128 | scrollView.scrollDelegate?.scrollViewDidScroll?(scrollView) 129 | } 130 | 131 | public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 132 | scrollView.scrollDelegate?.scrollViewWillBeginDragging?(scrollView) 133 | } 134 | 135 | public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 136 | scrollView.scrollDelegate?.scrollViewWillEndDragging?(scrollView,velocity, targetContentOffset) 137 | } 138 | 139 | public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 140 | scrollView.scrollDelegate?.scrollViewDidEndDragging?(scrollView, decelerate) 141 | } 142 | 143 | public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { 144 | scrollView.scrollDelegate?.scrollViewWillBeginDecelerating?(scrollView) 145 | } 146 | 147 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 148 | scrollView.scrollDelegate?.scrollViewDidEndDecelerating?(scrollView) 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/UITableView/TableViewRegiterGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewRegiterGroup.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import UIKit.UITableViewHeaderFooterView 9 | 10 | public struct TableViewRegiterGroup : DataNode { 11 | public var header:TableHeaderView? 12 | public var footer:TableFooterView? 13 | public var cells:[TableCell] 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftTableViewGroup/ViewRegister.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewRegister.swift 3 | // 4 | // 5 | // Created by 张行 on 2019/7/17. 6 | // 7 | 8 | import CoreGraphics 9 | import UIKit.UIView 10 | 11 | public protocol ViewRegister: DataNode { 12 | var anyClass:AnyClass { get set} 13 | var identifier: String { get set} 14 | var number:Int {get set} 15 | @discardableResult 16 | func number(_ number:Int) -> Self 17 | } 18 | 19 | struct VeiwRegisterKey { 20 | static var number = "number" 21 | } 22 | 23 | extension ViewRegister { 24 | public var number: Int { 25 | get { 26 | objc_getAssociatedObject(self, &VeiwRegisterKey.number) as? Int ?? 1 27 | } 28 | set { 29 | objc_setAssociatedObject(self, &VeiwRegisterKey.number, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 30 | } 31 | } 32 | @discardableResult 33 | public func number(_ number:Int) -> Self { 34 | var view = self 35 | view.number = number 36 | return view 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftTableViewGroup.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "SwiftTableViewGroup" 3 | spec.version = "2.0.0" 4 | spec.summary = "SwiftTableViewGroup 是符合 SwiftUI 设计的 UITableView 数据驱动(SwiftTableViewGroup is a UITableView data driver compliant with SwiftUI design)" 5 | # spec.description = <<-DESC 6 | # SwiftTableViewGroup 是符合 SwiftUI 设计的 UITableView 数据驱动(SwiftTableViewGroup is a UITableView data driver compliant with SwiftUI design) 7 | # DESC 8 | 9 | spec.homepage = "https://github.com/josercc/SwiftTableViewGroup" 10 | spec.license = "MIT" 11 | spec.author = { "张行" => "josercc@163.com" } 12 | spec.platform = :ios, "9.0" 13 | spec.source = { :git => "https://github.com/josercc/SwiftTableViewGroup.git", :tag => "#{spec.version}" } 14 | spec.source_files = "SwiftTableViewGroup/Sources/SwiftTableViewGroup/**/*.{swift}" 15 | spec.swift_versions = '5.1' 16 | end 17 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftTableViewGroupTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftTableViewGroupTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SwiftTableViewGroupTests/SwiftTableViewGroupTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftTableViewGroup 3 | 4 | final class SwiftTableViewGroupTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(SwiftTableViewGroup().text, "Hello, World!") 10 | let tableView = TableView { 11 | 12 | } 13 | 14 | } 15 | 16 | static var allTests = [ 17 | ("testExample", testExample), 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /Tests/SwiftTableViewGroupTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftTableViewGroupTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /images/2019-07-26-063607.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/2019-07-26-063607.png -------------------------------------------------------------------------------- /images/2019-07-26-063634.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/2019-07-26-063634.png -------------------------------------------------------------------------------- /images/2019-07-29-022957.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/2019-07-29-022957.png -------------------------------------------------------------------------------- /images/Simulator Screen Shot - iPhone Xʀ - 2019-07-27 at 21.43.43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Simulator Screen Shot - iPhone Xʀ - 2019-07-27 at 21.43.43.png -------------------------------------------------------------------------------- /images/Snipaste_2019-07-29_14-11-01-4381583.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Snipaste_2019-07-29_14-11-01-4381583.png -------------------------------------------------------------------------------- /images/Snipaste_2019-07-29_14-11-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Snipaste_2019-07-29_14-11-01.png -------------------------------------------------------------------------------- /images/Snipaste_2019-07-29_14-13-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Snipaste_2019-07-29_14-13-30.png -------------------------------------------------------------------------------- /images/Snipaste_2019-07-29_14-18-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Snipaste_2019-07-29_14-18-03.png -------------------------------------------------------------------------------- /images/Snipaste_2019-07-29_14-19-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/Snipaste_2019-07-29_14-19-23.png -------------------------------------------------------------------------------- /images/image-20190726143356551.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190726143356551.png -------------------------------------------------------------------------------- /images/image-20190726143714253.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190726143714253.png -------------------------------------------------------------------------------- /images/image-20190726145544399.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190726145544399.png -------------------------------------------------------------------------------- /images/image-20190726145605726.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190726145605726.png -------------------------------------------------------------------------------- /images/image-20190729103132510.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190729103132510.png -------------------------------------------------------------------------------- /images/image-20190729103652825.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190729103652825.png -------------------------------------------------------------------------------- /images/image-20190729105419637.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josercc/SwiftTableViewGroup/6122626bdce2e793b37aee5fb51c922d455f696b/images/image-20190729105419637.png --------------------------------------------------------------------------------