├── LICENSE ├── Object-C ├── .DS_Store ├── Example │ ├── .DS_Store │ ├── Example.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcuserdata │ │ │ │ ├── Dan.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ │ └── sparrow.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ ├── Dan.xcuserdatad │ │ │ ├── xcdebugger │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ │ ├── Example.xcscheme │ │ │ │ └── xcschememanagement.plist │ │ │ └── sparrow.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ ├── Example.xcscheme │ │ │ └── xcschememanagement.plist │ ├── Example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── TestCoverSubController.h │ │ ├── TestCoverSubController.m │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ └── main.m │ ├── ExampleTests │ │ ├── ExampleTests.m │ │ └── Info.plist │ └── ExampleUITests │ │ ├── ExampleUITests.m │ │ └── Info.plist └── SPPage │ ├── .DS_Store │ ├── Controller │ ├── SPCoverController.h │ ├── SPCoverController.m │ ├── SPPageController.h │ ├── SPPageController.m │ ├── SPTabController.h │ └── SPTabController.m │ ├── Protocol │ ├── SPCoverProtocol.h │ ├── SPPageProtocol.h │ └── SPTabProtocol.h │ └── View │ ├── SPPageContentView.h │ ├── SPPageContentView.m │ ├── SPPageTagView.h │ ├── SPPageTagView.m │ ├── SPTagBarScrollView.h │ └── SPTagBarScrollView.m ├── ReadMe └── Swift ├── .DS_Store ├── Example ├── .DS_Store ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── Dan.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── Dan.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── TestSubController.swift │ └── ViewController.swift ├── ExampleTests │ ├── ExampleTests.swift │ └── Info.plist └── ExampleUITests │ ├── ExampleUITests.swift │ └── Info.plist └── SPPage ├── .DS_Store ├── Protocol ├── SPCoverProtocol.swift ├── SPPageProtocol.swift └── SPTabProtocol.swift ├── View ├── SPPageContentView.swift ├── SPPageTagView.swift └── SPTagBarScrollView.swift └── core ├── SPCoverController.swift ├── SPPageController.swift └── SPTabController.swift /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 xichen744 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 | -------------------------------------------------------------------------------- /Object-C/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Object-C/.DS_Store -------------------------------------------------------------------------------- /Object-C/Example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Object-C/Example/.DS_Store -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Dan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Object-C/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Dan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/sparrow.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Object-C/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/sparrow.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/Dan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/Dan.xcuserdatad/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/Dan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F3C00B431F2DD50B002E557E 16 | 17 | primary 18 | 19 | 20 | F3C00B5C1F2DD50B002E557E 21 | 22 | primary 23 | 24 | 25 | F3C00B671F2DD50B002E557E 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/sparrow.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/sparrow.xcuserdatad/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Object-C/Example/Example.xcodeproj/xcuserdata/sparrow.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F3C00B431F2DD50B002E557E 16 | 17 | primary 18 | 19 | 20 | F3C00B5C1F2DD50B002E557E 21 | 22 | primary 23 | 24 | 25 | F3C00B671F2DD50B002E557E 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Object-C/Example/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Object-C/Example/Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Object-C/Example/Example/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 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Object-C/Example/Example/Base.lproj/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 | 27 | 28 | -------------------------------------------------------------------------------- /Object-C/Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Object-C/Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Object-C/Example/Example/TestCoverSubController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPTestCoverSubController.h 3 | // Radio 4 | // 5 | // Created by sparrow on 10/02/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPPageProtocol.h" 10 | 11 | @interface TestCoverSubController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Object-C/Example/Example/TestCoverSubController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPTestCoverSubController.m 3 | // Radio 4 | // 5 | // Created by sparrow on 10/02/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "TestCoverSubController.h" 10 | #import "ViewController.h" 11 | 12 | @interface TestCoverSubController () 13 | 14 | @property (nonatomic, strong) UITableView *tableView; 15 | 16 | @end 17 | 18 | @implementation TestCoverSubController 19 | 20 | -(UIScrollView *)preferScrollView 21 | { 22 | return self.tableView; 23 | } 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | 28 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds]; 29 | self.tableView.autoresizingMask = (0x1<<6) - 1; 30 | self.tableView.delegate = self; 31 | self.tableView.dataSource = self; 32 | [self.view addSubview:self.tableView]; 33 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 34 | self.tableView.backgroundColor = [UIColor clearColor]; 35 | self.tableView.estimatedRowHeight= 0.0f; 36 | } 37 | 38 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 39 | { 40 | return 1; 41 | } 42 | 43 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 44 | { 45 | return 20; 46 | } 47 | 48 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 49 | { 50 | [self.tableView deselectRowAtIndexPath:indexPath animated:NO]; 51 | 52 | ViewController *vc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"SUID_ViewController"]; 53 | vc.navTitle = [NSString stringWithFormat:@"Row%@",@(indexPath.row)]; 54 | [self.navigationController pushViewController:vc animated:YES]; 55 | 56 | 57 | } 58 | 59 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 60 | { 61 | UITableViewCell *cell = [[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 0)]; 62 | cell.textLabel.text = [NSString stringWithFormat:@"Row%zd",indexPath.row]; 63 | cell.backgroundColor = [UIColor clearColor]; 64 | cell.contentView.backgroundColor = [UIColor clearColor]; 65 | return cell; 66 | } 67 | 68 | 69 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 70 | { 71 | return 60; 72 | } 73 | 74 | - (void)didReceiveMemoryWarning { 75 | [super didReceiveMemoryWarning]; 76 | // Dispose of any resources that can be recreated. 77 | } 78 | 79 | -(void)viewWillAppear:(BOOL)animated 80 | { 81 | [super viewWillAppear:animated]; 82 | } 83 | 84 | -(void)viewDidAppear:(BOOL)animated 85 | { 86 | [super viewDidAppear:animated]; 87 | } 88 | 89 | 90 | /* 91 | #pragma mark - Navigation 92 | 93 | // In a storyboard-based application, you will often want to do a little preparation before navigation 94 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 95 | // Get the new view controller using [segue destinationViewController]. 96 | // Pass the selected object to the new view controller. 97 | } 98 | */ 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Object-C/Example/Example/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import "SPCoverController.h" 10 | 11 | @interface ViewController : SPCoverController 12 | 13 | @property (nonatomic, strong) NSString *navTitle; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Object-C/Example/Example/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TestCoverSubController.h" 11 | #import "SPPageController.h" 12 | 13 | #define CoverHeight 245 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | self.minYPullUp = KNAVIGATIONANDSTATUSBARHEIGHT; 25 | self.automaticallyAdjustsScrollViewInsets = NO; 26 | self.navigationController.navigationBarHidden=NO; 27 | self.navigationItem.title = self.navTitle?:@"SPPage"; 28 | if (!self.navTitle) 29 | self.navigationController.navigationBar.barTintColor = [UIColor redColor]; 30 | } 31 | 32 | - (NSString *)titleForIndex:(NSInteger)index 33 | { 34 | return [NSString stringWithFormat:@"TAB%zd", index]; 35 | } 36 | 37 | - (BOOL)needMarkView 38 | { 39 | return YES; 40 | } 41 | 42 | - (UIView *)preferCoverView 43 | { 44 | UIView *view = [[UIView alloc] initWithFrame:[self preferCoverFrame]]; 45 | view.backgroundColor = [UIColor blackColor]; 46 | 47 | return view; 48 | } 49 | 50 | - (CGFloat)preferTabY 51 | { 52 | return CoverHeight; 53 | } 54 | 55 | - (CGRect)preferCoverFrame 56 | { 57 | return CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, CoverHeight); 58 | } 59 | 60 | - (UIViewController *)controllerAtIndex:(NSInteger)index 61 | { 62 | TestCoverSubController *coverController = [[TestCoverSubController alloc] init]; 63 | coverController.view.frame = [self preferPageFrame]; 64 | 65 | if (index == 0) { 66 | coverController.view.backgroundColor = [UIColor greenColor]; 67 | } else if (index == 1) { 68 | coverController.view.backgroundColor = [UIColor orangeColor]; 69 | } else { 70 | coverController.view.backgroundColor = [UIColor redColor]; 71 | } 72 | 73 | return coverController; 74 | 75 | } 76 | -(NSInteger)preferPageFirstAtIndex { 77 | return 1; 78 | } 79 | 80 | -(BOOL)isSubPageCanScrollForIndex:(NSInteger)index 81 | { 82 | return YES; 83 | } 84 | 85 | -(void)viewDidAppear:(BOOL)animated 86 | { 87 | [super viewDidAppear:animated]; 88 | 89 | } 90 | 91 | - (NSInteger)numberOfControllers 92 | { 93 | return 8; 94 | } 95 | 96 | -(BOOL)isPreLoad { 97 | return NO; 98 | } 99 | 100 | 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Object-C/Example/Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Object-C/Example/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.m 3 | // ExampleTests 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ExampleTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Object-C/Example/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Object-C/Example/ExampleUITests/ExampleUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleUITests.m 3 | // ExampleUITests 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExampleUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ExampleUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Object-C/Example/ExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Object-C/SPPage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Object-C/SPPage/.DS_Store -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPCoverController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPCoverViewControllerViewController.h 3 | // Radio 4 | // 5 | // Created by sparrow on 19/01/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPTabController.h" 10 | #import "SPCoverProtocol.h" 11 | 12 | 13 | typedef NS_ENUM(NSInteger, CoverScrollStyle) { 14 | CoverScrollStyleHeight = 0,//需要做Cover的背景图片拉伸的选择这个 否则选择下面的Style 15 | CoverScrollStyleTop = 1, 16 | 17 | }; 18 | 19 | @interface SPCoverController : SPTabController 20 | 21 | - (CoverScrollStyle)preferCoverStyle; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPCoverController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPCoverViewControllerViewController.m 3 | // Radio 4 | // 5 | // Created by sparrow on 19/01/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPCoverController.h" 10 | 11 | @interface SPCoverController () 12 | 13 | @property (nonatomic, strong) UIView *__coverView; 14 | 15 | @end 16 | 17 | @implementation SPCoverController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | [self __loadCoverView]; 22 | } 23 | 24 | 25 | 26 | -(void)viewWillAppear:(BOOL)animated 27 | { 28 | [super viewWillAppear:animated]; 29 | } 30 | 31 | - (void)__reloadCoverView 32 | { 33 | [self.__coverView removeFromSuperview]; 34 | [self __loadCoverView]; 35 | } 36 | 37 | - (void)__loadCoverView 38 | { 39 | self.__coverView = [self preferCoverView]; 40 | self.__coverView.frame = [self preferCoverFrame]; 41 | [self.view addSubview:self.__coverView]; 42 | } 43 | 44 | - (void)reloadData 45 | { 46 | [super reloadData]; 47 | [self __reloadCoverView]; 48 | } 49 | 50 | - (CoverScrollStyle)preferCoverStyle 51 | { 52 | return CoverScrollStyleHeight; 53 | } 54 | 55 | #pragma SPPageControllerDelegate 56 | 57 | - (void)scrollWithPageOffset:(CGFloat)offset index:(NSInteger)index 58 | { 59 | 60 | [super scrollWithPageOffset:offset index:index]; 61 | 62 | CGFloat realOffset = offset+[self pageTopAtIndex:index]; 63 | CGFloat top = [self preferTabY]-realOffset; 64 | if (realOffset >= 0) { 65 | if (top <= self.minYPullUp) { 66 | top = self.minYPullUp; 67 | } 68 | } else { 69 | if (top >= self.maxYPullDown) { 70 | top = self.maxYPullDown; 71 | } 72 | } 73 | 74 | realOffset = [self preferTabY] - top; 75 | 76 | UIView *coverView = (UIView *)self.__coverView; 77 | 78 | CGRect frame = coverView.frame; 79 | 80 | if ([self preferCoverStyle]==CoverScrollStyleHeight) { 81 | CGFloat coverHeight = [self preferCoverFrame].size.height - realOffset; 82 | if (coverHeight >= 0) { 83 | coverView.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, coverHeight); 84 | 85 | } else { 86 | coverView.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 0); 87 | } 88 | } else { 89 | CGFloat coverTop = [self preferCoverFrame].origin.y - realOffset; 90 | 91 | if (coverTop >= [self preferCoverFrame].origin.y-[self preferCoverFrame].size.height) { 92 | coverView.frame = CGRectMake(frame.origin.x, coverTop, frame.size.width, frame.size.height); 93 | 94 | } else { 95 | coverView.frame = CGRectMake(frame.origin.x, [self preferCoverFrame].origin.y-[self preferCoverFrame].size.height, frame.size.width, frame.size.height); 96 | } 97 | } 98 | 99 | } 100 | 101 | 102 | #pragma SPPageControllerDataSource 103 | -(CGFloat)pageTopAtIndex:(NSInteger)index 104 | { 105 | CGFloat pageTop = [super pageTopAtIndex:index]; 106 | return pageTop > [self preferCoverFrame].origin.y+ [self preferCoverFrame].size.height?pageTop: [self preferCoverFrame].origin.y+ [self preferCoverFrame].size.height; 107 | } 108 | 109 | #pragma SPCoverProtocol 110 | 111 | - (UIView *)preferCoverView 112 | { 113 | return nil; 114 | } 115 | 116 | - (CGRect)preferCoverFrame 117 | { 118 | return CGRectZero; 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPPageController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageController.h 3 | // Radio 4 | // 5 | // Created by sparrow on 25/11/2016. 6 | // Copyright © 2016 qzone. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SPPageProtocol.h" 11 | 12 | #define KSCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 13 | #define KSCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 14 | #define KSTATUSBARHEIGHT ([SPPageController iPhoneX]?44.0:20.0) 15 | #define KNAVIGATIONANDSTATUSBARHEIGHT (KSTATUSBARHEIGHT+44.0) 16 | 17 | /** 18 | * 19 | * 替换系统的pageController 20 | * 21 | */ 22 | @interface SPPageController : UIViewController 23 | 24 | @property (nonatomic, weak) id delegate; 25 | @property (nonatomic, weak) id dataSource; 26 | 27 | @property (nonatomic, assign, readonly) NSInteger currentPageIndex; 28 | 29 | //必须在reloadpage之前 把datasource 回调的pagecount变了 30 | - (void)reloadPage; 31 | 32 | //用于非交互切换接口 33 | - (void)showPageAtIndex:(NSInteger)index animated:(BOOL)animated; 34 | //在tab高度可变的情况,需要去动态修改 tableview的contentinset 35 | - (void)resizePageAtIndex:(NSInteger)index offset:(CGFloat)offset isNeedChangeOffset:(BOOL)isNeedChangeOffset atBeginOffsetChangeOffset:(BOOL)atBeginOffsetChangeOffset; 36 | 37 | - (NSInteger)indexOfController:(UIViewController *)vc; 38 | 39 | - (void)updateCurrentIndex:(NSInteger)index; 40 | 41 | + (BOOL)iPhoneX; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPPageController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageController.m 3 | // Radio 4 | // 5 | // Created by sparrow on 25/11/2016. 6 | // Copyright © 2016 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPPageController.h" 10 | #import "SPPageContentView.h" 11 | 12 | typedef NS_ENUM(NSInteger,SPPageScrollDirection) { 13 | PageLeft = 0, 14 | PageRight = 1, 15 | }; 16 | 17 | 18 | @interface SPPageController () 19 | 20 | @property (nonatomic, strong) NSMutableDictionary *memCacheDic; 21 | @property (nonatomic, strong) NSMutableDictionary *lastContentOffset; 22 | @property (nonatomic, strong) NSMutableDictionary *lastContentSize; 23 | 24 | @property (nonatomic, strong) SPPageContentView *scrollView; 25 | 26 | @property (nonatomic, assign) NSInteger lastSelectedIndex; 27 | @property (nonatomic, assign) NSInteger guessToIndex; 28 | @property (nonatomic, assign) CGFloat originOffset; 29 | @property (nonatomic, assign) BOOL firstWillAppear; 30 | @property (nonatomic, assign) BOOL firstWillLayoutSubViews; 31 | @property (nonatomic, assign) BOOL firstDidAppear; 32 | 33 | @property (nonatomic, assign, readwrite) NSInteger currentPageIndex; 34 | 35 | @end 36 | 37 | @implementation SPPageController 38 | 39 | -(instancetype)init 40 | { 41 | self = [super init]; 42 | if (self) { 43 | self.memCacheDic = [[NSMutableDictionary alloc] init]; 44 | self.lastContentOffset = [[NSMutableDictionary alloc] init]; 45 | self.lastContentSize = [[NSMutableDictionary alloc] init]; 46 | } 47 | return self; 48 | } 49 | 50 | 51 | - (void)clearMemory 52 | { 53 | for (UIView *view in self.scrollView.subviews) { 54 | [view removeFromSuperview]; 55 | } 56 | 57 | [self.lastContentOffset removeAllObjects]; 58 | [self.lastContentSize removeAllObjects]; 59 | 60 | if (self.memCacheDic.count > 0) { 61 | [self clearObserver]; 62 | NSArray *vcArray = self.memCacheDic.allValues; 63 | [self.memCacheDic removeAllObjects]; 64 | for (UIViewController *vc in vcArray) { 65 | [self __removeFromParentViewController:vc]; 66 | 67 | } 68 | vcArray = nil; 69 | } 70 | } 71 | 72 | - (void)reloadPage 73 | { 74 | [self clearMemory]; 75 | [self updateScrollViewLayoutIfNeed]; 76 | [self showPageAtIndex:self.currentPageIndex animated:YES]; 77 | } 78 | 79 | - (void)viewDidLoad { 80 | [super viewDidLoad]; 81 | self.view.backgroundColor = [UIColor clearColor]; 82 | self.firstWillAppear = YES; 83 | self.firstDidAppear = YES; 84 | self.firstWillLayoutSubViews = YES; 85 | 86 | [self configScrollView]; 87 | } 88 | 89 | - (void)viewWillAppear:(BOOL)animated 90 | { 91 | [super viewWillAppear:animated]; 92 | if (self.firstWillAppear) { 93 | if ([self.delegate respondsToSelector:@selector(pageviewController:willLeaveFromVC:toViewController:)]) { 94 | [self.delegate pageviewController:self willLeaveFromVC:[self controllerAtIndex:self.lastSelectedIndex] toViewController:[self controllerAtIndex:self.currentPageIndex]]; 95 | } 96 | 97 | if ([self.dataSource respondsToSelector:@selector(screenEdgePanGestureRecognizer)] && self.dataSource.screenEdgePanGestureRecognizer){ 98 | [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:self.dataSource.screenEdgePanGestureRecognizer]; 99 | } else { 100 | 101 | if ([self __screenEdgePanGestureRecognizer]) { 102 | [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:[self __screenEdgePanGestureRecognizer]]; 103 | 104 | } 105 | } 106 | 107 | self.firstWillAppear = NO; 108 | 109 | [self updateScrollViewLayoutIfNeed]; 110 | } 111 | 112 | [[self controllerAtIndex:self.currentPageIndex] beginAppearanceTransition:YES animated:YES]; 113 | 114 | } 115 | 116 | - (void)viewDidAppear:(BOOL)animated 117 | { 118 | [super viewDidAppear:animated]; 119 | if (self.firstDidAppear) { 120 | 121 | if ([self.delegate respondsToSelector:@selector(willChangeInit)]) { 122 | [self.delegate willChangeInit]; 123 | } 124 | 125 | if ([self.delegate respondsToSelector:@selector(pageviewController:didLeaveFromVC:toViewController:)]) { 126 | [self.delegate pageviewController:self didLeaveFromVC:[self controllerAtIndex:self.lastSelectedIndex] toViewController:[self controllerAtIndex:self.currentPageIndex]]; 127 | } 128 | 129 | self.firstDidAppear = NO; 130 | } 131 | 132 | 133 | [[self controllerAtIndex:self.currentPageIndex] endAppearanceTransition]; 134 | } 135 | 136 | - (void)viewWillLayoutSubviews 137 | { 138 | [super viewWillLayoutSubviews]; 139 | if (self.firstWillLayoutSubViews) { 140 | [self updateScrollViewLayoutIfNeed]; 141 | [self updateScrollViewDisplayIndexIfNeed]; 142 | self.firstWillLayoutSubViews = NO; 143 | } else { 144 | [self updateScrollViewLayoutIfNeed]; 145 | } 146 | } 147 | 148 | - (void)viewDidLayoutSubviews 149 | { 150 | [super viewDidLayoutSubviews]; 151 | 152 | } 153 | 154 | - (void)configScrollView 155 | { 156 | self.scrollView = [[SPPageContentView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; 157 | 158 | self.scrollView.delegate = self; 159 | 160 | [self.view addSubview:self.scrollView]; 161 | } 162 | 163 | - (UIScreenEdgePanGestureRecognizer *)__screenEdgePanGestureRecognizer 164 | { 165 | UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil; 166 | if (self.navigationController.view.gestureRecognizers.count > 0) 167 | { 168 | for (UIGestureRecognizer *recognizer in self.navigationController.view.gestureRecognizers) 169 | { 170 | if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) 171 | { 172 | screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer; 173 | break; 174 | } 175 | } 176 | } 177 | 178 | return screenEdgePanGestureRecognizer; 179 | } 180 | 181 | - (void)didReceiveMemoryWarning { 182 | [super didReceiveMemoryWarning]; 183 | // [self clearMemory]; 184 | } 185 | 186 | 187 | - (void)__removeFromParentViewController:(UIViewController *)controller 188 | { 189 | [controller willMoveToParentViewController:nil]; 190 | [controller.view removeFromSuperview]; 191 | [controller removeFromParentViewController]; 192 | } 193 | 194 | - (NSInteger)pageCount 195 | { 196 | return [self.dataSource numberOfControllers]; 197 | } 198 | 199 | #pragma scrollViewDelegate 200 | 201 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 202 | { 203 | if (scrollView.isDragging && scrollView == self.scrollView) { 204 | 205 | 206 | CGFloat offset = scrollView.contentOffset.x; 207 | CGFloat width = scrollView.frame.size.width; 208 | NSInteger lastGuestIndex= self.guessToIndex < 0? self.currentPageIndex:self.guessToIndex; 209 | 210 | if (self.originOffset < offset){ 211 | self.guessToIndex = ceil(offset/width); 212 | } else if (self.originOffset >= offset) { 213 | self.guessToIndex = floor(offset/width); 214 | } 215 | NSInteger maxCount = [self pageCount]; 216 | 217 | 218 | if (((self.guessToIndex != self.currentPageIndex && !self.scrollView.isDecelerating) || self.scrollView.isDecelerating) && lastGuestIndex != self.guessToIndex && self.guessToIndex >= 0 && self.guessToIndex =0 && lastGuestIndex < maxCount) { 235 | UIViewController *lastGuestVC = [self controllerAtIndex:lastGuestIndex]; 236 | [lastGuestVC beginAppearanceTransition:NO animated:YES]; 237 | [lastGuestVC endAppearanceTransition]; 238 | } 239 | } 240 | } 241 | 242 | if ([self.delegate respondsToSelector:@selector(scrollViewContentOffsetWithRatio:draging:)]) { 243 | [self.delegate scrollViewContentOffsetWithRatio:scrollView.contentOffset.x/scrollView.frame.size.width draging:YES]; 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 251 | { 252 | if (!scrollView.isDecelerating) { 253 | self.originOffset = scrollView.contentOffset.x; 254 | self.guessToIndex = self.currentPageIndex; 255 | } 256 | } 257 | 258 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 259 | { 260 | [self updatePageViewAfterDragging:scrollView]; 261 | } 262 | 263 | -(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 264 | { 265 | if ([self.delegate respondsToSelector:@selector(scrollViewContentOffsetWithRatio: draging:)]) { 266 | [self.delegate scrollViewContentOffsetWithRatio:targetContentOffset->x/scrollView.frame.size.width draging:NO]; 267 | } 268 | } 269 | 270 | - (BOOL)shouldAutomaticallyForwardAppearanceMethods 271 | { 272 | return NO; 273 | } 274 | 275 | - (void)updatePageViewAfterDragging:(UIScrollView *)scrollView 276 | { 277 | NSInteger newIndex = [self.scrollView calcIndex]; 278 | NSInteger oldIndex = self.currentPageIndex; 279 | self.currentPageIndex = newIndex; 280 | 281 | if (newIndex == oldIndex) { 282 | if (self.guessToIndex >= 0 && self.guessToIndex < [self pageCount]) { 283 | [[self controllerAtIndex:oldIndex] beginAppearanceTransition:YES animated:YES]; 284 | 285 | [[self controllerAtIndex:oldIndex] endAppearanceTransition]; 286 | 287 | [[self controllerAtIndex:self.guessToIndex] beginAppearanceTransition:NO animated:YES]; 288 | 289 | [[self controllerAtIndex:self.guessToIndex] endAppearanceTransition]; 290 | } 291 | } else { 292 | if (![self isPreLoad]) { 293 | [[self controllerAtIndex:newIndex] beginAppearanceTransition:YES animated:YES]; 294 | [[self controllerAtIndex:oldIndex] beginAppearanceTransition:NO animated:YES]; 295 | 296 | } 297 | [[self controllerAtIndex:newIndex] endAppearanceTransition]; 298 | [[self controllerAtIndex:oldIndex] endAppearanceTransition]; 299 | 300 | } 301 | 302 | self.originOffset = scrollView.contentOffset.x; 303 | self.guessToIndex = self.currentPageIndex; 304 | if ([self.delegate respondsToSelector:@selector(pageviewController:didTransitionFromVC:toViewController:)]) { 305 | [self.delegate pageviewController:self didTransitionFromVC:[self controllerAtIndex:self.lastSelectedIndex] toViewController:[self controllerAtIndex:self.currentPageIndex]]; 306 | } 307 | 308 | } 309 | 310 | 311 | - (BOOL)isPreLoad 312 | { 313 | return [self.dataSource respondsToSelector:@selector(isPreLoad)] && [self.dataSource isPreLoad]; 314 | } 315 | 316 | - (void)addVisibleViewContorllerWithVC:(UIViewController *)vc index:(NSInteger)index 317 | { 318 | 319 | CGRect childViewFrame = [self.scrollView calcVisibleViewControllerFrameWithIndex:index]; 320 | [self __addChildViewController:vc frame:childViewFrame]; 321 | 322 | } 323 | 324 | - (void)__addChildViewController:(UIViewController *)childController frame:(CGRect)frame 325 | { 326 | [self __addChildViewController:childController]; 327 | childController.view.frame = frame; 328 | [self.scrollView addSubview:childController.view]; 329 | } 330 | 331 | - (void)__addChildViewController:(UIViewController *)childController 332 | { 333 | if (![self.childViewControllers containsObject:childController]) { 334 | [self addChildViewController:childController]; 335 | [self didMoveToParentViewController:childController]; 336 | } 337 | [super addChildViewController:childController]; 338 | } 339 | 340 | - (void)updateScrollViewDisplayIndexIfNeed 341 | { 342 | if (self.scrollView.frame.size.width > 0) { 343 | CGPoint newOffset = [self.scrollView calOffsetWithIndex:self.currentPageIndex]; 344 | if (newOffset.x != self.scrollView.contentOffset.x || newOffset.y != self.scrollView.contentOffset.y) { 345 | self.scrollView.contentOffset = newOffset; 346 | } 347 | 348 | [self controllerAtIndex:self.currentPageIndex].view.frame = [self.scrollView calcVisibleViewControllerFrameWithIndex:self.currentPageIndex]; 349 | } 350 | } 351 | 352 | - (void)updateScrollViewLayoutIfNeed 353 | { 354 | if (self.scrollView.frame.size.width > 0) { 355 | [self.scrollView setItem:self.dataSource]; 356 | } 357 | } 358 | 359 | - (void)showPageAtIndex:(NSInteger)index animated:(BOOL)animated 360 | { 361 | if (index < 0 || index>= [self pageCount]) { 362 | return; 363 | } 364 | 365 | if (self.scrollView.frame.size.width > 0 && self.scrollView.contentSize.width > 0) { 366 | NSInteger oldSelectIndex = self.lastSelectedIndex; 367 | self.lastSelectedIndex = self.currentPageIndex; 368 | self.currentPageIndex = index; 369 | 370 | if ([self.delegate respondsToSelector:@selector(willChangeInit)]) { 371 | [self.delegate willChangeInit]; 372 | } 373 | 374 | if ([self.delegate respondsToSelector:@selector(pageviewController:willLeaveFromVC:toViewController:)]) { 375 | [self.delegate pageviewController:self willLeaveFromVC:[self controllerAtIndex:self.lastSelectedIndex] toViewController:[self controllerAtIndex:self.currentPageIndex]]; 376 | } 377 | 378 | [self scrollBeginAnimation:animated]; 379 | if (animated) { 380 | //页面切换动画 381 | if (self.lastSelectedIndex != self.currentPageIndex) {//如果非交互切换的index和currentindex一样,则什么都不做 382 | __block CGSize pageSize = self.scrollView.frame.size; 383 | SPPageScrollDirection direction = (self.lastSelectedIndex < self.currentPageIndex) ? PageRight :PageLeft; 384 | UIView *lastView = [self controllerAtIndex:self.lastSelectedIndex].view; 385 | UIView *currentView = [self controllerAtIndex:self.currentPageIndex].view; 386 | //oldselectindex 就是第一个动画选择的index 387 | UIView *oldSelectView = [self controllerAtIndex:oldSelectIndex].view; 388 | CGFloat backgroundIndex = [self.scrollView calcIndex]; 389 | UIView *backgroundView = nil; 390 | //这里考虑的是第一次动画还没结束,就开始第二次动画,需要把当前的处的位置的view给隐藏掉,避免出现一闪而过的情形。 391 | if (oldSelectView.layer.animationKeys.count > 0 && lastView.layer.animationKeys.count > 0) {// 392 | UIView *tmpView = [self controllerAtIndex:backgroundIndex].view; 393 | if (tmpView != currentView && tmpView != lastView) { 394 | backgroundView = tmpView; 395 | backgroundView.hidden = YES; 396 | } 397 | } 398 | 399 | //这里考虑的是第一次动画还没结束,就开始第二次动画,需要把之前的动画给结束掉,oldselectindex 就是第一个动画选择的index 400 | [self.scrollView.layer removeAllAnimations]; 401 | [oldSelectView.layer removeAllAnimations]; 402 | [lastView.layer removeAllAnimations]; 403 | [currentView.layer removeAllAnimations]; 404 | //这里需要还原第一次切换的view的位置 405 | [self moveBackToOriginPositionIfNeeded:oldSelectView index:oldSelectIndex]; 406 | 407 | 408 | //下面就是lastview 切换到currentview的代码,direction则是切换的方向,这里把lastview和currentview 起始放到了相邻位置在动画结束的时候,还原位置。 409 | [self.scrollView bringSubviewToFront:lastView]; 410 | 411 | [self.scrollView bringSubviewToFront:currentView]; 412 | 413 | lastView.hidden = NO; 414 | currentView.hidden = NO; 415 | 416 | CGPoint lastViewStartOrigin = lastView.frame.origin; 417 | CGPoint currentViewStartOrigin = lastViewStartOrigin; 418 | CGFloat offset = direction == PageRight?self.scrollView.frame.size.width:-self.scrollView.frame.size.width; 419 | currentViewStartOrigin.x += offset; 420 | 421 | CGPoint lastViewAnimationOrgin = lastViewStartOrigin; 422 | lastViewAnimationOrgin.x -= offset; 423 | CGPoint currentViewAnimationOrgin = lastViewStartOrigin; 424 | CGPoint lastViewEndOrigin = lastViewStartOrigin; 425 | CGPoint currentViewEndOrgin = currentView.frame.origin; 426 | 427 | lastView.frame = CGRectMake(lastViewStartOrigin.x, lastViewStartOrigin.y, pageSize.width, pageSize.height); 428 | 429 | currentView.frame = CGRectMake(currentViewStartOrigin.x, currentViewStartOrigin.y, pageSize.width, pageSize.height); 430 | 431 | CGFloat duration = 0.3; 432 | 433 | __weak SPPageController *wSelf = self; 434 | [UIView animateWithDuration:duration animations:^{ 435 | lastView.frame = CGRectMake(lastViewAnimationOrgin.x, lastViewAnimationOrgin.y, pageSize.width, pageSize.height); 436 | currentView.frame = CGRectMake(currentViewAnimationOrgin.x, currentViewAnimationOrgin.y, pageSize.width, pageSize.height); 437 | } completion:^(BOOL finished) { 438 | SPPageController *bSelf = wSelf; 439 | 440 | if (finished) { 441 | pageSize = bSelf.scrollView.frame.size; 442 | lastView.frame = CGRectMake(lastViewEndOrigin.x, lastViewEndOrigin.y, pageSize.width, pageSize.height); 443 | 444 | currentView.frame = CGRectMake(currentViewEndOrgin.x, currentViewEndOrgin.y, pageSize.width, pageSize.height); 445 | 446 | backgroundView.hidden = NO; 447 | [bSelf moveBackToOriginPositionIfNeeded:currentView index:bSelf.currentPageIndex]; 448 | [bSelf moveBackToOriginPositionIfNeeded:lastView index:bSelf.lastSelectedIndex]; 449 | [bSelf scrollAnimation:animated]; 450 | [bSelf scrollEndAnimation:animated]; 451 | } 452 | }]; 453 | 454 | 455 | } else { 456 | [self scrollAnimation:animated]; 457 | [self scrollEndAnimation:animated]; 458 | } 459 | } else { 460 | [self scrollAnimation:animated]; 461 | [self scrollEndAnimation:animated]; 462 | } 463 | 464 | 465 | } 466 | 467 | } 468 | 469 | - (void) moveBackToOriginPositionIfNeeded:(UIView *)view index:(NSInteger)index 470 | { 471 | if (index < 0 || index >= [self pageCount] || !view) { 472 | return; 473 | } 474 | 475 | 476 | UIView *destView = view; 477 | CGPoint originPostion = [self.scrollView calOffsetWithIndex:index]; 478 | if (destView.frame.origin.x != originPostion.x) { 479 | CGRect newFrame = destView.frame; 480 | newFrame.origin = originPostion; 481 | destView.frame = newFrame; 482 | } 483 | 484 | } 485 | 486 | - (UIViewController *)controllerAtIndex:(NSInteger)index 487 | { 488 | if (![self.memCacheDic objectForKey:@(index)]) { 489 | UIViewController *controller = [self.dataSource controllerAtIndex:index]; 490 | 491 | if (controller) { 492 | 493 | if ([self.dataSource respondsToSelector:@selector(isSubPageCanScrollForIndex:)] && [self.dataSource isSubPageCanScrollForIndex:index]) { 494 | 495 | controller.view.hidden = NO; 496 | } else { 497 | controller.view.hidden = YES; 498 | } 499 | 500 | if ([controller conformsToProtocol:@protocol(SPPageSubControllerDataSource)]) { 501 | [self bindController:(UIViewController *)controller index:index]; 502 | } 503 | 504 | [self.memCacheDic setObject:controller forKey:@(index)]; 505 | 506 | [self addVisibleViewContorllerWithVC:controller index:index]; 507 | 508 | 509 | } 510 | } 511 | 512 | 513 | return [self.memCacheDic objectForKey:@(index)]; 514 | } 515 | 516 | 517 | - (void)clearObserver 518 | { 519 | for (NSNumber *key in self.memCacheDic) { 520 | UIViewController *vc = self.memCacheDic[key]; 521 | if ([vc conformsToProtocol:@protocol(SPPageSubControllerDataSource)]) { 522 | UIScrollView *scrollView = [(UIViewController *)vc preferScrollView]; 523 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 524 | } 525 | } 526 | } 527 | 528 | -(void)dealloc 529 | { 530 | [self clearMemory]; 531 | } 532 | 533 | - (void)bindController:(UIViewController *)controller index:(NSInteger)index 534 | { 535 | UIScrollView *scrollView = [controller preferScrollView]; 536 | scrollView.scrollsToTop = NO; 537 | scrollView.tag = index; 538 | if ([self.dataSource respondsToSelector:@selector(pageTopAtIndex:)]) { 539 | UIEdgeInsets contentInset = scrollView.contentInset; 540 | 541 | scrollView.contentInset = UIEdgeInsetsMake([self.dataSource pageTopAtIndex:index], contentInset.left, contentInset.bottom, contentInset.right); 542 | 543 | #ifdef __IPHONE_11_0 544 | if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {// ios11 苹果加了一个安全区域 会自动修改scrollView的contentOffset 545 | scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 546 | } 547 | #endif 548 | 549 | } 550 | 551 | [scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; 552 | 553 | scrollView.contentOffset = CGPointMake(0, -scrollView.contentInset.top) ; 554 | } 555 | 556 | - (void)observeValueForKeyPath:(NSString *)keyPath 557 | ofObject:(id)object 558 | change:(NSDictionary *)change 559 | context:(void *)context 560 | { 561 | UIScrollView *scrollView = object; 562 | NSInteger index = scrollView.tag; 563 | if ([keyPath isEqualToString:@"contentOffset"]) { 564 | if (scrollView.tag != self.currentPageIndex) { 565 | return; 566 | } 567 | 568 | if (self.memCacheDic.count == 0) { 569 | return; 570 | } 571 | 572 | BOOL isNotNeedChangeContentOffset = scrollView.contentSize.height < KSCREEN_HEIGHT-KNAVIGATIONANDSTATUSBARHEIGHT && fabs (self.lastContentSize[@(index)].floatValue -scrollView.contentSize.height) > 1.0; 573 | 574 | if (self.delegate.cannotScrollWithPageOffset || isNotNeedChangeContentOffset) { 575 | if (self.lastContentOffset[@(index)] && fabs (self.lastContentOffset[@(index)].floatValue -scrollView.contentOffset.y) >0.1) { 576 | scrollView.contentOffset = CGPointMake(0, [self.lastContentOffset[@(index)] floatValue]); 577 | } 578 | } else { 579 | self.lastContentOffset[@(index)] = @(scrollView.contentOffset.y); 580 | [self.delegate scrollWithPageOffset:scrollView.contentOffset.y index:index]; 581 | } 582 | 583 | self.lastContentSize[@(index)] = @(scrollView.contentSize.height); 584 | 585 | } 586 | } 587 | 588 | - (void)scrollAnimation:(BOOL)animated 589 | { 590 | [self.scrollView setContentOffset:[self.scrollView calOffsetWithIndex:self.currentPageIndex ] animated:NO]; 591 | } 592 | 593 | - (void)scrollBeginAnimation:(BOOL)animated 594 | { 595 | [[self controllerAtIndex:self.currentPageIndex] beginAppearanceTransition:YES animated:animated]; 596 | if (self.currentPageIndex != self.lastSelectedIndex) { 597 | [[self controllerAtIndex:self.lastSelectedIndex] beginAppearanceTransition:NO animated:animated]; 598 | } 599 | } 600 | 601 | - (void)scrollEndAnimation:(BOOL)animated 602 | { 603 | [[self controllerAtIndex:self.currentPageIndex] endAppearanceTransition]; 604 | if (self.currentPageIndex != self.lastSelectedIndex) { 605 | [[self controllerAtIndex:self.lastSelectedIndex] endAppearanceTransition]; 606 | } 607 | if ([self.delegate respondsToSelector:@selector(pageviewController:didLeaveFromVC:toViewController:)]) { 608 | [self.delegate pageviewController:self didLeaveFromVC:[self controllerAtIndex:self.lastSelectedIndex] toViewController:[self controllerAtIndex:self.currentPageIndex]]; 609 | } 610 | 611 | } 612 | 613 | - (void)resizePageAtIndex:(NSInteger)index offset:(CGFloat)offset isNeedChangeOffset:(BOOL)isNeedChangeOffset atBeginOffsetChangeOffset:(BOOL)atBeginOffsetChangeOffset 614 | { 615 | UIViewController *vc = [self controllerAtIndex:index]; 616 | if (vc && [vc conformsToProtocol:@protocol(SPPageSubControllerDataSource)]) { 617 | UIScrollView *scrollView = [(UIViewController *)vc preferScrollView]; 618 | BOOL atBeginOffset = scrollView.contentOffset.y == - (scrollView.contentInset.top); 619 | 620 | CGPoint contentOffset = scrollView.contentOffset; 621 | scrollView.contentInset = UIEdgeInsetsMake([self.dataSource pageTopAtIndex:index], scrollView.contentInset.left, scrollView.contentInset.bottom, scrollView.contentInset.right); 622 | scrollView.contentOffset = contentOffset; 623 | if (isNeedChangeOffset) { 624 | scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, offset); 625 | } else { 626 | if (atBeginOffsetChangeOffset && atBeginOffset) { 627 | scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, offset); 628 | } 629 | } 630 | 631 | } 632 | } 633 | 634 | - (NSInteger)indexOfController:(UIViewController *)vc 635 | { 636 | for (NSNumber *key in self.memCacheDic) { 637 | if (vc ==self.memCacheDic[key]) { 638 | return [key integerValue]; 639 | } 640 | } 641 | 642 | return -1; 643 | } 644 | 645 | - (void)updateCurrentIndex:(NSInteger)index 646 | { 647 | self.currentPageIndex = index; 648 | } 649 | 650 | + (BOOL)iPhoneX 651 | { 652 | static BOOL b; 653 | static dispatch_once_t onceToken; 654 | dispatch_once(&onceToken, ^{ 655 | b = CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size); 656 | }); 657 | return b; 658 | } 659 | 660 | @end 661 | -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPTabController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPTabController.h 3 | // Radio 4 | // 5 | // Created by sparrow on 16/01/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPPageProtocol.h" 10 | #import "SPTabProtocol.h" 11 | 12 | @interface SPTabController : UIViewController 13 | //优先展示在哪个页面,reloadData后 会调用这个方法。 14 | - (NSInteger)preferPageFirstAtIndex; 15 | 16 | //适用于TAB纵向滑动的情况 17 | @property (nonatomic, assign) CGFloat maxYPullDown;//往下拉 tab的最大值 18 | @property (nonatomic, assign) CGFloat minYPullUp;//拉上拉 tab的最小值 19 | 20 | //单一tab 是否需要展示 21 | - (BOOL)preferSingleTabNotShow; 22 | 23 | //自定义TABView,最好遵循SPTabDelegate,SPTabDataSource,要不有些情况要重写 24 | - (UIView *)customTabView; 25 | //适用于Tab高度 变化的情况 26 | - (void)reloadTabH:(BOOL)isTabScroll; 27 | //需要完全刷新页面时调用这个接口 28 | - (void)reloadData; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Object-C/SPPage/Controller/SPTabController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPTabController.m 3 | // Radio 4 | // 5 | // Created by sparrow on 16/01/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPTabController.h" 10 | #import "SPTagBarScrollView.h" 11 | #import "SPPageController.h" 12 | 13 | #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 14 | #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 15 | 16 | @interface SPTabController () 17 | 18 | @property (nonatomic, strong) UIView *tabView; 19 | 20 | @property (nonatomic, strong) SPPageController *pageController; 21 | 22 | @property (nonatomic, assign) CGFloat tabViewTop; 23 | 24 | @property (nonatomic, assign) BOOL cannotScrollWithPageOffset;//为解决pagecontroller的横向滑动问题 25 | 26 | @end 27 | 28 | @implementation SPTabController 29 | 30 | 31 | - (void)tabDragWithOffset:(CGFloat)offset 32 | { 33 | 34 | [self setTabViewTop:[self tabScrollTopWithContentOffset:offset]]; 35 | } 36 | 37 | - (CGFloat)tabScrollTopWithContentOffset:(CGFloat)offset 38 | { 39 | CGFloat top = [self preferTabY]-offset; 40 | if (offset >= 0) {//上滑 41 | if (top <= self.minYPullUp) { 42 | top = self.minYPullUp; 43 | } 44 | } else {//下拉 45 | if (top >= self.maxYPullDown) { 46 | top = self.maxYPullDown; 47 | } 48 | } 49 | return top; 50 | } 51 | 52 | 53 | - (void)setTabViewTop:(CGFloat)tabViewTop 54 | { 55 | self.tabView.frame = CGRectMake(self.tabView.frame.origin.x, tabViewTop, self.tabView.frame.size.width, self.tabView.frame.size.height); 56 | } 57 | 58 | - (CGFloat)tabViewTop 59 | { 60 | return self.tabView.frame.origin.y; 61 | } 62 | 63 | - (void)reloadTab 64 | { 65 | [self.tabView removeFromSuperview]; 66 | self.tabView = nil; 67 | 68 | [self __setupTabBar]; 69 | } 70 | 71 | - (void)pageviewControllerDidTransitiontoViewController:(UIViewController *)toVC fromVC:(UIViewController *)fromVC 72 | { 73 | 74 | if ([fromVC conformsToProtocol:@protocol(SPPageSubControllerDataSource) ]) { 75 | UIViewController *fromVCTemp = (UIViewController *)fromVC; 76 | fromVCTemp.preferScrollView.scrollsToTop = NO; 77 | } 78 | 79 | if ([toVC conformsToProtocol:@protocol(SPPageSubControllerDataSource) ]) { 80 | UIViewController *toVCTemp = (UIViewController *)toVC; 81 | toVCTemp.preferScrollView.scrollsToTop = YES; 82 | } 83 | 84 | [self changeToSubControllerOffset:toVC isDelay:YES]; 85 | 86 | } 87 | 88 | - (CGFloat)scrollViewOffsetAtIndex:(NSInteger)index 89 | { 90 | return ([self preferTabY]- self.tabViewTop)- [self pageTopAtIndex:index]; 91 | } 92 | 93 | - (void)changeToSubControllerOffset:(UIViewController *)toVC isDelay:(BOOL)isDelay 94 | { 95 | if (!toVC || [self numberOfControllers] <=1) { 96 | self.cannotScrollWithPageOffset = NO; 97 | return; 98 | } 99 | 100 | if ([toVC conformsToProtocol:@protocol(SPPageSubControllerDataSource)]) { 101 | UIViewController *toVCTemp = (UIViewController *)toVC; 102 | NSInteger newIndex = [self.pageController indexOfController:toVCTemp]; 103 | CGFloat pageTop = [self pageTopAtIndex:newIndex]; 104 | CGFloat top = [self tabScrollTopWithContentOffset:[toVCTemp preferScrollView].contentOffset.y+pageTop]; 105 | 106 | //如果计算出来的高度一样,不用去修改offset 107 | if ( fabs(top -self.tabViewTop) > 0.1) { 108 | CGFloat scrollOffset = [self scrollViewOffsetAtIndex:newIndex]; 109 | self.cannotScrollWithPageOffset = NO; 110 | [toVCTemp preferScrollView].contentOffset = CGPointMake(0, scrollOffset); 111 | 112 | } else { 113 | self.cannotScrollWithPageOffset = NO; 114 | } 115 | 116 | } else { 117 | self.cannotScrollWithPageOffset = NO; 118 | } 119 | } 120 | 121 | 122 | 123 | - (void)__setupPage 124 | { 125 | self.pageController = [[SPPageController alloc] init]; 126 | self.pageController.dataSource = self; 127 | self.pageController.delegate = self; 128 | [self.pageController updateCurrentIndex:[self preferPageFirstAtIndex]]; 129 | self.pageController.view.frame = [self preferPageFrame]; 130 | 131 | [self addChildViewController:self.pageController]; 132 | [self.view addSubview:self.pageController.view]; 133 | [self.pageController didMoveToParentViewController:self]; 134 | 135 | } 136 | 137 | - (UIView *)customTabView 138 | { 139 | return nil; 140 | } 141 | 142 | - (void)__setupTabBar 143 | { 144 | if ([self preferSingleTabNotShow] && [self numberOfControllers] <=1) { 145 | 146 | return ; 147 | } 148 | 149 | UIView *tabView = [self customTabView]; 150 | if (!tabView) { 151 | tabView = [[SPTagBarScrollView alloc] initWithFrame:[self preferTabFrame] dataSource:self delegate:self]; 152 | 153 | } 154 | 155 | [self.view addSubview:tabView]; 156 | self.tabView = tabView; 157 | } 158 | 159 | - (void)viewDidLoad { 160 | [super viewDidLoad]; 161 | [self __setupData]; 162 | [self __setupPage]; 163 | [self __setupTabBar]; 164 | } 165 | 166 | - (CGRect)preferTabFrame 167 | { 168 | return CGRectMake([self preferTabX], [self preferTabY], [self preferTabW], [self preferTabHAtIndex:self.pageController.currentPageIndex]); 169 | } 170 | 171 | - (void)__setupData 172 | { 173 | self.maxYPullDown = SCREEN_HEIGHT; 174 | self.minYPullUp = 64;//默认的navbar和statusbar的高度 175 | } 176 | 177 | - (void)didReceiveMemoryWarning { 178 | [super didReceiveMemoryWarning]; 179 | } 180 | 181 | - (void)viewWillAppear:(BOOL)animated 182 | { 183 | [super viewWillAppear:animated]; 184 | self.cannotScrollWithPageOffset = NO; 185 | [self.pageController beginAppearanceTransition:YES animated:animated]; 186 | } 187 | 188 | - (void)viewDidAppear:(BOOL)animated 189 | { 190 | [super viewDidAppear:animated]; 191 | 192 | [self.pageController endAppearanceTransition]; 193 | } 194 | 195 | 196 | - (void)viewWillDisappear:(BOOL)animated 197 | { 198 | [super viewWillDisappear:animated]; 199 | self.cannotScrollWithPageOffset = YES; 200 | [self.pageController beginAppearanceTransition:NO animated:animated]; 201 | 202 | } 203 | 204 | - (void)viewDidDisappear:(BOOL)animated 205 | { 206 | [super viewDidDisappear:animated]; 207 | [self.pageController endAppearanceTransition]; 208 | } 209 | 210 | - (BOOL)preferSingleTabNotShow 211 | { 212 | return NO; 213 | } 214 | 215 | - (void)reloadData 216 | { 217 | [self reloadPage]; 218 | [self reloadTab]; 219 | } 220 | 221 | - (void)reloadPage 222 | { 223 | [self.pageController updateCurrentIndex:[self preferPageFirstAtIndex]]; 224 | self.pageController.view.frame = [self preferPageFrame]; 225 | [self.pageController reloadPage]; 226 | } 227 | 228 | - (void)updateTabBarWithIndex:(NSInteger)index 229 | { 230 | if ([self.tabView isKindOfClass:[SPTagBarScrollView class]]) { 231 | SPTagBarScrollView *tabView = (SPTagBarScrollView *)self.tabView; 232 | [tabView reloadHighlightToIndex:index]; 233 | } 234 | 235 | } 236 | 237 | 238 | - (void)reloadTabH:(BOOL)isTabScroll 239 | { 240 | if (self.currentIndex < 0) { 241 | return; 242 | } 243 | self.tabView.frame = [self preferTabFrame]; 244 | 245 | if (!isTabScroll) { 246 | self.pageController.view.frame = [self preferPageFrame]; 247 | } else { 248 | [self.pageController resizePageAtIndex:self.pageController.currentPageIndex offset:[self scrollViewOffsetAtIndex:self.currentIndex] isNeedChangeOffset:self.tabViewTop > self.minYPullUp atBeginOffsetChangeOffset:self.tabViewTop == self.minYPullUp]; 249 | } 250 | } 251 | 252 | - (NSInteger)currentIndex 253 | { 254 | return self.pageController.currentPageIndex; 255 | } 256 | 257 | #pragma SPPageControllerDelegate 258 | 259 | -(void)scrollViewContentOffsetWithRatio:(CGFloat)ratio draging:(BOOL)draging 260 | { 261 | if ([self.tabView isKindOfClass:[SPTagBarScrollView class]]) { 262 | 263 | if (!draging) { 264 | __weak SPTabController *wSelf = self; 265 | [UIView animateWithDuration:0.3 animations:^{ 266 | SPTabController *bSelf = wSelf; 267 | SPTagBarScrollView *scrollView = (SPTagBarScrollView *)bSelf.tabView; 268 | [scrollView markViewScrollToIndex:ratio]; 269 | scrollView.markViewScroll = NO; 270 | } completion:^(BOOL finished) { 271 | SPTabController *bSelf = wSelf; 272 | SPTagBarScrollView *scrollView = (SPTagBarScrollView *)bSelf.tabView; 273 | scrollView.markViewScroll = YES; 274 | [self updateTabBarWithIndex:floor(ratio+0.5)]; 275 | 276 | }]; 277 | } else { 278 | SPTagBarScrollView *scrollView = (SPTagBarScrollView *)self.tabView; 279 | [scrollView markViewScrollToContentRatio:ratio]; 280 | [self updateTabBarWithIndex:floor(ratio+0.5)]; 281 | } 282 | } 283 | 284 | } 285 | 286 | - (void)scrollWithPageOffset:(CGFloat)offset index:(NSInteger)index 287 | { 288 | [self tabDragWithOffset:offset+[self pageTopAtIndex:index]]; 289 | } 290 | 291 | 292 | - (void)pageviewController:(SPPageController*)pageController willTransitionFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC 293 | { 294 | 295 | [self changeToSubControllerOffset:toVC isDelay:NO]; 296 | } 297 | 298 | - (void)pageviewController:(SPPageController*)pageController didTransitionFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC 299 | { 300 | [self pageviewControllerDidTransitiontoViewController:toVC fromVC:fromVC]; 301 | if ([self.tabView isKindOfClass:[SPTagBarScrollView class]]) { 302 | SPTagBarScrollView *scrollView = (SPTagBarScrollView *)self.tabView; 303 | [scrollView scrollTagToIndex:self.pageController.currentPageIndex]; 304 | 305 | } 306 | 307 | } 308 | 309 | - (void)pageviewController:(SPPageController*)pageController willLeaveFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC 310 | { 311 | [self changeToSubControllerOffset:toVC isDelay:NO]; 312 | } 313 | 314 | - (void)pageviewController:(SPPageController*)pageController didLeaveFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC 315 | { 316 | [self pageviewControllerDidTransitiontoViewController:toVC fromVC:fromVC]; 317 | } 318 | 319 | #pragma SPPageControllerDataSource 320 | 321 | - (NSInteger)numberOfControllers 322 | { 323 | return 0; 324 | } 325 | 326 | - (CGRect)preferPageFrame 327 | { 328 | return CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); 329 | } 330 | 331 | - (UIViewController *)controllerAtIndex:(NSInteger)index 332 | { 333 | return nil; 334 | } 335 | 336 | - (CGFloat)pageTopAtIndex:(NSInteger)index 337 | { 338 | return [self preferTabY] + [self preferTabHAtIndex:index]-[self preferPageFrame].origin.y; 339 | } 340 | 341 | - (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer 342 | { 343 | UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil; 344 | if (self.navigationController.view.gestureRecognizers.count > 0) 345 | { 346 | for (UIGestureRecognizer *recognizer in self.navigationController.view.gestureRecognizers) 347 | { 348 | if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) 349 | { 350 | screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer; 351 | break; 352 | } 353 | } 354 | } 355 | 356 | return screenEdgePanGestureRecognizer; 357 | } 358 | 359 | - (BOOL)isPreLoad 360 | { 361 | return NO; 362 | } 363 | 364 | #pragma SPTabDataSource 规范化的TAB设计样式可以放在这里 365 | 366 | - (NSString *)titleForIndex:(NSInteger)index 367 | { 368 | return nil; 369 | } 370 | 371 | - (CGFloat)preferTabHAtIndex:(NSInteger)index 372 | { 373 | return 40; 374 | } 375 | 376 | - (CGFloat)preferTabY 377 | { 378 | return KNAVIGATIONANDSTATUSBARHEIGHT; 379 | } 380 | 381 | - (CGFloat)preferTabX 382 | { 383 | return 0; 384 | } 385 | 386 | - (CGFloat)preferTabW 387 | { 388 | return SCREEN_WIDTH; 389 | } 390 | 391 | -(NSInteger)preferPageFirstAtIndex 392 | { 393 | return 0; 394 | } 395 | 396 | - (NSInteger)numberOfTab 397 | { 398 | return [self numberOfControllers]; 399 | } 400 | 401 | 402 | -(CGFloat)tabTopForIndex:(NSInteger)index 403 | { 404 | return 0; 405 | } 406 | 407 | - (CGFloat)tabWidthForIndex:(NSInteger)index 408 | { 409 | NSString *text = [self titleForIndex:index]; 410 | if (text.length <=3) { 411 | return 73; 412 | } else { 413 | return 105; 414 | } 415 | } 416 | 417 | - (UIColor *)titleColorForIndex:(NSInteger)index 418 | { 419 | return [UIColor blackColor]; 420 | } 421 | 422 | - (UIColor *)titleHighlightColorForIndex:(NSInteger)index 423 | { 424 | return [UIColor orangeColor]; 425 | } 426 | 427 | - (UIFont *)titleFontForIndex:(NSInteger)index 428 | { 429 | return [UIFont systemFontOfSize:13.0]; 430 | } 431 | 432 | - (UIColor *)tabBackgroundColor 433 | { 434 | return [UIColor whiteColor]; 435 | } 436 | 437 | - (NSInteger)preferTabIndex{ 438 | return [self preferPageFirstAtIndex]; 439 | } 440 | 441 | - (BOOL)needMarkView 442 | { 443 | return YES; 444 | } 445 | 446 | - (CGFloat)markViewBottom 447 | { 448 | return [self preferTabHAtIndex:self.pageController.currentPageIndex]-7; 449 | } 450 | 451 | -(CGFloat)markViewWidthForIndex:(NSInteger)index 452 | { 453 | NSString *text = [self titleForIndex:index]; 454 | 455 | CGRect rect = [text boundingRectWithSize:CGSizeMake(20000, 40) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[self titleFontForIndex:index ]} context:nil]; 456 | 457 | return rect.size.width; 458 | } 459 | 460 | -(UIColor *)markViewColorForIndex:(NSInteger)index 461 | { 462 | return [UIColor orangeColor]; 463 | } 464 | 465 | #pragma SPTabDelegate 466 | - (void)didPressTabForIndex:(NSInteger)index 467 | { 468 | if ([self isSubPageCanScrollForIndex:index]) { 469 | [self.pageController showPageAtIndex:index animated:YES]; 470 | 471 | } 472 | 473 | } 474 | 475 | - (void)willChangeInit 476 | { 477 | self.cannotScrollWithPageOffset = YES; 478 | } 479 | 480 | -(BOOL)isTabCanPressForIndex:(NSInteger)index 481 | { 482 | return [self isSubPageCanScrollForIndex:index]; 483 | } 484 | 485 | -(BOOL)isSubPageCanScrollForIndex:(NSInteger)index 486 | { 487 | return YES; 488 | } 489 | 490 | 491 | @end 492 | -------------------------------------------------------------------------------- /Object-C/SPPage/Protocol/SPCoverProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPCoverProtocol.h 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SPCoverDataSource 12 | @required 13 | - (UIView *)preferCoverView;//不用管什么时机去生成coverview 14 | - (CGRect)preferCoverFrame; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Object-C/SPPage/Protocol/SPPageProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageProtocol.h 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class SPPageController; 13 | @protocol SPPageControllerDelegate 14 | 15 | @optional 16 | 17 | //交互切换回调 18 | - (void)pageviewController:(SPPageController*)pageController willTransitionFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; 19 | - (void)pageviewController:(SPPageController*)pageController didTransitionFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; 20 | //非交互切换回调 21 | - (void)pageviewController:(SPPageController*)pageController willLeaveFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; 22 | - (void)pageviewController:(SPPageController*)pageController didLeaveFromVC:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; 23 | 24 | //横向滑动回调 25 | - (void)scrollViewContentOffsetWithRatio:(CGFloat)ratio draging:(BOOL)draging; 26 | 27 | //垂直滑动的回调 28 | - (void)scrollWithPageOffset:(CGFloat)realOffset index:(NSInteger)index; 29 | 30 | - (void)willChangeInit;//针对初始化. 31 | 32 | - (BOOL)cannotScrollWithPageOffset; 33 | 34 | @end 35 | 36 | @protocol SPPageControllerDataSource 37 | @required 38 | - (UIViewController *)controllerAtIndex:(NSInteger)index; 39 | - (NSInteger)numberOfControllers; 40 | - (CGRect)preferPageFrame;//默认情况下是整屏幕.通常情况下,cover在最上面 tab在中间 page在下面的情况不用设这个frame 41 | 42 | @optional 43 | - (CGFloat)pageTopAtIndex:(NSInteger)index;//用于设子controller的scrollview的 inset 44 | - (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer;//解决侧滑失效的问题 45 | - (BOOL)isPreLoad;//交互切换的时候 是否预加载 46 | 47 | - (BOOL)isSubPageCanScrollForIndex:(NSInteger)index;//表示这个页面是否可用 48 | 49 | 50 | @end 51 | 52 | 53 | //如ChildController实现了这个协议,表示Tab和Cover会跟随Page纵向滑动 54 | @protocol SPPageSubControllerDataSource 55 | 56 | @optional 57 | - (UIScrollView *)preferScrollView;//子controller需要实现这个方法,如果需要cover跟着上下滑动 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Object-C/SPPage/Protocol/SPTabProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPTabProtocol.h 3 | // Example 4 | // 5 | // Created by sparrow on 30/07/2017. 6 | // Copyright © 2017 tencent. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SPTabDataSource 12 | 13 | @required 14 | - (NSString *)titleForIndex:(NSInteger)index; 15 | 16 | @optional 17 | - (UIColor *)titleColorForIndex:(NSInteger)index; 18 | - (UIColor *)titleHighlightColorForIndex:(NSInteger)index; 19 | - (UIFont *)titleFontForIndex:(NSInteger)index; 20 | - (NSInteger)numberOfTab; 21 | - (CGFloat)preferTabY; 22 | - (CGFloat)preferTabX; 23 | - (CGFloat)preferTabHAtIndex:(NSInteger)index;//假设每一个tab高度都可变的情况 24 | - (CGFloat)preferTabW; 25 | - (CGFloat)tabWidthForIndex:(NSInteger)index; 26 | - (CGFloat)preferTabLeftOffset; 27 | - (UIColor *)tabBackgroundColor; 28 | - (CGFloat)tabTopForIndex:(NSInteger)index;//默认是0 29 | - (NSInteger)preferTabIndex; 30 | 31 | - (CGFloat)markViewWidthForIndex:(NSInteger)index; 32 | - (UIColor *)markViewColorForIndex:(NSInteger)index; 33 | - (CGFloat)markViewBottom; 34 | - (BOOL)needMarkView; 35 | 36 | - (BOOL)isTabCanPressForIndex:(NSInteger)index; 37 | 38 | @end 39 | 40 | @protocol SPTabDelegate 41 | @optional 42 | - (void)didPressTabForIndex:(NSInteger)index;//页面切换已在SPTabcontroller 实现 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPPageContentView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageContentView.h 3 | // Radio 4 | // 5 | // Created by sparrow on 09/02/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SPPageProtocol.h" 11 | 12 | @interface SPPageContentView : UIScrollView 13 | 14 | - (CGRect)calcVisibleViewControllerFrameWithIndex:(NSInteger)index; 15 | - (CGPoint)calOffsetWithIndex:(NSInteger)index; 16 | - (NSInteger)calcIndex; 17 | 18 | - (void)setItem:(id)item; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPPageContentView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageContentView.m 3 | // Radio 4 | // 5 | // Created by sparrow on 09/02/2017. 6 | // Copyright © 2017 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPPageContentView.h" 10 | #import "SPPageProtocol.h" 11 | #import "SPPageController.h" 12 | 13 | @interface SPPageContentView() 14 | 15 | 16 | @property (nonatomic, weak) id dataSource; 17 | 18 | @end 19 | 20 | 21 | @implementation SPPageContentView 22 | 23 | 24 | -(instancetype)initWithFrame:(CGRect)frame 25 | { 26 | self = [super initWithFrame:frame]; 27 | if (self) { 28 | [self __configure]; 29 | } 30 | 31 | return self; 32 | } 33 | 34 | - (void)__configure 35 | { 36 | self.autoresizingMask = (0x1<<6) - 1; 37 | self.showsVerticalScrollIndicator = NO; 38 | self.showsHorizontalScrollIndicator = NO; 39 | self.pagingEnabled = YES; 40 | self.backgroundColor = [UIColor clearColor]; 41 | self.scrollsToTop = NO; 42 | 43 | #ifdef __IPHONE_11_0 44 | if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {// ios11 苹果加了一个安全区域 会自动修改scrollView的contentOffset 45 | self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 46 | } 47 | #endif 48 | } 49 | 50 | - (void)addSubview:(UIView *)view 51 | { 52 | if (![self.subviews containsObject:view]){ 53 | [super addSubview:view]; 54 | } 55 | } 56 | 57 | - (CGRect)calcVisibleViewControllerFrameWithIndex:(NSInteger)index 58 | { 59 | CGFloat offsetX = 0.0; 60 | offsetX = index * self.frame.size.width; 61 | return CGRectMake(offsetX, 0, self.frame.size.width, self.frame.size.height); 62 | } 63 | 64 | - (CGPoint)calOffsetWithIndex:(NSInteger)index 65 | { 66 | CGFloat width = self.frame.size.width; 67 | CGFloat maxWidth = self.contentSize.width; 68 | 69 | CGFloat offsetX = ((index) * width); 70 | 71 | if (offsetX < 0 ){ 72 | offsetX = 0; 73 | } 74 | 75 | if( maxWidth > 0.0 && 76 | offsetX > maxWidth - width) 77 | { 78 | offsetX = maxWidth - width; 79 | } 80 | 81 | return CGPointMake(offsetX, 0); 82 | } 83 | 84 | - (NSInteger)calcIndex 85 | { 86 | CGFloat width = self.frame.size.width; 87 | CGFloat offset = self.contentOffset.x; 88 | NSInteger startIndex = (NSInteger)offset/width; 89 | if (startIndex < 0) { 90 | startIndex = 0; 91 | } 92 | 93 | return startIndex; 94 | } 95 | 96 | - (void)setItem:(id)item 97 | { 98 | int startIndex =-1; 99 | NSInteger endIndex = -1; 100 | for (int i=0;i<[item numberOfControllers];i++) { 101 | if ([item respondsToSelector:@selector(isSubPageCanScrollForIndex:)] && [item isSubPageCanScrollForIndex:i] && startIndex == -1) { 102 | startIndex = i; 103 | } 104 | 105 | if (startIndex >= 0) { 106 | if (![item respondsToSelector:@selector(isSubPageCanScrollForIndex:)] || ![item isSubPageCanScrollForIndex:i]) { 107 | endIndex = i; 108 | break; 109 | } 110 | } 111 | } 112 | 113 | if (startIndex>=0 && endIndex == -1) { 114 | endIndex = [item numberOfControllers]; 115 | } 116 | 117 | self.contentInset = UIEdgeInsetsMake(0, -(startIndex)*self.frame.size.width, 0, 0); 118 | self.contentSize = CGSizeMake((endIndex)*self.frame.size.width, self.frame.size.height); 119 | 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPPageTagView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPMainTagView.h 3 | // Radio 4 | // 5 | // Created by WealongCai on 15/12/10. 6 | // Copyright (c) 2015年 qzone. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SPPageTagView : UIControl 12 | 13 | @property (assign, nonatomic, readwrite) NSUInteger tagIndex; 14 | @property (strong, nonatomic) UIColor *highlightedTitleColor; 15 | @property (strong, nonatomic) UIColor *normalTitleColor; 16 | @property (nonatomic, readonly) BOOL isHighlighted; 17 | 18 | - (void)highlightTagView; 19 | - (void)unhighlightTagView; 20 | 21 | @end 22 | 23 | 24 | @interface SPPageTagTitleView : SPPageTagView 25 | 26 | @property (strong, nonatomic) UILabel *title; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPPageTagView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPMainTagView.m 3 | // Radio 4 | // 5 | // Created by WealongCai on 15/12/10. 6 | // Copyright (c) 2015年 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPPageTagView.h" 10 | 11 | 12 | @interface SPPageTagView() 13 | @property (nonatomic, assign, readwrite) BOOL isHighlighted; 14 | @end 15 | 16 | 17 | @implementation SPPageTagView 18 | 19 | - (void)highlightTagView 20 | { 21 | NSException *exception = [NSException exceptionWithName:@"Method no override Exception" reason:@"Method highlightTagView must be override!" userInfo:nil]; 22 | @throw exception; 23 | } 24 | 25 | - (void)unhighlightTagView 26 | { 27 | NSException *exception = [NSException exceptionWithName:@"Method no override Exception" reason:@"Method unhighlightTagView must be override!" userInfo:nil]; 28 | @throw exception; 29 | } 30 | 31 | 32 | @end 33 | 34 | 35 | 36 | @implementation SPPageTagTitleView 37 | 38 | - (instancetype)initWithFrame:(CGRect)frame 39 | { 40 | if (self = [super initWithFrame:frame]) { 41 | self.backgroundColor = [UIColor clearColor]; 42 | self.frame = frame; 43 | 44 | self.title = [UILabel new]; 45 | self.title.backgroundColor = [UIColor clearColor]; 46 | 47 | self.title.textAlignment = NSTextAlignmentCenter; 48 | self.title.frame = self.bounds; 49 | 50 | 51 | [self addSubview:self.title]; 52 | } 53 | return self; 54 | } 55 | 56 | 57 | 58 | - (void)setHighlightedTitleColor:(UIColor *)highlightedTitleColor 59 | { 60 | [super setHighlightedTitleColor:highlightedTitleColor]; 61 | } 62 | 63 | - (void)setNormalTitleColor:(UIColor *)normalTitleColor 64 | { 65 | [super setNormalTitleColor:normalTitleColor]; 66 | } 67 | 68 | 69 | - (void)highlightTagView 70 | { 71 | self.title.textColor = self.highlightedTitleColor; 72 | self.isHighlighted = YES; 73 | } 74 | 75 | - (void)unhighlightTagView 76 | { 77 | self.title.textColor = self.normalTitleColor ; 78 | self.isHighlighted = NO; 79 | } 80 | 81 | - (void)layoutSubviews 82 | { 83 | [super layoutSubviews]; 84 | [_title sizeToFit]; 85 | _title.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2); 86 | } 87 | 88 | @end 89 | 90 | 91 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPTagBarScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPTagBarScrollView.h 3 | // Radio 4 | // 5 | // Created by sparrow on 20/10/2016. 6 | // Copyright © 2016 qzone. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SPTabProtocol.h" 11 | 12 | @interface SPTagBarScrollView : UIScrollView 13 | 14 | - (instancetype)initWithFrame:(CGRect)frame dataSource:(id)dataSource delegate:(id) delegate; 15 | 16 | @property (nonatomic, weak, readonly) id tabDataSource; 17 | @property (nonatomic, weak, readonly) id tabDelegate; 18 | @property (assign, nonatomic) BOOL markViewScroll;//解决滑动bug 19 | 20 | - (void)markViewScrollToContentRatio:(CGFloat)contentRatio; 21 | 22 | - (void)markViewScrollToIndex:(NSInteger)index; 23 | 24 | - (void)reloadHighlightToIndex:(NSInteger)index; 25 | 26 | - (void)scrollTagToIndex:(NSUInteger)toIndex; 27 | 28 | - (void)reloadTabBarTitleColor; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Object-C/SPPage/View/SPTagBarScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPTagBarScrollView.m 3 | // Radio 4 | // 5 | // Created by sparrow on 20/10/2016. 6 | // Copyright © 2016 qzone. All rights reserved. 7 | // 8 | 9 | #import "SPTagBarScrollView.h" 10 | #import "SPPageTagView.h" 11 | 12 | @interface SPTagBarScrollView () 13 | 14 | @property (strong, nonatomic) NSMutableArray *tagViewsCache; 15 | 16 | @property (strong, nonatomic) UIView *markView; 17 | 18 | @property (assign, nonatomic) NSInteger index; 19 | 20 | @property (nonatomic, weak, readwrite) id tabDataSource; 21 | 22 | @property (nonatomic, weak, readwrite) id tabDelegate; 23 | 24 | 25 | @end 26 | 27 | @implementation SPTagBarScrollView 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame dataSource:(id)dataSource delegate:(id) delegate 30 | { 31 | self = [super init]; 32 | 33 | if (self) { 34 | self.frame = frame; 35 | self.tabDataSource = dataSource; 36 | self.tabDelegate = delegate; 37 | [self __setupProperty]; 38 | [self __setupTagBarView]; 39 | [self __setupMaskView]; 40 | [self __setupFirstIndex]; 41 | 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (void)__setupProperty 48 | { 49 | self.contentSize = CGSizeZero; 50 | self.directionalLockEnabled = YES; 51 | self.scrollsToTop = NO; 52 | self.showsHorizontalScrollIndicator = NO; 53 | self.showsVerticalScrollIndicator = NO; 54 | self.backgroundColor = [UIColor clearColor]; 55 | } 56 | 57 | - (void)__setupTagBarView 58 | { 59 | 60 | 61 | self.tagViewsCache = [NSMutableArray new]; 62 | 63 | for (UIView *view in self.subviews) { 64 | [view removeFromSuperview]; 65 | } 66 | 67 | CGFloat tabContentWidth = 0; 68 | 69 | NSInteger offset = 0; 70 | NSInteger preferTabOffset = [self.tabDataSource respondsToSelector:@selector(preferTabLeftOffset)]?[self.tabDataSource preferTabLeftOffset]:5; 71 | for (int i=0;i<[self.tabDataSource numberOfTab];i++) { 72 | tabContentWidth += [self.tabDataSource respondsToSelector:@selector(tabWidthForIndex:)]?[self.tabDataSource tabWidthForIndex:i]:73; 73 | } 74 | if ((tabContentWidth+2*preferTabOffset ) > self.frame.size.width) { 75 | offset = preferTabOffset; 76 | } else { 77 | offset = (self.frame.size.width-tabContentWidth)/2.0; 78 | } 79 | 80 | 81 | for (int i=0;i<[self.tabDataSource numberOfTab];i++) { 82 | NSInteger tagWidth = [self.tabDataSource respondsToSelector:@selector(tabWidthForIndex:)]?[self.tabDataSource tabWidthForIndex:i]:73; 83 | CGFloat top = [self.tabDataSource respondsToSelector:@selector(tabTopForIndex:)]? [self.tabDataSource tabTopForIndex:i]:0; 84 | SPPageTagTitleView *titleView = [[SPPageTagTitleView alloc] initWithFrame:CGRectMake(offset, top, tagWidth, self.frame.size.height)]; 85 | if ([self.tabDataSource respondsToSelector:@selector(titleColorForIndex:)]) { 86 | titleView.normalTitleColor = [self.tabDataSource titleColorForIndex:i]; 87 | } 88 | if ([self.tabDataSource respondsToSelector:@selector(titleHighlightColorForIndex:)]) { 89 | titleView.highlightedTitleColor = [self.tabDataSource titleHighlightColorForIndex:i]; 90 | } 91 | titleView.title.text = [self.tabDataSource titleForIndex:i]; 92 | titleView.title.font = [self.tabDataSource respondsToSelector:@selector(titleFontForIndex:)]? [self.tabDataSource titleFontForIndex:i]:[UIFont systemFontOfSize:15.0]; 93 | titleView.tag = i; 94 | 95 | titleView.userInteractionEnabled = YES; 96 | 97 | [titleView addTarget:self action:@selector(pressTab:) forControlEvents:UIControlEventTouchUpInside]; 98 | 99 | [self addSubview:titleView]; 100 | [self.tagViewsCache addObject:titleView]; 101 | offset += tagWidth; 102 | } 103 | 104 | [self reloadHighlight]; 105 | 106 | self.contentSize = CGSizeMake(offset, self.frame.size.height); 107 | if ([self.tabDataSource respondsToSelector:@selector(tabBackgroundColor)]) { 108 | self.backgroundColor = [self.tabDataSource tabBackgroundColor]; 109 | } else { 110 | self.backgroundColor = [UIColor clearColor]; 111 | } 112 | } 113 | 114 | - (void)pressTab:(UIControl *)sender 115 | { 116 | 117 | NSInteger i = sender.tag; 118 | 119 | [self.tabDelegate didPressTabForIndex:i]; 120 | 121 | if (self.index == i) { 122 | return; 123 | } 124 | 125 | if ([self.tabDataSource respondsToSelector:@selector(isTabCanPressForIndex:)] && ![self.tabDataSource isTabCanPressForIndex:i]) { 126 | return; 127 | } 128 | 129 | 130 | self.index = i; 131 | [self reloadHighlight]; 132 | [self scrollTagToIndex:i]; 133 | [self markViewToIndex:i animatied:YES]; 134 | 135 | } 136 | 137 | - (void)__setupFirstIndex 138 | { 139 | NSInteger index = [self.tabDataSource respondsToSelector:@selector(preferTabIndex)]?[self.tabDataSource preferTabIndex]:0; 140 | self.index = index; 141 | [self scrollTagToIndex:index]; 142 | [self markViewToIndex:index animatied:NO]; 143 | } 144 | 145 | - (void)__setupMaskView 146 | { 147 | 148 | 149 | if ([self needMarkView]) { 150 | self.markView = [[UIView alloc] init]; 151 | self.markView.frame = CGRectMake(0, [self.tabDataSource respondsToSelector:@selector(markViewBottom)]?[self.tabDataSource markViewBottom]:-13, [self.tabDataSource markViewWidthForIndex:self.index], 2); 152 | 153 | self.markView.layer.cornerRadius = 1.0; 154 | self.markView.layer.masksToBounds = YES; 155 | self.markView.backgroundColor = [self.tabDataSource markViewColorForIndex:self.index]; 156 | [self addSubview:self.markView]; 157 | self.markViewScroll = YES; 158 | } 159 | } 160 | 161 | //点击 和初始化使用 162 | - (void)markViewToIndex:(NSInteger)index animatied:(BOOL)animated 163 | { 164 | if (index >= self.tagViewsCache.count || index < 0) { 165 | return; 166 | } 167 | 168 | if (![self needMarkView]) { 169 | [self reloadHighlight]; 170 | } else { 171 | SPPageTagView *nextTagView = self.tagViewsCache[index]; 172 | if (animated) { 173 | __weak SPTagBarScrollView *wScrollView =self; 174 | [UIView animateWithDuration:0.3 animations:^{ 175 | __weak SPTagBarScrollView *bScrollView =wScrollView; 176 | 177 | bScrollView.markView.center = CGPointMake(nextTagView.center.x, bScrollView.markView.center.y); 178 | } completion:^(BOOL finished) { 179 | __weak SPTagBarScrollView *bScrollView =wScrollView; 180 | [bScrollView reloadHighlight]; 181 | }]; 182 | } else { 183 | 184 | SPPageTagView *nextTagView = self.tagViewsCache[index]; 185 | self.markView.center = CGPointMake(nextTagView.center.x, self.markView.center.y); 186 | [self reloadHighlight]; 187 | 188 | } 189 | 190 | } 191 | } 192 | 193 | - (void)layoutSubviews 194 | { 195 | [super layoutSubviews]; 196 | 197 | } 198 | 199 | - (void)reloadHighlightToIndex:(NSInteger)index 200 | { 201 | self.index = index; 202 | [self reloadHighlight]; 203 | } 204 | 205 | - (void)reloadHighlight 206 | { 207 | for (int i=0;i= lastTagView.center.x) { 279 | moveCenterX = lastTagView.center.x; 280 | } 281 | 282 | self.markView.center = CGPointMake(moveCenterX, self.markView.center.y); 283 | } 284 | 285 | - (void)setMarkViewWidth:(CGFloat)width 286 | { 287 | CGRect frame = self.markView.frame; 288 | self.markView.frame = CGRectMake(frame.origin.x, frame.origin.y, width, frame.size.height); 289 | } 290 | 291 | - (void)markViewScrollToIndex:(NSInteger)index 292 | { 293 | if (!self.markViewScroll) { 294 | return; 295 | } 296 | 297 | if (index >= self.tagViewsCache.count || index < 0) { 298 | return; 299 | } 300 | 301 | SPPageTagView *curTagView = self.tagViewsCache[index]; 302 | [self setMarkViewWidth:[self.tabDataSource markViewWidthForIndex:index]]; 303 | self.markView.center = CGPointMake(curTagView.center.x, self.markView.center.y); 304 | } 305 | 306 | -(void)reloadTabBarTitleColor 307 | { 308 | for (int i=0;i<[self.tabDataSource numberOfTab];i++) { 309 | SPPageTagTitleView *titleView = (SPPageTagTitleView *)self.tagViewsCache[i]; 310 | if ([self.tabDataSource respondsToSelector:@selector(titleColorForIndex:)]) { 311 | titleView.normalTitleColor = [self.tabDataSource titleColorForIndex:i]; 312 | } 313 | if ([self.tabDataSource respondsToSelector:@selector(titleHighlightColorForIndex:)]) { 314 | titleView.highlightedTitleColor = [self.tabDataSource titleHighlightColorForIndex:i]; 315 | } 316 | } 317 | 318 | } 319 | 320 | @end 321 | -------------------------------------------------------------------------------- /ReadMe: -------------------------------------------------------------------------------- 1 | SPPage 2 | 3 | Please do not add QQ. 4 | If you have questions, send my Email or create issues. 5 | 6 | Requirements 7 | 8 | iOS >= 7 9 | 10 | Installation 11 | 12 | Copy codes or git specs. 13 | 14 | attention 15 | please set ContainerVc property automaticallyAdjustsScrollViewInsets NO 16 | 17 | Contact 18 | 19 | Email:376197328@qq.com 20 | 21 | Version 22 | 23 | 1.0 24 | -------------------------------------------------------------------------------- /Swift/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Swift/.DS_Store -------------------------------------------------------------------------------- /Swift/Example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Swift/Example/.DS_Store -------------------------------------------------------------------------------- /Swift/Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3750A9942056491E003C88A7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9932056491E003C88A7 /* AppDelegate.swift */; }; 11 | 3750A9962056491E003C88A7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9952056491E003C88A7 /* ViewController.swift */; }; 12 | 3750A9992056491E003C88A7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3750A9972056491E003C88A7 /* Main.storyboard */; }; 13 | 3750A99B2056491E003C88A7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3750A99A2056491E003C88A7 /* Assets.xcassets */; }; 14 | 3750A99E2056491E003C88A7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3750A99C2056491E003C88A7 /* LaunchScreen.storyboard */; }; 15 | 3750A9A92056491E003C88A7 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9A82056491E003C88A7 /* ExampleTests.swift */; }; 16 | 3750A9B42056491E003C88A7 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9B32056491E003C88A7 /* ExampleUITests.swift */; }; 17 | 3750A9C220564936003C88A7 /* TestSubController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9C120564936003C88A7 /* TestSubController.swift */; }; 18 | 3750A9E6205649CB003C88A7 /* SPCoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9DB205649CA003C88A7 /* SPCoverController.swift */; }; 19 | 3750A9E7205649CB003C88A7 /* SPPageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9DC205649CA003C88A7 /* SPPageController.swift */; }; 20 | 3750A9E8205649CB003C88A7 /* SPTabController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9DD205649CA003C88A7 /* SPTabController.swift */; }; 21 | 3750A9E9205649CB003C88A7 /* SPCoverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9DF205649CA003C88A7 /* SPCoverProtocol.swift */; }; 22 | 3750A9EA205649CB003C88A7 /* SPPageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9E0205649CA003C88A7 /* SPPageProtocol.swift */; }; 23 | 3750A9EB205649CB003C88A7 /* SPTabProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9E1205649CA003C88A7 /* SPTabProtocol.swift */; }; 24 | 3750A9EC205649CB003C88A7 /* SPPageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9E3205649CA003C88A7 /* SPPageContentView.swift */; }; 25 | 3750A9ED205649CB003C88A7 /* SPPageTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9E4205649CA003C88A7 /* SPPageTagView.swift */; }; 26 | 3750A9EE205649CB003C88A7 /* SPTagBarScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3750A9E5205649CA003C88A7 /* SPTagBarScrollView.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 3750A9A52056491E003C88A7 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 3750A9882056491E003C88A7 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 3750A98F2056491E003C88A7; 35 | remoteInfo = Example; 36 | }; 37 | 3750A9B02056491E003C88A7 /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = 3750A9882056491E003C88A7 /* Project object */; 40 | proxyType = 1; 41 | remoteGlobalIDString = 3750A98F2056491E003C88A7; 42 | remoteInfo = Example; 43 | }; 44 | /* End PBXContainerItemProxy section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 3750A9902056491E003C88A7 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 3750A9932056491E003C88A7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 3750A9952056491E003C88A7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 50 | 3750A9982056491E003C88A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 3750A99A2056491E003C88A7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 3750A99D2056491E003C88A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 3750A99F2056491E003C88A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 3750A9A42056491E003C88A7 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 3750A9A82056491E003C88A7 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; }; 56 | 3750A9AA2056491E003C88A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 3750A9AF2056491E003C88A7 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 3750A9B32056491E003C88A7 /* ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; }; 59 | 3750A9B52056491E003C88A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 3750A9C120564936003C88A7 /* TestSubController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestSubController.swift; sourceTree = ""; }; 61 | 3750A9DB205649CA003C88A7 /* SPCoverController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPCoverController.swift; sourceTree = ""; }; 62 | 3750A9DC205649CA003C88A7 /* SPPageController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPPageController.swift; sourceTree = ""; }; 63 | 3750A9DD205649CA003C88A7 /* SPTabController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPTabController.swift; sourceTree = ""; }; 64 | 3750A9DF205649CA003C88A7 /* SPCoverProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPCoverProtocol.swift; sourceTree = ""; }; 65 | 3750A9E0205649CA003C88A7 /* SPPageProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPPageProtocol.swift; sourceTree = ""; }; 66 | 3750A9E1205649CA003C88A7 /* SPTabProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPTabProtocol.swift; sourceTree = ""; }; 67 | 3750A9E3205649CA003C88A7 /* SPPageContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPPageContentView.swift; sourceTree = ""; }; 68 | 3750A9E4205649CA003C88A7 /* SPPageTagView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPPageTagView.swift; sourceTree = ""; }; 69 | 3750A9E5205649CA003C88A7 /* SPTagBarScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPTagBarScrollView.swift; sourceTree = ""; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | 3750A98D2056491E003C88A7 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | 3750A9A12056491E003C88A7 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 3750A9AC2056491E003C88A7 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | 3750A9872056491E003C88A7 = { 98 | isa = PBXGroup; 99 | children = ( 100 | 3750A9D9205649CA003C88A7 /* SPPage */, 101 | 3750A9922056491E003C88A7 /* Example */, 102 | 3750A9A72056491E003C88A7 /* ExampleTests */, 103 | 3750A9B22056491E003C88A7 /* ExampleUITests */, 104 | 3750A9912056491E003C88A7 /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 3750A9912056491E003C88A7 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 3750A9902056491E003C88A7 /* Example.app */, 112 | 3750A9A42056491E003C88A7 /* ExampleTests.xctest */, 113 | 3750A9AF2056491E003C88A7 /* ExampleUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 3750A9922056491E003C88A7 /* Example */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 3750A9C120564936003C88A7 /* TestSubController.swift */, 122 | 3750A9932056491E003C88A7 /* AppDelegate.swift */, 123 | 3750A9952056491E003C88A7 /* ViewController.swift */, 124 | 3750A9972056491E003C88A7 /* Main.storyboard */, 125 | 3750A99A2056491E003C88A7 /* Assets.xcassets */, 126 | 3750A99C2056491E003C88A7 /* LaunchScreen.storyboard */, 127 | 3750A99F2056491E003C88A7 /* Info.plist */, 128 | ); 129 | path = Example; 130 | sourceTree = ""; 131 | }; 132 | 3750A9A72056491E003C88A7 /* ExampleTests */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 3750A9A82056491E003C88A7 /* ExampleTests.swift */, 136 | 3750A9AA2056491E003C88A7 /* Info.plist */, 137 | ); 138 | path = ExampleTests; 139 | sourceTree = ""; 140 | }; 141 | 3750A9B22056491E003C88A7 /* ExampleUITests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 3750A9B32056491E003C88A7 /* ExampleUITests.swift */, 145 | 3750A9B52056491E003C88A7 /* Info.plist */, 146 | ); 147 | path = ExampleUITests; 148 | sourceTree = ""; 149 | }; 150 | 3750A9D9205649CA003C88A7 /* SPPage */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 3750A9DA205649CA003C88A7 /* core */, 154 | 3750A9DE205649CA003C88A7 /* Protocol */, 155 | 3750A9E2205649CA003C88A7 /* View */, 156 | ); 157 | name = SPPage; 158 | path = ../SPPage; 159 | sourceTree = ""; 160 | }; 161 | 3750A9DA205649CA003C88A7 /* core */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 3750A9DB205649CA003C88A7 /* SPCoverController.swift */, 165 | 3750A9DC205649CA003C88A7 /* SPPageController.swift */, 166 | 3750A9DD205649CA003C88A7 /* SPTabController.swift */, 167 | ); 168 | path = core; 169 | sourceTree = ""; 170 | }; 171 | 3750A9DE205649CA003C88A7 /* Protocol */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 3750A9DF205649CA003C88A7 /* SPCoverProtocol.swift */, 175 | 3750A9E0205649CA003C88A7 /* SPPageProtocol.swift */, 176 | 3750A9E1205649CA003C88A7 /* SPTabProtocol.swift */, 177 | ); 178 | path = Protocol; 179 | sourceTree = ""; 180 | }; 181 | 3750A9E2205649CA003C88A7 /* View */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 3750A9E3205649CA003C88A7 /* SPPageContentView.swift */, 185 | 3750A9E4205649CA003C88A7 /* SPPageTagView.swift */, 186 | 3750A9E5205649CA003C88A7 /* SPTagBarScrollView.swift */, 187 | ); 188 | path = View; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXGroup section */ 192 | 193 | /* Begin PBXNativeTarget section */ 194 | 3750A98F2056491E003C88A7 /* Example */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = 3750A9B82056491E003C88A7 /* Build configuration list for PBXNativeTarget "Example" */; 197 | buildPhases = ( 198 | 3750A98C2056491E003C88A7 /* Sources */, 199 | 3750A98D2056491E003C88A7 /* Frameworks */, 200 | 3750A98E2056491E003C88A7 /* Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | ); 206 | name = Example; 207 | productName = Example; 208 | productReference = 3750A9902056491E003C88A7 /* Example.app */; 209 | productType = "com.apple.product-type.application"; 210 | }; 211 | 3750A9A32056491E003C88A7 /* ExampleTests */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = 3750A9BB2056491E003C88A7 /* Build configuration list for PBXNativeTarget "ExampleTests" */; 214 | buildPhases = ( 215 | 3750A9A02056491E003C88A7 /* Sources */, 216 | 3750A9A12056491E003C88A7 /* Frameworks */, 217 | 3750A9A22056491E003C88A7 /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | 3750A9A62056491E003C88A7 /* PBXTargetDependency */, 223 | ); 224 | name = ExampleTests; 225 | productName = ExampleTests; 226 | productReference = 3750A9A42056491E003C88A7 /* ExampleTests.xctest */; 227 | productType = "com.apple.product-type.bundle.unit-test"; 228 | }; 229 | 3750A9AE2056491E003C88A7 /* ExampleUITests */ = { 230 | isa = PBXNativeTarget; 231 | buildConfigurationList = 3750A9BE2056491E003C88A7 /* Build configuration list for PBXNativeTarget "ExampleUITests" */; 232 | buildPhases = ( 233 | 3750A9AB2056491E003C88A7 /* Sources */, 234 | 3750A9AC2056491E003C88A7 /* Frameworks */, 235 | 3750A9AD2056491E003C88A7 /* Resources */, 236 | ); 237 | buildRules = ( 238 | ); 239 | dependencies = ( 240 | 3750A9B12056491E003C88A7 /* PBXTargetDependency */, 241 | ); 242 | name = ExampleUITests; 243 | productName = ExampleUITests; 244 | productReference = 3750A9AF2056491E003C88A7 /* ExampleUITests.xctest */; 245 | productType = "com.apple.product-type.bundle.ui-testing"; 246 | }; 247 | /* End PBXNativeTarget section */ 248 | 249 | /* Begin PBXProject section */ 250 | 3750A9882056491E003C88A7 /* Project object */ = { 251 | isa = PBXProject; 252 | attributes = { 253 | LastSwiftUpdateCheck = 0920; 254 | LastUpgradeCheck = 0920; 255 | ORGANIZATIONNAME = GodDan; 256 | TargetAttributes = { 257 | 3750A98F2056491E003C88A7 = { 258 | CreatedOnToolsVersion = 9.2; 259 | ProvisioningStyle = Automatic; 260 | }; 261 | 3750A9A32056491E003C88A7 = { 262 | CreatedOnToolsVersion = 9.2; 263 | ProvisioningStyle = Automatic; 264 | TestTargetID = 3750A98F2056491E003C88A7; 265 | }; 266 | 3750A9AE2056491E003C88A7 = { 267 | CreatedOnToolsVersion = 9.2; 268 | ProvisioningStyle = Automatic; 269 | TestTargetID = 3750A98F2056491E003C88A7; 270 | }; 271 | }; 272 | }; 273 | buildConfigurationList = 3750A98B2056491E003C88A7 /* Build configuration list for PBXProject "Example" */; 274 | compatibilityVersion = "Xcode 8.0"; 275 | developmentRegion = en; 276 | hasScannedForEncodings = 0; 277 | knownRegions = ( 278 | en, 279 | Base, 280 | ); 281 | mainGroup = 3750A9872056491E003C88A7; 282 | productRefGroup = 3750A9912056491E003C88A7 /* Products */; 283 | projectDirPath = ""; 284 | projectRoot = ""; 285 | targets = ( 286 | 3750A98F2056491E003C88A7 /* Example */, 287 | 3750A9A32056491E003C88A7 /* ExampleTests */, 288 | 3750A9AE2056491E003C88A7 /* ExampleUITests */, 289 | ); 290 | }; 291 | /* End PBXProject section */ 292 | 293 | /* Begin PBXResourcesBuildPhase section */ 294 | 3750A98E2056491E003C88A7 /* Resources */ = { 295 | isa = PBXResourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | 3750A99E2056491E003C88A7 /* LaunchScreen.storyboard in Resources */, 299 | 3750A99B2056491E003C88A7 /* Assets.xcassets in Resources */, 300 | 3750A9992056491E003C88A7 /* Main.storyboard in Resources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 3750A9A22056491E003C88A7 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 3750A9AD2056491E003C88A7 /* Resources */ = { 312 | isa = PBXResourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXResourcesBuildPhase section */ 319 | 320 | /* Begin PBXSourcesBuildPhase section */ 321 | 3750A98C2056491E003C88A7 /* Sources */ = { 322 | isa = PBXSourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | 3750A9E7205649CB003C88A7 /* SPPageController.swift in Sources */, 326 | 3750A9EA205649CB003C88A7 /* SPPageProtocol.swift in Sources */, 327 | 3750A9E9205649CB003C88A7 /* SPCoverProtocol.swift in Sources */, 328 | 3750A9E6205649CB003C88A7 /* SPCoverController.swift in Sources */, 329 | 3750A9ED205649CB003C88A7 /* SPPageTagView.swift in Sources */, 330 | 3750A9E8205649CB003C88A7 /* SPTabController.swift in Sources */, 331 | 3750A9962056491E003C88A7 /* ViewController.swift in Sources */, 332 | 3750A9EC205649CB003C88A7 /* SPPageContentView.swift in Sources */, 333 | 3750A9EB205649CB003C88A7 /* SPTabProtocol.swift in Sources */, 334 | 3750A9EE205649CB003C88A7 /* SPTagBarScrollView.swift in Sources */, 335 | 3750A9C220564936003C88A7 /* TestSubController.swift in Sources */, 336 | 3750A9942056491E003C88A7 /* AppDelegate.swift in Sources */, 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | 3750A9A02056491E003C88A7 /* Sources */ = { 341 | isa = PBXSourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | 3750A9A92056491E003C88A7 /* ExampleTests.swift in Sources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | 3750A9AB2056491E003C88A7 /* Sources */ = { 349 | isa = PBXSourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | 3750A9B42056491E003C88A7 /* ExampleUITests.swift in Sources */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | /* End PBXSourcesBuildPhase section */ 357 | 358 | /* Begin PBXTargetDependency section */ 359 | 3750A9A62056491E003C88A7 /* PBXTargetDependency */ = { 360 | isa = PBXTargetDependency; 361 | target = 3750A98F2056491E003C88A7 /* Example */; 362 | targetProxy = 3750A9A52056491E003C88A7 /* PBXContainerItemProxy */; 363 | }; 364 | 3750A9B12056491E003C88A7 /* PBXTargetDependency */ = { 365 | isa = PBXTargetDependency; 366 | target = 3750A98F2056491E003C88A7 /* Example */; 367 | targetProxy = 3750A9B02056491E003C88A7 /* PBXContainerItemProxy */; 368 | }; 369 | /* End PBXTargetDependency section */ 370 | 371 | /* Begin PBXVariantGroup section */ 372 | 3750A9972056491E003C88A7 /* Main.storyboard */ = { 373 | isa = PBXVariantGroup; 374 | children = ( 375 | 3750A9982056491E003C88A7 /* Base */, 376 | ); 377 | name = Main.storyboard; 378 | sourceTree = ""; 379 | }; 380 | 3750A99C2056491E003C88A7 /* LaunchScreen.storyboard */ = { 381 | isa = PBXVariantGroup; 382 | children = ( 383 | 3750A99D2056491E003C88A7 /* Base */, 384 | ); 385 | name = LaunchScreen.storyboard; 386 | sourceTree = ""; 387 | }; 388 | /* End PBXVariantGroup section */ 389 | 390 | /* Begin XCBuildConfiguration section */ 391 | 3750A9B62056491E003C88A7 /* Debug */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 398 | CLANG_CXX_LIBRARY = "libc++"; 399 | CLANG_ENABLE_MODULES = YES; 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_COMMA = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 406 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 407 | CLANG_WARN_EMPTY_BODY = YES; 408 | CLANG_WARN_ENUM_CONVERSION = YES; 409 | CLANG_WARN_INFINITE_RECURSION = YES; 410 | CLANG_WARN_INT_CONVERSION = YES; 411 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 412 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | CODE_SIGN_IDENTITY = "iPhone Developer"; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = dwarf; 423 | ENABLE_STRICT_OBJC_MSGSEND = YES; 424 | ENABLE_TESTABILITY = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu11; 426 | GCC_DYNAMIC_NO_PIC = NO; 427 | GCC_NO_COMMON_BLOCKS = YES; 428 | GCC_OPTIMIZATION_LEVEL = 0; 429 | GCC_PREPROCESSOR_DEFINITIONS = ( 430 | "DEBUG=1", 431 | "$(inherited)", 432 | ); 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 440 | MTL_ENABLE_DEBUG_INFO = YES; 441 | ONLY_ACTIVE_ARCH = YES; 442 | SDKROOT = iphoneos; 443 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 444 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 445 | }; 446 | name = Debug; 447 | }; 448 | 3750A9B72056491E003C88A7 /* Release */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | ALWAYS_SEARCH_USER_PATHS = NO; 452 | CLANG_ANALYZER_NONNULL = YES; 453 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 454 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 455 | CLANG_CXX_LIBRARY = "libc++"; 456 | CLANG_ENABLE_MODULES = YES; 457 | CLANG_ENABLE_OBJC_ARC = YES; 458 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 459 | CLANG_WARN_BOOL_CONVERSION = YES; 460 | CLANG_WARN_COMMA = YES; 461 | CLANG_WARN_CONSTANT_CONVERSION = YES; 462 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 463 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 464 | CLANG_WARN_EMPTY_BODY = YES; 465 | CLANG_WARN_ENUM_CONVERSION = YES; 466 | CLANG_WARN_INFINITE_RECURSION = YES; 467 | CLANG_WARN_INT_CONVERSION = YES; 468 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 469 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 470 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 471 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 472 | CLANG_WARN_STRICT_PROTOTYPES = YES; 473 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 474 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 475 | CLANG_WARN_UNREACHABLE_CODE = YES; 476 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 477 | CODE_SIGN_IDENTITY = "iPhone Developer"; 478 | COPY_PHASE_STRIP = NO; 479 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 480 | ENABLE_NS_ASSERTIONS = NO; 481 | ENABLE_STRICT_OBJC_MSGSEND = YES; 482 | GCC_C_LANGUAGE_STANDARD = gnu11; 483 | GCC_NO_COMMON_BLOCKS = YES; 484 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 485 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 486 | GCC_WARN_UNDECLARED_SELECTOR = YES; 487 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 488 | GCC_WARN_UNUSED_FUNCTION = YES; 489 | GCC_WARN_UNUSED_VARIABLE = YES; 490 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 491 | MTL_ENABLE_DEBUG_INFO = NO; 492 | SDKROOT = iphoneos; 493 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 494 | VALIDATE_PRODUCT = YES; 495 | }; 496 | name = Release; 497 | }; 498 | 3750A9B92056491E003C88A7 /* Debug */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 502 | CODE_SIGN_STYLE = Automatic; 503 | INFOPLIST_FILE = Example/Info.plist; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = example.Example; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_VERSION = 4.0; 508 | TARGETED_DEVICE_FAMILY = "1,2"; 509 | }; 510 | name = Debug; 511 | }; 512 | 3750A9BA2056491E003C88A7 /* Release */ = { 513 | isa = XCBuildConfiguration; 514 | buildSettings = { 515 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 516 | CODE_SIGN_STYLE = Automatic; 517 | INFOPLIST_FILE = Example/Info.plist; 518 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 519 | PRODUCT_BUNDLE_IDENTIFIER = example.Example; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | SWIFT_VERSION = 4.0; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | }; 524 | name = Release; 525 | }; 526 | 3750A9BC2056491E003C88A7 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 530 | BUNDLE_LOADER = "$(TEST_HOST)"; 531 | CODE_SIGN_STYLE = Automatic; 532 | INFOPLIST_FILE = ExampleTests/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 534 | PRODUCT_BUNDLE_IDENTIFIER = example.ExampleTests; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | SWIFT_VERSION = 4.0; 537 | TARGETED_DEVICE_FAMILY = "1,2"; 538 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 539 | }; 540 | name = Debug; 541 | }; 542 | 3750A9BD2056491E003C88A7 /* Release */ = { 543 | isa = XCBuildConfiguration; 544 | buildSettings = { 545 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 546 | BUNDLE_LOADER = "$(TEST_HOST)"; 547 | CODE_SIGN_STYLE = Automatic; 548 | INFOPLIST_FILE = ExampleTests/Info.plist; 549 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 550 | PRODUCT_BUNDLE_IDENTIFIER = example.ExampleTests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_VERSION = 4.0; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 555 | }; 556 | name = Release; 557 | }; 558 | 3750A9BF2056491E003C88A7 /* Debug */ = { 559 | isa = XCBuildConfiguration; 560 | buildSettings = { 561 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 562 | CODE_SIGN_STYLE = Automatic; 563 | INFOPLIST_FILE = ExampleUITests/Info.plist; 564 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 565 | PRODUCT_BUNDLE_IDENTIFIER = example.ExampleUITests; 566 | PRODUCT_NAME = "$(TARGET_NAME)"; 567 | SWIFT_VERSION = 4.0; 568 | TARGETED_DEVICE_FAMILY = "1,2"; 569 | TEST_TARGET_NAME = Example; 570 | }; 571 | name = Debug; 572 | }; 573 | 3750A9C02056491E003C88A7 /* Release */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 577 | CODE_SIGN_STYLE = Automatic; 578 | INFOPLIST_FILE = ExampleUITests/Info.plist; 579 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 580 | PRODUCT_BUNDLE_IDENTIFIER = example.ExampleUITests; 581 | PRODUCT_NAME = "$(TARGET_NAME)"; 582 | SWIFT_VERSION = 4.0; 583 | TARGETED_DEVICE_FAMILY = "1,2"; 584 | TEST_TARGET_NAME = Example; 585 | }; 586 | name = Release; 587 | }; 588 | /* End XCBuildConfiguration section */ 589 | 590 | /* Begin XCConfigurationList section */ 591 | 3750A98B2056491E003C88A7 /* Build configuration list for PBXProject "Example" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | 3750A9B62056491E003C88A7 /* Debug */, 595 | 3750A9B72056491E003C88A7 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | 3750A9B82056491E003C88A7 /* Build configuration list for PBXNativeTarget "Example" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 3750A9B92056491E003C88A7 /* Debug */, 604 | 3750A9BA2056491E003C88A7 /* Release */, 605 | ); 606 | defaultConfigurationIsVisible = 0; 607 | defaultConfigurationName = Release; 608 | }; 609 | 3750A9BB2056491E003C88A7 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 610 | isa = XCConfigurationList; 611 | buildConfigurations = ( 612 | 3750A9BC2056491E003C88A7 /* Debug */, 613 | 3750A9BD2056491E003C88A7 /* Release */, 614 | ); 615 | defaultConfigurationIsVisible = 0; 616 | defaultConfigurationName = Release; 617 | }; 618 | 3750A9BE2056491E003C88A7 /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { 619 | isa = XCConfigurationList; 620 | buildConfigurations = ( 621 | 3750A9BF2056491E003C88A7 /* Debug */, 622 | 3750A9C02056491E003C88A7 /* Release */, 623 | ); 624 | defaultConfigurationIsVisible = 0; 625 | defaultConfigurationName = Release; 626 | }; 627 | /* End XCConfigurationList section */ 628 | }; 629 | rootObject = 3750A9882056491E003C88A7 /* Project object */; 630 | } 631 | -------------------------------------------------------------------------------- /Swift/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Dan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Swift/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/Dan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Swift/Example/Example.xcodeproj/xcuserdata/Dan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift/Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by GodDan on 2018/3/12. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Swift/Example/Example/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 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Swift/Example/Example/Base.lproj/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 | -------------------------------------------------------------------------------- /Swift/Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Swift/Example/Example/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Swift/Example/Example/TestSubController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSubController.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/12. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | class TestSubController: UIViewController,UITableViewDelegate,UITableViewDataSource,SPPageSubControllerDataSource { 11 | private var tableView:UITableView? 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | self.tableView = UITableView(frame:self.view.bounds) 17 | self.tableView?.delegate = self 18 | self.tableView?.dataSource = self 19 | self.tableView?.autoresizingMask = [.flexibleTopMargin,.flexibleWidth,.flexibleHeight,.flexibleLeftMargin,.flexibleRightMargin,.flexibleBottomMargin] 20 | self.view.addSubview(self.tableView!) 21 | self.tableView?.separatorStyle = .none 22 | self.tableView?.backgroundColor = UIColor.clear 23 | self.tableView?.estimatedRowHeight = 0 24 | 25 | } 26 | 27 | func numberOfSections(in tableView: UITableView) -> Int { 28 | return 1 29 | } 30 | 31 | 32 | 33 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 34 | return 20 35 | } 36 | 37 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 | let cell = UITableViewCell.init(frame: CGRect.init(x: 0, y: 0, width: kScreenWidth, height: 60)) 39 | cell.textLabel?.text = "Row"+String(indexPath.row) 40 | cell.backgroundColor = UIColor.clear 41 | cell.contentView.backgroundColor = UIColor.clear 42 | 43 | return cell 44 | } 45 | 46 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 47 | return 60 48 | } 49 | 50 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 51 | self.tableView?.deselectRow(at: indexPath, animated: false) 52 | 53 | let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SUID_ViewController") 54 | 55 | self.navigationController?.pushViewController(vc, animated: true) 56 | } 57 | 58 | func preferScrollView() -> UIScrollView? { 59 | return self.tableView 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Swift/Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/2/27. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class ViewController: SPCoverController { 13 | 14 | var navTitle:String? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | self.minYPullUp = kNavigationAndStatusBarHeight 19 | self.automaticallyAdjustsScrollViewInsets = false 20 | self.navigationController?.isNavigationBarHidden = false 21 | self.navigationItem.title = self.navTitle ?? "SPPage" 22 | if (self.navTitle == nil) { 23 | self.navigationController?.navigationBar.barTintColor = UIColor.red 24 | } 25 | // Do any additional setup after loading the view, typically from a nib. 26 | } 27 | 28 | override func title(index: NSInteger) -> String { 29 | return "TAB" + String(index) 30 | } 31 | 32 | override func needMarkView() -> Bool { 33 | return true 34 | } 35 | 36 | override func preferCoverView() -> UIView? { 37 | let view = UIView.init(frame: self.preferCoverFrame()) 38 | view.backgroundColor = UIColor.black 39 | 40 | return view 41 | } 42 | 43 | override func preferTabFrame(index: NSInteger) -> CGRect { 44 | return CGRect.init(x: 0, y: 245, width: kScreenWidth, height: 40) 45 | } 46 | 47 | override func preferCoverFrame() -> CGRect { 48 | return CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: kScreenWidth, height: 245)) 49 | } 50 | 51 | override func controller(index: NSInteger) -> UIViewController { 52 | let subVC = TestSubController() 53 | if index == 0 { 54 | subVC.view.backgroundColor = UIColor.green 55 | } else if index == 1 { 56 | subVC.view.backgroundColor = UIColor.orange 57 | 58 | } else { 59 | subVC.view.backgroundColor = UIColor.red 60 | 61 | } 62 | 63 | return subVC 64 | } 65 | 66 | override func preferPageFirstAtIndex() -> NSInteger { 67 | return 0 68 | } 69 | 70 | override func isSubPageCanScrollForIndex(index: NSInteger) -> Bool { 71 | return true 72 | } 73 | 74 | override func numberOfControllers() -> NSInteger { 75 | return 8 76 | } 77 | 78 | override func isPreLoad() -> Bool { 79 | return false 80 | } 81 | 82 | override func didReceiveMemoryWarning() { 83 | super.didReceiveMemoryWarning() 84 | // Dispose of any resources that can be recreated. 85 | } 86 | 87 | 88 | } 89 | 90 | -------------------------------------------------------------------------------- /Swift/Example/ExampleTests/ExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.swift 3 | // ExampleTests 4 | // 5 | // Created by GodDan on 2018/3/12. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Example 11 | 12 | class ExampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Swift/Example/ExampleTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Swift/Example/ExampleUITests/ExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleUITests.swift 3 | // ExampleUITests 4 | // 5 | // Created by GodDan on 2018/3/12. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ExampleUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Swift/Example/ExampleUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Swift/SPPage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xichen744/SPPage/e4a3425d06721b4f49dbc38e2787db4e80da9736/Swift/SPPage/.DS_Store -------------------------------------------------------------------------------- /Swift/SPPage/Protocol/SPCoverProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPCoverProtocol.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/11. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SPCoverDataSource :class{ 12 | func preferCoverView() -> UIView? 13 | func preferCoverFrame() -> CGRect 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Swift/SPPage/Protocol/SPPageProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageProtocol.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/4. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @objc protocol SPPageControllerDelegate :class{ 13 | 14 | @objc optional func pageviewControllerWillTransition(pageVC:SPPageController, fromVC:UIViewController, toVC:UIViewController) 15 | @objc optional func pageviewControllerDidTransition(pageVC:SPPageController, fromVC:UIViewController, toVC:UIViewController) 16 | @objc optional func pageviewControllerWillLeave(pageVC:SPPageController, fromVC:UIViewController, toVC:UIViewController) 17 | @objc optional func pageviewControllerDidLeave(pageVC:SPPageController, fromVC:UIViewController, toVC:UIViewController) 18 | @objc optional func scrollViewContentOffsetWithRatio(ratio:CGFloat, draging:Bool) 19 | @objc optional func scrollWithPageOffset(pageOffset:CGFloat, index:NSInteger) 20 | @objc optional func willChangeInit() 21 | @objc optional func cannotScrollWithPageOffset() -> Bool 22 | 23 | } 24 | 25 | 26 | @objc protocol SPPageControllerDataSource:class { 27 | 28 | func controller(index:NSInteger) ->UIViewController 29 | func numberOfControllers() -> NSInteger 30 | func preferPageFrame() -> CGRect 31 | func preferPageFirstAtIndex() -> NSInteger 32 | @objc optional func pageTopAtIndex(index:NSInteger) -> CGFloat 33 | @objc optional func screenEdgePanGestureRecognizer() -> UIScreenEdgePanGestureRecognizer? 34 | @objc optional func isPreLoad() -> Bool 35 | @objc optional func isSubPageCanScrollForIndex(index:NSInteger) -> Bool 36 | 37 | } 38 | 39 | 40 | protocol SPPageSubControllerDataSource :class{ 41 | func preferScrollView() -> UIScrollView? 42 | } 43 | -------------------------------------------------------------------------------- /Swift/SPPage/Protocol/SPTabProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPTabProtocol.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/10. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc protocol SPTabDataSource :class{ 12 | 13 | func title(index:NSInteger) -> String 14 | func numberOfTab() -> NSInteger 15 | func preferTabFrame(index:NSInteger) -> CGRect 16 | 17 | @objc optional func tabWidth(index:NSInteger) -> CGFloat 18 | @objc optional func prferTabLeftOffset() -> CGFloat 19 | @objc optional func tabBackgroundColor() -> UIColor 20 | @objc optional func tabTopForIndex(index:NSInteger) -> CGFloat 21 | @objc optional func preferTabIndex() -> NSInteger 22 | @objc optional func titleFont(index:NSInteger) -> UIFont 23 | @objc optional func titleHighlightColor(index:NSInteger) -> UIColor 24 | @objc optional func titleColor(index:NSInteger) -> UIColor 25 | 26 | @objc optional func tabCanPress(index:NSInteger) -> Bool 27 | 28 | @objc optional func needMarkView() -> Bool 29 | @objc optional func markViewWidth(index:NSInteger) -> CGFloat 30 | @objc optional func markViewColor(index:NSInteger) -> UIColor 31 | @objc optional func markViewBottom(index:NSInteger) -> CGFloat 32 | 33 | } 34 | 35 | @objc protocol SPTabDelegate :class{ 36 | 37 | @objc optional func didPressTab(index:NSInteger) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Swift/SPPage/View/SPPageContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageContentView.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/1. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class SPPageContentView: UIScrollView { 13 | 14 | required override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | self.__setup() 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | super.init(coder: aDecoder) 21 | self.__setup() 22 | 23 | } 24 | 25 | func __setup() { 26 | self.showsVerticalScrollIndicator = false 27 | self.showsHorizontalScrollIndicator = false 28 | self.isPagingEnabled = true 29 | self.backgroundColor = UIColor.clear 30 | self.scrollsToTop = false 31 | 32 | if #available(OSX 11.0, *) {// ios11 苹果加了一个安全区域 会自动修改scrollView的contentOffset 33 | self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.never; 34 | } 35 | } 36 | 37 | func addSubView(view:UIView) { 38 | if !self.subviews.contains(view) { 39 | super.addSubview(view) 40 | } 41 | } 42 | 43 | func calcVisibleViewControllerFrameWithIndex(index:NSInteger) -> CGRect { 44 | let offsetX:CGFloat = CGFloat(index) * CGFloat(self.frame.size.width) 45 | 46 | return CGRect.init(x: offsetX, y: 0, width: self.frame.size.width, height: self.frame.size.height) 47 | 48 | } 49 | 50 | func calOffsetWithIndex(index:NSInteger) -> CGPoint { 51 | 52 | let width:CGFloat = self.frame.size.width 53 | let maxWidth:CGFloat = self.contentSize.width 54 | 55 | var offsetX:CGFloat = CGFloat(index)*width 56 | 57 | if offsetX < 0 { 58 | offsetX = 0 59 | } 60 | 61 | if maxWidth > 0 && offsetX > maxWidth-width { 62 | offsetX = maxWidth-width 63 | } 64 | 65 | return CGPoint.init(x: offsetX, y: 0) 66 | 67 | } 68 | 69 | func calcIndex() -> NSInteger { 70 | let offsetX = self.contentOffset.x 71 | let width = self.frame.size.width 72 | var startIndex:NSInteger = NSInteger(offsetX/width) 73 | 74 | if startIndex < 0 { 75 | startIndex = 0 76 | } 77 | 78 | return startIndex 79 | } 80 | 81 | func setItem(item:SPPageControllerDataSource) { 82 | var startIndex:NSInteger = -1 83 | var endIndex:NSInteger = item.numberOfControllers() 84 | for i in 0...item.numberOfControllers()-1 { 85 | if (item.isSubPageCanScrollForIndex?(index: i))! && startIndex == -1 { 86 | startIndex = i 87 | } 88 | 89 | if startIndex >= 0 && !(item.isSubPageCanScrollForIndex?(index: i))! { 90 | endIndex = i 91 | break 92 | } 93 | } 94 | 95 | self.contentInset = UIEdgeInsets.init(top: 0, left: -(CGFloat(startIndex) )*self.frame.size.width, bottom: 0, right: 0) 96 | self.contentSize = CGSize.init(width: (CGFloat(endIndex))*self.frame.size.width, height: self.frame.size.height) 97 | 98 | } 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /Swift/SPPage/View/SPPageTagView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageTagView.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/10. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SPPageTagTitleView: UIControl { 12 | 13 | private var highlightedTitleColor:UIColor = UIColor.clear 14 | private var normalTitleColor:UIColor = UIColor.clear 15 | var titleLable:UILabel = UILabel.init() 16 | 17 | 18 | convenience init(frame: CGRect, highlightedTitleColor:UIColor, normalTitleColor:UIColor) { 19 | self.init(frame: frame) 20 | self.highlightedTitleColor = highlightedTitleColor 21 | self.normalTitleColor = normalTitleColor 22 | 23 | } 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | self.setup() 28 | self.frame = frame 29 | self.titleLable.frame = self.bounds 30 | 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | self.setup() 36 | } 37 | 38 | func setup() { 39 | self.backgroundColor = UIColor.clear 40 | self.titleLable.backgroundColor = UIColor.clear 41 | self.titleLable.textAlignment = NSTextAlignment.center 42 | self.addSubview(self.titleLable) 43 | } 44 | 45 | func highlightTagView() { 46 | self.titleLable.textColor = self.highlightedTitleColor 47 | } 48 | 49 | func unHighlightTagView () { 50 | self.titleLable.textColor = self.normalTitleColor 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Swift/SPPage/View/SPTagBarScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPTagBarScrollView.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/10. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SPTagBarScrollView: UIScrollView { 12 | 13 | private var tagViews:Array = Array() 14 | private var markView:UIView = UIView() 15 | private var index:NSInteger = 0 16 | private weak var tabDataSource:SPTabDataSource? 17 | private weak var tabDelegate:SPTabDelegate? 18 | var markViewScroll: Bool = false 19 | 20 | convenience init(frame: CGRect, tabDelagate: SPTabDelegate, tabDataSource: SPTabDataSource) { 21 | self.init(frame: frame) 22 | self.tabDelegate = tabDelagate 23 | self.tabDataSource = tabDataSource 24 | self.setup() 25 | } 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | super.init(coder: aDecoder) 33 | } 34 | 35 | private func setup() { 36 | self.index = self.tabDataSource?.preferTabIndex?() ?? 0 37 | self.setupProperty() 38 | self.setupTabBarScrollView() 39 | self.setupMaskView() 40 | self.scrollTab(index: self.index) 41 | self.scrollMarkView(index: self.index, animated: false) 42 | } 43 | 44 | private func setupProperty() { 45 | self.isDirectionalLockEnabled = true 46 | self.scrollsToTop = false 47 | self.showsVerticalScrollIndicator = false 48 | self.showsHorizontalScrollIndicator = false 49 | self.backgroundColor = UIColor.clear 50 | } 51 | 52 | private func setupTabBarScrollView() { 53 | 54 | var offset:CGFloat = self.tabDataSource?.prferTabLeftOffset?() ?? 5 55 | let num:NSInteger! = self.tabDataSource?.numberOfTab() 56 | 57 | for i in 0...num { 58 | let tabWidth:CGFloat = self.tabDataSource?.tabWidth?(index: i) ?? 73 59 | let top:CGFloat = self.tabDataSource?.tabTopForIndex?(index: i) ?? 0 60 | let normalTitleColor:UIColor = self.tabDataSource?.titleColor?(index: i) ?? UIColor.black 61 | let hightlightedTitleColor = self.tabDataSource?.titleHighlightColor?(index: i) ?? UIColor.orange 62 | 63 | let titleView:SPPageTagTitleView = SPPageTagTitleView.init(frame: CGRect.init(x: offset, y: top, width: tabWidth, height: self.frame.size.height), highlightedTitleColor: hightlightedTitleColor, normalTitleColor: normalTitleColor) 64 | titleView.titleLable.text = self.tabDataSource?.title(index: i) 65 | titleView.titleLable.font = self.tabDataSource?.titleFont?(index: i) ?? UIFont.systemFont(ofSize: 15.0) 66 | titleView.tag = i 67 | titleView.isUserInteractionEnabled = true 68 | titleView.addTarget(self, action: #selector(SPTagBarScrollView.pressTab(sender:)), for: .touchUpInside) 69 | 70 | self.addSubview(titleView) 71 | 72 | self.tagViews.append(titleView) 73 | 74 | offset += tabWidth 75 | 76 | } 77 | 78 | self.reloadHighlight() 79 | 80 | 81 | self.contentSize.width = offset 82 | self.backgroundColor = self.tabDataSource?.tabBackgroundColor?() ?? UIColor.clear 83 | 84 | } 85 | 86 | private func setupMaskView() { 87 | if self.needMarkView() { 88 | self.markView = UIView.init(frame: CGRect.init(x: 0, y: self.tabDataSource?.markViewBottom?(index: self.index) ?? -13, width: self.tabDataSource?.markViewWidth?(index: self.index) ?? 0, height: 2)) 89 | self.markView.layer.cornerRadius = 1.0 90 | self.markView.layer.masksToBounds = true 91 | self.markView.backgroundColor = self.tabDataSource?.markViewColor?(index: self.index) ?? UIColor.orange 92 | self.addSubview(self.markView) 93 | self.markViewScroll = true 94 | } 95 | } 96 | 97 | @objc private func pressTab(sender:UIControl) { 98 | let index = sender.tag 99 | 100 | if self.index == index { 101 | return 102 | } 103 | 104 | if (self.tabDataSource?.tabCanPress?(index: index))! { 105 | self.index = index 106 | self.reloadHighlight() 107 | self.scrollTab(index: index) 108 | self.scrollMarkView(index: index, animated: true) 109 | self.tabDelegate?.didPressTab?(index: index) 110 | } 111 | } 112 | 113 | private func reloadHighlight() { 114 | for i in 0...self.tagViews.count-1 { 115 | let titleView:SPPageTagTitleView = self.tagViews[i] 116 | if (self.index == i) { 117 | titleView.highlightTagView() 118 | } else { 119 | titleView.unHighlightTagView() 120 | } 121 | } 122 | } 123 | 124 | private func needMarkView() -> Bool { 125 | return (self.tabDataSource?.needMarkView?())! 126 | } 127 | 128 | func reloadHighlight(index:NSInteger) { 129 | self.index = index 130 | self.reloadHighlight() 131 | } 132 | 133 | func scrollTab(index:NSInteger) { 134 | if self.tagViews.count <= index || self.contentSize.width < self.frame.size.width { 135 | return 136 | } 137 | 138 | if index == self.tagViews.count-1 && index != 0 139 | { 140 | self.setContentOffset(CGPoint.init(x: self.contentSize.width-self.frame.size.width+self.contentInset.right, y: 0), animated: true) 141 | } else { 142 | let nextTitleView = self.tagViews[index] 143 | 144 | let tabExceptInScreen = UIScreen.main.bounds.size.width - nextTitleView.frame.size.width 145 | let tabPaddingInScreen = tabExceptInScreen / 2.0 146 | let offsetX = max(0, min(nextTitleView.frame.origin.x - tabPaddingInScreen, (self.tagViews.last?.frame.origin.x)! - tabExceptInScreen)) 147 | 148 | let nextPoint = CGPoint.init(x: offsetX, y: 0) 149 | self.setContentOffset(nextPoint, animated: true) 150 | 151 | } 152 | } 153 | 154 | func markViewScroll(index:NSInteger) { 155 | if !self.markViewScroll { 156 | return 157 | } 158 | 159 | if index < 0 || index >= self.tagViews.count { 160 | return 161 | } 162 | 163 | let curTab = self.tagViews[index] 164 | 165 | 166 | self.markView.center.x = curTab.center.x 167 | self.markView.frame.size.width = self.tabDataSource?.markViewWidth?(index: index) ?? 0 168 | 169 | } 170 | 171 | func markViewScroll(contentRatio:Double) { 172 | if !self.markViewScroll { 173 | return 174 | } 175 | 176 | let fromIndex = Int( ceil(contentRatio ) - 1) 177 | 178 | if fromIndex < 0 || fromIndex >= self.tagViews.count - 1 { 179 | return 180 | } 181 | 182 | let fromWidth:CGFloat = (self.tabDataSource?.markViewWidth?(index: fromIndex))! 183 | let toWidth:CGFloat = (self.tabDataSource?.markViewWidth?(index: fromIndex+1))! 184 | 185 | if fromWidth != toWidth { 186 | self.markView.frame.size.width = fromWidth + (toWidth - fromWidth)*(CGFloat( contentRatio) - CGFloat(fromIndex) ) 187 | } 188 | 189 | let curTabView = self.tagViews[fromIndex] 190 | let nextTabView = self.tagViews[fromIndex+1] 191 | let firstTabView = self.tagViews.first 192 | let lastTabView = self.tagViews.last 193 | 194 | var moveCenterX = curTabView.center.x + (CGFloat(contentRatio) - CGFloat(fromIndex)) * (nextTabView.center.x - curTabView.center.x) 195 | 196 | if moveCenterX <= (firstTabView?.center.x)! { 197 | moveCenterX = (firstTabView?.center.x)! 198 | } else if moveCenterX >= (lastTabView?.center.x)! { 199 | moveCenterX = (lastTabView?.center.x)! 200 | } 201 | 202 | self.markView.center.x = moveCenterX 203 | } 204 | 205 | func scrollMarkView(index:NSInteger, animated:Bool) { 206 | if !self.markViewScroll { 207 | return 208 | } 209 | 210 | if index >= self.tagViews.count || index < 0 { 211 | return 212 | } 213 | 214 | 215 | let curTabView = self.tagViews[index] 216 | if (animated) { 217 | 218 | UIView.animate(withDuration: 0.3, animations:{[weak self] in 219 | 220 | if let weakSelf = self { 221 | weakSelf.markView.center.x = curTabView.center.x 222 | weakSelf.markView.frame.size.width = (weakSelf.tabDataSource?.markViewWidth?(index: index))! 223 | } 224 | }) 225 | } else { 226 | self.markView.center.x = curTabView.center.x 227 | self.markView.frame.size.width = (self.tabDataSource?.markViewWidth?(index: index))! 228 | } 229 | 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /Swift/SPPage/core/SPCoverController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPCoverController.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/11. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum CoverScrollStyle { 12 | case height 13 | case top 14 | } 15 | 16 | class SPCoverController: SPTabController,SPCoverDataSource { 17 | private var coverView:UIView? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | self.loadCoverView() 22 | } 23 | 24 | func preferCoverStyle() -> CoverScrollStyle { 25 | return .height 26 | } 27 | 28 | private func reloadCoverView() { 29 | self.coverView?.removeFromSuperview() 30 | self.loadCoverView() 31 | } 32 | 33 | override func reloadData() { 34 | super.reloadData() 35 | self.reloadCoverView() 36 | } 37 | 38 | private func loadCoverView() { 39 | if let coverView = self.preferCoverView() { 40 | coverView.frame = self.preferCoverFrame() 41 | self.view.addSubview(coverView) 42 | self.coverView = coverView 43 | } 44 | 45 | } 46 | 47 | //SPCoverProtocol 48 | func preferCoverView() -> UIView? { 49 | return nil 50 | } 51 | 52 | func preferCoverFrame() -> CGRect { 53 | return CGRect() 54 | } 55 | 56 | // SPPageControllerDelegate 57 | override func scrollWithPageOffset(pageOffset: CGFloat, index: NSInteger) { 58 | super.scrollWithPageOffset(pageOffset: pageOffset, index: index) 59 | 60 | let top = self.tabScrollTopWithContentOffset(offset: pageOffset + self.pageTopAtIndex(index: index)) 61 | 62 | let realOffset = self.preferTabFrame(index: index).origin.y - top 63 | 64 | if self.preferCoverStyle() == .height { 65 | let coverHeight = self.preferCoverFrame().size.height - realOffset 66 | if coverHeight >= 0 { 67 | coverView?.frame.size.height = coverHeight 68 | } else { 69 | coverView?.frame.size.height = 0 70 | } 71 | } else { 72 | let coverTop = self.preferCoverFrame().origin.y - realOffset 73 | if coverTop >= self.preferCoverFrame().origin.y - self.preferCoverFrame().size.height { 74 | coverView?.frame.origin.y = coverTop 75 | } else { 76 | coverView?.frame.origin.y = self.preferCoverFrame().origin.y - self.preferCoverFrame().size.height 77 | } 78 | } 79 | } 80 | 81 | // SPPageControllerDataSource 82 | 83 | override func pageTopAtIndex(index: NSInteger) -> CGFloat { 84 | let pageTop = super.pageTopAtIndex(index: index) 85 | let coverPageTop = self.preferCoverFrame().origin.y + self.preferCoverFrame().size.height 86 | return pageTop > coverPageTop ? pageTop :coverPageTop 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Swift/SPPage/core/SPPageController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPPageController.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/1. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let iPhoneX:Bool! = CGSize.init(width: 1125, height: 2436).equalTo((UIScreen.main.currentMode?.size)!) 12 | let kScreenWidth:CGFloat = UIScreen.main.bounds.size.width 13 | let kScreenHeight:CGFloat = UIScreen.main.bounds.size.height 14 | let kStatusBarHeight:CGFloat = iPhoneX ? 44.0:22.0 15 | let kNavigationAndStatusBarHeight:CGFloat = kStatusBarHeight + 44.0 16 | 17 | 18 | class SPPageController: UIViewController,UIScrollViewDelegate { 19 | 20 | weak var delegate:SPPageControllerDelegate? 21 | weak var dataSource:SPPageControllerDataSource? 22 | 23 | private(set) var currentPageIndex:NSInteger! 24 | private var memCacheDic = Dictionary() 25 | private var lastContentOffset = Dictionary() 26 | private var lastContentSize = Dictionary() 27 | private var scrollView:SPPageContentView! 28 | private var lastSelectedIndex:NSInteger! 29 | private var guessToIndex:NSInteger! 30 | private var originOffset:CGFloat! 31 | private var firstWillAppear:Bool! = true 32 | private var firstWillLayoutSubViews:Bool! = true 33 | private var firstDidAppear:Bool! = true 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | self.currentPageIndex = self.dataSource?.preferPageFirstAtIndex() 38 | self.lastSelectedIndex = self.currentPageIndex 39 | self.view.backgroundColor = UIColor.clear 40 | self.configScrollView() 41 | } 42 | 43 | override func viewWillAppear(_ animated: Bool) { 44 | super.viewWillAppear(animated) 45 | if self.firstWillAppear { 46 | self.delegate?.pageviewControllerWillLeave?(pageVC: self, fromVC: self.controller(index: self.lastSelectedIndex), toVC: self.controller(index: self.currentPageIndex)) 47 | 48 | if self.dataSource?.screenEdgePanGestureRecognizer?() != nil { 49 | self.scrollView.panGestureRecognizer.require(toFail: (self.dataSource?.screenEdgePanGestureRecognizer?())!) 50 | 51 | } else { 52 | if self.screenEdgePanGestureRecognizer() != nil { 53 | self.scrollView.panGestureRecognizer.require(toFail: self.screenEdgePanGestureRecognizer()!) 54 | } 55 | 56 | } 57 | 58 | self.firstWillAppear = false 59 | self.updateScrollViewLayoutIfNeed() 60 | } 61 | 62 | self.controller(index: self.currentPageIndex).beginAppearanceTransition(true, animated: animated) 63 | } 64 | 65 | override func viewDidAppear(_ animated: Bool) { 66 | super.viewDidAppear(animated) 67 | if self.firstDidAppear { 68 | self.delegate?.willChangeInit?() 69 | 70 | self.delegate?.pageviewControllerDidLeave?(pageVC: self, fromVC: self.controller(index: self.lastSelectedIndex), toVC: self.controller(index: self.currentPageIndex)) 71 | 72 | self.firstDidAppear = false 73 | } 74 | 75 | self.controller(index: self.currentPageIndex).endAppearanceTransition() 76 | } 77 | 78 | private func updateScrollViewLayoutIfNeed() { 79 | if self.scrollView.frame.size.width > 0 { 80 | self.scrollView.setItem(item: self.dataSource!) 81 | 82 | let newOffset:CGPoint = self.scrollView.calOffsetWithIndex(index: self.currentPageIndex) 83 | if !newOffset.equalTo(self.scrollView.contentOffset) { 84 | self.scrollView.contentOffset = newOffset 85 | } 86 | } 87 | } 88 | 89 | override func viewWillLayoutSubviews() { 90 | super.viewWillLayoutSubviews() 91 | 92 | if self.firstWillLayoutSubViews { 93 | self.updateScrollViewLayoutIfNeed() 94 | 95 | } 96 | } 97 | 98 | override func didReceiveMemoryWarning() { 99 | super.didReceiveMemoryWarning() 100 | 101 | } 102 | 103 | private func removeFromParentViewController(controller:UIViewController) { 104 | controller.willMove(toParentViewController: nil) 105 | controller.view.removeFromSuperview() 106 | controller.removeFromParentViewController() 107 | } 108 | 109 | private func pageCount() -> NSInteger { 110 | return (self.dataSource?.numberOfControllers())! 111 | } 112 | 113 | private func controller(index:NSInteger) -> UIViewController { 114 | if (self.memCacheDic[index] == nil) { 115 | let uiviewController = self.dataSource?.controller(index: index) 116 | if (uiviewController != nil) { 117 | if (self.dataSource?.isSubPageCanScrollForIndex?(index: index))! { 118 | uiviewController?.view.isHidden = false 119 | } else { 120 | uiviewController?.view.isHidden = true 121 | } 122 | 123 | self.bindController(subVC: uiviewController!, index: index) 124 | 125 | self.memCacheDic[index] = uiviewController 126 | self.addVisibleViewContorller(vc: uiviewController!, index: index) 127 | } 128 | 129 | 130 | 131 | } 132 | 133 | return self.memCacheDic[index]! 134 | } 135 | 136 | func addVisibleViewContorller(vc:UIViewController,index:NSInteger) { 137 | 138 | let childFrame = self.scrollView.calcVisibleViewControllerFrameWithIndex(index: index) 139 | self.addChildViewController(childController: vc, frame: childFrame) 140 | 141 | } 142 | 143 | func addChildViewController(childController:UIViewController,frame:CGRect) { 144 | if !self.childViewControllers.contains(childController) { 145 | self.addChildViewController(childController) 146 | self.didMove(toParentViewController: childController) 147 | childController.view.frame = frame 148 | self.scrollView.addSubview(childController.view) 149 | } 150 | } 151 | 152 | func reloadPage() { 153 | self.clearMemory() 154 | self.currentPageIndex = self.dataSource?.preferPageFirstAtIndex() 155 | self.updateScrollViewLayoutIfNeed() 156 | self.showPage(index: self.currentPageIndex, animated: true) 157 | 158 | } 159 | 160 | private func clearMemory () { 161 | for view in self.scrollView.subviews { 162 | view.removeFromSuperview() 163 | } 164 | 165 | self.lastContentSize.removeAll() 166 | self.lastContentOffset.removeAll() 167 | 168 | if self.memCacheDic.count > 0 { 169 | self.clearObserver() 170 | 171 | let tmpArray = [UIViewController](self.memCacheDic.values) 172 | self.memCacheDic.removeAll() 173 | for (vc) in tmpArray { 174 | self.removeFromParentViewController(controller: vc) 175 | } 176 | } 177 | 178 | } 179 | 180 | deinit { 181 | self.clearMemory() 182 | 183 | } 184 | 185 | private func clearObserver () { 186 | for (_, controller) in self.memCacheDic { 187 | if let tempVC = controller as? SPPageSubControllerDataSource { 188 | if let subScrollView = tempVC.preferScrollView() { 189 | subScrollView.removeObserver(self, forKeyPath: "contentOffset") 190 | } 191 | } 192 | } 193 | } 194 | 195 | private func configScrollView () { 196 | self.scrollView = SPPageContentView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) 197 | self.scrollView.delegate = self 198 | self.view.addSubview(self.scrollView) 199 | } 200 | 201 | func screenEdgePanGestureRecognizer() -> UIScreenEdgePanGestureRecognizer? { 202 | if let gestureRecognizers = self.navigationController?.view.gestureRecognizers { 203 | for recognizer:UIGestureRecognizer? in gestureRecognizers { 204 | if recognizer is UIScreenEdgePanGestureRecognizer { 205 | return recognizer as? UIScreenEdgePanGestureRecognizer 206 | } 207 | } 208 | } 209 | 210 | return nil 211 | } 212 | 213 | 214 | override var shouldAutomaticallyForwardAppearanceMethods: Bool { 215 | return false 216 | } 217 | 218 | // scrollViewDelegate 219 | 220 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 221 | if self.scrollView.isDragging { 222 | let offset = scrollView.contentOffset.x 223 | let width = scrollView.frame.size.width 224 | let lastGuessIndex = self.guessToIndex >= 0 ? self.guessToIndex:self.currentPageIndex 225 | if self.originOffset < offset { 226 | self.guessToIndex = NSInteger(ceil(offset/width)) 227 | } else { 228 | self.guessToIndex = NSInteger( floor(offset/width)) 229 | } 230 | 231 | let maxCount = self.pageCount() 232 | 233 | if ((self.guessToIndex != self.currentPageIndex && !scrollView.isDecelerating) || scrollView.isDecelerating) && lastGuessIndex != self.guessToIndex && self.guessToIndex >= 0 && self.guessToIndex < maxCount { 234 | 235 | self.delegate?.willChangeInit?() 236 | 237 | self.delegate?.pageviewControllerWillTransition?(pageVC: self, fromVC: self.controller(index: self.guessToIndex), toVC: self.controller(index: self.currentPageIndex)) 238 | 239 | if (self.dataSource?.isPreLoad?())! { 240 | self.controller(index: self.guessToIndex).beginAppearanceTransition(true, animated: true) 241 | 242 | if lastGuessIndex == self.currentPageIndex { 243 | self.controller(index: self.currentPageIndex).beginAppearanceTransition(false, animated: true) 244 | } 245 | 246 | if lastGuessIndex != self.currentPageIndex && 247 | lastGuessIndex! >= 0 && 248 | lastGuessIndex! < maxCount{ 249 | self.controller(index: lastGuessIndex!).beginAppearanceTransition(false, animated: true) 250 | self.controller(index: lastGuessIndex!).endAppearanceTransition() 251 | } 252 | } 253 | } 254 | self.delegate?.scrollViewContentOffsetWithRatio?(ratio: scrollView.contentOffset.x/scrollView.frame.size.width, draging: true) 255 | 256 | } 257 | } 258 | 259 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 260 | if !scrollView.isDecelerating { 261 | self.originOffset = scrollView.contentOffset.y 262 | self.guessToIndex = self.currentPageIndex 263 | } 264 | } 265 | 266 | func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 267 | self.delegate?.scrollViewContentOffsetWithRatio?(ratio: targetContentOffset.pointee.x/scrollView.frame.size.width, draging: false) 268 | } 269 | 270 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 271 | self.updatePageAfterDraging() 272 | } 273 | 274 | private func updatePageAfterDraging () { 275 | let newIndex:NSInteger = self.scrollView.calcIndex() 276 | let oldIndex:NSInteger = self.currentPageIndex 277 | 278 | self.currentPageIndex = newIndex 279 | self.lastSelectedIndex = self.currentPageIndex 280 | 281 | let newVc = self.controller(index: newIndex) 282 | let oldVC = self.controller(index: oldIndex) 283 | if newIndex == oldIndex { 284 | if self.guessToIndex >= 0 && self.guessToIndex < self.pageCount() && (self.dataSource?.isPreLoad?())!{ 285 | oldVC.beginAppearanceTransition(true, animated: true) 286 | oldVC.endAppearanceTransition() 287 | newVc.beginAppearanceTransition(false, animated: true) 288 | newVc.endAppearanceTransition() 289 | } 290 | } else { 291 | if !(self.dataSource?.isPreLoad?())! { 292 | oldVC.beginAppearanceTransition(false, animated: true) 293 | newVc.beginAppearanceTransition(true, animated: true) 294 | } 295 | oldVC.endAppearanceTransition() 296 | newVc.endAppearanceTransition() 297 | } 298 | 299 | self.originOffset = self.scrollView.contentOffset.x 300 | self.guessToIndex = self.currentPageIndex 301 | 302 | self.delegate?.pageviewControllerDidTransition?(pageVC: self, fromVC: self.controller(index: self.lastSelectedIndex), toVC: newVc) 303 | } 304 | 305 | //非交互切换 306 | 307 | func showPage(index:NSInteger, animated:Bool) { 308 | if index < 0 || index >= self.pageCount() { 309 | return 310 | } 311 | 312 | if self.scrollView.frame.size.width > 0 && self.scrollView.contentSize.width > 0 { 313 | 314 | 315 | let scrollBeginAnimation = { [weak self]() -> Void in 316 | 317 | if let weakSelf = self { 318 | weakSelf.controller(index: weakSelf.currentPageIndex).beginAppearanceTransition(true, animated: animated) 319 | 320 | if weakSelf.currentPageIndex != weakSelf.lastSelectedIndex { 321 | weakSelf.controller(index: weakSelf.lastSelectedIndex).beginAppearanceTransition(false, animated: animated) 322 | } 323 | } 324 | 325 | } 326 | 327 | let scrollAnimationing = { [weak self]() -> Void in 328 | 329 | if let weakSelf = self { 330 | weakSelf.scrollView.setContentOffset(weakSelf.scrollView.calOffsetWithIndex(index: weakSelf.currentPageIndex), animated: false) 331 | 332 | } 333 | 334 | } 335 | 336 | let scrollEndAnimation = { [weak self]() -> Void in 337 | 338 | if let weakSelf = self { 339 | weakSelf.controller(index: weakSelf.currentPageIndex).endAppearanceTransition() 340 | 341 | if weakSelf.currentPageIndex != weakSelf.lastSelectedIndex { 342 | weakSelf.controller(index: weakSelf.lastSelectedIndex).endAppearanceTransition() 343 | } 344 | 345 | weakSelf.delegate?.pageviewControllerDidLeave?(pageVC: weakSelf, fromVC: weakSelf.controller(index: weakSelf.lastSelectedIndex), toVC: weakSelf.controller(index: weakSelf.currentPageIndex)) 346 | } 347 | 348 | } 349 | 350 | self.delegate?.willChangeInit?() 351 | 352 | self.lastSelectedIndex = self.currentPageIndex 353 | self.currentPageIndex = index 354 | let currentVC:UIViewController = self.controller(index: self.currentPageIndex) 355 | let lastSelectedVC:UIViewController = self.controller(index: self.lastSelectedIndex) 356 | 357 | self.delegate?.pageviewControllerWillLeave?(pageVC: self, fromVC: lastSelectedVC, toVC: currentVC) 358 | scrollBeginAnimation() 359 | 360 | if (animated) { 361 | 362 | if self.lastSelectedIndex == self.currentPageIndex { 363 | scrollAnimationing() 364 | scrollEndAnimation() 365 | return 366 | } 367 | 368 | let oldSelectedIndex:NSInteger! = self.lastSelectedIndex 369 | let oldSeletedVC:UIViewController = self.controller(index: oldSelectedIndex) 370 | 371 | let lastView:UIView = lastSelectedVC.view 372 | let currentView:UIView = currentVC.view 373 | let oldSelectedView:UIView = oldSeletedVC.view 374 | 375 | let backgroundIndex = self.scrollView.calcIndex() 376 | var backgroundView:UIView? = nil 377 | 378 | 379 | if let oldAnitmatonKey = oldSelectedView.layer.animationKeys(), let lastAnimationKey = lastView.layer.animationKeys() { 380 | 381 | if oldAnitmatonKey.count > 0 && lastAnimationKey.count > 0 && backgroundIndex != self.currentPageIndex && backgroundIndex != self.lastSelectedIndex { 382 | backgroundView = self.controller(index: backgroundIndex).view 383 | backgroundView?.isHidden = true 384 | } 385 | } 386 | 387 | //初始还原 388 | self.scrollView.layer.removeAllAnimations() 389 | oldSelectedView.layer.removeAllAnimations() 390 | lastView.layer.removeAllAnimations() 391 | currentView.layer.removeAllAnimations() 392 | 393 | self.moveBackToOriginPosition(view: oldSelectedView, index: oldSelectedIndex) 394 | 395 | self.scrollView.bringSubview(toFront: lastView) 396 | self.scrollView.bringSubview(toFront: currentView) 397 | 398 | lastView.isHidden = false 399 | currentView.isHidden = false 400 | 401 | let lastViewStartOrigin = lastView.frame.origin 402 | var currentViewStartOrigin = lastViewStartOrigin 403 | 404 | let offset = self.lastSelectedIndex < self.currentPageIndex ? self.scrollView.frame.size.width:-self.scrollView.frame.size.width 405 | currentViewStartOrigin.x += offset 406 | 407 | var lastViewAnimationOrigin = lastViewStartOrigin 408 | lastViewAnimationOrigin.x -= offset 409 | let currentViewAnimationOrigin = lastViewStartOrigin 410 | let lastViewEndOrigin = lastViewStartOrigin 411 | let currentViewEndOrigin = currentView.frame.origin 412 | let pageSize:CGSize = self.scrollView.frame.size 413 | 414 | lastView.frame = CGRect.init(origin: lastViewStartOrigin, size: pageSize) 415 | currentView.frame = CGRect.init(origin: currentViewStartOrigin, size: pageSize) 416 | 417 | 418 | UIView.animate(withDuration: 0.3, animations: { 419 | lastView.frame = CGRect.init(origin: lastViewAnimationOrigin, size: pageSize) 420 | currentView.frame = CGRect.init(origin: currentViewAnimationOrigin, size: pageSize) 421 | }, completion: {[weak self] (finished) in 422 | 423 | if finished { 424 | lastView.frame = CGRect.init(origin: lastViewEndOrigin, size: pageSize) 425 | currentView.frame = CGRect.init(origin: currentViewEndOrigin, size: pageSize) 426 | 427 | backgroundView?.isHidden = false 428 | 429 | if let weakSelf = self { 430 | weakSelf.moveBackToOriginPosition(view: currentView, index: weakSelf.currentPageIndex) 431 | weakSelf.moveBackToOriginPosition(view: lastView, index: weakSelf.lastSelectedIndex) 432 | } 433 | 434 | scrollAnimationing() 435 | scrollEndAnimation() 436 | 437 | } 438 | 439 | }) 440 | 441 | } else { 442 | scrollAnimationing() 443 | scrollEndAnimation() 444 | } 445 | 446 | 447 | 448 | 449 | } 450 | } 451 | 452 | private func moveBackToOriginPosition(view:UIView, index:NSInteger) { 453 | if index < 0 || index >= self.pageCount() { 454 | return 455 | } 456 | 457 | let originPosition = self.scrollView.calOffsetWithIndex(index: index) 458 | if view.frame.origin.x != originPosition.x { 459 | view.frame = CGRect.init(origin: originPosition, size: view.frame.size) 460 | } 461 | } 462 | 463 | //KVO 464 | private func bindController(subVC:UIViewController, index:NSInteger) { 465 | 466 | if let tempVC = subVC as? SPPageSubControllerDataSource { 467 | if let subScrollView = tempVC.preferScrollView() { 468 | subScrollView.scrollsToTop = false 469 | subScrollView.tag = index 470 | if let pageTop = self.dataSource?.pageTopAtIndex?(index: index) { 471 | subScrollView.contentInset.top = pageTop 472 | } 473 | 474 | if #available(OSX 11.0, *) {// ios11 苹果加了一个安全区域 会自动修改scrollView的contentOffset 475 | subScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.never; 476 | } 477 | 478 | subScrollView.addObserver(self, forKeyPath: "contentOffset", options: [.new,.initial], context: nil) 479 | subScrollView.contentOffset.x = -scrollView.contentInset.top 480 | } 481 | } 482 | 483 | } 484 | 485 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 486 | if let scrollView = object as? UIScrollView { 487 | if keyPath == "contentOffset" { 488 | if scrollView.tag != self.currentPageIndex { 489 | return 490 | } 491 | 492 | if self.memCacheDic.count == 0 { 493 | return 494 | } 495 | 496 | let index = scrollView.tag 497 | 498 | 499 | let isNotNeedChangeContentOffset:Bool = scrollView.contentSize.height < kScreenHeight-kNavigationAndStatusBarHeight && fabs(self.lastContentSize[index] ?? 0 - scrollView.contentSize.height) > 1.0 500 | 501 | if self.delegate?.cannotScrollWithPageOffset?() != nil && (self.delegate?.cannotScrollWithPageOffset?())! || isNotNeedChangeContentOffset { 502 | if let contentOffset = self.lastContentOffset[index] { 503 | scrollView.contentOffset.y = contentOffset 504 | } 505 | } else { 506 | self.lastContentOffset[index] = scrollView.contentOffset.y 507 | self.delegate?.scrollWithPageOffset?(pageOffset: scrollView.contentOffset.y, index: index) 508 | } 509 | 510 | self.lastContentSize[index] = scrollView.contentSize.height 511 | 512 | } 513 | 514 | } 515 | } 516 | 517 | //indexOfController 518 | 519 | func indexOf(vc:UIViewController) -> Int { 520 | for (key,tempVc) in self.memCacheDic { 521 | if vc == tempVc { 522 | return key 523 | } 524 | } 525 | 526 | return -1 527 | } 528 | 529 | } 530 | -------------------------------------------------------------------------------- /Swift/SPPage/core/SPTabController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPTabController.swift 3 | // SPPage 4 | // 5 | // Created by GodDan on 2018/3/11. 6 | // Copyright © 2018年 GodDan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SPTabController : UIViewController,SPPageControllerDelegate,SPPageControllerDataSource,SPTabDelegate,SPTabDataSource { 12 | 13 | var maxYPullDown:CGFloat = kScreenHeight 14 | var minYPullUp: CGFloat = 64 15 | 16 | private var tabView:UIView? 17 | private var pageVC:SPPageController! 18 | private var _cannotScrollWithPageOffset:Bool = false 19 | 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | self.setupPage() 24 | self.setupTabBar() 25 | } 26 | 27 | override func viewWillAppear(_ animated: Bool) { 28 | super.viewWillAppear(animated) 29 | self._cannotScrollWithPageOffset = false 30 | self.pageVC.beginAppearanceTransition(true, animated: animated) 31 | } 32 | 33 | override func viewDidAppear(_ animated: Bool) { 34 | super.viewDidAppear(animated) 35 | self.pageVC.endAppearanceTransition() 36 | } 37 | 38 | override func viewWillDisappear(_ animated: Bool) { 39 | super.viewWillDisappear(animated) 40 | self._cannotScrollWithPageOffset = true 41 | self.pageVC.beginAppearanceTransition(false, animated: animated) 42 | } 43 | 44 | override func viewDidDisappear(_ animated: Bool) { 45 | super.viewDidDisappear(animated) 46 | self.pageVC.endAppearanceTransition() 47 | } 48 | 49 | func preferSingleTabNotShow() -> Bool { 50 | return false 51 | } 52 | 53 | func customView() -> UIView? { 54 | return nil 55 | } 56 | 57 | func reloadData() { 58 | self.reloadPage() 59 | self.reloadTab() 60 | } 61 | 62 | private func reloadTab() { 63 | self.tabView?.removeFromSuperview() 64 | self.tabView = nil 65 | self.setupTabBar() 66 | } 67 | 68 | private func reloadPage() { 69 | self.pageVC.view.frame = self.preferPageFrame() 70 | self.pageVC.reloadPage() 71 | } 72 | 73 | private func setupPage() { 74 | self.pageVC = SPPageController() 75 | self.pageVC.dataSource = self 76 | self.pageVC.delegate = self 77 | 78 | self.pageVC.view.frame = self.preferPageFrame() 79 | self.addChildViewController(self.pageVC) 80 | self.view.addSubview(self.pageVC.view) 81 | self.pageVC.didMove(toParentViewController: self) 82 | 83 | } 84 | 85 | private func setupTabBar() { 86 | if self.preferSingleTabNotShow() && self.numberOfControllers() <= 1 { 87 | return 88 | } 89 | 90 | self.tabView = self.customView() ?? SPTagBarScrollView(frame: self.preferTabFrame(index: self.pageVC.currentPageIndex), tabDelagate: self, tabDataSource: self) 91 | 92 | self.view.addSubview(self.tabView!) 93 | } 94 | 95 | private func scrollViewOffset(index:NSInteger) -> CGFloat { 96 | return self.preferTabFrame(index: index).origin.y - (self.tabView?.frame.origin.y)! - self.pageTopAtIndex(index:index) 97 | } 98 | 99 | private func tabDragWithOffset(offset:CGFloat) { 100 | self.tabView?.frame.origin.y = self.tabScrollTopWithContentOffset(offset: offset) 101 | } 102 | 103 | 104 | func tabScrollTopWithContentOffset(offset:CGFloat) -> CGFloat { 105 | var top:CGFloat = self.preferTabFrame(index: self.pageVC.currentPageIndex).origin.y - offset 106 | if offset >= 0 { 107 | if top <= self.minYPullUp { 108 | top = self.minYPullUp 109 | } 110 | } else { 111 | if top >= self.maxYPullDown { 112 | top = self.maxYPullDown 113 | } 114 | } 115 | 116 | return top 117 | } 118 | 119 | private func changeToSubControllerOffset(toVc:UIViewController?, isDelay:Bool) { 120 | if toVc == nil || self.numberOfControllers() <= 1 { 121 | self._cannotScrollWithPageOffset = false 122 | return 123 | } 124 | 125 | if toVc is SPPageSubControllerDataSource { 126 | if let tempVC = toVc as? SPPageSubControllerDataSource { 127 | let newIndex = self.pageVC.indexOf(vc:tempVC as! UIViewController) 128 | let pageTop = self.pageTopAtIndex(index: newIndex) 129 | let top = self.tabScrollTopWithContentOffset(offset: (tempVC.preferScrollView()?.contentOffset.y)!+pageTop) 130 | if fabs(top-(self.tabView?.frame.origin.y)!) > 0.1 { 131 | let scrollOffset = self.scrollViewOffset(index: newIndex) 132 | self._cannotScrollWithPageOffset = false 133 | tempVC.preferScrollView()?.contentOffset.y = scrollOffset 134 | } else { 135 | self._cannotScrollWithPageOffset = false 136 | } 137 | 138 | } 139 | } else { 140 | self._cannotScrollWithPageOffset = false 141 | 142 | } 143 | } 144 | 145 | // SPPageDataSource 146 | 147 | func pageTopAtIndex(index: NSInteger) -> CGFloat { 148 | return self.preferTabFrame(index: index).origin.y + self.preferTabFrame(index: index).size.height - self.preferPageFrame().origin.y 149 | } 150 | 151 | func screenEdgePanGestureRecognizer() -> UIScreenEdgePanGestureRecognizer? { 152 | 153 | if let gestureRecognizers = self.navigationController?.view.gestureRecognizers { 154 | for recognizer:UIGestureRecognizer? in gestureRecognizers { 155 | if recognizer is UIScreenEdgePanGestureRecognizer { 156 | return recognizer as? UIScreenEdgePanGestureRecognizer 157 | } 158 | } 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func preferPageFirstAtIndex() -> NSInteger { 165 | 166 | return 0 167 | } 168 | 169 | func controller(index: NSInteger) -> UIViewController { 170 | return UIViewController.init() 171 | } 172 | 173 | func numberOfControllers() -> NSInteger { 174 | return 0 175 | } 176 | 177 | func preferPageFrame() -> CGRect { 178 | return CGRect.init(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight) 179 | } 180 | 181 | func isPreLoad() -> Bool { 182 | return false 183 | } 184 | 185 | func isSubPageCanScrollForIndex(index: NSInteger) -> Bool { 186 | return true 187 | } 188 | 189 | // SPTabDataSource 190 | func title(index: NSInteger) -> String { 191 | return "" 192 | } 193 | 194 | func numberOfTab() -> NSInteger { 195 | return self.numberOfControllers() 196 | } 197 | 198 | func preferTabFrame(index: NSInteger) -> CGRect { 199 | return CGRect.init(x: 0, y: 0, width: kScreenWidth, height: 40) 200 | } 201 | 202 | func tabTopForIndex(index: NSInteger) -> CGFloat { 203 | return 0 204 | } 205 | 206 | func tabWidth(index: NSInteger) -> CGFloat { 207 | if self.title(index: index).count <= 3 { 208 | return 73 209 | } else { 210 | return 105 211 | } 212 | } 213 | 214 | func titleColor(index: NSInteger) -> UIColor { 215 | return UIColor.black 216 | } 217 | 218 | func titleHighlightColor(index: NSInteger) -> UIColor { 219 | return UIColor.orange 220 | } 221 | 222 | func titleFont(index: NSInteger) -> UIFont { 223 | return UIFont.systemFont(ofSize: 13.0) 224 | } 225 | 226 | func tabBackgroundColor() -> UIColor { 227 | return UIColor.white 228 | } 229 | 230 | func preferTabIndex() -> NSInteger { 231 | return self.preferPageFirstAtIndex() 232 | } 233 | 234 | func needMarkView() -> Bool { 235 | return true 236 | } 237 | 238 | func markViewBottom(index: NSInteger) -> CGFloat { 239 | return self.preferTabFrame(index: self.pageVC.currentPageIndex).size.height - 7 240 | } 241 | 242 | func markViewColor(index: NSInteger) -> UIColor { 243 | return UIColor.orange 244 | } 245 | 246 | func markViewWidth(index: NSInteger) -> CGFloat { 247 | let text = NSString (string: self.title(index: index)) 248 | let rect = text.boundingRect(with: CGSize.init(width: 20000, height: 40), options: [.usesFontLeading,.usesLineFragmentOrigin] , attributes: [NSAttributedStringKey.font:self.titleFont(index: index)], context: nil) 249 | return rect.size.width 250 | } 251 | 252 | func tabCanPress(index: NSInteger) -> Bool { 253 | return true 254 | } 255 | 256 | // SPTabDelegate 257 | func didPressTab(index: NSInteger) { 258 | if self.isSubPageCanScrollForIndex(index: index) { 259 | self.pageVC.showPage(index: index, animated: true) 260 | } 261 | } 262 | 263 | //SPPageControllerDelegate 264 | func willChangeInit() { 265 | self._cannotScrollWithPageOffset = true 266 | } 267 | 268 | func scrollViewContentOffsetWithRatio(ratio: CGFloat, draging: Bool) { 269 | if self.tabView is SPTagBarScrollView { 270 | if !draging { 271 | UIView.animate(withDuration: 0.3, animations: {[weak self] in 272 | if let weakSelf = self { 273 | if let tabView = weakSelf.tabView as? SPTagBarScrollView { 274 | tabView.markViewScroll(index: NSInteger(ratio)) 275 | tabView.markViewScroll = false 276 | } 277 | 278 | } 279 | }, completion: { [weak self](finished:Bool) in 280 | if let weakSelf = self { 281 | if let tabView = weakSelf.tabView as? SPTagBarScrollView { 282 | tabView.markViewScroll = true 283 | tabView.reloadHighlight(index: NSInteger(floor(ratio+0.5))) 284 | } 285 | } 286 | }) 287 | } else { 288 | if let tabView = self.tabView as? SPTagBarScrollView { 289 | tabView.markViewScroll(contentRatio: Double(ratio)) 290 | tabView.reloadHighlight(index: NSInteger(floor(ratio+0.5))) 291 | } 292 | } 293 | } 294 | } 295 | 296 | func scrollWithPageOffset(pageOffset: CGFloat, index: NSInteger) { 297 | self.tabDragWithOffset(offset: pageOffset + self.pageTopAtIndex(index: index)) 298 | } 299 | 300 | func pageviewControllerWillLeave(pageVC: SPPageController, fromVC: UIViewController, toVC: UIViewController) { 301 | self.changeToSubControllerOffset(toVc: toVC, isDelay: false) 302 | } 303 | 304 | func pageviewControllerDidLeave(pageVC: SPPageController, fromVC: UIViewController, toVC: UIViewController) { 305 | self.pageviewControllerDidChange(fromVC: fromVC, toVC: toVC) 306 | 307 | } 308 | 309 | func pageviewControllerWillTransition(pageVC: SPPageController, fromVC: UIViewController, toVC: UIViewController) { 310 | self.changeToSubControllerOffset(toVc: toVC, isDelay: false) 311 | } 312 | 313 | func pageviewControllerDidTransition(pageVC: SPPageController, fromVC: UIViewController, toVC: UIViewController) { 314 | self.pageviewControllerDidChange(fromVC: fromVC, toVC: toVC) 315 | if let tempView = self.tabView as? SPTagBarScrollView { 316 | tempView.scrollTab(index: self.pageVC.currentPageIndex) 317 | } 318 | } 319 | 320 | func pageviewControllerDidChange(fromVC: UIViewController, toVC: UIViewController) { 321 | if let tempVC = fromVC as? SPPageSubControllerDataSource { 322 | tempVC.preferScrollView()?.scrollsToTop = false 323 | } 324 | 325 | if let tempVC = toVC as? SPPageSubControllerDataSource { 326 | tempVC.preferScrollView()?.scrollsToTop = false 327 | } 328 | 329 | self.changeToSubControllerOffset(toVc: toVC, isDelay: true) 330 | } 331 | 332 | func cannotScrollWithPageOffset() -> Bool { 333 | return self._cannotScrollWithPageOffset 334 | } 335 | } 336 | --------------------------------------------------------------------------------