├── .gitignore ├── LICENSE ├── OLEContainerScrollView ├── OLEContainerScrollView+Swizzling.h ├── OLEContainerScrollView+Swizzling.m ├── OLEContainerScrollView.h ├── OLEContainerScrollView.m ├── OLEContainerScrollViewContentView.h ├── OLEContainerScrollViewContentView.m ├── OLEContainerScrollView_Private.h ├── OLESwizzling.h └── OLESwizzling.m ├── OLEContainerScrollViewDemo.xcodeproj └── project.pbxproj ├── OLEContainerScrollViewDemo ├── AppDelegate.h ├── AppDelegate.m ├── ChildView.xib ├── ChildViewUsingAutolayout.h ├── ChildViewUsingAutolayout.m ├── ChildViewUsingAutolayout.xib ├── CustomContainerScrollViewController.h ├── CustomContainerScrollViewController.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Main.storyboard ├── MultipleCollectionsViewController.h ├── MultipleCollectionsViewController.m ├── MultipleTableViewsViewController.h ├── MultipleTableViewsViewController.m ├── NaiveContainerScrollViewController.h ├── NaiveContainerScrollViewController.m ├── OLEBorderedView.h ├── OLEBorderedView.m ├── OLEContainerScrollViewDemo-Info.plist ├── OLEContainerScrollViewDemo-Prefix.pch ├── OLESimulatedTableView.h ├── OLESimulatedTableView.m ├── SimpleTableViewController.h ├── SimpleTableViewController.m ├── SingleViewsContainerViewController.h ├── SingleViewsContainerViewController.m ├── SingleViewsUsingAutolayoutContainerViewController.h ├── SingleViewsUsingAutolayoutContainerViewController.m ├── TableViewWithWebViewViewController.h ├── TableViewWithWebViewViewController.m ├── UIColor+RandomColor.h ├── UIColor+RandomColor.m ├── en.lproj │ └── InfoPlist.strings └── main.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | Pods/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ole Begemann 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollView+Swizzling.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | void swizzleUICollectionViewLayoutFinalizeCollectionViewUpdates(void); 9 | void swizzleUITableView(void); 10 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollView+Swizzling.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | #import "OLEContainerScrollView+Swizzling.h" 11 | #import "OLESwizzling.h" 12 | #import "OLEContainerScrollView.h" 13 | #import "OLEContainerScrollViewContentView.h" 14 | 15 | void swizzleUICollectionViewLayoutFinalizeCollectionViewUpdates() 16 | { 17 | Class classToSwizzle = [UICollectionViewLayout class]; 18 | SEL selectorToSwizzle = @selector(finalizeCollectionViewUpdates); 19 | 20 | __block IMP originalIMP = NULL; 21 | originalIMP = OLEReplaceMethodWithBlock(classToSwizzle, selectorToSwizzle, ^(UICollectionViewLayout *_self) { 22 | // Call original implementation 23 | ((void ( *)(id, SEL))originalIMP)(_self, selectorToSwizzle); 24 | 25 | // Manually set the collection view's contentSize to its new size (after the updates have been performed) to cause 26 | // a relayout of all views in the container scroll view. 27 | // We do this to animate the resizing of the collection view and its adjacent views in the container scroll view 28 | // in sync with the cell update animations (finalizeCollectionViewUpdates is called inside the animation block). 29 | // If we don't do this, the collection view will set its new content size only after the cell update animations 30 | // have finished, which is too late for us. 31 | UICollectionView *collectionView = _self.collectionView; 32 | BOOL collectionViewIsInsideOLEContainerScrollView = [collectionView.superview isKindOfClass:[OLEContainerScrollViewContentView class]]; 33 | if (collectionViewIsInsideOLEContainerScrollView) { 34 | collectionView.contentSize = _self.collectionViewContentSize; 35 | } 36 | }); 37 | } 38 | 39 | void swizzleUITableView() 40 | { 41 | Class classToSwizzle = [UITableView class]; 42 | NSString *obfuscatedSelector = [NSString stringWithFormat:@"_%@llAnimat%@hContext:", @"endCe", @"ionsWit"]; 43 | SEL selectorToSwizzle = NSSelectorFromString(obfuscatedSelector); 44 | 45 | __block IMP originalIMP = NULL; 46 | originalIMP = OLEReplaceMethodWithBlock(classToSwizzle, selectorToSwizzle, ^(UITableView *_self, id context) { 47 | // Call original implementation 48 | ((void ( *)(id, SEL, id))originalIMP)(_self, selectorToSwizzle, context); 49 | 50 | [UIView animateWithDuration:0.25 animations:^{ 51 | 52 | // Manually set the table view's contentSize to its new size (after the updates have been performed) to cause 53 | // a relayout of all views in the container scroll view. 54 | // We do this to animate the resizing of the table view and its adjacent views in the container scroll view 55 | // in sync with the cell update animations. If we don't do this, the table view will set its new content size 56 | // only after the cell update animations have finished, which is too late for us. 57 | BOOL tableViewIsInsideOLEContainerScrollView = [_self.superview isKindOfClass:[OLEContainerScrollViewContentView class]]; 58 | if (tableViewIsInsideOLEContainerScrollView) { 59 | NSString *obfuscatedPropertyKey = [NSString stringWithFormat:@"_%@entSize", @"cont"]; 60 | _self.contentSize = [[_self valueForKey:obfuscatedPropertyKey] CGSizeValue]; 61 | } 62 | }]; 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollView.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | /** 11 | A container scroll view that allows you to place multiple views (including scroll views, table views and collection views) in a stacked layout. 12 | 13 | `OLEContainerScrollView` will automatically arrange all subviews that are added to its content view in a stacked vertical order. For any scroll views among those subviews (that is, any view that is an instance of `UIScrollView` or one of its subclasses; that includes `UITableView` and `UICollectionView`), the container view will seamlessly manage their frame rectangles in such a way that the subviews' frame height is never larger than absolutely necessary for the current scroll position. 14 | 15 | In other words, scroll views that are currently outside the container viewʼs bounds (and thus invisible) will have their frame height reduced to zero. Scroll views that are partly or fully inside the container viewʼs bounds will have their frame height and content offset adjusted accordingly in a way that gives the user the impression that the subviews themselves are being scrolled. All adjustments happen seamlessly as the container view scrolls. 16 | 17 | This allows you to stack multiple table or collection views vertically while retaining the full cell reuse capabilities of `UITableView` and `UICollectionView`. 18 | 19 | ## Usage 20 | 21 | Just add your views to the container scroll view's `contentView`. 22 | 23 | ## Limitations 24 | 25 | - Only vertically stacked layouts (and vertical scrolling) are supported at the moment. 26 | - The stack layout is very inflexible. It resizes all subviews to the width of the container. It does not support spacing between or free positioning of the subviews. 27 | - Content size changes of the subviews are observed, but not animated in the case of table or collection views. 28 | 29 | ## More Information 30 | 31 | See the accompanying blog post, [Scroll Views Inside Scroll Views](http://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/), for more information. 32 | */ 33 | @interface OLEContainerScrollView : UIScrollView 34 | 35 | /** @name Managing Subviews */ 36 | /** 37 | The container scroll view's content view. You should add your subviews to this content view. Only views that are added to the content view will be managed by the container scroll view. The container scroll view will lay the content view's subviews out vertically in the order in which they were added. 38 | */ 39 | @property (nonatomic, readonly) UIView *contentView; 40 | 41 | /** 42 | Spacing between adjacent edges of subviews. 43 | */ 44 | @property (nonatomic) CGFloat spacing; 45 | 46 | /** 47 | All hidden subviews are ignored in layout, this is similar to what UIStackView does 48 | */ 49 | @property (nonatomic) BOOL ignoreHiddenSubviews; 50 | 51 | @end 52 | 53 | 54 | @protocol OLEContainerScrollViewScrollable 55 | @required 56 | @property (nonatomic, readonly) UIScrollView *scrollView; 57 | 58 | @end 59 | 60 | @interface UIScrollView () 61 | @end 62 | 63 | @implementation UIScrollView (OLEContainerScrollViewScrollable) 64 | - (UIScrollView *)scrollView 65 | { 66 | return self; 67 | } 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollView.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import QuartzCore; 9 | 10 | #import "OLEContainerScrollView.h" 11 | #import "OLEContainerScrollView_Private.h" 12 | #import "OLEContainerScrollViewContentView.h" 13 | #import "OLEContainerScrollView+Swizzling.h" 14 | 15 | @interface OLEContainerScrollView () 16 | 17 | @property (nonatomic, readonly) NSMutableArray *subviewsInLayoutOrder; 18 | 19 | @end 20 | 21 | 22 | @implementation OLEContainerScrollView 23 | 24 | + (void)initialize 25 | { 26 | // +initialize can be called multiple times if subclasses don't implement it. 27 | // Protect against multiple calls 28 | if (self == [OLEContainerScrollView self]) { 29 | swizzleUICollectionViewLayoutFinalizeCollectionViewUpdates(); 30 | swizzleUITableView(); 31 | } 32 | } 33 | 34 | - (void)dealloc 35 | { 36 | // Removing the subviews will unregister KVO observers 37 | for (UIView *subview in self.contentView.subviews) { 38 | [subview removeFromSuperview]; 39 | } 40 | } 41 | 42 | - (id)initWithFrame:(CGRect)frame 43 | { 44 | self = [super initWithFrame:frame]; 45 | if (self) { 46 | [self commonInitForOLEContainerScrollView]; 47 | } 48 | return self; 49 | } 50 | 51 | - (void)awakeFromNib 52 | { 53 | [super awakeFromNib]; 54 | [self commonInitForOLEContainerScrollView]; 55 | } 56 | 57 | - (void)commonInitForOLEContainerScrollView 58 | { 59 | _contentView = [[OLEContainerScrollViewContentView alloc] initWithFrame:CGRectZero]; 60 | [self addSubview:_contentView]; 61 | _subviewsInLayoutOrder = [NSMutableArray arrayWithCapacity:4]; 62 | _spacing = 0.0; 63 | _ignoreHiddenSubviews = YES; 64 | } 65 | 66 | - (void)setSpacing:(CGFloat)spacing { 67 | _spacing = spacing; 68 | [self setNeedsLayout]; 69 | } 70 | 71 | - (void)setIgnoreHiddenSubviews:(BOOL)newValue { 72 | _ignoreHiddenSubviews = newValue; 73 | [self setNeedsLayout]; 74 | } 75 | 76 | #pragma mark - Adding and removing subviews 77 | 78 | - (void)didAddSubviewToContainer:(UIView *)subview 79 | { 80 | NSParameterAssert(subview != nil); 81 | 82 | NSUInteger ix = [self.subviewsInLayoutOrder indexOfObjectIdenticalTo:subview]; 83 | if (ix != NSNotFound) { 84 | [self.subviewsInLayoutOrder removeObjectAtIndex:ix]; 85 | [self.subviewsInLayoutOrder addObject:subview]; 86 | [self setNeedsLayout]; 87 | return; 88 | } 89 | 90 | subview.autoresizingMask = UIViewAutoresizingNone; 91 | 92 | [self.subviewsInLayoutOrder addObject:subview]; 93 | 94 | if ([subview isKindOfClass:[UIScrollView class]]) { 95 | UIScrollView *scrollView = (UIScrollView *)subview; 96 | scrollView.scrollEnabled = NO; 97 | [scrollView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:NSKeyValueObservingOptionOld context:KVOContext]; 98 | } else if ([subview respondsToSelector:@selector(scrollView)]) { 99 | UIScrollView *scrollView = [subview performSelector:@selector(scrollView)]; 100 | scrollView.scrollEnabled = NO; 101 | [scrollView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:NSKeyValueObservingOptionOld context:KVOContext]; 102 | } else { 103 | [subview addObserver:self forKeyPath:NSStringFromSelector(@selector(frame)) options:NSKeyValueObservingOptionOld context:KVOContext]; 104 | [subview addObserver:self forKeyPath:NSStringFromSelector(@selector(bounds)) options:NSKeyValueObservingOptionOld context:KVOContext]; 105 | } 106 | 107 | [self setNeedsLayout]; 108 | } 109 | 110 | - (void)willRemoveSubviewFromContainer:(UIView *)subview 111 | { 112 | NSParameterAssert(subview != nil); 113 | 114 | if ([subview isKindOfClass:[UIScrollView class]]) { 115 | [subview removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:KVOContext]; 116 | } else if ([subview respondsToSelector:@selector(scrollView)]) { 117 | UIScrollView *scrollView = [subview performSelector:@selector(scrollView)]; 118 | [scrollView removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:KVOContext]; 119 | } else { 120 | [subview removeObserver:self forKeyPath:NSStringFromSelector(@selector(frame)) context:KVOContext]; 121 | [subview removeObserver:self forKeyPath:NSStringFromSelector(@selector(bounds)) context:KVOContext]; 122 | } 123 | [self.subviewsInLayoutOrder removeObject:subview]; 124 | [self setNeedsLayout]; 125 | } 126 | 127 | #pragma mark - KVO 128 | 129 | static void *KVOContext = &KVOContext; 130 | 131 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 132 | { 133 | if (context == KVOContext) { 134 | // Initiate a layout recalculation only when a subviewʼs frame or contentSize has changed 135 | if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) { 136 | UIScrollView *scrollView = object; 137 | CGSize oldContentSize = [change[NSKeyValueChangeOldKey] CGSizeValue]; 138 | CGSize newContentSize = scrollView.contentSize; 139 | if (!CGSizeEqualToSize(newContentSize, oldContentSize)) { 140 | [self setNeedsLayout]; 141 | [self layoutIfNeeded]; 142 | } 143 | } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(frame))] || 144 | [keyPath isEqualToString:NSStringFromSelector(@selector(bounds))]) { 145 | UIView *subview = object; 146 | CGRect oldFrame = [change[NSKeyValueChangeOldKey] CGRectValue]; 147 | CGRect newFrame = subview.frame; 148 | if (!CGRectEqualToRect(newFrame, oldFrame)) { 149 | [self setNeedsLayout]; 150 | [self layoutIfNeeded]; 151 | } 152 | } 153 | } else { 154 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 155 | } 156 | } 157 | 158 | #pragma mark - Layout 159 | 160 | - (void)layoutSubviews 161 | { 162 | [super layoutSubviews]; 163 | 164 | // Translate the container view's content offset to contentView bounds. 165 | // This keeps the contentView always centered on the visible portion of the container view's 166 | // full content size, and avoids the need to make the contentView large enough to fit the 167 | // container view's full content size. 168 | self.contentView.frame = self.bounds; 169 | self.contentView.bounds = (CGRect){ self.contentOffset, self.contentView.bounds.size }; 170 | 171 | // The logical vertical offset where the current subview (while iterating over all subviews) 172 | // must be positioned. Subviews are positioned below each other, in the order they were added 173 | // to the container. For scroll views, we reserve their entire contentSize.height as vertical 174 | // space. For non-scroll views, we reserve their current frame.size.height as vertical space. 175 | CGFloat yOffsetOfCurrentSubview = 0.0; 176 | 177 | for (int index = 0; index < self.subviewsInLayoutOrder.count; index++) 178 | { 179 | UIView *subview = self.subviewsInLayoutOrder[index]; 180 | 181 | // Make the height hidden subviews zero in order to behave like UIStackView. 182 | if (self.ignoreHiddenSubviews && subview.hidden) { 183 | CGRect frame = subview.frame; 184 | frame.origin.y = yOffsetOfCurrentSubview; 185 | frame.origin.x = 0; 186 | frame.size.width = self.contentView.bounds.size.width; 187 | subview.frame = frame; 188 | 189 | // Do not set the height to zero. Just don't add the original height to yOffsetOfCurrentSubview. 190 | // This is to keep the original height when the view is unhidden. 191 | continue; 192 | } 193 | 194 | if ([subview respondsToSelector:@selector(scrollView)]) { 195 | UIScrollView *scrollView = [subview performSelector:@selector(scrollView)]; 196 | CGRect frame = subview.frame; 197 | CGPoint contentOffset = scrollView.contentOffset; 198 | 199 | // Translate the logical offset into the sub-scrollview's real content offset and frame size. 200 | // Methodology: 201 | 202 | // (1) As long as the sub-scrollview has not yet reached the top of the screen, set its scroll position 203 | // to 0.0 and position it just like a normal view. Its content scrolls naturally as the container 204 | // scroll view scrolls. 205 | if (self.contentOffset.y < yOffsetOfCurrentSubview) { 206 | contentOffset.y = 0.0; 207 | frame.origin.y = yOffsetOfCurrentSubview; 208 | } 209 | // (2) If the user has scrolled far enough down so that the sub-scrollview reaches the top of the 210 | // screen, position its frame at 0.0 and start adjusting the sub-scrollview's content offset to 211 | // scroll its content. 212 | else { 213 | contentOffset.y = self.contentOffset.y - yOffsetOfCurrentSubview; 214 | frame.origin.y = self.contentOffset.y; 215 | } 216 | 217 | // (3) The sub-scrollview's frame should never extend beyond the bottom of the screen, even if its 218 | // content height is potentially much greater. When the user has scrolled so far that the remaining 219 | // content height is smaller than the height of the screen, adjust the frame height accordingly. 220 | CGFloat remainingBoundsHeight = fmax(CGRectGetMaxY(self.bounds) - CGRectGetMinY(frame), 0.0); 221 | CGFloat remainingContentHeight = fmax(scrollView.contentSize.height - contentOffset.y, 0.0); 222 | frame.size.height = fmin(remainingBoundsHeight, remainingContentHeight); 223 | frame.size.width = self.contentView.bounds.size.width; 224 | 225 | subview.frame = frame; 226 | scrollView.contentOffset = contentOffset; 227 | 228 | yOffsetOfCurrentSubview += scrollView.contentSize.height + scrollView.contentInset.top + scrollView.contentInset.bottom; 229 | } else { 230 | // Normal views are simply positioned at the current offset 231 | CGRect frame = subview.frame; 232 | frame.origin.y = yOffsetOfCurrentSubview; 233 | frame.origin.x = 0; 234 | frame.size.width = self.contentView.bounds.size.width; 235 | subview.frame = frame; 236 | 237 | yOffsetOfCurrentSubview += frame.size.height; 238 | } 239 | 240 | if (index < (self.subviewsInLayoutOrder.count - 1)) { 241 | yOffsetOfCurrentSubview += self.spacing; 242 | } 243 | } 244 | 245 | // If our content is shorter than our bounds height, take the contentInset into account to avoid 246 | // scrolling when it is not needed. 247 | CGFloat minimumContentHeight = self.bounds.size.height - (self.contentInset.top + self.contentInset.bottom); 248 | 249 | CGPoint initialContentOffset = self.contentOffset; 250 | self.contentSize = CGSizeMake(self.bounds.size.width, fmax(yOffsetOfCurrentSubview, minimumContentHeight)); 251 | 252 | // If contentOffset changes after contentSize change, we need to trigger layout update one more time. 253 | if (!CGPointEqualToPoint(initialContentOffset, self.contentOffset)) { 254 | [self setNeedsLayout]; 255 | [self layoutIfNeeded]; 256 | } 257 | } 258 | 259 | @end 260 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollViewContentView.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface OLEContainerScrollViewContentView : UIView 11 | @end 12 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollViewContentView.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "OLEContainerScrollViewContentView.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "OLEContainerScrollView_Private.h" 11 | 12 | @implementation OLEContainerScrollViewContentView 13 | 14 | - (void)didAddSubview:(UIView *)subview 15 | { 16 | [super didAddSubview:subview]; 17 | if ([self.superview isKindOfClass:[OLEContainerScrollView class]]) { 18 | [(OLEContainerScrollView *)self.superview didAddSubviewToContainer:subview]; 19 | } 20 | } 21 | 22 | - (void)willRemoveSubview:(UIView *)subview 23 | { 24 | if ([self.superview isKindOfClass:[OLEContainerScrollView class]]) { 25 | [(OLEContainerScrollView *)self.superview willRemoveSubviewFromContainer:subview]; 26 | } 27 | [super willRemoveSubview:subview]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLEContainerScrollView_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // OLEContainerScrollView_Private.h 3 | // OLEContainerScrollViewDemo 4 | // 5 | // Created by Ole Begemann on 24.07.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import "OLEContainerScrollView.h" 10 | 11 | @interface OLEContainerScrollView () 12 | 13 | - (void)didAddSubviewToContainer:(UIView *)subview; 14 | - (void)willRemoveSubviewFromContainer:(UIView *)subview; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLESwizzling.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import 9 | 10 | IMP OLEReplaceMethodWithBlock(Class c, SEL origSEL, id block); 11 | -------------------------------------------------------------------------------- /OLEContainerScrollView/OLESwizzling.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import Foundation; 9 | #import "OLESwizzling.h" 10 | 11 | // Adapted from Peter Steinberger's PSPDFReplaceMethodWithBlock 12 | // See http://petersteinberger.com/blog/2014/a-story-about-swizzling-the-right-way-and-touch-forwarding/ 13 | IMP OLEReplaceMethodWithBlock(Class c, SEL origSEL, id block) 14 | { 15 | NSCParameterAssert(block); 16 | 17 | // get original method 18 | Method origMethod = class_getInstanceMethod(c, origSEL); 19 | NSCParameterAssert(origMethod); 20 | 21 | // convert block to IMP trampoline and replace method implementation 22 | IMP newIMP = imp_implementationWithBlock(block); 23 | 24 | // Try adding the method if not yet in the current class 25 | if (!class_addMethod(c, origSEL, newIMP, method_getTypeEncoding(origMethod))) { 26 | return method_setImplementation(origMethod, newIMP); 27 | }else { 28 | return method_getImplementation(origMethod); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5D050BE41959D8F200D2447F /* MultipleCollectionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D050BE31959D8F200D2447F /* MultipleCollectionsViewController.m */; }; 11 | 5D050BE71959DEAD00D2447F /* UIColor+RandomColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D050BE61959DEAD00D2447F /* UIColor+RandomColor.m */; }; 12 | 5D06E4831ADC21D300E08F02 /* SingleViewsContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E4821ADC21D300E08F02 /* SingleViewsContainerViewController.m */; }; 13 | 5D06E4851ADC237800E08F02 /* ChildView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D06E4841ADC237800E08F02 /* ChildView.xib */; }; 14 | 5D2A88E8191D1DC300CEE791 /* OLEBorderedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D2A88E7191D1DC300CEE791 /* OLEBorderedView.m */; }; 15 | 5D2A88EB192246D700CEE791 /* SimpleTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D2A88EA192246D700CEE791 /* SimpleTableViewController.m */; }; 16 | 5D63B223192250990064DB38 /* NaiveContainerScrollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D63B222192250990064DB38 /* NaiveContainerScrollViewController.m */; }; 17 | 5D6E3C5B1CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6E3C5A1CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.m */; }; 18 | 5D6E3C5E1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6E3C5D1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.m */; }; 19 | 5D8CFD6E19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D8CFD6D19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.m */; }; 20 | 5D8CFD7419815DA800B3B7F7 /* OLESwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D8CFD7319815DA800B3B7F7 /* OLESwizzling.m */; }; 21 | 5D8CFD7719815E5300B3B7F7 /* OLEContainerScrollViewContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D8CFD7619815E5300B3B7F7 /* OLEContainerScrollViewContentView.m */; }; 22 | 5D8CFD7B1981729D00B3B7F7 /* MultipleTableViewsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D8CFD7A1981729D00B3B7F7 /* MultipleTableViewsViewController.m */; }; 23 | 5DBDF1E518D5F26B005B1E18 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DBDF1E418D5F26B005B1E18 /* Foundation.framework */; }; 24 | 5DBDF1E718D5F26B005B1E18 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DBDF1E618D5F26B005B1E18 /* CoreGraphics.framework */; }; 25 | 5DBDF1E918D5F26B005B1E18 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DBDF1E818D5F26B005B1E18 /* UIKit.framework */; }; 26 | 5DBDF1EF18D5F26B005B1E18 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5DBDF1ED18D5F26B005B1E18 /* InfoPlist.strings */; }; 27 | 5DBDF1F118D5F26B005B1E18 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DBDF1F018D5F26B005B1E18 /* main.m */; }; 28 | 5DBDF1F518D5F26B005B1E18 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DBDF1F418D5F26B005B1E18 /* AppDelegate.m */; }; 29 | 5DBDF1FB18D5F26B005B1E18 /* CustomContainerScrollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DBDF1FA18D5F26B005B1E18 /* CustomContainerScrollViewController.m */; }; 30 | 5DBDF1FD18D5F26B005B1E18 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5DBDF1FC18D5F26B005B1E18 /* Images.xcassets */; }; 31 | 5DBDF21B18D5F2A2005B1E18 /* OLESimulatedTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DBDF21A18D5F2A2005B1E18 /* OLESimulatedTableView.m */; }; 32 | 5DD430131CE1F40C00989D11 /* ChildViewUsingAutolayout.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5DD430121CE1F40C00989D11 /* ChildViewUsingAutolayout.xib */; }; 33 | 5DEAE59F192CF6A80075B6DE /* OLEContainerScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DEAE59E192CF6A80075B6DE /* OLEContainerScrollView.m */; }; 34 | 9C171B4120FF42F000CEE278 /* TableViewWithWebViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C171B4020FF42F000CEE278 /* TableViewWithWebViewViewController.m */; }; 35 | 9C37E18520FCD30C00C410A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9C37E18420FCD30B00C410A4 /* Main.storyboard */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 5D050BE21959D8F200D2447F /* MultipleCollectionsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleCollectionsViewController.h; sourceTree = ""; }; 40 | 5D050BE31959D8F200D2447F /* MultipleCollectionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleCollectionsViewController.m; sourceTree = ""; }; 41 | 5D050BE51959DEAD00D2447F /* UIColor+RandomColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+RandomColor.h"; sourceTree = ""; }; 42 | 5D050BE61959DEAD00D2447F /* UIColor+RandomColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+RandomColor.m"; sourceTree = ""; }; 43 | 5D06E4811ADC21D300E08F02 /* SingleViewsContainerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SingleViewsContainerViewController.h; sourceTree = ""; }; 44 | 5D06E4821ADC21D300E08F02 /* SingleViewsContainerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SingleViewsContainerViewController.m; sourceTree = ""; }; 45 | 5D06E4841ADC237800E08F02 /* ChildView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChildView.xib; sourceTree = ""; }; 46 | 5D2A88E6191D1DC300CEE791 /* OLEBorderedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLEBorderedView.h; sourceTree = ""; }; 47 | 5D2A88E7191D1DC300CEE791 /* OLEBorderedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLEBorderedView.m; sourceTree = ""; }; 48 | 5D2A88E9192246D700CEE791 /* SimpleTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleTableViewController.h; sourceTree = ""; }; 49 | 5D2A88EA192246D700CEE791 /* SimpleTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleTableViewController.m; sourceTree = ""; }; 50 | 5D63B221192250990064DB38 /* NaiveContainerScrollViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NaiveContainerScrollViewController.h; sourceTree = ""; }; 51 | 5D63B222192250990064DB38 /* NaiveContainerScrollViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NaiveContainerScrollViewController.m; sourceTree = ""; }; 52 | 5D6E3C591CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SingleViewsUsingAutolayoutContainerViewController.h; sourceTree = ""; }; 53 | 5D6E3C5A1CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SingleViewsUsingAutolayoutContainerViewController.m; sourceTree = ""; }; 54 | 5D6E3C5C1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChildViewUsingAutolayout.h; sourceTree = ""; }; 55 | 5D6E3C5D1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChildViewUsingAutolayout.m; sourceTree = ""; }; 56 | 5D8CFD6C19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OLEContainerScrollView+Swizzling.h"; sourceTree = ""; }; 57 | 5D8CFD6D19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OLEContainerScrollView+Swizzling.m"; sourceTree = ""; }; 58 | 5D8CFD7219815DA800B3B7F7 /* OLESwizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLESwizzling.h; sourceTree = ""; }; 59 | 5D8CFD7319815DA800B3B7F7 /* OLESwizzling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLESwizzling.m; sourceTree = ""; }; 60 | 5D8CFD7519815E5300B3B7F7 /* OLEContainerScrollViewContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLEContainerScrollViewContentView.h; sourceTree = ""; }; 61 | 5D8CFD7619815E5300B3B7F7 /* OLEContainerScrollViewContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLEContainerScrollViewContentView.m; sourceTree = ""; }; 62 | 5D8CFD781981623200B3B7F7 /* OLEContainerScrollView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OLEContainerScrollView_Private.h; sourceTree = ""; }; 63 | 5D8CFD791981729D00B3B7F7 /* MultipleTableViewsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleTableViewsViewController.h; sourceTree = ""; }; 64 | 5D8CFD7A1981729D00B3B7F7 /* MultipleTableViewsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleTableViewsViewController.m; sourceTree = ""; }; 65 | 5DBDF1E118D5F26B005B1E18 /* OLEContainerScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OLEContainerScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 5DBDF1E418D5F26B005B1E18 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 67 | 5DBDF1E618D5F26B005B1E18 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 68 | 5DBDF1E818D5F26B005B1E18 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 69 | 5DBDF1EC18D5F26B005B1E18 /* OLEContainerScrollViewDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "OLEContainerScrollViewDemo-Info.plist"; sourceTree = ""; }; 70 | 5DBDF1EE18D5F26B005B1E18 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 71 | 5DBDF1F018D5F26B005B1E18 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 72 | 5DBDF1F218D5F26B005B1E18 /* OLEContainerScrollViewDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OLEContainerScrollViewDemo-Prefix.pch"; sourceTree = ""; }; 73 | 5DBDF1F318D5F26B005B1E18 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 74 | 5DBDF1F418D5F26B005B1E18 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 75 | 5DBDF1F918D5F26B005B1E18 /* CustomContainerScrollViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomContainerScrollViewController.h; sourceTree = ""; }; 76 | 5DBDF1FA18D5F26B005B1E18 /* CustomContainerScrollViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomContainerScrollViewController.m; sourceTree = ""; }; 77 | 5DBDF1FC18D5F26B005B1E18 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 78 | 5DBDF20318D5F26B005B1E18 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 79 | 5DBDF21918D5F2A2005B1E18 /* OLESimulatedTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLESimulatedTableView.h; sourceTree = ""; }; 80 | 5DBDF21A18D5F2A2005B1E18 /* OLESimulatedTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLESimulatedTableView.m; sourceTree = ""; }; 81 | 5DD430121CE1F40C00989D11 /* ChildViewUsingAutolayout.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChildViewUsingAutolayout.xib; sourceTree = ""; }; 82 | 5DEAE59D192CF6A80075B6DE /* OLEContainerScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLEContainerScrollView.h; sourceTree = ""; }; 83 | 5DEAE59E192CF6A80075B6DE /* OLEContainerScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLEContainerScrollView.m; sourceTree = ""; }; 84 | 9C171B3F20FF42F000CEE278 /* TableViewWithWebViewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TableViewWithWebViewViewController.h; sourceTree = ""; }; 85 | 9C171B4020FF42F000CEE278 /* TableViewWithWebViewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableViewWithWebViewViewController.m; sourceTree = ""; }; 86 | 9C37E18420FCD30B00C410A4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 87 | /* End PBXFileReference section */ 88 | 89 | /* Begin PBXFrameworksBuildPhase section */ 90 | 5DBDF1DE18D5F26B005B1E18 /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | 5DBDF1E718D5F26B005B1E18 /* CoreGraphics.framework in Frameworks */, 95 | 5DBDF1E918D5F26B005B1E18 /* UIKit.framework in Frameworks */, 96 | 5DBDF1E518D5F26B005B1E18 /* Foundation.framework in Frameworks */, 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | 5D56AD97192A2C7400C42405 /* Demo App */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 9C37E18420FCD30B00C410A4 /* Main.storyboard */, 107 | 5DBDF1F318D5F26B005B1E18 /* AppDelegate.h */, 108 | 5DBDF1F418D5F26B005B1E18 /* AppDelegate.m */, 109 | 5D2A88E9192246D700CEE791 /* SimpleTableViewController.h */, 110 | 5D2A88EA192246D700CEE791 /* SimpleTableViewController.m */, 111 | 5D63B221192250990064DB38 /* NaiveContainerScrollViewController.h */, 112 | 5D63B222192250990064DB38 /* NaiveContainerScrollViewController.m */, 113 | 5DBDF1F918D5F26B005B1E18 /* CustomContainerScrollViewController.h */, 114 | 5DBDF1FA18D5F26B005B1E18 /* CustomContainerScrollViewController.m */, 115 | 5D050BE21959D8F200D2447F /* MultipleCollectionsViewController.h */, 116 | 5D050BE31959D8F200D2447F /* MultipleCollectionsViewController.m */, 117 | 5D8CFD791981729D00B3B7F7 /* MultipleTableViewsViewController.h */, 118 | 5D8CFD7A1981729D00B3B7F7 /* MultipleTableViewsViewController.m */, 119 | 5D06E4811ADC21D300E08F02 /* SingleViewsContainerViewController.h */, 120 | 5D06E4821ADC21D300E08F02 /* SingleViewsContainerViewController.m */, 121 | 5D6E3C591CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.h */, 122 | 5D6E3C5A1CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.m */, 123 | 9C171B3F20FF42F000CEE278 /* TableViewWithWebViewViewController.h */, 124 | 9C171B4020FF42F000CEE278 /* TableViewWithWebViewViewController.m */, 125 | 5D6E3C5C1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.h */, 126 | 5D6E3C5D1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.m */, 127 | 5D06E4841ADC237800E08F02 /* ChildView.xib */, 128 | 5DD430121CE1F40C00989D11 /* ChildViewUsingAutolayout.xib */, 129 | ); 130 | name = "Demo App"; 131 | sourceTree = ""; 132 | }; 133 | 5D56AD99192A2C9000C42405 /* Helpers */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 5DBDF21918D5F2A2005B1E18 /* OLESimulatedTableView.h */, 137 | 5DBDF21A18D5F2A2005B1E18 /* OLESimulatedTableView.m */, 138 | 5D2A88E6191D1DC300CEE791 /* OLEBorderedView.h */, 139 | 5D2A88E7191D1DC300CEE791 /* OLEBorderedView.m */, 140 | 5D050BE51959DEAD00D2447F /* UIColor+RandomColor.h */, 141 | 5D050BE61959DEAD00D2447F /* UIColor+RandomColor.m */, 142 | ); 143 | name = Helpers; 144 | sourceTree = ""; 145 | }; 146 | 5DBDF1D818D5F26B005B1E18 = { 147 | isa = PBXGroup; 148 | children = ( 149 | 5DEAE59C192CF6A80075B6DE /* OLEContainerScrollView */, 150 | 5DBDF1EA18D5F26B005B1E18 /* OLEContainerScrollViewDemo */, 151 | 5DBDF1E318D5F26B005B1E18 /* Frameworks */, 152 | 5DBDF1E218D5F26B005B1E18 /* Products */, 153 | ); 154 | sourceTree = ""; 155 | }; 156 | 5DBDF1E218D5F26B005B1E18 /* Products */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 5DBDF1E118D5F26B005B1E18 /* OLEContainerScrollViewDemo.app */, 160 | ); 161 | name = Products; 162 | sourceTree = ""; 163 | }; 164 | 5DBDF1E318D5F26B005B1E18 /* Frameworks */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 5DBDF1E418D5F26B005B1E18 /* Foundation.framework */, 168 | 5DBDF1E618D5F26B005B1E18 /* CoreGraphics.framework */, 169 | 5DBDF1E818D5F26B005B1E18 /* UIKit.framework */, 170 | 5DBDF20318D5F26B005B1E18 /* XCTest.framework */, 171 | ); 172 | name = Frameworks; 173 | sourceTree = ""; 174 | }; 175 | 5DBDF1EA18D5F26B005B1E18 /* OLEContainerScrollViewDemo */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 5D56AD97192A2C7400C42405 /* Demo App */, 179 | 5D56AD99192A2C9000C42405 /* Helpers */, 180 | 5DBDF1FC18D5F26B005B1E18 /* Images.xcassets */, 181 | 5DBDF1EB18D5F26B005B1E18 /* Supporting Files */, 182 | ); 183 | path = OLEContainerScrollViewDemo; 184 | sourceTree = ""; 185 | }; 186 | 5DBDF1EB18D5F26B005B1E18 /* Supporting Files */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 5DBDF1EC18D5F26B005B1E18 /* OLEContainerScrollViewDemo-Info.plist */, 190 | 5DBDF1ED18D5F26B005B1E18 /* InfoPlist.strings */, 191 | 5DBDF1F018D5F26B005B1E18 /* main.m */, 192 | 5DBDF1F218D5F26B005B1E18 /* OLEContainerScrollViewDemo-Prefix.pch */, 193 | ); 194 | name = "Supporting Files"; 195 | sourceTree = ""; 196 | }; 197 | 5DEAE59C192CF6A80075B6DE /* OLEContainerScrollView */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 5DEAE59D192CF6A80075B6DE /* OLEContainerScrollView.h */, 201 | 5DEAE59E192CF6A80075B6DE /* OLEContainerScrollView.m */, 202 | 5D8CFD781981623200B3B7F7 /* OLEContainerScrollView_Private.h */, 203 | 5D8CFD7519815E5300B3B7F7 /* OLEContainerScrollViewContentView.h */, 204 | 5D8CFD7619815E5300B3B7F7 /* OLEContainerScrollViewContentView.m */, 205 | 5D8CFD6C19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.h */, 206 | 5D8CFD6D19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.m */, 207 | 5D8CFD7219815DA800B3B7F7 /* OLESwizzling.h */, 208 | 5D8CFD7319815DA800B3B7F7 /* OLESwizzling.m */, 209 | ); 210 | path = OLEContainerScrollView; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXGroup section */ 214 | 215 | /* Begin PBXNativeTarget section */ 216 | 5DBDF1E018D5F26B005B1E18 /* OLEContainerScrollViewDemo */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 5DBDF21318D5F26B005B1E18 /* Build configuration list for PBXNativeTarget "OLEContainerScrollViewDemo" */; 219 | buildPhases = ( 220 | 5DBDF1DD18D5F26B005B1E18 /* Sources */, 221 | 5DBDF1DE18D5F26B005B1E18 /* Frameworks */, 222 | 5DBDF1DF18D5F26B005B1E18 /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = OLEContainerScrollViewDemo; 229 | productName = ScrollVisualization; 230 | productReference = 5DBDF1E118D5F26B005B1E18 /* OLEContainerScrollViewDemo.app */; 231 | productType = "com.apple.product-type.application"; 232 | }; 233 | /* End PBXNativeTarget section */ 234 | 235 | /* Begin PBXProject section */ 236 | 5DBDF1D918D5F26B005B1E18 /* Project object */ = { 237 | isa = PBXProject; 238 | attributes = { 239 | LastUpgradeCheck = 0730; 240 | ORGANIZATIONNAME = "Ole Begemann"; 241 | }; 242 | buildConfigurationList = 5DBDF1DC18D5F26B005B1E18 /* Build configuration list for PBXProject "OLEContainerScrollViewDemo" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | Base, 249 | ); 250 | mainGroup = 5DBDF1D818D5F26B005B1E18; 251 | productRefGroup = 5DBDF1E218D5F26B005B1E18 /* Products */; 252 | projectDirPath = ""; 253 | projectRoot = ""; 254 | targets = ( 255 | 5DBDF1E018D5F26B005B1E18 /* OLEContainerScrollViewDemo */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 5DBDF1DF18D5F26B005B1E18 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 5DBDF1FD18D5F26B005B1E18 /* Images.xcassets in Resources */, 266 | 9C37E18520FCD30C00C410A4 /* Main.storyboard in Resources */, 267 | 5DD430131CE1F40C00989D11 /* ChildViewUsingAutolayout.xib in Resources */, 268 | 5D06E4851ADC237800E08F02 /* ChildView.xib in Resources */, 269 | 5DBDF1EF18D5F26B005B1E18 /* InfoPlist.strings in Resources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXResourcesBuildPhase section */ 274 | 275 | /* Begin PBXSourcesBuildPhase section */ 276 | 5DBDF1DD18D5F26B005B1E18 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 5DEAE59F192CF6A80075B6DE /* OLEContainerScrollView.m in Sources */, 281 | 5DBDF1FB18D5F26B005B1E18 /* CustomContainerScrollViewController.m in Sources */, 282 | 5D050BE41959D8F200D2447F /* MultipleCollectionsViewController.m in Sources */, 283 | 5D2A88E8191D1DC300CEE791 /* OLEBorderedView.m in Sources */, 284 | 5D8CFD7B1981729D00B3B7F7 /* MultipleTableViewsViewController.m in Sources */, 285 | 5DBDF1F518D5F26B005B1E18 /* AppDelegate.m in Sources */, 286 | 5D8CFD7719815E5300B3B7F7 /* OLEContainerScrollViewContentView.m in Sources */, 287 | 5D06E4831ADC21D300E08F02 /* SingleViewsContainerViewController.m in Sources */, 288 | 5D63B223192250990064DB38 /* NaiveContainerScrollViewController.m in Sources */, 289 | 5D6E3C5E1CE1F7E900DAD7CE /* ChildViewUsingAutolayout.m in Sources */, 290 | 9C171B4120FF42F000CEE278 /* TableViewWithWebViewViewController.m in Sources */, 291 | 5D8CFD7419815DA800B3B7F7 /* OLESwizzling.m in Sources */, 292 | 5D8CFD6E19815CE500B3B7F7 /* OLEContainerScrollView+Swizzling.m in Sources */, 293 | 5D050BE71959DEAD00D2447F /* UIColor+RandomColor.m in Sources */, 294 | 5DBDF21B18D5F2A2005B1E18 /* OLESimulatedTableView.m in Sources */, 295 | 5D6E3C5B1CE1F5FD00DAD7CE /* SingleViewsUsingAutolayoutContainerViewController.m in Sources */, 296 | 5D2A88EB192246D700CEE791 /* SimpleTableViewController.m in Sources */, 297 | 5DBDF1F118D5F26B005B1E18 /* main.m in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXSourcesBuildPhase section */ 302 | 303 | /* Begin PBXVariantGroup section */ 304 | 5DBDF1ED18D5F26B005B1E18 /* InfoPlist.strings */ = { 305 | isa = PBXVariantGroup; 306 | children = ( 307 | 5DBDF1EE18D5F26B005B1E18 /* en */, 308 | ); 309 | name = InfoPlist.strings; 310 | sourceTree = ""; 311 | }; 312 | /* End PBXVariantGroup section */ 313 | 314 | /* Begin XCBuildConfiguration section */ 315 | 5DBDF21118D5F26B005B1E18 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ALWAYS_SEARCH_USER_PATHS = NO; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_WARN_BOOL_CONVERSION = YES; 324 | CLANG_WARN_CONSTANT_CONVERSION = YES; 325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 331 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 332 | COPY_PHASE_STRIP = NO; 333 | ENABLE_TESTABILITY = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu99; 335 | GCC_DYNAMIC_NO_PIC = NO; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 349 | ONLY_ACTIVE_ARCH = YES; 350 | SDKROOT = iphoneos; 351 | TARGETED_DEVICE_FAMILY = 2; 352 | WARNING_CFLAGS = ( 353 | "-Wall", 354 | "-Wextra", 355 | "-Wno-unused-parameter", 356 | ); 357 | }; 358 | name = Debug; 359 | }; 360 | 5DBDF21218D5F26B005B1E18 /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 365 | CLANG_CXX_LIBRARY = "libc++"; 366 | CLANG_ENABLE_MODULES = YES; 367 | CLANG_ENABLE_OBJC_ARC = YES; 368 | CLANG_WARN_BOOL_CONVERSION = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 376 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 377 | COPY_PHASE_STRIP = YES; 378 | ENABLE_NS_ASSERTIONS = NO; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 381 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 382 | GCC_WARN_UNDECLARED_SELECTOR = YES; 383 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 384 | GCC_WARN_UNUSED_FUNCTION = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 387 | SDKROOT = iphoneos; 388 | TARGETED_DEVICE_FAMILY = 2; 389 | VALIDATE_PRODUCT = YES; 390 | WARNING_CFLAGS = ( 391 | "-Wall", 392 | "-Wextra", 393 | "-Wno-unused-parameter", 394 | ); 395 | }; 396 | name = Release; 397 | }; 398 | 5DBDF21418D5F26B005B1E18 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 402 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 403 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 404 | GCC_PREFIX_HEADER = "OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Prefix.pch"; 405 | INFOPLIST_FILE = "OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Info.plist"; 406 | PRODUCT_BUNDLE_IDENTIFIER = "com.olebegemann.${PRODUCT_NAME:rfc1034identifier}"; 407 | PRODUCT_NAME = OLEContainerScrollViewDemo; 408 | WRAPPER_EXTENSION = app; 409 | }; 410 | name = Debug; 411 | }; 412 | 5DBDF21518D5F26B005B1E18 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 417 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 418 | GCC_PREFIX_HEADER = "OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Prefix.pch"; 419 | INFOPLIST_FILE = "OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Info.plist"; 420 | PRODUCT_BUNDLE_IDENTIFIER = "com.olebegemann.${PRODUCT_NAME:rfc1034identifier}"; 421 | PRODUCT_NAME = OLEContainerScrollViewDemo; 422 | WRAPPER_EXTENSION = app; 423 | }; 424 | name = Release; 425 | }; 426 | /* End XCBuildConfiguration section */ 427 | 428 | /* Begin XCConfigurationList section */ 429 | 5DBDF1DC18D5F26B005B1E18 /* Build configuration list for PBXProject "OLEContainerScrollViewDemo" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | 5DBDF21118D5F26B005B1E18 /* Debug */, 433 | 5DBDF21218D5F26B005B1E18 /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | 5DBDF21318D5F26B005B1E18 /* Build configuration list for PBXNativeTarget "OLEContainerScrollViewDemo" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 5DBDF21418D5F26B005B1E18 /* Debug */, 442 | 5DBDF21518D5F26B005B1E18 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = 5DBDF1D918D5F26B005B1E18 /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | @implementation AppDelegate 11 | 12 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 13 | { 14 | return YES; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/ChildView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/ChildViewUsingAutolayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | /// A view that holds a variable amount of text and adjusts its intrinsic content size accordingly. 11 | @interface ChildViewUsingAutolayout : UIView 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *textLabel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/ChildViewUsingAutolayout.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "ChildViewUsingAutolayout.h" 9 | 10 | @implementation ChildViewUsingAutolayout 11 | 12 | - (instancetype)initWithFrame:(CGRect)frame 13 | { 14 | self = [super initWithFrame:frame]; 15 | if (self != nil) { 16 | [self updateText]; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)awakeFromNib 22 | { 23 | [super awakeFromNib]; 24 | [self updateText]; 25 | } 26 | 27 | - (IBAction)changeTextButtonTapped:(UIButton *)sender 28 | { 29 | [self updateText]; 30 | } 31 | 32 | - (void)updateText 33 | { 34 | self.textLabel.text = [[self class] randomText]; 35 | 36 | // Update our bounds to "notify" the `OLEContainerScrollView` to recompute its layout. 37 | // TODO: This is a hack, not a very good solution. If we want `OLEContainerScrollView` to work 38 | // properly with subviews using auto layout, we probably have to change it completely to use 39 | // constraints for everything. 40 | CGSize fittingSize = [self systemLayoutSizeFittingSize:UILayoutFittingExpandedSize]; 41 | self.bounds = (CGRect){ CGPointZero, fittingSize }; 42 | } 43 | 44 | + (NSString *)randomText 45 | { 46 | NSArray *texts = @[ 47 | @"Lorem ipsum dolor sit amet.", 48 | @"Lorem ipsum dolor sit amet, consectetur adipisicing elit.", 49 | @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 50 | @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 51 | @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", 52 | @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 53 | ]; 54 | u_int32_t randomIndex = arc4random_uniform((u_int32_t)texts.count); 55 | return texts[randomIndex]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/ChildViewUsingAutolayout.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/CustomContainerScrollViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface CustomContainerScrollViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/CustomContainerScrollViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "CustomContainerScrollViewController.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "OLESimulatedTableView.h" 11 | 12 | @interface CustomContainerScrollViewController () 13 | 14 | @property (nonatomic) OLEContainerScrollView *containerScrollView; 15 | @property (nonatomic) OLESimulatedTableView *tableView1; 16 | @property (nonatomic) OLESimulatedTableView *tableView2; 17 | 18 | @end 19 | 20 | @implementation CustomContainerScrollViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | 26 | self.containerScrollView = [[OLEContainerScrollView alloc] initWithFrame:CGRectZero]; 27 | self.containerScrollView.clipsToBounds = NO; 28 | self.containerScrollView.backgroundColor = [UIColor clearColor]; 29 | self.containerScrollView.layer.borderWidth = 2.0; 30 | self.containerScrollView.layer.borderColor = [[UIColor blackColor] CGColor]; 31 | [self.view addSubview:self.containerScrollView]; 32 | 33 | self.tableView1 = [[OLESimulatedTableView alloc] initWithNumberOfRows:9 rowHeight:72 edgeInsets:UIEdgeInsetsMake(16, 16, 16, 16) cellSpacing:4]; 34 | self.tableView1.backgroundColor = [UIColor colorWithHue:0.562 saturation:0.295 brightness:0.943 alpha:1]; 35 | self.tableView1.cellColor = [UIColor colorWithHue:0.564 saturation:0.709 brightness:0.768 alpha:1]; 36 | self.tableView1.contentSizeOutlineColor = [UIColor colorWithHue:0.992 saturation:0.654 brightness:0.988 alpha:1]; 37 | [self.containerScrollView.contentView addSubview:self.tableView1]; 38 | 39 | self.tableView2 = [[OLESimulatedTableView alloc] initWithNumberOfRows:12 rowHeight:48 edgeInsets:UIEdgeInsetsMake(16, 16, 16, 16) cellSpacing:4]; 40 | self.tableView2.backgroundColor = [UIColor colorWithHue:0.113 saturation:0.235 brightness:0.98 alpha:1]; 41 | self.tableView2.cellColor = [UIColor colorWithHue:0.117 saturation:0.665 brightness:0.984 alpha:1]; 42 | self.tableView2.contentSizeOutlineColor = [UIColor colorWithHue:0.992 saturation:0.654 brightness:0.988 alpha:1]; 43 | [self.containerScrollView.contentView addSubview:self.tableView2]; 44 | } 45 | 46 | - (void)viewDidLayoutSubviews 47 | { 48 | [super viewDidLayoutSubviews]; 49 | 50 | CGSize containerViewSize = CGSizeMake(320, 568); 51 | CGPoint containerViewOrigin = CGPointMake(CGRectGetMidX(self.view.bounds) - containerViewSize.width / 2, self.topLayoutGuide.length + 100); 52 | self.containerScrollView.frame = (CGRect){ containerViewOrigin, containerViewSize }; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "ipad", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "1x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "extent" : "full-screen", 14 | "minimum-system-version" : "7.0", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "orientation" : "portrait", 19 | "idiom" : "ipad", 20 | "extent" : "full-screen", 21 | "minimum-system-version" : "7.0", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "orientation" : "landscape", 26 | "idiom" : "ipad", 27 | "extent" : "full-screen", 28 | "minimum-system-version" : "7.0", 29 | "scale" : "2x" 30 | } 31 | ], 32 | "info" : { 33 | "version" : 1, 34 | "author" : "xcode" 35 | } 36 | } -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/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 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/MultipleCollectionsViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface MultipleCollectionsViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/MultipleCollectionsViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "MultipleCollectionsViewController.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "UIColor+RandomColor.h" 11 | 12 | @interface MultipleCollectionsViewController () 13 | 14 | @property (nonatomic) IBOutlet OLEContainerScrollView *containerScrollView; 15 | @property (nonatomic) NSMutableArray *collectionViews; 16 | @property (nonatomic) NSMutableArray *numberOfItemsPerCollectionView; 17 | @property (nonatomic) NSMutableArray *cellColorPerCollectionView; 18 | 19 | @end 20 | 21 | @implementation MultipleCollectionsViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | 27 | NSInteger numberOfCollectionViews = 3; 28 | self.collectionViews = [NSMutableArray new]; 29 | self.numberOfItemsPerCollectionView = [NSMutableArray new]; 30 | self.cellColorPerCollectionView = [NSMutableArray new]; 31 | 32 | for (NSInteger collectionViewIndex = 0; collectionViewIndex < numberOfCollectionViews; collectionViewIndex++) { 33 | UIView *collectionView = [self preconfiguredCollectionView]; 34 | NSInteger randomNumberOfItemsInCollectionView = arc4random_uniform(50) + 10; 35 | [self.collectionViews addObject:collectionView]; 36 | [self.numberOfItemsPerCollectionView addObject:@(randomNumberOfItemsInCollectionView)]; 37 | [self.cellColorPerCollectionView addObject:[UIColor randomColor]]; 38 | [self.containerScrollView.contentView addSubview:collectionView]; 39 | } 40 | } 41 | 42 | - (UICollectionView *)preconfiguredCollectionView 43 | { 44 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 45 | layout.itemSize = CGSizeMake(100, 100); 46 | layout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20); 47 | UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; 48 | collectionView.delegate = self; 49 | collectionView.dataSource = self; 50 | [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyCell"]; 51 | collectionView.backgroundColor = [UIColor whiteColor]; 52 | return collectionView; 53 | } 54 | 55 | - (IBAction)addCell:(id)sender 56 | { 57 | NSInteger collectionViewIndex = 0; 58 | UICollectionView *collectionView = self.collectionViews[collectionViewIndex]; 59 | NSInteger previousNumberOfItemsInCollectionView = [self.numberOfItemsPerCollectionView[collectionViewIndex] integerValue]; 60 | NSInteger newNumberOfItemsInCollectionView = previousNumberOfItemsInCollectionView + 1; 61 | self.numberOfItemsPerCollectionView[collectionViewIndex] = @(newNumberOfItemsInCollectionView); 62 | [collectionView performBatchUpdates:^{ 63 | NSIndexPath *indexPathOfAddedCell = [NSIndexPath indexPathForItem:previousNumberOfItemsInCollectionView inSection:0]; 64 | [collectionView insertItemsAtIndexPaths:@[ indexPathOfAddedCell ]]; 65 | } completion:nil]; 66 | } 67 | 68 | #pragma mark - UICollectionViewDataSource 69 | 70 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 71 | { 72 | return 1; 73 | } 74 | 75 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 76 | { 77 | NSUInteger collectionViewIndex = [self.collectionViews indexOfObject:collectionView]; 78 | NSInteger numberOfItemsInCollectionView = [self.numberOfItemsPerCollectionView[collectionViewIndex] integerValue]; 79 | return numberOfItemsInCollectionView; 80 | } 81 | 82 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 83 | { 84 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath]; 85 | NSUInteger collectionViewIndex = [self.collectionViews indexOfObject:collectionView]; 86 | UIColor *cellColor = self.cellColorPerCollectionView[collectionViewIndex]; 87 | cell.backgroundColor = cellColor; 88 | return cell; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/MultipleTableViewsViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface MultipleTableViewsViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/MultipleTableViewsViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "MultipleTableViewsViewController.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "UIColor+RandomColor.h" 11 | 12 | @interface MultipleTableViewsViewController () 13 | 14 | @property (nonatomic) IBOutlet OLEContainerScrollView *containerScrollView; 15 | @property (nonatomic) NSMutableArray *tableViews; 16 | @property (nonatomic) NSMutableArray *numberOfRowsPerTableView; 17 | @property (nonatomic) NSMutableArray *cellColorsPerTableView; 18 | 19 | @end 20 | 21 | @implementation MultipleTableViewsViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | 27 | NSInteger numberOfTableViews = 3; 28 | self.tableViews = [NSMutableArray new]; 29 | self.numberOfRowsPerTableView = [NSMutableArray new]; 30 | self.cellColorsPerTableView = [NSMutableArray new]; 31 | 32 | for (NSInteger collectionViewIndex = 0; collectionViewIndex < numberOfTableViews; collectionViewIndex++) { 33 | UIView *tableView = [self preconfiguredTableView]; 34 | NSInteger randomNumberOfRows = 10 + arc4random_uniform(10); 35 | [self.tableViews addObject:tableView]; 36 | [self.numberOfRowsPerTableView addObject:@(randomNumberOfRows)]; 37 | [self.cellColorsPerTableView addObject:[UIColor randomColor]]; 38 | [self.containerScrollView.contentView addSubview:tableView]; 39 | } 40 | } 41 | 42 | - (UITableView *)preconfiguredTableView 43 | { 44 | UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 45 | tableView.delegate = self; 46 | tableView.dataSource = self; 47 | [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"MyCell"]; 48 | tableView.backgroundColor = [UIColor whiteColor]; 49 | return tableView; 50 | } 51 | 52 | - (IBAction)addCell:(id)sender 53 | { 54 | NSInteger tableViewIndex = 0; 55 | UITableView *tableView = self.tableViews[tableViewIndex]; 56 | NSInteger previousNumberOfRows = [self.numberOfRowsPerTableView[tableViewIndex] integerValue]; 57 | NSInteger newNumberOfRows = previousNumberOfRows + 1; 58 | self.numberOfRowsPerTableView[tableViewIndex] = @(newNumberOfRows); 59 | 60 | [tableView beginUpdates]; 61 | NSIndexPath *indexPathOfAddedCell = [NSIndexPath indexPathForItem:previousNumberOfRows inSection:0]; 62 | [tableView insertRowsAtIndexPaths:@[ indexPathOfAddedCell ] withRowAnimation:UITableViewRowAnimationTop]; 63 | [tableView endUpdates]; 64 | } 65 | 66 | #pragma mark - UITableViewDataSource 67 | 68 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 69 | { 70 | return 1; 71 | } 72 | 73 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 74 | { 75 | NSUInteger tableViewIndex = [self.tableViews indexOfObject:tableView]; 76 | NSInteger numberOfRowsInTableView = [self.numberOfRowsPerTableView[tableViewIndex] integerValue]; 77 | return numberOfRowsInTableView; 78 | } 79 | 80 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 81 | { 82 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; 83 | NSUInteger tableViewIndex = [self.tableViews indexOfObject:tableView]; 84 | UIColor *cellColor = self.cellColorsPerTableView[tableViewIndex]; 85 | cell.backgroundColor = cellColor; 86 | return cell; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/NaiveContainerScrollViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface NaiveContainerScrollViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/NaiveContainerScrollViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "NaiveContainerScrollViewController.h" 9 | #import "OLESimulatedTableView.h" 10 | 11 | @interface NaiveContainerScrollViewController () 12 | 13 | @property (nonatomic) UIScrollView *containerScrollView; 14 | @property (nonatomic) OLESimulatedTableView *tableView1; 15 | @property (nonatomic) OLESimulatedTableView *tableView2; 16 | 17 | @end 18 | 19 | @implementation NaiveContainerScrollViewController 20 | 21 | static void *KVOContext = &KVOContext; 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | 27 | self.containerScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; 28 | self.containerScrollView.clipsToBounds = NO; 29 | self.containerScrollView.backgroundColor = [UIColor clearColor]; 30 | self.containerScrollView.layer.borderWidth = 2.0; 31 | self.containerScrollView.layer.borderColor = [[UIColor blackColor] CGColor]; 32 | [self.view addSubview:self.containerScrollView]; 33 | 34 | self.tableView1 = [[OLESimulatedTableView alloc] initWithNumberOfRows:9 rowHeight:72 edgeInsets:UIEdgeInsetsMake(16, 16, 16, 16) cellSpacing:4]; 35 | self.tableView1.backgroundColor = [UIColor colorWithHue:0.562 saturation:0.295 brightness:0.943 alpha:1]; 36 | self.tableView1.cellColor = [UIColor colorWithHue:0.564 saturation:0.709 brightness:0.768 alpha:1]; 37 | self.tableView1.contentSizeOutlineColor = [UIColor colorWithHue:0.992 saturation:0.654 brightness:0.988 alpha:1]; 38 | [self.containerScrollView addSubview:self.tableView1]; 39 | 40 | self.tableView2 = [[OLESimulatedTableView alloc] initWithNumberOfRows:12 rowHeight:48 edgeInsets:UIEdgeInsetsMake(16, 16, 16, 16) cellSpacing:4]; 41 | self.tableView2.backgroundColor = [UIColor colorWithHue:0.113 saturation:0.235 brightness:0.98 alpha:1]; 42 | self.tableView2.cellColor = [UIColor colorWithHue:0.117 saturation:0.665 brightness:0.984 alpha:1]; 43 | self.tableView2.contentSizeOutlineColor = [UIColor colorWithHue:0.992 saturation:0.654 brightness:0.988 alpha:1]; 44 | [self.containerScrollView addSubview:self.tableView2]; 45 | } 46 | 47 | - (void)viewWillAppear:(BOOL)animated 48 | { 49 | [super viewWillAppear:animated]; 50 | [self.tableView1 addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:0 context:KVOContext]; 51 | [self.tableView2 addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:0 context:KVOContext]; 52 | } 53 | 54 | - (void)viewDidDisappear:(BOOL)animated 55 | { 56 | [self.tableView1 removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:KVOContext]; 57 | [self.tableView2 removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:KVOContext]; 58 | [super viewDidDisappear:animated]; 59 | } 60 | 61 | - (void)viewDidLayoutSubviews 62 | { 63 | [super viewDidLayoutSubviews]; 64 | 65 | CGSize containerViewSize = CGSizeMake(320, 568); 66 | CGPoint containerViewOrigin = CGPointMake(CGRectGetMidX(self.view.bounds) - containerViewSize.width / 2, self.topLayoutGuide.length + 100); 67 | self.containerScrollView.frame = (CGRect){ containerViewOrigin, containerViewSize }; 68 | 69 | self.tableView1.frame = CGRectMake(0, 0, self.containerScrollView.bounds.size.width, self.tableView1.contentSize.height); 70 | self.tableView2.frame = CGRectMake(0, self.tableView1.contentSize.height, self.containerScrollView.bounds.size.width, self.tableView2.contentSize.height); 71 | 72 | self.containerScrollView.contentSize = CGSizeMake(self.containerScrollView.bounds.size.width, self.tableView1.contentSize.height + self.tableView2.contentSize.height); 73 | } 74 | 75 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 76 | { 77 | if (context == KVOContext) { 78 | [self.view setNeedsLayout]; 79 | } else { 80 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 81 | } 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLEBorderedView.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface OLEBorderedView : UIView 11 | 12 | @property (nonatomic) CGFloat borderWidth; 13 | @property (nonatomic) UIColor *borderColor; 14 | @property (nonatomic) NSArray *lineDashPattern; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLEBorderedView.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "OLEBorderedView.h" 9 | 10 | @interface OLEBorderedView () 11 | 12 | @property (nonatomic, readonly) CAShapeLayer *shapeLayer; 13 | 14 | @end 15 | 16 | @implementation OLEBorderedView 17 | 18 | - (id)initWithFrame:(CGRect)frame 19 | { 20 | self = [super initWithFrame:frame]; 21 | if (self == nil) { 22 | return nil; 23 | } 24 | [self commonInitForOLEBorderedView]; 25 | return self; 26 | } 27 | 28 | - (id)initWithCoder:(NSCoder *)decoder 29 | { 30 | self = [super initWithCoder:decoder]; 31 | if (self == nil) { 32 | return nil; 33 | } 34 | [self commonInitForOLEBorderedView]; 35 | return self; 36 | } 37 | 38 | - (void)commonInitForOLEBorderedView 39 | { 40 | self.shapeLayer.fillColor = NULL; 41 | [self updateBorderPath]; 42 | } 43 | 44 | + (Class)layerClass 45 | { 46 | return [CAShapeLayer class]; 47 | } 48 | 49 | - (CAShapeLayer *)shapeLayer 50 | { 51 | return (CAShapeLayer *)self.layer; 52 | } 53 | 54 | //- (CGFloat)borderWidth 55 | //{ 56 | // return self.layer.borderWidth; 57 | //} 58 | // 59 | //- (void)setBorderWidth:(CGFloat)borderWidth 60 | //{ 61 | // self.layer.borderWidth = borderWidth; 62 | //} 63 | // 64 | //- (UIColor *)borderColor 65 | //{ 66 | // if (self.layer.borderColor == NULL) { 67 | // return nil; 68 | // } 69 | // return [UIColor colorWithCGColor:self.layer.borderColor]; 70 | //} 71 | // 72 | //- (void)setBorderColor:(UIColor *)borderColor 73 | //{ 74 | // self.layer.borderColor = [borderColor CGColor]; 75 | //} 76 | 77 | - (void)updateBorderPath 78 | { 79 | CGRect borderBounds = CGRectInset(self.bounds, self.borderWidth/2, self.borderWidth/2); 80 | UIBezierPath *border = [UIBezierPath bezierPathWithRect:borderBounds]; 81 | self.shapeLayer.path = [border CGPath]; 82 | } 83 | 84 | - (void)setFrame:(CGRect)frame 85 | { 86 | [super setFrame:frame]; 87 | [self updateBorderPath]; 88 | } 89 | 90 | - (void)setBounds:(CGRect)bounds 91 | { 92 | [super setBounds:bounds]; 93 | [self updateBorderPath]; 94 | } 95 | 96 | - (CGFloat)borderWidth 97 | { 98 | return self.shapeLayer.lineWidth; 99 | } 100 | 101 | - (void)setBorderWidth:(CGFloat)borderWidth 102 | { 103 | self.shapeLayer.lineWidth = borderWidth; 104 | } 105 | 106 | - (UIColor *)borderColor 107 | { 108 | if (self.shapeLayer.strokeColor == nil) { 109 | return nil; 110 | } 111 | return [UIColor colorWithCGColor:self.shapeLayer.strokeColor]; 112 | } 113 | 114 | - (void)setBorderColor:(UIColor *)borderColor 115 | { 116 | self.shapeLayer.strokeColor = [borderColor CGColor]; 117 | } 118 | 119 | - (NSArray *)lineDashPattern 120 | { 121 | return self.shapeLayer.lineDashPattern; 122 | } 123 | 124 | - (void)setLineDashPattern:(NSArray *)lineDashPattern 125 | { 126 | self.shapeLayer.lineDashPattern = lineDashPattern; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLEContainerScrollViewDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLESimulatedTableView.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | /** 11 | * A view that can be used to illustrate how cell reuse works in UITableView. 12 | * 13 | * It creates a scrollable list of dummy cells (with no content). Dashed outlines 14 | * represent cells that exist conceptually but do not get instantiated until they 15 | * are scrolled into the visible bounds of the table view. 16 | * 17 | * When you place this view into your view hierarchy, you should give it a top and 18 | * bottom margin in order to see how cells get added and removed during scrolling. 19 | */ 20 | @interface OLESimulatedTableView : UIScrollView 21 | 22 | - (instancetype)initWithNumberOfRows:(NSUInteger)numberOfRows rowHeight:(CGFloat)rowHeight edgeInsets:(UIEdgeInsets)edgeInsets cellSpacing:(CGFloat)cellSpacing; 23 | 24 | @property (nonatomic) BOOL showBoundsOutline; 25 | @property (nonatomic) UIColor *cellColor; 26 | @property (nonatomic) UIColor *contentSizeOutlineColor; 27 | @property (nonatomic, readonly) NSUInteger numberOfRows; 28 | @property (nonatomic, readonly) CGFloat rowHeight; 29 | @property (nonatomic, readonly) CGFloat cellSpacing; 30 | @property (nonatomic, readonly) UIEdgeInsets edgeInsets; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/OLESimulatedTableView.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "OLESimulatedTableView.h" 9 | #import "OLEBorderedView.h" 10 | 11 | typedef OLEBorderedView OLESimulatedTableViewCell; 12 | 13 | 14 | @interface OLESimulatedTableView () 15 | 16 | @property (nonatomic, strong) NSMutableArray *cells; 17 | @property (nonatomic, strong) NSMutableSet *visibleCells; 18 | @property (nonatomic, strong) OLEBorderedView *contentSizeView; 19 | @property (nonatomic, strong) OLEBorderedView *boundsView; 20 | 21 | @end 22 | 23 | 24 | @implementation OLESimulatedTableView 25 | 26 | - (instancetype)initWithNumberOfRows:(NSUInteger)numberOfRows rowHeight:(CGFloat)rowHeight edgeInsets:(UIEdgeInsets)edgeInsets cellSpacing:(CGFloat)cellSpacing 27 | { 28 | self = [super initWithFrame:CGRectZero]; 29 | if (self == nil) { 30 | return nil; 31 | } 32 | 33 | _numberOfRows = numberOfRows; 34 | _rowHeight = rowHeight; 35 | _cellSpacing = cellSpacing; 36 | _edgeInsets = edgeInsets; 37 | 38 | [self commonInitForSimulatedTableView]; 39 | 40 | return self; 41 | } 42 | 43 | - (id)initWithFrame:(CGRect)frame 44 | { 45 | self = [self initWithNumberOfRows:10 rowHeight:44.0 edgeInsets:UIEdgeInsetsMake(5, 5, 5, 5) cellSpacing:5]; 46 | self.frame = frame; 47 | return self; 48 | } 49 | 50 | - (id)initWithCoder:(NSCoder *)decoder 51 | { 52 | self = [super initWithCoder:decoder]; 53 | if (self == nil) { 54 | return nil; 55 | } 56 | 57 | _numberOfRows = 10; 58 | _rowHeight = 44.0; 59 | _cellSpacing = 5.0; 60 | _edgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); 61 | 62 | [self commonInitForSimulatedTableView]; 63 | 64 | return self; 65 | } 66 | 67 | - (void)commonInitForSimulatedTableView 68 | { 69 | self.clipsToBounds = NO; 70 | 71 | _showBoundsOutline = NO; 72 | _cellColor = [UIColor blackColor]; 73 | _contentSizeOutlineColor = [UIColor redColor]; 74 | 75 | _cells = [NSMutableArray arrayWithCapacity:_numberOfRows]; 76 | _visibleCells = [NSMutableSet setWithCapacity:_numberOfRows]; 77 | 78 | [self _setupCells]; 79 | [self _setupContentSizeView]; 80 | [self _setupBoundsView]; 81 | } 82 | 83 | - (void)_setupCells 84 | { 85 | for (OLESimulatedTableViewCell *existingCell in self.cells) { 86 | [existingCell removeFromSuperview]; 87 | } 88 | [self.cells removeAllObjects]; 89 | 90 | for (NSUInteger rowIndex = 0; rowIndex < _numberOfRows; rowIndex++) { 91 | OLESimulatedTableViewCell *cell = [[OLESimulatedTableViewCell alloc] initWithFrame:CGRectZero]; 92 | cell.translatesAutoresizingMaskIntoConstraints = NO; 93 | cell.borderColor = self.cellColor; 94 | cell.borderWidth = 2.0; 95 | [self addSubview:cell]; 96 | [self.cells addObject:cell]; 97 | [self _toggleCellVisibilityIfNeeded:cell animated:NO]; 98 | } 99 | 100 | [self setNeedsLayout]; 101 | } 102 | 103 | - (void)_setupBoundsView 104 | { 105 | self.boundsView = [[OLEBorderedView alloc] initWithFrame:CGRectZero]; 106 | self.boundsView.translatesAutoresizingMaskIntoConstraints = NO; 107 | self.boundsView.backgroundColor = [UIColor clearColor]; 108 | self.boundsView.borderColor = [UIColor blueColor]; 109 | self.boundsView.borderWidth = 2.0; 110 | self.boundsView.lineDashPattern = @[ @10, @10 ]; 111 | [self addSubview:self.boundsView]; 112 | 113 | [self setNeedsLayout]; 114 | } 115 | 116 | - (void)_setupContentSizeView 117 | { 118 | self.contentSizeView = [[OLEBorderedView alloc] initWithFrame:CGRectZero]; 119 | self.contentSizeView.translatesAutoresizingMaskIntoConstraints = NO; 120 | self.contentSizeView.backgroundColor = [UIColor clearColor]; 121 | self.contentSizeView.borderColor = self.contentSizeOutlineColor; 122 | self.contentSizeView.borderWidth = 2.0; 123 | self.contentSizeView.lineDashPattern = @[ @10, @10 ]; 124 | [self insertSubview:self.contentSizeView atIndex:0]; 125 | 126 | [self setNeedsLayout]; 127 | } 128 | 129 | #pragma mark - Layout 130 | 131 | - (void)layoutSubviews 132 | { 133 | [super layoutSubviews]; 134 | 135 | CGRect bounds = self.bounds; 136 | 137 | CGFloat cellWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right; 138 | CGFloat cumulativeCellHeight = _numberOfRows * _rowHeight; 139 | CGFloat cumulativeCellSpacingHeight = _numberOfRows > 0 ? (_numberOfRows - 1) * _cellSpacing : 0.0; 140 | CGFloat contentHeight = _edgeInsets.top + cumulativeCellHeight + cumulativeCellSpacingHeight + _edgeInsets.bottom; 141 | 142 | CGSize newContentSize = CGSizeMake(bounds.size.width, contentHeight); 143 | if (!CGSizeEqualToSize(self.contentSize, newContentSize)) { 144 | self.contentSize = newContentSize; 145 | } 146 | [self.cells enumerateObjectsUsingBlock:^(OLESimulatedTableViewCell *cell, NSUInteger idx, BOOL *stop) { 147 | CGFloat cellYPosition = _edgeInsets.top + (idx * (_rowHeight + _cellSpacing)); 148 | cell.frame = CGRectMake(_edgeInsets.left, cellYPosition, cellWidth, _rowHeight); 149 | [self _toggleCellVisibilityIfNeeded:cell animated:YES]; 150 | }]; 151 | 152 | self.boundsView.hidden = !self.showBoundsOutline; 153 | CGFloat boundsViewHorizontalMargin = floor(_edgeInsets.left / 3); 154 | CGFloat boundsViewVerticalMargin = floor(_edgeInsets.top / 3); 155 | self.boundsView.frame = CGRectInset(bounds, boundsViewHorizontalMargin, boundsViewVerticalMargin); 156 | 157 | CGFloat contentSizeViewHorizontalMargin = self.showBoundsOutline ? floor(_edgeInsets.left / 3 * 2) : floor(_edgeInsets.left / 2); 158 | CGFloat contentSizeViewVerticalMargin = self.showBoundsOutline ? floor(_edgeInsets.top / 3 * 2) : floor(_edgeInsets.top / 2); 159 | CGRect contentSizeViewFrame = { CGPointZero, self.contentSize }; 160 | contentSizeViewFrame = CGRectInset(contentSizeViewFrame, contentSizeViewHorizontalMargin, contentSizeViewVerticalMargin); 161 | self.contentSizeView.frame = contentSizeViewFrame; 162 | self.contentSizeView.borderColor = self.contentSizeOutlineColor; 163 | } 164 | 165 | - (void)setShowBoundsOutline:(BOOL)showBoundsOutline 166 | { 167 | _showBoundsOutline = showBoundsOutline; 168 | [self setNeedsLayout]; 169 | } 170 | 171 | - (void)setCellColor:(UIColor *)cellColor 172 | { 173 | _cellColor = cellColor; 174 | 175 | for (OLESimulatedTableViewCell *cell in self.cells) { 176 | cell.backgroundColor = cellColor; 177 | cell.borderColor = cellColor; 178 | } 179 | } 180 | 181 | #pragma mark - Manage Cell Visibility 182 | 183 | - (void)_toggleCellVisibilityIfNeeded:(OLESimulatedTableViewCell *)cell animated:(BOOL)animated 184 | { 185 | BOOL cellShouldBeVisible = [self _shouldCellBeVisible:cell]; 186 | BOOL cellIsVisible = [self _isCellVisible:cell]; 187 | 188 | BOOL shouldShowCell = cellShouldBeVisible && !cellIsVisible; 189 | BOOL shouldHideCell = cellIsVisible && !cellShouldBeVisible; 190 | 191 | if (shouldShowCell) { 192 | [self _makeCellVisible:cell animated:animated]; 193 | } else if (shouldHideCell) { 194 | [self _makeCellInvisible:cell animated:animated]; 195 | } 196 | } 197 | 198 | - (BOOL)_shouldCellBeVisible:(OLESimulatedTableViewCell *)cell 199 | { 200 | return CGRectIntersectsRect(cell.frame, self.bounds); 201 | } 202 | 203 | - (BOOL)_isCellVisible:(OLESimulatedTableViewCell *)cell 204 | { 205 | return [self.visibleCells containsObject:cell]; 206 | } 207 | 208 | - (void)_makeCellVisible:(OLESimulatedTableViewCell *)cell animated:(BOOL)animated 209 | { 210 | BOOL isAlreadyVisible = [self _isCellVisible:cell]; 211 | if (isAlreadyVisible) { 212 | return; 213 | } 214 | 215 | [self.visibleCells addObject:cell]; 216 | 217 | CABasicAnimation *backgroundColorAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"]; 218 | backgroundColorAnimation.fromValue = (id)cell.layer.backgroundColor; 219 | backgroundColorAnimation.duration = 0.15; 220 | 221 | cell.layer.backgroundColor = [self.cellColor CGColor]; 222 | cell.lineDashPattern = nil; 223 | 224 | if (animated) { 225 | [cell.layer addAnimation:backgroundColorAnimation forKey:@"showCell"]; 226 | } 227 | } 228 | 229 | - (void)_makeCellInvisible:(OLESimulatedTableViewCell *)cell animated:(BOOL)animated 230 | { 231 | BOOL isAlreadyInvisible = ![self _isCellVisible:cell]; 232 | if (isAlreadyInvisible) { 233 | return; 234 | } 235 | 236 | [self.visibleCells removeObject:cell]; 237 | 238 | CABasicAnimation *backgroundColorAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"]; 239 | backgroundColorAnimation.fromValue = (id)cell.layer.backgroundColor; 240 | backgroundColorAnimation.duration = 0.15; 241 | 242 | cell.backgroundColor = [UIColor clearColor]; 243 | cell.lineDashPattern = @[ @10, @10 ]; 244 | 245 | if (animated) { 246 | [cell.layer addAnimation:backgroundColorAnimation forKey:@"showCell"]; 247 | } 248 | } 249 | 250 | @end 251 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SimpleTableViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface SimpleTableViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SimpleTableViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "SimpleTableViewController.h" 9 | #import "OLESimulatedTableView.h" 10 | 11 | @interface SimpleTableViewController () 12 | 13 | @property (nonatomic) OLESimulatedTableView *tableView; 14 | 15 | @end 16 | 17 | @implementation SimpleTableViewController 18 | 19 | - (void)viewDidLoad 20 | { 21 | [super viewDidLoad]; 22 | 23 | self.tableView = [[OLESimulatedTableView alloc] initWithNumberOfRows:14 rowHeight:72 edgeInsets:UIEdgeInsetsMake(16, 16, 16, 16) cellSpacing:4]; 24 | self.tableView.backgroundColor = [UIColor colorWithHue:0.562 saturation:0.295 brightness:0.943 alpha:1]; 25 | self.tableView.cellColor = [UIColor colorWithHue:0.564 saturation:0.709 brightness:0.768 alpha:1]; 26 | self.tableView.contentSizeOutlineColor = [UIColor colorWithHue:0.992 saturation:0.654 brightness:0.988 alpha:1]; 27 | [self.view addSubview:self.tableView]; 28 | } 29 | 30 | - (void)viewDidLayoutSubviews 31 | { 32 | [super viewDidLayoutSubviews]; 33 | 34 | CGSize tableViewSize = CGSizeMake(320, 568); 35 | CGPoint tableViewOrigin = CGPointMake(CGRectGetMidX(self.view.bounds) - tableViewSize.width / 2, self.topLayoutGuide.length + 100); 36 | self.tableView.frame = (CGRect){ tableViewOrigin, tableViewSize }; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SingleViewsContainerViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface SingleViewsContainerViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SingleViewsContainerViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "SingleViewsContainerViewController.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "UIColor+RandomColor.h" 11 | 12 | @interface SingleViewsContainerViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet OLEContainerScrollView *containerScrollView; 15 | 16 | @end 17 | 18 | 19 | @implementation SingleViewsContainerViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | 25 | NSInteger numberOfChildViews = 10; 26 | for (NSInteger childViewIndex = 0; childViewIndex < numberOfChildViews; childViewIndex++) { 27 | CGFloat randomHeight = arc4random_uniform(500) + 100; 28 | UIView *childView = [self preconfiguredChildViewWithHeight:randomHeight]; 29 | [self.containerScrollView.contentView addSubview:childView]; 30 | } 31 | } 32 | 33 | - (UIView *)preconfiguredChildViewWithHeight:(CGFloat)height 34 | { 35 | UINib *nib = [UINib nibWithNibName:@"ChildView" bundle:nil]; 36 | 37 | UIView *view = [[nib instantiateWithOwner:nil options:nil] firstObject]; 38 | view.frame = ({ 39 | CGRect frame = view.frame; 40 | frame.size.height = height; 41 | frame; 42 | }); 43 | view.backgroundColor = [UIColor randomColor]; 44 | return view; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SingleViewsUsingAutolayoutContainerViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface SingleViewsUsingAutolayoutContainerViewController : UIViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/SingleViewsUsingAutolayoutContainerViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "SingleViewsUsingAutolayoutContainerViewController.h" 9 | #import "OLEContainerScrollView.h" 10 | #import "ChildViewUsingAutolayout.h" 11 | 12 | @interface SingleViewsUsingAutolayoutContainerViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet OLEContainerScrollView *containerScrollView; 15 | 16 | @end 17 | 18 | 19 | @implementation SingleViewsUsingAutolayoutContainerViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | 25 | NSInteger numberOfChildViews = 10; 26 | for (NSInteger childViewIndex = 0; childViewIndex < numberOfChildViews; childViewIndex++) { 27 | UIView *childView = [self preconfiguredChildView]; 28 | [self.containerScrollView.contentView addSubview:childView]; 29 | } 30 | } 31 | 32 | - (UIView *)preconfiguredChildView 33 | { 34 | UINib *nib = [UINib nibWithNibName:@"ChildViewUsingAutolayout" bundle:nil]; 35 | ChildViewUsingAutolayout *view = [[nib instantiateWithOwner:nil options:nil] firstObject]; 36 | view.layer.borderWidth = 1.0; 37 | view.layer.borderColor = [UIColor blackColor].CGColor; 38 | return view; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/TableViewWithWebViewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewWithWebViewViewController.h 3 | // OLEContainerScrollViewDemo 4 | // 5 | // Created by Tom Kraina on 18.7.2018. 6 | // Copyright © 2018 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableViewWithWebViewViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/TableViewWithWebViewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewWithWebViewViewController.m 3 | // OLEContainerScrollViewDemo 4 | // 5 | // Created by Tom Kraina on 18.7.2018. 6 | // Copyright © 2018 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import "TableViewWithWebViewViewController.h" 10 | #import "OLEContainerScrollView.h" 11 | #import "UIColor+RandomColor.h" 12 | 13 | @interface TableViewWithWebViewViewController () 14 | @property (nonatomic) IBOutlet OLEContainerScrollView *containerScrollView; 15 | @property (nonatomic) NSMutableArray *tableViews; 16 | @property (nonatomic) NSMutableArray *numberOfRowsPerTableView; 17 | @property (nonatomic) NSMutableArray *cellColorsPerTableView; 18 | @end 19 | 20 | 21 | @import WebKit; 22 | 23 | // Make WKWebView conform to OLEContainerScrollViewScrollable in order to work correcly 24 | @interface WKWebView () 25 | @end 26 | 27 | 28 | @implementation TableViewWithWebViewViewController 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | 34 | self.tableViews = [NSMutableArray new]; 35 | self.numberOfRowsPerTableView = [NSMutableArray new]; 36 | self.cellColorsPerTableView = [NSMutableArray new]; 37 | 38 | [self addTableView]; 39 | 40 | WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero]; 41 | [self.containerScrollView.contentView addSubview:webView]; 42 | NSURL *url = [[NSURL alloc] initWithString: @"https://oleb.net"]; 43 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url]; 44 | [webView loadRequest:request]; 45 | 46 | [self addTableView]; 47 | } 48 | 49 | - (void)addTableView 50 | { 51 | UIView *tableView = [self preconfiguredTableView]; 52 | NSInteger randomNumberOfRows = 10 + arc4random_uniform(10); 53 | [self.tableViews addObject:tableView]; 54 | [self.numberOfRowsPerTableView addObject:@(randomNumberOfRows)]; 55 | [self.cellColorsPerTableView addObject:[UIColor randomColor]]; 56 | [self.containerScrollView.contentView addSubview:tableView]; 57 | } 58 | 59 | - (UITableView *)preconfiguredTableView 60 | { 61 | UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 62 | tableView.delegate = self; 63 | tableView.dataSource = self; 64 | [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"MyCell"]; 65 | tableView.backgroundColor = [UIColor whiteColor]; 66 | return tableView; 67 | } 68 | 69 | - (IBAction)addCell:(id)sender 70 | { 71 | NSInteger tableViewIndex = 0; 72 | UITableView *tableView = self.tableViews[tableViewIndex]; 73 | NSInteger previousNumberOfRows = [self.numberOfRowsPerTableView[tableViewIndex] integerValue]; 74 | NSInteger newNumberOfRows = previousNumberOfRows + 1; 75 | self.numberOfRowsPerTableView[tableViewIndex] = @(newNumberOfRows); 76 | 77 | [tableView beginUpdates]; 78 | NSIndexPath *indexPathOfAddedCell = [NSIndexPath indexPathForItem:previousNumberOfRows inSection:0]; 79 | [tableView insertRowsAtIndexPaths:@[ indexPathOfAddedCell ] withRowAnimation:UITableViewRowAnimationTop]; 80 | [tableView endUpdates]; 81 | } 82 | 83 | #pragma mark - UITableViewDataSource 84 | 85 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 86 | { 87 | return 1; 88 | } 89 | 90 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 91 | { 92 | NSUInteger tableViewIndex = [self.tableViews indexOfObject:tableView]; 93 | NSInteger numberOfRowsInTableView = [self.numberOfRowsPerTableView[tableViewIndex] integerValue]; 94 | return numberOfRowsInTableView; 95 | } 96 | 97 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 98 | { 99 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; 100 | NSUInteger tableViewIndex = [self.tableViews indexOfObject:tableView]; 101 | UIColor *cellColor = self.cellColorsPerTableView[tableViewIndex]; 102 | cell.backgroundColor = cellColor; 103 | return cell; 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/UIColor+RandomColor.h: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | @interface UIColor (RandomColor) 11 | 12 | + (instancetype)randomColor; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/UIColor+RandomColor.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | #import "UIColor+RandomColor.h" 9 | 10 | @implementation UIColor (RandomColor) 11 | 12 | + (instancetype)randomColor 13 | { 14 | return [self colorWithHue:arc4random_uniform(256)/255.0 saturation:1.0 brightness:1.0 alpha:1.0]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /OLEContainerScrollViewDemo/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | OLEContainerScrollView 3 | 4 | Copyright (c) 2014 Ole Begemann. 5 | https://github.com/ole/OLEContainerScrollView 6 | */ 7 | 8 | @import UIKit; 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OLEContainerScrollView 2 | 3 | A `UIScrollView` subclass that intelligently handles multiple child scroll views and does not interfere with UIKitʼs cell reuse functionality. 4 | 5 | Written by Ole Begemann, May 2014. 6 | 7 | ## ⚠️ No maintenance ⚠️ 8 | 9 | **This project is not being maintained. I’m not using it myself and I don’t want to spend time on it adding more features. I still think the code contains some good ideas. Feel free to read the code and take ideas from it and/or maintain your own fork.** 10 | 11 | ## Blog Post 12 | 13 | Please read my [blog post about OLEContainerScrollView](http://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/) for details about the implementation. 14 | 15 | ## Demo App 16 | 17 | To check out the demo app: 18 | 19 | 1. Clone this repository. 20 | 2. Open `OLEContainerScrollViewDemo.xcodeproj` in Xcode. 21 | 22 | The demo app uses a class I have written named `OLESimulatedTableView` to illustrate how a `UITableView` reuses its cells. 23 | 24 | ## Usage 25 | 26 | 1. Manually clone this repository to your machine or add it as a Git submodule to your project. 27 | 2. Drag the folder `OLEContainerScrollView` into your Xcode project to add all the files in it to your project. 28 | 3. `#import "OLEContainerScrollView.h"` 29 | 4. Create an `OLEContainerScrollView` instead of a regular `UIScrollView`. 30 | 5. Add subviews (like table views, collection views, regular scroll views, or just other regular views) to the scroll view’s `contentView`. Check out the comments in `OLEContainerScrollView.h`. 31 | 32 | ## License 33 | 34 | Published under the MIT License. See the `LICENSE` file for details. 35 | --------------------------------------------------------------------------------