├── .XDPagesView.podspec.swp ├── .gitignore ├── Demo ├── Page_0.h ├── Page_0.m ├── Page_1.h ├── Page_1.m ├── Page_2.h ├── Page_2.m ├── Page_3.h ├── Page_3.m ├── Page_4.h ├── Page_4.m ├── VC_Style0.h ├── VC_Style0.m ├── VC_Style1.h ├── VC_Style1.m ├── VC_Style2.h ├── VC_Style2.m ├── VC_Style3.h ├── VC_Style3.m ├── VC_Style4.h ├── VC_Style4.m ├── VC_Style5.h └── VC_Style5.m ├── LICENSE ├── README.md ├── Root ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Info.plist ├── LaunchScreen.storyboard ├── UINavigationBar+handle.h ├── UINavigationBar+handle.m ├── ViewController.h ├── ViewController.m └── main.m ├── XDPagesView.podspec ├── XDPagesView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── XDPagesView ├── XDPagesCache.h ├── XDPagesCache.m ├── XDPagesCell.h ├── XDPagesCell.m ├── XDPagesConfig.h ├── XDPagesConfig.m ├── XDPagesLayout.h ├── XDPagesLayout.m ├── XDPagesMacros.h ├── XDPagesMap.h ├── XDPagesMap.m ├── XDPagesNode.h ├── XDPagesNode.m ├── XDPagesTable.h ├── XDPagesTable.m ├── XDPagesTitle.h ├── XDPagesTitle.m ├── XDPagesTitleBar.h ├── XDPagesTitleBar.m ├── XDPagesTools.h ├── XDPagesTools.m ├── XDPagesTypes.h ├── XDPagesValueLock.h ├── XDPagesValueLock.m ├── XDPagesView.h ├── XDPagesView.m ├── XDSlideEffect.h └── XDSlideEffect.m ├── show1.gif ├── show2.gif ├── show3.gif └── sources ├── xd_back.jpg └── xd_header.jpg /.XDPagesView.podspec.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/.XDPagesView.podspec.swp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata/ 18 | 19 | ## Other 20 | *.moved-aside 21 | *.xccheckout 22 | *.xcscmblueprint 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 51 | 52 | fastlane/report.xml 53 | fastlane/Preview.html 54 | fastlane/screenshots/**/*.png 55 | fastlane/test_output 56 | 57 | # Code Injection 58 | # 59 | # After new code Injection tools there's a generated folder /iOSInjectionProject 60 | # https://github.com/johnno1962/injectionforxcode 61 | 62 | iOSInjectionProject/ 63 | -------------------------------------------------------------------------------- /Demo/Page_0.h: -------------------------------------------------------------------------------- 1 | // 2 | // Page_0.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Page_0 : UIViewController 14 | //传值方式 15 | - (instancetype)initByInfo:(id)info; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Demo/Page_0.m: -------------------------------------------------------------------------------- 1 | // 2 | // Page_0.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "Page_0.h" 10 | 11 | @interface Page_0 () 12 | @property (nonatomic, strong) id info; 13 | @end 14 | 15 | @implementation Page_0 16 | - (instancetype)initByInfo:(id)info { 17 | self = [super init]; 18 | if (self) { 19 | _info = info; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)viewWillAppear:(BOOL)animated { 25 | [super viewWillAppear:animated]; 26 | NSLog(@"Page_0_viewWillAppear"); 27 | } 28 | 29 | - (void)viewWillDisappear:(BOOL)animated { 30 | [super viewWillDisappear:animated]; 31 | NSLog(@"Page_0_viewWillDisappear"); 32 | } 33 | 34 | - (void)viewDidAppear:(BOOL)animated { 35 | [super viewDidAppear:animated]; 36 | NSLog(@"Page_0_viewDidAppear"); 37 | } 38 | 39 | - (void)viewDidDisappear:(BOOL)animated { 40 | [super viewDidDisappear:animated]; 41 | NSLog(@"Page_0_viewDidDisappear"); 42 | } 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"xd_back.jpg"].CGImage; 47 | self.view.layer.contentsScale = [UIScreen mainScreen].scale; 48 | self.view.layer.contentsGravity = kCAGravityResizeAspectFill; 49 | } 50 | 51 | /* 52 | #pragma mark - Navigation 53 | 54 | // In a storyboard-based application, you will often want to do a little preparation before navigation 55 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 56 | // Get the new view controller using [segue destinationViewController]. 57 | // Pass the selected object to the new view controller. 58 | } 59 | */ 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Demo/Page_1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Page_1.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/17. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Page_1 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/Page_1.m: -------------------------------------------------------------------------------- 1 | // 2 | // Page_1.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/17. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "Page_1.h" 10 | 11 | @interface Page_1 () 12 | @property (nonatomic, strong) UITableView *tableView; 13 | @property (nonatomic, strong) UITableView *tableView2; 14 | @end 15 | 16 | @implementation Page_1 17 | - (void)viewWillAppear:(BOOL)animated { 18 | [super viewWillAppear:animated]; 19 | NSLog(@"Page_1_viewWillAppear"); 20 | } 21 | 22 | - (void)viewWillDisappear:(BOOL)animated { 23 | [super viewWillDisappear:animated]; 24 | NSLog(@"Page_1_viewWillDisappear"); 25 | } 26 | 27 | - (void)viewDidAppear:(BOOL)animated { 28 | [super viewDidAppear:animated]; 29 | NSLog(@"Page_1_viewDidAppear"); 30 | } 31 | 32 | - (void)viewDidDisappear:(BOOL)animated { 33 | [super viewDidDisappear:animated]; 34 | NSLog(@"Page_1_viewDidDisappear"); 35 | } 36 | 37 | - (void)viewDidLoad { 38 | [super viewDidLoad]; 39 | self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"xd_back.jpg"].CGImage; 40 | self.view.layer.contentsScale = [UIScreen mainScreen].scale; 41 | self.view.layer.contentsGravity = kCAGravityResizeAspectFill; 42 | 43 | _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 44 | _tableView.delegate = self; 45 | _tableView.dataSource = self; 46 | _tableView.backgroundColor = [UIColor clearColor]; 47 | [self.view addSubview:_tableView]; 48 | 49 | _tableView2 = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 50 | _tableView2.delegate = self; 51 | _tableView2.dataSource = self; 52 | _tableView2.backgroundColor = [UIColor clearColor]; 53 | [self.view addSubview:_tableView2]; 54 | 55 | _tableView.translatesAutoresizingMaskIntoConstraints = NO; 56 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 57 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 58 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 59 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:_tableView2 attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; 60 | [NSLayoutConstraint activateConstraints:@[top, leading, bottom, trailing]]; 61 | 62 | _tableView2.translatesAutoresizingMaskIntoConstraints = NO; 63 | NSLayoutConstraint *top2 = [NSLayoutConstraint constraintWithItem:_tableView2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 64 | NSLayoutConstraint *leading2 = [NSLayoutConstraint constraintWithItem:_tableView2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_tableView attribute:NSLayoutAttributeRight multiplier:1 constant:0]; 65 | NSLayoutConstraint *bottom2 = [NSLayoutConstraint constraintWithItem:_tableView2 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 66 | NSLayoutConstraint *trailing2 = [NSLayoutConstraint constraintWithItem:_tableView2 attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 67 | NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_tableView2 attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; 68 | [NSLayoutConstraint activateConstraints:@[top2, leading2, bottom2, trailing2, width]]; 69 | } 70 | 71 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 72 | return 1; 73 | } 74 | 75 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 76 | return 100; 77 | } 78 | 79 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 80 | return 40; 81 | } 82 | 83 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 84 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 85 | if (!cell) { 86 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 87 | cell.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]; 88 | } 89 | cell.textLabel.text = @"test"; 90 | return cell; 91 | } 92 | 93 | /* 94 | #pragma mark - Navigation 95 | 96 | // In a storyboard-based application, you will often want to do a little preparation before navigation 97 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 98 | // Get the new view controller using [segue destinationViewController]. 99 | // Pass the selected object to the new view controller. 100 | } 101 | */ 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Demo/Page_2.h: -------------------------------------------------------------------------------- 1 | // 2 | // Page_2.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Page_2 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/Page_2.m: -------------------------------------------------------------------------------- 1 | // 2 | // Page_2.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "Page_2.h" 10 | 11 | @interface Page_2 () 12 | @property (nonatomic, strong) UITableView *tableView; 13 | @end 14 | 15 | @implementation Page_2 16 | - (void)viewWillAppear:(BOOL)animated { 17 | [super viewWillAppear:animated]; 18 | NSLog(@"Page_2_viewWillAppear"); 19 | } 20 | 21 | - (void)viewWillDisappear:(BOOL)animated { 22 | [super viewWillDisappear:animated]; 23 | NSLog(@"Page_2_viewWillDisappear"); 24 | } 25 | 26 | - (void)viewDidAppear:(BOOL)animated { 27 | [super viewDidAppear:animated]; 28 | NSLog(@"Page_2_viewDidAppear"); 29 | } 30 | 31 | - (void)viewDidDisappear:(BOOL)animated { 32 | [super viewDidDisappear:animated]; 33 | NSLog(@"Page_2_viewDidDisappear"); 34 | } 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"xd_back.jpg"].CGImage; 38 | self.view.layer.contentsScale = [UIScreen mainScreen].scale; 39 | self.view.layer.contentsGravity = kCAGravityResizeAspectFill; 40 | 41 | _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 42 | _tableView.delegate = self; 43 | _tableView.dataSource = self; 44 | _tableView.backgroundColor = [UIColor clearColor]; 45 | [self.view addSubview:_tableView]; 46 | 47 | _tableView.translatesAutoresizingMaskIntoConstraints = NO; 48 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 49 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 50 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 51 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 52 | [NSLayoutConstraint activateConstraints:@[top, leading, bottom, trailing]]; 53 | } 54 | 55 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 56 | return 1; 57 | } 58 | 59 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 60 | return 100; 61 | } 62 | 63 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 64 | return 40; 65 | } 66 | 67 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 68 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 69 | if (!cell) { 70 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 71 | cell.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]; 72 | } 73 | cell.textLabel.text = @"test"; 74 | return cell; 75 | } 76 | 77 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 78 | Page_2 *root = [[Page_2 alloc]init]; 79 | [self.navigationController pushViewController:root animated:YES]; 80 | } 81 | 82 | /* 83 | #pragma mark - Navigation 84 | 85 | // In a storyboard-based application, you will often want to do a little preparation before navigation 86 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 87 | // Get the new view controller using [segue destinationViewController]. 88 | // Pass the selected object to the new view controller. 89 | } 90 | */ 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /Demo/Page_3.h: -------------------------------------------------------------------------------- 1 | // 2 | // Page_3.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Page_3 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/Page_3.m: -------------------------------------------------------------------------------- 1 | // 2 | // Page_3.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/3. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "Page_3.h" 10 | #import 11 | 12 | @interface Page_3 () 13 | @property (nonatomic, strong) WKWebView *web; 14 | @end 15 | 16 | @implementation Page_3 17 | - (void)viewWillAppear:(BOOL)animated { 18 | [super viewWillAppear:animated]; 19 | NSLog(@"Page_3_viewWillAppear"); 20 | } 21 | 22 | - (void)viewWillDisappear:(BOOL)animated { 23 | [super viewWillDisappear:animated]; 24 | NSLog(@"Page_3_viewWillDisappear"); 25 | } 26 | 27 | - (void)viewDidAppear:(BOOL)animated { 28 | [super viewDidAppear:animated]; 29 | NSLog(@"Page_3_viewDidAppear"); 30 | } 31 | 32 | - (void)viewDidDisappear:(BOOL)animated { 33 | [super viewDidDisappear:animated]; 34 | NSLog(@"Page_3_viewDidDisappear"); 35 | } 36 | 37 | - (void)viewDidLoad { 38 | [super viewDidLoad]; 39 | self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"xd_back.jpg"].CGImage; 40 | self.view.layer.contentsScale = [UIScreen mainScreen].scale; 41 | self.view.layer.contentsGravity = kCAGravityResizeAspectFill; 42 | self.view.layer.masksToBounds = YES; 43 | 44 | WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init]; 45 | config.ignoresViewportScaleLimits = NO; 46 | // 创建设置对象 47 | WKPreferences *preference = [[WKPreferences alloc]init]; 48 | preference.minimumFontSize = 0; 49 | preference.javaScriptEnabled = YES; 50 | preference.javaScriptCanOpenWindowsAutomatically = YES; 51 | config.preferences = preference; 52 | _web = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config]; 53 | _web.backgroundColor = [[UIColor alloc]initWithWhite:1 alpha:0.5]; 54 | _web.opaque = NO; 55 | _web.UIDelegate = self; 56 | _web.allowsBackForwardNavigationGestures = YES; 57 | [_web loadRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.jianshu.com/p/b8aa3f98af78"]]]; 58 | [self.view addSubview:_web]; 59 | 60 | _web.translatesAutoresizingMaskIntoConstraints = NO; 61 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_web attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 62 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_web attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 63 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_web attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 64 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_web attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 65 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 66 | } 67 | 68 | // 页面开始加载时调用 69 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { 70 | } 71 | 72 | // 页面加载失败时调用 73 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { 74 | } 75 | 76 | // 当内容开始返回时调用 77 | - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { 78 | } 79 | 80 | // 页面加载完成之后调用 81 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { 82 | } 83 | /* 84 | #pragma mark - Navigation 85 | 86 | // In a storyboard-based application, you will often want to do a little preparation before navigation 87 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 88 | // Get the new view controller using [segue destinationViewController]. 89 | // Pass the selected object to the new view controller. 90 | } 91 | */ 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Demo/Page_4.h: -------------------------------------------------------------------------------- 1 | // 2 | // Page_4.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Page_4 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/Page_4.m: -------------------------------------------------------------------------------- 1 | // 2 | // Page_4.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "Page_4.h" 10 | 11 | @interface Page_4 () 12 | @property (nonatomic, strong) UITableView *tableView; 13 | @end 14 | 15 | @implementation Page_4 16 | - (void)viewWillAppear:(BOOL)animated { 17 | [super viewWillAppear:animated]; 18 | NSLog(@"Page_2_viewWillAppear"); 19 | } 20 | 21 | - (void)viewWillDisappear:(BOOL)animated { 22 | [super viewWillDisappear:animated]; 23 | NSLog(@"Page_2_viewWillDisappear"); 24 | } 25 | 26 | - (void)viewDidAppear:(BOOL)animated { 27 | [super viewDidAppear:animated]; 28 | NSLog(@"Page_2_viewDidAppear"); 29 | } 30 | 31 | - (void)viewDidDisappear:(BOOL)animated { 32 | [super viewDidDisappear:animated]; 33 | NSLog(@"Page_2_viewDidDisappear"); 34 | } 35 | 36 | - (void)beginRefresh { 37 | NSLog(@"子列表下拉刷新"); 38 | __weak typeof(self) weakSelf = self; 39 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 40 | [weakSelf.tableView.refreshControl endRefreshing]; 41 | }); 42 | } 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"xd_back.jpg"].CGImage; 47 | self.view.layer.contentsScale = [UIScreen mainScreen].scale; 48 | self.view.layer.contentsGravity = kCAGravityResizeAspectFill; 49 | 50 | _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 51 | _tableView.delegate = self; 52 | _tableView.dataSource = self; 53 | _tableView.backgroundColor = [UIColor clearColor]; 54 | [self.view addSubview:_tableView]; 55 | 56 | //下拉刷新(子列表可以采用任意方式下拉,比如mjRefresh,这里用系统方式演示) 57 | UIRefreshControl *downRefresh = [[UIRefreshControl alloc]init]; 58 | [downRefresh addTarget:self action:@selector(beginRefresh) forControlEvents:UIControlEventValueChanged]; 59 | [_tableView setRefreshControl:downRefresh]; 60 | 61 | 62 | 63 | _tableView.translatesAutoresizingMaskIntoConstraints = NO; 64 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 65 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 66 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 67 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 68 | [NSLayoutConstraint activateConstraints:@[top, leading, bottom, trailing]]; 69 | } 70 | 71 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 72 | return 1; 73 | } 74 | 75 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 76 | return 100; 77 | } 78 | 79 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 80 | return 40; 81 | } 82 | 83 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 84 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 85 | if (!cell) { 86 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 87 | cell.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]; 88 | } 89 | cell.textLabel.text = @"test"; 90 | return cell; 91 | } 92 | 93 | /* 94 | #pragma mark - Navigation 95 | 96 | // In a storyboard-based application, you will often want to do a little preparation before navigation 97 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 98 | // Get the new view controller using [segue destinationViewController]. 99 | // Pass the selected object to the new view controller. 100 | } 101 | */ 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Demo/VC_Style0.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style0.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 顶部下拉 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style0 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style0.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style0.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style0.h" 10 | #import "XDPagesView.h" 11 | #import "Page_0.h" 12 | #import "Page_1.h" 13 | #import "Page_2.h" 14 | #import "Page_3.h" 15 | 16 | @interface VC_Style0 () 17 | @property (nonatomic, strong) XDPagesView *pages; 18 | @property (nonatomic, strong) NSArray *titles; 19 | @property (nonatomic, strong) UIImageView *header; 20 | @end 21 | 22 | @implementation VC_Style0 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | self.view.backgroundColor = [UIColor grayColor]; 26 | 27 | // 顶部下拉 XDPagesPullOnTop 28 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:nil style:XDPagesPullOnTop]; 29 | _pages.delegate = self; 30 | _pages.pagesHeader = self.header; 31 | [self.view addSubview:_pages]; 32 | [self layoutPage]; 33 | // 不能有重复标题 34 | _titles = @[@"普通视图",@"多列表组合",@"单列表",@"网页"]; 35 | } 36 | 37 | - (void)beginRefresh { 38 | NSLog(@"下拉刷新"); 39 | __weak typeof(self) weakSelf = self; 40 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 41 | [weakSelf.pages.refreshControl endRefreshing]; 42 | }); 43 | } 44 | 45 | #pragma mark -- XDPagesViewDelegate 46 | // 必须实现以下两个代理 47 | - (NSArray *)xd_pagesViewPageTitles { 48 | return _titles; 49 | } 50 | 51 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 52 | 53 | // 缓存复用控制器 54 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 55 | 56 | if (!vc) { 57 | if ([title isEqualToString:@"普通视图"]) { 58 | // 传值方式,可以通过重写init来实现传值 59 | Page_0 *page = [[Page_0 alloc]initByInfo:@"普通视图"]; 60 | vc = page; 61 | 62 | } else if ([title isEqualToString:@"多列表组合"]) { 63 | Page_1 *page = [[Page_1 alloc]init]; 64 | vc = page; 65 | 66 | } else if([title isEqualToString:@"单列表"]) { 67 | Page_2 *page = [[Page_2 alloc]init]; 68 | vc = page; 69 | 70 | } else if([title isEqualToString:@"网页"]) { 71 | Page_3 *page = [[Page_3 alloc]init]; 72 | vc = page; 73 | } 74 | } 75 | return vc; 76 | } 77 | 78 | // 以下代理非必须实现 79 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 80 | NSLog(@"竖直:%f",changedy); 81 | } 82 | 83 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 84 | NSLog(@"当前页面:%@",title); 85 | } 86 | - (void)xd_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)currentPage willShowPage:(NSInteger)willShowPage { 87 | NSLog(@"横向滚动:%f->当前页:%ld->目标页:%ld",changedx,(long)currentPage,(long)willShowPage); 88 | } 89 | 90 | - (CGFloat)xd_pagesViewTitleWidthForIndex:(NSInteger)index title:(NSString *)title { 91 | //只有当titleFlex=NO时才会调用 92 | return 100; 93 | } 94 | 95 | #pragma mark -- getter 96 | - (UIImageView *)header { 97 | if (!_header) { 98 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 99 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 200)]; 100 | _header.contentMode = UIViewContentModeScaleAspectFill; 101 | _header.clipsToBounds = YES; 102 | 103 | } 104 | return _header; 105 | } 106 | 107 | #pragma mark -- 自适应布局 108 | - (void)layoutPage { 109 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 110 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 111 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 112 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 113 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 114 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 115 | } 116 | @end 117 | -------------------------------------------------------------------------------- /Demo/VC_Style1.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style1.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 列表下拉 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style1 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style1.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style1.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style1.h" 10 | #import "XDPagesView.h" 11 | #import "Page_2.h" 12 | 13 | @interface VC_Style1 () 14 | @property (nonatomic, strong) XDPagesView *pages; 15 | @property (nonatomic, strong) NSArray *titles; 16 | @property (nonatomic, strong) UIImageView *header; 17 | @end 18 | 19 | @implementation VC_Style1 20 | 21 | - (void)viewWillAppear:(BOOL)animated { 22 | [super viewWillAppear:animated]; 23 | 24 | } 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | self.view.backgroundColor = [UIColor grayColor]; 29 | 30 | // 子列表下拉 XDPagesPullOnCenter 31 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:nil style:XDPagesPullOnCenter]; 32 | _pages.delegate = self; 33 | _pages.pagesHeader = self.header; 34 | [self.view addSubview:_pages]; 35 | [self layoutPage]; 36 | 37 | // 不能有重复标题 38 | _titles = @[@"列表_1",@"列表_2",@"列表_3",@"列表_4"]; 39 | } 40 | 41 | #pragma mark -- XDPagesViewDelegate 42 | // 必须实现以下两个代理 43 | - (NSArray *)xd_pagesViewPageTitles { 44 | return _titles; 45 | } 46 | 47 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 48 | 49 | // 缓存复用控制器 50 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 51 | 52 | if (!vc) { 53 | Page_2 *page = [[Page_2 alloc]init]; 54 | vc = page; 55 | } 56 | return vc; 57 | } 58 | 59 | // 以下代理非必须实现 60 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 61 | NSLog(@"竖直:%f",changedy); 62 | } 63 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 64 | NSLog(@"当前页面:%@",title); 65 | [self.pages showBadgeNumber:1 index:0 color:UIColor.redColor isNumber:YES]; 66 | } 67 | 68 | #pragma mark -- getter 69 | - (UIImageView *)header { 70 | if (!_header) { 71 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 72 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 200)]; 73 | _header.contentMode = UIViewContentModeScaleAspectFill; 74 | _header.clipsToBounds = YES; 75 | 76 | } 77 | return _header; 78 | } 79 | 80 | #pragma mark -- 自适应布局 81 | - (void)layoutPage { 82 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 83 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 84 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 85 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 86 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 87 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 88 | } 89 | @end 90 | -------------------------------------------------------------------------------- /Demo/VC_Style2.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style2.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 顶部下拉刷新 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style2 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style2.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style2.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style2.h" 10 | #import "XDPagesView.h" 11 | #import "Page_0.h" 12 | #import "Page_1.h" 13 | #import "Page_2.h" 14 | #import "Page_3.h" 15 | 16 | @interface VC_Style2 () 17 | @property (nonatomic, strong) XDPagesView *pages; 18 | @property (nonatomic, strong) NSArray *titles; 19 | @property (nonatomic, strong) UIImageView *header; 20 | @end 21 | 22 | @implementation VC_Style2 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | self.view.backgroundColor = [UIColor grayColor]; 26 | 27 | //顶部下拉刷新时需要设置列表风格为 XDPagesPullOnTop 28 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:nil style:XDPagesPullOnTop]; 29 | _pages.delegate = self; 30 | _pages.pagesHeader = self.header; 31 | [self.view addSubview:_pages]; 32 | [self layoutPage]; 33 | 34 | /* 35 | 说明:由于不想把主列表暴露出来,所以顶部刷新只支持系统下拉刷新, 36 | 如果需要其他刷新,可以自行把XDPagesView.m内的mainTable移到.h文件中。(只可用来做刷新,不可更改其属性,frame等) 37 | */ 38 | UIRefreshControl *downRefresh = [[UIRefreshControl alloc]init]; 39 | downRefresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"XDPagesView"]; 40 | [downRefresh addTarget:self action:@selector(beginRefresh) forControlEvents:UIControlEventValueChanged]; 41 | [_pages setRefreshControl:downRefresh]; 42 | 43 | // 不能有重复标题 44 | _titles = @[@"普通视图",@"多列表组合",@"单列表",@"网页"]; 45 | } 46 | 47 | - (void)beginRefresh { 48 | NSLog(@"顶部下拉刷新"); 49 | __weak typeof(self) weakSelf = self; 50 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 51 | // 停止刷新 52 | [weakSelf.pages.refreshControl endRefreshing]; 53 | }); 54 | } 55 | 56 | #pragma mark -- XDPagesViewDelegate 57 | // 必须实现以下两个代理 58 | - (NSArray *)xd_pagesViewPageTitles { 59 | return _titles; 60 | } 61 | 62 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 63 | 64 | // 缓存复用控制器 65 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 66 | 67 | if (!vc) { 68 | if ([title isEqualToString:@"普通视图"]) { 69 | // 传值方式,可以通过重写init来实现传值 70 | Page_0 *page = [[Page_0 alloc]initByInfo:@"普通视图"]; 71 | vc = page; 72 | 73 | } else if ([title isEqualToString:@"多列表组合"]) { 74 | Page_1 *page = [[Page_1 alloc]init]; 75 | vc = page; 76 | 77 | } else if([title isEqualToString:@"单列表"]) { 78 | Page_2 *page = [[Page_2 alloc]init]; 79 | vc = page; 80 | 81 | } else if([title isEqualToString:@"网页"]) { 82 | Page_3 *page = [[Page_3 alloc]init]; 83 | vc = page; 84 | } 85 | } 86 | return vc; 87 | } 88 | 89 | //以下代理非必须实现 90 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 91 | // NSLog(@"竖直:%f",changedy); 92 | } 93 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 94 | NSLog(@"当前页面:%@",title); 95 | } 96 | 97 | #pragma mark -- getter 98 | - (UIImageView *)header { 99 | if (!_header) { 100 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 101 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 200)]; 102 | _header.contentMode = UIViewContentModeScaleAspectFill; 103 | _header.clipsToBounds = YES; 104 | 105 | } 106 | return _header; 107 | } 108 | 109 | #pragma mark -- 自适应布局 110 | - (void)layoutPage { 111 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 112 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 113 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 114 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 115 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 116 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 117 | } 118 | @end 119 | -------------------------------------------------------------------------------- /Demo/VC_Style3.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style3.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 列表下拉刷新 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style3 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style3.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style3.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style3.h" 10 | #import "XDPagesView.h" 11 | #import "Page_4.h" 12 | 13 | @interface VC_Style3 () 14 | @property (nonatomic, strong) XDPagesView *pages; 15 | @property (nonatomic, strong) NSArray *titles; 16 | @property (nonatomic, strong) UIImageView *header; 17 | @end 18 | 19 | @implementation VC_Style3 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | self.view.backgroundColor = [UIColor grayColor]; 24 | 25 | // 子列表下拉 XDPagesPullOnCenter 26 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:nil style:XDPagesPullOnCenter]; 27 | _pages.delegate = self; 28 | _pages.pagesHeader = self.header; 29 | [self.view addSubview:_pages]; 30 | [self layoutPage]; 31 | 32 | // 不能有重复标题 33 | _titles = @[@"列表刷新_1",@"列表刷新_2",@"列表刷新_3",@"列表刷新_4"]; 34 | } 35 | 36 | #pragma mark -- XDPagesViewDelegate 37 | // 必须实现以下两个代理 38 | - (NSArray *)xd_pagesViewPageTitles { 39 | return _titles; 40 | } 41 | 42 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 43 | 44 | // 缓存复用控制器 45 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 46 | 47 | if (!vc) { 48 | Page_4 *page = [[Page_4 alloc]init]; 49 | vc = page; 50 | } 51 | return vc; 52 | } 53 | 54 | // 以下代理非必须实现 55 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 56 | NSLog(@"竖直:%f",changedy); 57 | } 58 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 59 | NSLog(@"当前页面:%@",title); 60 | } 61 | 62 | #pragma mark -- getter 63 | - (UIImageView *)header { 64 | if (!_header) { 65 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 66 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 200)]; 67 | _header.contentMode = UIViewContentModeScaleAspectFill; 68 | _header.clipsToBounds = YES; 69 | 70 | } 71 | return _header; 72 | } 73 | 74 | #pragma mark -- 自适应布局 75 | - (void)layoutPage { 76 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 77 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 78 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 79 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 80 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 81 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 82 | } 83 | @end 84 | -------------------------------------------------------------------------------- /Demo/VC_Style4.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style4.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 头部和标题栏一体化 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style4 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style4.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style4.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style4.h" 10 | #import "XDPagesView.h" 11 | #import "Page_0.h" 12 | #import "Page_1.h" 13 | #import "Page_2.h" 14 | #import "Page_3.h" 15 | 16 | @interface VC_Style4 () 17 | @property (nonatomic, strong) XDPagesView *pages; 18 | @property (nonatomic, strong) NSArray *titles; 19 | @property (nonatomic, strong) UIImageView *header; 20 | @end 21 | 22 | @implementation VC_Style4 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | self.view.backgroundColor = [UIColor grayColor]; 26 | 27 | // 之前一直没有设置config,这里需要设置一下,这是个很强大的类 28 | XDPagesConfig *config = [XDPagesConfig config]; 29 | // 标题栏和header一体化,只需要把该属性设置为YES 30 | config.titleBarFitHeader = YES; 31 | 32 | // 顶部下拉 XDPagesPullOnTop 33 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:config style:XDPagesPullOnTop]; 34 | _pages.delegate = self; 35 | _pages.pagesHeader = self.header; 36 | [self.view addSubview:_pages]; 37 | [self layoutPage]; 38 | 39 | // 不能有重复标题 40 | _titles = @[@"普通视图",@"多列表组合",@"单列表",@"网页"]; 41 | } 42 | 43 | - (void)beginRefresh { 44 | NSLog(@"下拉刷新"); 45 | __weak typeof(self) weakSelf = self; 46 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 47 | [weakSelf.pages.refreshControl endRefreshing]; 48 | }); 49 | } 50 | 51 | #pragma mark -- XDPagesViewDelegate 52 | // 必须实现以下两个代理 53 | - (NSArray *)xd_pagesViewPageTitles { 54 | return _titles; 55 | } 56 | 57 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 58 | 59 | // 缓存复用控制器 60 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 61 | 62 | if (!vc) { 63 | if ([title isEqualToString:@"普通视图"]) { 64 | // 传值方式,可以通过重写init来实现传值 65 | Page_0 *page = [[Page_0 alloc]initByInfo:@"普通视图"]; 66 | vc = page; 67 | 68 | } else if ([title isEqualToString:@"多列表组合"]) { 69 | Page_1 *page = [[Page_1 alloc]init]; 70 | vc = page; 71 | 72 | } else if([title isEqualToString:@"单列表"]) { 73 | Page_2 *page = [[Page_2 alloc]init]; 74 | vc = page; 75 | 76 | } else if([title isEqualToString:@"网页"]) { 77 | Page_3 *page = [[Page_3 alloc]init]; 78 | vc = page; 79 | } 80 | } 81 | return vc; 82 | } 83 | 84 | // 以下代理非必须实现 85 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 86 | NSLog(@"竖直:%f",changedy); 87 | } 88 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 89 | NSLog(@"当前页面:%@",title); 90 | } 91 | 92 | #pragma mark -- getter 93 | - (UIImageView *)header { 94 | if (!_header) { 95 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 96 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 260)]; 97 | _header.contentMode = UIViewContentModeScaleAspectFill; 98 | _header.clipsToBounds = YES; 99 | 100 | } 101 | return _header; 102 | } 103 | 104 | #pragma mark -- 自适应布局 105 | - (void)layoutPage { 106 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 107 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 108 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 109 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 110 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 111 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 112 | } 113 | @end 114 | -------------------------------------------------------------------------------- /Demo/VC_Style5.h: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style5.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 其他用法(主要介绍自定义配置属性) 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VC_Style5 : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Demo/VC_Style5.m: -------------------------------------------------------------------------------- 1 | // 2 | // VC_Style5.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/8. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "VC_Style5.h" 10 | #import "XDPagesView.h" 11 | #import "Page_0.h" 12 | #import "Page_1.h" 13 | #import "Page_2.h" 14 | #import "Page_3.h" 15 | 16 | @interface VC_Style5 () 17 | @property (nonatomic, strong) XDPagesView *pages; 18 | @property (nonatomic, strong) NSArray *titles; 19 | @property (nonatomic, strong) UIImageView *header; 20 | @property (nonatomic, strong) UIButton *rightBtn; 21 | @property (nonatomic, strong) XDPagesConfig *config; 22 | @end 23 | 24 | @implementation VC_Style5 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | self.view.backgroundColor = [UIColor grayColor]; 28 | 29 | // 在这个示例里详细介绍一下 XDPagesConfig 30 | XDPagesConfig *config = [XDPagesConfig config]; 31 | 32 | // 起始页面 33 | config.beginPage = 1; 34 | 35 | // 最大缓存页数 36 | config.maxCacheCount = 20; 37 | 38 | // 是否可以滑动翻页 39 | config.pagesSlideEnable = YES; 40 | 41 | // 相邻页面变动时是否需要展示动画 42 | config.animateForPageChange = YES; 43 | 44 | // 是否可以边界自由滑动 45 | config.pagesHorizontalBounce = NO; 46 | 47 | // 是否需要标题栏 48 | config.needTitleBar = YES; 49 | 50 | // 是否标题栏和header作为一个整体 (当设置为yes时 标题栏的背景颜色和背景图片将失效) 51 | // config.titleBarFitHeader = YES; 52 | 53 | // 标题栏高度 54 | config.titleBarHeight = 50; 55 | 56 | // 距离上端的悬停距离 57 | config.titleBarMarginTop = 64; 58 | 59 | // 是否需要标题栏底线 60 | config.needTitleBarBottomLine = YES; 61 | 62 | // 底线颜色 63 | config.titleBarBottomLineColor = [UIColor redColor]; 64 | 65 | // 是否需要下滑线 66 | config.needTitleBarSlideLine = YES; 67 | 68 | // 下滑线跟踪方式 69 | config.titleBarSlideLineStyle = XDSlideLine_Scale; 70 | 71 | // 下滑线宽度比例 72 | config.titleBarSlideLineWidthRatio = 0.5; 73 | 74 | // 下滑线颜色 75 | config.titleBarSlideLineColor = [UIColor grayColor]; 76 | 77 | // 标题栏背景色 78 | config.titleBarBackColor = [UIColor greenColor]; 79 | 80 | // 标题栏背景图片 81 | config.titleBarBackImage = nil; 82 | 83 | // 标题栏是否可以边界自由滑动 84 | config.titleBarHorizontalBounce = NO; 85 | 86 | // 自定义标题栏(传入自定义的标题栏即可) 87 | // config.customTitleBar = myTitleBar; 88 | 89 | // 标题背景颜色 90 | config.titleItemBackColor = [UIColor clearColor]; 91 | 92 | // 标题选中时背景颜色 93 | config.titleItemBackHightlightColor = [UIColor clearColor]; 94 | 95 | // 标题背景图片 96 | config.titleItemBackImage = nil; 97 | 98 | // 标题选中时的背景图片 99 | config.titleItemBackHightlightImage = nil; 100 | 101 | // 是否自动计算标题宽 (当设置为yes时会自动根据标题计算宽度,自定义宽度将失效) 102 | config.titleFlex = YES; 103 | 104 | // 标题是否采用渐变方式(设置yes时 标题的大小和颜色在切换时会有正常到高光的渐变效果) 105 | config.titleGradual = YES; 106 | 107 | // 标题竖直方向对齐方式 108 | config.titleVerticalAlignment = XDVerticalAlignmentMiddle; 109 | 110 | // 正常标题颜色 111 | config.titleTextColor = [UIColor grayColor]; 112 | 113 | // 高光标题颜色 114 | config.titleTextHightlightColor = [UIColor orangeColor]; 115 | 116 | // 标题字号大小(默认16) 117 | config.titleFont = [UIFont systemFontOfSize:16]; 118 | 119 | // 选中后的字号大小(默认18) 120 | config.titleHightlightFont = [UIFont systemFontOfSize:18 weight:bold]; 121 | 122 | 123 | // ************************************设置右边按钮********************************** 124 | // 是否需要右按钮 125 | config.needRightBtn = YES; 126 | 127 | // 右按钮大小(高度最好要小于等于标题栏高度) 128 | config.rightBtnSize = CGSizeMake(80, 50); 129 | 130 | // 自定义右按钮 131 | config.rightBtn = self.rightBtn; 132 | 133 | self.config = config; 134 | // 顶部下拉 XDPagesPullOnTop 135 | _pages = [[XDPagesView alloc]initWithFrame:self.view.bounds config:config style:XDPagesPullOnTop]; 136 | _pages.delegate = self; 137 | _pages.pagesHeader = self.header; 138 | [self.view addSubview:_pages]; 139 | [self layoutPage]; 140 | 141 | // 不能有重复标题 142 | _titles = @[@"普通视图",@"多列表组合",@"单列表",@"多列表组合2",@"视图"]; 143 | } 144 | 145 | - (void)beginRefresh { 146 | NSLog(@"下拉刷新"); 147 | __weak typeof(self) weakSelf = self; 148 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 149 | [weakSelf.pages.refreshControl endRefreshing]; 150 | }); 151 | } 152 | 153 | - (void)rightBtnTap { 154 | NSLog(@"点击右按钮"); 155 | self.config.titleBarBackColor = UIColor.yellowColor; 156 | [self.pages reloadConfigs]; 157 | } 158 | 159 | #pragma mark -- XDPagesViewDelegate 160 | // 必须实现以下两个代理 161 | - (NSArray *)xd_pagesViewPageTitles { 162 | return _titles; 163 | } 164 | 165 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 166 | 167 | // 缓存复用控制器 168 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 169 | 170 | if (!vc) { 171 | if ([title isEqualToString:@"普通视图"]) { 172 | // 传值方式,可以通过重写init来实现传值 173 | Page_0 *page = [[Page_0 alloc]initByInfo:@"普通视图"]; 174 | vc = page; 175 | 176 | } else if ([title isEqualToString:@"多列表组合"]) { 177 | Page_1 *page = [[Page_1 alloc]init]; 178 | vc = page; 179 | 180 | } else if([title isEqualToString:@"单列表"]) { 181 | Page_2 *page = [[Page_2 alloc]init]; 182 | vc = page; 183 | } else if ([title isEqualToString:@"多列表组合2"]) { 184 | Page_1 *page = [[Page_1 alloc]init]; 185 | vc = page; 186 | 187 | } else if ([title isEqualToString:@"视图"]) { 188 | Page_0 *page = [[Page_0 alloc]initByInfo:@"视图"]; 189 | vc = page; 190 | } 191 | } 192 | return vc; 193 | } 194 | 195 | // 以下代理非必须实现 196 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 197 | NSLog(@"竖直:%f",changedy); 198 | } 199 | 200 | - (void)xd_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx { 201 | NSLog(@"水平:%f",changedx); 202 | } 203 | 204 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 205 | NSLog(@"当前页面:%@",title); 206 | } 207 | 208 | #pragma mark -- getter 209 | - (UIImageView *)header { 210 | if (!_header) { 211 | _header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"xd_header.jpg"]]; 212 | [_header setFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 200)]; 213 | _header.contentMode = UIViewContentModeScaleAspectFill; 214 | _header.clipsToBounds = YES; 215 | 216 | } 217 | return _header; 218 | } 219 | 220 | - (UIButton *)rightBtn { 221 | if (!_rightBtn) { 222 | _rightBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 223 | [_rightBtn setFrame:CGRectMake(0, 0, 80, 50)]; 224 | _rightBtn.backgroundColor = [[UIColor alloc]initWithWhite:1 alpha:0.5]; 225 | [_rightBtn setTitle:@"右按钮" forState:UIControlStateNormal]; 226 | [_rightBtn addTarget:self action:@selector(rightBtnTap) forControlEvents:UIControlEventTouchUpInside]; 227 | } 228 | return _rightBtn; 229 | } 230 | 231 | #pragma mark -- 自适应布局 232 | - (void)layoutPage { 233 | _pages.translatesAutoresizingMaskIntoConstraints = NO; 234 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 235 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 236 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 237 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_pages attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 238 | [self.view addConstraints:@[top, leading, bottom, trailing]]; 239 | } 240 | @end 241 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 上北以北 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XDPagesView 2 | 3 | # 最近更新2023-12-7(未在cocopods更新,直接把XDPagesView文件夹拉入项目中) 4 | 5 | # 功能简介: 6 | - XDPagesView是一个多控制器视图,代码结构简单,拥有极多的自定义配置项,使用起来非常简介灵活(该控件不采用分类,也不需要继承,对源代码没有任何代码污染) 7 | 8 | 1. 支持横竖屏 9 | 2. 支持刷新列表 10 | 3. 支持暗夜模式 11 | 4. 支持header视图 12 | 5. 还有很多,就不一一列举了,自己看吧... 13 | 14 | # 基本用法: 15 | 16 | 17 | ``` 18 | #import "XDPagesView.h" 19 | 20 | 21 | @interface Demo () 22 | ... 23 | @end 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | 28 | //配置项,可以做一些自定义配置 29 | XDPagesConfig *config = [XDPagesConfig config]; 30 | 31 | // 顶部下拉 XDPagesPullOnTop 32 | XDPagesView *pagesView = [[XDPagesView alloc]initWithFrame:self.view.bounds config:config style:XDPagesPullOnTop]; 33 | 34 | //遵循代理 35 | pagesView.delegate = self; 36 | 37 | //设置header视图 38 | pagesView.pagesHeader = self.header; 39 | 40 | [self.view addSubview:pagesView]; 41 | } 42 | 43 | 44 | #pragma mark -- XDPagesViewDelegate 必须实现以下两个代理 45 | 46 | /* 47 | 返回一个标题组 48 | 标题作为每个页面的唯一,不能重复 49 | */ 50 | - (NSArray *)xd_pagesViewPageTitles { 51 | return @[@"标题一", @"标题二", @"标题三"]; 52 | } 53 | 54 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title { 55 | 56 | // 缓存复用控制器 57 | UIViewController *vc = [pagesView dequeueReusablePageForIndex:index]; 58 | 59 | if (!vc) { 60 | if (index == 0) { 61 | // 传值方式,可以通过重写init来实现传值 62 | Page_0 *page = [[Page_0 alloc]init]; 63 | vc = page; 64 | } 65 | else if (index == 1) { 66 | Page_1 *page = [[Page_1 alloc]init]; 67 | vc = page; 68 | } 69 | else if(index == 2) { 70 | Page_2 *page = [[Page_2 alloc]init]; 71 | vc = page; 72 | } 73 | } 74 | 75 | return vc; 76 | } 77 | 78 | ``` 79 | 80 | # 基础功能 81 | 82 | #### 1. 跳转到某一页 83 | 84 | ``` 85 | 应用场景: 86 | 想要切换到某个页面,可以使用下面两个方法 87 | 88 | 功能方法: 89 | - (void)jumpToPage:(NSInteger)page; 90 | - (void)jumpToPage:(NSInteger)page animate:(BOOL)animate; 91 | 92 | 使用示例: 93 | [pagesView jumpToPage:1]; 94 | ``` 95 | 96 | #### 2. 刷新列表 97 | 98 | ``` 99 | 应用场景: 100 | 当控制器列表有变动时,可以刷新列表, 101 | 比如开始只有控制@[@"标题一", @"标题二", @"标题三"],后来又添加了一个变成了@[@"标题一", @"标题二", @"标题三", @"标题四"] 102 | 103 | 功能方法:刷新列表后定位到哪一页 104 | - (void)reloadataToPage:(NSInteger)page; 105 | 106 | 使用示例: 107 | [pagesView reloadataToPage:1]; 108 | ``` 109 | 110 | #### 3. 刷新配置 111 | 112 | ``` 113 | 应用场景: 114 | 当要对某些配置进行更改时,可以通过刷新配置来实现,比如暗黑模式的处理(注意* 有些属性是不能被刷新的,具体可参考代码里注释) 115 | 116 | 功能方法: 117 | - (void)reloadConfigs; 118 | 119 | 使用示例: 120 | 121 | //初始化时,我们把标题栏配置成绿色 122 | XDPagesConfig *config = [XDPagesConfig config]; 123 | config.titleBarBackColor = [UIColor greenColor]; 124 | XDPagesView *pagesView = [[XDPagesView alloc]initWithFrame:self.view.bounds config:config style:XDPagesPullOnTop]; 125 | pagesView.delegate = self; 126 | pagesView.pagesHeader = self.header; 127 | [self.view addSubview:pagesView]; 128 | 129 | 130 | //暗夜模式时我们需要把导航栏变成黄色 131 | config.titleBarBackColor = [UIColor yellowColor]; 132 | //配置更改后,调用刷新会立即生效 133 | [pagesView reloadConfigs]; 134 | ``` 135 | 136 | #### 4. 滚动到吸顶位置 137 | 138 | ``` 139 | 应用场景: 140 | 在某些情况下,为了让列表展示的内容更多,可能需要把列表滚动到吸顶位置 141 | 142 | 功能方法: 143 | - (void)scrollToCeiling:(BOOL)animate; 144 | 145 | 使用示例: 146 | [pagesView scrollToCeiling:YES]; 147 | ``` 148 | 149 | #### 5. 添加标题栏未读标记 150 | 151 | ``` 152 | 应用场景: 153 | 通知,未读消息 154 | 155 | 功能方法: 156 | /** 157 | 展示标题栏某个页面的未读消息 158 | @param number 未读数,当为0时隐藏 159 | @param idx 对应索引 160 | @param color badge颜色 161 | @param isNumber 是否显示数字 162 | */ 163 | - (void)showBadgeNumber:(NSInteger)number index:(NSInteger)idx color:(UIColor *)color isNumber:(BOOL)isNumber; 164 | 165 | 使用示例: 在第一个标题上展示有五个未读消息 166 | [pagesView showBadgeNumber:5 index:0 color:[UIColor greenColor] isNumber:YES]; 167 | ``` 168 | 169 | # 其他可用delegate 170 | 171 | ``` 172 | /** 173 | 已经跳到的当前界面 174 | @param controller 当前控制器 175 | @param index 索引 176 | @param title 标题 177 | */ 178 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title { 179 | 180 | } 181 | 182 | /** 183 | 竖直滚动监听 184 | @param changedy 竖直offset.y 185 | @param ceiling 是否已吸顶 186 | */ 187 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling { 188 | 189 | } 190 | 191 | /** 192 | 水平滚动监听 193 | @param changedx 水平offset.x 194 | @param currentPage 当前页 195 | @param willShowPage 目标页 196 | */ 197 | - (void)xd_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)currentPage willShowPage:(NSInteger)willShowPage { 198 | 199 | } 200 | 201 | /** 202 | 自定义标题宽度 (注意* 只有当config.titleFlex=NO的时候,该代理才会生效) 203 | @param index 索引 204 | @param title 标题 205 | */ 206 | - (CGFloat)xd_pagesViewTitleWidthForIndex:(NSInteger)index title:(NSString *)title { 207 | return 100; 208 | } 209 | 210 | ``` 211 | 212 | # 详细XDPagesConfig配置项 213 | #### 具体可参考demo中的<其他用法> 214 | #### 注意*:其中标记了(⚠️不可被刷新)的配置,不会在调用- (void)reloadConfigs方法时刷新 215 | 216 | ``` 217 | /** 218 | titleBar的下划指示线展示效果 219 | */ 220 | typedef NS_ENUM(NSInteger, SlideLineStyle) { 221 | XDSlideLine_None, // 下划线无展示效果 222 | XDSlideLine_Scale, // 下划线伸缩 223 | XDSlideLine_translation // 下划线平移(默认效果) 224 | }; 225 | 226 | /** 227 | titleBar的标题文字对齐方式 228 | */ 229 | typedef NS_ENUM(NSInteger, TitleVerticalAlignment) { 230 | XDVerticalAlignmentTop = 0, //标题顶部垂直对齐 231 | XDVerticalAlignmentMiddle, //标题中部垂直对齐 232 | XDVerticalAlignmentBottom, //标题底部垂直对齐 233 | }; 234 | 235 | 236 | @property (nonatomic, assign) NSInteger beginPage; // 起始页(⚠️不可被刷新) 237 | @property (nonatomic, assign) NSInteger maxCacheCount; // 最大缓存页数 238 | @property (nonatomic, assign) BOOL pagesSlideEnable; // 是否可滑动翻页(默认YES) 239 | @property (nonatomic, assign) BOOL animateForPageChange; // 页面变动时是否需要动画(默认YES) 240 | @property (nonatomic, assign) BOOL pagesHorizontalBounce; // 是否页面边界自由滑动(默认YES) 241 | 242 | @property (nonatomic, assign) BOOL needTitleBar; // 是否需要标题栏(默认YES)(⚠️不可被刷新) 243 | @property (nonatomic, assign) BOOL titleBarFitHeader; // 是否标题栏和header作为一个整体(默认NO)(⚠️不可被刷新) 244 | @property (nonatomic, assign) CGFloat titleBarHeight; // 标题栏高度(默认50)(⚠️不可被刷新) 245 | @property (nonatomic, assign) CGFloat titleBarMarginTop; // 悬停位置距上端距离(默认0) 246 | @property (nonatomic, assign) BOOL needTitleBarBottomLine; // 是否需要标题栏底线(默认YES) 247 | @property (nonatomic, strong) UIColor *titleBarBottomLineColor; // 底线颜色(默认浅灰色) 248 | @property (nonatomic, assign) BOOL needTitleBarSlideLine; // 是否需要下滑线(默认YES) 249 | @property (nonatomic, assign) SlideLineStyle titleBarSlideLineStyle;// 下划线跟随方式 250 | @property (nonatomic, assign) CGFloat titleBarSlideLineWidthRatio; // 下滑线相对于当前item宽的比例[0-1] 251 | @property (nonatomic, strong) UIColor *titleBarSlideLineColor; // 下滑线颜色(默认灰色) 252 | @property (nonatomic, strong) UIColor *titleBarBackColor; // 标题栏背景色 253 | @property (nonatomic, strong) UIImage *titleBarBackImage; // 标题栏背景图 254 | @property (nonatomic, assign) BOOL titleBarHorizontalBounce; // 标题栏是否可以边界自由滑动(默认YES) 255 | @property (nonatomic, strong) UIView *customTitleBar; // 自定义标题栏(⚠️不可被刷新) 256 | 257 | @property (nonatomic, strong) UIColor *titleItemBackColor; // 标题背景颜色 258 | @property (nonatomic, strong) UIColor *titleItemBackHightlightColor;// 标题选中时背景颜色 259 | @property (nonatomic, strong) UIImage *titleItemBackImage; // 标题背景图片 260 | @property (nonatomic, strong) UIImage *titleItemBackHightlightImage;// 标题选中时的背景图片 261 | 262 | @property (nonatomic, assign) BOOL titleFlex; // 是否自动计算标题宽(默认YES) 263 | @property (nonatomic, assign) BOOL titleGradual; // 是否采用渐变方式(默认YES,只渐变标题属性) 264 | @property (nonatomic, assign) TitleVerticalAlignment titleVerticalAlignment; // 标题竖直对齐方式 265 | @property (nonatomic, strong) UIColor *titleTextColor; // 标题颜色 266 | @property (nonatomic, strong) UIColor *titleTextHightlightColor; // 标题选中时的颜色 267 | @property (nonatomic, strong) UIFont *titleFont; // 标题字号大小(默认16) 268 | @property (nonatomic, strong) UIFont *titleHightlightFont; // 选中后的字号大小(默认18) 269 | 270 | @property (nonatomic, assign) BOOL needRightBtn; // 是否需要右按钮(默认NO)(⚠️不可被刷新) 271 | @property (nonatomic, assign) CGSize rightBtnSize; // 右按钮大小 272 | @property (nonatomic, strong) UIView *rightBtn; // 右按钮自定义视图 273 | ``` 274 | 275 | 276 | # 展示 277 | 278 | ![show1 style.gifo动](https://github.com/Xiexingda/XDPagesView/blob/master/show1.gif) 279 | ![show2 style.gifo动](https://github.com/Xiexingda/XDPagesView/blob/master/show2.gif) 280 | ![show3 style.gifo动](https://github.com/Xiexingda/XDPagesView/blob/master/show3.gif) 281 | 282 | 283 | ### 如果有别的需求或发现了问题还请issue 或加群提问: 284 | 285 | 群:659700776 286 | 287 | 暗号:iOS 288 | -------------------------------------------------------------------------------- /Root/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Root/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 15 | self.window = [[UIWindow alloc]initWithFrame:UIScreen.mainScreen.bounds]; 16 | self.window.backgroundColor = UIColor.whiteColor; 17 | [self.window makeKeyAndVisible]; 18 | 19 | ViewController *root = [[ViewController alloc]init]; 20 | UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:root]; 21 | self.window.rootViewController = nav; 22 | return YES; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Root/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Root/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Root/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIRequiredDeviceCapabilities 31 | 32 | armv7 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Root/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Root/UINavigationBar+handle.h: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationBar+handle.h 3 | // Funny 4 | // 5 | // Created by 谢兴达 on 2018/4/6. 6 | // Copyright © 2018年 谢兴达. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UINavigationBar (handle) 12 | /** 13 | 更改导航栏颜色和图片 14 | 15 | @param color 颜色 16 | @param barImage 图片 17 | @param opaque 样式,YES:状态字体为白色 NO:状态字体为黑色(默认) 18 | */ 19 | - (void)navBarBackGroundColor:(UIColor *_Nullable)color image:(UIImage *_Nullable)barImage isOpaque:(BOOL)opaque; 20 | 21 | /** 22 | 更改透明度 23 | 24 | @param alpha 导航栏透明度 25 | @param opaque 样式,YES:状态字体为白色 NO:状态字体为黑色(默认) 26 | */ 27 | - (void)navBarAlpha:(CGFloat)alpha isOpaque:(BOOL)opaque; 28 | 29 | /** 30 | 导航栏背景高度 31 | 注意*这里并没有改导航栏高度,只是改了自定义背景高度 32 | 33 | @param height 高度 34 | @param opaque 样式,YES:状态字体为白色 NO:状态字体为黑色(默认) 35 | */ 36 | - (void)navBarMyLayerHeight:(CGFloat)height isOpaque:(BOOL)opaque; 37 | 38 | /** 39 | 隐藏底线 40 | */ 41 | - (void)navBarBottomLineHidden:(BOOL)hidden; 42 | 43 | //还原回系统导航栏 44 | - (void)navBarToBeSystem; 45 | @end 46 | 47 | #pragma mark -- 自定义导航栏层 48 | @interface MyNavView :UIView 49 | @property (nonatomic, assign) CGFloat alpha; 50 | @property (nonatomic, assign) BOOL hiddenBottomLine; 51 | @property (nonatomic, strong) UIColor * _Nullable backColor; 52 | @property (nonatomic, strong) UIImage * _Nullable backImage; 53 | @end 54 | -------------------------------------------------------------------------------- /Root/UINavigationBar+handle.m: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationBar+handle.m 3 | // Funny 4 | // 5 | // Created by 谢兴达 on 2018/4/6. 6 | // Copyright © 2018年 谢兴达. All rights reserved. 7 | // 8 | 9 | #import "UINavigationBar+handle.h" 10 | #import 11 | @class MyNavView; 12 | 13 | @interface UINavigationBar() 14 | @property (nonatomic, strong) UIImage *backClearImage; 15 | @property (nonatomic, strong) UIImage *lineClearImage; 16 | @property (nonatomic, strong) MyNavView *myNavView; //自定义插入层,自定义操作都要在这一层上进行 17 | @property (nonatomic, assign) BOOL hiddenBottom; 18 | @end 19 | 20 | static char backClear_key, lineClear_key, myNavView_key, hiddenBottom_key; 21 | 22 | @implementation UINavigationBar (handle) 23 | 24 | #pragma mark -- runtime:get/set 25 | - (void)setBackClearImage:(UIImage *)backClearImage { 26 | objc_setAssociatedObject(self, &backClear_key, backClearImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 27 | } 28 | 29 | - (UIImage *)backClearImage { 30 | return objc_getAssociatedObject(self, &backClear_key); 31 | } 32 | 33 | - (void)setLineClearImage:(UIImage *)lineClearImage { 34 | objc_setAssociatedObject(self, &lineClear_key, lineClearImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 35 | } 36 | 37 | - (UIImage *)lineClearImage { 38 | return objc_getAssociatedObject(self, &lineClear_key); 39 | } 40 | 41 | - (void)setMyNavView:(MyNavView *)myNavView { 42 | objc_setAssociatedObject(self, &myNavView_key, myNavView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 43 | } 44 | 45 | - (MyNavView *)myNavView { 46 | return objc_getAssociatedObject(self, &myNavView_key); 47 | } 48 | 49 | - (void)setHiddenBottom:(BOOL)hiddenBottom { 50 | objc_setAssociatedObject(self, &hiddenBottom_key, @(hiddenBottom), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 51 | } 52 | 53 | - (BOOL)hiddenBottom { 54 | return [objc_getAssociatedObject(self, &hiddenBottom_key) boolValue]; 55 | } 56 | 57 | #pragma mark -- function 58 | - (void)navBarAlpha:(CGFloat)alpha isOpaque:(BOOL)opaque { 59 | [self clearSystemLayerIsOpaque:opaque]; 60 | if (!self.myNavView) { 61 | //状态栏高度 62 | CGFloat statusHeight = [UIApplication sharedApplication].statusBarFrame.size.height; 63 | //导航栏高度 64 | CGFloat barHeight = self.bounds.size.height; 65 | CGRect barBounds = self.bounds; 66 | barBounds.size.height = statusHeight + barHeight; 67 | [[self customNavBar]setFrame:barBounds]; 68 | } 69 | 70 | self.myNavView.alpha = alpha; 71 | 72 | //通过kvc找到系统导航栏背景层,把自定义层添加到背景层 73 | /* 系统背景层无法改变其属性 所以通过添加自定义层,改变自定义层去实现效果*/ 74 | [[self valueForKey:@"backgroundView"] addSubview:self.myNavView]; 75 | } 76 | 77 | - (void)navBarBackGroundColor:(UIColor *)color image:(UIImage *)barImage isOpaque:(BOOL)opaque { 78 | [self clearSystemLayerIsOpaque:opaque]; 79 | if (!self.myNavView) { 80 | //状态栏高度 81 | CGFloat statusHeight = [UIApplication sharedApplication].statusBarFrame.size.height; 82 | //导航栏高度 83 | CGFloat barHeight = self.bounds.size.height; 84 | 85 | CGRect barBounds = self.bounds; 86 | barBounds.size.height = statusHeight + barHeight; 87 | [[self customNavBar]setFrame:barBounds]; 88 | } 89 | 90 | if (color) { 91 | self.myNavView.backColor = color; 92 | } 93 | 94 | if (barImage) { 95 | self.myNavView.backImage = barImage; 96 | } 97 | 98 | //通过kvc找到系统导航栏背景层,把自定义层添加到背景层 99 | /*系统背景层无法改变其属性 所以通过添加自定义层,改变自定义层上的属性去实现效果*/ 100 | [[self valueForKey:@"backgroundView"] addSubview:self.myNavView]; 101 | } 102 | 103 | - (void)navBarMyLayerHeight:(CGFloat)height isOpaque:(BOOL)opaque { 104 | height = height < 0 ? 0 : height; 105 | 106 | [self clearSystemLayerIsOpaque:opaque]; 107 | [[self customNavBar]setFrame:CGRectMake(0, 0, self.bounds.size.width, height)]; 108 | 109 | //通过kvc找到系统导航栏背景层,把自定义层添加到背景层 110 | /* 亲测,系统背景层无法改变其属性 所以通过添加自定义层,改变自定义层上的属性去实现效果*/ 111 | [[self valueForKey:@"backgroundView"] addSubview:self.myNavView]; 112 | } 113 | 114 | - (void)navBarBottomLineHidden:(BOOL)hidden { 115 | if (self.hiddenBottom == hidden) return; 116 | self.hiddenBottom = hidden; 117 | //如果是自定义图层 118 | [self customNavBar]; 119 | if (self.myNavView.hiddenBottomLine != hidden) { 120 | self.myNavView.hiddenBottomLine = hidden; 121 | 122 | } 123 | //如果是系统层 124 | if (hidden) { 125 | if (!self.lineClearImage) { 126 | self.lineClearImage = [[UIImage alloc]init]; 127 | [self setShadowImage:self.lineClearImage]; 128 | } 129 | } else { 130 | if (self.lineClearImage) { 131 | self.lineClearImage = nil; 132 | [self setShadowImage:self.lineClearImage]; 133 | } 134 | } 135 | } 136 | 137 | //还原到系统初始状态 138 | - (void)navBarToBeSystem { 139 | if (self.myNavView) { 140 | [self.myNavView removeFromSuperview]; 141 | self.myNavView = nil; 142 | } 143 | if (self.lineClearImage) { 144 | self.lineClearImage = nil; 145 | } 146 | if (self.backClearImage) { 147 | self.backClearImage = nil; 148 | } 149 | [self setBackgroundImage:nil 150 | forBarMetrics:UIBarMetricsDefault]; 151 | [self setShadowImage:nil]; 152 | self.barStyle = UIBarStyleDefault; 153 | } 154 | 155 | //去掉系统导航栏特征 156 | - (void)clearSystemLayerIsOpaque:(BOOL)opaque{ 157 | //通过插入空image把背景变透明 158 | if (!self.backClearImage) { 159 | self.backClearImage = [[UIImage alloc]init]; 160 | [self setBackgroundImage:self.backClearImage 161 | forBarMetrics:UIBarMetricsDefault]; 162 | } 163 | self.barStyle = opaque ? UIBarStyleBlackOpaque : UIBarStyleDefault; 164 | //去掉系统底线,使用自定义底线 165 | if (!self.lineClearImage) { 166 | self.lineClearImage = [[UIImage alloc]init]; 167 | [self setShadowImage:self.lineClearImage]; 168 | } 169 | } 170 | 171 | - (MyNavView *)customNavBar { 172 | if (!self.myNavView) { 173 | self.myNavView = [[MyNavView alloc]init]; 174 | } 175 | return self.myNavView; 176 | } 177 | @end 178 | 179 | #pragma mark -- 自定义导航栏层 180 | @interface MyNavView() 181 | @property (nonatomic, strong) UIImageView * _Nullable backImageView; 182 | @property (nonatomic, strong) UIView * _Nullable bottomLine; 183 | @end 184 | 185 | @implementation MyNavView 186 | - (instancetype)initWithFrame:(CGRect)frame { 187 | self = [super initWithFrame:frame]; 188 | if (self) { 189 | 190 | } 191 | return self; 192 | } 193 | 194 | - (void)setFrame:(CGRect)frame { 195 | [super setFrame:frame]; 196 | [self creatMainUIByFrame:frame]; 197 | } 198 | 199 | - (void)creatMainUIByFrame:(CGRect)frame { 200 | CGFloat height = frame.size.height; 201 | CGFloat width = frame.size.width; 202 | [self.backImageView setFrame:CGRectMake(0, 0, width, height)]; 203 | [self addSubview:self.backImageView]; 204 | 205 | [self.bottomLine setFrame:CGRectMake(0, height-0.5, width, 0.5)]; 206 | [self addSubview:self.bottomLine]; 207 | } 208 | 209 | //底层背景层永远透明 210 | - (void)setBackgroundColor:(UIColor *)backgroundColor { 211 | [super setBackgroundColor:[UIColor clearColor]]; 212 | } 213 | 214 | //设置图片背景颜色 215 | - (void)setBackColor:(UIColor *)backColor { 216 | _backColor = backColor; 217 | self.backImageView.backgroundColor = backColor; 218 | } 219 | 220 | //设置图片背景透明度 221 | - (void)setAlpha:(CGFloat)alpha { 222 | _alpha = alpha; 223 | self.backImageView.alpha = alpha; 224 | } 225 | 226 | //设置图片 227 | - (void)setBackImage:(UIImage *)backImage { 228 | _backImage = backImage; 229 | self.backImageView.image = backImage; 230 | } 231 | 232 | - (void)setHiddenBottomLine:(BOOL)hiddenBottomLine { 233 | _hiddenBottomLine = hiddenBottomLine; 234 | self.bottomLine.hidden = hiddenBottomLine; 235 | } 236 | 237 | #pragma mark -- 懒加载视图 238 | - (UIImageView *)backImageView { 239 | if (!_backImageView) { 240 | _backImageView = [UIImageView new]; 241 | _backImageView.clipsToBounds = YES; 242 | _backImageView.contentMode = UIViewContentModeTop; 243 | } 244 | return _backImageView; 245 | } 246 | 247 | - (UIView *)bottomLine { 248 | if (!_bottomLine) { 249 | _bottomLine = [UIView new]; 250 | [_bottomLine setBackgroundColor:[UIColor grayColor]]; 251 | } 252 | return _bottomLine; 253 | } 254 | @end 255 | -------------------------------------------------------------------------------- /Root/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Root/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "UINavigationBar+handle.h" 11 | #import "VC_Style0.h" 12 | #import "VC_Style1.h" 13 | #import "VC_Style2.h" 14 | #import "VC_Style3.h" 15 | #import "VC_Style4.h" 16 | #import "VC_Style5.h" 17 | 18 | @interface ViewController () 19 | @property (nonatomic, strong) UITableView *tableView; 20 | @property (nonatomic, strong) NSArray *titles; 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewWillAppear:(BOOL)animated { 26 | [super viewWillAppear:animated]; 27 | [self.navigationController.navigationBar navBarBackGroundColor:[UIColor clearColor] image:nil isOpaque:NO]; 28 | [self.navigationController.navigationBar navBarBottomLineHidden:YES]; 29 | } 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | self.navigationItem.title = @"XDPagesStyle"; 34 | self.view.backgroundColor = [UIColor grayColor]; 35 | _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]; 36 | _tableView.delegate = self; 37 | _tableView.dataSource = self; 38 | _tableView.backgroundColor = [UIColor clearColor]; 39 | _tableView.tableFooterView = [UIView new]; 40 | [self.view addSubview:_tableView]; 41 | 42 | _tableView.translatesAutoresizingMaskIntoConstraints = NO; 43 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 44 | NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]; 45 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 46 | NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_tableView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]; 47 | [NSLayoutConstraint activateConstraints:@[top, leading, bottom, trailing]]; 48 | _titles = @[@"顶部下拉",@"列表下拉",@"顶部下拉刷新",@"列表下拉刷新",@"头部和标题栏一体化",@"其他用法(主要介绍自定义配置属性)"]; 49 | } 50 | 51 | #pragma mark -- Delegate 52 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 53 | return 1; 54 | } 55 | 56 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 57 | return _titles.count; 58 | } 59 | 60 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 61 | return 50; 62 | } 63 | 64 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 65 | if (indexPath.row == 0) { 66 | //顶部下拉 67 | VC_Style0 *root = [[VC_Style0 alloc]init]; 68 | [self.navigationController pushViewController:root animated:YES]; 69 | 70 | } else if (indexPath.row == 1) { 71 | //列表下拉 72 | VC_Style1 *root = [[VC_Style1 alloc]init]; 73 | [self.navigationController pushViewController:root animated:YES]; 74 | 75 | } else if (indexPath.row == 2) { 76 | //顶部下拉刷新 77 | VC_Style2 *root = [[VC_Style2 alloc]init]; 78 | [self.navigationController pushViewController:root animated:YES]; 79 | 80 | } else if (indexPath.row == 3) { 81 | //顶部下拉刷新 82 | VC_Style3 *root = [[VC_Style3 alloc]init]; 83 | [self.navigationController pushViewController:root animated:YES]; 84 | 85 | } else if (indexPath.row == 4) { 86 | //头部和标题栏一体化 87 | VC_Style4 *root = [[VC_Style4 alloc]init]; 88 | [self.navigationController pushViewController:root animated:YES]; 89 | 90 | } else if (indexPath.row == 5) { 91 | //其他用法 92 | VC_Style5 *root = [[VC_Style5 alloc]init]; 93 | [self.navigationController pushViewController:root animated:YES]; 94 | } 95 | } 96 | 97 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 98 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 99 | if (!cell) { 100 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 101 | cell.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]; 102 | } 103 | cell.textLabel.text = _titles[indexPath.row]; 104 | return cell; 105 | } 106 | @end 107 | -------------------------------------------------------------------------------- /Root/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /XDPagesView.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint XDPlayer.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | s.name = "XDPagesView" 18 | s.version = "2.3.1" 19 | s.summary = "多控制器列表" 20 | # This description is used to generate tags and improve search results. 21 | # * Think: What does it do? Why did you write it? What is the focus? 22 | # * Try to keep it short, snappy and to the point. 23 | # * Write the description between the DESC delimiters below. 24 | # * Finally, don't worry about the indent, CocoaPods strips it! 25 | s.description = <<-DESC 26 | 一个可以添加header的多控制器切换列表 27 | DESC 28 | 29 | s.homepage = "https://github.com/Xiexingda/XDPagesView" 30 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 31 | 32 | 33 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 34 | # 35 | # Licensing your code is important. See http://choosealicense.com for more info. 36 | # CocoaPods will detect a license file if there is a named LICENSE* 37 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 38 | # 39 | 40 | s.license = "MIT" 41 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 42 | 43 | 44 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 45 | # 46 | # Specify the authors of the library, with email addresses. Email addresses 47 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 48 | # accepts just a name if you'd rather not provide an email address. 49 | # 50 | # Specify a social_media_url where others can refer to, for example a twitter 51 | # profile URL. 52 | # 53 | 54 | s.author = { "xiexingda" => "779656694@qq.com" } 55 | # Or just: s.author = "xiexingda" 56 | # s.authors = { "xiexingda" => "779656694@qq.com" } 57 | 58 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 59 | # 60 | # If this Pod runs only on iOS or OS X, then specify the platform and 61 | # the deployment target. You can optionally include the target after the platform. 62 | # 63 | 64 | s.platform = :ios 65 | # s.platform = :ios, "10.0" 66 | 67 | # When using multiple platforms 68 | s.ios.deployment_target = "10.0" 69 | # s.osx.deployment_target = "10.7" 70 | # s.watchos.deployment_target = "2.0" 71 | # s.tvos.deployment_target = "9.0" 72 | 73 | 74 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 75 | # 76 | # Specify the location from where the source should be retrieved. 77 | # Supports git, hg, bzr, svn and HTTP. 78 | # 79 | 80 | s.source = { :git => "https://github.com/Xiexingda/XDPagesView.git", :tag => "#{s.version}" } 81 | 82 | 83 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 84 | # 85 | # CocoaPods is smart about how it includes source code. For source files 86 | # giving a folder will include any swift, h, m, mm, c & cpp files. 87 | # For header files it will include any header in the folder. 88 | # Not including the public_header_files will make all headers public. 89 | # 90 | 91 | s.source_files = "XDPagesView/*.{h,m}" 92 | s.exclude_files = "XDPagesView/Exclude" 93 | 94 | # s.public_header_files = "Classes/**/*.h" 95 | 96 | 97 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 98 | # 99 | # A list of resources included with the Pod. These are copied into the 100 | # target bundle with a build phase script. Anything else will be cleaned. 101 | # You can preserve files from being cleaned, please don't preserve 102 | # non-essential files like tests, examples and documentation. 103 | # 104 | 105 | # s.resource = "timg.jpeg" 106 | # s.resources = "XDRefresh/XDRefreshResource.bundle" 107 | 108 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 109 | 110 | 111 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 112 | # 113 | # Link your library with frameworks, or libraries. Libraries do not include 114 | # the lib prefix of their name. 115 | # 116 | 117 | s.framework = "UIKit" 118 | # s.frameworks = "SomeFramework", "AnotherFramework" 119 | 120 | # s.library = "iconv" 121 | # s.libraries = "iconv", "xml2" 122 | 123 | 124 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 125 | # 126 | # If your library depends on compiler flags you can set them in the xcconfig hash 127 | # where they will only apply to your library. If you depend on other Podspecs 128 | # you can include multiple dependencies to ensure it works. 129 | 130 | s.requires_arc = true 131 | 132 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 133 | # s.dependency "JSONKit", "~> 1.4" 134 | 135 | s.user_target_xcconfig = { 136 | 'GENERATE_INFOPLIST_FILE' => 'YES' 137 | } 138 | 139 | s.pod_target_xcconfig = { 140 | 'GENERATE_INFOPLIST_FILE' => 'YES' 141 | } 142 | 143 | 144 | end 145 | -------------------------------------------------------------------------------- /XDPagesView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XDPagesView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesCache.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 缓存管理 8 | 9 | #import 10 | #import 11 | 12 | typedef struct { 13 | NSInteger badgeNumber; 14 | UIColor *badgeColor; 15 | BOOL isNumber; 16 | } XDBADGE; 17 | 18 | @interface XDPagesCache : NSObject 19 | @property (nonatomic, weak) UIViewController *mainController; // 主控制器 20 | @property (nonatomic, strong) NSArray *titles; // 所有标题 21 | @property (nonatomic, strong) NSMutableArray *kvoTitles;// 当前所有添加了观察者的对象标题 22 | @property (nonatomic, assign) NSInteger maxCacheCount; // 最大缓存个数 23 | + (instancetype)cache; 24 | 25 | - (void)setPage:(UIViewController *)page title:(NSString *)title; 26 | - (void)cancelPageForTitle:(NSString *)title; 27 | - (void)clearPages; 28 | 29 | // will动作,包括willAppear willDisAppear 30 | - (void)pageWillAppearHandle:(BOOL)need; 31 | // did动作,包括didAppear didDisAppear 32 | - (void)pageDidApearHandle:(BOOL)need; 33 | 34 | - (UIView *)viewForTitle:(NSString *)title; 35 | - (UIViewController *)controllerForTitle:(NSString *)title; 36 | - (NSArray *)scrollViewsForTitle:(NSString *)title; 37 | 38 | // 设置未读 39 | - (void)setBadgeForIndex:(NSInteger)idx number:(NSInteger)number color:(UIColor *)color isNumber:(BOOL)isNumber; 40 | 41 | //未读数 42 | - (XDBADGE)badgeNumberForIndex:(NSInteger)idx; 43 | @end 44 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesCache.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesCache.h" 10 | #import "XDPagesMap.h" 11 | #import 12 | #import "XDPagesTools.h" 13 | #import "XDPagesMacros.h" 14 | 15 | NS_INLINE XDBADGE 16 | XDBADGEMake(NSInteger number, UIColor *color, BOOL isNumber) { 17 | XDBADGE badge; 18 | badge.badgeNumber = number; 19 | badge.badgeColor = color; 20 | badge.isNumber = isNumber; 21 | return badge; 22 | } 23 | 24 | @interface XDPagesCache () 25 | @property (nonatomic, strong) XDPagesMap *map; 26 | @property (nonatomic, strong) XDPagesMap *badgeMap; 27 | @end 28 | @implementation XDPagesCache 29 | - (void)setMaxCacheCount:(NSInteger)maxCacheCount { 30 | _maxCacheCount = maxCacheCount >= 2 ? maxCacheCount : 2; 31 | } 32 | - (void)setTitles:(NSArray *)titles { 33 | NSAssert(![XDPagesTools hasRepeatItemInArray:titles], @"____XDPagesView___出现重复标题"); 34 | _titles = titles; 35 | } 36 | 37 | + (instancetype)cache { 38 | return [[self alloc]init]; 39 | } 40 | 41 | - (instancetype)init { 42 | 43 | self = [super init]; 44 | 45 | if (self) { 46 | _kvoTitles = @[].mutableCopy; 47 | _map = [XDPagesMap map]; 48 | _badgeMap = [XDPagesMap map]; 49 | } 50 | 51 | return self; 52 | } 53 | 54 | - (void)setPage:(UIViewController *)page title:(NSString *)title { 55 | if (!page) return; 56 | 57 | XDPagesNode *node = [_map->mapDic objectForKey:title]; 58 | 59 | if (node) { 60 | node->right += 1; 61 | [_map bringNodeToHeader:node]; 62 | } else { 63 | node = [XDPagesNode node]; 64 | node->key = title; 65 | node->controller = page; 66 | node->view = [self viewClipsBoundsForView:page.view]; 67 | node->scrollViews = [self allNeedObserveScrollsInView:page.view]; 68 | [_map insertNode:node]; 69 | 70 | [node->scrollViews enumerateObjectsUsingBlock:^(UIScrollView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 71 | [XDPagesTools closeAdjustForScroll:obj controller:page]; 72 | }]; 73 | } 74 | 75 | [self surplusOfCaches:_maxCacheCount]; 76 | } 77 | 78 | - (void)pageWillAppearHandle:(BOOL)need { 79 | if (!need) return; 80 | 81 | if (_map->header) { 82 | if (_map->header->right>=1) { 83 | [_map->header->controller beginAppearanceTransition:YES animated:YES]; 84 | }; 85 | } 86 | 87 | if (_map->header->next) { 88 | [_map->header->next->controller beginAppearanceTransition:NO animated:YES]; 89 | } 90 | } 91 | 92 | - (void)pageDidApearHandle:(BOOL)need { 93 | if (!need) return; 94 | 95 | if (_map->header) { 96 | // 添加到当前控制器 97 | if (_mainController) { 98 | if (![_mainController.childViewControllers containsObject:_map->header->controller]) { 99 | [_mainController addChildViewController:_map->header->controller]; 100 | [_map->header->controller didMoveToParentViewController:_mainController]; 101 | } 102 | } else { 103 | NSLog(@"cache没有添加主控器"); 104 | } 105 | if (_map->header->right >= 1) { 106 | [_map->header->controller endAppearanceTransition]; 107 | } 108 | } 109 | 110 | if (_map->header->next) { 111 | [_map->header->next->controller endAppearanceTransition]; 112 | if (_mainController) { 113 | if ([_mainController.childViewControllers containsObject:_map->header->next->controller]) { 114 | [_map->header->next->controller willMoveToParentViewController:nil]; 115 | [_map->header->next->controller removeFromParentViewController]; 116 | } 117 | } else { 118 | NSLog(@"cache没有添加主控器"); 119 | } 120 | } 121 | } 122 | 123 | - (void)cancelPageForTitle:(NSString *)title { 124 | 125 | XDPagesNode *node = [_map->mapDic objectForKey:title]; 126 | 127 | if (node) { 128 | [_map removeNode:node]; 129 | node->scrollViews = nil; 130 | [node->view removeFromSuperview]; 131 | [node->controller willMoveToParentViewController:nil]; 132 | [node->controller removeFromParentViewController]; 133 | node->controller = nil; 134 | node = nil; 135 | } 136 | } 137 | 138 | - (void)clearPages { 139 | [self surplusOfCaches:0]; 140 | } 141 | 142 | 143 | - (UIView *)viewForTitle:(NSString *)title { 144 | 145 | XDPagesNode *node = [_map->mapDic objectForKey:title]; 146 | 147 | if (node) { 148 | return node->view; 149 | } 150 | 151 | return nil; 152 | } 153 | 154 | - (UIViewController *)controllerForTitle:(NSString *)title { 155 | 156 | XDPagesNode *node = [_map->mapDic objectForKey:title]; 157 | 158 | if (node) { 159 | return node->controller; 160 | } 161 | 162 | return nil; 163 | } 164 | 165 | - (NSArray *)scrollViewsForTitle:(NSString *)title { 166 | 167 | XDPagesNode *node = [_map->mapDic objectForKey:title]; 168 | 169 | if (node) { 170 | return node->scrollViews; 171 | } 172 | 173 | return nil; 174 | } 175 | 176 | // 使缓存剩余个数 177 | - (void)surplusOfCaches:(NSInteger)count { 178 | while (_map->count > count) { 179 | XDPagesNode *cancelNode = [_map removeLastNode]; 180 | cancelNode->scrollViews = nil; 181 | [cancelNode->view removeFromSuperview]; 182 | [cancelNode->controller willMoveToParentViewController:nil]; 183 | [cancelNode->controller removeFromParentViewController]; 184 | cancelNode->controller = nil; 185 | cancelNode = nil; 186 | } 187 | } 188 | 189 | // 把控制器内的view进行剪裁,否则可能会因为设置背景造成view的bounds变化 190 | - (UIView *)viewClipsBoundsForView:(UIView *)view { 191 | 192 | view.clipsToBounds = YES; 193 | 194 | return view; 195 | } 196 | 197 | // 找到所有符合的滚动控件 198 | - (NSArray *)allNeedObserveScrollsInView:(UIView *)view { 199 | 200 | __block NSMutableArray *scrolls = @[].mutableCopy; 201 | 202 | if (view.tag != XD_IGNORETAG) { 203 | [self subViewsInView:view matchView:^(UIScrollView *scroll) { 204 | if (scroll) { 205 | [scrolls addObject:scroll]; 206 | } 207 | }]; 208 | } 209 | 210 | return scrolls.count > 0 ? [scrolls copy] : nil; 211 | } 212 | 213 | // 遍历(对于嵌套的滚动控件,只监听父滚动控件) 214 | - (void)subViewsInView:(UIView *)view matchView:(void(^)(UIScrollView *scroll))match { 215 | for (UIScrollView *child in view.subviews) { 216 | 217 | if (child.tag == XD_IGNORETAG) continue; 218 | 219 | if ([child isKindOfClass:UIScrollView.class]) { 220 | if (match) { 221 | match(child); 222 | } 223 | continue; 224 | 225 | } else if ([child isKindOfClass:WKWebView.class]) { 226 | if (((WKWebView *)child).scrollView.tag != XD_IGNORETAG) { 227 | if (match) { 228 | match(((WKWebView *)child).scrollView); 229 | } 230 | }; 231 | continue; 232 | } 233 | 234 | [self subViewsInView:child matchView:match]; 235 | } 236 | } 237 | 238 | - (void)setBadgeForIndex:(NSInteger)idx number:(NSInteger)number color:(UIColor *)color isNumber:(BOOL)isNumber { 239 | XDPagesNode *node = [_badgeMap->mapDic objectForKey:@(idx).stringValue]; 240 | if (!node) { 241 | node = [XDPagesNode node]; 242 | node->key = @(idx).stringValue; 243 | node->value = number>0?@(number):@(0); 244 | node->badgeColor = color; 245 | node->isNumber = isNumber; 246 | [_badgeMap insertNode:node]; 247 | } else { 248 | node->value = number>0?@(number):@(0); 249 | node->badgeColor = color; 250 | node->isNumber = isNumber; 251 | [_badgeMap bringNodeToHeader:node]; 252 | } 253 | } 254 | 255 | - (XDBADGE)badgeNumberForIndex:(NSInteger)idx { 256 | XDPagesNode *node = [_badgeMap->mapDic objectForKey:@(idx).stringValue]; 257 | if (node) { 258 | return XDBADGEMake([node->value integerValue], node->badgeColor, node->isNumber); 259 | } 260 | return XDBADGEMake(0, UIColor.clearColor, NO); 261 | } 262 | 263 | @end 264 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesCell.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 主cell 8 | 9 | #import 10 | #import "XDPagesTable.h" 11 | #import "XDPagesConfig.h" 12 | #import "XDPagesTypes.h" 13 | 14 | @protocol XDPagesCellDelegate 15 | 16 | /** 17 | 数据代理,用于接受标题数组 18 | @return 标题数组 19 | */ 20 | - (NSArray *)cell_pagesViewAllTitles; 21 | 22 | /** 23 | 数据代理,用于接受当前控制器 24 | @param index 索引 25 | @param title 对应标题 26 | @return 子控制器 27 | */ 28 | - (UIViewController *)cell_pagesViewChildControllerForIndex:(NSInteger)index title:(NSString *)title; 29 | 30 | /** 31 | 已经跳到的当前界面 32 | @param pageController 当前控制器 33 | @param pageTitle 标题 34 | @param pageIndex 索引 35 | */ 36 | - (void)cell_pagesViewDidChangeToPageController:(UIViewController *const)pageController title:(NSString *)pageTitle pageIndex:(NSInteger)pageIndex; 37 | 38 | /** 39 | 横向滚动安全范围内回调,用于标题的渐变等 40 | @param changedx 变动值 41 | @param page 当前页 42 | @param willShowPage 目标页 43 | */ 44 | - (void)cell_pagesViewSafeHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willShowPage:(NSInteger)willShowPage; 45 | 46 | /** 47 | 水平滚动监听 48 | @param changedx 水平offset.x 49 | @param page 当前页 50 | @param willShowPage 目标页 51 | */ 52 | - (void)cell_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willShowPage:(NSInteger)willShowPage; 53 | 54 | /** 55 | 主table锁定消息传递 56 | @param need 是否需要锁定 57 | @param y 锁定值 58 | */ 59 | - (void)cell_mainTableNeedLock:(BOOL)need offsety:(CGFloat)y; 60 | 61 | /** 62 | 列表以上可以变动的竖直高度 63 | */ 64 | - (CGFloat)cell_headerVerticalCanChangedSpace; 65 | @end 66 | 67 | @interface XDPagesCell : UITableViewCell 68 | @property (nonatomic, weak) UIScrollView *currentKVOChild; // 当前被观察的子列表 69 | @property (nonatomic, assign) CGFloat mainOffsetStatic; // 主table锁定偏移量 70 | /** 71 | 初始化主cell 72 | @param style cell风格 73 | @param reuseIdentifier 复用ID 74 | @param controller 主列表所在的父控制器 75 | @param delegate 代理 76 | @param pullStyle 下拉风格 77 | @param config 配置 78 | */ 79 | - (instancetype)initWithStyle:(UITableViewCellStyle)style 80 | reuseIdentifier:(NSString *)reuseIdentifier 81 | contentController:(UIViewController *)controller 82 | delegate:(id)delegate 83 | pagesPullStyle:(XDPagesPullStyle)pullStyle 84 | config:(XDPagesConfig *)config; 85 | 86 | /** 87 | 刷新配置项 88 | */ 89 | - (void)reloadConfigs; 90 | 91 | /** 92 | 内外视图交换管道 93 | @param mainTable 主列表 94 | */ 95 | - (UIScrollView *)exchangeChannelOfPagesContainerAndMainTable:(XDPagesTable *)mainTable; 96 | 97 | /** 98 | 缓存复用 99 | @param index 索引 100 | @return 缓存的子控制器 101 | */ 102 | - (UIViewController *)dequeueReusablePageForIndex:(NSInteger)index; 103 | 104 | /** 105 | 页面跳转 106 | @param page 跳转页 107 | @param animate 是否有跳转动画 108 | */ 109 | - (void)changeToPage:(NSInteger)page animate:(BOOL)animate; 110 | 111 | /** 112 | 刷新列表 113 | @param page 刷新完成后跳转到的页面 114 | @param finish 刷新完成回调 115 | */ 116 | - (void)reloadToPage:(NSInteger)page finish:(void(^)(NSArray* titles))finish; 117 | @end 118 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesCell.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesCell.h" 10 | #import "XDPagesCache.h" 11 | #import "XDPagesTools.h" 12 | #import "XDPagesValueLock.h" 13 | 14 | @interface XDPagesCell () 15 | @property (nonatomic, weak) id delegate; 16 | @property (nonatomic, weak) XDPagesTable *mainTable; // 主列表 17 | @property (nonatomic, strong) UIScrollView *pagesContainer; // 子列表容器 18 | @property (nonatomic, strong) XDPagesConfig *config; // 配置管理 19 | @property (nonatomic, strong) XDPagesCache *pagesCache; // 缓存管理 20 | @property (nonatomic, strong) XDPagesValueLock *childLock; // 子列表偏移锁 21 | 22 | @property (nonatomic, assign) NSInteger willShowPage; // 将要出现的页 23 | @property (nonatomic, assign) NSInteger currentPage; // 当前页 24 | @property (nonatomic, assign) XDPagesPullStyle pagePullStyle; // 风格 25 | @property (nonatomic, assign) BOOL isRectChanging; // 是否正在调整rect 26 | @property (nonatomic, assign) BOOL skipLoop; //跳过当前loop 27 | @property (nonatomic, copy) void (^reloadToPageDelayBlock)(void); 28 | @end 29 | @implementation XDPagesCell 30 | - (void)dealloc { 31 | [self clearKVO]; 32 | [self.pagesCache clearPages]; 33 | [self unregisterFromNotifications]; 34 | } 35 | 36 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier contentController:(UIViewController *)controller delegate:(id)delegate pagesPullStyle:(XDPagesPullStyle)pullStyle config:(XDPagesConfig *)config { 37 | 38 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 39 | 40 | if (self) { 41 | self.selectionStyle = UITableViewCellSelectionStyleNone; 42 | self.backgroundColor = [UIColor clearColor]; 43 | self.config = config; 44 | self.delegate = delegate; 45 | self.pagePullStyle = pullStyle; 46 | self.childLock = [XDPagesValueLock lock]; 47 | self.pagesCache.mainController = controller; 48 | self.pagesCache.maxCacheCount = config.maxCacheCount; 49 | _willShowPage = config.beginPage; 50 | _currentPage = config.beginPage; 51 | self.contentView.frame = self.frame; 52 | [self createUI]; 53 | [self reloadToPage:config.beginPage finish:nil]; 54 | [self registerForNotifications]; 55 | } 56 | 57 | return self; 58 | } 59 | 60 | - (void)registerForNotifications { 61 | #pragma clang diagnostic push 62 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 63 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 64 | [nc addObserver:self selector:@selector(statusBarOrientationDidChange) 65 | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 66 | #pragma clang diagnostic pop 67 | } 68 | 69 | - (void)unregisterFromNotifications { 70 | #pragma clang diagnostic push 71 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 72 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 73 | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 74 | #pragma clang diagnostic pop 75 | } 76 | 77 | - (void)statusBarOrientationDidChange { 78 | self.isRectChanging = YES; 79 | dispatch_async(dispatch_get_main_queue(), ^{ 80 | [self rectChangedToPage:self.currentPage]; 81 | self.isRectChanging = NO; 82 | if (self.reloadToPageDelayBlock) { 83 | self.reloadToPageDelayBlock(); 84 | self.reloadToPageDelayBlock = nil; 85 | } 86 | }); 87 | } 88 | 89 | - (void)reloadConfigs { 90 | _pagesCache.maxCacheCount = _config.maxCacheCount; 91 | _pagesContainer.bounces = _config.pagesHorizontalBounce; 92 | _pagesContainer.scrollEnabled = _config.pagesSlideEnable; 93 | [self scrollViewDidScroll:_pagesContainer]; 94 | } 95 | 96 | // 互换通道,使内外都拿到彼此的对象去做响应控制 97 | - (UIScrollView *)exchangeChannelOfPagesContainerAndMainTable:(XDPagesTable *)mainTable { 98 | 99 | self.mainTable = mainTable; 100 | 101 | return self.pagesContainer; 102 | } 103 | 104 | // 刷新并跳到对应页 105 | - (void)reloadToPage:(NSInteger)page finish:(void(^)(NSArray* titles))finish { 106 | [self clearKVO]; 107 | 108 | NSArray *old_titles = [self.pagesCache.titles copy]; 109 | 110 | [self configAllTitles]; 111 | 112 | // 找到所有原标题组在新标题组中被去掉的标题,并删除对应的页 113 | [[XDPagesTools canceledTitlesInNewTitles:self.pagesCache.titles comparedOldTitles:old_titles] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 114 | [self.pagesCache cancelPageForTitle:obj]; 115 | }]; 116 | 117 | dispatch_async(dispatch_get_main_queue(), ^{ 118 | __weak typeof(self) weakSelf = self; 119 | [self setReloadToPageDelayBlock:^{ 120 | [weakSelf rectChangedToPage:page]; 121 | [weakSelf scrollViewDidScroll:weakSelf.pagesContainer]; 122 | [weakSelf pageIndexDidChangedToPage:page]; 123 | }]; 124 | 125 | if (!weakSelf.isRectChanging) { 126 | if (weakSelf.reloadToPageDelayBlock) { 127 | weakSelf.reloadToPageDelayBlock(); 128 | weakSelf.reloadToPageDelayBlock = nil; 129 | } 130 | } 131 | }); 132 | 133 | if (finish) { 134 | finish(self.pagesCache.titles); 135 | } 136 | } 137 | 138 | // 清除监听 139 | - (void)clearKVO { 140 | while (self.pagesCache.kvoTitles.count) { 141 | for (UIScrollView *child in [self.pagesCache scrollViewsForTitle:self.pagesCache.kvoTitles.lastObject]) { 142 | [child removeObserver:self forKeyPath:@"contentOffset"]; 143 | } 144 | 145 | [self.pagesCache.kvoTitles removeLastObject]; 146 | } 147 | } 148 | 149 | // 更换kvo监听 150 | - (void)setKVOForCurrentPage:(NSInteger)currentPage { 151 | if (self.pagesCache.titles.count == 0) return; 152 | 153 | [self clearKVO]; 154 | 155 | NSArray* childs = [self.pagesCache scrollViewsForTitle:self.pagesCache.titles[currentPage]]; 156 | if (childs && childs.count) { 157 | for (UIScrollView *child in childs) { 158 | [child addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; 159 | } 160 | [self.pagesCache.kvoTitles addObject:self.pagesCache.titles[currentPage]]; 161 | } 162 | } 163 | 164 | // 配置所有标题 165 | - (void)configAllTitles { 166 | NSArray *allTitles = [self.delegate cell_pagesViewAllTitles]; 167 | self.pagesCache.titles = allTitles; 168 | 169 | [self.pagesContainer setContentSize:CGSizeMake(allTitles.count * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds))]; 170 | } 171 | 172 | // 监听到rect变化,比如横屏 173 | - (void)rectChangedToPage:(NSInteger)page { 174 | if (!CGRectEqualToRect(self.contentView.bounds, self.bounds)) { 175 | self.contentView.frame = self.frame; 176 | } 177 | 178 | [self.pagesContainer setContentSize:CGSizeMake(self.pagesCache.titles.count * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds))]; 179 | 180 | [self resetAllRect]; 181 | 182 | [self changeToPage:page animate:NO]; 183 | } 184 | 185 | // 重置所有rect 186 | - (void)resetAllRect { 187 | [self layoutIfNeeded]; 188 | [self.pagesCache.titles enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 189 | [self setRectForPage:idx]; 190 | }]; 191 | } 192 | 193 | // 设置当前位置的view的frame 194 | - (void)setRectForPage:(NSInteger)page { 195 | 196 | UIView *c_view = [self.pagesCache viewForTitle:self.pagesCache.titles[page]]; 197 | 198 | if (c_view) { 199 | CGRect c_frame = self.bounds; 200 | c_frame.origin.x = page*CGRectGetWidth(self.bounds); 201 | [c_view setFrame:c_frame]; 202 | 203 | if (![c_view isDescendantOfView:self.pagesContainer]) { 204 | [self.pagesContainer addSubview:c_view]; 205 | } 206 | } 207 | } 208 | 209 | // 添加页面处理 210 | - (void)handleForPage:(NSInteger)page { 211 | if (!self.pagesCache.titles.count) return; 212 | 213 | _willShowPage = page; 214 | 215 | NSString *c_title = self.pagesCache.titles[page]; 216 | 217 | UIViewController *childController = [self.delegate cell_pagesViewChildControllerForIndex:page title:c_title]; 218 | 219 | /* 220 | 验证缓存,这里有个很实用的技巧, 221 | 为了提高性能,用双链表去管理子控制器, 222 | 通过LRU算法实现其生命周期函数的调用 223 | */ 224 | if (childController == [self dequeueReusablePageForIndex:page]) { 225 | [self.pagesCache setPage:childController title:c_title]; 226 | } else { 227 | [self.pagesCache cancelPageForTitle:c_title]; 228 | [self.pagesCache setPage:childController title:c_title]; 229 | [self setRectForPage:page]; 230 | } 231 | 232 | [self.pagesCache pageWillAppearHandle:!_isRectChanging]; 233 | } 234 | 235 | - (void)changeToPage:(NSInteger)page animate:(BOOL)animate { 236 | 237 | NSAssert((page>=0&&page=0&&index page_right) { 279 | // 当前页超出右边界时说明切换到了新的一页 280 | [self pageIndexDidChangedToPage:page_left]; 281 | } 282 | } 283 | 284 | // 页面已经更换处理 285 | - (void)pageIndexDidChangedToPage:(NSInteger)page { 286 | if (!self.pagesCache.titles.count || _isRectChanging) return; 287 | self.currentPage = page; 288 | [self.pagesCache pageDidApearHandle:!_isRectChanging]; 289 | 290 | NSString *c_title = self.pagesCache.titles[page]; 291 | UIViewController *c_vc = [self.pagesCache controllerForTitle:c_title]; 292 | 293 | [self.delegate cell_pagesViewDidChangeToPageController:c_vc 294 | title:c_title 295 | pageIndex:page]; 296 | } 297 | 298 | // 是否锁定主列表偏移通知 299 | - (void)mainTableLock:(BOOL)need offsety:(CGFloat)y { 300 | [self.delegate cell_mainTableNeedLock:need offsety:y]; 301 | } 302 | 303 | // 当横向滚动时不需要手势共享,达到禁止一切竖直滚动 304 | - (void)mainTabelNeedGesturePublick:(BOOL)publick { 305 | if (self.mainTable && self.mainTable.gesturePublic != publick) { 306 | self.mainTable.gesturePublic = publick; 307 | } 308 | } 309 | 310 | #pragma mark -- kvo 311 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 312 | 313 | UIScrollView *kvo_scroll = (UIScrollView *)object; 314 | 315 | // 手动拉动哪个滚动控件就把那个滚动控件作为当前滚动控件 316 | if (_currentKVOChild != kvo_scroll && kvo_scroll.isTracking) { 317 | [_childLock unlock]; 318 | _skipLoop = NO; 319 | _currentKVOChild = kvo_scroll; 320 | } 321 | 322 | // 如果滚动的并非当前滚动控件就过滤掉 323 | if (_currentKVOChild != kvo_scroll) return; 324 | 325 | //跳过本次监听 326 | if (_skipLoop) return; 327 | 328 | CGFloat changeSpace = [self.delegate cell_headerVerticalCanChangedSpace]; 329 | CGPoint oldPoint = [[change objectForKey:NSKeyValueChangeOldKey] CGPointValue]; 330 | CGPoint newPoint = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue]; 331 | 332 | if (oldPoint.y < newPoint.y) { 333 | /* 向上拉动 334 | 所有模式的上拉逻辑是相同的 335 | */ 336 | _mainOffsetStatic = _mainTable.contentOffset.y; 337 | 338 | if (_mainOffsetStatic < changeSpace) { 339 | if (newPoint.y >= 0) { 340 | [self mainTableLock:NO offsety:0]; 341 | 342 | _skipLoop = YES; 343 | kvo_scroll.contentOffset = CGPointMake(0, [_childLock lockValue:oldPoint.y >= 0 ? oldPoint.y : 0]); 344 | _skipLoop = NO; 345 | } else { 346 | [_childLock unlock]; 347 | } 348 | } else { 349 | [self mainTableLock:NO offsety:0]; 350 | [_childLock unlock]; 351 | } 352 | } 353 | else if (oldPoint.y > newPoint.y) { 354 | /* 向下拉动 */ 355 | [_childLock unlock]; 356 | if (newPoint.y > 0) { 357 | if (_mainOffsetStatic >= 0) { 358 | [self mainTableLock:YES offsety:_mainOffsetStatic]; 359 | } else { 360 | [self mainTableLock:NO offsety:0]; 361 | } 362 | } else { 363 | [self mainTableLock:NO offsety:0]; 364 | if (self.pagePullStyle == XDPagesPullOnCenter) { 365 | /* 366 | 子列表下拉逻辑 367 | 该逻辑下子列表偏移可小于0 368 | */ 369 | 370 | if (self.mainTable.contentOffset.y > 0) { 371 | _skipLoop = YES; 372 | kvo_scroll.contentOffset = CGPointMake(0, 0); 373 | _skipLoop = NO; 374 | } 375 | } else { 376 | /* 377 | 顶部下拉逻辑 378 | 该逻辑下子列表偏移不可小于0 379 | */ 380 | 381 | _skipLoop = YES; 382 | kvo_scroll.contentOffset = CGPointMake(0, 0); 383 | _skipLoop = NO; 384 | } 385 | } 386 | } 387 | } 388 | 389 | #pragma mark -- scroll_delegate 390 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 391 | if (scrollView.contentOffset.x >= 0 && scrollView.contentOffset.x <= scrollView.contentSize.width-scrollView.bounds.size.width) { 392 | 393 | [self pageAppearanceHandleByScrollXvalue:scrollView.contentOffset.x]; 394 | 395 | [self.delegate cell_pagesViewSafeHorizontalScrollOffsetxChanged:scrollView.contentOffset.x currentPage:self.currentPage willShowPage:self.willShowPage]; 396 | } 397 | 398 | [self.delegate cell_pagesViewHorizontalScrollOffsetxChanged:scrollView.contentOffset.x currentPage:self.currentPage willShowPage:self.willShowPage]; 399 | } 400 | 401 | //以下代理用于判断pagesContainer是否在滚动状态 402 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 403 | [self mainTabelNeedGesturePublick:NO]; 404 | } 405 | 406 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 407 | [self mainTabelNeedGesturePublick:YES]; 408 | } 409 | 410 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 411 | if (!decelerate) { 412 | [self mainTabelNeedGesturePublick:YES]; 413 | } 414 | } 415 | 416 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 417 | [self mainTabelNeedGesturePublick:YES]; 418 | } 419 | 420 | #pragma mark -- getter 421 | - (UIScrollView *)pagesContainer { 422 | if (!_pagesContainer) { 423 | _pagesContainer = [[UIScrollView alloc]initWithFrame:self.bounds]; 424 | _pagesContainer.pagingEnabled = YES; 425 | _pagesContainer.showsVerticalScrollIndicator = NO; 426 | _pagesContainer.showsHorizontalScrollIndicator = NO; 427 | _pagesContainer.delegate = self; 428 | _pagesContainer.backgroundColor = [UIColor clearColor]; 429 | _pagesContainer.bounces = _config.pagesHorizontalBounce; 430 | _pagesContainer.scrollEnabled = _config.pagesSlideEnable; 431 | [XDPagesTools closeAdjustForScroll:_pagesContainer controller:nil]; 432 | } 433 | 434 | return _pagesContainer; 435 | } 436 | 437 | - (XDPagesCache *)pagesCache { 438 | if (!_pagesCache) { 439 | _pagesCache = [XDPagesCache cache]; 440 | } 441 | 442 | return _pagesCache; 443 | } 444 | 445 | #pragma mark -- setter 446 | - (void)setCurrentPage:(NSInteger)currentPage { 447 | _currentPage = currentPage; 448 | [self setKVOForCurrentPage:currentPage]; 449 | } 450 | 451 | #pragma mark -- UI 452 | - (void)createUI { 453 | 454 | [self addSubview:self.pagesContainer]; 455 | 456 | self.pagesContainer.translatesAutoresizingMaskIntoConstraints = NO; 457 | 458 | NSLayoutConstraint *relat_top = [NSLayoutConstraint 459 | constraintWithItem:self.pagesContainer 460 | attribute:NSLayoutAttributeTop 461 | relatedBy:NSLayoutRelationEqual 462 | toItem:self 463 | attribute:NSLayoutAttributeTop 464 | multiplier:1 465 | constant:0]; 466 | NSLayoutConstraint *relat_led = [NSLayoutConstraint 467 | constraintWithItem:self.pagesContainer 468 | attribute:NSLayoutAttributeLeading 469 | relatedBy:NSLayoutRelationEqual 470 | toItem:self 471 | attribute:NSLayoutAttributeLeading 472 | multiplier:1 473 | constant:0]; 474 | NSLayoutConstraint *relat_btm = [NSLayoutConstraint 475 | constraintWithItem:self.pagesContainer 476 | attribute:NSLayoutAttributeBottom 477 | relatedBy:NSLayoutRelationEqual 478 | toItem:self 479 | attribute:NSLayoutAttributeBottom 480 | multiplier:1 481 | constant:0]; 482 | NSLayoutConstraint *relat_tal = [NSLayoutConstraint 483 | constraintWithItem:self.pagesContainer 484 | attribute:NSLayoutAttributeTrailing 485 | relatedBy:NSLayoutRelationEqual 486 | toItem:self 487 | attribute:NSLayoutAttributeTrailing 488 | multiplier:1 489 | constant:0]; 490 | [NSLayoutConstraint activateConstraints:@[relat_top, relat_led, relat_btm, relat_tal]]; 491 | } 492 | @end 493 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesConfig.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 配置 8 | 9 | #import 10 | #import 11 | #import "XDPagesTypes.h" 12 | 13 | /* 14 | (⚠️不可被刷新) 表示在reloadConfigs时,该属性的变更无效 15 | */ 16 | 17 | @interface XDPagesConfig : NSObject 18 | @property (nonatomic, assign) NSInteger beginPage; // 起始页(⚠️不可被刷新) 19 | @property (nonatomic, assign) NSInteger maxCacheCount; // 最大缓存页数 20 | @property (nonatomic, assign) BOOL pagesSlideEnable; // 是否可滑动翻页(默认YES) 21 | @property (nonatomic, assign) BOOL animateForPageChange; // 页面变动时是否需要动画(默认YES) 22 | @property (nonatomic, assign) BOOL pagesHorizontalBounce; // 是否页面边界自由滑动(默认YES) 23 | 24 | @property (nonatomic, assign) BOOL needTitleBar; // 是否需要标题栏(默认YES)(⚠️不可被刷新) 25 | @property (nonatomic, assign) BOOL titleBarFitHeader; // 是否标题栏和header作为一个整体(默认NO)(⚠️不可被刷新) 26 | @property (nonatomic, assign) CGFloat titleBarHeight; // 标题栏高度(默认50)(⚠️不可被刷新) 27 | @property (nonatomic, assign) CGFloat titleBarMarginTop; // 悬停位置距上端距离(默认0) 28 | @property (nonatomic, assign) BOOL needTitleBarBottomLine; // 是否需要标题栏底线(默认YES) 29 | @property (nonatomic, strong) UIColor *titleBarBottomLineColor; // 底线颜色(默认浅灰色) 30 | @property (nonatomic, assign) BOOL needTitleBarSlideLine; // 是否需要下滑线(默认YES) 31 | @property (nonatomic, assign) SlideLineStyle titleBarSlideLineStyle;// 下划线跟随方式 32 | @property (nonatomic, assign) CGFloat titleBarSlideLineWidthRatio; // 下滑线相对于当前item宽的比例[0-1] 33 | @property (nonatomic, strong) UIColor *titleBarSlideLineColor; // 下滑线颜色(默认灰色) 34 | @property (nonatomic, strong) UIColor *titleBarBackColor; // 标题栏背景色 35 | @property (nonatomic, strong) UIImage *titleBarBackImage; // 标题栏背景图 36 | @property (nonatomic, assign) BOOL titleBarHorizontalBounce; // 标题栏是否可以边界自由滑动(默认YES) 37 | @property (nonatomic, strong) UIView *customTitleBar; // 自定义标题栏(⚠️不可被刷新) 38 | 39 | @property (nonatomic, strong) UIColor *titleItemBackColor; // 标题背景颜色 40 | @property (nonatomic, strong) UIColor *titleItemBackHightlightColor;// 标题选中时背景颜色 41 | @property (nonatomic, strong) UIImage *titleItemBackImage; // 标题背景图片 42 | @property (nonatomic, strong) UIImage *titleItemBackHightlightImage;// 标题选中时的背景图片 43 | 44 | @property (nonatomic, assign) BOOL titleFlex; // 是否自动计算标题宽(默认YES) 45 | @property (nonatomic, assign) BOOL titleGradual; // 是否采用渐变方式(默认YES,只渐变标题属性) 46 | @property (nonatomic, assign) TitleVerticalAlignment titleVerticalAlignment; // 标题竖直对齐方式 47 | @property (nonatomic, strong) UIColor *titleTextColor; // 标题颜色 48 | @property (nonatomic, strong) UIColor *titleTextHightlightColor; // 标题选中时的颜色 49 | @property (nonatomic, strong) UIFont *titleFont; // 标题字号大小(默认16) 50 | @property (nonatomic, strong) UIFont *titleHightlightFont; // 选中后的字号大小(默认18) 51 | 52 | @property (nonatomic, assign) BOOL needRightBtn; // 是否需要右按钮(默认NO)(⚠️不可被刷新) 53 | @property (nonatomic, assign) CGSize rightBtnSize; // 右按钮大小 54 | @property (nonatomic, strong) UIView *rightBtn; // 右按钮自定义视图 55 | 56 | + (instancetype)config; 57 | @end 58 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesConfig.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesConfig.h" 10 | #import "XDPagesTools.h" 11 | 12 | @implementation XDPagesConfig 13 | + (instancetype)config { 14 | return [[self alloc]init]; 15 | } 16 | 17 | - (instancetype)init { 18 | self = [super init]; 19 | if (self) { 20 | [self defaultConfig]; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)defaultConfig { 26 | _beginPage = 0; 27 | _maxCacheCount = 20; 28 | _pagesSlideEnable = YES; 29 | _animateForPageChange = YES; 30 | _pagesHorizontalBounce = YES; 31 | 32 | _needTitleBar = YES; 33 | _titleBarFitHeader = NO; 34 | _titleBarHeight = 50; 35 | _titleBarMarginTop = 0; 36 | _needTitleBarBottomLine = YES; 37 | _titleBarBottomLineColor = [UIColor lightGrayColor]; 38 | _needTitleBarSlideLine = YES; 39 | _titleBarSlideLineStyle = XDSlideLine_translation; 40 | _titleBarSlideLineWidthRatio = 0.5; 41 | _titleBarSlideLineColor = [UIColor grayColor]; 42 | _titleBarBackColor = [UIColor orangeColor]; 43 | _titleBarBackImage = nil; 44 | _titleBarHorizontalBounce = YES; 45 | _customTitleBar = nil; 46 | 47 | _titleItemBackColor = [UIColor clearColor]; 48 | _titleItemBackHightlightColor = [UIColor clearColor]; 49 | _titleItemBackImage = nil; 50 | _titleItemBackHightlightImage = nil; 51 | 52 | _titleFlex = YES; 53 | _titleGradual = YES; 54 | _titleVerticalAlignment = XDVerticalAlignmentMiddle; 55 | _titleTextColor = [UIColor grayColor]; 56 | _titleTextHightlightColor = [UIColor greenColor]; 57 | _titleFont = [UIFont systemFontOfSize:16]; 58 | _titleHightlightFont = [UIFont systemFontOfSize:18]; 59 | 60 | _needRightBtn = NO; 61 | _rightBtnSize = CGSizeMake(40, 50); 62 | _rightBtn = nil; 63 | } 64 | 65 | - (void)setNeedTitleBar:(BOOL)needTitleBar { 66 | _needTitleBar = needTitleBar; 67 | if (!needTitleBar) { 68 | _titleBarHeight = 0; 69 | } 70 | } 71 | 72 | - (void)setTitleBarHeight:(CGFloat)titleBarHeight { 73 | if (!_needTitleBar) return; 74 | _titleBarHeight = fabs(titleBarHeight); 75 | } 76 | 77 | - (void)setTitleBarMarginTop:(CGFloat)titleBarMarginTop { 78 | _titleBarMarginTop = fabs(titleBarMarginTop); 79 | } 80 | 81 | - (void)setTitleBarFitHeader:(BOOL)titleBarFitHeader { 82 | _titleBarFitHeader = titleBarFitHeader; 83 | if (titleBarFitHeader) { 84 | _titleBarBackColor = [UIColor clearColor]; 85 | _titleBarBackImage = nil; 86 | } 87 | } 88 | 89 | - (void)setTitleBarBackColor:(UIColor *)titleBarBackColor { 90 | if (_titleBarFitHeader) return; 91 | _titleBarBackColor = titleBarBackColor; 92 | } 93 | 94 | - (void)setTitleBarBackImage:(UIImage *)titleBarBackImage { 95 | if (_titleBarFitHeader) return; 96 | _titleBarBackImage = titleBarBackImage; 97 | } 98 | @end 99 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesLayout.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 标题栏自定义布局 8 | 9 | #import 10 | 11 | @protocol XDPagesLayouDelegate 12 | - (CGSize)xd_itemLayoutSizeAtIndex:(NSIndexPath *)indexPath; 13 | @end 14 | @interface XDPagesLayout : UICollectionViewLayout 15 | @property (nonatomic, weak) id delegate; 16 | @property (nonatomic, strong) NSArray *allAttributes; 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesLayout.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesLayout.h" 10 | 11 | @interface XDPagesLayout () 12 | // 用来记录X值 13 | @property (nonatomic, assign) CGFloat item_x; 14 | 15 | // 用来记录高 16 | @property (nonatomic, assign) CGFloat item_height; 17 | 18 | // 保存每一个item的attributes 19 | @property (nonatomic, strong) NSMutableArray *attributesArray; 20 | @end 21 | @implementation XDPagesLayout 22 | - (instancetype)init { 23 | 24 | self = [super init]; 25 | 26 | if (self) { 27 | _item_x = 0; 28 | _item_height = 0; 29 | _attributesArray = @[].mutableCopy; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | - (void)prepareLayout { 36 | [super prepareLayout]; 37 | 38 | //关掉预估取值,否则在某些机型上可能会崩溃 39 | if (@available(iOS 10.0, *)) { 40 | if ([self.collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) { 41 | self.collectionView.prefetchingEnabled = NO; 42 | } 43 | } 44 | 45 | _item_x = 0; 46 | _item_height = 0; 47 | [self.attributesArray removeAllObjects]; 48 | 49 | // 获取Bar中cell总个数 50 | NSInteger items_insection_0 = [self.collectionView numberOfItemsInSection:0]; 51 | 52 | // 为每一个cell 创建一个依赖 53 | for (int i = 0; i < items_insection_0; i ++) { 54 | UICollectionViewLayoutAttributes *item_attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; 55 | [self.attributesArray addObject:item_attributes]; 56 | } 57 | 58 | self.allAttributes = [self.attributesArray copy]; 59 | } 60 | 61 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 62 | // 根据indexPath获取item的attributes 63 | UICollectionViewLayoutAttributes *item_attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 64 | 65 | // 从外面获取每个cell的高度 66 | CGSize item_size = [self.delegate xd_itemLayoutSizeAtIndex:indexPath]; 67 | 68 | // 设置attributes 69 | item_attribute.frame = CGRectMake(_item_x, 0, item_size.width, item_size.height); 70 | _item_x += item_size.width; 71 | _item_height = item_size.height; 72 | 73 | return item_attribute; 74 | } 75 | 76 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { 77 | return NO; 78 | } 79 | 80 | - (CGSize)collectionViewContentSize { 81 | return CGSizeMake(_item_x, _item_height); 82 | } 83 | 84 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 85 | return self.attributesArray; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesMacros.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesMacros.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2023/10/11. 6 | // Copyright © 2023 xie. All rights reserved. 7 | // 8 | #define XD_IGNORETAG 12321 //如果一个view的tag等于该值,则该view以及该view内部的所有视图都不会进入滚动监听 9 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesMap.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesMap.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 页面双链表 8 | 9 | #import 10 | #import "XDPagesNode.h" 11 | 12 | @interface XDPagesMap : NSObject { 13 | @package 14 | NSMutableDictionary *mapDic; // 存储map中的节点 15 | NSInteger count; // 当前节点个数 16 | XDPagesNode *header; // 头结点 17 | XDPagesNode *footer; // 尾节点 18 | } 19 | 20 | + (instancetype)map; 21 | - (void)insertNode:(XDPagesNode *)node; // 添加一个节点在最顶端 22 | - (void)addNode:(XDPagesNode *)node; // 添加一个节点在最末端 23 | 24 | - (void)bringNodeToHeader:(XDPagesNode *)node; // 把节点移动到最顶端 25 | - (void)bringNodeToFooter:(XDPagesNode *)node; // 把节点移动到最末端 26 | 27 | - (XDPagesNode *)removeFirstNode; // 删除最顶端节点 28 | - (XDPagesNode *)removeLastNode; // 删除最末端节点 29 | 30 | - (XDPagesNode *)removeNode:(XDPagesNode *)node; // 删除某个节点 31 | - (void)removeAllNode; // 删除所有节点 32 | 33 | @end 34 | 35 | 36 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesMap.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDMap.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesMap.h" 10 | 11 | @implementation XDPagesMap 12 | 13 | + (instancetype)map { 14 | return [[self alloc]init]; 15 | } 16 | 17 | - (instancetype)init { 18 | self = [super init]; 19 | if (self) { 20 | self->mapDic = @{}.mutableCopy; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)insertNode:(XDPagesNode *)node { 26 | if (!node || [self->mapDic objectForKey:node->key]) { 27 | return; 28 | } 29 | self->count += 1; 30 | if (!self->header) { 31 | self->header = node; 32 | self->footer = node; 33 | } else { 34 | self->header->pre = node; 35 | node->next = self->header; 36 | self->header = node; 37 | node->pre = nil; 38 | } 39 | [self->mapDic setValue:node forKey:node->key]; 40 | } 41 | 42 | - (void)addNode:(XDPagesNode *)node { 43 | if (!node || [self->mapDic objectForKey:node->key]) { 44 | return; 45 | } 46 | self->count += 1; 47 | if (!self->footer) { 48 | self->footer = node; 49 | self->header = node; 50 | } else { 51 | self->footer->next = node; 52 | node->pre = self->footer; 53 | self->footer = node; 54 | node->next = nil; 55 | } 56 | [self->mapDic setValue:node forKey:node->key]; 57 | } 58 | 59 | - (void)bringNodeToHeader:(XDPagesNode *)node { 60 | if (!node || ![self->mapDic objectForKey:node->key] || node == self->header) { 61 | return; 62 | } 63 | if (node->pre) node->pre->next = node->next; 64 | if (node->next) node->next->pre = node->pre; 65 | if (!node->next) self->footer = node->pre; 66 | self->header->pre = node; 67 | node->next = self->header; 68 | self->header = node; 69 | node->pre = nil; 70 | } 71 | 72 | - (void)bringNodeToFooter:(XDPagesNode *)node { 73 | if (!node || ![self->mapDic objectForKey:node->key] || node == self->footer) { 74 | return; 75 | } 76 | if (node->next) node->next->pre = node->pre; 77 | if (node->pre) node->pre->next = node->next; 78 | if (!node->pre) self->header = node->next; 79 | self->footer->next = node; 80 | node->pre = self->footer; 81 | self->footer = node; 82 | node->next = nil; 83 | } 84 | 85 | - (XDPagesNode *)removeFirstNode { 86 | if (!header) return nil; 87 | return [self removeNode:header]; 88 | } 89 | 90 | - (XDPagesNode *)removeLastNode { 91 | if (!footer) return nil; 92 | return [self removeNode:footer]; 93 | } 94 | 95 | - (XDPagesNode *)removeNode:(XDPagesNode *)node { 96 | if (!node || ![self->mapDic objectForKey:node->key]) { 97 | return node; 98 | } 99 | self->count -= 1; 100 | if (node->next) node->next->pre = node->pre; 101 | if (!node->next) footer = node->pre; 102 | if (node->pre) node->pre->next = node->next; 103 | if (!node->pre) header = node->next; 104 | [self->mapDic removeObjectForKey:node->key]; 105 | return node; 106 | } 107 | 108 | - (void)removeAllNode { 109 | [self->mapDic removeAllObjects]; 110 | self->count = 0; 111 | self->footer = nil; 112 | self->header = nil; 113 | } 114 | @end 115 | 116 | 117 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesNode.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 页面链表节点 8 | 9 | #import 10 | #import 11 | 12 | @interface XDPagesNode : NSObject { 13 | @package 14 | NSString *key; // key 15 | id value; // value 16 | UIColor *badgeColor; // 未读消息颜色 17 | BOOL isNumber; // 未读消息是否显示数字 18 | UIViewController *controller; // 控制器 19 | UIView *view; // 控制器中的子view 20 | NSArray *scrollViews; // 每页需要监控的所有滚动单元 21 | NSInteger right; // 加权 22 | __weak XDPagesNode *pre; // 上一个 23 | __weak XDPagesNode *next; // 下一个 24 | } 25 | + (instancetype)node; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDNode.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesNode.h" 10 | 11 | @implementation XDPagesNode 12 | + (instancetype)node { 13 | return [[self alloc]init]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTable.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTable.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 主table基类 8 | 9 | #import 10 | 11 | @interface XDPagesTable : UITableView 12 | @property (nonatomic, assign) BOOL gesturePublic;// 是否要共享手势 13 | @end 14 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTable.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTable.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesTable.h" 10 | 11 | @implementation XDPagesTable 12 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 13 | return self.gesturePublic; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTitle.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTitle.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 标题 8 | 9 | #import 10 | #import "XDPagesConfig.h" 11 | #import "XDPagesCache.h" 12 | @class XDPagesTitleLabel; 13 | 14 | @interface XDPagesTitle : UICollectionViewCell 15 | /// 初始化标题 16 | /// @param title 标题 17 | /// @param fidx 当前选中项索引 18 | /// @param config 配置 19 | /// @param badge 未读标记 20 | /// @param indexPath 索引 21 | - (void)configTitleByTitle:(NSString *)title focusIdx:(NSInteger)fidx config:(XDPagesConfig *)config badge:(XDBADGE)badge indexPath:(NSIndexPath *)indexPath; 22 | 23 | // 渐变上升 24 | - (void)gradualUpByConfig:(XDPagesConfig *)config percent:(CGFloat)percent; 25 | // 渐变下降 26 | - (void)gradualDownByConfig:(XDPagesConfig *)config percent:(CGFloat)percent; 27 | @end 28 | 29 | @interface XDPagesTitleLabel : UILabel 30 | @property (nonatomic, strong) XDPagesConfig *config; 31 | @end 32 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTitle.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTitle.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesTitle.h" 10 | 11 | @interface XDPagesTitle () 12 | @property (nonatomic, strong) XDPagesTitleLabel *title; 13 | @property (nonatomic, strong) UIImageView *backImage; 14 | @property (nonatomic, strong) UILabel *tipLabel; 15 | @property (nonatomic, strong) UIView *numberTip; 16 | @property (nonatomic, strong) UILabel *tipNumerLabel; 17 | @end 18 | 19 | typedef struct { 20 | CGFloat red; 21 | CGFloat green; 22 | CGFloat blue; 23 | CGFloat alpha; 24 | } XDRGB; 25 | 26 | NS_INLINE XDRGB 27 | XDRGBMake(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { 28 | XDRGB rgb; 29 | rgb.red = red; 30 | rgb.green = green; 31 | rgb.blue = blue; 32 | rgb.alpha = alpha; 33 | return rgb; 34 | } 35 | 36 | @implementation XDPagesTitle 37 | - (instancetype)initWithFrame:(CGRect)frame { 38 | 39 | self = [super initWithFrame:frame]; 40 | 41 | if (self) { 42 | [self creatItem]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)configTitleByTitle:(NSString *)title focusIdx:(NSInteger)fidx config:(XDPagesConfig *)config badge:(XDBADGE)badge indexPath:(NSIndexPath *)indexPath { 49 | self.title.config = config; 50 | self.title.text = title; 51 | 52 | self.tipLabel.hidden = badge.badgeNumber > 0 ? NO : YES; 53 | self.numberTip.hidden = badge.badgeNumber > 0 ? NO : YES; 54 | 55 | if (badge.isNumber) { 56 | //以数字形式展示未读 57 | self.numberTip.backgroundColor = badge.badgeColor; 58 | self.tipNumerLabel.text = badge.badgeNumber > 99 ? @"99+" : @(badge.badgeNumber).stringValue; 59 | self.tipLabel.hidden = YES; 60 | [self layoutIfNeeded]; 61 | } else { 62 | //以红点形式展示未读 63 | self.tipLabel.backgroundColor = badge.badgeColor; 64 | self.numberTip.hidden = YES; 65 | [self layoutIfNeeded]; 66 | } 67 | 68 | if (fidx == indexPath.row) { 69 | // 当前选中 70 | self.backImage.backgroundColor = config.titleItemBackHightlightColor; 71 | self.backImage.image = config.titleItemBackHightlightImage; 72 | self.title.backgroundColor = config.titleItemBackHightlightColor; 73 | self.title.font = config.titleHightlightFont; 74 | self.title.textColor = config.titleTextHightlightColor; 75 | 76 | } else { 77 | // 未选中 78 | self.backImage.backgroundColor = config.titleItemBackColor; 79 | self.backImage.image = config.titleItemBackImage; 80 | self.title.backgroundColor = config.titleItemBackColor; 81 | self.title.font = config.titleFont; 82 | self.title.textColor = config.titleTextColor; 83 | } 84 | } 85 | 86 | - (void)gradualUpByConfig:(XDPagesConfig *)config percent:(CGFloat)percent { 87 | // 标题渐变 88 | CGFloat d_value = config.titleHightlightFont.pointSize-config.titleFont.pointSize; 89 | self.title.font = [UIFont systemFontOfSize:config.titleFont.pointSize + d_value*percent weight:[[config.titleHightlightFont.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute][UIFontWeightTrait] floatValue]]; 90 | 91 | // 标题颜色渐变 92 | XDRGB c_RGB = [self getRGBValueFromColor:config.titleTextColor]; 93 | XDRGB w_RGB = [self getRGBValueFromColor:config.titleTextHightlightColor]; 94 | CGFloat g_red = c_RGB.red + (w_RGB.red - c_RGB.red) * percent; 95 | CGFloat g_green = c_RGB.green + (w_RGB.green - c_RGB.green) * percent; 96 | CGFloat g_blue = c_RGB.blue + (w_RGB.blue - c_RGB.blue) * percent; 97 | 98 | self.title.textColor = [[UIColor alloc]initWithRed:g_red green:g_green blue:g_blue alpha:1]; 99 | } 100 | 101 | - (void)gradualDownByConfig:(XDPagesConfig *)config percent:(CGFloat)percent { 102 | // 标题渐变 103 | CGFloat d_value = config.titleHightlightFont.pointSize-config.titleFont.pointSize; 104 | self.title.font = [UIFont systemFontOfSize:config.titleHightlightFont.pointSize - d_value*percent weight:[[config.titleFont.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute][UIFontWeightTrait] floatValue]]; 105 | 106 | // 标题颜色渐变 107 | XDRGB c_RGB = [self getRGBValueFromColor:config.titleTextColor]; 108 | XDRGB w_RGB = [self getRGBValueFromColor:config.titleTextHightlightColor]; 109 | CGFloat g_red = w_RGB.red - (w_RGB.red - c_RGB.red) * percent; 110 | CGFloat g_green = w_RGB.green - (w_RGB.green - c_RGB.green) * percent; 111 | CGFloat g_blue = w_RGB.blue - (w_RGB.blue - c_RGB.blue) * percent; 112 | 113 | self.title.textColor = [[UIColor alloc]initWithRed:g_red green:g_green blue:g_blue alpha:1]; 114 | } 115 | 116 | // 颜色RGB值 117 | - (XDRGB)getRGBValueFromColor:(UIColor *)color { 118 | 119 | CGFloat red = 0.0; 120 | CGFloat green = 0.0; 121 | CGFloat blue = 0.0; 122 | CGFloat alpha = 1.0; 123 | 124 | [color getRed:&red green:&green blue:&blue alpha:&alpha]; 125 | 126 | return XDRGBMake(red, green, blue, alpha); 127 | } 128 | 129 | #pragma mark -- getter 130 | - (XDPagesTitleLabel *)title { 131 | if (!_title) { 132 | _title = [[XDPagesTitleLabel alloc]initWithFrame:self.bounds]; 133 | _title.backgroundColor = [UIColor clearColor]; 134 | _title.textAlignment = NSTextAlignmentCenter; 135 | } 136 | 137 | return _title; 138 | } 139 | 140 | - (UIImageView *)backImage { 141 | if (!_backImage) { 142 | _backImage = [[UIImageView alloc]initWithFrame:self.bounds]; 143 | _backImage.contentMode = UIViewContentModeScaleAspectFill; 144 | _backImage.userInteractionEnabled = YES; 145 | } 146 | 147 | return _backImage; 148 | } 149 | 150 | - (UILabel *)tipLabel { 151 | if (!_tipLabel) { 152 | _tipLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 8, 8)]; 153 | _tipLabel.clipsToBounds = YES; 154 | _tipLabel.layer.cornerRadius = 4; 155 | _tipLabel.backgroundColor = UIColor.redColor; 156 | _tipLabel.hidden = YES; 157 | } 158 | 159 | return _tipLabel; 160 | } 161 | 162 | - (UILabel *)tipNumerLabel { 163 | if (!_tipNumerLabel) { 164 | _tipNumerLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 16, 16)]; 165 | _tipNumerLabel.backgroundColor = UIColor.clearColor; 166 | _tipNumerLabel.textAlignment = NSTextAlignmentCenter; 167 | _tipNumerLabel.textColor = UIColor.whiteColor; 168 | _tipNumerLabel.font = [UIFont systemFontOfSize:11.f]; 169 | } 170 | 171 | return _tipNumerLabel; 172 | } 173 | 174 | - (UIView *)numberTip { 175 | if (!_numberTip) { 176 | _numberTip = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 16, 16)]; 177 | _numberTip.clipsToBounds = YES; 178 | _numberTip.layer.cornerRadius = 8; 179 | } 180 | 181 | return _numberTip; 182 | } 183 | 184 | #pragma mark -- UI 185 | - (void)creatItem { 186 | 187 | [self.contentView addSubview:self.backImage]; 188 | [self.contentView addSubview:self.title]; 189 | [self.contentView addSubview:self.tipLabel]; 190 | [self.contentView addSubview:self.numberTip]; 191 | [self.numberTip addSubview:self.tipNumerLabel]; 192 | self.backImage.translatesAutoresizingMaskIntoConstraints = NO; 193 | self.title.translatesAutoresizingMaskIntoConstraints = NO; 194 | self.tipLabel.translatesAutoresizingMaskIntoConstraints = NO; 195 | self.tipNumerLabel.translatesAutoresizingMaskIntoConstraints = NO; 196 | self.numberTip.translatesAutoresizingMaskIntoConstraints = NO; 197 | 198 | // backImage 199 | NSLayoutConstraint *back_top = [NSLayoutConstraint 200 | constraintWithItem:self.backImage 201 | attribute:NSLayoutAttributeTop 202 | relatedBy:NSLayoutRelationEqual 203 | toItem:self.contentView 204 | attribute:NSLayoutAttributeTop 205 | multiplier:1 206 | constant:0]; 207 | NSLayoutConstraint *back_lef = [NSLayoutConstraint 208 | constraintWithItem:self.backImage 209 | attribute:NSLayoutAttributeLeading 210 | relatedBy:NSLayoutRelationEqual 211 | toItem:self.contentView 212 | attribute:NSLayoutAttributeLeading 213 | multiplier:1 214 | constant:0]; 215 | NSLayoutConstraint *back_btm = [NSLayoutConstraint 216 | constraintWithItem:self.backImage 217 | attribute:NSLayoutAttributeBottom 218 | relatedBy:NSLayoutRelationEqual 219 | toItem:self.contentView 220 | attribute:NSLayoutAttributeBottom 221 | multiplier:1 222 | constant:0]; 223 | NSLayoutConstraint *back_rit = [NSLayoutConstraint 224 | constraintWithItem:self.backImage 225 | attribute:NSLayoutAttributeTrailing 226 | relatedBy:NSLayoutRelationEqual 227 | toItem:self.contentView 228 | attribute:NSLayoutAttributeTrailing 229 | multiplier:1 230 | constant:0]; 231 | [NSLayoutConstraint activateConstraints:@[back_top, back_lef, back_btm, back_rit]]; 232 | 233 | // title 234 | NSLayoutConstraint *title_top = [NSLayoutConstraint 235 | constraintWithItem:self.title 236 | attribute:NSLayoutAttributeTop 237 | relatedBy:NSLayoutRelationEqual 238 | toItem:self.contentView 239 | attribute:NSLayoutAttributeTop 240 | multiplier:1 241 | constant:0]; 242 | NSLayoutConstraint *title_lef = [NSLayoutConstraint 243 | constraintWithItem:self.title 244 | attribute:NSLayoutAttributeLeading 245 | relatedBy:NSLayoutRelationGreaterThanOrEqual 246 | toItem:self.contentView 247 | attribute:NSLayoutAttributeLeading 248 | multiplier:1 249 | constant:0]; 250 | NSLayoutConstraint *title_btm = [NSLayoutConstraint 251 | constraintWithItem:self.title 252 | attribute:NSLayoutAttributeBottom 253 | relatedBy:NSLayoutRelationEqual 254 | toItem:self.contentView 255 | attribute:NSLayoutAttributeBottom 256 | multiplier:1 257 | constant:0]; 258 | NSLayoutConstraint *title_rit = [NSLayoutConstraint 259 | constraintWithItem:self.title 260 | attribute:NSLayoutAttributeTrailing 261 | relatedBy:NSLayoutRelationLessThanOrEqual 262 | toItem:self.contentView 263 | attribute:NSLayoutAttributeTrailing 264 | multiplier:1 265 | constant:0]; 266 | NSLayoutConstraint *title_mid = [NSLayoutConstraint 267 | constraintWithItem:self.title 268 | attribute:NSLayoutAttributeCenterX 269 | relatedBy:NSLayoutRelationEqual 270 | toItem:self.contentView 271 | attribute:NSLayoutAttributeCenterX 272 | multiplier:1 273 | constant:0]; 274 | [NSLayoutConstraint activateConstraints:@[title_top, title_lef, title_btm, title_rit, title_mid]]; 275 | 276 | [self redDotTipLayout]; 277 | [self numberTipLayout]; 278 | } 279 | 280 | - (void)redDotTipLayout { 281 | // tip for red dot type 282 | NSLayoutConstraint *tip_top = [NSLayoutConstraint 283 | constraintWithItem:self.tipLabel 284 | attribute:NSLayoutAttributeCenterY 285 | relatedBy:NSLayoutRelationEqual 286 | toItem:self.contentView 287 | attribute:NSLayoutAttributeCenterY 288 | multiplier:1 289 | constant:-6]; 290 | NSLayoutConstraint *tip_lef = [NSLayoutConstraint 291 | constraintWithItem:self.tipLabel 292 | attribute:NSLayoutAttributeLeft 293 | relatedBy:NSLayoutRelationEqual 294 | toItem:self.title 295 | attribute:NSLayoutAttributeRight 296 | multiplier:1 297 | constant:0]; 298 | NSLayoutConstraint *tip_wid = [NSLayoutConstraint 299 | constraintWithItem:self.tipLabel 300 | attribute:NSLayoutAttributeWidth 301 | relatedBy:NSLayoutRelationEqual 302 | toItem:nil 303 | attribute:NSLayoutAttributeNotAnAttribute 304 | multiplier:1 305 | constant:8]; 306 | NSLayoutConstraint *tip_hei = [NSLayoutConstraint 307 | constraintWithItem:self.tipLabel 308 | attribute:NSLayoutAttributeHeight 309 | relatedBy:NSLayoutRelationEqual 310 | toItem:nil 311 | attribute:NSLayoutAttributeNotAnAttribute 312 | multiplier:1 313 | constant:8]; 314 | [NSLayoutConstraint activateConstraints:@[tip_top, tip_lef, tip_wid, tip_hei]]; 315 | } 316 | 317 | - (void)numberTipLayout { 318 | // tip for number type 319 | 320 | NSLayoutConstraint *lab_c_y = [NSLayoutConstraint 321 | constraintWithItem:self.tipNumerLabel 322 | attribute:NSLayoutAttributeCenterY 323 | relatedBy:NSLayoutRelationEqual 324 | toItem:self.numberTip 325 | attribute:NSLayoutAttributeCenterY 326 | multiplier:1 327 | constant:0]; 328 | NSLayoutConstraint *lab_rig = [NSLayoutConstraint 329 | constraintWithItem:self.tipNumerLabel 330 | attribute:NSLayoutAttributeTrailing 331 | relatedBy:NSLayoutRelationEqual 332 | toItem:self.numberTip 333 | attribute:NSLayoutAttributeTrailing 334 | multiplier:1 335 | constant:-5]; 336 | NSLayoutConstraint *lab_lef = [NSLayoutConstraint 337 | constraintWithItem:self.tipNumerLabel 338 | attribute:NSLayoutAttributeLeading 339 | relatedBy:NSLayoutRelationEqual 340 | toItem:self.numberTip 341 | attribute:NSLayoutAttributeLeading 342 | multiplier:1 343 | constant:5]; 344 | NSLayoutConstraint *lab_wid = [NSLayoutConstraint 345 | constraintWithItem:self.tipNumerLabel 346 | attribute:NSLayoutAttributeWidth 347 | relatedBy:NSLayoutRelationGreaterThanOrEqual 348 | toItem:nil 349 | attribute:NSLayoutAttributeNotAnAttribute 350 | multiplier:1 351 | constant:0]; 352 | [NSLayoutConstraint activateConstraints:@[lab_c_y, lab_lef, lab_rig, lab_wid]]; 353 | 354 | 355 | NSLayoutConstraint *tip_top = [NSLayoutConstraint 356 | constraintWithItem:self.numberTip 357 | attribute:NSLayoutAttributeCenterY 358 | relatedBy:NSLayoutRelationEqual 359 | toItem:self.contentView 360 | attribute:NSLayoutAttributeCenterY 361 | multiplier:1 362 | constant:-6]; 363 | NSLayoutConstraint *tip_rig = [NSLayoutConstraint 364 | constraintWithItem:self.numberTip 365 | attribute:NSLayoutAttributeRight 366 | relatedBy:NSLayoutRelationEqual 367 | toItem:self.title 368 | attribute:NSLayoutAttributeRight 369 | multiplier:1 370 | constant:10]; 371 | NSLayoutConstraint *tip_wid = [NSLayoutConstraint 372 | constraintWithItem:self.numberTip 373 | attribute:NSLayoutAttributeWidth 374 | relatedBy:NSLayoutRelationGreaterThanOrEqual 375 | toItem:nil 376 | attribute:NSLayoutAttributeNotAnAttribute 377 | multiplier:1 378 | constant:16]; 379 | NSLayoutConstraint *tip_hei = [NSLayoutConstraint 380 | constraintWithItem:self.numberTip 381 | attribute:NSLayoutAttributeHeight 382 | relatedBy:NSLayoutRelationEqual 383 | toItem:nil 384 | attribute:NSLayoutAttributeNotAnAttribute 385 | multiplier:1 386 | constant:16]; 387 | [NSLayoutConstraint activateConstraints:@[tip_top, tip_rig, tip_wid, tip_hei]]; 388 | } 389 | 390 | @end 391 | 392 | @implementation XDPagesTitleLabel 393 | 394 | - (id)init { 395 | self = [super init]; 396 | if (self) { 397 | [self setNeedsDisplay]; 398 | } 399 | 400 | return self; 401 | } 402 | 403 | - (void)setConfig:(XDPagesConfig *)config { 404 | _config = config; 405 | [self setNeedsDisplay]; 406 | } 407 | 408 | - (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { 409 | TitleVerticalAlignment alignment = _config.titleVerticalAlignment; 410 | CGRect textRect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; 411 | CGFloat topSpace = 2; 412 | CGFloat bottomSpace = _config.needTitleBarSlideLine ? 5 : 2; 413 | CGFloat hf_edge = (bounds.size.height-textRect.size.height) / 2.0; 414 | 415 | switch (alignment) { 416 | case XDVerticalAlignmentTop: 417 | if (hf_edge > topSpace) { 418 | textRect.origin.y = bounds.origin.y + topSpace; 419 | } else { 420 | textRect.origin.y = bounds.origin.y + hf_edge; 421 | } 422 | break; 423 | 424 | case XDVerticalAlignmentMiddle: 425 | textRect.origin.y = bounds.origin.y + hf_edge; 426 | break; 427 | 428 | case XDVerticalAlignmentBottom: 429 | if (hf_edge > bottomSpace) { 430 | textRect.origin.y = bounds.origin.y + hf_edge * 2 - bottomSpace; 431 | } else { 432 | textRect.origin.y = bounds.origin.y + hf_edge; 433 | } 434 | break; 435 | 436 | default: 437 | textRect.origin.y = bounds.origin.y + hf_edge; 438 | break; 439 | } 440 | 441 | return textRect; 442 | } 443 | 444 | - (void)drawTextInRect:(CGRect)rect { 445 | CGRect actualRect = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines]; 446 | [super drawTextInRect:actualRect]; 447 | } 448 | 449 | @end 450 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTitleBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTitleBar.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 标题栏 8 | 9 | #import 10 | #import "XDPagesConfig.h" 11 | 12 | @protocol XDPagesTitleBarDelegate 13 | 14 | - (void)xd_titleTapAtIndex:(NSInteger)index; 15 | - (CGFloat)xd_titleWidthForIndex:(NSInteger)index title:(NSString *)title; 16 | 17 | @end 18 | @interface XDPagesTitleBar : UIView 19 | @property (nonatomic, weak) id delegate; 20 | - (instancetype)initWithFrame:(CGRect)frame config:(XDPagesConfig *)config titles:(NSArray*)titles; 21 | /** 22 | 刷新配置项 23 | */ 24 | - (void)reloadConfigs; 25 | 26 | // 刷新标题 27 | - (void(^)(NSArray * titles))refreshTitles; 28 | // 滑动翻页时,聚焦到当前页标题 29 | - (void(^)(NSInteger focusIdx))currentFocusIndex; 30 | // 展示某个item的未读消息 31 | - (void)showBadgeNumber:(NSInteger)number index:(NSInteger)idx color:(UIColor *)color isNumber:(BOOL)isNumber; 32 | // 监听横向滑动值 当前页 将要跳转到的页面 页面宽度,用于渐变动画 33 | - (void)pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willToPage:(NSInteger)willToPage width:(CGFloat)width; 34 | @end 35 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTitleBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTitleBar.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/29. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesTitleBar.h" 10 | #import "XDPagesLayout.h" 11 | #import "XDPagesTitle.h" 12 | #import "XDPagesTools.h" 13 | #import "XDSlideEffect.h" 14 | 15 | @interface XDPagesTitleBar () 16 | @property (nonatomic, strong) UICollectionView *titleBar; 17 | @property (nonatomic, strong) XDPagesCache *cache; 18 | @property (nonatomic, strong) XDPagesLayout *layout; 19 | @property (nonatomic, strong) XDPagesConfig *config; 20 | @property (nonatomic, strong) XDSlideEffect *effect; 21 | @property (nonatomic, strong) NSArray *titles; 22 | @property (nonatomic, strong) UIImageView *backImage; 23 | @property (nonatomic, assign) NSInteger focusIndex; 24 | /** 25 | ------------------------------------------------- 26 | | title_1 | title_2 | .... | rightBtn | 27 | ------------------------------------------------- 28 | */ 29 | @property (nonatomic, strong) UIView *rightBtn; 30 | @property (nonatomic, strong) UIView *bottomLine; 31 | @property (nonatomic, strong) UIView *slideLine; 32 | 33 | @property (nonatomic, copy) NSArray *backImage_layouts; 34 | @property (nonatomic, copy) NSArray *titleBar_layouts; 35 | @property (nonatomic, copy) NSArray *rightBtn_layouts; 36 | @property (nonatomic, copy) NSArray *bottomLine_layouts; 37 | @end 38 | 39 | @implementation XDPagesTitleBar 40 | - (instancetype)initWithFrame:(CGRect)frame config:(XDPagesConfig *)config titles:(NSArray *)titles { 41 | 42 | self = [super initWithFrame:frame]; 43 | 44 | if (self) { 45 | self.config = config; 46 | self.titles = titles; 47 | 48 | [self addSubview:self.backImage]; 49 | [self addSubview:self.titleBar]; 50 | [self addSubview:self.rightBtn]; 51 | [self addSubview:self.bottomLine]; 52 | 53 | dispatch_async(dispatch_get_main_queue(), ^{ 54 | [self.titleBar addSubview:self.slideLine]; 55 | }); 56 | 57 | self.backImage.translatesAutoresizingMaskIntoConstraints = NO; 58 | self.titleBar.translatesAutoresizingMaskIntoConstraints = NO; 59 | self.rightBtn.translatesAutoresizingMaskIntoConstraints = NO; 60 | self.bottomLine.translatesAutoresizingMaskIntoConstraints = NO; 61 | [self barLayouts]; 62 | } 63 | 64 | return self; 65 | } 66 | 67 | - (void)reloadConfigs { 68 | [self barLayouts]; 69 | _backImage.image = _config.titleBarBackImage; 70 | _backImage.backgroundColor = _config.titleBarBackColor; 71 | 72 | _titleBar.bounces = _config.titleBarHorizontalBounce; 73 | 74 | _bottomLine.hidden = !_config.needTitleBarBottomLine; 75 | _bottomLine.backgroundColor = _config.titleBarBottomLineColor; 76 | 77 | _slideLine.hidden = !_config.needTitleBarSlideLine; 78 | _slideLine.backgroundColor = _config.titleBarSlideLineColor; 79 | CGRect c_slideLine_Frame = _slideLine.frame; 80 | c_slideLine_Frame.origin.y = CGRectGetHeight(self.frame) - 3 - (_config.needTitleBarBottomLine ? 0.4 : 0); 81 | _slideLine.frame = c_slideLine_Frame; 82 | 83 | [_titleBar reloadData]; 84 | } 85 | 86 | - (void)pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willToPage:(NSInteger)willToPage width:(CGFloat)width { 87 | 88 | // 保持百分比为正数 89 | CGFloat percent = fabs((changedx-page*width)/width); 90 | 91 | if (_config.needTitleBarSlideLine) { 92 | 93 | // 下划线滑动效果 94 | if (_config.titleBarSlideLineStyle == XDSlideLine_translation) { 95 | 96 | [self.effect slideLineTransEffectForView:self.slideLine 97 | attributes:_layout.allAttributes 98 | currentPage:page 99 | willPage:willToPage 100 | percent:percent 101 | ratio:_config.titleBarSlideLineWidthRatio]; 102 | 103 | } else if (_config.titleBarSlideLineStyle == XDSlideLine_Scale) { 104 | 105 | [self.effect slideLineScaleEffectForView:self.slideLine 106 | attributes:_layout.allAttributes 107 | currentPage:page 108 | willPage:willToPage 109 | percent:percent 110 | ratio:_config.titleBarSlideLineWidthRatio]; 111 | 112 | } else { 113 | 114 | [self.effect slideLineNoneEffectForView:self.slideLine 115 | attributes:_layout.allAttributes 116 | currentPage:page 117 | willPage:willToPage 118 | ratio:_config.titleBarSlideLineWidthRatio]; 119 | } 120 | } 121 | 122 | // 标题渐变 123 | if (_config.titleGradual && page != willToPage) { 124 | 125 | NSIndexPath *c_idx = [NSIndexPath indexPathForItem:page inSection:0]; 126 | NSIndexPath *w_idx = [NSIndexPath indexPathForItem:willToPage inSection:0]; 127 | 128 | XDPagesTitle *c_title = (XDPagesTitle*)[self.titleBar cellForItemAtIndexPath:c_idx]; 129 | XDPagesTitle *w_title = (XDPagesTitle*)[self.titleBar cellForItemAtIndexPath:w_idx]; 130 | 131 | [c_title gradualDownByConfig:_config percent:percent]; 132 | [w_title gradualUpByConfig:_config percent:percent]; 133 | 134 | } 135 | 136 | if (page == willToPage) { 137 | [self refreshTitleBar]; 138 | } 139 | } 140 | 141 | - (void)showBadgeNumber:(NSInteger)number index:(NSInteger)idx color:(UIColor *)color isNumber:(BOOL)isNumber { 142 | NSInteger count = [self.titleBar numberOfItemsInSection:0]; 143 | if (count > 0 && idx < count) { 144 | [self.cache setBadgeForIndex:idx number:number color:color isNumber:isNumber]; 145 | [self.titleBar reloadData]; 146 | } 147 | } 148 | 149 | #pragma mark -- delegate 150 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 151 | return 1; 152 | } 153 | 154 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 155 | return _titles.count; 156 | } 157 | 158 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 159 | if (_focusIndex != indexPath.row) { 160 | [self.delegate xd_titleTapAtIndex:indexPath.row]; 161 | } 162 | } 163 | 164 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 165 | 166 | XDPagesTitle *title = [collectionView dequeueReusableCellWithReuseIdentifier:@"title" forIndexPath:indexPath]; 167 | 168 | [title configTitleByTitle:_titles[indexPath.row] focusIdx:_focusIndex config:_config badge:[self.cache badgeNumberForIndex:indexPath.row] indexPath:indexPath]; 169 | 170 | return title; 171 | } 172 | 173 | #pragma mark -- XDPagesLayouDelegate 174 | - (CGSize)xd_itemLayoutSizeAtIndex:(NSIndexPath *)indexPath { 175 | CGFloat c_width = 0; 176 | if (_config.titleFlex) { 177 | c_width = [XDPagesTools adjustItemWidthByString:_titles[indexPath.row] 178 | font:_config.titleFont.pointSize 179 | baseSize:CGSizeMake(MAXFLOAT, _config.titleFont.pointSize+2)]+30; 180 | } else { 181 | c_width = [self.delegate xd_titleWidthForIndex:indexPath.row title:_titles[indexPath.row]]; 182 | if (c_width < 0) { 183 | c_width = [XDPagesTools adjustItemWidthByString:_titles[indexPath.row] 184 | font:_config.titleFont.pointSize 185 | baseSize:CGSizeMake(MAXFLOAT, _config.titleFont.pointSize+2)]+30; 186 | } 187 | } 188 | 189 | return CGSizeMake(c_width, CGRectGetHeight(self.frame)-(self.config.needTitleBarBottomLine ? 0.5 : 0)); 190 | } 191 | 192 | #pragma mark -- getter 193 | - (XDPagesCache *)cache { 194 | if (!_cache) { 195 | _cache = [XDPagesCache cache]; 196 | } 197 | 198 | return _cache; 199 | } 200 | 201 | - (XDSlideEffect *)effect { 202 | if (!_effect) { 203 | _effect = [[XDSlideEffect alloc]init]; 204 | } 205 | 206 | return _effect; 207 | } 208 | 209 | - (UIImageView *)backImage { 210 | if (!_backImage) { 211 | _backImage = [[UIImageView alloc] initWithImage:self.config.titleBarBackImage]; 212 | _backImage.contentMode = UIViewContentModeScaleAspectFill; 213 | _backImage.backgroundColor = self.config.titleBarBackColor; 214 | _backImage.clipsToBounds = YES; 215 | } 216 | 217 | return _backImage; 218 | } 219 | 220 | - (UICollectionView *)titleBar { 221 | if (!_titleBar) { 222 | 223 | _layout = [[XDPagesLayout alloc]init]; 224 | 225 | _titleBar = [[UICollectionView alloc]initWithFrame:self.bounds collectionViewLayout:_layout]; 226 | _layout.delegate = self; 227 | _titleBar.delegate = self; 228 | _titleBar.dataSource = self; 229 | _titleBar.alwaysBounceHorizontal = YES; 230 | _titleBar.showsVerticalScrollIndicator = NO; 231 | _titleBar.showsHorizontalScrollIndicator = NO; 232 | _titleBar.backgroundColor = [UIColor clearColor]; 233 | _titleBar.bounces = _config.titleBarHorizontalBounce; 234 | [_titleBar registerClass:XDPagesTitle.class forCellWithReuseIdentifier:@"title"]; 235 | } 236 | 237 | return _titleBar; 238 | } 239 | 240 | - (UIView *)rightBtn { 241 | if (!_rightBtn) { 242 | _rightBtn = [[UIView alloc]initWithFrame:CGRectZero]; 243 | _rightBtn.backgroundColor = [UIColor clearColor]; 244 | _rightBtn.clipsToBounds = YES; 245 | _rightBtn.hidden = !self.config.needRightBtn; 246 | } 247 | 248 | return _rightBtn; 249 | } 250 | 251 | - (UIView *)bottomLine { 252 | if (!_bottomLine) { 253 | _bottomLine = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0.5)]; 254 | _bottomLine.backgroundColor = _config.titleBarBottomLineColor; 255 | _bottomLine.hidden = !_config.needTitleBarBottomLine; 256 | } 257 | 258 | return _bottomLine; 259 | } 260 | 261 | - (UIView *)slideLine { 262 | if (!_slideLine) { 263 | _slideLine = [[UIView alloc]initWithFrame:CGRectMake(0, CGRectGetHeight(self.frame)-3-(_config.needTitleBarBottomLine ? 0.4 : 0), 0, 3)]; 264 | _slideLine.clipsToBounds = YES; 265 | _slideLine.layer.cornerRadius = 1.5; 266 | _slideLine.hidden = YES; 267 | _slideLine.backgroundColor = _config.titleBarSlideLineColor; 268 | } 269 | 270 | return _slideLine; 271 | } 272 | 273 | - (void (^)(NSArray *))refreshTitles { 274 | __weak typeof(self) weakSelf = self; 275 | return ^(NSArray *titles) { 276 | weakSelf.titles = titles; 277 | [weakSelf refreshTitleBar]; 278 | }; 279 | } 280 | 281 | - (void (^)(NSInteger))currentFocusIndex { 282 | __weak typeof(self) weakSelf = self; 283 | return ^(NSInteger focusIndex) { 284 | [weakSelf.titleBar layoutIfNeeded]; 285 | [weakSelf.titleBar scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:focusIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:YES]; 286 | weakSelf.focusIndex = focusIndex; 287 | [CATransaction begin]; 288 | [CATransaction setDisableActions:YES]; 289 | [weakSelf.titleBar reloadData]; 290 | [CATransaction commit]; 291 | }; 292 | } 293 | 294 | #pragma mark - Private 295 | - (void)refreshTitleBar { 296 | [CATransaction begin]; 297 | [CATransaction setDisableActions:YES]; 298 | if (self.titles.count == 0) { 299 | _slideLine.hidden = YES; 300 | } 301 | [self.titleBar reloadData]; 302 | [self.titleBar layoutIfNeeded]; 303 | [CATransaction commit]; 304 | } 305 | 306 | #pragma mark -- UI 307 | - (void)barLayouts { 308 | if (self.backImage_layouts) [NSLayoutConstraint deactivateConstraints:self.backImage_layouts]; 309 | if (self.titleBar_layouts) [NSLayoutConstraint deactivateConstraints:self.titleBar_layouts]; 310 | if (self.rightBtn_layouts) [NSLayoutConstraint deactivateConstraints:self.rightBtn_layouts]; 311 | if (self.bottomLine_layouts) [NSLayoutConstraint deactivateConstraints:self.bottomLine_layouts]; 312 | 313 | // 背景 314 | NSLayoutConstraint *back_top = [NSLayoutConstraint 315 | constraintWithItem:self.backImage 316 | attribute:NSLayoutAttributeTop 317 | relatedBy:NSLayoutRelationEqual 318 | toItem:self 319 | attribute:NSLayoutAttributeTop 320 | multiplier:1 321 | constant:0]; 322 | NSLayoutConstraint *back_lef = [NSLayoutConstraint 323 | constraintWithItem:self.backImage 324 | attribute:NSLayoutAttributeLeading 325 | relatedBy:NSLayoutRelationEqual 326 | toItem:self 327 | attribute:NSLayoutAttributeLeading 328 | multiplier:1 329 | constant:0]; 330 | NSLayoutConstraint *back_btm = [NSLayoutConstraint 331 | constraintWithItem:self.backImage 332 | attribute:NSLayoutAttributeBottom 333 | relatedBy:NSLayoutRelationEqual 334 | toItem:self 335 | attribute:NSLayoutAttributeBottom 336 | multiplier:1 337 | constant:0]; 338 | NSLayoutConstraint *back_rit = [NSLayoutConstraint 339 | constraintWithItem:self.backImage 340 | attribute:NSLayoutAttributeTrailing 341 | relatedBy:NSLayoutRelationEqual 342 | toItem:self 343 | attribute:NSLayoutAttributeTrailing 344 | multiplier:1 345 | constant:0]; 346 | self.backImage_layouts = @[back_top, back_lef, back_btm, back_rit]; 347 | [NSLayoutConstraint activateConstraints:self.backImage_layouts]; 348 | 349 | // 标题栏 350 | NSLayoutConstraint *title_top = [NSLayoutConstraint 351 | constraintWithItem:self.titleBar 352 | attribute:NSLayoutAttributeTop 353 | relatedBy:NSLayoutRelationEqual 354 | toItem:self 355 | attribute:NSLayoutAttributeTop 356 | multiplier:1 357 | constant:0]; 358 | NSLayoutConstraint *title_led = [NSLayoutConstraint 359 | constraintWithItem:self.titleBar 360 | attribute:NSLayoutAttributeLeading 361 | relatedBy:NSLayoutRelationEqual 362 | toItem:self 363 | attribute:NSLayoutAttributeLeading 364 | multiplier:1 365 | constant:0]; 366 | NSLayoutConstraint *title_btm = [NSLayoutConstraint 367 | constraintWithItem:self.titleBar 368 | attribute:NSLayoutAttributeBottom 369 | relatedBy:NSLayoutRelationEqual 370 | toItem:self 371 | attribute:NSLayoutAttributeBottom 372 | multiplier:1 373 | constant:(self.config.needTitleBarBottomLine ? -0.5 : 0)]; 374 | NSLayoutConstraint *title_tal = [NSLayoutConstraint 375 | constraintWithItem:self.titleBar 376 | attribute:NSLayoutAttributeRight 377 | relatedBy:NSLayoutRelationEqual 378 | toItem:self.rightBtn 379 | attribute:NSLayoutAttributeLeft 380 | multiplier:1 381 | constant:0]; 382 | self.titleBar_layouts = @[title_top, title_led, title_btm, title_tal]; 383 | [NSLayoutConstraint activateConstraints:self.titleBar_layouts]; 384 | 385 | // 右按钮 386 | NSLayoutConstraint *btn_top = [NSLayoutConstraint 387 | constraintWithItem:self.rightBtn 388 | attribute:NSLayoutAttributeTop 389 | relatedBy:NSLayoutRelationEqual 390 | toItem:self 391 | attribute:NSLayoutAttributeTop 392 | multiplier:1 393 | constant:0]; 394 | NSLayoutConstraint *btn_led = [NSLayoutConstraint 395 | constraintWithItem:self.rightBtn 396 | attribute:NSLayoutAttributeLeft 397 | relatedBy:NSLayoutRelationEqual 398 | toItem:self.titleBar 399 | attribute:NSLayoutAttributeRight 400 | multiplier:1 401 | constant:0]; 402 | NSLayoutConstraint *btn_btm = [NSLayoutConstraint 403 | constraintWithItem:self.rightBtn 404 | attribute:NSLayoutAttributeBottom 405 | relatedBy:NSLayoutRelationEqual 406 | toItem:self 407 | attribute:NSLayoutAttributeBottom 408 | multiplier:1 409 | constant:(self.config.needTitleBarBottomLine ? -0.5 : 0)]; 410 | NSLayoutConstraint *btn_tal = [NSLayoutConstraint 411 | constraintWithItem:self.rightBtn 412 | attribute:NSLayoutAttributeTrailing 413 | relatedBy:NSLayoutRelationEqual 414 | toItem:self 415 | attribute:NSLayoutAttributeTrailing 416 | multiplier:1 417 | constant:0]; 418 | NSLayoutConstraint *btn_width = [NSLayoutConstraint 419 | constraintWithItem:self.rightBtn 420 | attribute:NSLayoutAttributeWidth 421 | relatedBy:NSLayoutRelationEqual 422 | toItem:nil 423 | attribute:NSLayoutAttributeNotAnAttribute 424 | multiplier:1 425 | constant:(self.config.needRightBtn ? self.config.rightBtnSize.width : 0)]; 426 | self.rightBtn_layouts = @[btn_top, btn_led, btn_btm, btn_tal, btn_width]; 427 | [NSLayoutConstraint activateConstraints:self.rightBtn_layouts]; 428 | 429 | // 底线 430 | NSLayoutConstraint *bottom_bottom = [NSLayoutConstraint 431 | constraintWithItem:self.bottomLine 432 | attribute:NSLayoutAttributeBottom 433 | relatedBy:NSLayoutRelationEqual 434 | toItem:self 435 | attribute:NSLayoutAttributeBottom 436 | multiplier:1 437 | constant:-0]; 438 | NSLayoutConstraint *bottom_left = [NSLayoutConstraint 439 | constraintWithItem:self.bottomLine 440 | attribute:NSLayoutAttributeLeading 441 | relatedBy:NSLayoutRelationEqual 442 | toItem:self 443 | attribute:NSLayoutAttributeLeading 444 | multiplier:1 445 | constant:0]; 446 | NSLayoutConstraint *bottom_right = [NSLayoutConstraint 447 | constraintWithItem:self.bottomLine 448 | attribute:NSLayoutAttributeTrailing 449 | relatedBy:NSLayoutRelationEqual 450 | toItem:self 451 | attribute:NSLayoutAttributeTrailing 452 | multiplier:1 453 | constant:0]; 454 | NSLayoutConstraint *bottom_height = [NSLayoutConstraint 455 | constraintWithItem:self.bottomLine 456 | attribute:NSLayoutAttributeHeight 457 | relatedBy:NSLayoutRelationEqual 458 | toItem:nil 459 | attribute:NSLayoutAttributeNotAnAttribute 460 | multiplier:1 461 | constant:(self.config.needTitleBarBottomLine ? 0.5 : 0)]; 462 | self.bottomLine_layouts = @[bottom_bottom, bottom_left, bottom_right, bottom_height]; 463 | [NSLayoutConstraint activateConstraints:self.bottomLine_layouts]; 464 | 465 | [self layoutIfNeeded]; 466 | if (_config.needRightBtn) { 467 | 468 | [_config.rightBtn setFrame:CGRectMake(0, 0, _config.rightBtnSize.width, _config.rightBtnSize.height-(self.config.needTitleBarBottomLine ? 0.5 : 0))]; 469 | 470 | if (![self.rightBtn isDescendantOfView:_config.rightBtn]) { 471 | [self.rightBtn addSubview:_config.rightBtn]; 472 | } 473 | } 474 | } 475 | @end 476 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTools.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 工具 8 | 9 | #import 10 | #import 11 | 12 | @interface XDPagesTools : NSObject 13 | // 获取视图所在控制器 14 | + (UIViewController *)viewControllerForView:(UIView *)view; 15 | 16 | // 关闭滚动控件自适应 17 | + (void)closeAdjustForScroll:(UIScrollView *)scrollView controller:(UIViewController *)controller; 18 | 19 | // 根据文字大小计算宽度 20 | + (CGFloat)adjustItemWidthByString:(NSString *)str font:(CGFloat)font baseSize:(CGSize)baseSize; 21 | 22 | // 检测数值中是否有重复项 23 | + (BOOL)hasRepeatItemInArray:(NSArray *)array; 24 | 25 | // 找出老数组在新数组中被去掉的标题 26 | + (NSArray*)canceledTitlesInNewTitles:(NSArray*)newTitles comparedOldTitles:(NSArray*)oldTitles; 27 | @end 28 | 29 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTools.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/16. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesTools.h" 10 | 11 | @implementation XDPagesTools 12 | + (UIViewController *)viewControllerForView:(UIView *)view { 13 | for (UIResponder *next = view; next; next = next.nextResponder) { 14 | if ([next isKindOfClass:[UIViewController class]]) { 15 | return (UIViewController *)next; 16 | } 17 | } 18 | 19 | return nil; 20 | } 21 | 22 | // 关闭自适应 23 | + (void)closeAdjustForScroll:(UIScrollView *)scrollView controller:(UIViewController *)controller { 24 | if (@available(iOS 11.0, *)) { 25 | if (scrollView) { 26 | if (scrollView.contentInsetAdjustmentBehavior != UIScrollViewContentInsetAdjustmentNever) { 27 | scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 28 | } 29 | } 30 | } else { 31 | if (controller) { 32 | #pragma clang diagnostic push 33 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 34 | if (controller.automaticallyAdjustsScrollViewInsets) { 35 | controller.automaticallyAdjustsScrollViewInsets = NO; 36 | } 37 | #pragma clang diagnostic pop 38 | } 39 | } 40 | } 41 | 42 | + (CGFloat)adjustItemWidthByString:(NSString *)str font:(CGFloat)font baseSize:(CGSize)baseSize { 43 | CGSize c_size = [str boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:font]} context:nil].size; 44 | return c_size.width; 45 | } 46 | 47 | + (BOOL)hasRepeatItemInArray:(NSArray *)array { 48 | NSMutableSet *setArry = [NSMutableSet setWithArray:array]; 49 | return (array.count > setArry.count ? YES : NO); 50 | } 51 | 52 | + (NSArray *)canceledTitlesInNewTitles:(NSArray *)newTitles comparedOldTitles:(NSArray *)oldTitles { 53 | 54 | NSMutableSet *oldSet = [NSMutableSet setWithArray:oldTitles]; 55 | NSMutableSet *newSet = [NSMutableSet setWithArray:newTitles]; 56 | 57 | [newSet intersectSet:oldSet]; 58 | [oldSet minusSet:newSet]; 59 | 60 | return oldSet.allObjects; 61 | } 62 | @end 63 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesTypes.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2023/10/11. 6 | // Copyright © 2023 xie. All rights reserved. 7 | // 8 | 9 | /** 10 | pagesview下拉样式 11 | 默认情况下为XDPagesPullOnTop,即顶端下拉 12 | */ 13 | typedef NS_ENUM(NSInteger, XDPagesPullStyle) { 14 | XDPagesPullOnTop = 0, //顶端下拉 15 | XDPagesPullOnCenter = 1 //中间下拉 16 | }; 17 | 18 | /** 19 | titleBar的下划指示线展示效果 20 | */ 21 | typedef NS_ENUM(NSInteger, SlideLineStyle) { 22 | XDSlideLine_None, // 下划线无展示效果 23 | XDSlideLine_Scale, // 下划线伸缩 24 | XDSlideLine_translation // 下划线平移(默认效果) 25 | }; 26 | 27 | /** 28 | titleBar的标题文字对齐方式 29 | */ 30 | typedef NS_ENUM(NSInteger, TitleVerticalAlignment) { 31 | XDVerticalAlignmentTop = 0, //标题顶部垂直对齐 32 | XDVerticalAlignmentMiddle, //标题中部垂直对齐 33 | XDVerticalAlignmentBottom, //标题底部垂直对齐 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesValueLock.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesValueLock.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/22. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 对某个值进行一对一锁定 8 | 9 | #import 10 | #import 11 | 12 | @interface XDPagesValueLock : NSObject 13 | + (instancetype)lock; 14 | // 锁定数值 15 | - (CGFloat)lockValue:(CGFloat)value; 16 | - (void)unlock; 17 | @end 18 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesValueLock.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesValueLock.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/22. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesValueLock.h" 10 | 11 | typedef NS_ENUM(NSInteger,LockValue) { 12 | XD_UnLock, 13 | XD_Lock 14 | }; 15 | 16 | @interface XDPagesValueLock () 17 | @property (nonatomic, assign) LockValue lock; 18 | @property (nonatomic, assign) CGFloat value; 19 | @end 20 | @implementation XDPagesValueLock 21 | + (instancetype)lock { 22 | return [[XDPagesValueLock alloc]init]; 23 | } 24 | 25 | - (CGFloat)lockValue:(CGFloat)value { 26 | if (_lock != XD_Lock) { 27 | _lock = XD_Lock; 28 | _value = value; 29 | } 30 | 31 | return _value; 32 | } 33 | 34 | - (void)unlock { 35 | _lock = XD_UnLock; 36 | } 37 | @end 38 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesView.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 控制器列表页 8 | 9 | #import 10 | #import "XDPagesConfig.h" 11 | #import "XDPagesTypes.h" 12 | #import "XDPagesMacros.h" 13 | @class XDPagesView; 14 | 15 | @protocol XDPagesViewDelegate 16 | @required 17 | /** 18 | 数据代理,用于接受标题数组 19 | @return 标题数组 20 | */ 21 | - (NSArray *)xd_pagesViewPageTitles; 22 | 23 | /** 24 | 数据代理,用于接受当前控制器 25 | @param pagesView XDPagesView 26 | @param index 索引 27 | @param title 对应标题 28 | @return 子控制器 29 | */ 30 | - (UIViewController *)xd_pagesView:(XDPagesView *)pagesView controllerForIndex:(NSInteger)index title:(NSString *)title; 31 | 32 | @optional 33 | 34 | /** 35 | 已经跳到的当前界面 36 | @param controller 当前控制器 37 | @param index 索引 38 | @param title 标题 39 | */ 40 | - (void)xd_pagesViewDidChangeToController:(UIViewController *const)controller index:(NSInteger)index title:(NSString *)title; 41 | 42 | /** 43 | 竖直滚动监听 44 | @param changedy 竖直offset.y 45 | @param ceiling 是否已吸顶 46 | */ 47 | - (void)xd_pagesViewVerticalScrollOffsetyChanged:(CGFloat)changedy isCeiling:(BOOL)ceiling; 48 | 49 | /** 50 | 水平滚动监听 51 | @param changedx 水平offset.x 52 | @param currentPage 当前页 53 | @param willShowPage 目标页 54 | */ 55 | - (void)xd_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)currentPage willShowPage:(NSInteger)willShowPage; 56 | 57 | /** 58 | 自定义标题宽度 59 | @param index 索引 60 | @param title 标题 61 | */ 62 | - (CGFloat)xd_pagesViewTitleWidthForIndex:(NSInteger)index title:(NSString *)title; 63 | @end 64 | 65 | @interface XDPagesView : UIView 66 | @property (nonatomic, weak) id delegate; 67 | @property (nonatomic, strong) UIView *pagesHeader; 68 | 69 | //系统下拉刷新(头部下拉只支持系统下拉刷新) 70 | @property (nonatomic, strong) UIRefreshControl *refreshControl; 71 | 72 | /** 73 | 初始化 74 | @param frame rect 75 | @param config 配置信息 76 | @param style 列表风格 77 | */ 78 | - (instancetype)initWithFrame:(CGRect)frame config:(XDPagesConfig *)config style:(XDPagesPullStyle)style; 79 | 80 | /** 81 | 缓存复用 82 | @param index 索引 83 | @return 缓存的子控制器 84 | */ 85 | - (UIViewController *)dequeueReusablePageForIndex:(NSInteger)index; 86 | 87 | /** 88 | 跳转到某页 89 | @param page 页面索引 90 | */ 91 | - (void)jumpToPage:(NSInteger)page; 92 | - (void)jumpToPage:(NSInteger)page animate:(BOOL)animate; 93 | 94 | /** 95 | 滚动到吸顶位置 96 | @param animate 动画 97 | */ 98 | - (void)scrollToCeiling:(BOOL)animate; 99 | 100 | /** 101 | 刷新控制器列表,并定位到页 102 | @param page 刷新后定位到的页面索引 103 | */ 104 | - (void)reloadataToPage:(NSInteger)page; 105 | 106 | /** 107 | 刷新配置项 108 | */ 109 | - (void)reloadConfigs; 110 | 111 | /** 112 | 展示某个item的未读消息 113 | @param number 未读数,当为0时隐藏 114 | @param idx 对应索引 115 | @param color badge颜色 116 | @param isNumber 是否显示数字 117 | */ 118 | - (void)showBadgeNumber:(NSInteger)number index:(NSInteger)idx color:(UIColor *)color isNumber:(BOOL)isNumber; 119 | @end 120 | 121 | -------------------------------------------------------------------------------- /XDPagesView/XDPagesView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDPagesView.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/2/13. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDPagesView.h" 10 | #import "XDPagesTable.h" 11 | #import "XDPagesCell.h" 12 | #import "XDPagesTools.h" 13 | #import "XDPagesValueLock.h" 14 | #import "XDPagesTitleBar.h" 15 | 16 | static NSString *const cellID = @"xdpagecell"; 17 | typedef NS_ENUM(NSInteger, XDPagesScrollStatus) { 18 | XDPages_None = 0, 19 | XDPages_Up = 1, 20 | XDPages_Down = 2 21 | }; 22 | @interface XDPagesView () 23 | @property (nonatomic, strong) XDPagesTable *mainTable; 24 | @property (nonatomic, strong) XDPagesCell *mainCell; 25 | @property (nonatomic, strong) UIView *formHeader; 26 | @property (nonatomic, strong) XDPagesValueLock *mainLock; 27 | @property (nonatomic, weak) UIScrollView *pagesContainer; 28 | @property (nonatomic, assign) BOOL needLockOffset; 29 | @property (nonatomic, strong) XDPagesTitleBar *titleBar; 30 | @property (nonatomic, strong) XDPagesConfig *config; 31 | @property (nonatomic, assign) XDPagesPullStyle pagesPullStyle; 32 | @property (nonatomic, assign) CGFloat canChangeSpace;// 标题可变动高度 33 | 34 | @property (nonatomic, assign) XDPagesScrollStatus s_status; 35 | @property (nonatomic, assign) CGFloat mainOffsetStatic; 36 | @property (nonatomic, assign) BOOL isCurrentPageCanScroll; 37 | @end 38 | 39 | @implementation XDPagesView 40 | - (void)dealloc { 41 | NSLog(@"\n_____XDPagesView_____已释放\n"); 42 | } 43 | 44 | - (void)didMoveToSuperview { 45 | if (self.superview) { 46 | //添加到视图上 47 | dispatch_async(dispatch_get_main_queue(), ^{ 48 | [self createUI]; 49 | }); 50 | } else { 51 | //从视图上被移除 52 | } 53 | } 54 | 55 | - (instancetype)initWithFrame:(CGRect)frame config:(XDPagesConfig *)config style:(XDPagesPullStyle)style { 56 | 57 | self = [super initWithFrame:frame]; 58 | 59 | if (self) { 60 | self.backgroundColor = [UIColor clearColor]; 61 | self.clipsToBounds = YES; 62 | self.mainLock = [XDPagesValueLock lock]; 63 | self.config = config ? config : [XDPagesConfig config]; 64 | _pagesPullStyle = style; 65 | _s_status = XDPages_None; 66 | _mainOffsetStatic = 0; 67 | } 68 | 69 | return self; 70 | } 71 | 72 | - (void)layoutSubviews { 73 | [super layoutSubviews]; 74 | [_mainTable beginUpdates]; 75 | [_mainTable endUpdates]; 76 | } 77 | 78 | - (UIViewController *)dequeueReusablePageForIndex:(NSInteger)index { 79 | return [self.mainCell dequeueReusablePageForIndex:index]; 80 | } 81 | 82 | - (void)jumpToPage:(NSInteger)page { 83 | [self jumpToPage:page animate:self.config.animateForPageChange]; 84 | } 85 | 86 | - (void)jumpToPage:(NSInteger)page animate:(BOOL)animate { 87 | [self.mainCell changeToPage:page animate:animate]; 88 | } 89 | 90 | - (void)scrollToCeiling:(BOOL)animate { 91 | self.mainTable.gesturePublic = NO; 92 | self.mainTable.userInteractionEnabled = NO; 93 | [self.mainTable setContentOffset:CGPointMake(0, _canChangeSpace) animated:animate]; 94 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 95 | self.mainTable.userInteractionEnabled = YES; 96 | self.mainTable.gesturePublic = YES; 97 | }); 98 | } 99 | 100 | - (void)reloadataToPage:(NSInteger)page { 101 | __weak typeof(self)weakSelf = self; 102 | [self.mainCell reloadToPage:page finish:^(NSArray *titles) { 103 | if (weakSelf.config.needTitleBar && !weakSelf.config.customTitleBar) { 104 | weakSelf.titleBar.refreshTitles(titles); 105 | } 106 | }]; 107 | } 108 | 109 | - (void)reloadConfigs { 110 | [_mainTable beginUpdates]; 111 | [self calculateChangeSpace]; 112 | [_mainTable endUpdates]; 113 | [self scrollViewDidScroll:_mainTable]; 114 | [_mainCell reloadConfigs]; 115 | [_titleBar reloadConfigs]; 116 | } 117 | 118 | - (void)showBadgeNumber:(NSInteger)number index:(NSInteger)idx color:(UIColor *)color isNumber:(BOOL)isNumber { 119 | if (self.config.needTitleBar && !self.config.customTitleBar) { 120 | [self.titleBar showBadgeNumber:number index:idx color:color isNumber:isNumber]; 121 | } 122 | } 123 | 124 | // 当竖直滚动时禁止横向滚动,由于此时仍需要手势共享,所以只能关闭横向滚动的scrollEnabled 125 | - (void)pagesContainerScrollEnable:(BOOL)enabel { 126 | if (self.pagesContainer && self.pagesContainer.scrollEnabled != enabel && self.config.pagesSlideEnable) { 127 | self.pagesContainer.scrollEnabled = enabel; 128 | } 129 | } 130 | 131 | #pragma mark -- table_delegate 132 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 133 | return 1; 134 | } 135 | 136 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 137 | return 1; 138 | } 139 | 140 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 141 | CGFloat margin = _config.titleBarMarginTop > CGRectGetHeight(self.formHeader.bounds) ? CGRectGetHeight(self.formHeader.bounds) : _config.titleBarMarginTop; 142 | return CGRectGetHeight(self.mainTable.bounds) - self.mainTable.sectionHeaderHeight - margin; 143 | } 144 | 145 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 146 | UIView *header = _config.needTitleBar ? (self.config.customTitleBar ? self.config.customTitleBar : self.titleBar) : nil; 147 | header.layer.zPosition = self.formHeader.layer.zPosition + 1; 148 | return header; 149 | } 150 | 151 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 152 | 153 | _mainCell = [tableView dequeueReusableCellWithIdentifier:cellID]; 154 | 155 | if (!_mainCell) { 156 | _mainCell = [[XDPagesCell alloc]initWithStyle:UITableViewCellStyleDefault 157 | reuseIdentifier:cellID 158 | contentController:[XDPagesTools viewControllerForView:self] 159 | delegate:self 160 | pagesPullStyle:self.pagesPullStyle 161 | config:self.config]; 162 | 163 | self.pagesContainer = [_mainCell exchangeChannelOfPagesContainerAndMainTable:self.mainTable]; 164 | } 165 | 166 | return _mainCell; 167 | } 168 | 169 | #pragma mark -- scroll_delegate 170 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 171 | if (!scrollView) { 172 | return; 173 | } 174 | 175 | // 如果滚动方向改变,先把主列表锁定,然后通过子view滚动去判断是否解锁,已达到主列表自由滚动响应延后的目的,过滤垂直滚动代理脏数据 176 | if (_isCurrentPageCanScroll) { 177 | if (_mainOffsetStatic < scrollView.contentOffset.y) { 178 | if (self.s_status != XDPages_Up) { 179 | self.s_status = XDPages_Up; 180 | _needLockOffset = YES; 181 | } 182 | } else if (_mainOffsetStatic > scrollView.contentOffset.y) { 183 | if (self.s_status != XDPages_Down) { 184 | self.s_status = XDPages_Down; 185 | _needLockOffset = YES; 186 | } 187 | } 188 | } 189 | 190 | if (scrollView.contentOffset.y > _canChangeSpace) { 191 | scrollView.contentOffset = CGPointMake(0, _canChangeSpace); 192 | } 193 | if (self.pagesPullStyle == XDPagesPullOnCenter) { 194 | if (scrollView.contentOffset.y < 0) { 195 | scrollView.contentOffset = CGPointMake(0, 0); 196 | } 197 | } 198 | 199 | if (_needLockOffset && _mainTable.gesturePublic) { 200 | if (_mainOffsetStatic >= 0 && _mainOffsetStatic <= _canChangeSpace) { 201 | scrollView.contentOffset = CGPointMake(0, [_mainLock lockValue:_mainOffsetStatic]); 202 | } else { 203 | [_mainLock unlock]; 204 | } 205 | } else { 206 | [_mainLock unlock]; 207 | } 208 | 209 | _mainOffsetStatic = scrollView.contentOffset.y; 210 | if ([self.delegate respondsToSelector:@selector(xd_pagesViewVerticalScrollOffsetyChanged:isCeiling:)]) { 211 | [self.delegate xd_pagesViewVerticalScrollOffsetyChanged:_mainOffsetStatic isCeiling:floor(_mainOffsetStatic * 100) >= floor(_canChangeSpace * 100)]; 212 | } 213 | } 214 | 215 | // 以下代理用于判断mainTable是否在滚动状态 216 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 217 | [self pagesContainerScrollEnable:NO]; 218 | } 219 | 220 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 221 | [self pagesContainerScrollEnable:YES]; 222 | } 223 | 224 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 225 | if (!decelerate) { 226 | [self pagesContainerScrollEnable:YES]; 227 | } 228 | } 229 | 230 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 231 | [self pagesContainerScrollEnable:YES]; 232 | } 233 | 234 | #pragma mark -- titleBar_delegate 235 | - (void)xd_titleTapAtIndex:(NSInteger)index { 236 | [self.mainCell changeToPage:index animate:self.config.animateForPageChange]; 237 | } 238 | 239 | - (CGFloat)xd_titleWidthForIndex:(NSInteger)index title:(NSString *)title { 240 | return [self.delegate respondsToSelector:@selector(xd_pagesViewTitleWidthForIndex:title:)] ? [self.delegate xd_pagesViewTitleWidthForIndex:index title:title] : -1; 241 | } 242 | 243 | #pragma mark -- cell_delegate 244 | - (NSArray *)cell_pagesViewAllTitles { 245 | return [self.delegate xd_pagesViewPageTitles]; 246 | } 247 | 248 | - (UIViewController *)cell_pagesViewChildControllerForIndex:(NSInteger)index title:(NSString *)title { 249 | return [self.delegate xd_pagesView:self controllerForIndex:index title:title]; 250 | } 251 | 252 | - (CGFloat)cell_headerVerticalCanChangedSpace { 253 | return _canChangeSpace; 254 | } 255 | 256 | - (void)cell_pagesViewDidChangeToPageController:(UIViewController *const)pageController title:(NSString *)pageTitle pageIndex:(NSInteger)pageIndex { 257 | if (self.config.needTitleBar && !self.config.customTitleBar) { 258 | self.titleBar.currentFocusIndex(pageIndex); 259 | } 260 | 261 | if ([self.delegate respondsToSelector:@selector(xd_pagesViewDidChangeToController:index:title:)]) { 262 | [self.delegate xd_pagesViewDidChangeToController:pageController index:pageIndex title:pageTitle]; 263 | } 264 | } 265 | 266 | - (void)cell_pagesViewHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willShowPage:(NSInteger)willShowPage { 267 | if ([self.delegate respondsToSelector:@selector(xd_pagesViewHorizontalScrollOffsetxChanged:currentPage:willShowPage:)]) { 268 | [self.delegate xd_pagesViewHorizontalScrollOffsetxChanged:changedx currentPage:page willShowPage:willShowPage]; 269 | } 270 | } 271 | 272 | - (void)cell_pagesViewSafeHorizontalScrollOffsetxChanged:(CGFloat)changedx currentPage:(NSInteger)page willShowPage:(NSInteger)willShowPage { 273 | if (self.config.needTitleBar && !self.config.customTitleBar) { 274 | [self.titleBar pagesViewHorizontalScrollOffsetxChanged:changedx 275 | currentPage:page 276 | willToPage:willShowPage 277 | width:CGRectGetWidth(self.bounds)]; 278 | } 279 | } 280 | 281 | - (void)cell_mainTableNeedLock:(BOOL)need offsety:(CGFloat)y { 282 | if (_needLockOffset != need) { 283 | _needLockOffset = need; 284 | if (need) { 285 | _mainOffsetStatic = y; 286 | _mainTable.contentOffset = CGPointMake(0, y); 287 | [self scrollViewDidScroll:_mainTable]; 288 | } 289 | } 290 | } 291 | 292 | #pragma mark -- setter 293 | - (void)setPagesHeader:(UIView *)pagesHeader { 294 | 295 | _pagesHeader = pagesHeader; 296 | 297 | if (_mainTable) { 298 | [_mainTable beginUpdates]; 299 | _mainTable.tableHeaderView = [self formHeader:pagesHeader]; 300 | [self calculateChangeSpace]; 301 | [_mainTable endUpdates]; 302 | } 303 | } 304 | 305 | - (void)setRefreshControl:(UIRefreshControl *)refreshControl { 306 | if (!refreshControl || _pagesPullStyle == XDPagesPullOnCenter) return; 307 | 308 | _refreshControl = refreshControl; 309 | 310 | if (_mainTable) { 311 | _mainTable.refreshControl = refreshControl; 312 | } 313 | } 314 | 315 | #pragma mark -- getter 316 | - (XDPagesTable *)mainTable { 317 | if (!_mainTable) { 318 | _mainTable = [[XDPagesTable alloc]initWithFrame:self.bounds style:UITableViewStylePlain]; 319 | _mainTable.showsVerticalScrollIndicator = NO; 320 | _mainTable.showsHorizontalScrollIndicator = NO; 321 | _mainTable.backgroundColor = [UIColor clearColor]; 322 | _mainTable.gesturePublic = YES; 323 | _mainTable.delegate = self; 324 | _mainTable.dataSource = self; 325 | _mainTable.scrollsToTop = NO; 326 | _mainTable.separatorStyle = UITableViewCellSeparatorStyleNone; 327 | _mainTable.sectionHeaderHeight = _config.titleBarHeight; 328 | _mainTable.tableHeaderView = [self formHeader:_pagesHeader]; 329 | [self calculateChangeSpace]; 330 | if (@available(iOS 15.0, *)) { 331 | _mainTable.sectionHeaderTopPadding = 0; 332 | } 333 | 334 | [XDPagesTools closeAdjustForScroll:_mainTable controller:[XDPagesTools viewControllerForView:self]]; 335 | 336 | if (_refreshControl) { 337 | _mainTable.refreshControl = _refreshControl; 338 | } 339 | } 340 | 341 | return _mainTable; 342 | } 343 | 344 | - (XDPagesTitleBar *)titleBar { 345 | if (!self.config.needTitleBar || self.config.customTitleBar)return nil; 346 | 347 | if (!_titleBar) { 348 | _titleBar = [[XDPagesTitleBar alloc]initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), _config.titleBarHeight) config:self.config titles:[self.delegate xd_pagesViewPageTitles]]; 349 | _titleBar.delegate = self; 350 | } 351 | 352 | return _titleBar; 353 | } 354 | 355 | #pragma mark -- UI 356 | - (void)createUI { 357 | 358 | [self addSubview:self.mainTable]; 359 | 360 | self.mainTable.translatesAutoresizingMaskIntoConstraints = NO; 361 | // 上 362 | NSLayoutConstraint *relat_top = [NSLayoutConstraint 363 | constraintWithItem:self.mainTable 364 | attribute:NSLayoutAttributeTop 365 | relatedBy:NSLayoutRelationEqual 366 | toItem:self 367 | attribute:NSLayoutAttributeTop 368 | multiplier:1 369 | constant:0]; 370 | // 左 371 | NSLayoutConstraint *relat_led = [NSLayoutConstraint 372 | constraintWithItem:self.mainTable 373 | attribute:NSLayoutAttributeLeading 374 | relatedBy:NSLayoutRelationEqual 375 | toItem:self 376 | attribute:NSLayoutAttributeLeading 377 | multiplier:1 378 | constant:0]; 379 | // 下 380 | NSLayoutConstraint *relat_btm = [NSLayoutConstraint 381 | constraintWithItem:self.mainTable 382 | attribute:NSLayoutAttributeBottom 383 | relatedBy:NSLayoutRelationEqual 384 | toItem:self 385 | attribute:NSLayoutAttributeBottom 386 | multiplier:1 387 | constant:0]; 388 | // 右 389 | NSLayoutConstraint *relat_tal = [NSLayoutConstraint 390 | constraintWithItem:self.mainTable 391 | attribute:NSLayoutAttributeTrailing 392 | relatedBy:NSLayoutRelationEqual 393 | toItem:self 394 | attribute:NSLayoutAttributeTrailing 395 | multiplier:1 396 | constant:0]; 397 | // 约束 398 | [NSLayoutConstraint activateConstraints:@[relat_top, relat_led, relat_btm, relat_tal]]; 399 | } 400 | 401 | // 对header进行重新包装用于内部 402 | - (UIView *)formHeader:(UIView *)header { 403 | UIView *f_header = nil; 404 | if (header) { 405 | CGFloat headerHeight = CGRectGetHeight(header.bounds) - (_config.titleBarFitHeader ? _config.titleBarHeight : 0); 406 | CGRect c_frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), headerHeight); 407 | f_header = [[UIView alloc]initWithFrame:c_frame]; 408 | f_header.backgroundColor = [UIColor clearColor]; 409 | [f_header addSubview:header]; 410 | 411 | f_header.userInteractionEnabled = header.userInteractionEnabled; 412 | 413 | header.translatesAutoresizingMaskIntoConstraints = NO; 414 | 415 | if (_config.titleBarFitHeader) { 416 | NSLayoutConstraint *relat_btm = [NSLayoutConstraint 417 | constraintWithItem:header 418 | attribute:NSLayoutAttributeBottom 419 | relatedBy:NSLayoutRelationEqual 420 | toItem:f_header 421 | attribute:NSLayoutAttributeBottom 422 | multiplier:1 423 | constant:_config.titleBarHeight]; 424 | [NSLayoutConstraint activateConstraints:@[relat_btm]]; 425 | } else { 426 | NSLayoutConstraint *relat_btm = [NSLayoutConstraint 427 | constraintWithItem:header 428 | attribute:NSLayoutAttributeBottom 429 | relatedBy:NSLayoutRelationEqual 430 | toItem:f_header 431 | attribute:NSLayoutAttributeBottom 432 | multiplier:1 433 | constant:0]; 434 | [NSLayoutConstraint activateConstraints:@[relat_btm]]; 435 | } 436 | 437 | NSLayoutConstraint *relat_top = [NSLayoutConstraint 438 | constraintWithItem:header 439 | attribute:NSLayoutAttributeTop 440 | relatedBy:NSLayoutRelationEqual 441 | toItem:f_header 442 | attribute:NSLayoutAttributeTop 443 | multiplier:1 444 | constant:0]; 445 | NSLayoutConstraint *relat_led = [NSLayoutConstraint 446 | constraintWithItem:header 447 | attribute:NSLayoutAttributeLeading 448 | relatedBy:NSLayoutRelationEqual 449 | toItem:f_header 450 | attribute:NSLayoutAttributeLeading 451 | multiplier:1 452 | constant:0]; 453 | NSLayoutConstraint *relat_tal = [NSLayoutConstraint 454 | constraintWithItem:header 455 | attribute:NSLayoutAttributeTrailing 456 | relatedBy:NSLayoutRelationEqual 457 | toItem:f_header 458 | attribute:NSLayoutAttributeTrailing 459 | multiplier:1 460 | constant:0]; 461 | [NSLayoutConstraint activateConstraints:@[relat_top, relat_led, relat_tal]]; 462 | [f_header layoutIfNeeded]; 463 | } 464 | 465 | self.formHeader = f_header; 466 | 467 | return f_header; 468 | } 469 | 470 | //计算活动区间 471 | - (void)calculateChangeSpace { 472 | CGFloat headerHeight = _mainTable.tableHeaderView ? CGRectGetHeight(_mainTable.tableHeaderView.frame) : 0; 473 | CGFloat cmargin = _config.titleBarMarginTop > headerHeight ? headerHeight : _config.titleBarMarginTop; 474 | CGFloat cheight = headerHeight-cmargin; 475 | _canChangeSpace = cheight > 0 ? cheight : 0; 476 | } 477 | 478 | #pragma mark -- sys_method 479 | static NSTimeInterval lastEventTimeStamp = 0; 480 | // 利用hittest在手势进入之前,判断手势不在container中时就关闭手势共享,目的:防止header中有滚动控件,造成共同滚动 481 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 482 | CGPoint relative_point = [self.pagesContainer convertPoint:point fromView:self]; 483 | 484 | if ([self.pagesContainer.layer containsPoint:relative_point]) { 485 | if (!self.mainTable.gesturePublic) self.mainTable.gesturePublic = YES; 486 | 487 | //寻找这个点击事件的位置上有没有可以监听的滚动控件 488 | UIView *view = [super hitTest:point withEvent:event]; 489 | if (lastEventTimeStamp != event.timestamp) { 490 | lastEventTimeStamp = event.timestamp; 491 | UIScrollView *kvoScroll; 492 | BOOL canfindScroll = NO; 493 | for (UIView *next = view; next; next = next.superview) { 494 | if (next == self.pagesContainer) { 495 | break; 496 | } 497 | else if ([next isKindOfClass:[UIScrollView class]]) { 498 | kvoScroll = (UIScrollView *)next; 499 | } 500 | } 501 | if (kvoScroll && kvoScroll.scrollEnabled && kvoScroll.tag != XD_IGNORETAG) { 502 | canfindScroll = YES; 503 | } 504 | self.isCurrentPageCanScroll = canfindScroll; 505 | if (!canfindScroll) { 506 | //当前触点空间不能滚动,则当做普通视图处理,解除maintable锁定,并解除之前的滚动观察 507 | self.needLockOffset = NO; 508 | self.mainCell.currentKVOChild = nil; 509 | } 510 | self.mainCell.mainOffsetStatic = self.mainTable.contentOffset.y; 511 | } 512 | return view; 513 | } else { 514 | if (self.mainTable.gesturePublic) self.mainTable.gesturePublic = NO; 515 | } 516 | 517 | return [super hitTest:point withEvent:event]; 518 | } 519 | 520 | @end 521 | -------------------------------------------------------------------------------- /XDPagesView/XDSlideEffect.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDSlideEffect.h 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/6. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 下划线滑动效果管理 8 | 9 | #import 10 | #import 11 | 12 | @interface XDSlideEffect : NSObject 13 | 14 | /// 下划线伸缩效果 15 | /// @param view 展示动画的view 16 | /// @param attributes 当前所有标题的布局 17 | /// @param cpage 当前页 18 | /// @param wpage 目标页 19 | /// @param percent 翻页进度 20 | /// @param ratio 下划线相对宽度的百分比 21 | - (void)slideLineScaleEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage percent:(CGFloat)percent ratio:(CGFloat)ratio; 22 | 23 | /// 下划线平移效果 24 | /// @param view 展示动画的view 25 | /// @param attributes 当前所有标题的布局 26 | /// @param cpage 当前页 27 | /// @param wpage 目标页 28 | /// @param percent 翻页进度 29 | /// @param ratio 下划线相对宽度的百分比 30 | - (void)slideLineTransEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage percent:(CGFloat)percent ratio:(CGFloat)ratio; 31 | 32 | /// 无滑动效果 33 | /// @param view 展示动画的view 34 | /// @param attributes 当前所有标题的布局 35 | /// @param cpage 当前页 36 | /// @param wpage 目标页 37 | /// @param ratio 下划线相对宽度的百分比 38 | - (void)slideLineNoneEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage ratio:(CGFloat)ratio; 39 | @end 40 | 41 | -------------------------------------------------------------------------------- /XDPagesView/XDSlideEffect.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDSlideEffect.m 3 | // XDPagesView 4 | // 5 | // Created by 谢兴达 on 2020/3/6. 6 | // Copyright © 2020 xie. All rights reserved. 7 | // 8 | 9 | #import "XDSlideEffect.h" 10 | 11 | @implementation XDSlideEffect 12 | 13 | // 下划线伸缩效果 14 | - (void)slideLineScaleEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage percent:(CGFloat)percent ratio:(CGFloat)ratio { 15 | 16 | if (attributes.count == 0 || ratio <= 0) { 17 | view.hidden = YES; 18 | return; 19 | } 20 | 21 | ratio = ratio > 1 ? 1 : ratio; 22 | 23 | if (view.isHidden) { 24 | view.hidden = NO; 25 | } 26 | 27 | UICollectionViewLayoutAttributes *c_attr = attributes[cpage]; 28 | UICollectionViewLayoutAttributes *w_attr = attributes[wpage]; 29 | CGFloat c_width = CGRectGetWidth(c_attr.bounds); 30 | CGFloat w_width = CGRectGetWidth(w_attr.bounds); 31 | 32 | // 换算后的一半 33 | CGFloat c_hf_width = c_width*ratio/2.0; 34 | CGFloat w_hf_widht = w_width*ratio/2.0; 35 | CGFloat distance = ((c_width + w_width)/2.0 + w_hf_widht - c_hf_width); 36 | CGRect c_frame = CGRectZero; 37 | 38 | if (cpage > wpage) { 39 | c_frame = CGRectMake(CGRectGetMidX(c_attr.frame)-c_hf_width-distance*percent, 40 | CGRectGetMinY(view.frame), 41 | c_hf_width*2+distance*percent, 42 | CGRectGetHeight(view.bounds)); 43 | } else { 44 | c_frame = CGRectMake(CGRectGetMidX(c_attr.frame)-c_hf_width, 45 | CGRectGetMinY(view.frame), 46 | c_hf_width*2+distance*percent, 47 | CGRectGetHeight(view.bounds)); 48 | } 49 | 50 | [view setFrame:c_frame]; 51 | } 52 | 53 | // 下划线平移效果 54 | - (void)slideLineTransEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage percent:(CGFloat)percent ratio:(CGFloat)ratio { 55 | 56 | if (attributes.count == 0 || ratio <= 0) { 57 | view.hidden = YES; 58 | return; 59 | } 60 | 61 | ratio = ratio > 1 ? 1 : ratio; 62 | 63 | if (view.isHidden) { 64 | view.hidden = NO; 65 | } 66 | 67 | UICollectionViewLayoutAttributes *c_attr = attributes[cpage]; 68 | UICollectionViewLayoutAttributes *w_attr = attributes[wpage]; 69 | CGFloat c_width = CGRectGetWidth(c_attr.bounds); 70 | CGFloat w_width = CGRectGetWidth(w_attr.bounds); 71 | 72 | // 换算后的一半 73 | CGFloat c_hf_width = c_width*ratio/2.0; 74 | CGFloat w_hf_widht = w_width*ratio/2.0; 75 | CGFloat distance = ((c_width + w_width)/2.0 + w_hf_widht - c_hf_width); 76 | CGFloat d_value = (w_hf_widht-c_hf_width)*2; 77 | CGRect c_frame = CGRectZero; 78 | 79 | if (cpage > wpage) { 80 | // 因为不管向左还是向右,都是以左边为基准,所以向左变化时自身长度变化不会影响最终结果,无需要减去长度变化补偿 81 | c_frame = CGRectMake(CGRectGetMidX(c_attr.frame)-c_hf_width-distance*percent, 82 | CGRectGetMinY(view.frame), 83 | c_hf_width*2+d_value*percent, 84 | CGRectGetHeight(view.bounds)); 85 | } else { 86 | // 因为不管向左还是向右,都是以左边为基准,所以向右变化时自身长度变化会影响最终结果,需要减去长度变化补偿 87 | c_frame = CGRectMake(CGRectGetMidX(c_attr.frame)-c_hf_width+(distance-d_value)*percent, 88 | CGRectGetMinY(view.frame), 89 | c_hf_width*2+d_value*percent, 90 | CGRectGetHeight(view.bounds)); 91 | } 92 | 93 | [view setFrame:c_frame]; 94 | } 95 | 96 | // 无滑动效果 97 | - (void)slideLineNoneEffectForView:(UIView *)view attributes:(NSArray *)attributes currentPage:(NSInteger)cpage willPage:(NSInteger)wpage ratio:(CGFloat)ratio { 98 | 99 | if (attributes.count == 0 || ratio <= 0) { 100 | view.hidden = YES; 101 | return; 102 | } 103 | 104 | ratio = ratio > 1 ? 1 : ratio; 105 | 106 | if (view.isHidden) { 107 | view.hidden = NO; 108 | } 109 | 110 | if (cpage == wpage) { 111 | UICollectionViewLayoutAttributes *c_attr = attributes[cpage]; 112 | CGFloat c_width = CGRectGetWidth(c_attr.bounds); 113 | 114 | // 换算后的一半 115 | CGFloat c_hf_width = c_width*ratio/2.0; 116 | CGRect c_frame = CGRectMake(CGRectGetMidX(c_attr.frame)-c_hf_width, 117 | CGRectGetMinY(view.frame), 118 | c_hf_width*2, 119 | CGRectGetHeight(view.bounds)); 120 | 121 | [view setFrame:c_frame]; 122 | } 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /show1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/show1.gif -------------------------------------------------------------------------------- /show2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/show2.gif -------------------------------------------------------------------------------- /show3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/show3.gif -------------------------------------------------------------------------------- /sources/xd_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/sources/xd_back.jpg -------------------------------------------------------------------------------- /sources/xd_header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiexingda/XDPagesView/bcee766400254aeb648465ad582e419290666a13/sources/xd_header.jpg --------------------------------------------------------------------------------