├── .gitignore ├── LICENSE ├── README.md ├── Screenshots ├── DefaultHeight.png ├── DeviceWidth500PreferredWidth270.png ├── DisableAutoresizeConstraints.png ├── FixedHeight.png ├── PreferredWidth270.png ├── ProperLayout320.png ├── ProperLayout500.png ├── SizeHeaderToFit.png └── Storyboard.png ├── TableViewHeader.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── TableViewHeader ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m └── TableViewHeaderTests ├── Info.plist └── TableViewHeaderTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | #AppCode 23 | *.idea 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dave Anderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TableViewHeader 2 | 3 | A demonstration of the techniques required to have the storyboard-defined tableHeaderView of a UITableView have dynamic height and width using AutoLayout. 4 | 5 | With a size-class enabled storyboard scene composed of a `UITableViewController` (or `UITableView` inside a `UIViewController`), drag a `UIView` such that it is above the first prototype cell. Arrange other views, including a UILabel within subviews. And define appropriate autolayout constraints such that these views should resize appropriately in the Resizeable Simulator. 6 | 7 | ![](Screenshots/Storyboard.png) 8 | 9 | The text of the `UILabel` is such that with a wide screen the text should fit on one line, but should wrap to two lines on a 320 point wide device. The label is also configured for `0` lines, which should allow the label to automatically wrap to the number of lines needed. 10 | 11 | One of the requirements of this sample is to hide or show the `TemporaryView` by adjusting the system of containts at runtime, and causing the `tableHeaderView` to be resized. 12 | 13 | Because we are modifying the constraints at runtime, the entry point for applying these layout-affecting constraint changes is `- (void)updateViewConstraints`. 14 | 15 | ## Issue #1 – Fixed Height 16 | 17 | A `UIView` added to a `UITableView` above the prototype cells gets treated as a `tableHeaderView` automatically, however the runtime `height` of this `tableHeaderView` is defined by the frame-rectangle of the view in the storyboard, even if the Intrinsic Size is defined to none. 18 | 19 | e.g. In the following screenshot the Frame Rectangle is defined to have a height of `100`, and the Intrinsic Size Height is set to `None`. At runtime the height of this `tableHeaderView` will be `100` regardless of the constraints of the contents. 20 | 21 | ![](Screenshots/DefaultHeight.png) 22 | 23 | Note: Runtime inspection indicates that the `tableHeaderView` has `translatesAutoresizingMaskIntoConstraints` set to `YES`. This is unexpected. 24 | 25 | At runtime the `NSAutoresizingMaskLayoutConstraints` triggered by the `translatesAutoresizingMaskIntoConstraints == YES` conflict with the system of constraints within the `tableHeaderView` and the runtime (non-deterministically?) discards one of the constraints, resulting in an undesirable layout. 26 | 27 | ``` 28 | 2014-08-28 07:53:52.932 TableViewHeader[1659:4451505] Unable to simultaneously satisfy constraints. 29 | Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 30 | ( 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "" 38 | ) 39 | 40 | Will attempt to recover by breaking constraint 41 | 42 | ``` 43 | 44 | ![](Screenshots/FixedHeight.png) 45 | 46 | It was anticipated that the text "The quick brown fox jumped over the lazy dog." would be visible within the label, however it is not because of the constraint `""`. 47 | 48 | ## Workaround #1 49 | 50 | A technique to workaround the fixed-height of the `tableHeaderView` is to send `systemLayoutSizeFittingSize:UILayoutFittingCompressedSize` to the `tableHeaderView` to calculate the compressed height and manually update the view's frame. This is accomplished using the following method (called within `- (void)updateViewConstraints`. 51 | 52 | ``` 53 | - (void)sizeHeaderToFit { 54 | UIView *header = self.tableView.tableHeaderView; 55 | 56 | [header setNeedsLayout]; 57 | [header layoutIfNeeded]; 58 | 59 | CGSize headerSize = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 60 | CGFloat height = headerSize.height; 61 | CGRect frame = header.frame; 62 | 63 | frame.size.height = height; 64 | header.frame = frame; 65 | 66 | self.tableView.tableHeaderView = header; 67 | } 68 | ``` 69 | 70 | This results in the following layout, but the conflicting constraints from above still exist. 71 | 72 | ![](Screenshots/SizeHeaderToFit.png) 73 | 74 | Note: Because we are using size classes to support adaptive layout `preferredMaxLayoutWidth` is set to automatic. 75 | 76 | If `preferredMaxLayoutWidth` is set to a value for a specific display width, we get a suitable layout, but *only* for that specific display width. 77 | 78 | e.g. Changing the `preferredMaxLayoutWidth` to `270` and using a `320` point width simulator, we get the following layout. 79 | 80 | ![](Screenshots/PreferredWidth270.png) 81 | 82 | Changing the resizable simulator width to `500` we get the following layout. 83 | 84 | ![](Screenshots/DeviceWidth500PreferredWidth270.png) 85 | 86 | At first glance this looks appropriate, except that the height of the label (bright red background) is sized as though it should contain two lines, even though it is layed out to use just one. (The padding above and below the single line of text is more than it should be.) 87 | 88 | We have no intention of updating `preferredMaxLayoutWidth` at runtime to contain a correct value and instead will continue by setting it back to automatic. 89 | 90 | ## Issue #2 – Conflicting Constraints 91 | 92 | With `preferredMaxLayoutWidth` is set to automatic and using `-sizeHeaderToFit` the layout looked correct, however there were layout conflicts with the constraint `""`. 93 | 94 | ## Workaround #2 95 | 96 | In order to avoid these conflicting constraints, at runtime we can use the following sequence. 97 | 98 | 1. Disable the `NSAutoresizingMaskLayoutConstraint` by setting `self.tableView.tableHeaderView.translatesAutoresizingMaskIntoConstraints = NO;` 99 | 2. Making our runtime constraint manipulations for the desired layout. 100 | 3. Call `-sizeHeaderToFit` to manually set the frame of the `tableHeaderView` 101 | 4. Reenable the `NSAutoresizingMaskLayoutConstraint` by setting `self.tableView.tableHeaderView.translatesAutoresizingMaskIntoConstraints = YES;` 102 | 103 | Applying these changes avoids the conflicting constraints, but results in the following layout. 104 | 105 | ![](Screenshots/DisableAutoresizeConstraints.png) 106 | 107 | Unfortunately, the label still does not wrap the text as desired. 108 | 109 | ## Issue #3 Unconstrained Width 110 | 111 | A side-effect of disabling the vertical `NSAutoresizingMaskLayoutConstraint` is that the horizontal `NSAutoresizingMaskLayoutConstraint` is also disabled. As such, if we examine the `headerSize` within `sizeHeaderToFit` we can see that for a `tableView` that is `320` wide, our compressed header size has a width of `405.5`. 112 | 113 | ``` 114 | (lldb) po self.tableView 115 | ; layer = ; contentOffset: {0, 0}; contentSize: {320, 125.5}> 116 | 117 | (lldb) po headerSize 118 | (width=405.5, height=125.5) 119 | { 120 | 405.5 121 | 122 | 125.5 123 | 124 | } 125 | ``` 126 | 127 | It appears that because the horizontal `NSAutoresizingMaskLayoutConstraint` was disabled, the width of the `tableHeaderView` is no longer constrained, therefore an "automatic" setting for `preferredMaxLayoutWidth` is unbounded and results in a single-line label that is not properly wrapped. 128 | 129 | ## Workaround #3 130 | 131 | In order to avoid having an unconstrained width, we need to set up a width constraint that mimics the `NSAutoresizingMaskLayoutConstraint` that we have disabled, but *leaving the height unconstrained*. 132 | 133 | In the above sequence, we then insert a step 1.5 where we add a width constraint and remove it at step 3.5 134 | 135 | ### After Step 1 136 | ``` 137 | // Step 1.5 138 | CGFloat headerWidth = self.tableView. tableHeaderView.bounds.size.width; 139 | NSArray *temporaryWidthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[headerView(width)]" options:0 metrics:@{@"width": @(headerWidth)} views:@{@"headerView": self.headerView}]; 140 | [self.headerView addConstraints:temporaryWidthConstraints]; 141 | ``` 142 | 143 | ### Before Step 4 144 | ``` 145 | // Step 3.5 146 | // now that we've coerced our headerView to lay out correctly, remove our temporary width contraint and reapply our autoresizeMaskConstraints 147 | [self.headerView removeConstraints:temporaryWidthConstraints]; 148 | ``` 149 | 150 | With this set of changes the layout appears correctly for both a 320 width device and for a 500 width device. 151 | 152 | ![](Screenshots/ProperLayout320.png) 153 | 154 | ![](Screenshots/ProperLayout500.png) 155 | 156 | ## Summary 157 | 158 | Without workarounds, the `tableHeaderView` does not allow its height to be defined by the constraints of its contents. The NSAutoresizingMaskLayoutConstraints that are defined for this view conflict with the height constraints of its content. Removing the NSAutoresizingMaskLayoutConstraints causes the width to be unbounded and necessitates applying a temporary width constraint to result in proper height calculation and width. 159 | 160 | It is expected that the `tableHeaderView` height should be able to be determined at runtime automatically, equivalent to the automatic sizing of table view cells. -------------------------------------------------------------------------------- /Screenshots/DefaultHeight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/DefaultHeight.png -------------------------------------------------------------------------------- /Screenshots/DeviceWidth500PreferredWidth270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/DeviceWidth500PreferredWidth270.png -------------------------------------------------------------------------------- /Screenshots/DisableAutoresizeConstraints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/DisableAutoresizeConstraints.png -------------------------------------------------------------------------------- /Screenshots/FixedHeight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/FixedHeight.png -------------------------------------------------------------------------------- /Screenshots/PreferredWidth270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/PreferredWidth270.png -------------------------------------------------------------------------------- /Screenshots/ProperLayout320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/ProperLayout320.png -------------------------------------------------------------------------------- /Screenshots/ProperLayout500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/ProperLayout500.png -------------------------------------------------------------------------------- /Screenshots/SizeHeaderToFit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/SizeHeaderToFit.png -------------------------------------------------------------------------------- /Screenshots/Storyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveanderson/TableViewHeader/a910565ff4eb602496f79f6361c61226aa20b883/Screenshots/Storyboard.png -------------------------------------------------------------------------------- /TableViewHeader.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F27598519ABEC500046C72C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F27598419ABEC500046C72C /* main.m */; }; 11 | 4F27598819ABEC500046C72C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F27598719ABEC500046C72C /* AppDelegate.m */; }; 12 | 4F27598B19ABEC500046C72C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F27598A19ABEC500046C72C /* ViewController.m */; }; 13 | 4F27598E19ABEC500046C72C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F27598C19ABEC500046C72C /* Main.storyboard */; }; 14 | 4F27599019ABEC500046C72C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F27598F19ABEC500046C72C /* Images.xcassets */; }; 15 | 4F27599C19ABEC500046C72C /* TableViewHeaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F27599B19ABEC500046C72C /* TableViewHeaderTests.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | 4F27599619ABEC500046C72C /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = 4F27597719ABEC500046C72C /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = 4F27597E19ABEC500046C72C; 24 | remoteInfo = TableViewHeader; 25 | }; 26 | /* End PBXContainerItemProxy section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 4F27597F19ABEC500046C72C /* TableViewHeader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableViewHeader.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 4F27598319ABEC500046C72C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 4F27598419ABEC500046C72C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 32 | 4F27598619ABEC500046C72C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 33 | 4F27598719ABEC500046C72C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 34 | 4F27598919ABEC500046C72C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 35 | 4F27598A19ABEC500046C72C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 36 | 4F27598D19ABEC500046C72C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | 4F27598F19ABEC500046C72C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | 4F27599519ABEC500046C72C /* TableViewHeaderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableViewHeaderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 4F27599A19ABEC500046C72C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 4F27599B19ABEC500046C72C /* TableViewHeaderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableViewHeaderTests.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 4F27597C19ABEC500046C72C /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | 4F27599219ABEC500046C72C /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 4F27597619ABEC500046C72C = { 62 | isa = PBXGroup; 63 | children = ( 64 | 4F27598119ABEC500046C72C /* TableViewHeader */, 65 | 4F27599819ABEC500046C72C /* TableViewHeaderTests */, 66 | 4F27598019ABEC500046C72C /* Products */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | 4F27598019ABEC500046C72C /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 4F27597F19ABEC500046C72C /* TableViewHeader.app */, 74 | 4F27599519ABEC500046C72C /* TableViewHeaderTests.xctest */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | 4F27598119ABEC500046C72C /* TableViewHeader */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 4F27598619ABEC500046C72C /* AppDelegate.h */, 83 | 4F27598719ABEC500046C72C /* AppDelegate.m */, 84 | 4F27598919ABEC500046C72C /* ViewController.h */, 85 | 4F27598A19ABEC500046C72C /* ViewController.m */, 86 | 4F27598C19ABEC500046C72C /* Main.storyboard */, 87 | 4F27598F19ABEC500046C72C /* Images.xcassets */, 88 | 4F27598219ABEC500046C72C /* Supporting Files */, 89 | ); 90 | path = TableViewHeader; 91 | sourceTree = ""; 92 | }; 93 | 4F27598219ABEC500046C72C /* Supporting Files */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 4F27598319ABEC500046C72C /* Info.plist */, 97 | 4F27598419ABEC500046C72C /* main.m */, 98 | ); 99 | name = "Supporting Files"; 100 | sourceTree = ""; 101 | }; 102 | 4F27599819ABEC500046C72C /* TableViewHeaderTests */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 4F27599B19ABEC500046C72C /* TableViewHeaderTests.m */, 106 | 4F27599919ABEC500046C72C /* Supporting Files */, 107 | ); 108 | path = TableViewHeaderTests; 109 | sourceTree = ""; 110 | }; 111 | 4F27599919ABEC500046C72C /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 4F27599A19ABEC500046C72C /* Info.plist */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 4F27597E19ABEC500046C72C /* TableViewHeader */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 4F27599F19ABEC500046C72C /* Build configuration list for PBXNativeTarget "TableViewHeader" */; 125 | buildPhases = ( 126 | 4F27597B19ABEC500046C72C /* Sources */, 127 | 4F27597C19ABEC500046C72C /* Frameworks */, 128 | 4F27597D19ABEC500046C72C /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = TableViewHeader; 135 | productName = TableViewHeader; 136 | productReference = 4F27597F19ABEC500046C72C /* TableViewHeader.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | 4F27599419ABEC500046C72C /* TableViewHeaderTests */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 4F2759A219ABEC500046C72C /* Build configuration list for PBXNativeTarget "TableViewHeaderTests" */; 142 | buildPhases = ( 143 | 4F27599119ABEC500046C72C /* Sources */, 144 | 4F27599219ABEC500046C72C /* Frameworks */, 145 | 4F27599319ABEC500046C72C /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | 4F27599719ABEC500046C72C /* PBXTargetDependency */, 151 | ); 152 | name = TableViewHeaderTests; 153 | productName = TableViewHeaderTests; 154 | productReference = 4F27599519ABEC500046C72C /* TableViewHeaderTests.xctest */; 155 | productType = "com.apple.product-type.bundle.unit-test"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 4F27597719ABEC500046C72C /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | LastUpgradeCheck = 0600; 164 | ORGANIZATIONNAME = "ElectroBarn Inc"; 165 | TargetAttributes = { 166 | 4F27597E19ABEC500046C72C = { 167 | CreatedOnToolsVersion = 6.0; 168 | }; 169 | 4F27599419ABEC500046C72C = { 170 | CreatedOnToolsVersion = 6.0; 171 | TestTargetID = 4F27597E19ABEC500046C72C; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 4F27597A19ABEC500046C72C /* Build configuration list for PBXProject "TableViewHeader" */; 176 | compatibilityVersion = "Xcode 3.2"; 177 | developmentRegion = English; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 4F27597619ABEC500046C72C; 184 | productRefGroup = 4F27598019ABEC500046C72C /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 4F27597E19ABEC500046C72C /* TableViewHeader */, 189 | 4F27599419ABEC500046C72C /* TableViewHeaderTests */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 4F27597D19ABEC500046C72C /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 4F27598E19ABEC500046C72C /* Main.storyboard in Resources */, 200 | 4F27599019ABEC500046C72C /* Images.xcassets in Resources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | 4F27599319ABEC500046C72C /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | 4F27597B19ABEC500046C72C /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | 4F27598B19ABEC500046C72C /* ViewController.m in Sources */, 219 | 4F27598819ABEC500046C72C /* AppDelegate.m in Sources */, 220 | 4F27598519ABEC500046C72C /* main.m in Sources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | 4F27599119ABEC500046C72C /* Sources */ = { 225 | isa = PBXSourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 4F27599C19ABEC500046C72C /* TableViewHeaderTests.m in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXSourcesBuildPhase section */ 233 | 234 | /* Begin PBXTargetDependency section */ 235 | 4F27599719ABEC500046C72C /* PBXTargetDependency */ = { 236 | isa = PBXTargetDependency; 237 | target = 4F27597E19ABEC500046C72C /* TableViewHeader */; 238 | targetProxy = 4F27599619ABEC500046C72C /* PBXContainerItemProxy */; 239 | }; 240 | /* End PBXTargetDependency section */ 241 | 242 | /* Begin PBXVariantGroup section */ 243 | 4F27598C19ABEC500046C72C /* Main.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 4F27598D19ABEC500046C72C /* Base */, 247 | ); 248 | name = Main.storyboard; 249 | sourceTree = ""; 250 | }; 251 | /* End PBXVariantGroup section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 4F27599D19ABEC500046C72C /* Debug */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INT_CONVERSION = YES; 268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_DYNAMIC_NO_PIC = NO; 276 | GCC_OPTIMIZATION_LEVEL = 0; 277 | GCC_PREPROCESSOR_DEFINITIONS = ( 278 | "DEBUG=1", 279 | "$(inherited)", 280 | ); 281 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 289 | MTL_ENABLE_DEBUG_INFO = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = iphoneos; 292 | TARGETED_DEVICE_FAMILY = "1,2"; 293 | }; 294 | name = Debug; 295 | }; 296 | 4F27599E19ABEC500046C72C /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_UNREACHABLE_CODE = YES; 312 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 313 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 314 | COPY_PHASE_STRIP = YES; 315 | ENABLE_NS_ASSERTIONS = NO; 316 | ENABLE_STRICT_OBJC_MSGSEND = YES; 317 | GCC_C_LANGUAGE_STANDARD = gnu99; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 325 | MTL_ENABLE_DEBUG_INFO = NO; 326 | SDKROOT = iphoneos; 327 | TARGETED_DEVICE_FAMILY = "1,2"; 328 | VALIDATE_PRODUCT = YES; 329 | }; 330 | name = Release; 331 | }; 332 | 4F2759A019ABEC500046C72C /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 337 | INFOPLIST_FILE = TableViewHeader/Info.plist; 338 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 339 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 340 | PRODUCT_NAME = "$(TARGET_NAME)"; 341 | }; 342 | name = Debug; 343 | }; 344 | 4F2759A119ABEC500046C72C /* Release */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 348 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 349 | INFOPLIST_FILE = TableViewHeader/Info.plist; 350 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 351 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 352 | PRODUCT_NAME = "$(TARGET_NAME)"; 353 | }; 354 | name = Release; 355 | }; 356 | 4F2759A319ABEC500046C72C /* Debug */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | BUNDLE_LOADER = "$(TEST_HOST)"; 360 | FRAMEWORK_SEARCH_PATHS = ( 361 | "$(SDKROOT)/Developer/Library/Frameworks", 362 | "$(inherited)", 363 | ); 364 | GCC_PREPROCESSOR_DEFINITIONS = ( 365 | "DEBUG=1", 366 | "$(inherited)", 367 | ); 368 | INFOPLIST_FILE = TableViewHeaderTests/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TableViewHeader.app/TableViewHeader"; 372 | }; 373 | name = Debug; 374 | }; 375 | 4F2759A419ABEC500046C72C /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | BUNDLE_LOADER = "$(TEST_HOST)"; 379 | FRAMEWORK_SEARCH_PATHS = ( 380 | "$(SDKROOT)/Developer/Library/Frameworks", 381 | "$(inherited)", 382 | ); 383 | INFOPLIST_FILE = TableViewHeaderTests/Info.plist; 384 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TableViewHeader.app/TableViewHeader"; 387 | }; 388 | name = Release; 389 | }; 390 | /* End XCBuildConfiguration section */ 391 | 392 | /* Begin XCConfigurationList section */ 393 | 4F27597A19ABEC500046C72C /* Build configuration list for PBXProject "TableViewHeader" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | 4F27599D19ABEC500046C72C /* Debug */, 397 | 4F27599E19ABEC500046C72C /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | 4F27599F19ABEC500046C72C /* Build configuration list for PBXNativeTarget "TableViewHeader" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | 4F2759A019ABEC500046C72C /* Debug */, 406 | 4F2759A119ABEC500046C72C /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | 4F2759A219ABEC500046C72C /* Build configuration list for PBXNativeTarget "TableViewHeaderTests" */ = { 412 | isa = XCConfigurationList; 413 | buildConfigurations = ( 414 | 4F2759A319ABEC500046C72C /* Debug */, 415 | 4F2759A419ABEC500046C72C /* Release */, 416 | ); 417 | defaultConfigurationIsVisible = 0; 418 | defaultConfigurationName = Release; 419 | }; 420 | /* End XCConfigurationList section */ 421 | }; 422 | rootObject = 4F27597719ABEC500046C72C /* Project object */; 423 | } 424 | -------------------------------------------------------------------------------- /TableViewHeader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TableViewHeader/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TableViewHeader 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TableViewHeader/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TableViewHeader 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /TableViewHeader/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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /TableViewHeader/Images.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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /TableViewHeader/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /TableViewHeader/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.robotsandpencils.$(PRODUCT_NAME:rfc1034identifier) 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 | 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 | -------------------------------------------------------------------------------- /TableViewHeader/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TableViewHeader 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UITableViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UIView *headerView; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TableViewHeader/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TableViewHeader 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | @property (weak, nonatomic) IBOutlet UIView *permanentView; 13 | @property (weak, nonatomic) IBOutlet UIView *temporaryView; 14 | 15 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *temporaryBottomToSuperBottom; 16 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *permanentBottomToTemporaryTop; 17 | @property (weak, nonatomic) NSLayoutConstraint *permanentBottomToSuperBottom; 18 | 19 | @property (assign, nonatomic) BOOL showTemporaryView; 20 | @property (assign, nonatomic) BOOL translatesAutoresizingMask; 21 | 22 | @end 23 | 24 | @implementation ViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | // Do any additional setup after loading the view, typically from a nib. 29 | } 30 | 31 | - (void)didReceiveMemoryWarning { 32 | [super didReceiveMemoryWarning]; 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | - (void)updateViewConstraints { 37 | [super updateViewConstraints]; 38 | 39 | static dispatch_once_t onceToken; 40 | dispatch_once(&onceToken, ^{ 41 | self.showTemporaryView = YES; 42 | }); 43 | 44 | // In iOS 8 beta 5 `self.headerView` (a.k.a. `self.tableView.tableHeaderView`) has `translatesAutoresizingMaskIntoConstraints` set to `YES` which results in a vertical height constraint equivalent to the height of the view in the storyboard (or whatever the frame was last set to in `-sizeHeaderToFit`). In our case we want to change the height of the `headerView` at runtime by modifying constraints. When we attempt to do this, the contraints in the system generated by `translatesAutoresizingMaskIntoConstraints = YES` conflict with our runtime constraints, and autolayout then discards (non-deterministically) one of the constraints, resulting in an undesirable, unexpected layout. 45 | // By disabling `translatesAutoresizingMaskIntoConstraints` before triggering a layout pass, and reenabling it afterward, the conflicting constraint is avoided and layout appears as expected, but ONLY if the `preferredMaxLayoutWidth` of the label is not set to automatic and has a value that is correct for the current resizeable simulator width. We do not want to have to calculate the `preferredMaxLayoutWidth` at runtime and expect to be able to use the automatic value. 46 | 47 | // ## Step 1 48 | [self disableAutoresizeMaskConstraints]; // disable this method and `reenableAutoresizeMaskConstraints` to trigger the unexpected conflicting constraints. 49 | 50 | // Unfortunately, by disabling the AutoresizingMaskConstraints, the current (runtime) width of the headerView is no longer constrained. Therefore, an "automatic" setting for `preferredMaxLayoutWidth` is unbounded and results in a single-line label that is not properly wrapped. 51 | 52 | // Thus, in order to avoid having an unconstrained width, we need to set up a width constraint that mimics the NSAutoresizingMaskLayoutConstraint that we have disabled, but leaving the height unconstrained. 53 | 54 | // ## Step 1.5 55 | CGFloat headerWidth = self.headerView.bounds.size.width; 56 | NSArray *temporaryWidthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[headerView(width)]" options:0 metrics:@{@"width": @(headerWidth)} views:@{@"headerView": self.headerView}]; 57 | [self.headerView addConstraints:temporaryWidthConstraints]; 58 | 59 | // ## Step 2 60 | [self removeConstraints]; 61 | 62 | if (self.showTemporaryView) { 63 | 64 | // adjust constraints to show our temporary view 65 | self.temporaryView.hidden = NO; 66 | NSDictionary *viewsDictionary = @{@"permanentView": self.permanentView, @"temporaryView": self.temporaryView}; 67 | NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[permanentView]-5-[temporaryView]-15-|" options:0 metrics:0 views:viewsDictionary]; 68 | 69 | self.permanentBottomToTemporaryTop = constraints[0]; 70 | self.temporaryBottomToSuperBottom = constraints[1]; 71 | [self.headerView addConstraints:constraints]; 72 | 73 | } else { 74 | 75 | // adjust constraints to hide our temporary view 76 | self.temporaryView.hidden = YES; 77 | NSDictionary *viewsDictionary = @{@"permanentView": self.permanentView}; 78 | NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[permanentView]-20-|" options:0 metrics:0 views:viewsDictionary]; 79 | 80 | self.permanentBottomToSuperBottom = constraints[0]; 81 | [self.headerView addConstraints:constraints]; 82 | } 83 | 84 | // ## Step 3 85 | [self sizeHeaderToFit]; 86 | 87 | // now that we've coerced our headerView to lay out correctly, remove our temporary width contraint and reapply our autoresizeMaskConstraints 88 | 89 | // ## Step 3.5 90 | [self.headerView removeConstraints:temporaryWidthConstraints]; 91 | 92 | // ## Step 4 93 | [self reenableAutoresizeMaskConstraints]; // disable this method and `disableAutoresizeMaskConstraints` to trigger the unexpected conflicting constraints. 94 | } 95 | 96 | - (void)sizeHeaderToFit { 97 | UIView *header = self.tableView.tableHeaderView; 98 | 99 | [header setNeedsLayout]; 100 | [header layoutIfNeeded]; 101 | 102 | CGSize headerSize = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 103 | CGRect frame = header.frame; 104 | 105 | frame.size.height = headerSize.height; 106 | header.frame = frame; 107 | 108 | self.tableView.tableHeaderView = header; 109 | } 110 | 111 | 112 | - (void)removeConstraints { 113 | if (self.permanentBottomToTemporaryTop) { 114 | [self.headerView removeConstraint:self.permanentBottomToTemporaryTop]; 115 | } 116 | if (self.temporaryBottomToSuperBottom) { 117 | [self.headerView removeConstraint:self.temporaryBottomToSuperBottom]; 118 | } 119 | if (self.permanentBottomToSuperBottom) { 120 | [self.headerView removeConstraint:self.permanentBottomToSuperBottom]; 121 | } 122 | } 123 | 124 | - (void)disableAutoresizeMaskConstraints { 125 | 126 | self.translatesAutoresizingMask = self.headerView.translatesAutoresizingMaskIntoConstraints; 127 | if (self.translatesAutoresizingMask) { 128 | self.headerView.translatesAutoresizingMaskIntoConstraints = NO; 129 | } 130 | } 131 | 132 | - (void)reenableAutoresizeMaskConstraints { 133 | self.headerView.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMask; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /TableViewHeader/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TableViewHeader 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TableViewHeaderTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.robotsandpencils.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /TableViewHeaderTests/TableViewHeaderTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewHeaderTests.m 3 | // TableViewHeaderTests 4 | // 5 | // Created by David Anderson on 2014-08-25. 6 | // Copyright (c) 2014 ElectroBarn Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface TableViewHeaderTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation TableViewHeaderTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------