├── .gitignore ├── DGTableViewExtension ├── DGIndexPathHeightCache.swift ├── DGKeyedHeightCache.swift ├── DGTableViewExtension.swift ├── DGTemplateLayoutCell.swift └── DGTemplateLayoutellDebug.swift ├── DGTemplateLayoutCell.podspec ├── README.md ├── Sceenshots ├── ZHAODG_480.gif ├── screenshot0.png └── screenshot1.png └── TemplateLayoutDemo ├── .gitignore ├── TemplateLayoutDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── zhaodg.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── zhaodg.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── TemplateLayoutDemo.xcscheme │ └── xcschememanagement.plist ├── TemplateLayoutDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── 1.imageset │ │ ├── 1.jpeg │ │ └── Contents.json │ ├── 2.imageset │ │ ├── 2.jpg │ │ └── Contents.json │ ├── 3.imageset │ │ ├── 11.jpeg │ │ └── Contents.json │ ├── 4.imageset │ │ ├── 4.jpg │ │ └── Contents.json │ ├── 5.imageset │ │ ├── 5.jpg │ │ └── Contents.json │ ├── 6.imageset │ │ ├── 6.jpg │ │ └── Contents.json │ ├── 7.imageset │ │ ├── 7.jpg │ │ └── Contents.json │ ├── 8.imageset │ │ ├── 22.jpg │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── DGFeedCell.swift ├── DGFeedItem.swift ├── Data.json ├── Info.plist ├── LaunchScreen.xib └── ViewController.swift ├── TemplateLayoutDemoTests ├── Info.plist └── TemplateLayoutDemoTests.swift └── TemplateLayoutDemoUITests ├── Info.plist └── TemplateLayoutDemoUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ### Swift ### 2 | # Xcode 3 | # 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | ### AppCode ### 22 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 23 | 24 | *.iml 25 | 26 | ## Directory-based project format: 27 | .idea/ 28 | # if you remove the above rule, at least ignore the following: 29 | 30 | # User-specific stuff: 31 | # .idea/workspace.xml 32 | # .idea/tasks.xml 33 | # .idea/dictionaries 34 | 35 | # Sensitive or high-churn files: 36 | # .idea/dataSources.ids 37 | # .idea/dataSources.xml 38 | # .idea/sqlDataSources.xml 39 | # .idea/dynamic.xml 40 | # .idea/uiDesigner.xml 41 | 42 | # Gradle: 43 | # .idea/gradle.xml 44 | # .idea/libraries 45 | 46 | # Mongo Explorer plugin: 47 | # .idea/mongoSettings.xml 48 | 49 | ## File-based project format: 50 | *.ipr 51 | *.iws 52 | 53 | ## Plugin-specific files: 54 | 55 | # IntelliJ 56 | /out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | 69 | # CocoaPods 70 | # 71 | # We recommend against adding the Pods directory to your .gitignore. However 72 | # you should judge for yourself, the pros and cons are mentioned at: 73 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 74 | # 75 | #Pods/ 76 | 77 | # Carthage 78 | Carthage.pkg 79 | Carthage.build 80 | Carthage.checkout 81 | -------------------------------------------------------------------------------- /DGTableViewExtension/DGIndexPathHeightCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGIndexPathHeightCache.swift 3 | // 4 | // Created by zhaodg on 11/25/15. 5 | // Copyright © 2015 zhaodg. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - DGIndexPathHeightCache 11 | class DGIndexPathHeightCache { 12 | 13 | private var heights: [[CGFloat]] = [] 14 | 15 | // Enable automatically if you're using index path driven height cache. Default is true 16 | internal var automaticallyInvalidateEnabled: Bool = true 17 | 18 | init() { 19 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DGIndexPathHeightCache.deviceOrientationDidChange), name: UIDeviceOrientationDidChangeNotification, object: nil) 20 | } 21 | 22 | deinit { 23 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil) 24 | } 25 | 26 | // MARK: - public 27 | subscript(indexPath: NSIndexPath) -> CGFloat? { 28 | get { 29 | if indexPath.section < heights.count && indexPath.row < heights[indexPath.section].count { 30 | return heights[indexPath.section][indexPath.row] 31 | } 32 | return nil 33 | } 34 | set { 35 | self.buildIndexPathIfNeeded(indexPath) 36 | heights[indexPath.section][indexPath.row] = newValue! 37 | } 38 | } 39 | 40 | internal func invalidateHeightAtIndexPath(indexPath: NSIndexPath) { 41 | self[indexPath] = -1 42 | } 43 | 44 | internal func invalidateAllHeightCache() { 45 | self.heights.removeAll() 46 | } 47 | 48 | internal func existsIndexPath(indexPath: NSIndexPath) -> Bool { 49 | return self[indexPath] != nil && self[indexPath] > -0.0000000001 50 | } 51 | 52 | internal func insertSections(sections: NSIndexSet) { 53 | sections.enumerateIndexesUsingBlock({ (index, stop) -> Void in 54 | self.buildSectionsIfNeeded(index) 55 | self.heights.insert([], atIndex: index) 56 | }) 57 | } 58 | 59 | internal func deleteSections(sections: NSIndexSet) { 60 | sections.enumerateIndexesUsingBlock({ (index, stop) -> Void in 61 | self.buildSectionsIfNeeded(index) 62 | self.heights.removeAtIndex(index) 63 | }) 64 | } 65 | 66 | internal func reloadSections(sections: NSIndexSet) { 67 | sections.enumerateIndexesUsingBlock({ (index, stop) -> Void in 68 | self.buildSectionsIfNeeded(index) 69 | self.heights[index] = [] 70 | }) 71 | } 72 | 73 | internal func moveSection(section: Int, toSection newSection: Int) { 74 | self.buildSectionsIfNeeded(section) 75 | self.buildSectionsIfNeeded(newSection) 76 | swap(&self.heights[section], &self.heights[newSection]) 77 | } 78 | 79 | internal func insertRowsAtIndexPaths(indexPaths: [NSIndexPath]) { 80 | for indexPath in indexPaths { 81 | self.buildIndexPathIfNeeded(indexPath) 82 | self.heights[indexPath.section].insert(-1, atIndex: indexPath.row) 83 | } 84 | } 85 | 86 | internal func deleteRowsAtIndexPaths(indexPaths: [NSIndexPath]) { 87 | let indexPathSorted = indexPaths.sort { $0.section > $1.section || $0.row > $1.row } 88 | for indexPath in indexPathSorted { 89 | self.buildIndexPathIfNeeded(indexPath) 90 | self.heights[indexPath.section].removeAtIndex(indexPath.row) 91 | } 92 | } 93 | 94 | internal func reloadRowsAtIndexPaths(indexPaths: [NSIndexPath]) { 95 | for indexPath in indexPaths { 96 | self.buildIndexPathIfNeeded(indexPath) 97 | self.invalidateHeightAtIndexPath(indexPath) 98 | } 99 | } 100 | 101 | internal func moveRowAtIndexPath(indexPath: NSIndexPath, toIndexPath newIndexPath: NSIndexPath) { 102 | self.buildIndexPathIfNeeded(indexPath) 103 | self.buildIndexPathIfNeeded(newIndexPath) 104 | swap(&self.heights[indexPath.section][indexPath.row], &self.heights[indexPath.section][indexPath.row]) 105 | } 106 | 107 | // MARK: - private 108 | private func buildSectionsIfNeeded(targetSection: Int) { 109 | if targetSection >= heights.count { 110 | for _ in heights.count...targetSection { 111 | self.heights.append([]) 112 | } 113 | } 114 | } 115 | 116 | private func buildRowsIfNeeded(targetRow: Int, existSextion: Int) { 117 | if existSextion < heights.count && targetRow >= heights[existSextion].count { 118 | for _ in heights[existSextion].count...targetRow { 119 | self.heights[existSextion].append(-1) 120 | } 121 | } 122 | } 123 | 124 | private func buildIndexPathIfNeeded(indexPath: NSIndexPath) { 125 | self.buildSectionsIfNeeded(indexPath.section) 126 | self.buildRowsIfNeeded(indexPath.row, existSextion: indexPath.section) 127 | } 128 | 129 | @objc private func deviceOrientationDidChange() { 130 | self.invalidateAllHeightCache() 131 | } 132 | } 133 | 134 | // MARK: - UITableView Extension 135 | extension UITableView { 136 | 137 | private struct AssociatedKey { 138 | static var DGIndexPathHeightCache = "DGIndexPathHeightCache" 139 | } 140 | 141 | /// Height cache by index path. Generally, you don't need to use it directly. 142 | internal var dg_indexPathHeightCache: DGIndexPathHeightCache { 143 | if let value: DGIndexPathHeightCache = objc_getAssociatedObject(self, &AssociatedKey.DGIndexPathHeightCache) as? DGIndexPathHeightCache { 144 | return value 145 | } else { 146 | let cache: DGIndexPathHeightCache = DGIndexPathHeightCache() 147 | objc_setAssociatedObject(self, &AssociatedKey.DGIndexPathHeightCache, cache, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 148 | return cache 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /DGTableViewExtension/DGKeyedHeightCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGKeyedHeightCache.swift 3 | // 4 | // Created by zhaodg on 11/25/15. 5 | // Copyright © 2015 zhaodg. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - DGHeightsDictionary 11 | class DGHeightsDictionary { 12 | 13 | private var heights: [String: CGFloat] = [:] 14 | 15 | init() { 16 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DGHeightsDictionary.deviceOrientationDidChange), name: UIDeviceOrientationDidChangeNotification, object: nil) 17 | } 18 | 19 | deinit { 20 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil) 21 | } 22 | 23 | // MARK: - public 24 | subscript(key: String) -> CGFloat? { 25 | get { 26 | return heights[key] 27 | } 28 | set { 29 | heights[key] = newValue 30 | } 31 | } 32 | 33 | internal func invalidateHeightForKey(key: String) -> CGFloat? { 34 | return self.heights.removeValueForKey(key) 35 | } 36 | 37 | internal func invalidateAllHeightCache() { 38 | return self.heights.removeAll() 39 | } 40 | 41 | internal func existsKey(key: String) -> Bool { 42 | return self[key] != nil 43 | } 44 | 45 | // MARK: - private 46 | @objc private func deviceOrientationDidChange() { 47 | self.invalidateAllHeightCache() 48 | } 49 | } 50 | 51 | // MARK: - UITableView Extension 52 | extension UITableView { 53 | 54 | private struct AssociatedKey { 55 | static var DGkeyedHeightCache = "DGkeyedHeightCache" 56 | } 57 | 58 | /// Height cache by key. Generally, you don't need to use it directly. 59 | internal var dg_keyedHeightCache: DGHeightsDictionary { 60 | if let value: DGHeightsDictionary = objc_getAssociatedObject(self, &AssociatedKey.DGkeyedHeightCache) as? DGHeightsDictionary { 61 | return value 62 | } else { 63 | let cache: DGHeightsDictionary = DGHeightsDictionary() 64 | objc_setAssociatedObject(self, &AssociatedKey.DGkeyedHeightCache, cache, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 65 | return cache 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DGTableViewExtension/DGTableViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGTableViewExtension.swift 3 | // 4 | // Created by zhaodg on 11/24/15. 5 | // Copyright © 2015 zhaodg. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - UITableView Extension 11 | extension UITableView { 12 | 13 | // MARK: - public method 14 | /// Returns height of cell of type specifed by a reuse identifier and configured 15 | /// by the configuration block. 16 | /// 17 | /// The cell would be layed out on a fixed-width, vertically expanding basis with 18 | /// respect to its dynamic content, using auto layout. Thus, it is imperative that 19 | /// the cell was set up to be self-satisfied, i.e. its content always determines 20 | /// its height given the width is equal to the tableview's. 21 | /// 22 | /// @param identifier A string identifier for retrieving and maintaining template 23 | /// cells with system's "-dequeueReusableCellWithIdentifier:" call. 24 | /// @param configuration An optional block for configuring and providing content 25 | /// to the template cell. The configuration should be minimal for scrolling 26 | /// performance yet sufficient for calculating cell's height. 27 | /// 28 | public func dg_heightForCellWithIdentifier(identifier: String, configuration: ((cell: UITableViewCell) -> Void)?) -> CGFloat { 29 | if identifier.characters.count == 0 { 30 | return 0 31 | } 32 | 33 | let cell = self.dg_templateCellForReuseIdentifier(identifier) 34 | 35 | // Manually calls to ensure consistent behavior with actual cells (that are displayed on screen). 36 | cell.prepareForReuse() 37 | 38 | // Customize and provide content for our template cell. 39 | if configuration != nil { 40 | configuration!(cell: cell) 41 | } 42 | 43 | var contentViewWidth: CGFloat = CGRectGetWidth(self.frame) 44 | 45 | // If a cell has accessory view or system accessory type, its content view's width is smaller 46 | // than cell's by some fixed values. 47 | if cell.accessoryView != nil { 48 | contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView!.frame) 49 | } else { 50 | var accessoryWidths: CGFloat? 51 | switch cell.accessoryType { 52 | case .None: 53 | accessoryWidths = 0 54 | case .DisclosureIndicator: 55 | accessoryWidths = 34 56 | case .DetailDisclosureButton: 57 | accessoryWidths = 68 58 | case .Checkmark: 59 | accessoryWidths = 40 60 | case .DetailButton: 61 | accessoryWidths = 38 62 | } 63 | contentViewWidth -= accessoryWidths! 64 | } 65 | 66 | var fittingSize: CGSize = CGSizeZero 67 | 68 | if cell.dg_enforceFrameLayout == true { 69 | // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself. 70 | // This is the same method used in iOS8 self-sizing cell's implementation. 71 | // Note: fitting height should not include separator view. 72 | let selector: Selector = #selector(UIView.sizeThatFits(_:)) 73 | let inherited: Bool = cell.isMemberOfClass(UITableViewCell.self) 74 | let overrided: Bool = cell.dynamicType.instanceMethodForSelector(selector) != UITableViewCell.instanceMethodForSelector(selector) 75 | if inherited && !overrided { 76 | assert(false, "Customized cell must override '-sizeThatFits:' method if not using auto layout.") 77 | } 78 | fittingSize = cell.sizeThatFits(CGSizeMake(contentViewWidth, 0)) 79 | } else { 80 | // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead 81 | // of growing horizontally, in a flow-layout manner. 82 | let tempWidthConstraint: NSLayoutConstraint = NSLayoutConstraint(item: cell.contentView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: contentViewWidth) 83 | cell.contentView.addConstraint(tempWidthConstraint) 84 | 85 | // Make sure the constraints have been set up for this cell, since it may have just been created from 86 | // scratch. Use the following lines, assuming you are setting up constraints from within the cell's 87 | // updateConstraints method: 88 | cell.setNeedsUpdateConstraints() 89 | cell.updateConstraintsIfNeeded() 90 | 91 | // Do the layout pass on the cell, which will calculate the frames for all the views based on the constraints. 92 | // (Note that you must set the preferredMaxLayoutWidth on multi-line UILabels inside the -[layoutSubviews] 93 | // method of the UITableViewCell subclass, or do it manually at this point before the below 2 lines!) 94 | cell.setNeedsLayout() 95 | cell.layoutIfNeeded() 96 | // Auto layout engine does its math 97 | fittingSize = cell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) 98 | cell.contentView.removeConstraint(tempWidthConstraint) 99 | } 100 | 101 | // Add 1px extra space for separator line if needed, simulating default UITableViewCell. 102 | if self.separatorStyle != .None { 103 | fittingSize.height += 1.0 / UIScreen.mainScreen().scale 104 | } 105 | 106 | if cell.dg_enforceFrameLayout == true { 107 | self.dg_debugLog("calculate using frame layout - \(fittingSize.height)") 108 | } else { 109 | self.dg_debugLog("calculate using auto layout - \(fittingSize.height)") 110 | } 111 | 112 | return fittingSize.height 113 | } 114 | 115 | /// This method caches height by your model entity's identifier. 116 | /// If your model's changed, call "invalidateHeightForKey(key: String)" to 117 | /// invalidate cache and re-calculate, it's much cheaper and effective than "cacheByIndexPath". 118 | /// 119 | /// @param key model entity's identifier whose data configures a cell. 120 | /// 121 | public func dg_heightForCellWithIdentifier(identifier: String, key: String, configuration: ((cell: UITableViewCell) -> Void)?) -> CGFloat { 122 | if identifier.characters.count == 0 || key.characters.count == 0 { 123 | return 0 124 | } 125 | 126 | // Hit cache 127 | if self.dg_keyedHeightCache.existsKey(key) { 128 | let height: CGFloat = self.dg_keyedHeightCache[key]! 129 | self.dg_debugLog("hit cache by key[\(key)] -> \(height)") 130 | return height 131 | } 132 | 133 | let height = self.dg_heightForCellWithIdentifier(identifier, configuration: configuration) 134 | self.dg_keyedHeightCache[key] = height 135 | self.dg_debugLog("cached by key[\(key)] --> \(height)") 136 | 137 | return height 138 | } 139 | 140 | /// This method does what "-dg_heightForCellWithIdentifier:configuration" does, and 141 | /// calculated height will be cached by its index path, returns a cached height 142 | /// when needed. Therefore lots of extra height calculations could be saved. 143 | /// 144 | /// No need to worry about invalidating cached heights when data source changes, it 145 | /// will be done automatically when you call "-reloadData" or any method that triggers 146 | /// UITableView's reloading. 147 | /// 148 | /// @param indexPath where this cell's height cache belongs. 149 | /// 150 | public func dg_heightForCellWithIdentifier(identifier: String, indexPath: NSIndexPath, configuration: ((cell: UITableViewCell) -> Void)?) -> CGFloat { 151 | if identifier.characters.count == 0 { 152 | return 0 153 | } 154 | 155 | // Hit cache 156 | if self.dg_indexPathHeightCache.existsIndexPath(indexPath) { 157 | let height: CGFloat = self.dg_indexPathHeightCache[indexPath]! 158 | self.dg_debugLog("hit cache by indexPath:[\(indexPath.section),\(indexPath.row)] -> \(height)") 159 | return height 160 | } 161 | 162 | let height = self.dg_heightForCellWithIdentifier(identifier, configuration: configuration) 163 | self.dg_indexPathHeightCache[indexPath] = height 164 | self.dg_debugLog("cached by indexPath:[\(indexPath.section),\(indexPath.row)] --> \(height)") 165 | 166 | return height 167 | } 168 | 169 | 170 | // MARK: - private 171 | private func dg_templateCellForReuseIdentifier(identifier: String) -> UITableViewCell { 172 | assert(identifier.characters.count > 0, "Expect a valid identifier - \(identifier)") 173 | var templateCell = self.dg_templateCellsByIdentifiers?[identifier] as? UITableViewCell 174 | if templateCell == nil { 175 | templateCell = self.dequeueReusableCellWithIdentifier(identifier) 176 | assert(templateCell != nil, "Cell must be registered to table view for identifier - \(identifier)") 177 | templateCell?.dg_isTemplateLayoutCell = true 178 | templateCell?.contentView.translatesAutoresizingMaskIntoConstraints = false 179 | self.dg_templateCellsByIdentifiers?[identifier] = templateCell 180 | self.dg_debugLog("layout cell created - \(identifier)") 181 | } 182 | 183 | return templateCell! 184 | } 185 | 186 | private struct AssociatedKey { 187 | static var DGtemplateCellsByIdentifiers = "DGtemplateCellsByIdentifiers" 188 | } 189 | 190 | private var dg_templateCellsByIdentifiers: [String : AnyObject]? { 191 | get { 192 | return objc_getAssociatedObject(self, &AssociatedKey.DGtemplateCellsByIdentifiers) as? [String : AnyObject] 193 | } 194 | set { 195 | objc_setAssociatedObject(self, &AssociatedKey.DGtemplateCellsByIdentifiers, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 196 | } 197 | } 198 | } 199 | 200 | // MARK: - UITableView Method Swizzling 201 | extension UITableView { 202 | public override class func initialize() { 203 | struct Static { 204 | static var token: dispatch_once_t = 0 205 | } 206 | 207 | // make sure this isn't a subclass 208 | if self !== UITableView.self { 209 | return 210 | } 211 | 212 | dispatch_once(&Static.token) { 213 | let selectorStrings: [String] = [ 214 | "reloadData", 215 | "insertSections:withRowAnimation:", 216 | "deleteSections:withRowAnimation:", 217 | "reloadSections:withRowAnimation:", 218 | "moveSection:toSection:", 219 | "insertRowsAtIndexPaths:withRowAnimation:", 220 | "deleteRowsAtIndexPaths:withRowAnimation:", 221 | "reloadRowsAtIndexPaths:withRowAnimation:", 222 | "moveRowAtIndexPath:toIndexPath:" 223 | ] 224 | 225 | for selectorString in selectorStrings { 226 | let originalSelector = Selector(selectorString) 227 | let swizzledSelector = Selector("dg_"+selectorString) 228 | let originalMethod = class_getInstanceMethod(self, originalSelector) 229 | let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) 230 | method_exchangeImplementations(originalMethod, swizzledMethod); 231 | } 232 | } 233 | } 234 | 235 | // MARK: - Method Swizzling 236 | /// Call this method when you want to reload data but don't want to invalidate 237 | /// all height cache by index path, for example, load more data at the bottom of 238 | /// table view. 239 | public func dg_reloadDataWithoutInvalidateIndexPathHeightCache() { 240 | self.dg_reloadData() 241 | } 242 | 243 | public func dg_reloadData() { 244 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 245 | self.dg_indexPathHeightCache.invalidateAllHeightCache() 246 | } 247 | self.dg_reloadData() 248 | } 249 | 250 | public func dg_insertSections(sections: NSIndexSet, withRowAnimation animation: UITableViewRowAnimation) { 251 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 252 | self.dg_indexPathHeightCache.insertSections(sections) 253 | } 254 | self.dg_insertSections(sections, withRowAnimation: animation) 255 | } 256 | 257 | public func dg_deleteSections(sections: NSIndexSet, withRowAnimation animation: UITableViewRowAnimation) { 258 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 259 | self.dg_indexPathHeightCache.deleteSections(sections) 260 | } 261 | self.dg_deleteSections(sections, withRowAnimation: animation) 262 | } 263 | 264 | public func dg_reloadSections(sections: NSIndexSet, withRowAnimation animation: UITableViewRowAnimation) { 265 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 266 | self.dg_indexPathHeightCache.reloadSections(sections) 267 | } 268 | self.dg_reloadSections(sections, withRowAnimation: animation) 269 | } 270 | 271 | public func dg_moveSection(section: Int, toSection newSection: Int) { 272 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 273 | self.dg_indexPathHeightCache.moveSection(section, toSection: newSection) 274 | } 275 | self.dg_moveSection(section, toSection: newSection) 276 | } 277 | 278 | public func dg_insertRowsAtIndexPaths(indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation) { 279 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 280 | self.dg_indexPathHeightCache.insertRowsAtIndexPaths(indexPaths) 281 | } 282 | self.dg_insertRowsAtIndexPaths(indexPaths, withRowAnimation: animation) 283 | } 284 | 285 | public func dg_deleteRowsAtIndexPaths(indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation) { 286 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 287 | self.dg_indexPathHeightCache.deleteRowsAtIndexPaths(indexPaths) 288 | } 289 | self.dg_deleteRowsAtIndexPaths(indexPaths, withRowAnimation: animation) 290 | } 291 | 292 | public func dg_reloadRowsAtIndexPaths(indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation) { 293 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 294 | self.dg_indexPathHeightCache.reloadRowsAtIndexPaths(indexPaths) 295 | } 296 | self.dg_reloadRowsAtIndexPaths(indexPaths, withRowAnimation: animation) 297 | } 298 | 299 | public func dg_moveRowAtIndexPath(indexPath: NSIndexPath, toIndexPath newIndexPath: NSIndexPath) { 300 | if self.dg_indexPathHeightCache.automaticallyInvalidateEnabled { 301 | self.dg_indexPathHeightCache.moveRowAtIndexPath(indexPath, toIndexPath: newIndexPath) 302 | } 303 | self.dg_moveRowAtIndexPath(indexPath, toIndexPath: newIndexPath) 304 | } 305 | } -------------------------------------------------------------------------------- /DGTableViewExtension/DGTemplateLayoutCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGTemplateLayoutCell.swift 3 | // 4 | // Created by zhaodg on 11/24/15. 5 | // Copyright © 2015 zhaodg. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - UITableViewCell Extension 11 | extension UITableViewCell { 12 | 13 | // MARK: - public 14 | /// Enable to enforce this template layout cell to use "frame layout" rather than "auto layout", 15 | /// and will ask cell's height by calling "-sizeThatFits:", so you must override this method. 16 | /// Use this property only when you want to manually control this template layout cell's height 17 | /// calculation mode, default to false. 18 | /// 19 | public var dg_isTemplateLayoutCell: Bool? { 20 | get { 21 | return objc_getAssociatedObject(self, &AssociatedKey.DGisTemplateLayoutCell) as? Bool 22 | } 23 | set { 24 | objc_setAssociatedObject(self, &AssociatedKey.DGisTemplateLayoutCell, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) 25 | } 26 | } 27 | 28 | /// Indicate this is a template layout cell for calculation only. 29 | /// You may need this when there are non-UI side effects when configure a cell. 30 | /// Like: 31 | /// func configureCell(cell: DGFeedCell, atIndexPath indexPath: NSIndexPath) { 32 | /// cell.loadData(item...) 33 | /// if cell.fd_isTemplateLayoutCell == false { 34 | /// self.notifySomething() // non-UI side effects 35 | /// } 36 | /// } 37 | /// 38 | public var dg_enforceFrameLayout: Bool? { 39 | get { 40 | return objc_getAssociatedObject(self, &AssociatedKey.DGenforceFrameLayout) as? Bool 41 | } 42 | set { 43 | objc_setAssociatedObject(self, &AssociatedKey.DGenforceFrameLayout, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) 44 | } 45 | } 46 | 47 | // MARK: - private 48 | private struct AssociatedKey { 49 | static var DGisTemplateLayoutCell = "DGisTemplateLayoutCell" 50 | static var DGenforceFrameLayout = "DGenforceFrameLayout" 51 | } 52 | } -------------------------------------------------------------------------------- /DGTableViewExtension/DGTemplateLayoutellDebug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGTemplateLayoutellDebug.swift 3 | // 4 | // Created by zhaodg on 11/24/15. 5 | // Copyright © 2015 zhaodg. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - UITableView Extension 11 | extension UITableView { 12 | 13 | /// Helps to debug or inspect what is this "DGTemplateLayoutCell" extention doing, 14 | /// turning on to print logs when "creating", "calculating", "precaching" or "hitting cache". 15 | /// 16 | /// Default to false, log by print. 17 | /// 18 | public var dg_debugLogEnabled: Bool? { 19 | get { 20 | return objc_getAssociatedObject(self, &AssociatedKey.DGdebugLogEnabled) as? Bool 21 | } 22 | set { 23 | objc_setAssociatedObject(self, &AssociatedKey.DGdebugLogEnabled, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) 24 | } 25 | } 26 | 27 | // MARK: - public method 28 | 29 | /// Debug log controlled by "dg_debugLogEnabled". 30 | public func dg_debugLog(message: String) { 31 | if self.dg_debugLogEnabled == true { 32 | print(message) 33 | } 34 | } 35 | 36 | // MARK: - private 37 | private struct AssociatedKey { 38 | static var DGdebugLogEnabled = "DGdebugLogEnabled" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DGTemplateLayoutCell.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "DGTemplateLayoutCell" 3 | s.version = "1.1.2" 4 | s.summary = "[Swift 3.0] Template auto layout cell for automatically UITableViewCell height calculate, cache and precache" 5 | s.description = "[Swift 3.0] Template auto layout cell for automatically UITableViewCell height calculate, cache and precache. Requires a `self-satisfied` UITableViewCell, using system's `- systemLayoutSizeFittingSize:`, provides heights caching." 6 | s.homepage = "https://github.com/zhaodg/DGTemplateLayoutCell" 7 | 8 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 9 | s.license = { :type => "MIT", :file => "LICENSE" } 10 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 11 | s.author = { "zhaodg" => "https://github.com/zhaodg" } 12 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 13 | s.platform = :ios, "8.0" 14 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 15 | s.source = { :git => "https://github.com/zhaodg/DGTemplateLayoutCell.git", :tag => "v1.1.2" } 16 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 17 | s.source_files = "DGTableViewExtension/*.{swift}" 18 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 19 | s.requires_arc = true 20 | end 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DGTemplateLayoutCell (Swift 3.0) 2 | Template auto layout cell for automatically UITableViewCell height calculating.(**Swift 3.0**) 3 | > DGTemplateLayoutCell copy and modify forkingdog's [UITableView-FDTemplateLayoutCell](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell), and it is implemented in Swift 3.0. 4 | 5 | ## Requirements 6 | - Xcode 7 or higher 7 | - iOS 8.0 or higher (may work on previous versions, just did not test it) 8 | - ARC 9 | - Swift 3.0 10 | 11 | ## Overview 12 | Template auto layout cell for **automatically** UITableViewCell height calculating. 13 | 14 | ![Demo Overview](https://github.com/zhaodg/DGTemplateLayoutCell/blob/master/Sceenshots/ZHAODG_480.gif) 15 | 16 | ## Basic usage 17 | 18 | If you have a **self-satisfied** cell, then all you have to do is: 19 | 20 | ``` 21 | import DGTemplateLayoutCell 22 | 23 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 24 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", configuration: { (cell) -> Void in 25 | // Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:" 26 | // Like: 27 | // let cell = cell as! DGFeedCell 28 | // cell.loadData(self.feedList[indexPath.section][indexPath.row]) 29 | }) 30 | } 31 | ``` 32 | 33 | ## Height Caching API 34 | 35 | Since iOS8, `-tableView:heightForRowAtIndexPath:` will be called more times than we expect, we can feel these extra calculations when scrolling. So we provide another API with cache by index path: 36 | 37 | ``` 38 | import DGTemplateLayoutCell 39 | 40 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 41 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", indexPath: indexPath, configuration: { (cell) -> Void in 42 | // Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:" 43 | // Like: 44 | // let cell = cell as! DGFeedCell 45 | // cell.loadData(self.feedList[indexPath.section][indexPath.row]) 46 | }) 47 | } 48 | ``` 49 | 50 | Or, if your entity has an unique identifier, use cache by key API: 51 | 52 | ``` 53 | import DGTemplateLayoutCell 54 | 55 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 56 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", key: uuid, configuration: { (cell) -> Void in 57 | // Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:" 58 | // Like: 59 | // let cell = cell as! DGFeedCell 60 | // cell.loadData(self.feedList[indexPath.section][indexPath.row]) 61 | }) 62 | } 63 | ``` 64 | 65 | ## Frame layout mode 66 | 67 | `FDTemplateLayoutCell` offers 2 modes for asking cell's height. 68 | 69 | 1. Auto layout mode using "-systemLayoutSizeFittingSize:" 70 | 2. Frame layout mode using "-sizeThatFits:" 71 | 72 | Generally, no need to care about modes, it will **automatically** choose a proper mode by whether you have set auto layout constrants on cell's content view. If you want to enforce frame layout mode, enable this property in your cell's configuration block: 73 | 74 | ``` 75 | cell.dg_enforceFrameLayout = true 76 | ``` 77 | And if you're using frame layout mode, you must override `-sizeThatFits:` in your customized cell and return content view's height (separator excluded) 78 | 79 | ``` 80 | override func sizeThatFits(size: CGSize) -> CGSize { 81 | // var height: CGFloat = 0 82 | // height += self.contentLabel.sizeThatFits(size).height 83 | // height += self.contentLabel.sizeThatFits(size).height 84 | // height += self.contentImageView.sizeThatFits(size).height 85 | // height += self.userNameLabel.sizeThatFits(size).height 86 | // height += self.timeLabel.sizeThatFits(size).height 87 | return CGSizeMake(size.width, height) 88 | } 89 | 90 | ``` 91 | 92 | ## Debug log 93 | 94 | Debug log helps to debug or inspect what is this "FDTemplateLayoutCell" extention doing, turning on to print logs when "calculating", "precaching" or "hitting cache".Default to "false", log by "print". 95 | 96 | ``` 97 | self.tableView.dg_debugLogEnabled = true 98 | ``` 99 | 100 | It will print like this: 101 | 102 | ``` 103 | calculate using auto layout - 388.666666666667 104 | cached by indexPath:[0,4] --> 388.666666666667 105 | layout cell created - DGFeedCell 106 | calculate using auto layout - 338.666666666667 107 | cached by indexPath:[0,5] --> 338.666666666667 108 | layout cell created - DGFeedCell 109 | calculate using auto layout - 371.666666666667 110 | cached by indexPath:[0,6] --> 371.666666666667 111 | layout cell created - DGFeedCell 112 | calculate using auto layout - 242.333333333333 113 | cached by indexPath:[0,7] --> 242.333333333333 114 | hit cache by indexPath:[0,0] -> 125.333333333333 115 | hit cache by indexPath:[0,0] -> 125.333333333333 116 | hit cache by indexPath:[0,1] -> 147.0 117 | hit cache by indexPath:[0,1] -> 147.0 118 | hit cache by indexPath:[0,2] -> 352.0 119 | hit cache by indexPath:[0,2] -> 352.0 120 | ``` 121 | 122 | ## About self-satisfied cell 123 | 124 | a fully **self-satisfied** cell is constrainted by auto layout and each edge("top", "left", "bottom", "right") has at least one layout constraint against it. It's the same concept introduced as "self-sizing cell" in iOS8 using auto layout. 125 | 126 | A bad one :( - missing right and bottom 127 | ![non-self-satisfied](https://github.com/zhaodg/DGTemplateLayoutCell/blob/master/Sceenshots/screenshot0.png) 128 | 129 | A good one :) 130 | ![self-satisfied](https://github.com/zhaodg/DGTemplateLayoutCell/blob/master/Sceenshots/screenshot1.png) 131 | 132 | ## Notes 133 | 134 | A template layout cell is created by `-dequeueReusableCellWithIdentifier:` method, it means that you MUST have registered this cell reuse identifier by one of: 135 | 136 | - A prototype cell of UITableView in storyboard. 137 | - Use `-registerNib:forCellReuseIdentifier:` 138 | - Use `-registerClass:forCellReuseIdentifier:` 139 | 140 | ## 如果你在天朝 141 | 原作者(**sunnyxx**)文章: 142 | [http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/) 143 | 144 | ## Installation 145 | 146 | Latest version: **1.1.2** 147 | 148 | ``` 149 | pod search DGTemplateLayoutCell 150 | pod 'DGTemplateLayoutCell', '~> 1.1.2' 151 | ``` 152 | If you cannot search out the latest version, try: 153 | 154 | ``` 155 | pod setup 156 | ``` 157 | 158 | ## Release Notes 159 | 160 | We recommend to use the latest release in cocoapods. 161 | 162 | - 1.0 163 | > 1. Modify [FDTemplateLayoutCell](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell) 164 | > 2. Writes in Swift 2.0 165 | 166 | - 1.1 167 | > 1. Fix some bugs. 168 | 169 | - 1.1.1 170 | > 1. Add Some Comment and modify podspec 171 | 172 | - 1.1.2 173 | > 1. Support Swift 3.0 174 | 175 | 176 | ## License 177 | MIT 178 | -------------------------------------------------------------------------------- /Sceenshots/ZHAODG_480.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/Sceenshots/ZHAODG_480.gif -------------------------------------------------------------------------------- /Sceenshots/screenshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/Sceenshots/screenshot0.png -------------------------------------------------------------------------------- /Sceenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/Sceenshots/screenshot1.png -------------------------------------------------------------------------------- /TemplateLayoutDemo/.gitignore: -------------------------------------------------------------------------------- 1 | ### Swift ### 2 | # Xcode 3 | # 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | ### AppCode ### 22 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 23 | 24 | *.iml 25 | 26 | ## Directory-based project format: 27 | .idea/ 28 | # if you remove the above rule, at least ignore the following: 29 | 30 | # User-specific stuff: 31 | # .idea/workspace.xml 32 | # .idea/tasks.xml 33 | # .idea/dictionaries 34 | 35 | # Sensitive or high-churn files: 36 | # .idea/dataSources.ids 37 | # .idea/dataSources.xml 38 | # .idea/sqlDataSources.xml 39 | # .idea/dynamic.xml 40 | # .idea/uiDesigner.xml 41 | 42 | # Gradle: 43 | # .idea/gradle.xml 44 | # .idea/libraries 45 | 46 | # Mongo Explorer plugin: 47 | # .idea/mongoSettings.xml 48 | 49 | ## File-based project format: 50 | *.ipr 51 | *.iws 52 | 53 | ## Plugin-specific files: 54 | 55 | # IntelliJ 56 | /out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | 69 | # CocoaPods 70 | # 71 | # We recommend against adding the Pods directory to your .gitignore. However 72 | # you should judge for yourself, the pros and cons are mentioned at: 73 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 74 | # 75 | #Pods/ 76 | 77 | # Carthage 78 | Carthage.pkg 79 | Carthage.build 80 | Carthage.checkout 81 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C200F9061C07078900481943 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9051C07078900481943 /* AppDelegate.swift */; }; 11 | C200F9081C07078900481943 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9071C07078900481943 /* ViewController.swift */; }; 12 | C200F90B1C07078900481943 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C200F9091C07078900481943 /* Main.storyboard */; }; 13 | C200F90D1C07078900481943 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C200F90C1C07078900481943 /* Assets.xcassets */; }; 14 | C200F91B1C07078900481943 /* TemplateLayoutDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F91A1C07078900481943 /* TemplateLayoutDemoTests.swift */; }; 15 | C200F9261C07078900481943 /* TemplateLayoutDemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9251C07078900481943 /* TemplateLayoutDemoUITests.swift */; }; 16 | C200F9391C0707A500481943 /* DGIndexPathHeightCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9341C0707A500481943 /* DGIndexPathHeightCache.swift */; }; 17 | C200F93A1C0707A500481943 /* DGTemplateLayoutellDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9351C0707A500481943 /* DGTemplateLayoutellDebug.swift */; }; 18 | C200F93B1C0707A500481943 /* DGTableViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9361C0707A500481943 /* DGTableViewExtension.swift */; }; 19 | C200F93C1C0707A500481943 /* DGTemplateLayoutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9371C0707A500481943 /* DGTemplateLayoutCell.swift */; }; 20 | C200F93D1C0707A500481943 /* DGKeyedHeightCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9381C0707A500481943 /* DGKeyedHeightCache.swift */; }; 21 | C200F93F1C0709A300481943 /* Data.json in Resources */ = {isa = PBXBuildFile; fileRef = C200F93E1C0709A300481943 /* Data.json */; }; 22 | C200F9411C0709E700481943 /* DGFeedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9401C0709E700481943 /* DGFeedItem.swift */; }; 23 | C200F9431C0709F300481943 /* DGFeedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C200F9421C0709F300481943 /* DGFeedCell.swift */; }; 24 | C200F9451C07399A00481943 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C200F9441C07399A00481943 /* LaunchScreen.xib */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | C200F9171C07078900481943 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = C200F8FA1C07078900481943 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = C200F9011C07078900481943; 33 | remoteInfo = TemplateLayoutDemo; 34 | }; 35 | C200F9221C07078900481943 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = C200F8FA1C07078900481943 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = C200F9011C07078900481943; 40 | remoteInfo = TemplateLayoutDemo; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | C200F9021C07078900481943 /* TemplateLayoutDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TemplateLayoutDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | C200F9051C07078900481943 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | C200F9071C07078900481943 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 48 | C200F90A1C07078900481943 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | C200F90C1C07078900481943 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | C200F9111C07078900481943 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | C200F9161C07078900481943 /* TemplateLayoutDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemplateLayoutDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | C200F91A1C07078900481943 /* TemplateLayoutDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateLayoutDemoTests.swift; sourceTree = ""; }; 53 | C200F91C1C07078900481943 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | C200F9211C07078900481943 /* TemplateLayoutDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemplateLayoutDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | C200F9251C07078900481943 /* TemplateLayoutDemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateLayoutDemoUITests.swift; sourceTree = ""; }; 56 | C200F9271C07078900481943 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | C200F9341C0707A500481943 /* DGIndexPathHeightCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DGIndexPathHeightCache.swift; path = ../DGTableViewExtension/DGIndexPathHeightCache.swift; sourceTree = ""; }; 58 | C200F9351C0707A500481943 /* DGTemplateLayoutellDebug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DGTemplateLayoutellDebug.swift; path = ../DGTableViewExtension/DGTemplateLayoutellDebug.swift; sourceTree = ""; }; 59 | C200F9361C0707A500481943 /* DGTableViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DGTableViewExtension.swift; path = ../DGTableViewExtension/DGTableViewExtension.swift; sourceTree = ""; }; 60 | C200F9371C0707A500481943 /* DGTemplateLayoutCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DGTemplateLayoutCell.swift; path = ../DGTableViewExtension/DGTemplateLayoutCell.swift; sourceTree = ""; }; 61 | C200F9381C0707A500481943 /* DGKeyedHeightCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DGKeyedHeightCache.swift; path = ../DGTableViewExtension/DGKeyedHeightCache.swift; sourceTree = ""; }; 62 | C200F93E1C0709A300481943 /* Data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Data.json; sourceTree = ""; }; 63 | C200F9401C0709E700481943 /* DGFeedItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DGFeedItem.swift; sourceTree = ""; }; 64 | C200F9421C0709F300481943 /* DGFeedCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DGFeedCell.swift; sourceTree = ""; }; 65 | C200F9441C07399A00481943 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | C200F8FF1C07078900481943 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | C200F9131C07078900481943 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | C200F91E1C07078900481943 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | C200F8F91C07078900481943 = { 94 | isa = PBXGroup; 95 | children = ( 96 | C200F9331C07079000481943 /* Classes */, 97 | C200F9041C07078900481943 /* TemplateLayoutDemo */, 98 | C200F9191C07078900481943 /* TemplateLayoutDemoTests */, 99 | C200F9241C07078900481943 /* TemplateLayoutDemoUITests */, 100 | C200F9031C07078900481943 /* Products */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | C200F9031C07078900481943 /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C200F9021C07078900481943 /* TemplateLayoutDemo.app */, 108 | C200F9161C07078900481943 /* TemplateLayoutDemoTests.xctest */, 109 | C200F9211C07078900481943 /* TemplateLayoutDemoUITests.xctest */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | C200F9041C07078900481943 /* TemplateLayoutDemo */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | C200F9401C0709E700481943 /* DGFeedItem.swift */, 118 | C200F9421C0709F300481943 /* DGFeedCell.swift */, 119 | C200F9051C07078900481943 /* AppDelegate.swift */, 120 | C200F9071C07078900481943 /* ViewController.swift */, 121 | C200F9091C07078900481943 /* Main.storyboard */, 122 | C200F90C1C07078900481943 /* Assets.xcassets */, 123 | C200F9111C07078900481943 /* Info.plist */, 124 | C200F93E1C0709A300481943 /* Data.json */, 125 | C200F9441C07399A00481943 /* LaunchScreen.xib */, 126 | ); 127 | path = TemplateLayoutDemo; 128 | sourceTree = ""; 129 | }; 130 | C200F9191C07078900481943 /* TemplateLayoutDemoTests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | C200F91A1C07078900481943 /* TemplateLayoutDemoTests.swift */, 134 | C200F91C1C07078900481943 /* Info.plist */, 135 | ); 136 | path = TemplateLayoutDemoTests; 137 | sourceTree = ""; 138 | }; 139 | C200F9241C07078900481943 /* TemplateLayoutDemoUITests */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | C200F9251C07078900481943 /* TemplateLayoutDemoUITests.swift */, 143 | C200F9271C07078900481943 /* Info.plist */, 144 | ); 145 | path = TemplateLayoutDemoUITests; 146 | sourceTree = ""; 147 | }; 148 | C200F9331C07079000481943 /* Classes */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | C200F9361C0707A500481943 /* DGTableViewExtension.swift */, 152 | C200F9341C0707A500481943 /* DGIndexPathHeightCache.swift */, 153 | C200F9371C0707A500481943 /* DGTemplateLayoutCell.swift */, 154 | C200F9381C0707A500481943 /* DGKeyedHeightCache.swift */, 155 | C200F9351C0707A500481943 /* DGTemplateLayoutellDebug.swift */, 156 | ); 157 | name = Classes; 158 | sourceTree = ""; 159 | }; 160 | /* End PBXGroup section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | C200F9011C07078900481943 /* TemplateLayoutDemo */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = C200F92A1C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemo" */; 166 | buildPhases = ( 167 | C200F8FE1C07078900481943 /* Sources */, 168 | C200F8FF1C07078900481943 /* Frameworks */, 169 | C200F9001C07078900481943 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = TemplateLayoutDemo; 176 | productName = TemplateLayoutDemo; 177 | productReference = C200F9021C07078900481943 /* TemplateLayoutDemo.app */; 178 | productType = "com.apple.product-type.application"; 179 | }; 180 | C200F9151C07078900481943 /* TemplateLayoutDemoTests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = C200F92D1C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemoTests" */; 183 | buildPhases = ( 184 | C200F9121C07078900481943 /* Sources */, 185 | C200F9131C07078900481943 /* Frameworks */, 186 | C200F9141C07078900481943 /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | C200F9181C07078900481943 /* PBXTargetDependency */, 192 | ); 193 | name = TemplateLayoutDemoTests; 194 | productName = TemplateLayoutDemoTests; 195 | productReference = C200F9161C07078900481943 /* TemplateLayoutDemoTests.xctest */; 196 | productType = "com.apple.product-type.bundle.unit-test"; 197 | }; 198 | C200F9201C07078900481943 /* TemplateLayoutDemoUITests */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = C200F9301C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemoUITests" */; 201 | buildPhases = ( 202 | C200F91D1C07078900481943 /* Sources */, 203 | C200F91E1C07078900481943 /* Frameworks */, 204 | C200F91F1C07078900481943 /* Resources */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | C200F9231C07078900481943 /* PBXTargetDependency */, 210 | ); 211 | name = TemplateLayoutDemoUITests; 212 | productName = TemplateLayoutDemoUITests; 213 | productReference = C200F9211C07078900481943 /* TemplateLayoutDemoUITests.xctest */; 214 | productType = "com.apple.product-type.bundle.ui-testing"; 215 | }; 216 | /* End PBXNativeTarget section */ 217 | 218 | /* Begin PBXProject section */ 219 | C200F8FA1C07078900481943 /* Project object */ = { 220 | isa = PBXProject; 221 | attributes = { 222 | LastUpgradeCheck = 0700; 223 | ORGANIZATIONNAME = zhaodg; 224 | TargetAttributes = { 225 | C200F9011C07078900481943 = { 226 | CreatedOnToolsVersion = 7.0.1; 227 | DevelopmentTeam = 4JDGGKN384; 228 | }; 229 | C200F9151C07078900481943 = { 230 | CreatedOnToolsVersion = 7.0.1; 231 | TestTargetID = C200F9011C07078900481943; 232 | }; 233 | C200F9201C07078900481943 = { 234 | CreatedOnToolsVersion = 7.0.1; 235 | TestTargetID = C200F9011C07078900481943; 236 | }; 237 | }; 238 | }; 239 | buildConfigurationList = C200F8FD1C07078900481943 /* Build configuration list for PBXProject "TemplateLayoutDemo" */; 240 | compatibilityVersion = "Xcode 3.2"; 241 | developmentRegion = English; 242 | hasScannedForEncodings = 0; 243 | knownRegions = ( 244 | en, 245 | Base, 246 | ); 247 | mainGroup = C200F8F91C07078900481943; 248 | productRefGroup = C200F9031C07078900481943 /* Products */; 249 | projectDirPath = ""; 250 | projectRoot = ""; 251 | targets = ( 252 | C200F9011C07078900481943 /* TemplateLayoutDemo */, 253 | C200F9151C07078900481943 /* TemplateLayoutDemoTests */, 254 | C200F9201C07078900481943 /* TemplateLayoutDemoUITests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | C200F9001C07078900481943 /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | C200F9451C07399A00481943 /* LaunchScreen.xib in Resources */, 265 | C200F90D1C07078900481943 /* Assets.xcassets in Resources */, 266 | C200F90B1C07078900481943 /* Main.storyboard in Resources */, 267 | C200F93F1C0709A300481943 /* Data.json in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | C200F9141C07078900481943 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | C200F91F1C07078900481943 /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXResourcesBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | C200F8FE1C07078900481943 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | C200F93C1C0707A500481943 /* DGTemplateLayoutCell.swift in Sources */, 293 | C200F9411C0709E700481943 /* DGFeedItem.swift in Sources */, 294 | C200F93B1C0707A500481943 /* DGTableViewExtension.swift in Sources */, 295 | C200F9081C07078900481943 /* ViewController.swift in Sources */, 296 | C200F9431C0709F300481943 /* DGFeedCell.swift in Sources */, 297 | C200F9391C0707A500481943 /* DGIndexPathHeightCache.swift in Sources */, 298 | C200F93D1C0707A500481943 /* DGKeyedHeightCache.swift in Sources */, 299 | C200F93A1C0707A500481943 /* DGTemplateLayoutellDebug.swift in Sources */, 300 | C200F9061C07078900481943 /* AppDelegate.swift in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | C200F9121C07078900481943 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | C200F91B1C07078900481943 /* TemplateLayoutDemoTests.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | C200F91D1C07078900481943 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | C200F9261C07078900481943 /* TemplateLayoutDemoUITests.swift in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXSourcesBuildPhase section */ 321 | 322 | /* Begin PBXTargetDependency section */ 323 | C200F9181C07078900481943 /* PBXTargetDependency */ = { 324 | isa = PBXTargetDependency; 325 | target = C200F9011C07078900481943 /* TemplateLayoutDemo */; 326 | targetProxy = C200F9171C07078900481943 /* PBXContainerItemProxy */; 327 | }; 328 | C200F9231C07078900481943 /* PBXTargetDependency */ = { 329 | isa = PBXTargetDependency; 330 | target = C200F9011C07078900481943 /* TemplateLayoutDemo */; 331 | targetProxy = C200F9221C07078900481943 /* PBXContainerItemProxy */; 332 | }; 333 | /* End PBXTargetDependency section */ 334 | 335 | /* Begin PBXVariantGroup section */ 336 | C200F9091C07078900481943 /* Main.storyboard */ = { 337 | isa = PBXVariantGroup; 338 | children = ( 339 | C200F90A1C07078900481943 /* Base */, 340 | ); 341 | name = Main.storyboard; 342 | sourceTree = ""; 343 | }; 344 | /* End PBXVariantGroup section */ 345 | 346 | /* Begin XCBuildConfiguration section */ 347 | C200F9281C07078900481943 /* Debug */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | ALWAYS_SEARCH_USER_PATHS = NO; 351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 352 | CLANG_CXX_LIBRARY = "libc++"; 353 | CLANG_ENABLE_MODULES = YES; 354 | CLANG_ENABLE_OBJC_ARC = YES; 355 | CLANG_WARN_BOOL_CONVERSION = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 358 | CLANG_WARN_EMPTY_BODY = YES; 359 | CLANG_WARN_ENUM_CONVERSION = YES; 360 | CLANG_WARN_INT_CONVERSION = YES; 361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 362 | CLANG_WARN_UNREACHABLE_CODE = YES; 363 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 364 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 365 | COPY_PHASE_STRIP = NO; 366 | DEBUG_INFORMATION_FORMAT = dwarf; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | GCC_C_LANGUAGE_STANDARD = gnu99; 370 | GCC_DYNAMIC_NO_PIC = NO; 371 | GCC_NO_COMMON_BLOCKS = YES; 372 | GCC_OPTIMIZATION_LEVEL = 0; 373 | GCC_PREPROCESSOR_DEFINITIONS = ( 374 | "DEBUG=1", 375 | "$(inherited)", 376 | ); 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 384 | MTL_ENABLE_DEBUG_INFO = YES; 385 | ONLY_ACTIVE_ARCH = YES; 386 | SDKROOT = iphoneos; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | C200F9291C07078900481943 /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INT_CONVERSION = YES; 406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 410 | COPY_PHASE_STRIP = NO; 411 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 412 | ENABLE_NS_ASSERTIONS = NO; 413 | ENABLE_STRICT_OBJC_MSGSEND = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 423 | MTL_ENABLE_DEBUG_INFO = NO; 424 | SDKROOT = iphoneos; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | VALIDATE_PRODUCT = YES; 427 | }; 428 | name = Release; 429 | }; 430 | C200F92B1C07078900481943 /* Debug */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 434 | CODE_SIGN_IDENTITY = "iPhone Developer"; 435 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 436 | INFOPLIST_FILE = TemplateLayoutDemo/Info.plist; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemo; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | PROVISIONING_PROFILE = ""; 442 | }; 443 | name = Debug; 444 | }; 445 | C200F92C1C07078900481943 /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 449 | CODE_SIGN_IDENTITY = "iPhone Developer"; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 451 | INFOPLIST_FILE = TemplateLayoutDemo/Info.plist; 452 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 454 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemo; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | PROVISIONING_PROFILE = ""; 457 | }; 458 | name = Release; 459 | }; 460 | C200F92E1C07078900481943 /* Debug */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | BUNDLE_LOADER = "$(TEST_HOST)"; 464 | INFOPLIST_FILE = TemplateLayoutDemoTests/Info.plist; 465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 466 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemoTests; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TemplateLayoutDemo.app/TemplateLayoutDemo"; 469 | }; 470 | name = Debug; 471 | }; 472 | C200F92F1C07078900481943 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | BUNDLE_LOADER = "$(TEST_HOST)"; 476 | INFOPLIST_FILE = TemplateLayoutDemoTests/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemoTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TemplateLayoutDemo.app/TemplateLayoutDemo"; 481 | }; 482 | name = Release; 483 | }; 484 | C200F9311C07078900481943 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | INFOPLIST_FILE = TemplateLayoutDemoUITests/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 489 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemoUITests; 490 | PRODUCT_NAME = "$(TARGET_NAME)"; 491 | TEST_TARGET_NAME = TemplateLayoutDemo; 492 | USES_XCTRUNNER = YES; 493 | }; 494 | name = Debug; 495 | }; 496 | C200F9321C07078900481943 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | INFOPLIST_FILE = TemplateLayoutDemoUITests/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 501 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaodg.TemplateLayoutDemoUITests; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | TEST_TARGET_NAME = TemplateLayoutDemo; 504 | USES_XCTRUNNER = YES; 505 | }; 506 | name = Release; 507 | }; 508 | /* End XCBuildConfiguration section */ 509 | 510 | /* Begin XCConfigurationList section */ 511 | C200F8FD1C07078900481943 /* Build configuration list for PBXProject "TemplateLayoutDemo" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | C200F9281C07078900481943 /* Debug */, 515 | C200F9291C07078900481943 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | C200F92A1C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemo" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | C200F92B1C07078900481943 /* Debug */, 524 | C200F92C1C07078900481943 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | C200F92D1C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemoTests" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | C200F92E1C07078900481943 /* Debug */, 533 | C200F92F1C07078900481943 /* Release */, 534 | ); 535 | defaultConfigurationIsVisible = 0; 536 | defaultConfigurationName = Release; 537 | }; 538 | C200F9301C07078900481943 /* Build configuration list for PBXNativeTarget "TemplateLayoutDemoUITests" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | C200F9311C07078900481943 /* Debug */, 542 | C200F9321C07078900481943 /* Release */, 543 | ); 544 | defaultConfigurationIsVisible = 0; 545 | defaultConfigurationName = Release; 546 | }; 547 | /* End XCConfigurationList section */ 548 | }; 549 | rootObject = C200F8FA1C07078900481943 /* Project object */; 550 | } 551 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/project.xcworkspace/xcuserdata/zhaodg.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/project.xcworkspace/xcuserdata/zhaodg.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/xcuserdata/zhaodg.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 12 | 13 | 14 | 16 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/xcuserdata/zhaodg.xcuserdatad/xcschemes/TemplateLayoutDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo.xcodeproj/xcuserdata/zhaodg.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TemplateLayoutDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C200F9011C07078900481943 16 | 17 | primary 18 | 19 | 20 | C200F9151C07078900481943 21 | 22 | primary 23 | 24 | 25 | C200F9201C07078900481943 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TemplateLayoutDemo 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/1.imageset/1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/1.imageset/1.jpeg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1.jpeg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/2.imageset/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/2.imageset/2.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "2.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/3.imageset/11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/3.imageset/11.jpeg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "11.jpeg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/4.imageset/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/4.imageset/4.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "4.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/5.imageset/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/5.imageset/5.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "5.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/6.imageset/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/6.imageset/6.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "6.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/7.imageset/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/7.imageset/7.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "7.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/8.imageset/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodg/DGTemplateLayoutCell/2bb6a46987cff55b0dc0dfa5db949e68de9d02d3/TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/8.imageset/22.jpg -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "22.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 66 | 73 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/DGFeedCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGFeedCell.swift 3 | // TemplateLayoutDemo 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DGFeedCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var contentLabel: UILabel! 15 | @IBOutlet weak var contentImageView: UIImageView! 16 | @IBOutlet weak var userNameLabel: UILabel! 17 | @IBOutlet weak var timeLabel: UILabel! 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | self.contentView.bounds = UIScreen.mainScreen().bounds 22 | } 23 | 24 | func loadData(item: DGFeedItem) { 25 | self.titleLabel.text = item.title 26 | self.contentLabel.text = item.content 27 | if item.imageName.characters.count > 0 { 28 | self.contentImageView.image = UIImage(named: item.imageName) 29 | } 30 | self.userNameLabel.text = item.userName 31 | self.timeLabel.text = item.time 32 | } 33 | 34 | // If you are not using auto layout, override this method, enable it by setting 35 | // "fd_enforceFrameLayout" to YES. 36 | override func sizeThatFits(size: CGSize) -> CGSize { 37 | var height: CGFloat = 0 38 | height += self.contentLabel.sizeThatFits(size).height 39 | height += self.contentLabel.sizeThatFits(size).height 40 | height += self.contentImageView.sizeThatFits(size).height 41 | height += self.userNameLabel.sizeThatFits(size).height 42 | height += self.timeLabel.sizeThatFits(size).height 43 | return CGSizeMake(size.width, height) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/DGFeedItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DGFeedItem.swift 3 | // TemplateLayoutDemo 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | var countDGFeedItemIdentifier: Int = 1 12 | 13 | struct DGFeedItem { 14 | 15 | var identifier: String 16 | let title: String 17 | let content: String 18 | let userName: String 19 | let time: String 20 | let imageName: String 21 | 22 | init(dict: NSDictionary) { 23 | countDGFeedItemIdentifier += 1 24 | identifier = "unique_id_\(countDGFeedItemIdentifier)" 25 | // dict["identifier"] as! String 26 | title = dict["title"] as! String 27 | content = dict["content"] as! String 28 | userName = dict["username"] as! String 29 | time = dict["time"] as! String 30 | imageName = dict["imageName"] as! String 31 | } 32 | } -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Data.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": [ 3 | { 4 | "identifier": "1", 5 | "title": "Hello world", 6 | "content": "DGTemplateLayout is modified from FDTemplateLayout And writing in Swift 2.0.", 7 | "username": "zhaodg", 8 | "time": "2015.11.26", 9 | "imageName": "1" 10 | }, 11 | { 12 | "identifier": "2", 13 | "title": "Thanks sunnyxx", 14 | "content": "修改FDTemplateLayout,并用Swift 2.0实现,50-60%的逻辑一样,测试demo的逻辑完全和sunnyxx一样,只不过是用swift实现,并用于测试", 15 | "username": "zhaodg", 16 | "time": "2015.11.26", 17 | "imageName": "2" 18 | }, 19 | { 20 | "identifier": "3", 21 | "title": "William Shakespear", 22 | "content": "Good name in man and woman, dear my lord, is the immediate jewel of their souls: Who steals my purse steals trash; ’tis something, nothing. (Othello 3.3)", 23 | "username": "sunnyxx", 24 | "time": "2015.04.15", 25 | "imageName": "3" 26 | }, 27 | { 28 | "identifier": "4", 29 | "title": "hah", 30 | "content": "happy ~~~~", 31 | "username": "zhaodg", 32 | "time": "2015.04.15", 33 | "imageName": "4" 34 | }, 35 | { 36 | "identifier": "5", 37 | "title": "~~~", 38 | "content": "Good", 39 | "username": "xxx", 40 | "time": "2015.04.12", 41 | "imageName": "5" 42 | }, 43 | { 44 | "identifier": "6", 45 | "title": "title!", 46 | "content": "To be, or not to be —that is the question, Whether'tis nobler in the mind to suffer. The slings and arrows of outrageous fortune Or to take arms against a sea of troubles, And by opposing end them. To die —to sleep", 47 | "username": "user1", 48 | "time": "2015.04.16", 49 | "imageName": "6" 50 | }, 51 | { 52 | "identifier": "7", 53 | "title": "nice", 54 | "content": "", 55 | "username": "xmnf", 56 | "time": "2015.04.16", 57 | "imageName": "7" 58 | }, 59 | { 60 | "identifier": "8", 61 | "title": "", 62 | "content": "Things base and vile, holding no quantity, love can transpose to from and dignity: love looks not with the eyes, but with mind. (A Midsummer Night’s Dream 1.1)", 63 | "username": "zhaodg", 64 | "time": "2015.04.17", 65 | "imageName": "8" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TemplateLayoutDemo 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UITableViewController { 12 | 13 | var feedList: [[DGFeedItem]] = [] 14 | var jsonData: [DGFeedItem] = [] 15 | 16 | @IBOutlet weak var cacheModeSegmentControl: UISegmentedControl! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | self.tableView.dg_debugLogEnabled = true 22 | 23 | self.cacheModeSegmentControl.selectedSegmentIndex = 1 24 | 25 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in 26 | let path: String = NSBundle.mainBundle().pathForResource("Data", ofType: "json")! 27 | let data: NSData = NSData(contentsOfFile: path)! 28 | do { 29 | let dict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) 30 | let array: [AnyObject] = dict["feed"] as! [AnyObject] 31 | for item in array { 32 | if let item = item as? NSDictionary { 33 | self.jsonData.append(DGFeedItem(dict: item)) 34 | } 35 | } 36 | } catch { 37 | print("serialization failed!!!") 38 | } 39 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 40 | self.feedList.append(self.jsonData) 41 | self.tableView.reloadData() 42 | }) 43 | } 44 | } 45 | 46 | @IBAction func refreshControlAction(sender: UIRefreshControl) { 47 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))) 48 | dispatch_after(delayTime, dispatch_get_main_queue()) { 49 | self.feedList.removeAll() 50 | self.feedList.append(self.jsonData) 51 | self.tableView.reloadData() 52 | sender.endRefreshing() 53 | } 54 | } 55 | 56 | @IBAction func rightNavigationItemAction(sender: AnyObject) { 57 | let alert = UIAlertController(title: "Action", message: "message", preferredStyle: .ActionSheet) 58 | alert.modalPresentationStyle = .Popover 59 | let action1 = UIAlertAction(title: "Insert a row", style: .Destructive) { action in 60 | self.inserRow() 61 | } 62 | let action2 = UIAlertAction(title: "Insert a section", style: .Destructive) { action in 63 | self.insertSection() 64 | } 65 | let action3 = UIAlertAction(title: "Delete a section", style: .Destructive) { action in 66 | self.deleteSection() 67 | } 68 | let action4 = UIAlertAction(title: "Insert Rows At Index Paths", style: .Destructive) { action in 69 | self.insertRowsAtIndexPaths() 70 | } 71 | let action5 = UIAlertAction(title: "Delete Rows At Index Paths", style: .Destructive) { action in 72 | self.deleteRowsAtIndexPaths() 73 | } 74 | 75 | let cancel = UIAlertAction(title: "Cancel", style: .Cancel) { action in 76 | 77 | } 78 | 79 | alert.addAction(cancel) 80 | alert.addAction(action1) 81 | alert.addAction(action2) 82 | alert.addAction(action3) 83 | alert.addAction(action4) 84 | alert.addAction(action5) 85 | 86 | self.presentViewController(alert, animated: true, completion: nil) 87 | } 88 | 89 | func randomItem() -> DGFeedItem { 90 | let randomNumber: Int = Int(arc4random_uniform(UInt32(self.jsonData.count))) 91 | var item: DGFeedItem = self.jsonData[randomNumber] 92 | countDGFeedItemIdentifier += 1 93 | item.identifier = "unique-id-\(countDGFeedItemIdentifier)" 94 | return item 95 | } 96 | 97 | func inserRow() { 98 | if self.feedList.count == 0 { 99 | self.insertSection() 100 | } else { 101 | self.feedList[0].insert(self.randomItem(), atIndex: 0) 102 | let indexPath: NSIndexPath = NSIndexPath(forRow: 0, inSection: 0) 103 | self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) 104 | } 105 | } 106 | 107 | func insertSection() { 108 | self.feedList.insert([self.randomItem()], atIndex: 0) 109 | self.tableView.insertSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Automatic) 110 | } 111 | 112 | func deleteSection() { 113 | if self.feedList.count > 0 { 114 | self.feedList.removeAtIndex(0) 115 | self.tableView.deleteSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Automatic) 116 | } 117 | } 118 | 119 | func insertRowsAtIndexPaths() { 120 | // demo 以section 0 为例子 121 | let section = 0 122 | if self.feedList.count == 0 { 123 | self.feedList.append([]) 124 | } 125 | let lastIndex = self.feedList[section].count 126 | let insertItems = [self.randomItem(), self.randomItem(), self.randomItem(), self.randomItem(), self.randomItem()] 127 | for item in insertItems { 128 | self.feedList[section].append(item) 129 | } 130 | 131 | var indexPaths: [NSIndexPath] = [] 132 | for index in 0.. section + 1 && self.feedList[section].count > 0 else { return } 149 | 150 | let row = self.feedList[section].count - 1 151 | let indexPath = NSIndexPath(forRow: row, inSection: section) 152 | self.feedList[section].removeLast() 153 | 154 | guard self.tableView.numberOfSections > section + 1 else { return } 155 | 156 | self.tableView.beginUpdates() 157 | if self.tableView.numberOfRowsInSection(section) > 1 { 158 | self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) 159 | } else { 160 | self.feedList.removeAtIndex(section) 161 | self.tableView.deleteSections(NSIndexSet(index: section), withRowAnimation: .Automatic) 162 | } 163 | self.tableView.endUpdates() 164 | } 165 | } 166 | 167 | // MARK: - UITableViewDelegate 168 | extension ViewController { 169 | 170 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 171 | return self.feedList.count 172 | } 173 | 174 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 175 | return self.feedList[section].count 176 | } 177 | 178 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 179 | let cell: DGFeedCell = tableView.dequeueReusableCellWithIdentifier("DGFeedCell", forIndexPath: indexPath) as! DGFeedCell 180 | if indexPath.row % 2 == 0 { 181 | cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator 182 | } else { 183 | cell.accessoryType = UITableViewCellAccessoryType.Checkmark 184 | } 185 | cell.loadData(self.feedList[indexPath.section][indexPath.row]) 186 | cell.setNeedsUpdateConstraints() 187 | cell.updateConstraintsIfNeeded() 188 | return cell ?? UITableViewCell() 189 | } 190 | 191 | } 192 | 193 | // MARK: - UITableViewDelegate 194 | extension ViewController { 195 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 196 | let mode: Int = self.cacheModeSegmentControl.selectedSegmentIndex 197 | switch mode { 198 | case 0: 199 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", configuration: { (cell) -> Void in 200 | let cell = cell as! DGFeedCell 201 | cell.loadData(self.feedList[indexPath.section][indexPath.row]) 202 | }) 203 | case 1: 204 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", indexPath: indexPath,configuration: { (cell) -> Void in 205 | let cell = cell as! DGFeedCell 206 | cell.loadData(self.feedList[indexPath.section][indexPath.row]) 207 | }) 208 | case 2: 209 | return tableView.dg_heightForCellWithIdentifier("DGFeedCell", key: self.feedList[indexPath.section][indexPath.row].identifier, configuration: { (cell) -> Void in 210 | let cell = cell as! DGFeedCell 211 | cell.loadData(self.feedList[indexPath.section][indexPath.row]) 212 | }) 213 | default: 214 | return 0 215 | } 216 | } 217 | 218 | override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 219 | if editingStyle == UITableViewCellEditingStyle.Delete { 220 | self.feedList[indexPath.section].removeAtIndex(indexPath.row) 221 | self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) 222 | } 223 | } 224 | } 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemoTests/TemplateLayoutDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemplateLayoutDemoTests.swift 3 | // TemplateLayoutDemoTests 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TemplateLayoutDemo 11 | 12 | class TemplateLayoutDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TemplateLayoutDemo/TemplateLayoutDemoUITests/TemplateLayoutDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemplateLayoutDemoUITests.swift 3 | // TemplateLayoutDemoUITests 4 | // 5 | // Created by zhaodg on 11/26/15. 6 | // Copyright © 2015 zhaodg. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class TemplateLayoutDemoUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // 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. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------