├── README.md ├── TableStickyViewExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── yusufdemirci.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── TableStickyViewExample ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── header.imageset │ ├── Contents.json │ └── new_york_view-wallpaper-1920x1080.jpg ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── MyHeaderView.swift ├── MyHeaderView.xib ├── ViewController.swift └── sticky header ├── StickyHeader.swift └── StickyHeaderView.swift /README.md: -------------------------------------------------------------------------------- 1 | ![Demo](https://media.giphy.com/media/gk2oG7walw85arhFhv/giphy.gif) 2 | 3 | ## Usage 4 | 5 | #### Add this 2 classes to your project 6 | 7 | ##### StickyHeader 8 | 9 | ```sh 10 | public class StickyHeader: NSObject { 11 | 12 | /** 13 | The view containing the provided header. 14 | */ 15 | private(set) lazy var contentView: StickyHeaderView = { 16 | let view = StickyHeaderView() 17 | view.parent = self 18 | view.clipsToBounds = true 19 | return view 20 | }() 21 | 22 | private weak var _scrollView: UIScrollView? 23 | 24 | /** 25 | The `UIScrollView` attached to the sticky header. 26 | */ 27 | public weak var scrollView: UIScrollView? { 28 | get { 29 | return _scrollView 30 | } 31 | 32 | set { 33 | if _scrollView != newValue { 34 | _scrollView = newValue 35 | 36 | if let scrollView = scrollView { 37 | self.adjustScrollViewTopInset(top: scrollView.contentInset.top + self.height) 38 | scrollView.addSubview(self.contentView) 39 | } 40 | 41 | self.layoutContentView() 42 | } 43 | } 44 | } 45 | 46 | private var _view: UIView? 47 | 48 | /** 49 | The `UIScrollView attached to the sticky header. 50 | */ 51 | public var view: UIView? { 52 | set { 53 | guard newValue != _view else { return } 54 | _view = newValue 55 | updateConstraints() 56 | } 57 | get { 58 | return _view 59 | } 60 | } 61 | 62 | private var _height: CGFloat = 0 63 | 64 | /** 65 | The height of the header. 66 | */ 67 | public var height: CGFloat { 68 | get { return _height } 69 | set { 70 | guard newValue != _height else { return } 71 | 72 | if let scrollView = self.scrollView { 73 | self.adjustScrollViewTopInset(top: scrollView.contentInset.top - height + newValue) 74 | } 75 | 76 | _height = newValue 77 | 78 | self.updateConstraints() 79 | self.layoutContentView() 80 | 81 | } 82 | } 83 | private var _minimumHeight: CGFloat = 0 84 | 85 | /** 86 | The minimum height of the header. 87 | */ 88 | public var minimumHeight: CGFloat { 89 | get { return _minimumHeight } 90 | set { 91 | _minimumHeight = newValue 92 | layoutContentView() 93 | } 94 | } 95 | 96 | private func adjustScrollViewTopInset(top: CGFloat) { 97 | 98 | guard let scrollView = self.scrollView else { return } 99 | var inset = scrollView.contentInset 100 | 101 | //Adjust content offset 102 | var offset = scrollView.contentOffset 103 | offset.y += inset.top - top 104 | scrollView.contentOffset = offset 105 | 106 | //Adjust content inset 107 | inset.top = top 108 | scrollView.contentInset = inset 109 | 110 | self.scrollView = scrollView 111 | } 112 | private func updateConstraints() { 113 | guard let view = self.view else { return } 114 | 115 | view.removeFromSuperview() 116 | self.contentView.addSubview(view) 117 | 118 | view.translatesAutoresizingMaskIntoConstraints = false 119 | 120 | self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: [], metrics: nil, views: ["v": view])) 121 | 122 | self.contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)) 123 | 124 | self.contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.height)) 125 | 126 | } 127 | 128 | private func layoutContentView() { 129 | var relativeYOffset: CGFloat = 0 130 | 131 | guard let scrollView = self.scrollView else { return } 132 | 133 | if scrollView.contentOffset.y < -self.minimumHeight { 134 | relativeYOffset = -self.height 135 | } else { 136 | 137 | let compensation: CGFloat = -self.minimumHeight - scrollView.contentOffset.y 138 | relativeYOffset = -self.height - compensation 139 | } 140 | 141 | let frame = CGRect(x: 0, y: relativeYOffset, width: scrollView.frame.size.width, height: height) 142 | 143 | self.contentView.frame = frame 144 | } 145 | 146 | public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 147 | if let path = keyPath, context == &StickyHeaderView.KVOContext && path == "contentOffset" { 148 | self.layoutContentView() 149 | } else { 150 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | ##### StickyHeaderView 157 | 158 | ```sh 159 | internal class StickyHeaderView: UIView { 160 | weak var parent: StickyHeader? 161 | 162 | internal static var KVOContext = 0 163 | 164 | override func willMove(toSuperview view: UIView?) { 165 | if let view = self.superview, view.isKind(of:UIScrollView.self), let parent = self.parent { 166 | view.removeObserver(parent, forKeyPath: "contentOffset", context: &StickyHeaderView.KVOContext) 167 | } 168 | } 169 | 170 | override func didMoveToSuperview() { 171 | if let view = self.superview, view.isKind(of:UIScrollView.self), let parent = parent { 172 | view.addObserver(parent, forKeyPath: "contentOffset", options: .new, context: &StickyHeaderView.KVOContext) 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | #### And extension 179 | 180 | ##### UIScrollViewExtension 181 | 182 | ```sh 183 | private var xoStickyHeaderKey: UInt8 = 0 184 | extension UIScrollView { 185 | 186 | public var stickyHeader: StickyHeader! { 187 | 188 | get { 189 | var header = objc_getAssociatedObject(self, &xoStickyHeaderKey) as? StickyHeader 190 | 191 | if header == nil { 192 | header = StickyHeader() 193 | header!.scrollView = self 194 | objc_setAssociatedObject(self, &xoStickyHeaderKey, header, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 195 | } 196 | return header! 197 | } 198 | } 199 | } 200 | ``` 201 | 202 | #### Integration 203 | 204 | ```sh 205 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 206 | 207 | // MARK: Outlets 208 | @IBOutlet weak var table: UITableView! 209 | 210 | // MARK: Properties 211 | var navigationView = UIView() 212 | 213 | override func viewDidLoad() { 214 | super.viewDidLoad() 215 | 216 | table.delegate = self 217 | table.dataSource = self 218 | 219 | let headerView = Bundle.main.loadNibNamed("MyHeaderView", owner: nil, options: nil)?.first as! MyHeaderView 220 | 221 | table.stickyHeader.view = headerView 222 | table.stickyHeader.height = headerView.frame.height 223 | table.stickyHeader.minimumHeight = 64 224 | 225 | navigationView.frame = CGRect(x: 0, y: 0, width: headerView.frame.width, height: headerView.frame.height) 226 | navigationView.backgroundColor = .green 227 | navigationView.alpha = 0 228 | table.stickyHeader.view = navigationView 229 | } 230 | 231 | // MARK: Tableview 232 | func numberOfSections(in tableView: UITableView) -> Int { 233 | return 1 234 | } 235 | 236 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 237 | return 20 238 | } 239 | 240 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 241 | return 44 242 | } 243 | 244 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 245 | return tableView.dequeueReusableCell(withIdentifier: "tableCell")! 246 | } 247 | 248 | // MARK: Scrollview 249 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 250 | 251 | let offset = scrollView.contentOffset.y 252 | 253 | let changeStartOffset: CGFloat = -180 254 | let changeSpeed: CGFloat = 100 255 | navigationView.alpha = min(1.0, (offset - changeStartOffset) / changeSpeed) 256 | } 257 | } 258 | ``` 259 | -------------------------------------------------------------------------------- /TableStickyViewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C9B248BC1FCB067A00F56D70 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B248BB1FCB067A00F56D70 /* AppDelegate.swift */; }; 11 | C9B248BE1FCB067A00F56D70 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B248BD1FCB067A00F56D70 /* ViewController.swift */; }; 12 | C9B248C11FCB067A00F56D70 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9B248BF1FCB067A00F56D70 /* Main.storyboard */; }; 13 | C9B248C31FCB067A00F56D70 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9B248C21FCB067A00F56D70 /* Assets.xcassets */; }; 14 | C9B248C61FCB067A00F56D70 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9B248C41FCB067A00F56D70 /* LaunchScreen.storyboard */; }; 15 | C9B248CE1FCB09B900F56D70 /* StickyHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B248CD1FCB09B900F56D70 /* StickyHeader.swift */; }; 16 | C9B248D01FCB09D400F56D70 /* StickyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B248CF1FCB09D400F56D70 /* StickyHeaderView.swift */; }; 17 | C9B248D31FCB09FE00F56D70 /* MyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B248D21FCB09FE00F56D70 /* MyHeaderView.swift */; }; 18 | C9B248D51FCB0A1700F56D70 /* MyHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9B248D41FCB0A1700F56D70 /* MyHeaderView.xib */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | C9B248B81FCB067A00F56D70 /* TableStickyViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableStickyViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | C9B248BB1FCB067A00F56D70 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | C9B248BD1FCB067A00F56D70 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | C9B248C01FCB067A00F56D70 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | C9B248C21FCB067A00F56D70 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | C9B248C51FCB067A00F56D70 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | C9B248C71FCB067A00F56D70 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | C9B248CD1FCB09B900F56D70 /* StickyHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeader.swift; sourceTree = ""; }; 30 | C9B248CF1FCB09D400F56D70 /* StickyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderView.swift; sourceTree = ""; }; 31 | C9B248D21FCB09FE00F56D70 /* MyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyHeaderView.swift; sourceTree = ""; }; 32 | C9B248D41FCB0A1700F56D70 /* MyHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyHeaderView.xib; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | C9B248B51FCB067A00F56D70 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | C9B248AF1FCB067A00F56D70 = { 47 | isa = PBXGroup; 48 | children = ( 49 | C9B248BA1FCB067A00F56D70 /* TableStickyViewExample */, 50 | C9B248B91FCB067A00F56D70 /* Products */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | C9B248B91FCB067A00F56D70 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | C9B248B81FCB067A00F56D70 /* TableStickyViewExample.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | C9B248BA1FCB067A00F56D70 /* TableStickyViewExample */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | C9B248D11FCB09DA00F56D70 /* sticky header */, 66 | C9B248BB1FCB067A00F56D70 /* AppDelegate.swift */, 67 | C9B248BD1FCB067A00F56D70 /* ViewController.swift */, 68 | C9B248BF1FCB067A00F56D70 /* Main.storyboard */, 69 | C9B248C21FCB067A00F56D70 /* Assets.xcassets */, 70 | C9B248C41FCB067A00F56D70 /* LaunchScreen.storyboard */, 71 | C9B248C71FCB067A00F56D70 /* Info.plist */, 72 | C9B248D21FCB09FE00F56D70 /* MyHeaderView.swift */, 73 | C9B248D41FCB0A1700F56D70 /* MyHeaderView.xib */, 74 | ); 75 | path = TableStickyViewExample; 76 | sourceTree = ""; 77 | }; 78 | C9B248D11FCB09DA00F56D70 /* sticky header */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | C9B248CD1FCB09B900F56D70 /* StickyHeader.swift */, 82 | C9B248CF1FCB09D400F56D70 /* StickyHeaderView.swift */, 83 | ); 84 | path = "sticky header"; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | C9B248B71FCB067A00F56D70 /* TableStickyViewExample */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = C9B248CA1FCB067A00F56D70 /* Build configuration list for PBXNativeTarget "TableStickyViewExample" */; 93 | buildPhases = ( 94 | C9B248B41FCB067A00F56D70 /* Sources */, 95 | C9B248B51FCB067A00F56D70 /* Frameworks */, 96 | C9B248B61FCB067A00F56D70 /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = TableStickyViewExample; 103 | productName = TableStickyViewExample; 104 | productReference = C9B248B81FCB067A00F56D70 /* TableStickyViewExample.app */; 105 | productType = "com.apple.product-type.application"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | C9B248B01FCB067A00F56D70 /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastSwiftUpdateCheck = 0910; 114 | LastUpgradeCheck = 0910; 115 | ORGANIZATIONNAME = demirciy; 116 | TargetAttributes = { 117 | C9B248B71FCB067A00F56D70 = { 118 | CreatedOnToolsVersion = 9.1; 119 | ProvisioningStyle = Automatic; 120 | }; 121 | }; 122 | }; 123 | buildConfigurationList = C9B248B31FCB067A00F56D70 /* Build configuration list for PBXProject "TableStickyViewExample" */; 124 | compatibilityVersion = "Xcode 8.0"; 125 | developmentRegion = en; 126 | hasScannedForEncodings = 0; 127 | knownRegions = ( 128 | en, 129 | Base, 130 | ); 131 | mainGroup = C9B248AF1FCB067A00F56D70; 132 | productRefGroup = C9B248B91FCB067A00F56D70 /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | C9B248B71FCB067A00F56D70 /* TableStickyViewExample */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXResourcesBuildPhase section */ 142 | C9B248B61FCB067A00F56D70 /* Resources */ = { 143 | isa = PBXResourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | C9B248C61FCB067A00F56D70 /* LaunchScreen.storyboard in Resources */, 147 | C9B248C31FCB067A00F56D70 /* Assets.xcassets in Resources */, 148 | C9B248C11FCB067A00F56D70 /* Main.storyboard in Resources */, 149 | C9B248D51FCB0A1700F56D70 /* MyHeaderView.xib in Resources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | C9B248B41FCB067A00F56D70 /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | C9B248D31FCB09FE00F56D70 /* MyHeaderView.swift in Sources */, 161 | C9B248D01FCB09D400F56D70 /* StickyHeaderView.swift in Sources */, 162 | C9B248CE1FCB09B900F56D70 /* StickyHeader.swift in Sources */, 163 | C9B248BE1FCB067A00F56D70 /* ViewController.swift in Sources */, 164 | C9B248BC1FCB067A00F56D70 /* AppDelegate.swift in Sources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXSourcesBuildPhase section */ 169 | 170 | /* Begin PBXVariantGroup section */ 171 | C9B248BF1FCB067A00F56D70 /* Main.storyboard */ = { 172 | isa = PBXVariantGroup; 173 | children = ( 174 | C9B248C01FCB067A00F56D70 /* Base */, 175 | ); 176 | name = Main.storyboard; 177 | sourceTree = ""; 178 | }; 179 | C9B248C41FCB067A00F56D70 /* LaunchScreen.storyboard */ = { 180 | isa = PBXVariantGroup; 181 | children = ( 182 | C9B248C51FCB067A00F56D70 /* Base */, 183 | ); 184 | name = LaunchScreen.storyboard; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXVariantGroup section */ 188 | 189 | /* Begin XCBuildConfiguration section */ 190 | C9B248C81FCB067A00F56D70 /* Debug */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | ALWAYS_SEARCH_USER_PATHS = NO; 194 | CLANG_ANALYZER_NONNULL = YES; 195 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 196 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 197 | CLANG_CXX_LIBRARY = "libc++"; 198 | CLANG_ENABLE_MODULES = YES; 199 | CLANG_ENABLE_OBJC_ARC = YES; 200 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 201 | CLANG_WARN_BOOL_CONVERSION = YES; 202 | CLANG_WARN_COMMA = YES; 203 | CLANG_WARN_CONSTANT_CONVERSION = YES; 204 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 205 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 206 | CLANG_WARN_EMPTY_BODY = YES; 207 | CLANG_WARN_ENUM_CONVERSION = YES; 208 | CLANG_WARN_INFINITE_RECURSION = YES; 209 | CLANG_WARN_INT_CONVERSION = YES; 210 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 211 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 214 | CLANG_WARN_STRICT_PROTOTYPES = YES; 215 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 216 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 217 | CLANG_WARN_UNREACHABLE_CODE = YES; 218 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 219 | CODE_SIGN_IDENTITY = "iPhone Developer"; 220 | COPY_PHASE_STRIP = NO; 221 | DEBUG_INFORMATION_FORMAT = dwarf; 222 | ENABLE_STRICT_OBJC_MSGSEND = YES; 223 | ENABLE_TESTABILITY = YES; 224 | GCC_C_LANGUAGE_STANDARD = gnu11; 225 | GCC_DYNAMIC_NO_PIC = NO; 226 | GCC_NO_COMMON_BLOCKS = YES; 227 | GCC_OPTIMIZATION_LEVEL = 0; 228 | GCC_PREPROCESSOR_DEFINITIONS = ( 229 | "DEBUG=1", 230 | "$(inherited)", 231 | ); 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 239 | MTL_ENABLE_DEBUG_INFO = YES; 240 | ONLY_ACTIVE_ARCH = YES; 241 | SDKROOT = iphoneos; 242 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 243 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 244 | }; 245 | name = Debug; 246 | }; 247 | C9B248C91FCB067A00F56D70 /* Release */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_NONNULL = YES; 252 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_COMMA = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 270 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 271 | CLANG_WARN_STRICT_PROTOTYPES = YES; 272 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 273 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 274 | CLANG_WARN_UNREACHABLE_CODE = YES; 275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 276 | CODE_SIGN_IDENTITY = "iPhone Developer"; 277 | COPY_PHASE_STRIP = NO; 278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 279 | ENABLE_NS_ASSERTIONS = NO; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | GCC_C_LANGUAGE_STANDARD = gnu11; 282 | GCC_NO_COMMON_BLOCKS = YES; 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 290 | MTL_ENABLE_DEBUG_INFO = NO; 291 | SDKROOT = iphoneos; 292 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 293 | VALIDATE_PRODUCT = YES; 294 | }; 295 | name = Release; 296 | }; 297 | C9B248CB1FCB067A00F56D70 /* Debug */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 301 | CODE_SIGN_STYLE = Automatic; 302 | DEVELOPMENT_TEAM = WL6ERJVJSX; 303 | INFOPLIST_FILE = TableStickyViewExample/Info.plist; 304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 305 | PRODUCT_BUNDLE_IDENTIFIER = com.demirciy.TableStickyViewExample; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | SWIFT_VERSION = 4.0; 308 | TARGETED_DEVICE_FAMILY = "1,2"; 309 | }; 310 | name = Debug; 311 | }; 312 | C9B248CC1FCB067A00F56D70 /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 316 | CODE_SIGN_STYLE = Automatic; 317 | DEVELOPMENT_TEAM = WL6ERJVJSX; 318 | INFOPLIST_FILE = TableStickyViewExample/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 320 | PRODUCT_BUNDLE_IDENTIFIER = com.demirciy.TableStickyViewExample; 321 | PRODUCT_NAME = "$(TARGET_NAME)"; 322 | SWIFT_VERSION = 4.0; 323 | TARGETED_DEVICE_FAMILY = "1,2"; 324 | }; 325 | name = Release; 326 | }; 327 | /* End XCBuildConfiguration section */ 328 | 329 | /* Begin XCConfigurationList section */ 330 | C9B248B31FCB067A00F56D70 /* Build configuration list for PBXProject "TableStickyViewExample" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | C9B248C81FCB067A00F56D70 /* Debug */, 334 | C9B248C91FCB067A00F56D70 /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | C9B248CA1FCB067A00F56D70 /* Build configuration list for PBXNativeTarget "TableStickyViewExample" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | C9B248CB1FCB067A00F56D70 /* Debug */, 343 | C9B248CC1FCB067A00F56D70 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | /* End XCConfigurationList section */ 349 | }; 350 | rootObject = C9B248B01FCB067A00F56D70 /* Project object */; 351 | } 352 | -------------------------------------------------------------------------------- /TableStickyViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TableStickyViewExample.xcodeproj/xcuserdata/yusufdemirci.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TableStickyViewExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TableStickyViewExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TableStickyViewExample 4 | // 5 | // Created by Yusuf Demirci on 26.11.2017. 6 | // Copyright © 2017 demirciy. 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: [UIApplicationLaunchOptionsKey: Any]?) -> 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 invalidate graphics rendering callbacks. 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 active 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 | -------------------------------------------------------------------------------- /TableStickyViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TableStickyViewExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TableStickyViewExample/Assets.xcassets/header.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "new_york_view-wallpaper-1920x1080.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 | } -------------------------------------------------------------------------------- /TableStickyViewExample/Assets.xcassets/header.imageset/new_york_view-wallpaper-1920x1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyusufdemirci/TableStickyViewExample/396d65f5e48ea3ddfdf3322ff558de853461e0bb/TableStickyViewExample/Assets.xcassets/header.imageset/new_york_view-wallpaper-1920x1080.jpg -------------------------------------------------------------------------------- /TableStickyViewExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TableStickyViewExample/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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /TableStickyViewExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TableStickyViewExample/MyHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyHeaderView.swift 3 | // TableStickyViewExample 4 | // 5 | // Created by Yusuf Demirci on 26.11.2017. 6 | // Copyright © 2017 demirciy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MyHeaderView: UIView { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /TableStickyViewExample/MyHeaderView.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /TableStickyViewExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TableStickyViewExample 4 | // 5 | // Created by Yusuf Demirci on 26.11.2017. 6 | // Copyright © 2017 demirciy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private var xoStickyHeaderKey: UInt8 = 0 12 | extension UIScrollView { 13 | 14 | public var stickyHeader: StickyHeader! { 15 | 16 | get { 17 | var header = objc_getAssociatedObject(self, &xoStickyHeaderKey) as? StickyHeader 18 | 19 | if header == nil { 20 | header = StickyHeader() 21 | header!.scrollView = self 22 | objc_setAssociatedObject(self, &xoStickyHeaderKey, header, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 23 | } 24 | return header! 25 | } 26 | } 27 | } 28 | 29 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 30 | 31 | // MARK: Outlets 32 | @IBOutlet weak var table: UITableView! 33 | 34 | // MARK: Properties 35 | var navigationView = UIView() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | table.delegate = self 41 | table.dataSource = self 42 | 43 | let headerView = Bundle.main.loadNibNamed("MyHeaderView", owner: nil, options: nil)?.first as! MyHeaderView 44 | 45 | table.stickyHeader.view = headerView 46 | table.stickyHeader.height = headerView.frame.height 47 | table.stickyHeader.minimumHeight = 64 48 | 49 | navigationView.frame = CGRect(x: 0, y: 0, width: headerView.frame.width, height: headerView.frame.height) 50 | navigationView.backgroundColor = .green 51 | navigationView.alpha = 0 52 | table.stickyHeader.view = navigationView 53 | } 54 | 55 | // MARK: Tableview 56 | func numberOfSections(in tableView: UITableView) -> Int { 57 | return 1 58 | } 59 | 60 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 61 | return 20 62 | } 63 | 64 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 65 | return 44 66 | } 67 | 68 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 69 | return tableView.dequeueReusableCell(withIdentifier: "tableCell")! 70 | } 71 | 72 | // MARK: Scrollview 73 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 74 | 75 | let offset = scrollView.contentOffset.y 76 | 77 | let changeStartOffset: CGFloat = -180 78 | let changeSpeed: CGFloat = 100 79 | navigationView.alpha = min(1.0, (offset - changeStartOffset) / changeSpeed) 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /TableStickyViewExample/sticky header/StickyHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeader.swift 3 | // TableStickyViewExample 4 | // 5 | // Created by Yusuf Demirci on 26.11.2017. 6 | // Copyright © 2017 demirciy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class StickyHeader: NSObject { 12 | 13 | /** 14 | The view containing the provided header. 15 | */ 16 | private(set) lazy var contentView: StickyHeaderView = { 17 | let view = StickyHeaderView() 18 | view.parent = self 19 | view.clipsToBounds = true 20 | return view 21 | }() 22 | 23 | private weak var _scrollView: UIScrollView? 24 | 25 | /** 26 | The `UIScrollView` attached to the sticky header. 27 | */ 28 | public weak var scrollView: UIScrollView? { 29 | get { 30 | return _scrollView 31 | } 32 | 33 | set { 34 | if _scrollView != newValue { 35 | _scrollView = newValue 36 | 37 | if let scrollView = scrollView { 38 | self.adjustScrollViewTopInset(top: scrollView.contentInset.top + self.height) 39 | scrollView.addSubview(self.contentView) 40 | } 41 | 42 | self.layoutContentView() 43 | } 44 | } 45 | } 46 | 47 | private var _view: UIView? 48 | 49 | /** 50 | The `UIScrollView attached to the sticky header. 51 | */ 52 | public var view: UIView? { 53 | set { 54 | guard newValue != _view else { return } 55 | _view = newValue 56 | updateConstraints() 57 | } 58 | get { 59 | return _view 60 | } 61 | } 62 | 63 | private var _height: CGFloat = 0 64 | 65 | /** 66 | The height of the header. 67 | */ 68 | public var height: CGFloat { 69 | get { return _height } 70 | set { 71 | guard newValue != _height else { return } 72 | 73 | if let scrollView = self.scrollView { 74 | self.adjustScrollViewTopInset(top: scrollView.contentInset.top - height + newValue) 75 | } 76 | 77 | _height = newValue 78 | 79 | self.updateConstraints() 80 | self.layoutContentView() 81 | 82 | } 83 | } 84 | private var _minimumHeight: CGFloat = 0 85 | 86 | /** 87 | The minimum height of the header. 88 | */ 89 | public var minimumHeight: CGFloat { 90 | get { return _minimumHeight } 91 | set { 92 | _minimumHeight = newValue 93 | layoutContentView() 94 | } 95 | } 96 | 97 | private func adjustScrollViewTopInset(top: CGFloat) { 98 | 99 | guard let scrollView = self.scrollView else { return } 100 | var inset = scrollView.contentInset 101 | 102 | //Adjust content offset 103 | var offset = scrollView.contentOffset 104 | offset.y += inset.top - top 105 | scrollView.contentOffset = offset 106 | 107 | //Adjust content inset 108 | inset.top = top 109 | scrollView.contentInset = inset 110 | 111 | self.scrollView = scrollView 112 | } 113 | private func updateConstraints() { 114 | guard let view = self.view else { return } 115 | 116 | view.removeFromSuperview() 117 | self.contentView.addSubview(view) 118 | 119 | view.translatesAutoresizingMaskIntoConstraints = false 120 | 121 | self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: [], metrics: nil, views: ["v": view])) 122 | 123 | self.contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)) 124 | 125 | self.contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.height)) 126 | 127 | } 128 | 129 | private func layoutContentView() { 130 | var relativeYOffset: CGFloat = 0 131 | 132 | guard let scrollView = self.scrollView else { return } 133 | 134 | if scrollView.contentOffset.y < -self.minimumHeight { 135 | relativeYOffset = -self.height 136 | } else { 137 | 138 | let compensation: CGFloat = -self.minimumHeight - scrollView.contentOffset.y 139 | relativeYOffset = -self.height - compensation 140 | } 141 | 142 | let frame = CGRect(x: 0, y: relativeYOffset, width: scrollView.frame.size.width, height: height) 143 | 144 | self.contentView.frame = frame 145 | } 146 | 147 | public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 148 | if let path = keyPath, context == &StickyHeaderView.KVOContext && path == "contentOffset" { 149 | self.layoutContentView() 150 | } else { 151 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /TableStickyViewExample/sticky header/StickyHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeaderView.swift 3 | // TableStickyViewExample 4 | // 5 | // Created by Yusuf Demirci on 26.11.2017. 6 | // Copyright © 2017 demirciy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal class StickyHeaderView: UIView { 12 | weak var parent: StickyHeader? 13 | 14 | internal static var KVOContext = 0 15 | 16 | override func willMove(toSuperview view: UIView?) { 17 | if let view = self.superview, view.isKind(of:UIScrollView.self), let parent = self.parent { 18 | view.removeObserver(parent, forKeyPath: "contentOffset", context: &StickyHeaderView.KVOContext) 19 | } 20 | } 21 | 22 | override func didMoveToSuperview() { 23 | if let view = self.superview, view.isKind(of:UIScrollView.self), let parent = parent { 24 | view.addObserver(parent, forKeyPath: "contentOffset", options: .new, context: &StickyHeaderView.KVOContext) 25 | } 26 | } 27 | } 28 | --------------------------------------------------------------------------------