TCNestScrollPageView.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. //
  2. // ENestScrollPageView.m
  3. // RingtoneDuoduo
  4. //
  5. // Created by 唐天成 on 2020/6/9.
  6. // Copyright © 2020 duoduo. All rights reserved.
  7. //
  8. #import "TCNestScrollPageView.h"
  9. #pragma mark - TCMainScrollView
  10. @interface TCMainScrollView ()
  11. @property (nonatomic, strong) UIView *viewPager;
  12. @end
  13. @implementation TCMainScrollView
  14. /********************** 多手势同时识别 ***************************/
  15. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  16. UIView *otherGestureRecognizerView = otherGestureRecognizer.view;
  17. if( [otherGestureRecognizerView isKindOfClass:[UIScrollView class]] && otherGestureRecognizerView != self && gestureRecognizer == self.panGestureRecognizer && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
  18. if([self.viewArray containsObject:(UIScrollView *)otherGestureRecognizerView]) {
  19. return YES;
  20. } else {
  21. return NO;
  22. }
  23. }
  24. return NO;
  25. }
  26. //1
  27. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
  28. if(gestureRecognizer == self.panGestureRecognizer) {
  29. self.isScrolBySelf = YES;
  30. self.currentSubScrolleView = nil;
  31. UIView *touchView = touch.view;
  32. BOOL isContain = NO;
  33. while (touchView != nil) {
  34. if(touchView == self.viewPager) {
  35. isContain = YES;
  36. break;
  37. }
  38. touchView = [touchView nextResponder];
  39. }
  40. if(isContain) {
  41. touchView = touch.view;
  42. while (touchView != nil) {
  43. if([touchView isKindOfClass:[UIScrollView class]] && touchView != self) {
  44. BOOL canContain = NO;
  45. // if(((UIScrollView *)touchView).contentSize.height > touchView.frame.size.height) {
  46. // canContain = YES;
  47. // } else {
  48. NSInteger scrollCount = 0;
  49. UIView *tempView = touchView;
  50. while (tempView != self.viewPager) {
  51. if([tempView isKindOfClass:[UIScrollView class]]) {
  52. scrollCount++;
  53. }
  54. tempView = [tempView nextResponder];
  55. }
  56. if(scrollCount == 2 && ((UIScrollView *)touchView).contentSize.width <= touchView.frame.size.width) {
  57. canContain = YES;
  58. }
  59. // }
  60. if(canContain) {
  61. self.isScrolBySelf = NO;
  62. self.currentSubScrolleView = (UIScrollView *)touchView;
  63. if(![self.viewArray containsObject:self.currentSubScrolleView]) {
  64. [self.viewArray addObject:self.currentSubScrolleView];
  65. [self.currentSubScrolleView addObserver:self.superview forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
  66. }
  67. break;
  68. }
  69. }
  70. if(touchView == self.viewPager) {
  71. break;
  72. }
  73. touchView = [touchView nextResponder];
  74. }
  75. }
  76. }
  77. return YES;
  78. }
  79. - (NSMutableArray *)viewArray {
  80. if(!_viewArray) {
  81. _viewArray = [NSMutableArray array];
  82. }
  83. return _viewArray;
  84. }
  85. - (void)dealloc {
  86. }
  87. @end
  88. #pragma mark - TCNestScrollParam
  89. @implementation TCNestScrollParam
  90. - (instancetype)init {
  91. if(self = [super init]) {
  92. self.pageType = NestScrollPageViewHeadViewChageType;
  93. self.yOffset = 0;
  94. self.bounces = YES;
  95. }
  96. return self;
  97. }
  98. @end
  99. #pragma mark - TCNestScrollPageView
  100. @interface TCNestScrollPageView ()<UITableViewDataSource, UITableViewDelegate>
  101. @property (nonatomic, strong) TCNestScrollParam *param;
  102. @property (nonatomic, strong) UIView *headerView;
  103. @property (nonatomic, strong) TCMainScrollView *mainTabelView;
  104. @property (nonatomic, strong) UITableViewCell *cell;
  105. @property (nonatomic, strong) UIView *viewPager;
  106. @property (nonatomic, assign) CGFloat lastDy;//mainTableView最后停留位置
  107. @property(nonatomic,assign)BOOL nextReturn;//这个用于记录是否是手动改变contentOffset的conteentOffSet,是的话就不用做监听处理
  108. @property(nonatomic,assign)CGFloat stayHeight;//头部允许向上滚动的距离
  109. @property (nonatomic, assign) CGFloat mainTabExchangeDy;//mainTableView最后变化距离
  110. @end
  111. @implementation TCNestScrollPageView
  112. - (instancetype)initWithFrame:(CGRect)frame headView:(UIView *)headView viewPageView:(UIView *)viewPager nestScrollParam:(TCNestScrollParam *)param {
  113. if(self = [super initWithFrame:frame]) {
  114. self.param = param;
  115. self.lastDy = 0.0;
  116. self.viewPager = viewPager;
  117. [self setupBaseView];
  118. self.headerView = headView;
  119. self.mainTabelView.viewPager = viewPager;
  120. }
  121. return self;
  122. }
  123. - (void)setupBaseView {
  124. self.mainTabelView = [[TCMainScrollView alloc] initWithFrame:self.bounds style:UITableViewStylePlain];
  125. if(@available(iOS 11.0, *)){
  126. self.mainTabelView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//UIScrollView也适用
  127. } else {
  128. self.viewController.automaticallyAdjustsScrollViewInsets = NO;
  129. }
  130. [self.mainTabelView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"reuseIdentifier"];
  131. self.mainTabelView.dataSource = self;
  132. self.mainTabelView.delegate = self;
  133. self.mainTabelView.bounces = self.param.bounces;
  134. self.mainTabelView.showsVerticalScrollIndicator = NO;
  135. [self insertSubview:self.mainTabelView atIndex:0];
  136. }
  137. - (void)setHeaderView:(UIView *)headerView {
  138. _headerView = headerView;
  139. self.stayHeight = ((CGFloat)ceil(headerView.frame.size.height - self.param.yOffset));
  140. self.mainTabelView.tableHeaderView = self.headerView;
  141. }
  142. - (void)resetHeader:(UIView *)headerView {
  143. self.headerView = headerView;
  144. }
  145. - (void)willMoveToSuperview:(UIView *)newSuperview{
  146. [super willMoveToSuperview:newSuperview];
  147. if (newSuperview == nil) {
  148. for (UIScrollView *sc in self.mainTabelView.viewArray) {
  149. [sc removeObserver:self forKeyPath:@"contentOffset"];
  150. }
  151. }
  152. }
  153. #pragma mark - ScrollVieeScroll
  154. //mainScrollView(三种效果都分开写,全写一起我自己都晕了,也不方便其他人阅读)
  155. - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
  156. if(self.param.pageType == NestScrollPageViewHeadViewChageType) {
  157. [self headViewChageTypeScrollViewDidScroll:scrollView];
  158. } else if(self.param.pageType == NestScrollPageViewHeadViewSuckTopType) {
  159. [self headViewSuckTopTypeScrollViewDidScroll:scrollView];
  160. } else if(self.param.pageType == NestScrollPageViewHeadViewNoSuckTopType) {
  161. [self headViewNoSuckTopTypeScrollViewDidScroll:scrollView];
  162. }
  163. if (self.didScrollBlock) {
  164. self.didScrollBlock(scrollView.contentOffset.y);
  165. }
  166. }
  167. //subScrollView(三种效果都分开写,全写一起我自己都晕了,也不方便其他人阅读)
  168. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  169. if ([keyPath isEqualToString:@"contentOffset"]){
  170. if(self.param.pageType == NestScrollPageViewHeadViewChageType) {
  171. [self headViewChageTypeObserveValueForKeyPath:keyPath ofObject:object change:change context:context];
  172. } else if(self.param.pageType == NestScrollPageViewHeadViewSuckTopType) {
  173. [self headViewSuckTopTypeObserveValueForKeyPath:keyPath ofObject:object change:change context:context];
  174. } else if(self.param.pageType == NestScrollPageViewHeadViewNoSuckTopType) {
  175. [self headViewNoSuckTopTypeObserveValueForKeyPath:keyPath ofObject:object change:change context:context];
  176. }
  177. }else{
  178. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  179. }
  180. }
  181. #pragma mark headViewChageType
  182. - (void)headViewChageTypeScrollViewDidScroll:(UIScrollView *)scrollView{
  183. CGFloat dh = scrollView.contentOffset.y;
  184. _mainTabExchangeDy = dh - _lastDy;
  185. if (dh >= _stayHeight) {
  186. scrollView.contentOffset = CGPointMake(0, _stayHeight);
  187. // _lastDy = scrollView.contentOffset.y;
  188. } else if(dh<=0) {
  189. scrollView.contentOffset = CGPointMake(0, 0);
  190. // _lastDy = scrollView.contentOffset.y;
  191. }
  192. _lastDy = scrollView.contentOffset.y;
  193. }
  194. //subScrollView
  195. - (void)headViewChageTypeObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  196. CGFloat new = [change[@"new"] CGPointValue].y;
  197. CGFloat old = [change[@"old"] CGPointValue].y;
  198. NSLog(@"%p %lf %lf %lf %lf %p %d %d %d %@",object,self.mainTabelView.contentOffset.y, ((UIScrollView *)object).contentOffset.y, old, new, object, ((UIScrollView *)object).tracking, ((UIScrollView *)object).dragging,((UIScrollView *)object).decelerating,NSStringFromUIEdgeInsets(((UIScrollView *)object).contentInset));
  199. if (_nextReturn) {
  200. _nextReturn = false;
  201. return;
  202. }
  203. // CGFloat new = [change[@"new"] CGPointValue].y;
  204. // CGFloat old = [change[@"old"] CGPointValue].y;
  205. if (new == old) {
  206. NSLog(@"相等");
  207. return;
  208. }
  209. CGFloat dh = new - old;
  210. if (dh < 0) {
  211. //向下
  212. NSLog(@"向下old : %lf new : %lf %lf %lf",old,new,((UIScrollView *)object).contentSize.height,((UIScrollView *)object).frame.size.height);
  213. if(self.mainTabelView.contentOffset.y > 0 && ((UIScrollView *)object).contentOffset.y < (((UIScrollView *)object).contentSize.height-((UIScrollView *)object).frame.size.height) && self.mainTabelView.dragging == YES) {
  214. _nextReturn = true;
  215. NSLog(@"向下--old : %lf",old);
  216. ((UIScrollView *)object).contentOffset = CGPointMake(0, old);
  217. }
  218. } else if(dh > 0) {
  219. //向上
  220. NSLog(@"向上old : %lf",old);
  221. if(self.mainTabelView.contentOffset.y < _stayHeight && ((UIScrollView *)object).contentOffset.y > 0 && self.mainTabelView.dragging == YES) {
  222. //加一下这个if判断是因为在tableView的情况下,如果只选reloadData之后,不知道为什么会导致contentOffSet莫名其妙加上40,也就是会照成dh>0.然后当执行((UIScrollView *)object).contentOffset = CGPointMake(0, old)后并不管用,tableView会反复让contentOffSet莫名其妙加上40,最后照成崩溃,所以想法:当maintableView的contentoffset变化没有subScrollView的contentOffset变化那么大时,就表示出现了上述行为,那么就不做强制修改contentoffset
  223. NSLog(@"uiuiuiu%lf %lf %lf %lf",_mainTabExchangeDy,self.mainTabelView.contentOffset.y,old,new);
  224. if(dh > (_mainTabExchangeDy/*+2*/)) {
  225. return;
  226. }
  227. _nextReturn = true;
  228. NSLog(@"向上--old : %lf",old);
  229. ((UIScrollView *)object).contentOffset = CGPointMake(0, old);
  230. }
  231. }
  232. }
  233. #pragma mark headViewSuckTopType
  234. - (void)headViewSuckTopTypeScrollViewDidScroll:(UIScrollView *)scrollView{
  235. CGFloat dh = scrollView.contentOffset.y;
  236. if (dh >= _stayHeight) {
  237. scrollView.contentOffset = CGPointMake(0, _stayHeight);
  238. _lastDy = scrollView.contentOffset.y;
  239. } else if(dh<=0) {
  240. scrollView.contentOffset = CGPointMake(0, 0);
  241. _lastDy = scrollView.contentOffset.y;
  242. } else {
  243. UIScrollView *currenSubScrollView = self.mainTabelView.currentSubScrolleView;//nil;
  244. if (currenSubScrollView == nil) {
  245. _lastDy = scrollView.contentOffset.y;
  246. return;
  247. }
  248. NSLog(@"哈哈哈哈%lf %lf %lf ",currenSubScrollView.contentOffset.y,scrollView.contentOffset.y,_stayHeight);
  249. if(currenSubScrollView.contentOffset.y > 0 && (scrollView.contentOffset.y < _stayHeight) && (scrollView.contentOffset.y - _lastDy)<0 && self.mainTabelView.isScrolBySelf == NO) {
  250. NSLog(@"main向下%lf",_lastDy);
  251. //向下拖拽
  252. scrollView.contentOffset = CGPointMake(0, _lastDy);
  253. } else if((scrollView.contentOffset.y - _lastDy)>0 && currenSubScrollView.contentOffset.y<0) {
  254. NSLog(@"main向上%lf",_lastDy);
  255. //向上
  256. scrollView.contentOffset = CGPointMake(0, _lastDy);
  257. }
  258. _lastDy = scrollView.contentOffset.y;
  259. }
  260. }
  261. //subScrollView
  262. - (void)headViewSuckTopTypeObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  263. if (_nextReturn) {
  264. _nextReturn = false;
  265. return;
  266. }
  267. CGFloat new = [change[@"new"] CGPointValue].y;
  268. CGFloat old = [change[@"old"] CGPointValue].y;
  269. if (new == old) {return;}
  270. CGFloat dh = new - old;
  271. if (dh < 0) {
  272. //向下
  273. if(((UIScrollView *)object).contentOffset.y < 0){
  274. if (self.mainTabelView.contentOffset.y > 0) {
  275. _nextReturn = true;
  276. ((UIScrollView *)object).contentOffset = CGPointMake(0, 0);
  277. }
  278. }
  279. }else{
  280. //向上
  281. if (self.mainTabelView.contentOffset.y < _stayHeight) {
  282. if (((UIScrollView *)object).contentOffset.y > 0) {
  283. _nextReturn = true;
  284. if(old < 0) {
  285. old = 0;
  286. }
  287. ((UIScrollView *)object).contentOffset = CGPointMake(0, old);
  288. }
  289. }else{
  290. }
  291. }
  292. }
  293. #pragma mark headViewNoSuckTopType
  294. - (void)headViewNoSuckTopTypeScrollViewDidScroll:(UIScrollView *)scrollView{
  295. NSLog(@" %lf %lf %lf %lf",scrollView.contentOffset.y,_stayHeight,scrollView.contentOffset.y,_lastDy);
  296. CGFloat dh = scrollView.contentOffset.y;
  297. if(_lastDy == dh) {
  298. return;
  299. }
  300. if (dh >= _stayHeight) {
  301. scrollView.contentOffset = CGPointMake(0, _stayHeight);
  302. _lastDy = scrollView.contentOffset.y;
  303. } else {
  304. UIScrollView *currenSubScrollView = self.mainTabelView.currentSubScrolleView;
  305. if (currenSubScrollView == nil) {
  306. _lastDy = scrollView.contentOffset.y;
  307. return;
  308. }
  309. //向下拖拽
  310. if (currenSubScrollView.contentOffset.y > 0 && (self.mainTabelView.contentOffset.y < _stayHeight) && (self.mainTabelView.contentOffset.y - _lastDy)<0 && self.mainTabelView.isScrolBySelf == NO) {
  311. scrollView.contentOffset = CGPointMake(0, _lastDy);
  312. }
  313. _lastDy = scrollView.contentOffset.y;
  314. }
  315. }
  316. //subScrollView
  317. - (void)headViewNoSuckTopTypeObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  318. if (_nextReturn) {
  319. _nextReturn = false;
  320. return;
  321. }
  322. CGFloat new = [change[@"new"] CGPointValue].y;
  323. CGFloat old = [change[@"old"] CGPointValue].y;
  324. if (new == old) {return;}
  325. CGFloat dh = new - old;
  326. if (dh < 0) {
  327. //向下
  328. if(((UIScrollView *)object).contentOffset.y < 0){
  329. _nextReturn = true;
  330. ((UIScrollView *)object).contentOffset = CGPointMake(0, 0);
  331. }
  332. }else{
  333. //向上
  334. if (self.mainTabelView.contentOffset.y < _stayHeight) {
  335. _nextReturn = true;
  336. ((UIScrollView *)object).contentOffset = CGPointMake(0, old);
  337. }else{
  338. }
  339. }
  340. }
  341. #pragma mark - UITableViewDataSource && UITableViewDlegate
  342. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  343. return 1;
  344. }
  345. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  346. return 1;
  347. }
  348. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  349. NSLog(@"%lf",self.frame.size.height - self.param.yOffset);
  350. return self.frame.size.height - self.param.yOffset;
  351. }
  352. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  353. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseIdentifier" forIndexPath:indexPath];
  354. self.cell = cell;
  355. if(![cell.contentView.subviews containsObject:self.viewPager]) {
  356. self.viewPager.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height - self.param.yOffset);
  357. [cell.contentView addSubview:self.viewPager];
  358. }
  359. return cell;
  360. }
  361. - (void)resetViewPage:(UIView *)viewPage {
  362. [self.viewPager removeFromSuperview];
  363. self.viewPager = viewPage;
  364. self.viewPager.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height - self.param.yOffset);
  365. [self.cell.contentView addSubview:self.viewPager];
  366. self.mainTabelView.viewPager = self.viewPager;
  367. }
  368. - (void)dealloc {
  369. NSLog(@"%@销毁了",self);
  370. }
  371. -(UIViewController*)viewController
  372. {
  373. UIResponder *nextResponder = self;
  374. do
  375. {
  376. nextResponder = [nextResponder nextResponder];
  377. if ([nextResponder isKindOfClass:[UIViewController class]])
  378. return (UIViewController*)nextResponder;
  379. } while (nextResponder != nil);
  380. return nil;
  381. }
  382. @end