├── 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 |
--------------------------------------------------------------------------------