UIScrollView使用masonry布局, 必須得給一個固定的contentsize,如果像普通的uiview一樣去布局,那么它將不會具備滾動的效果,只能當做view去使用。
因為UIScrollview內(nèi)部實現(xiàn)原理是依托于已知contentsize去實現(xiàn)的。
使用masonry布局, 首先需要在scrollview上加一個子視圖contentView,通過子視圖的尺寸去撐起scrollview,這樣scrollview就有了具體的contentsize。
具體實現(xiàn)代碼:
myScrollView = [[UIScrollView alloc] init];
[self.view addSubview:myScrollView];
myScrollView.backgroundColor = [UIColor whiteColor];
[myScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
STRONGSELF
make.edges.mas_equalTo(strongSelf.view);
}];
contentView = [[UIView alloc] init];
[myScrollView addSubview:contentView];
contentView.backgroundColor = [UIColor whiteColor];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(myScrollView);
make.width.mas_equalTo(myScrollView);
make.height.greaterThanOrEqualTo(@0.f);//此處保證容器View高度的動態(tài)變化 大于等于0.f的高度
}];
然后將所有的子視圖放在contentView上面,此處的例子是高度不固定,所以scrollview是上下滾動的,所以需要最下面的子視圖來撐起contentview的底部。
UIScrollView實現(xiàn)原理
scrollView繼承自UIView,檢測手指滑動應(yīng)該是在view上放置了一個手勢識別。
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
[panGesture addTarget:self action:@selector(panGestureAction:)];
[self addGestureRecognizer:panGesture];
}
return self;
}
實現(xiàn)scrollview的滾動,首先要理解frame和bounds的概念。
提到它們,大家都知道frame是相對于父視圖坐標系來說自己的位置和尺寸,bounds相對于自身坐標系來說的位置和尺寸,并且origin一般為(0,0)。
當我們嘗試改變bounds的origin時,我們就會發(fā)現(xiàn)視圖本身沒有發(fā)生變化,但是它的子視圖的位置卻發(fā)生了變化,其實當我們改變bounds的origin的時候,視圖本身位置沒有變化,但是由于origin的值是基于自身的坐標系,所以自身坐標系的位置被我們改變了。而子視圖的frame正是基于父視圖的坐標系,當我們更改父視圖bounds中origin的時候子視圖的位置就發(fā)生了變化,這就是實現(xiàn)scrollView的關(guān)鍵點?。?!
- (void)panGestureAction:(UIPanGestureRecognizer *)pan {
// 記錄每次滑動開始時的初始位置
if (pan.state == UIGestureRecognizerStateBegan) {
self.startLocation = self.bounds.origin;
NSLog(@"%@", NSStringFromCGPoint(self.startLocation));
}
// 相對于初始觸摸點的偏移量
if (pan.state == UIGestureRecognizerStateChanged) {
CGPoint point = [pan translationInView:self];
NSLog(@"%@", NSStringFromCGPoint(point));
CGFloat newOriginalX = self.startLocation.x - point.x;
CGFloat newOriginalY = self.startLocation.y - point.y;
CGRect bounds = self.bounds;
bounds.origin = CGPointMake(newOriginalX, newOriginalY);
self.bounds = bounds;
}
}
理解了上邊內(nèi)容的關(guān)鍵點,下邊我們將對我們實現(xiàn)的scrollView做一個簡單的優(yōu)化。通過contentSize限制scrollView的內(nèi)部空間,實現(xiàn)代碼如下
if (newOriginalX < 0) {
newOriginalX = 0;
} else {
CGFloat maxMoveWidth = self.contentSize.width - self.bounds.size.width;
if (newOriginalX > maxMoveWidth) {
newOriginalX = maxMoveWidth;
}
}
if (newOriginalY < 0) {
newOriginalY = 0;
} else {
CGFloat maxMoveHeight = self.contentSize.height - self.bounds.size.height;
if (newOriginalY > maxMoveHeight) {
newOriginalY = maxMoveHeight;
}
通過contentOffset設(shè)置scrollView的初始偏移量,相信大家已經(jīng)懂了如何設(shè)置偏移量了吧?沒錯我們只需設(shè)置view自身bounds的origin是實現(xiàn)代碼如下:
- (void)setContentOffset:(CGPoint)contentOffset {
_contentOffset = contentOffset;
CGRect newBounds = self.bounds;
newBounds.origin = contentOffset;
self.bounds = newBounds;
}
http://www.itdecent.cn/p/a9a1ca54ca54
關(guān)于scrollview原理的內(nèi)容大部分轉(zhuǎn)載了該作者的內(nèi)容。