├── LNCustomScrollView
├── LNCustomScrollView
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── fulian.imageset
│ │ │ ├── fulian-1.jpg
│ │ │ ├── fulian-2.jpg
│ │ │ ├── fulian.jpg
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Main
│ │ ├── DemoItemObj.m
│ │ ├── MainViewController.h
│ │ ├── DemoItemCollectionViewCell.h
│ │ ├── DemoItemObj.h
│ │ ├── DemoItemCollectionViewCell.m
│ │ └── MainViewController.m
│ ├── AppDelegate.h
│ ├── Demo
│ │ ├── Momentum
│ │ │ ├── LNMomentumObj.m
│ │ │ ├── LNVelocityDynamicItem.m
│ │ │ ├── LNVelocityDynamicItem.h
│ │ │ ├── LNMomentumObj.h
│ │ │ ├── LNMomentumImpulser.h
│ │ │ ├── LNRestMomentumDetector.h
│ │ │ ├── LNRestMomentumDetector.m
│ │ │ └── LNMomentumImpulser.m
│ │ ├── CustomScrollViewViewController.h
│ │ ├── BouncesObservationViewController.h
│ │ ├── BouncesOptimizationViewController.h
│ │ ├── DecelerateObservationViewController.h
│ │ ├── PanGestureObservationViewController.h
│ │ ├── CommonCell
│ │ │ ├── DemoCommonCell.h
│ │ │ └── DemoCommonCell.m
│ │ ├── BouncesOptimization
│ │ │ ├── BouncesOptimizationDataList.h
│ │ │ ├── BouncesOptimizationTrainer.h
│ │ │ ├── BouncesOptimizationDataList.m
│ │ │ └── BouncesOptimizationTrainer.m
│ │ ├── MomentumTransmitViewController.h
│ │ ├── CustomScrollViewViewController.m
│ │ ├── DecelerateObservationViewController.m
│ │ ├── BouncesObservationViewController.m
│ │ ├── PanGestureObservationViewController.m
│ │ ├── MomentumTransmitViewController.m
│ │ └── BouncesOptimizationViewController.m
│ ├── CustomScrollView
│ │ ├── LNCustomScrollView.h
│ │ ├── LNCustomScrollViewClockProxy.h
│ │ ├── LNCustomScrollViewClock.h
│ │ ├── LNCustomScrollViewClockProxy.m
│ │ ├── LNCustomScrollViewClock.m
│ │ └── LNCustomScrollView.m
│ ├── main.m
│ ├── AppDelegate.m
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── LNCustomScrollView.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
├── LNCustomScrollViewTests
│ ├── Info.plist
│ └── LNCustomScrollViewTests.m
└── LNCustomScrollViewUITests
│ ├── Info.plist
│ └── LNCustomScrollViewUITests.m
├── README.md
├── LICENSE
└── .gitignore
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LevisonNN/LNCustomScrollView/HEAD/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian-1.jpg
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LevisonNN/LNCustomScrollView/HEAD/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian-2.jpg
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LevisonNN/LNCustomScrollView/HEAD/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/fulian.jpg
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/DemoItemObj.m:
--------------------------------------------------------------------------------
1 | //
2 | // DemoItemObj.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "DemoItemObj.h"
10 |
11 | @implementation DemoItemObj
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | //
7 |
8 | #import
9 |
10 | @interface AppDelegate : UIResponder
11 |
12 | @property (nonatomic, strong) UIWindow *window;
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNMomentumObj.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNMomentumObj.m
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import "LNMomentumObj.h"
9 |
10 | @implementation LNMomentumObj
11 |
12 | - (float)momentumValue
13 | {
14 | return self.mass * self.velocityValue;
15 | }
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/MainViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MainViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/CustomScrollViewViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // CustomScrollViewViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface CustomScrollViewViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LNCustomScrollView
2 | A ScrollView implemented with UIView. Not for production ,just for fun. And provides demos to coculate those dynamic parameters.
3 | #### [掘金地址](https://juejin.cn/post/6947552980090486814)
4 | #### 一个用于展示iOS的UIScrollView运动特性的Demo,包含了观察Decelerate、Bounces、PanGesture的Demo,一个用于优化Bounces参数的Demo,和最终做好的CustomScrollView。
5 | #### 没有使用动画库,例如:UIDynamic、POP、UIViewAnimation,只使用了一个UIView、一个PanGesture、一个CADisplayLink,运动数值均为计算得出。
6 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesObservationViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesObservationViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface BouncesObservationViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimizationViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface BouncesOptimizationViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/DecelerateObservationViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DecelerateObservationViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface DecelerateObservationViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/PanGestureObservationViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // PanGestureObservationViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface PanGestureObservationViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNVelocityDynamicItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNVelocityDynamicItem.m
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import "LNVelocityDynamicItem.h"
9 |
10 | @implementation LNVelocityDynamicItem
11 |
12 | - (instancetype)init {
13 | self = [super init];
14 | if (self) {
15 | _bounds = CGRectMake(0, 0, 1, 1);
16 | }
17 | return self;
18 | }
19 |
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollView.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollView.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface LNCustomScrollView : UIView
14 |
15 | @property (nonatomic, assign) CGSize contentSize;
16 |
17 | @property (nonatomic, assign) BOOL bounces;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/CommonCell/DemoCommonCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // DemoCommonCell.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | extern NSString * const kDemoCommonCell;
14 |
15 | @interface DemoCommonCell : UICollectionViewCell
16 |
17 | @property (nonatomic, strong, readonly) UILabel *titleLabel;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/fulian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "fulian.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "fulian-1.jpg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "fulian-2.jpg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | //
7 |
8 | #import
9 | #import "AppDelegate.h"
10 |
11 | int main(int argc, char * argv[]) {
12 | NSString * appDelegateClassName;
13 | @autoreleasepool {
14 | // Setup code that might create autoreleased objects goes here.
15 | appDelegateClassName = NSStringFromClass([AppDelegate class]);
16 | }
17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName);
18 | }
19 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/DemoItemCollectionViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // DemoItemCollectionViewCell.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DemoItemObj.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | extern NSString *const kDemoItemCollectionViewCell;
15 |
16 | @interface DemoItemCollectionViewCell : UICollectionViewCell
17 |
18 | - (void)setItemObj:(DemoItemObj *)item;
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNVelocityDynamicItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNVelocityDynamicItem.h
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface LNVelocityDynamicItem : NSObject
14 |
15 | @property (nonatomic, readwrite) CGPoint center;
16 | @property (nonatomic, readonly) CGRect bounds;
17 | @property (nonatomic, readwrite) CGAffineTransform transform;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollViewClockProxy.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewClockProxy.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface LNCustomScrollViewClockProxy : NSProxy
14 |
15 | @property (nullable, nonatomic, weak, readonly) id target;
16 | - (instancetype)initWithTarget:(id)target;
17 | + (instancetype)proxyWithTarget:(id)target;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimization/BouncesOptimizationDataList.h:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationDataList.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface BouncesOptimizationData : NSObject
14 |
15 | @property (nonatomic, copy) NSString *title;
16 |
17 | @property (nonatomic, copy) NSArray *dataArray;
18 |
19 | @end
20 |
21 | @interface BouncesOptimizationDataList : NSObject
22 |
23 | @property (nonatomic, strong) NSMutableArray *dataMArray;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/DemoItemObj.h:
--------------------------------------------------------------------------------
1 | //
2 | // DemoItemObj.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | typedef NS_ENUM(NSInteger, DemoType)
14 | {
15 | DemoTypeDecelerateObservation,
16 | DemoTypeBouncesObservation,
17 | DemoTypePanGestureObservation,
18 | DemoTypeOptimization,
19 | DemoTypeCustomScrollView,
20 | DemoTypeMomentumTransmit,
21 | };
22 |
23 | @interface DemoItemObj : NSObject
24 |
25 | @property (nonatomic, assign) DemoType type;
26 |
27 | @property (nonatomic, copy) NSString *title;
28 |
29 | @end
30 |
31 | NS_ASSUME_NONNULL_END
32 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/MomentumTransmitViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MomentumTransmitViewController.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 17.7.22.
6 | // Copyright © 2022 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | //横向的UICollectionView(v1)的右侧和纵向UICollectionView(v2)的顶部进行了连接
14 | //v1的向右的剩余速度会转成v2向下的剩余速度。反之,v2的向上剩余速度会转成v1向左的剩余速度。
15 | //如果你需要两个纵向的列表v1嵌套v2,只做单向的就可以,将v1撞击底部的剩余速度转成v2向下的剩余速度
16 | //因为视图方向不一致,所以需要serializer过滤器将不同方向的速度转换一下。
17 | //我们强制加上的效果优先级总是最低的任何用户操作/函数调用都会让这个失效,所以不用担心它对其他效果造成影响。
18 | //它要求UIScrollView的bounces=NO,因为一份能量不能转化为两份(被bounces里的阻尼消耗+传递给其他视图)。
19 |
20 |
21 | @interface MomentumTransmitViewController : UIViewController
22 |
23 | @end
24 |
25 | NS_ASSUME_NONNULL_END
26 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollViewClock.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewClock.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @protocol LNCustomScrollViewClockProtocol
14 |
15 | - (void)customScrollViewClockUpdateTimeInterval:(NSTimeInterval)time;
16 |
17 | @end
18 |
19 | @interface LNCustomScrollViewClock : NSObject
20 |
21 | + (instancetype)shareInstance;
22 |
23 | - (void)addObject:(id )obj;
24 |
25 | @property (nonatomic, assign, readonly) BOOL isPaused;
26 |
27 | - (void)startOrResume;
28 | - (void)pause;
29 | - (void)stop;
30 |
31 | @end
32 |
33 | NS_ASSUME_NONNULL_END
34 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNMomentumObj.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNMomentumObj.h
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | //只有上下左右,其他方向可以用CGPoint改成单位向量表示
13 | typedef NS_ENUM(NSInteger, LNMomentumDirection)
14 | {
15 | LNMomentumDirectionLeft,
16 | LNMomentumDirectionRight,
17 | LNMomentumDirectionTop,
18 | LNMomentumDirectionBottom
19 | };
20 |
21 | @interface LNMomentumObj : NSObject
22 |
23 | @property (nonatomic, assign) float velocityValue;
24 |
25 | @property (nonatomic, assign) float mass;
26 |
27 | @property (nonatomic, assign, readonly) float momentumValue;
28 |
29 | @property (nonatomic, assign) LNMomentumDirection direction;
30 |
31 |
32 | @end
33 |
34 | NS_ASSUME_NONNULL_END
35 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollViewTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollViewUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollViewTests/LNCustomScrollViewTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewTests.m
3 | // LNCustomScrollViewTests
4 | //
5 | // Created by Levison on 5.4.21.
6 | //
7 |
8 | #import
9 |
10 | @interface LNCustomScrollViewTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation LNCustomScrollViewTests
15 |
16 | - (void)setUp {
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | }
19 |
20 | - (void)tearDown {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | }
23 |
24 | - (void)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 | - (void)testPerformanceExample {
30 | // This is an example of a performance test case.
31 | [self measureBlock:^{
32 | // Put the code you want to measure the time of here.
33 | }];
34 | }
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 LevisonNN
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 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | //
7 |
8 | #import "AppDelegate.h"
9 | #import "MainViewController.h"
10 | #import "LNCustomScrollViewClock.h"
11 |
12 |
13 | @interface AppDelegate ()
14 |
15 | @end
16 |
17 | @implementation AppDelegate
18 |
19 |
20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
21 |
22 | [[LNCustomScrollViewClock shareInstance] startOrResume];
23 |
24 | _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
25 | _window.backgroundColor = [UIColor whiteColor];
26 | MainViewController *mainController = [[MainViewController alloc] init];
27 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:mainController];
28 | nav.navigationBar.translucent = NO;
29 | _window.rootViewController = nav;
30 | [_window makeKeyAndVisible];
31 |
32 | // Override point for customization after application launch.
33 | return YES;
34 | }
35 |
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNMomentumImpulser.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNMomentumImpulser.h
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import
9 | #import "LNMomentumObj.h"
10 | #import
11 | #import "LNRestMomentumDetector.h"
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @class LNMomentumImpulser;
16 |
17 | @protocol LNMomentumImpulserSerializer
18 |
19 | @optional
20 | - (BOOL)impulser:(LNMomentumImpulser *)impulser shouldReactToMomentum:(LNMomentumObj *)momentum;
21 | - (LNMomentumObj *)impulser:(LNMomentumImpulser *)impulser momentumForOriginalMomentum:(LNMomentumObj *)obj;
22 |
23 | @end
24 |
25 | @interface LNMomentumImpulser : NSObject
26 |
27 | @property (nonatomic, assign) CGFloat mass;
28 |
29 | @property (nonatomic, weak, readonly) UIScrollView *targetScrollView;
30 |
31 | @property (nonatomic, weak) id serializer;
32 |
33 | - (instancetype)initWithTargetScrollView:(UIScrollView *)targetScrollView;
34 | - (void)removeCurrentMomentum;
35 |
36 | @end
37 |
38 | NS_ASSUME_NONNULL_END
39 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/CommonCell/DemoCommonCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // DemoCommonCell.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "DemoCommonCell.h"
10 |
11 | NSString * const kDemoCommonCell = @"kDemoCommonCell";
12 |
13 | @interface DemoCommonCell ()
14 |
15 | @property (nonatomic, strong) UILabel *titleLabel;
16 |
17 | @end
18 |
19 | @implementation DemoCommonCell
20 |
21 | - (instancetype)initWithFrame:(CGRect)frame
22 | {
23 | self = [super initWithFrame:frame];
24 | if (self) {
25 | [self addSubviews];
26 | [self addConstraints];
27 | self.contentView.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:(rand()%255)/255.f];
28 | }
29 | return self;
30 | }
31 |
32 | - (void)addSubviews
33 | {
34 | [self.contentView addSubview:self.titleLabel];
35 | }
36 |
37 | - (void)addConstraints
38 | {
39 | self.titleLabel.frame = self.contentView.frame;
40 | }
41 |
42 | - (UILabel *)titleLabel
43 | {
44 | if (!_titleLabel) {
45 | _titleLabel = [[UILabel alloc] init];
46 | _titleLabel.textColor = [UIColor blackColor];
47 | _titleLabel.textAlignment = NSTextAlignmentCenter;
48 | }
49 | return _titleLabel;
50 | }
51 |
52 | @end
53 |
54 |
55 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNRestMomentumDetector.h:
--------------------------------------------------------------------------------
1 | //
2 | // LNRestMomentumDetector.h
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import
9 | #import
10 | #import "LNMomentumObj.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | typedef NS_ENUM(NSInteger, LNMomentumEffectDirection)
15 | {
16 | LNMomentumEffectDirectionVertical,
17 | LNMomentumEffectDirectionHorizontal
18 | };
19 |
20 | @class LNRestMomentumDetector;
21 |
22 |
23 | @protocol LNRestMomentumDelegate
24 | @required
25 | - (void)detectorDidReleaseMomentum:(LNMomentumObj *)momentum;
26 |
27 | @end
28 |
29 | @protocol LNRestMomentumSerializer
30 |
31 | @optional
32 | - (BOOL)detector:(LNRestMomentumDetector *)detector shouldStashMomentum:(LNMomentumObj *)momentum;
33 | - (BOOL)detector:(LNRestMomentumDetector *)detector shouldReleaseMomentum:(LNMomentumObj *)momentum;
34 |
35 | @end
36 |
37 | @interface LNRestMomentumDetector : NSObject
38 |
39 | @property (nonatomic, assign) CGFloat mass;
40 |
41 | @property (nonatomic, assign) LNMomentumEffectDirection effectDirection;
42 |
43 | @property (nonatomic, weak) id< LNRestMomentumDelegate> delegate;
44 | @property (nonatomic, weak) id< LNRestMomentumSerializer> serializer;
45 |
46 | @end
47 |
48 | NS_ASSUME_NONNULL_END
49 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimization/BouncesOptimizationTrainer.h:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationTrainer.h
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | typedef NS_ENUM(NSInteger, LNBouncesOptimizationTrainerStatus)
14 | {
15 | LNBouncesOptimizationTrainerStatusFree,
16 | LNBouncesOptimizationTrainerStatusTraining
17 | };
18 |
19 | @interface LNBouncesOptimizationTrainerResult : NSObject
20 |
21 | @property (nonatomic, assign) float phi;
22 |
23 | @property (nonatomic, assign) float delta;
24 |
25 | @property (nonatomic, assign) float a;
26 |
27 | @property (nonatomic, assign) float currentBias;
28 |
29 | @end
30 |
31 | @protocol LNBouncesOptimizationTrainerDelegate
32 | @optional
33 | - (void)trainerDidUpdate:(LNBouncesOptimizationTrainerResult *)tempResult;
34 | - (void)trainerDidFinish:(LNBouncesOptimizationTrainerResult *)finalResult;
35 |
36 | - (void)trainerDidInterrupted;
37 |
38 | @end
39 |
40 | @interface LNBouncesOptimizationTrainer : NSObject
41 |
42 | @property (atomic, assign, readonly) LNBouncesOptimizationTrainerStatus status;
43 |
44 | @property (nonatomic, weak) NSObject *delegate;
45 |
46 | - (void)setObservationData:(NSArray *)observationArr;
47 |
48 | - (void)startTraining;
49 | - (void)stopTraining;
50 |
51 | @end
52 |
53 |
54 | NS_ASSUME_NONNULL_END
55 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollViewUITests/LNCustomScrollViewUITests.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewUITests.m
3 | // LNCustomScrollViewUITests
4 | //
5 | // Created by Levison on 5.4.21.
6 | //
7 |
8 | #import
9 |
10 | @interface LNCustomScrollViewUITests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation LNCustomScrollViewUITests
15 |
16 | - (void)setUp {
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 |
19 | // In UI tests it is usually best to stop immediately when a failure occurs.
20 | self.continueAfterFailure = NO;
21 |
22 | // 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.
23 | }
24 |
25 | - (void)tearDown {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | - (void)testExample {
30 | // UI tests must launch the application that they test.
31 | XCUIApplication *app = [[XCUIApplication alloc] init];
32 | [app launch];
33 |
34 | // Use recording to get started writing UI tests.
35 | // Use XCTAssert and related functions to verify your tests produce the correct results.
36 | }
37 |
38 | - (void)testLaunchPerformance {
39 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
40 | // This measures how long it takes to launch your application.
41 | [self measureWithMetrics:@[[[XCTApplicationLaunchMetric alloc] init]] block:^{
42 | [[[XCUIApplication alloc] init] launch];
43 | }];
44 | }
45 | }
46 |
47 | @end
48 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/DemoItemCollectionViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // DemoItemCollectionViewCell.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "DemoItemCollectionViewCell.h"
10 |
11 | NSString *const kDemoItemCollectionViewCell = @"kDemoItemCollectionViewCell";
12 |
13 | @interface DemoItemCollectionViewCell ()
14 |
15 | @property (nonatomic, strong) UILabel *titleLabel;
16 |
17 | @property (nonatomic, strong) DemoItemObj *itemObj;
18 |
19 | @end
20 |
21 | @implementation DemoItemCollectionViewCell
22 |
23 | - (instancetype)initWithFrame:(CGRect)frame
24 | {
25 | self = [super initWithFrame:frame];
26 | if (self) {
27 | [self addSubviews];
28 | [self addConstraints];
29 | }
30 | return self;
31 | }
32 |
33 | - (void)setItemObj:(DemoItemObj *)item
34 | {
35 | _itemObj = item;
36 | self.titleLabel.text = item.title;
37 | self.contentView.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:(rand()%255)/255.f];
38 | }
39 |
40 | - (void)addSubviews
41 | {
42 | [self.contentView addSubview:self.titleLabel];
43 | }
44 |
45 | - (void)addConstraints
46 | {
47 | self.titleLabel .frame = self.contentView.frame;
48 | }
49 |
50 | - (void)layoutSubviews
51 | {
52 | [super layoutSubviews];
53 | [self addConstraints];
54 | }
55 |
56 | - (UILabel *)titleLabel
57 | {
58 | if (!_titleLabel) {
59 | _titleLabel = [[UILabel alloc] init];
60 | _titleLabel.textAlignment = NSTextAlignmentCenter;
61 | }
62 | return _titleLabel;
63 | }
64 |
65 | @end
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/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 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/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 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | LNCustomScrollView
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UIApplicationSupportsIndirectInputEvents
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollViewClockProxy.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewClockProxy.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "LNCustomScrollViewClockProxy.h"
10 |
11 | @implementation LNCustomScrollViewClockProxy
12 |
13 | - (instancetype)initWithTarget:(id)target {
14 | _target = target;
15 | return self;
16 | }
17 |
18 | + (instancetype)proxyWithTarget:(id)target {
19 | return [[LNCustomScrollViewClockProxy alloc] initWithTarget:target];
20 | }
21 |
22 | - (id)forwardingTargetForSelector:(SEL)selector {
23 | return _target;
24 | }
25 |
26 | - (void)forwardInvocation:(NSInvocation *)invocation {
27 | void *null = NULL;
28 | [invocation setReturnValue:&null];
29 | }
30 |
31 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
32 | return [NSObject instanceMethodSignatureForSelector:@selector(init)];
33 | }
34 |
35 | - (BOOL)respondsToSelector:(SEL)aSelector {
36 | return [_target respondsToSelector:aSelector];
37 | }
38 |
39 | - (BOOL)isEqual:(id)object {
40 | return [_target isEqual:object];
41 | }
42 |
43 | - (NSUInteger)hash {
44 | return [_target hash];
45 | }
46 |
47 | - (Class)superclass {
48 | return [_target superclass];
49 | }
50 |
51 | - (Class)class {
52 | return [_target class];
53 | }
54 |
55 | - (BOOL)isKindOfClass:(Class)aClass {
56 | return [_target isKindOfClass:aClass];
57 | }
58 |
59 | - (BOOL)isMemberOfClass:(Class)aClass {
60 | return [_target isMemberOfClass:aClass];
61 | }
62 |
63 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol {
64 | return [_target conformsToProtocol:aProtocol];
65 | }
66 |
67 | - (BOOL)isProxy {
68 | return YES;
69 | }
70 |
71 | - (NSString *)description {
72 | return [_target description];
73 | }
74 |
75 | - (NSString *)debugDescription {
76 | return [_target debugDescription];
77 | }
78 |
79 | @end
80 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | .DS_Store
9 | */.DS_Store
10 |
11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
12 | *.xcscmblueprint
13 | *.xccheckout
14 |
15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
16 | build/
17 | DerivedData/
18 | *.moved-aside
19 | *.pbxuser
20 | !default.pbxuser
21 | *.mode1v3
22 | !default.mode1v3
23 | *.mode2v3
24 | !default.mode2v3
25 | *.perspectivev3
26 | !default.perspectivev3
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 |
31 | ## App packaging
32 | *.ipa
33 | *.dSYM.zip
34 | *.dSYM
35 |
36 | # CocoaPods
37 | #
38 | # We recommend against adding the Pods directory to your .gitignore. However
39 | # you should judge for yourself, the pros and cons are mentioned at:
40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
41 | #
42 | # Pods/
43 | #
44 | # Add this line if you want to avoid checking in source code from the Xcode workspace
45 | # *.xcworkspace
46 |
47 | # Carthage
48 | #
49 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
50 | # Carthage/Checkouts
51 |
52 | Carthage/Build/
53 |
54 | # fastlane
55 | #
56 | # It is recommended to not store the screenshots in the git repo.
57 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
58 | # For more information about the recommended setup visit:
59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
60 |
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots/**/*.png
64 | fastlane/test_output
65 |
66 | # Code Injection
67 | #
68 | # After new code Injection tools there's a generated folder /iOSInjectionProject
69 | # https://github.com/johnno1962/injectionforxcode
70 |
71 | iOSInjectionProject/
72 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/CustomScrollViewViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // CustomScrollViewViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "CustomScrollViewViewController.h"
10 | #import "LNCustomScrollView.h"
11 |
12 | @interface CustomScrollViewViewController ()
13 |
14 | @property (nonatomic, strong) LNCustomScrollView *customScrollView;
15 |
16 | @property (nonatomic, strong) UIImageView *imageView;
17 |
18 | @end
19 |
20 | @implementation CustomScrollViewViewController
21 |
22 | - (void)viewDidLoad {
23 | [super viewDidLoad];
24 | [self addSubviews];
25 | [self addConstraints];
26 |
27 | //self.navigationController.navigationBar.hidden = YES;
28 | }
29 |
30 | - (void)addSubviews
31 | {
32 | [self.view addSubview:self.customScrollView];
33 | [self.customScrollView addSubview:self.imageView];
34 | }
35 |
36 | - (void)addConstraints
37 | {
38 | self.customScrollView.frame = self.view.bounds;
39 |
40 | UIImage *image = [UIImage imageNamed:@"fulian"];
41 | CGFloat imageHeight = self.view.bounds.size.height;
42 | CGFloat imageWidth = imageHeight * (image.size.width/image.size.height);
43 | self.imageView.frame = CGRectMake(0.f, 0.f, imageWidth, imageHeight);
44 | self.imageView.image = image;
45 | self.customScrollView.contentSize = CGSizeMake(imageWidth, imageHeight);
46 |
47 | self.customScrollView.bounces = YES;
48 | }
49 |
50 | - (LNCustomScrollView *)customScrollView
51 | {
52 | if (!_customScrollView) {
53 | _customScrollView = [[LNCustomScrollView alloc] init];
54 | }
55 | return _customScrollView;
56 | }
57 |
58 | - (UIImageView *)imageView
59 | {
60 | if (!_imageView) {
61 | _imageView = [[UIImageView alloc] init];
62 | _imageView.contentMode = UIViewContentModeScaleAspectFill;
63 | _imageView.layer.masksToBounds = YES;
64 | }
65 | return _imageView;
66 | }
67 |
68 | @end
69 |
70 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/DecelerateObservationViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DecelerateObservationViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "DecelerateObservationViewController.h"
10 | #import "DemoCommonCell.h"
11 |
12 | @interface DecelerateObservationViewController ()
13 | <
14 | UICollectionViewDelegate,
15 | UICollectionViewDataSource,
16 | UICollectionViewDelegateFlowLayout
17 | >
18 |
19 | @property (nonatomic, strong) UICollectionView *collectionView;
20 |
21 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
22 |
23 |
24 | @end
25 |
26 | @implementation DecelerateObservationViewController
27 |
28 | - (void)viewDidLoad {
29 | [super viewDidLoad];
30 | [self addSubviews];
31 | [self addConstraints];
32 | self.view.backgroundColor = [UIColor whiteColor];
33 | }
34 |
35 | - (void)addSubviews
36 | {
37 | [self.view addSubview:self.collectionView];
38 | }
39 |
40 | - (void)addConstraints
41 | {
42 | self.collectionView.frame = self.view.bounds;
43 | }
44 |
45 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
46 | {
47 | return 1;
48 | }
49 |
50 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
51 | {
52 | return 1000;
53 | }
54 |
55 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
56 | {
57 | DemoCommonCell *decelerateCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:kDemoCommonCell forIndexPath:indexPath];
58 | return decelerateCell;
59 | }
60 |
61 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
62 | {
63 | return CGSizeMake(88.f, 88.f);
64 | }
65 |
66 | - (UICollectionView *)collectionView
67 | {
68 | if (!_collectionView) {
69 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
70 | _collectionView.backgroundColor = [UIColor whiteColor];
71 | _collectionView.delegate = self;
72 | _collectionView.dataSource = self;
73 | [_collectionView registerClass:DemoCommonCell.class forCellWithReuseIdentifier:kDemoCommonCell];
74 | }
75 | return _collectionView;
76 | }
77 |
78 | - (UICollectionViewFlowLayout *)flowLayout
79 | {
80 | if (!_flowLayout) {
81 | _flowLayout = [[UICollectionViewFlowLayout alloc] init];
82 | _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
83 | }
84 | return _flowLayout;
85 | }
86 |
87 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
88 | {
89 | NSLog(@"DecelerateVelocity:%lf", velocity.y);
90 | NSLog(@"DecelerateDistance:%lf", targetContentOffset->y - scrollView.contentOffset.y);
91 | }
92 |
93 | @end
94 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNRestMomentumDetector.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNRestMomentumDetector.m
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import "LNRestMomentumDetector.h"
9 |
10 | @interface LNRestMomentumDetector ()
11 |
12 | @property (nonatomic, strong) LNMomentumObj *stashedMomentum;
13 |
14 | @end
15 |
16 | @implementation LNRestMomentumDetector
17 |
18 | - (instancetype)init
19 | {
20 | self = [super init];
21 | if (self) {
22 | self.mass = 1;
23 | }
24 | return self;
25 | }
26 |
27 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
28 | {
29 | if (self.effectDirection == LNMomentumEffectDirectionHorizontal) {
30 | CGFloat targetOffset = scrollView.contentOffset.x + (velocity.x * 1000.f - 13.f)/2.f;
31 | if (fabs(targetOffset - targetContentOffset->x) > 13.f) {
32 | CGFloat v = fabs(targetOffset - targetContentOffset->x)*2 + 13.f;
33 | LNMomentumObj *stashedMomentum = [[LNMomentumObj alloc] init];
34 | stashedMomentum.velocityValue = v;
35 | stashedMomentum.mass = self.mass;
36 | stashedMomentum.direction = velocity.x > 0?LNMomentumDirectionRight:LNMomentumDirectionLeft;
37 | self.stashedMomentum = stashedMomentum;
38 | }
39 | } else {
40 | CGFloat targetOffset = scrollView.contentOffset.y + (velocity.y * 1000.f - 13.f)/2.f;
41 | if (fabs(targetOffset - targetContentOffset->y) > 13.f) {
42 | CGFloat v = fabs(targetOffset - targetContentOffset->y)*2 + 13.f;
43 | LNMomentumObj *stashedMomentum = [[LNMomentumObj alloc] init];
44 | stashedMomentum.velocityValue = v;
45 | stashedMomentum.mass = self.mass;
46 | stashedMomentum.direction = velocity.y > 0?LNMomentumDirectionBottom:LNMomentumDirectionTop;
47 | self.stashedMomentum = stashedMomentum;
48 | }
49 | }
50 | }
51 |
52 | - (void)setStashedMomentum:(LNMomentumObj *)stashedMomentum
53 | {
54 | if (self.serializer && [self.serializer respondsToSelector:@selector(detector:shouldStashMomentum:)]) {
55 | if ([self.serializer detector:self shouldStashMomentum:stashedMomentum]) {
56 | _stashedMomentum = stashedMomentum;
57 | }
58 | } else {
59 | _stashedMomentum = stashedMomentum;
60 | }
61 | }
62 |
63 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
64 | {
65 | if (self.stashedMomentum) {
66 | if (self.serializer && [self.serializer respondsToSelector:@selector(detector:shouldReleaseMomentum:)]) {
67 | if ([self.serializer detector:self shouldReleaseMomentum:self.stashedMomentum]) {
68 | [self.delegate detectorDidReleaseMomentum:self.stashedMomentum];
69 | }
70 | } else {
71 | [self.delegate detectorDidReleaseMomentum:self.stashedMomentum];
72 | }
73 | _stashedMomentum = nil;
74 | }
75 | }
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollViewClock.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollViewClock.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "LNCustomScrollViewClock.h"
10 | #import "LNCustomScrollViewClockProxy.h"
11 | #import
12 |
13 |
14 | @interface LNCustomScrollViewClock ()
15 |
16 | @property (nonatomic, strong) CADisplayLink *displayLink;
17 |
18 | @property (nonatomic, assign) CFTimeInterval realWorldTime;
19 |
20 | @property (nonatomic, assign) CGFloat speedScale;
21 |
22 | @property (nonatomic, assign) BOOL isPaused;
23 |
24 | @property (nonatomic, assign) NSTimeInterval allTime;
25 |
26 | @property (nonatomic, strong) NSHashTable> *hashTable;
27 |
28 | @end
29 |
30 | @implementation LNCustomScrollViewClock
31 |
32 | - (instancetype)init
33 | {
34 | self = [super init];
35 | if (self) {
36 | _realWorldTime = CACurrentMediaTime();
37 | _speedScale = 1.f;
38 | _isPaused = YES;
39 | }
40 | return self;
41 | }
42 |
43 | - (void)addObject:(id)obj
44 | {
45 | [self.hashTable addObject:obj];
46 |
47 | }
48 |
49 | + (instancetype)shareInstance
50 | {
51 | static LNCustomScrollViewClock* shareInstance = nil;
52 | static dispatch_once_t onceToken;
53 | dispatch_once(&onceToken, ^{
54 | if (shareInstance == nil) {
55 | shareInstance = [[LNCustomScrollViewClock alloc] init];
56 | }
57 | });
58 | return shareInstance;
59 | }
60 |
61 | - (void)dealloc
62 | {
63 | [self stop];
64 | }
65 |
66 | - (void)resetClock
67 | {
68 | [self stop];
69 | self.realWorldTime = CACurrentMediaTime();
70 | self.speedScale = 1.f;
71 | self.isPaused = NO;
72 | _displayLink = [CADisplayLink displayLinkWithTarget:[LNCustomScrollViewClockProxy proxyWithTarget:self] selector:@selector(callback)];
73 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
74 | }
75 |
76 | - (void)callback
77 | {
78 | CFTimeInterval newRealWorldTime = CACurrentMediaTime();
79 | CFTimeInterval timeInterval = newRealWorldTime - self.realWorldTime;
80 | for (id obj in self.hashTable) {
81 | if ([obj respondsToSelector:@selector(customScrollViewClockUpdateTimeInterval:)]) {
82 | [obj customScrollViewClockUpdateTimeInterval:timeInterval];
83 | }
84 | }
85 | self.realWorldTime = newRealWorldTime;
86 | }
87 |
88 | - (void)startOrResume
89 | {
90 | if (!_displayLink) {
91 | [self resetClock];
92 | }
93 | if (self.isPaused) {
94 | self.isPaused = NO;
95 | }
96 | }
97 |
98 | - (void)pause
99 | {
100 | self.isPaused = YES;
101 | }
102 |
103 | - (void)stop
104 | {
105 | self.isPaused = YES;
106 | [_displayLink invalidate];
107 | _displayLink = nil;
108 | }
109 |
110 | - (NSHashTable *)hashTable
111 | {
112 | if (!_hashTable) {
113 | _hashTable = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory capacity:2];
114 | }
115 | return _hashTable;
116 | }
117 |
118 | @end
119 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesObservationViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesObservationViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "BouncesObservationViewController.h"
10 | #import "LNCustomScrollViewClock.h"
11 | #import "DemoCommonCell.h"
12 |
13 | @interface BouncesObservationViewController ()
14 | <
15 | UICollectionViewDelegate,
16 | UICollectionViewDataSource,
17 | UICollectionViewDelegateFlowLayout,
18 | LNCustomScrollViewClockProtocol
19 | >
20 |
21 | @property (nonatomic, strong) UICollectionView *collectionView;
22 |
23 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
24 |
25 | @property (nonatomic, assign) BOOL isNeedRecord;
26 |
27 | @end
28 |
29 | @implementation BouncesObservationViewController
30 |
31 | - (void)viewDidLoad {
32 | [super viewDidLoad];
33 | [self addSubviews];
34 | [self addConstraints];
35 | self.view.backgroundColor = [UIColor whiteColor];
36 |
37 | [[LNCustomScrollViewClock shareInstance] addObject:self];
38 | }
39 |
40 | - (void)customScrollViewClockUpdateTimeInterval:(NSTimeInterval)time
41 | {
42 | if (self.collectionView.contentOffset.y < -64.f && (!self.collectionView.isTracking) && self.isNeedRecord) {
43 | NSLog(@"偏移 %lf", self.collectionView.contentOffset.y);
44 | }
45 | }
46 |
47 | - (void)addSubviews
48 | {
49 | [self.view addSubview:self.collectionView];
50 | }
51 |
52 | - (void)addConstraints
53 | {
54 | self.collectionView.frame = self.view.bounds;
55 | }
56 |
57 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
58 | {
59 | return 1;
60 | }
61 |
62 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
63 | {
64 | return 1000;
65 | }
66 |
67 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
68 | {
69 | DemoCommonCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:kDemoCommonCell forIndexPath:indexPath];
70 | return cell;
71 | }
72 |
73 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
74 | {
75 | return CGSizeMake(88.f, 88.f);
76 | }
77 |
78 | - (UICollectionView *)collectionView
79 | {
80 | if (!_collectionView) {
81 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
82 | _collectionView.backgroundColor = [UIColor whiteColor];
83 | _collectionView.delegate = self;
84 | _collectionView.dataSource = self;
85 | [_collectionView registerClass:DemoCommonCell.class forCellWithReuseIdentifier:kDemoCommonCell];
86 | }
87 | return _collectionView;
88 | }
89 |
90 | - (UICollectionViewFlowLayout *)flowLayout
91 | {
92 | if (!_flowLayout) {
93 | _flowLayout = [[UICollectionViewFlowLayout alloc] init];
94 | _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
95 | }
96 | return _flowLayout;
97 | }
98 |
99 | //测量方法
100 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
101 | {
102 | //可以从这里测量出距离和速度的关系
103 | self.isNeedRecord = YES;
104 |
105 | NSLog(@"startY:%@", @(scrollView.contentOffset.y));
106 | NSLog(@"startVelocity:%@", @(velocity.y));
107 | }
108 |
109 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
110 | {
111 | self.isNeedRecord = NO;
112 | }
113 |
114 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
115 | {
116 |
117 | }
118 |
119 |
120 | @end
121 |
122 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/PanGestureObservationViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // PanGestureObservationViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "PanGestureObservationViewController.h"
10 | #import "DemoCommonCell.h"
11 |
12 |
13 | @interface PanGestureObservationViewController ()
14 | <
15 | UICollectionViewDelegate,
16 | UICollectionViewDataSource,
17 | UICollectionViewDelegateFlowLayout
18 | >
19 |
20 | @property (nonatomic, strong) UICollectionView *collectionView;
21 |
22 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
23 |
24 | @property (nonatomic, assign) CGFloat lastPanDisplace;
25 |
26 | @property (nonatomic, assign) CGFloat lastContentOffsetDisplace;
27 |
28 | @property (nonatomic, assign) NSInteger mod;
29 | @property (nonatomic, assign) NSInteger count;
30 |
31 | @end
32 |
33 | @implementation PanGestureObservationViewController
34 |
35 | - (void)viewDidLoad {
36 | [super viewDidLoad];
37 |
38 | self.mod = 5;
39 | self.count = 1;
40 |
41 | [self addSubviews];
42 | [self addConstraints];
43 | self.view.backgroundColor = [UIColor whiteColor];
44 | }
45 |
46 | - (void)addSubviews
47 | {
48 | [self.view addSubview:self.collectionView];
49 | }
50 |
51 | - (void)addConstraints
52 | {
53 | self.collectionView.frame = self.view.bounds;
54 | }
55 |
56 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
57 | {
58 | return 1;
59 | }
60 |
61 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
62 | {
63 | return 1000;
64 | }
65 |
66 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
67 | {
68 | DemoCommonCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:kDemoCommonCell forIndexPath:indexPath];
69 | return cell;
70 | }
71 |
72 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
73 | {
74 | return CGSizeMake(88.f, 88.f);
75 | }
76 |
77 | - (UICollectionView *)collectionView
78 | {
79 | if (!_collectionView) {
80 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
81 | _collectionView.backgroundColor = [UIColor whiteColor];
82 | _collectionView.delegate = self;
83 | _collectionView.dataSource = self;
84 | [_collectionView registerClass:DemoCommonCell.class forCellWithReuseIdentifier:kDemoCommonCell];
85 | }
86 | return _collectionView;
87 | }
88 |
89 | - (UICollectionViewFlowLayout *)flowLayout
90 | {
91 | if (!_flowLayout) {
92 | _flowLayout = [[UICollectionViewFlowLayout alloc] init];
93 | _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
94 | }
95 | return _flowLayout;
96 | }
97 |
98 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
99 | {
100 | if (scrollView.isTracking) {
101 | if (self.lastPanDisplace < -10000.f) {
102 | self.lastPanDisplace = [scrollView.panGestureRecognizer locationInView:scrollView].y;
103 | }
104 |
105 | if (self.lastContentOffsetDisplace < -10000.f) {
106 | self.lastContentOffsetDisplace = scrollView.contentOffset.y;
107 | }
108 |
109 | CGFloat deltaPanDisplace = [scrollView.panGestureRecognizer locationInView:scrollView].y - self.lastPanDisplace;
110 |
111 | CGFloat deltaContentOffsetDisplace = scrollView.contentOffset.y - self.lastContentOffsetDisplace;
112 |
113 |
114 | self.count++;
115 | if (self.count % self.mod == 0) {
116 | self.lastPanDisplace = [scrollView.panGestureRecognizer locationInView:scrollView].y;
117 | self.lastContentOffsetDisplace = scrollView.contentOffset.y;
118 |
119 | CGFloat convertScale = fabs(deltaContentOffsetDisplace/deltaPanDisplace);
120 | NSLog(@"convertScale:%lf contentOffset:%lf", convertScale, fabs(self.collectionView.contentOffset.y + 88.f));
121 | }
122 |
123 | } else {
124 | self.lastPanDisplace = -10001.f;
125 | self.lastContentOffsetDisplace = -10001.f;
126 | self.count = 0;
127 | }
128 | }
129 |
130 | @end
131 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimization/BouncesOptimizationDataList.m:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationDataList.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "BouncesOptimizationDataList.h"
10 |
11 | @interface BouncesOptimizationData ()
12 |
13 | @end
14 |
15 | @implementation BouncesOptimizationData
16 |
17 | @end
18 |
19 |
20 | @interface BouncesOptimizationDataList ()
21 |
22 | @end
23 |
24 | @implementation BouncesOptimizationDataList
25 |
26 | - (instancetype)init
27 | {
28 | self = [super init];
29 | if (self) {
30 | [self loadAllLists];
31 | }
32 | return self;
33 | }
34 |
35 | - (void)loadAllLists
36 | {
37 | [self loadList1];
38 | [self loadList2];
39 | [self loadList3];
40 | }
41 |
42 | - (void)loadList1
43 | {
44 | BouncesOptimizationData *data = [[BouncesOptimizationData alloc] init];
45 |
46 | data.title = @"数据1";
47 | data.dataArray = @[
48 | @(123.6666),
49 | @(172.0000),
50 | @(203.3333),
51 | @(221.6666),
52 | @(230.6666),
53 | @(233.0000),
54 | @(230.3333),
55 | @(224.6666),
56 | @(217.0000),
57 | @(208.0000),
58 | @(198.6666),
59 | @(189.0000),
60 | @(179.3333),
61 | @(170.3333),
62 | @(161.6666),
63 | @(153.6666),
64 | @(146.0000),
65 | @(139.3333),
66 | @(133.3333),
67 | @(128.0000),
68 | @(123.0000),
69 | @(118.6666),
70 | @(114.6666),
71 | @(111.3333),
72 | @(108.3333),
73 | @(105.6666),
74 | @(103.3333),
75 | @(101.3333),
76 | @(99.33333),
77 | @(98.00000),
78 | @(96.66666),
79 | @(95.33333),
80 | @(94.33333),
81 | @(93.33333),
82 | @(92.66666),
83 | @(92.00000),
84 | @(91.33333),
85 | @(91.00000),
86 | @(90.66666),
87 | @(90.33333),
88 | @(90.00000),
89 | @(89.66666),
90 | @(89.33333),
91 | @(89.33333),
92 | @(89.00000),
93 | @(89.00000),
94 | @(88.66666),
95 | @(88.66666),
96 | @(88.66666)
97 | ];
98 |
99 | data.dataArray = [self cutBaseLine:88.f forArr:data.dataArray];
100 |
101 | [self.dataMArray addObject:data];
102 | }
103 |
104 | - (void)loadList2
105 | {
106 | BouncesOptimizationData *data = [[BouncesOptimizationData alloc] init];
107 |
108 | data.title = @"数据2";
109 | data.dataArray = @[
110 | @(73.500000),
111 | @(101.500000),
112 | @(120.000000),
113 | @(131.500000),
114 | @(137.000000),
115 | @(139.000000),
116 | @(138.500000),
117 | @(136.000000),
118 | @(132.500000),
119 | @(128.000000),
120 | @(123.000000),
121 | @(118.000000),
122 | @(113.000000),
123 | @(108.000000),
124 | @(103.500000),
125 | @(99.000000),
126 | @(95.500000),
127 | @(91.500000),
128 | @(88.500000),
129 | @(85.500000),
130 | @(83.000000),
131 | @(80.500000),
132 | @(78.500000),
133 | @(76.500000),
134 | @(75.000000),
135 | @(73.500000),
136 | @(72.000000),
137 | @(71.000000),
138 | @(70.000000),
139 | @(69.500000),
140 | @(68.500000),
141 | @(68.000000),
142 | @(67.500000),
143 | @(67.000000),
144 | @(66.500000),
145 | @(66.000000),
146 | @(66.000000),
147 | @(65.500000),
148 | @(65.500000),
149 | @(65.000000),
150 | @(65.000000),
151 | @(65.000000),
152 | @(64.500000),
153 | @(64.500000),
154 | @(64.500000)
155 | ];
156 |
157 | data.dataArray = [self cutBaseLine:64.f forArr:data.dataArray];
158 |
159 | [self.dataMArray addObject:data];
160 | }
161 |
162 | - (void)loadList3
163 | {
164 | BouncesOptimizationData *data = [[BouncesOptimizationData alloc] init];
165 |
166 | data.title = @"数据3";
167 | data.dataArray = @[
168 | @(457.50000),
169 | @(448.50000),
170 | @(431.50000),
171 | @(409.50000),
172 | @(384.50000),
173 | @(358.50000),
174 | @(332.00000),
175 | @(306.50000),
176 | @(282.00000),
177 | @(259.00000),
178 | @(237.50000),
179 | @(218.00000),
180 | @(200.00000),
181 | @(183.50000),
182 | @(169.00000),
183 | @(156.00000),
184 | @(144.50000),
185 | @(134.50000),
186 | @(125.00000),
187 | @(117.00000),
188 | @(110.00000),
189 | @(104.00000),
190 | @(98.500000),
191 | @(94.000000),
192 | @(90.000000),
193 | @(86.500000),
194 | @(83.500000),
195 | @(80.500000),
196 | @(78.500000),
197 | @(76.500000),
198 | @(74.500000),
199 | @(73.000000),
200 | @(72.000000),
201 | @(70.500000),
202 | @(69.500000),
203 | @(69.000000),
204 | @(68.000000),
205 | @(67.500000),
206 | @(67.000000),
207 | @(66.500000),
208 | @(66.000000),
209 | @(66.000000),
210 | @(65.500000),
211 | @(65.500000),
212 | @(65.000000),
213 | @(65.000000),
214 | @(65.000000),
215 | @(64.500000),
216 | @(64.500000),
217 | @(64.500000)
218 | ];
219 |
220 | data.dataArray = [self cutBaseLine:64.f forArr:data.dataArray];
221 |
222 | [self.dataMArray addObject:data];
223 | }
224 |
225 | - (NSArray *)cutBaseLine:(float)baseLine forArr:(NSArray *)arr
226 | {
227 | NSMutableArray *resultMArr = [[NSMutableArray alloc] initWithCapacity:arr.count];
228 | for (NSNumber *num in arr) {
229 | float cutNum = [num floatValue] - baseLine;
230 | [resultMArr addObject:@(cutNum)];
231 | }
232 | return resultMArr.copy;
233 | }
234 |
235 | - (NSMutableArray *)dataMArray
236 | {
237 | if (!_dataMArray) {
238 | _dataMArray = [[NSMutableArray alloc] init];
239 | }
240 | return _dataMArray;
241 | }
242 |
243 | @end
244 |
245 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Main/MainViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "MainViewController.h"
10 | #import "DemoItemObj.h"
11 | #import "DemoItemCollectionViewCell.h"
12 | #import "DecelerateObservationViewController.h"
13 | #import "BouncesObservationViewController.h"
14 | #import "BouncesOptimizationViewController.h"
15 | #import "PanGestureObservationViewController.h"
16 | #import "CustomScrollViewViewController.h"
17 | #import "MomentumTransmitViewController.h"
18 |
19 | @interface MainViewController ()
20 |
21 | @property (nonatomic, copy) NSArray *demoItemArr;
22 |
23 | @property (nonatomic, strong) UICollectionView *collectionView;
24 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
25 |
26 | @end
27 |
28 | @implementation MainViewController
29 |
30 | - (void)viewDidLoad {
31 | [super viewDidLoad];
32 | [self addSubviews];
33 | [self addConstraints];
34 | [self loadObjs];
35 | }
36 |
37 | - (void)addSubviews
38 | {
39 | [self.view addSubview:self.collectionView];
40 | }
41 |
42 | - (void)addConstraints
43 | {
44 | self.collectionView.frame = self.view.bounds;
45 | }
46 |
47 | - (void)loadObjs
48 | {
49 | NSMutableArray *demoItemMArr = [[NSMutableArray alloc] init];
50 |
51 | DemoItemObj *decelerateMeasureObj = [[DemoItemObj alloc] init];
52 | decelerateMeasureObj.type = DemoTypeDecelerateObservation;
53 | decelerateMeasureObj.title = @"Decelerate";
54 | [demoItemMArr addObject:decelerateMeasureObj];
55 |
56 | DemoItemObj *bouncesMeasureObj = [[DemoItemObj alloc] init];
57 | bouncesMeasureObj.type = DemoTypeBouncesObservation;
58 | bouncesMeasureObj.title = @"Bounces";
59 | [demoItemMArr addObject:bouncesMeasureObj];
60 |
61 | DemoItemObj *optimizationObj = [[DemoItemObj alloc] init];
62 | optimizationObj.type = DemoTypeOptimization;
63 | optimizationObj.title = @"Optimization";
64 | [demoItemMArr addObject:optimizationObj];
65 |
66 | DemoItemObj *panGestureMeasureObj = [[DemoItemObj alloc] init];
67 | panGestureMeasureObj.type = DemoTypePanGestureObservation;
68 | panGestureMeasureObj.title = @"PanGesture";
69 | [demoItemMArr addObject:panGestureMeasureObj];
70 |
71 | DemoItemObj *customScrollViewObj = [[DemoItemObj alloc] init];
72 | customScrollViewObj.type = DemoTypeCustomScrollView;
73 | customScrollViewObj.title = @"CustomScrollView";
74 | [demoItemMArr addObject:customScrollViewObj];
75 |
76 | DemoItemObj *momentumTransmitObj = [[DemoItemObj alloc] init];
77 | momentumTransmitObj.type = DemoTypeMomentumTransmit;
78 | momentumTransmitObj.title = @"Momentum";
79 | [demoItemMArr addObject:momentumTransmitObj];
80 |
81 | self.demoItemArr = demoItemMArr.copy;
82 |
83 | [self.collectionView reloadData];
84 | }
85 |
86 | - (UICollectionView *)collectionView
87 | {
88 | if (!_collectionView) {
89 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
90 | _collectionView.delegate = self;
91 | _collectionView.dataSource = self;
92 | _collectionView.backgroundColor = [UIColor whiteColor];
93 | [_collectionView registerClass:DemoItemCollectionViewCell.class forCellWithReuseIdentifier:kDemoItemCollectionViewCell];
94 |
95 | }
96 | return _collectionView;
97 | }
98 |
99 | - (UICollectionViewFlowLayout *)flowLayout
100 | {
101 | if (!_flowLayout) {
102 | _flowLayout = [[UICollectionViewFlowLayout alloc] init];
103 | _flowLayout.sectionInset = UIEdgeInsetsMake(20.f, 20.f, 20.f, 20.f);
104 | }
105 | return _flowLayout;
106 | }
107 |
108 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
109 | {
110 | return 1;
111 | }
112 |
113 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
114 | {
115 | return self.demoItemArr.count;
116 | }
117 |
118 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
119 | {
120 | return CGSizeMake(self.view.frame.size.width/2.f - 42.f, 44.f);
121 | }
122 |
123 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
124 | {
125 | DemoItemCollectionViewCell *cell = (DemoItemCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kDemoItemCollectionViewCell forIndexPath:indexPath];
126 | [cell setItemObj:self.demoItemArr[indexPath.row]];
127 | return cell;
128 | }
129 |
130 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
131 | {
132 | [collectionView deselectItemAtIndexPath:indexPath animated:NO];
133 | switch (self.demoItemArr[indexPath.row].type) {
134 | case DemoTypeDecelerateObservation: {
135 | DecelerateObservationViewController *vc = [[DecelerateObservationViewController alloc] init];
136 | [self.navigationController pushViewController:vc animated:YES];
137 | } break;
138 | case DemoTypeBouncesObservation: {
139 | BouncesObservationViewController *vc = [[BouncesObservationViewController alloc] init];
140 | [self.navigationController pushViewController:vc animated:YES];
141 | } break;
142 | case DemoTypeOptimization: {
143 | BouncesOptimizationViewController *vc = [[BouncesOptimizationViewController alloc] init];
144 | [self.navigationController pushViewController:vc animated:YES];
145 | } break;
146 | case DemoTypePanGestureObservation: {
147 | PanGestureObservationViewController *vc = [[PanGestureObservationViewController alloc] init];
148 | [self.navigationController pushViewController:vc animated:YES];
149 | } break;
150 | case DemoTypeCustomScrollView: {
151 | CustomScrollViewViewController *vc = [[CustomScrollViewViewController alloc] init];
152 | [self.navigationController pushViewController:vc animated:YES];
153 | } break;
154 | case DemoTypeMomentumTransmit: {
155 | MomentumTransmitViewController *vc = [[MomentumTransmitViewController alloc] init];
156 | [self.navigationController pushViewController:vc animated:YES];
157 | }
158 | default: {
159 |
160 | } break;
161 | }
162 | }
163 |
164 | @end
165 |
166 |
167 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/Momentum/LNMomentumImpulser.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNMomentumImpulser.m
3 | // LNMomentumTransmitDemo
4 | //
5 | // Created by Levison on 2022/7/17.
6 | //
7 |
8 | #import "LNMomentumImpulser.h"
9 | #import "LNVelocityDynamicItem.h"
10 |
11 | @interface LNMomentumImpulser ()
12 |
13 | @property (nonatomic, strong) UIDynamicAnimator *animator;
14 | @property (nonatomic, weak) UIDynamicItemBehavior *decelerationBehavior;
15 | @property (nonatomic, strong) LNVelocityDynamicItem *velocityItem;
16 |
17 | @property (nonatomic, assign) CGPoint stashedContentOffset;
18 |
19 | @property (nonatomic, weak) UIScrollView *targetScrollView;
20 |
21 | @end
22 |
23 | @implementation LNMomentumImpulser
24 |
25 | - (instancetype)initWithTargetScrollView:(UIScrollView *)targetScrollView
26 | {
27 | self = [super init];
28 | if (self) {
29 | self.targetScrollView = targetScrollView;
30 | self.mass = 1;
31 | _velocityItem = [[LNVelocityDynamicItem alloc] init];
32 | _animator = [[UIDynamicAnimator alloc] initWithReferenceView:targetScrollView];
33 | }
34 | return self;
35 | }
36 |
37 | - (void)detectorDidReleaseMomentum:(LNMomentumObj *)momentum
38 | {
39 | if (self.serializer && [self.serializer respondsToSelector:@selector(impulser:shouldReactToMomentum:)] && (![self.serializer impulser:self shouldReactToMomentum:momentum])) {
40 | return;
41 | }
42 |
43 | if (self.serializer && [self.serializer respondsToSelector:@selector(impulser:momentumForOriginalMomentum:)]) {
44 | momentum = [self.serializer impulser:self momentumForOriginalMomentum:momentum];
45 | }
46 |
47 | if (!momentum) {
48 | return;
49 | }
50 |
51 | if (self.targetScrollView && self.mass != 0) {
52 | self.stashedContentOffset = self.targetScrollView.contentOffset;
53 | switch (momentum.direction) {
54 | case LNMomentumDirectionLeft: {
55 | [self pulseHorizontalVelocity:-momentum.momentumValue/self.mass resistance:2.f];
56 | } break;
57 | case LNMomentumDirectionRight: {
58 | [self pulseHorizontalVelocity:momentum.momentumValue/self.mass resistance:2.f];
59 | } break;
60 | case LNMomentumDirectionTop: {
61 | [self pulseVerticalVelocity:-momentum.momentumValue/self.mass resistance:2.f];
62 | } break;
63 | case LNMomentumDirectionBottom: {
64 | [self pulseVerticalVelocity:momentum.momentumValue/self.mass resistance:2.f];
65 | } break;
66 | default:
67 | break;
68 | }
69 | }
70 | }
71 |
72 | - (void)removeCurrentMomentum
73 | {
74 | [self.animator removeAllBehaviors];
75 | self.decelerationBehavior = nil;
76 | }
77 |
78 | - (void)pulseHorizontalVelocity:(CGFloat)velocity resistance:(CGFloat)resistance
79 | {
80 | if (!self.targetScrollView) {
81 | return;
82 | }
83 |
84 | if ([self.targetScrollView isMemberOfClass:[UIScrollView class]]) {
85 | return;
86 | }
87 |
88 | [self removeCurrentMomentum];
89 |
90 | self.velocityItem.center = self.targetScrollView.contentOffset;
91 | UIDynamicItemBehavior *decelerationBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.velocityItem]];
92 | [decelerationBehavior addLinearVelocity:CGPointMake(velocity, 0) forItem:self.velocityItem];
93 | decelerationBehavior.resistance = 2.0;
94 |
95 | __weak LNMomentumImpulser *weakSelf = self;
96 | decelerationBehavior.action = ^{
97 | if (weakSelf.targetScrollView.tracking || weakSelf.targetScrollView.dragging || weakSelf.targetScrollView.decelerating || (!weakSelf.targetScrollView.scrollEnabled)) {
98 | [weakSelf.animator removeAllBehaviors];
99 | } else if (fabs(weakSelf.stashedContentOffset.x - weakSelf.targetScrollView.contentOffset.x) > 1.f) {
100 | [weakSelf.animator removeAllBehaviors];
101 | } else if (weakSelf.velocityItem.center.x < 0) {
102 | [weakSelf.animator removeAllBehaviors];
103 | weakSelf.targetScrollView.contentOffset = CGPointMake(0, weakSelf.targetScrollView.contentOffset.y);
104 | } else if (weakSelf.velocityItem.center.x > weakSelf.targetScrollView.contentSize.width - weakSelf.targetScrollView.frame.size.width) {
105 | [weakSelf.animator removeAllBehaviors];
106 | weakSelf.targetScrollView.contentOffset = CGPointMake(MAX(weakSelf.targetScrollView.contentSize.width - weakSelf.targetScrollView.frame.size.width, 0), weakSelf.targetScrollView.contentOffset.y) ;
107 | } else {
108 | CGPoint contentOffset = weakSelf.targetScrollView.contentOffset;
109 | contentOffset = weakSelf.velocityItem.center;
110 | weakSelf.targetScrollView.contentOffset = contentOffset;
111 | weakSelf.stashedContentOffset = contentOffset;
112 | }
113 |
114 | if (fabs([weakSelf.decelerationBehavior linearVelocityForItem:weakSelf.velocityItem].x)< 13.f) {
115 | [weakSelf.animator removeAllBehaviors];
116 | }
117 | };
118 |
119 | [self.animator addBehavior:decelerationBehavior];
120 | self.decelerationBehavior = decelerationBehavior;
121 | }
122 |
123 | - (void)pulseVerticalVelocity:(CGFloat)velocity resistance:(CGFloat)resistance
124 | {
125 | if (!self.targetScrollView) {
126 | return;
127 | }
128 |
129 | if ([self.targetScrollView isMemberOfClass:[UIScrollView class]]) {
130 | return;
131 | }
132 |
133 | [self removeCurrentMomentum];
134 |
135 | self.velocityItem.center = self.targetScrollView.contentOffset;
136 | UIDynamicItemBehavior *decelerationBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.velocityItem]];
137 | [decelerationBehavior addLinearVelocity:CGPointMake(0, velocity) forItem:self.velocityItem];
138 | decelerationBehavior.resistance = 2.0;
139 |
140 | __weak LNMomentumImpulser *weakSelf = self;
141 | decelerationBehavior.action = ^{
142 | if (weakSelf.targetScrollView.tracking || weakSelf.targetScrollView.dragging || weakSelf.targetScrollView.decelerating || (!weakSelf.targetScrollView.scrollEnabled)) {
143 | [weakSelf.animator removeAllBehaviors];
144 | } else if (fabs(weakSelf.stashedContentOffset.y - weakSelf.targetScrollView.contentOffset.y) > 1.f) {
145 | [weakSelf.animator removeAllBehaviors];
146 | } else if (weakSelf.velocityItem.center.y < - weakSelf.targetScrollView.contentInset.top) {
147 | [weakSelf.animator removeAllBehaviors];
148 | weakSelf.targetScrollView.contentOffset = CGPointMake(weakSelf.targetScrollView.contentOffset.x, - weakSelf.targetScrollView.contentInset.top);
149 | } else if (weakSelf.velocityItem.center.y > weakSelf.targetScrollView.contentSize.height + weakSelf.targetScrollView.contentInset.bottom - weakSelf.targetScrollView.frame.size.height) {
150 | [weakSelf.animator removeAllBehaviors];
151 | weakSelf.targetScrollView.contentOffset = CGPointMake(weakSelf.targetScrollView.contentOffset.x, MAX( weakSelf.targetScrollView.contentSize.height + weakSelf.targetScrollView.contentInset.bottom - weakSelf.targetScrollView.frame.size.height, 0)) ;
152 | } else {
153 | CGPoint contentOffset = weakSelf.targetScrollView.contentOffset;
154 | contentOffset = weakSelf.velocityItem.center;
155 | weakSelf.targetScrollView.contentOffset = contentOffset;
156 | weakSelf.stashedContentOffset = contentOffset;
157 | }
158 |
159 | if (fabs([weakSelf.decelerationBehavior linearVelocityForItem:weakSelf.velocityItem].y) < 13.f) {
160 | [weakSelf.animator removeAllBehaviors];
161 | }
162 | };
163 |
164 | [self.animator addBehavior:decelerationBehavior];
165 | self.decelerationBehavior = decelerationBehavior;
166 | }
167 |
168 | @end
169 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimization/BouncesOptimizationTrainer.m:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationTrainer.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "BouncesOptimizationTrainer.h"
10 |
11 | @interface LNBouncesOptimizationTrainerResult ()
12 |
13 | @end
14 |
15 | @implementation LNBouncesOptimizationTrainerResult
16 |
17 | @end
18 |
19 | @interface LNBouncesOptimizationTrainer ()
20 |
21 | @property (atomic, assign) LNBouncesOptimizationTrainerStatus status;
22 |
23 | @property (atomic, assign) BOOL shouldStop;
24 |
25 | @property (nonatomic, copy) NSArray *originalArray;
26 |
27 | @end
28 |
29 | @implementation LNBouncesOptimizationTrainer
30 | {
31 | dispatch_queue_t _coculateQueue;
32 | }
33 |
34 | - (instancetype)init
35 | {
36 | self = [super init];
37 | if (self) {
38 | _coculateQueue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
39 | }
40 | return self;
41 | }
42 |
43 | - (void)setObservationData:(NSArray *)observationArr
44 | {
45 | if (self.status == LNBouncesOptimizationTrainerStatusFree) {
46 | _originalArray = [observationArr copy];
47 | }
48 | }
49 |
50 | - (void)startTraining
51 | {
52 | if (self.status == LNBouncesOptimizationTrainerStatusTraining) {
53 | return;
54 | }
55 |
56 | if (!self.originalArray || self.originalArray.count <= 0) {
57 | return;
58 | }
59 |
60 | __weak LNBouncesOptimizationTrainer *weakSelf = self;
61 | self.status = LNBouncesOptimizationTrainerStatusTraining;
62 | dispatch_async(_coculateQueue, ^{
63 | [weakSelf train];
64 | });
65 | }
66 |
67 | - (void)stopTraining
68 | {
69 | if (self.status == LNBouncesOptimizationTrainerStatusFree) {
70 | return;
71 | }
72 |
73 | self.shouldStop = YES;
74 | __weak LNBouncesOptimizationTrainer *weakSelf = self;
75 | dispatch_async(_coculateQueue, ^{
76 | weakSelf.shouldStop = NO;
77 | weakSelf.status = LNBouncesOptimizationTrainerStatusTraining;
78 | dispatch_async(dispatch_get_main_queue(), ^{
79 | if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(trainerDidInterrupted)]) {
80 | weakSelf.status = LNBouncesOptimizationTrainerStatusFree;
81 | [weakSelf.delegate trainerDidInterrupted];
82 | }
83 | });
84 | });
85 | }
86 |
87 | - (void)train
88 | {
89 | float bias = 1000000000.f;
90 |
91 | //v0 = 410.f A = 426.f;
92 | //v0 = 2504.f A = 2493.f
93 | //v0 = 2661.f A = 2636.f
94 | float A = 0.f;
95 | float Delta = 0.f;
96 | float Phi = 0.f;
97 |
98 | float AStep = 1.f;
99 | float DeltaStep = 0.01f;
100 | float PhiStep = 0.001f;
101 |
102 | while (1) {
103 |
104 | if (self.shouldStop) {
105 | return;
106 | }
107 |
108 | float tryA1 = A + AStep;
109 | float bias1 = 0.f;
110 |
111 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
112 | float realValue = [self.originalArray[i] floatValue];
113 | float t = (Phi + i * 0.0167f);
114 | float coculateValue = tryA1 * t * expf(-Delta*t);
115 | bias1 += (coculateValue - realValue)*(coculateValue - realValue);
116 | }
117 |
118 | float tryA2 = A - AStep;
119 | float bias2 = 0.f;
120 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
121 | float realValue = [self.originalArray[i] floatValue];
122 | float t = (Phi + i * 0.0167f);
123 | float coculateValue = tryA2 * t * expf(-Delta*t);
124 | bias2 += (coculateValue - realValue)*(coculateValue - realValue);
125 | }
126 |
127 | float tryDelta3 = Delta + DeltaStep;
128 | float bias3 = 0.f;
129 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
130 | float realValue = [self.originalArray[i] floatValue];
131 | float t = (Phi + i * 0.0167f);
132 | float coculateValue = A * t * expf(-tryDelta3*t);
133 | bias3 += (coculateValue - realValue)*(coculateValue - realValue);
134 | }
135 |
136 | float tryDelta4 = Delta - DeltaStep;
137 | float bias4 = 0.f;
138 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
139 | float realValue = [self.originalArray[i] floatValue];
140 | float t = (Phi + i * 0.0167f);
141 | float coculateValue = A * t * expf(-tryDelta4*t);
142 | bias4 += (coculateValue - realValue)*(coculateValue - realValue);
143 | }
144 |
145 |
146 | float tryPhi5 = Phi + PhiStep;
147 | float bias5 = 0.f;
148 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
149 | float realValue = [self.originalArray[i] floatValue];
150 | float t = (tryPhi5 + i * 0.0167f);
151 | float coculateValue = A * t * expf(-Delta*t);
152 | bias5 += (coculateValue - realValue)*(coculateValue - realValue);
153 | }
154 |
155 | float tryPhi6 = Phi - PhiStep;
156 | float bias6 = 0.f;
157 | for (NSInteger i = 0 ; i < self.originalArray.count ; i++) {
158 | float realValue = [self.originalArray[i] floatValue];
159 | float t = (tryPhi6 + i * 0.0167f);
160 | float coculateValue = A * t * expf(-Delta*t);
161 | bias6 += (coculateValue - realValue)*(coculateValue - realValue);
162 | }
163 |
164 | float chooseBias = MIN(MIN(MIN(bias1, bias2), MIN(bias3, bias4)), MIN(bias5, bias6));
165 |
166 | LNBouncesOptimizationTrainerResult *result = [[LNBouncesOptimizationTrainerResult alloc] init];
167 | result.currentBias = chooseBias;
168 |
169 | if (chooseBias > bias) {
170 | __weak LNBouncesOptimizationTrainer *weakSelf = self;
171 | result.phi = Phi;
172 | result.a = A;
173 | result.delta = Delta;
174 | dispatch_sync(dispatch_get_main_queue(), ^{
175 | if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(trainerDidFinish:)]) {
176 | [weakSelf.delegate trainerDidFinish:result];
177 | }
178 | });
179 | break;
180 | } else {
181 | bias = chooseBias;
182 | __weak LNBouncesOptimizationTrainer *weakSelf = self;
183 | if (chooseBias == bias1) {
184 | A = tryA1;
185 | } else if (chooseBias == bias2) {
186 | A = tryA2;
187 | } else if (chooseBias == bias3) {
188 | Delta = tryDelta3;
189 | } else if (chooseBias == bias4) {
190 | Delta = tryDelta4;
191 | } else if (chooseBias == bias5) {
192 | Phi = tryPhi5;
193 | } else if (chooseBias == bias6) {
194 | Phi = tryPhi6;
195 | } else {
196 | result.phi = Phi;
197 | result.a = A;
198 | result.delta = Delta;
199 | dispatch_sync(dispatch_get_main_queue(), ^{
200 | if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(trainerDidFinish:)]) {
201 | [weakSelf.delegate trainerDidFinish:result];
202 | }
203 | });
204 | break;
205 | }
206 | result.phi = Phi;
207 | result.a = A;
208 | result.delta = Delta;
209 | dispatch_sync(dispatch_get_main_queue(), ^{
210 | if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(trainerDidUpdate:)]) {
211 | [weakSelf.delegate trainerDidUpdate:result];
212 | }
213 | });
214 | }
215 | }
216 | }
217 |
218 | @end
219 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/MomentumTransmitViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MomentumTransmitViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 17.7.22.
6 | // Copyright © 2022 Levison. All rights reserved.
7 | //
8 |
9 | #import "MomentumTransmitViewController.h"
10 | #import "LNRestMomentumDetector.h"
11 | #import "LNMomentumImpulser.h"
12 |
13 | @interface MomentumTransmitViewController ()
14 |
15 | @property (nonatomic, strong) UICollectionView *collectionView1;
16 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout1;
17 | @property (nonatomic, strong) UICollectionView *collectionView2;
18 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout2;
19 |
20 | //这对是把collectionView1的向右冲量传给collectionView2转为下冲量
21 | @property (nonatomic, strong) LNRestMomentumDetector *detector12;
22 | @property (nonatomic, strong) LNMomentumImpulser *impulser12;
23 |
24 | //这对是把collectionView2的向上冲量传给collectionView1转为左冲量
25 | @property (nonatomic, strong) LNRestMomentumDetector *detector21;
26 | @property (nonatomic, strong) LNMomentumImpulser *impulser21;
27 |
28 | //通常只需要单方向
29 |
30 | @end
31 |
32 | @implementation MomentumTransmitViewController
33 |
34 | - (void)viewDidLoad {
35 | [super viewDidLoad];
36 | [self addSubviews];
37 | [self addConstraints];
38 | // Do any additional setup after loading the view.
39 | }
40 |
41 | - (void)addSubviews
42 | {
43 | [self.view addSubview:self.collectionView1];
44 | [self.view addSubview:self.collectionView2];
45 | }
46 |
47 | - (void)addConstraints
48 | {
49 | self.collectionView1.frame = CGRectMake(0.f, 0.f, self.view.bounds.size.width, self.view.bounds.size.height/2.f);
50 | self.collectionView2.frame = CGRectMake(0.f, self.view.bounds.size.height/2.f, self.view.bounds.size.width, self.view.bounds.size.height/2.f);
51 |
52 | }
53 |
54 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
55 | {
56 | return 1;
57 | }
58 |
59 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
60 | {
61 | return 50;
62 | }
63 |
64 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
65 | {
66 | return CGSizeMake(100.f, 100.f);
67 | }
68 |
69 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
70 | {
71 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"kCollectionViewCell" forIndexPath:indexPath];
72 | cell.backgroundColor = [UIColor colorWithRed:(random()%255)/255.f green:(random()%255)/255.f blue:(random()%255)/255.f alpha:1.f];
73 | return cell;
74 | }
75 |
76 | - (UICollectionView *)collectionView1
77 | {
78 | if (!_collectionView1) {
79 | _collectionView1 = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout1];
80 | _collectionView1.delegate = self;
81 | _collectionView1.dataSource = self;
82 | //因为只有减速没有弹性,所以这里是局限
83 | _collectionView1.bounces = NO;
84 | [_collectionView1 registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:@"kCollectionViewCell"];
85 | }
86 | return _collectionView1;
87 | }
88 |
89 | - (UICollectionView *)collectionView2
90 | {
91 | if (!_collectionView2) {
92 | _collectionView2 = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout2];
93 | _collectionView2.dataSource = self;
94 | _collectionView2.delegate = self;
95 | //因为只有减速没有弹性,所以这里是局限
96 | _collectionView2.bounces = NO;
97 | [_collectionView2 registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:@"kCollectionViewCell"];
98 | }
99 | return _collectionView2;
100 | }
101 |
102 | - (UICollectionViewFlowLayout *)flowLayout1
103 | {
104 | if (!_flowLayout1) {
105 | _flowLayout1 = [[UICollectionViewFlowLayout alloc] init];
106 | _flowLayout1.scrollDirection = UICollectionViewScrollDirectionHorizontal;
107 | }
108 | return _flowLayout1;
109 | }
110 |
111 | - (UICollectionViewFlowLayout *)flowLayout2
112 | {
113 | if (!_flowLayout2) {
114 | _flowLayout2 = [[UICollectionViewFlowLayout alloc] init];
115 | _flowLayout2.scrollDirection = UICollectionViewScrollDirectionVertical;
116 | }
117 | return _flowLayout2;
118 | }
119 |
120 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
121 | {
122 | if (scrollView == self.collectionView1) {
123 | [self.detector12 scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
124 | }
125 |
126 | if (scrollView == self.collectionView2) {
127 | [self.detector21 scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
128 | }
129 | }
130 |
131 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
132 | {
133 | if (scrollView == self.collectionView2) {
134 | if (!decelerate) {
135 | [self.impulser12 removeCurrentMomentum];
136 | }
137 | }
138 |
139 | if (scrollView == self.collectionView1) {
140 | if (!decelerate) {
141 | [self.impulser21 removeCurrentMomentum];
142 | }
143 | }
144 | }
145 |
146 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
147 | {
148 | if (scrollView == self.collectionView1) {
149 | [self.detector12 scrollViewDidEndDecelerating:scrollView];
150 | [self.impulser21 removeCurrentMomentum];
151 | }
152 |
153 | if (scrollView == self.collectionView2) {
154 | [self.detector21 scrollViewDidEndDecelerating:scrollView];
155 | [self.impulser12 removeCurrentMomentum];
156 | }
157 | }
158 |
159 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
160 | {
161 | if (scrollView == self.collectionView2) {
162 | [self.impulser12 removeCurrentMomentum];
163 | }
164 |
165 | if (scrollView == self.collectionView1) {
166 | [self.impulser21 removeCurrentMomentum];
167 | }
168 | }
169 |
170 | - (LNRestMomentumDetector *)detector12
171 | {
172 | if (!_detector12) {
173 | _detector12 = [[LNRestMomentumDetector alloc] init];
174 | _detector12.delegate = self.impulser12;
175 | _detector12.serializer = self;
176 | _detector12.effectDirection = LNMomentumEffectDirectionHorizontal;
177 | }
178 | return _detector12;
179 | }
180 |
181 | // LNRestMomentumSerializer , 判断某个剩余速度是否应该生效
182 | - (BOOL)detector:(LNRestMomentumDetector *)detector shouldStashMomentum:(LNMomentumObj *)momentum
183 | {
184 | if (detector == self.detector12) {
185 | //只有撞到右侧边界才生效
186 | switch (momentum.direction) {
187 | case LNMomentumDirectionRight: {
188 | return YES;
189 | } break;
190 | default: return NO;
191 | break;
192 | }
193 | return NO;
194 | } else if (detector == self.detector21){
195 | //只有撞到顶部才生效
196 | switch (momentum.direction) {
197 | case LNMomentumDirectionTop: {
198 | return YES;
199 | } break;
200 | default: return NO;
201 | break;
202 | }
203 | return NO;
204 | }
205 | return NO;
206 | }
207 |
208 | - (LNMomentumImpulser *)impulser12
209 | {
210 | if (!_impulser12) {
211 | _impulser12 = [[LNMomentumImpulser alloc] initWithTargetScrollView:self.collectionView2];
212 | _impulser12.serializer = self;
213 | }
214 | return _impulser12;
215 | }
216 |
217 | - (BOOL)impulser:(LNMomentumImpulser *)impulser shouldReactToMomentum:(LNMomentumObj *)momentum
218 | {
219 | return YES;
220 | }
221 |
222 | - (LNMomentumObj *)impulser:(LNMomentumImpulser *)impulser momentumForOriginalMomentum:(LNMomentumObj *)obj
223 | {
224 | //传过来是向右,过滤成向下
225 | if (impulser == self.impulser12) {
226 | obj.direction = LNMomentumDirectionBottom;
227 | return obj;
228 | } else if (impulser == self.impulser21) {
229 | obj.direction = LNMomentumDirectionLeft;
230 | return obj;
231 | }
232 | return obj;
233 | }
234 |
235 | - (LNMomentumImpulser *)impulser21
236 | {
237 | if (!_impulser21) {
238 | _impulser21 = [[LNMomentumImpulser alloc] initWithTargetScrollView:self.collectionView1];
239 | _impulser21.serializer = self;
240 | //这样修改质量之后detector21 会很难撞动impulser21,impulser21只能获取到detector21yuliang速度的十分之一
241 | //m1*v1 = m2*v2
242 | //_impulser21.mass = 10.f;
243 | }
244 | return _impulser21;
245 | }
246 |
247 | - (LNRestMomentumDetector *)detector21
248 | {
249 | if (!_detector21) {
250 | _detector21 = [[LNRestMomentumDetector alloc] init];
251 | _detector21.delegate = self.impulser21;
252 | _detector21.serializer = self;
253 | _detector21.effectDirection = LNMomentumEffectDirectionVertical;
254 | //_detector21.mass = 1;
255 | }
256 | return _detector21;
257 | }
258 |
259 | @end
260 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/Demo/BouncesOptimizationViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // BouncesOptimizationViewController.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "BouncesOptimizationViewController.h"
10 | #import "BouncesOptimizationTrainer.h"
11 | #import "BouncesOptimizationDataList.h"
12 | #import "DemoCommonCell.h"
13 |
14 |
15 | #define BorderLineStartPointX 20.f
16 | #define BorderLineStartPointY 300.f
17 |
18 | #define xBorderLineLength 300.f
19 | #define yBorderLineLength 200.f
20 |
21 | @interface BouncesOptimizationViewController ()
22 | <
23 | UICollectionViewDelegate,
24 | UICollectionViewDataSource,
25 | UICollectionViewDelegateFlowLayout,
26 | LNBouncesOptimizationTrainerDelegate
27 | >
28 |
29 | @property (nonatomic, strong) BouncesOptimizationDataList *dataList;
30 |
31 | @property (nonatomic, strong) UICollectionView *collectionView;
32 | @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
33 |
34 | @property (nonatomic, strong) UILabel *selectionLabel;
35 |
36 | @property (nonatomic, strong) UIView *backgroundContainerView;
37 | @property (nonatomic, strong) UIView *yBorderLine;
38 | @property (nonatomic, strong) UIView *xBorderLine;
39 |
40 | @property (nonatomic, strong) UIView *originalDataPointContainerView;
41 | @property (nonatomic, strong) UIView *optimizationDataPointContainerView;
42 |
43 | @property (nonatomic, strong) UILabel *biasLabel;
44 | @property (nonatomic, strong) UILabel *deltaLabel;
45 | @property (nonatomic, strong) UILabel *aLabel;
46 | @property (nonatomic, strong) UILabel *phiLabel;
47 |
48 | @property (nonatomic, weak) NSArray *currentOriginalDataArray;
49 | @property (nonatomic, copy) NSArray *currentOptimizationArray;
50 |
51 | @property (nonatomic, strong) UIButton *startButton;
52 | @property (nonatomic, strong) UIButton *stopButton;
53 |
54 | @property (nonatomic, strong) LNBouncesOptimizationTrainer *trainer;
55 |
56 | @end
57 |
58 | @implementation BouncesOptimizationViewController
59 |
60 | - (void)viewDidLoad {
61 | [super viewDidLoad];
62 |
63 | self.view.backgroundColor = [UIColor whiteColor];
64 | [self addSubviews];
65 | [self addConstraints];
66 | }
67 |
68 | - (void)addSubviews
69 | {
70 | [self.view addSubview:self.collectionView];
71 | [self.view addSubview:self.selectionLabel];
72 |
73 | [self.view addSubview:self.backgroundContainerView];
74 | [self.backgroundContainerView addSubview:self.xBorderLine];
75 | [self.backgroundContainerView addSubview:self.yBorderLine];
76 | [self.backgroundContainerView addSubview:self.biasLabel];
77 | [self.backgroundContainerView addSubview:self.aLabel];
78 | [self.backgroundContainerView addSubview:self.deltaLabel];
79 | [self.backgroundContainerView addSubview:self.phiLabel];
80 |
81 | [self.view addSubview:self.originalDataPointContainerView];
82 | [self.view addSubview:self.optimizationDataPointContainerView];
83 |
84 |
85 |
86 | [self.view addSubview:self.startButton];
87 | [self.view addSubview:self.stopButton];
88 | }
89 |
90 | - (void)addConstraints
91 | {
92 | self.collectionView.frame = CGRectMake(0.f, 88.f, self.view.frame.size.width - 132.f, 44.f);
93 | self.selectionLabel.frame = CGRectMake(self.view.frame.size.width - 120.f, 88.f, 108.f, 44.f);
94 |
95 | self.backgroundContainerView.frame = CGRectMake(0.f, 132.f, self.view.frame.size.width, self.view.frame.size.height- 132.f - 88.f);
96 | self.xBorderLine.frame = CGRectMake(BorderLineStartPointX, BorderLineStartPointY, xBorderLineLength, 1.f/[UIScreen mainScreen].scale);
97 | self.yBorderLine.frame = CGRectMake(BorderLineStartPointX, BorderLineStartPointY - yBorderLineLength, 1.f/[UIScreen mainScreen].scale, yBorderLineLength);
98 |
99 | self.biasLabel.frame = CGRectMake(20.f, 10.f, 200.f, 20.f);
100 | self.aLabel.frame = CGRectMake(20.f, 30.f, 200.f, 20.f);
101 | self.deltaLabel.frame = CGRectMake(20.f, 50.f, 200.f, 20.f);
102 | self.phiLabel.frame = CGRectMake(20.f, 70.f, 200.f, 20.f);
103 |
104 | self.originalDataPointContainerView.frame = self.backgroundContainerView.frame;
105 | self.optimizationDataPointContainerView.frame = self.backgroundContainerView.frame;
106 |
107 | self.startButton.frame = CGRectMake(0.f, self.view.frame.size.height - 132.f, self.view.frame.size.width/2.f, 88.f);
108 | self.stopButton.frame = CGRectMake(self.view.frame.size.width/2.f, self.view.frame.size.height - 132.f, self.view.frame.size.width/2.f, 88.f);
109 | }
110 |
111 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
112 | {
113 | return self.dataList.dataMArray.count;
114 | }
115 |
116 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
117 | {
118 | return CGSizeMake(88.f, 44.f);
119 | }
120 |
121 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
122 | {
123 | DemoCommonCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kDemoCommonCell forIndexPath:indexPath];
124 | cell.titleLabel.text = [self.dataList.dataMArray objectAtIndex:indexPath.item].title;
125 | return cell;
126 | }
127 |
128 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
129 | {
130 | self.currentOriginalDataArray = [self.dataList.dataMArray objectAtIndex:indexPath.row].dataArray;
131 | [self redrawOriginalData];
132 | self.selectionLabel.text = [self.dataList.dataMArray objectAtIndex:indexPath.row].title;
133 | }
134 |
135 | - (void)redrawOriginalData
136 | {
137 | [self redraw:self.currentOriginalDataArray inContainerView:self.originalDataPointContainerView color:[UIColor blackColor]];
138 | }
139 |
140 | - (void)redrawOptimizationData
141 | {
142 | [self redraw:self.currentOptimizationArray inContainerView:self.optimizationDataPointContainerView color:[UIColor redColor]];
143 | }
144 |
145 | - (void)redraw:(NSArray *)numberArray inContainerView:(UIView *)containerView color:(UIColor *)color
146 | {
147 | for (UIView *subview in containerView.subviews) {
148 | [subview removeFromSuperview];
149 | }
150 |
151 | for (NSInteger i = 0; i < numberArray.count ; i++) {
152 | UIView *pointView = [[UIView alloc] init];
153 | pointView.backgroundColor = color;
154 | [containerView addSubview:pointView];
155 |
156 | pointView.frame = CGRectMake(BorderLineStartPointX + i * 5.f, BorderLineStartPointY - [numberArray[i] floatValue] , 2.f, 2.f);
157 | }
158 | }
159 |
160 | - (UILabel *)selectionLabel
161 | {
162 | if (!_selectionLabel) {
163 | _selectionLabel = [[UILabel alloc] init];
164 | _selectionLabel.textColor = [UIColor blackColor];
165 | }
166 | return _selectionLabel;
167 | }
168 |
169 | - (UICollectionView *)collectionView
170 | {
171 | if (!_collectionView) {
172 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
173 | [_collectionView registerClass:DemoCommonCell.class forCellWithReuseIdentifier:kDemoCommonCell];
174 | _collectionView.delegate = self;
175 | _collectionView.dataSource = self;
176 | _collectionView.allowsSelection = YES;
177 | _collectionView.allowsMultipleSelection = NO;
178 | _collectionView.backgroundColor = [UIColor whiteColor];
179 | _collectionView.contentInset = UIEdgeInsetsMake(0.f, 24.f, 0.f, 24.f);
180 | }
181 | return _collectionView;
182 | }
183 |
184 | - (UICollectionViewFlowLayout *)flowLayout
185 | {
186 | if (!_flowLayout) {
187 | _flowLayout = [[UICollectionViewFlowLayout alloc] init];
188 | _flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
189 | }
190 | return _flowLayout;
191 | }
192 |
193 | - (UIView *)backgroundContainerView
194 | {
195 | if (!_backgroundContainerView) {
196 | _backgroundContainerView = [[UIView alloc] init];
197 | }
198 | return _backgroundContainerView;
199 | }
200 |
201 | - (UIView *)xBorderLine
202 | {
203 | if (!_xBorderLine) {
204 | _xBorderLine = [[UIView alloc] init];
205 | _xBorderLine.backgroundColor = [UIColor blueColor];
206 | }
207 | return _xBorderLine;
208 | }
209 |
210 | - (UIView *)yBorderLine
211 | {
212 | if (!_yBorderLine) {
213 | _yBorderLine = [[UIView alloc] init];
214 | _yBorderLine.backgroundColor = [UIColor redColor];
215 | }
216 | return _yBorderLine;
217 | }
218 |
219 | - (UIView *)originalDataPointContainerView
220 | {
221 | if (!_originalDataPointContainerView) {
222 | _originalDataPointContainerView = [[UIView alloc] init];
223 | }
224 | return _originalDataPointContainerView;
225 | }
226 |
227 | - (UIView *)optimizationDataPointContainerView
228 | {
229 | if (!_optimizationDataPointContainerView) {
230 | _optimizationDataPointContainerView = [[UIView alloc] init];
231 | }
232 | return _optimizationDataPointContainerView;
233 | }
234 |
235 | - (BouncesOptimizationDataList *)dataList
236 | {
237 | if (!_dataList) {
238 | _dataList = [[BouncesOptimizationDataList alloc] init];
239 | }
240 | return _dataList;
241 | }
242 |
243 | - (UIButton *)startButton
244 | {
245 | if (!_startButton) {
246 | _startButton = [[UIButton alloc] init];
247 | _startButton.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:(rand()%255)/255.f];
248 | [_startButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
249 | [_startButton setTitle:@"Start" forState:UIControlStateNormal];
250 | [_startButton addTarget:self action:@selector(startButtonClicked) forControlEvents:UIControlEventTouchUpInside];
251 | }
252 | return _startButton;
253 | }
254 |
255 | - (void)startButtonClicked
256 | {
257 | [self.trainer setObservationData:self.currentOriginalDataArray];
258 | [self.trainer startTraining];
259 | }
260 |
261 | - (UIButton *)stopButton
262 | {
263 | if (!_stopButton) {
264 | _stopButton = [[UIButton alloc] init];
265 | [_stopButton setTitle:@"Stop" forState:UIControlStateNormal];
266 | [_stopButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
267 | _stopButton.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.f green:(rand()%255)/255.f blue:(rand()%255)/255.f alpha:(rand()%255)/255.f];
268 | [_stopButton addTarget:self action:@selector(stopButtonClicked) forControlEvents:UIControlEventTouchUpInside];
269 | }
270 | return _stopButton;
271 | }
272 |
273 | - (void)stopButtonClicked
274 | {
275 | [self.trainer stopTraining];
276 | }
277 |
278 | - (LNBouncesOptimizationTrainer *)trainer
279 | {
280 | if (!_trainer) {
281 | _trainer = [[LNBouncesOptimizationTrainer alloc] init];
282 | _trainer.delegate = self;
283 | }
284 | return _trainer;
285 | }
286 |
287 | - (void)trainerDidUpdate:(LNBouncesOptimizationTrainerResult *)tempResult
288 | {
289 | NSLog(@"%f", tempResult.currentBias);
290 | NSMutableArray *mArr = [[NSMutableArray alloc] initWithCapacity:self.currentOriginalDataArray.count];
291 | for (NSInteger i = 0 ; i < self.currentOriginalDataArray.count; i++) {
292 | float t = (i * 0.0167f + tempResult.phi);
293 | float resultValue = tempResult.a * t * expf(- tempResult.delta * t);
294 | [mArr addObject:@(resultValue)];
295 | }
296 | self.currentOptimizationArray = mArr.copy;
297 | [self redrawOptimizationData];
298 |
299 | self.aLabel.text = [NSString stringWithFormat:@"a:%@",@(tempResult.a)];
300 | self.deltaLabel.text = [NSString stringWithFormat:@"delta:%@",@(tempResult.delta)];
301 | self.phiLabel.text = [NSString stringWithFormat:@"phi:%@", @(tempResult.phi)];
302 | self.biasLabel.text = [NSString stringWithFormat:@"bias:%@", @(tempResult.currentBias)];
303 | }
304 |
305 | - (void)trainerDidFinish:(LNBouncesOptimizationTrainerResult *)finalResult
306 | {
307 | self.aLabel.text = [NSString stringWithFormat:@"a:%@",@(finalResult.a)];
308 | self.deltaLabel.text = [NSString stringWithFormat:@"delta:%@",@(finalResult.delta)];
309 | self.phiLabel.text = [NSString stringWithFormat:@"phi:%@", @(finalResult.phi)];
310 | self.biasLabel.text = [NSString stringWithFormat:@"bias:%@", @(finalResult.currentBias)];
311 | }
312 |
313 | - (void)trainerDidInterrupted
314 | {
315 | [self clear];
316 | }
317 |
318 | - (void)clear
319 | {
320 | for (UIView *subview in self.originalDataPointContainerView.subviews) {
321 | [subview removeFromSuperview];
322 | }
323 |
324 | for (UIView *subview in self.optimizationDataPointContainerView.subviews) {
325 | [subview removeFromSuperview];
326 | }
327 |
328 | self.biasLabel.text = @"";
329 | self.aLabel.text = @"";
330 | self.deltaLabel.text = @"";
331 | self.phiLabel.text = @"";
332 | }
333 |
334 | - (UILabel *)biasLabel
335 | {
336 | if (!_biasLabel) {
337 | _biasLabel = [[UILabel alloc] init];
338 | _biasLabel.textColor = [UIColor blackColor];
339 | }
340 | return _biasLabel;
341 | }
342 |
343 | - (UILabel *)aLabel
344 | {
345 | if (!_aLabel) {
346 | _aLabel = [[UILabel alloc] init];
347 | _aLabel.textColor = [UIColor blackColor];
348 | }
349 | return _aLabel;
350 | }
351 |
352 | - (UILabel *)deltaLabel
353 | {
354 | if (!_deltaLabel) {
355 | _deltaLabel = [[UILabel alloc] init];
356 | _deltaLabel.textColor = [UIColor blackColor];
357 | }
358 | return _deltaLabel;
359 | }
360 |
361 | - (UILabel *)phiLabel
362 | {
363 | if (!_phiLabel) {
364 | _phiLabel = [[UILabel alloc] init];
365 | _phiLabel.textColor = [UIColor blackColor];
366 | }
367 | return _phiLabel;
368 | }
369 |
370 | @end
371 |
372 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView/CustomScrollView/LNCustomScrollView.m:
--------------------------------------------------------------------------------
1 | //
2 | // LNCustomScrollView.m
3 | // LNCustomScrollView
4 | //
5 | // Created by Levison on 5.4.21.
6 | // Copyright © 2021 Levison. All rights reserved.
7 | //
8 |
9 | #import "LNCustomScrollView.h"
10 | #import "LNCustomScrollViewClock.h"
11 |
12 | #define BounceDisplaceConvertDelta 0.006f
13 | #define BounceDisplaceConvertC 0.88f
14 | #define BounceDisplaceConvertBias 0.12f
15 |
16 | #define MaxIgnoredVelocity 13.f
17 |
18 | #define BouncesTimeMultiple 1.1f
19 |
20 | @interface LNCustomScrollViewDirectionalStatus: NSObject
21 |
22 | @property (nonatomic, assign) CGFloat velocity;
23 |
24 | @property (nonatomic, assign) CGFloat position;
25 |
26 | @end
27 |
28 | @implementation LNCustomScrollViewDirectionalStatus
29 |
30 | @end
31 |
32 | @interface LNCustomScrollViewDirectionalRange: NSObject
33 |
34 | @property (nonatomic, assign) CGFloat minPosition;
35 |
36 | @property (nonatomic, assign) CGFloat maxPosition;
37 |
38 | @end
39 |
40 | @implementation LNCustomScrollViewDirectionalRange
41 |
42 | @end
43 |
44 | @interface LNCustomScrollView ()
45 | <
46 | LNCustomScrollViewClockProtocol
47 | >
48 |
49 | @property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
50 |
51 | @property (nonatomic, assign) BOOL isTracking;
52 | @property (nonatomic, assign) BOOL tracking;
53 |
54 | @property (nonatomic, assign) CGPoint startContentOffset;
55 | @property (nonatomic, assign) CGPoint startPanLocation;
56 |
57 | @property (nonatomic, assign) CGPoint velocity;
58 |
59 | @property (nonatomic, assign) CGPoint contentOffset;
60 |
61 | @end
62 |
63 | @implementation LNCustomScrollView
64 |
65 | - (instancetype)init
66 | {
67 | self = [super init];
68 | if (self) {
69 |
70 | }
71 | return self;
72 | }
73 |
74 | - (instancetype)initWithFrame:(CGRect)frame
75 | {
76 | self = [super initWithFrame:frame];
77 | if (self) {
78 | self.bounces = YES;
79 | self.velocity = CGPointMake(0.f, 0.f);
80 | self.contentOffset = CGPointMake(0.f, 0.f);
81 | [[LNCustomScrollViewClock shareInstance] addObject:self];
82 | [self addGestureRecognizer:self.panGesture];
83 | }
84 | return self;
85 | }
86 |
87 | - (void)setContentOffset:(CGPoint)contentOffset
88 | {
89 | _contentOffset = contentOffset;
90 | self.bounds = CGRectMake(contentOffset.x, contentOffset.y, self.bounds.size.width, self.bounds.size.height);
91 | }
92 |
93 | - (void)setFrame:(CGRect)frame
94 | {
95 | [super setFrame:frame];
96 | }
97 |
98 | - (void)setContentSize:(CGSize)contentSize
99 | {
100 | _contentSize = contentSize;
101 | }
102 |
103 | - (UIPanGestureRecognizer *)panGesture
104 | {
105 | if (!_panGesture) {
106 | _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dealWithPanEvent:)];
107 | }
108 | return _panGesture;
109 | }
110 |
111 | - (void)dealWithPanEvent:(UIPanGestureRecognizer *)panGesture
112 | {
113 | if (self.panGesture.state == UIGestureRecognizerStateBegan) {
114 | self.startContentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y);
115 | self.startPanLocation = [self.panGesture translationInView:self];
116 | self.isTracking = YES;
117 | } else if (self.panGesture.state == UIGestureRecognizerStateChanged) {
118 | self.isTracking = YES;
119 | CGPoint currentPanLocation = [self.panGesture translationInView:self];
120 |
121 | CGFloat targetX = self.startContentOffset.x - (currentPanLocation.x - self.startPanLocation.x);
122 | CGFloat targetY = self.startContentOffset.y - (currentPanLocation.y - self.startPanLocation.y);
123 |
124 | if (!self.bounces) {
125 | targetX = [self getInsideXOf:targetX];
126 | targetY = [self getInsideYOf:targetY];
127 | } else {
128 | targetX = [self getBouncesXOf:targetX];
129 | targetY = [self getBouncesYOf:targetY];
130 | }
131 |
132 | self.contentOffset = CGPointMake(targetX, targetY);
133 | } else {
134 | //possible
135 | self.isTracking = NO;
136 |
137 | self.startPanLocation = CGPointZero;
138 | self.startContentOffset = CGPointZero;
139 | }
140 | }
141 |
142 | - (void)customScrollViewClockUpdateTimeInterval:(NSTimeInterval)time
143 | {
144 | if (CGPointEqualToPoint(CGPointZero, self.velocity) &&
145 | self.contentOffset.y > 0.f &&
146 | self.contentOffset.y < self.contentSize.height - self.bounds.size.height &&
147 | self.contentOffset.x > 0.f &&
148 | self.contentOffset.x < self.contentSize.width - self.bounds.size.width) {
149 | return;
150 | }
151 |
152 | if (!self.isTracking) {
153 | //检查是否有剩余动画
154 | if (fabs(self.velocity.x) > MaxIgnoredVelocity || self.contentOffset.x < 0.f || self.contentOffset.x > self.contentSize.width - self.bounds.size.width) {
155 | LNCustomScrollViewDirectionalStatus *status = [[LNCustomScrollViewDirectionalStatus alloc] init];
156 | status.position = self.contentOffset.x;
157 | status.velocity = self.velocity.x;
158 | LNCustomScrollViewDirectionalRange *range = [[LNCustomScrollViewDirectionalRange alloc] init];
159 | range.minPosition = 0.f;
160 | range.maxPosition = MAX(self.contentSize.width - self.bounds.size.width, 0.f);
161 | LNCustomScrollViewDirectionalStatus *resultStatus = [self stepFromStatus:status inRange:range afterDuring:time bounces:YES];
162 |
163 | self.velocity = CGPointMake(resultStatus.velocity, self.velocity.y);
164 | self.contentOffset = CGPointMake(resultStatus.position, self.contentOffset.y);
165 | } else {
166 | self.velocity = CGPointMake(0.f, self.velocity.y);
167 | }
168 |
169 | if (fabs(self.velocity.y) > MaxIgnoredVelocity || self.contentOffset.y < 0.f || self.contentOffset.y > self.contentSize.height - self.bounds.size.height) {
170 | LNCustomScrollViewDirectionalStatus *status = [[LNCustomScrollViewDirectionalStatus alloc] init];
171 | status.position = self.contentOffset.y;
172 | status.velocity = self.velocity.y;
173 | LNCustomScrollViewDirectionalRange *range = [[LNCustomScrollViewDirectionalRange alloc] init];
174 | range.minPosition = 0.f;
175 | range.maxPosition = MAX(self.contentSize.height - self.bounds.size.height, 0.f);
176 | LNCustomScrollViewDirectionalStatus *resultStatus = [self stepFromStatus:status inRange:range afterDuring:time bounces:YES];
177 |
178 | self.velocity = CGPointMake(self.velocity.x, resultStatus.velocity);
179 | self.contentOffset = CGPointMake(self.contentOffset.x, resultStatus.position);
180 | } else {
181 | self.velocity = CGPointMake(self.velocity.x, 0.f);
182 | }
183 | } else {
184 | }
185 | }
186 |
187 | - (LNCustomScrollViewDirectionalStatus *)stepFromStatus:(LNCustomScrollViewDirectionalStatus *)status
188 | inRange:(LNCustomScrollViewDirectionalRange *)range
189 | afterDuring:(NSTimeInterval)during
190 | bounces:(BOOL)bounces
191 | {
192 | CGFloat originalVelocity = status.velocity;
193 | CGFloat originalPosition = status.position;
194 |
195 | LNCustomScrollViewDirectionalStatus *resultStatus = [[LNCustomScrollViewDirectionalStatus alloc] init];
196 |
197 | //先区分所在位置
198 | if (originalPosition < range.minPosition) {
199 | //在顶端
200 | CGFloat distanceToBalancePoint = range.minPosition - originalPosition;
201 | CGFloat velocity = originalVelocity;
202 |
203 | if (fabs(velocity) == 0) {
204 | velocity = +0;
205 | }
206 |
207 | NSTimeInterval t;
208 | if (velocity/distanceToBalancePoint > -10.9) {
209 | t = 1.f/(10.9 + velocity/distanceToBalancePoint);
210 | CGFloat A = distanceToBalancePoint/(t * exp(-10.9 * t));
211 | NSTimeInterval targetT = t + during * BouncesTimeMultiple;
212 |
213 | CGFloat targetVelocity = A * exp(-10.9 * targetT) + A * targetT * (-10.9) * exp(-10.9 * targetT);
214 | CGFloat targetPosition = A * targetT * exp(-10.9 * targetT);
215 | resultStatus.velocity = targetVelocity;
216 | resultStatus.position = range.minPosition - targetPosition;
217 |
218 | if (velocity < MaxIgnoredVelocity && distanceToBalancePoint < 1.f) {
219 | resultStatus.position = range.minPosition;
220 | resultStatus.velocity = 0.f;
221 | }
222 | } else {
223 | resultStatus.position = range.minPosition;
224 | resultStatus.velocity = 0.f;
225 | }
226 |
227 | } else if (originalPosition > range.maxPosition) {
228 | //在底端
229 | CGFloat distanceToBalancePoint = originalPosition - range.maxPosition;
230 | CGFloat velocity = originalVelocity;
231 |
232 | if (fabs(velocity) == 0) {
233 | velocity = +0;
234 | }
235 |
236 | NSTimeInterval t;
237 | if (velocity/distanceToBalancePoint > -10.9f) {
238 | t = 1.f/(10.9 + velocity/distanceToBalancePoint);
239 | CGFloat A = distanceToBalancePoint/(t * exp(-10.9 * t));
240 | NSTimeInterval targetT = t + during * BouncesTimeMultiple;
241 |
242 | CGFloat targetVelocity = A * exp(-10.9 * targetT) + A * targetT * (-10.9) * exp(-10.9 * targetT);
243 | CGFloat targetPosition = A * targetT * exp(-10.9 * targetT);
244 | resultStatus.velocity = targetVelocity;
245 | resultStatus.position = range.maxPosition + targetPosition;
246 |
247 | if (velocity < MaxIgnoredVelocity && distanceToBalancePoint < 1.f) {
248 | resultStatus.position = range.maxPosition;
249 | resultStatus.velocity = 0.f;
250 | }
251 | } else {
252 | resultStatus.position = range.maxPosition;
253 | resultStatus.velocity = 0.f;
254 | }
255 | } else {
256 | //在中央
257 |
258 | CGFloat estimatedVelocity = exp(log(fabs(originalVelocity))- 2*during) * (originalVelocity > 0.f ? 1.f:(-1.f));
259 | CGFloat estimatedDistance = fabs(estimatedVelocity - originalVelocity)/2.f * (originalVelocity > 0.f ? 1.f:(-1.f));
260 | CGFloat estimatedPosition = originalPosition + estimatedDistance;
261 |
262 |
263 | //这个运动被分为两段decelerate,减速和bounces
264 | if (estimatedPosition < range.minPosition) {
265 | CGFloat restDistance = fabs(originalPosition - range.minPosition);
266 | CGFloat v0 = fabs(originalVelocity) - restDistance * 2.f;
267 |
268 | CGFloat decelerateT = log(v0/(restDistance + v0))/2.f;
269 |
270 | CGFloat bouncesT = MAX(0, during - decelerateT);
271 |
272 | CGFloat bouncesDistance = v0 * bouncesT * exp(- 10.9 * bouncesT);
273 | CGFloat targetPosition = range.minPosition - bouncesDistance;
274 |
275 | CGFloat targetVelocity = v0*exp(-10.9*bouncesT) + v0*bouncesT*(-10.9)*exp(-10.9*bouncesT);
276 |
277 | if (!self.bounces) {
278 | resultStatus.position = range.minPosition;
279 | resultStatus.velocity = 0.f;
280 | } else {
281 | if (fabs(targetVelocity) > MaxIgnoredVelocity) {
282 | resultStatus.position = targetPosition;
283 | resultStatus.velocity = targetVelocity;
284 | } else {
285 | resultStatus.position = range.minPosition;
286 | resultStatus.velocity = 0.f;
287 | }
288 | }
289 |
290 | } else if (estimatedPosition > range.maxPosition) {
291 | CGFloat restDistance = fabs(originalPosition - range.maxPosition);
292 | CGFloat v0 = fabs(originalVelocity) - restDistance * 2.f;
293 |
294 | CGFloat decelerateT = log(v0/(restDistance + v0))/2.f;
295 |
296 | CGFloat bouncesT = MAX(0, during - decelerateT);
297 |
298 | CGFloat bouncesDistance = v0 * bouncesT * exp(- 10.9 * bouncesT);
299 | CGFloat targetPosition = range.maxPosition + bouncesDistance;
300 |
301 | CGFloat targetVelocity = v0*exp(-10.9*bouncesT) + v0*bouncesT*(-10.9)*exp(-10.9*bouncesT);
302 |
303 |
304 | if (!self.bounces) {
305 | resultStatus.position = range.maxPosition;
306 | resultStatus.velocity = 0.f;
307 | } else {
308 | if (fabs(targetVelocity) > MaxIgnoredVelocity) {
309 | resultStatus.position = targetPosition;
310 | resultStatus.velocity = targetVelocity;
311 | } else {
312 | resultStatus.position = range.maxPosition;
313 | resultStatus.velocity = 0.f;
314 | }
315 | }
316 |
317 | } else {
318 | //正常的减速
319 | resultStatus.position = estimatedPosition;
320 | resultStatus.velocity = estimatedVelocity;
321 | }
322 | }
323 |
324 | return resultStatus;
325 | }
326 |
327 | - (CGFloat)getInsideXOf:(CGFloat)targetX
328 | {
329 |
330 | if (targetX < 0.f) {
331 | targetX = 0.f;
332 | }
333 |
334 | if (targetX > self.contentSize.width - self.frame.size.width) {
335 | if (self.contentSize.width - self.frame.size.width > 0.f) {
336 | targetX = self.contentSize.width - self.frame.size.width;
337 | } else {
338 | targetX = 0.f;
339 | }
340 | }
341 | return targetX;
342 | }
343 |
344 | - (CGFloat)getInsideYOf:(CGFloat)targetY
345 | {
346 |
347 | if (targetY < 0.f) {
348 | targetY = 0.f;
349 | }
350 |
351 | if (targetY > self.contentSize.height - self.frame.size.height) {
352 | if (self.contentSize.height - self.frame.size.height > 0.f) {
353 | targetY = self.contentSize.height - self.frame.size.height;
354 | } else {
355 | targetY = 0.f;
356 | }
357 | }
358 | return targetY;
359 | }
360 |
361 |
362 | - (CGFloat)getBouncesXOf:(CGFloat)targetX
363 | {
364 | if (targetX < 0.f) {
365 | CGFloat resultX = BounceDisplaceConvertC * (1/BounceDisplaceConvertDelta) * (1 - exp(-BounceDisplaceConvertDelta * fabs(targetX))) + BounceDisplaceConvertBias *fabs(targetX);
366 | return -resultX;
367 | } else if (targetX > self.contentSize.width - self.frame.size.width) {
368 | CGFloat displace = targetX - (self.contentSize.width - self.frame.size.width);
369 | CGFloat resultDisplace = BounceDisplaceConvertC *(1/BounceDisplaceConvertDelta) * (1 - exp(-BounceDisplaceConvertDelta * fabs(displace))) + BounceDisplaceConvertBias * fabs(displace);
370 | CGFloat resultX = (self.contentSize.width - self.frame.size.width) + resultDisplace;
371 | return resultX;
372 | } else {
373 | return targetX;
374 | }
375 | }
376 |
377 | - (CGFloat)getBouncesYOf:(CGFloat)targetY
378 | {
379 | if (targetY < 0.f) {
380 | CGFloat resultY = BounceDisplaceConvertC * (1/BounceDisplaceConvertDelta) * (1 - exp(-BounceDisplaceConvertDelta * fabs(targetY))) + BounceDisplaceConvertBias *fabs(targetY);
381 | return -resultY;
382 | } else if (targetY > self.contentSize.height - self.frame.size.height) {
383 | CGFloat displace = targetY - (self.contentSize.height - self.frame.size.height);
384 | CGFloat resultDisplace = BounceDisplaceConvertC * (1/BounceDisplaceConvertDelta) * (1 - exp(-BounceDisplaceConvertDelta * fabs(displace))) + BounceDisplaceConvertBias * fabs(displace);
385 | CGFloat resultY = (self.contentSize.height - self.frame.size.height) + resultDisplace;
386 | return resultY;
387 | } else {
388 | return targetY;
389 | }
390 | }
391 |
392 | - (void)setIsTracking:(BOOL)isTracking
393 | {
394 | _isTracking = isTracking;
395 | if (_isTracking != self.tracking) {
396 | self.tracking = _isTracking;
397 | }
398 | }
399 |
400 | - (void)setTracking:(BOOL)tracking
401 | {
402 | _tracking = tracking;
403 | if (tracking) {
404 | //停止所有动画
405 | self.velocity = CGPointZero;
406 |
407 | } else {
408 | //检查有没有没做完的动画
409 | CGPoint velocity = [self.panGesture velocityInView:self];
410 |
411 | CGFloat velocityX = velocity.x;
412 | if (self.contentSize.width <= self.bounds.size.width) {
413 | velocityX = 0.f;
414 | }
415 |
416 | if (self.contentOffset.x < 0 || self.contentOffset.x > self.contentSize.width) {
417 | velocityX = 0.f;
418 | }
419 |
420 | CGFloat velocityY = velocity.y;
421 | if (self.contentSize.height <= self.bounds.size.height) {
422 | velocityY = 0.f;
423 | }
424 |
425 | if (self.contentOffset.y < 0 || self.contentOffset.y > self.contentSize.height) {
426 | velocityY = 0.f;
427 | }
428 |
429 | self.velocity = CGPointMake(- velocityX, - velocityY);
430 |
431 | //NSLog(@"%lf,%lf",velocity.x, velocity.y);
432 | }
433 | }
434 |
435 | @end
436 |
437 |
--------------------------------------------------------------------------------
/LNCustomScrollView/LNCustomScrollView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A598D8042884611A0013A7C1 /* LNRestMomentumDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = A598D7FD2884611A0013A7C1 /* LNRestMomentumDetector.m */; };
11 | A598D8052884611A0013A7C1 /* LNVelocityDynamicItem.m in Sources */ = {isa = PBXBuildFile; fileRef = A598D7FE2884611A0013A7C1 /* LNVelocityDynamicItem.m */; };
12 | A598D8062884611A0013A7C1 /* LNMomentumImpulser.m in Sources */ = {isa = PBXBuildFile; fileRef = A598D8002884611A0013A7C1 /* LNMomentumImpulser.m */; };
13 | A598D8072884611A0013A7C1 /* LNMomentumObj.m in Sources */ = {isa = PBXBuildFile; fileRef = A598D8012884611A0013A7C1 /* LNMomentumObj.m */; };
14 | A598D80D288461320013A7C1 /* MomentumTransmitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A598D80C288461320013A7C1 /* MomentumTransmitViewController.m */; };
15 | A59CF881261ABFEB00905EF3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF880261ABFEB00905EF3 /* AppDelegate.m */; };
16 | A59CF88A261ABFEB00905EF3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A59CF888261ABFEB00905EF3 /* Main.storyboard */; };
17 | A59CF88C261ABFEC00905EF3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A59CF88B261ABFEC00905EF3 /* Assets.xcassets */; };
18 | A59CF88F261ABFEC00905EF3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A59CF88D261ABFEC00905EF3 /* LaunchScreen.storyboard */; };
19 | A59CF892261ABFEC00905EF3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF891261ABFEC00905EF3 /* main.m */; };
20 | A59CF89C261ABFEC00905EF3 /* LNCustomScrollViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF89B261ABFEC00905EF3 /* LNCustomScrollViewTests.m */; };
21 | A59CF8A7261ABFEC00905EF3 /* LNCustomScrollViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8A6261ABFEC00905EF3 /* LNCustomScrollViewUITests.m */; };
22 | A59CF8C5261AC21600905EF3 /* LNCustomScrollViewClockProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8C4261AC21600905EF3 /* LNCustomScrollViewClockProxy.m */; };
23 | A59CF8CE261AC24200905EF3 /* LNCustomScrollViewClock.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8CD261AC24200905EF3 /* LNCustomScrollViewClock.m */; };
24 | A59CF8D4261AC25200905EF3 /* LNCustomScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8D3261AC25200905EF3 /* LNCustomScrollView.m */; };
25 | A59CF8E6261AC44400905EF3 /* BouncesOptimizationTrainer.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8E5261AC44400905EF3 /* BouncesOptimizationTrainer.m */; };
26 | A59CF8ED261AC81300905EF3 /* BouncesOptimizationDataList.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8EC261AC81300905EF3 /* BouncesOptimizationDataList.m */; };
27 | A59CF8F7261AC88100905EF3 /* DemoCommonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8F6261AC88100905EF3 /* DemoCommonCell.m */; };
28 | A59CF900261AC99200905EF3 /* DecelerateObservationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF8FF261AC99200905EF3 /* DecelerateObservationViewController.m */; };
29 | A59CF906261AC9B900905EF3 /* BouncesObservationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF905261AC9B900905EF3 /* BouncesObservationViewController.m */; };
30 | A59CF90C261AC9D200905EF3 /* BouncesOptimizationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF90B261AC9D200905EF3 /* BouncesOptimizationViewController.m */; };
31 | A59CF912261AC9EF00905EF3 /* PanGestureObservationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF911261AC9EF00905EF3 /* PanGestureObservationViewController.m */; };
32 | A59CF918261ACA1000905EF3 /* CustomScrollViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF917261ACA1000905EF3 /* CustomScrollViewViewController.m */; };
33 | A59CF91E261ACA5A00905EF3 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF91D261ACA5A00905EF3 /* MainViewController.m */; };
34 | A59CF924261ACA6700905EF3 /* DemoItemObj.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF923261ACA6700905EF3 /* DemoItemObj.m */; };
35 | A59CF92A261ACA7E00905EF3 /* DemoItemCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A59CF929261ACA7E00905EF3 /* DemoItemCollectionViewCell.m */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXContainerItemProxy section */
39 | A59CF898261ABFEC00905EF3 /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = A59CF874261ABFEB00905EF3 /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = A59CF87B261ABFEB00905EF3;
44 | remoteInfo = LNCustomScrollView;
45 | };
46 | A59CF8A3261ABFEC00905EF3 /* PBXContainerItemProxy */ = {
47 | isa = PBXContainerItemProxy;
48 | containerPortal = A59CF874261ABFEB00905EF3 /* Project object */;
49 | proxyType = 1;
50 | remoteGlobalIDString = A59CF87B261ABFEB00905EF3;
51 | remoteInfo = LNCustomScrollView;
52 | };
53 | /* End PBXContainerItemProxy section */
54 |
55 | /* Begin PBXFileReference section */
56 | A598D7FC2884611A0013A7C1 /* LNVelocityDynamicItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNVelocityDynamicItem.h; sourceTree = ""; };
57 | A598D7FD2884611A0013A7C1 /* LNRestMomentumDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNRestMomentumDetector.m; sourceTree = ""; };
58 | A598D7FE2884611A0013A7C1 /* LNVelocityDynamicItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNVelocityDynamicItem.m; sourceTree = ""; };
59 | A598D7FF2884611A0013A7C1 /* LNRestMomentumDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNRestMomentumDetector.h; sourceTree = ""; };
60 | A598D8002884611A0013A7C1 /* LNMomentumImpulser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNMomentumImpulser.m; sourceTree = ""; };
61 | A598D8012884611A0013A7C1 /* LNMomentumObj.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNMomentumObj.m; sourceTree = ""; };
62 | A598D8022884611A0013A7C1 /* LNMomentumImpulser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNMomentumImpulser.h; sourceTree = ""; };
63 | A598D8032884611A0013A7C1 /* LNMomentumObj.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNMomentumObj.h; sourceTree = ""; };
64 | A598D80B288461320013A7C1 /* MomentumTransmitViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MomentumTransmitViewController.h; sourceTree = ""; };
65 | A598D80C288461320013A7C1 /* MomentumTransmitViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MomentumTransmitViewController.m; sourceTree = ""; };
66 | A59CF87C261ABFEB00905EF3 /* LNCustomScrollView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LNCustomScrollView.app; sourceTree = BUILT_PRODUCTS_DIR; };
67 | A59CF87F261ABFEB00905EF3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
68 | A59CF880261ABFEB00905EF3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
69 | A59CF889261ABFEB00905EF3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
70 | A59CF88B261ABFEC00905EF3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
71 | A59CF88E261ABFEC00905EF3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
72 | A59CF890261ABFEC00905EF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
73 | A59CF891261ABFEC00905EF3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
74 | A59CF897261ABFEC00905EF3 /* LNCustomScrollViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LNCustomScrollViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
75 | A59CF89B261ABFEC00905EF3 /* LNCustomScrollViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNCustomScrollViewTests.m; sourceTree = ""; };
76 | A59CF89D261ABFEC00905EF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
77 | A59CF8A2261ABFEC00905EF3 /* LNCustomScrollViewUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LNCustomScrollViewUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
78 | A59CF8A6261ABFEC00905EF3 /* LNCustomScrollViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNCustomScrollViewUITests.m; sourceTree = ""; };
79 | A59CF8A8261ABFEC00905EF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
80 | A59CF8C3261AC21600905EF3 /* LNCustomScrollViewClockProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNCustomScrollViewClockProxy.h; sourceTree = ""; };
81 | A59CF8C4261AC21600905EF3 /* LNCustomScrollViewClockProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNCustomScrollViewClockProxy.m; sourceTree = ""; };
82 | A59CF8CC261AC24200905EF3 /* LNCustomScrollViewClock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNCustomScrollViewClock.h; sourceTree = ""; };
83 | A59CF8CD261AC24200905EF3 /* LNCustomScrollViewClock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNCustomScrollViewClock.m; sourceTree = ""; };
84 | A59CF8D2261AC25200905EF3 /* LNCustomScrollView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNCustomScrollView.h; sourceTree = ""; };
85 | A59CF8D3261AC25200905EF3 /* LNCustomScrollView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNCustomScrollView.m; sourceTree = ""; };
86 | A59CF8E4261AC44400905EF3 /* BouncesOptimizationTrainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BouncesOptimizationTrainer.h; sourceTree = ""; };
87 | A59CF8E5261AC44400905EF3 /* BouncesOptimizationTrainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BouncesOptimizationTrainer.m; sourceTree = ""; };
88 | A59CF8EB261AC81300905EF3 /* BouncesOptimizationDataList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BouncesOptimizationDataList.h; sourceTree = ""; };
89 | A59CF8EC261AC81300905EF3 /* BouncesOptimizationDataList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BouncesOptimizationDataList.m; sourceTree = ""; };
90 | A59CF8F5261AC88100905EF3 /* DemoCommonCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoCommonCell.h; sourceTree = ""; };
91 | A59CF8F6261AC88100905EF3 /* DemoCommonCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoCommonCell.m; sourceTree = ""; };
92 | A59CF8FE261AC99200905EF3 /* DecelerateObservationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DecelerateObservationViewController.h; sourceTree = ""; };
93 | A59CF8FF261AC99200905EF3 /* DecelerateObservationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DecelerateObservationViewController.m; sourceTree = ""; };
94 | A59CF904261AC9B900905EF3 /* BouncesObservationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BouncesObservationViewController.h; sourceTree = ""; };
95 | A59CF905261AC9B900905EF3 /* BouncesObservationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BouncesObservationViewController.m; sourceTree = ""; };
96 | A59CF90A261AC9D200905EF3 /* BouncesOptimizationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BouncesOptimizationViewController.h; sourceTree = ""; };
97 | A59CF90B261AC9D200905EF3 /* BouncesOptimizationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BouncesOptimizationViewController.m; sourceTree = ""; };
98 | A59CF910261AC9EF00905EF3 /* PanGestureObservationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PanGestureObservationViewController.h; sourceTree = ""; };
99 | A59CF911261AC9EF00905EF3 /* PanGestureObservationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PanGestureObservationViewController.m; sourceTree = ""; };
100 | A59CF916261ACA1000905EF3 /* CustomScrollViewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomScrollViewViewController.h; sourceTree = ""; };
101 | A59CF917261ACA1000905EF3 /* CustomScrollViewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomScrollViewViewController.m; sourceTree = ""; };
102 | A59CF91C261ACA5A00905EF3 /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; };
103 | A59CF91D261ACA5A00905EF3 /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; };
104 | A59CF922261ACA6700905EF3 /* DemoItemObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoItemObj.h; sourceTree = ""; };
105 | A59CF923261ACA6700905EF3 /* DemoItemObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoItemObj.m; sourceTree = ""; };
106 | A59CF928261ACA7E00905EF3 /* DemoItemCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoItemCollectionViewCell.h; sourceTree = ""; };
107 | A59CF929261ACA7E00905EF3 /* DemoItemCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoItemCollectionViewCell.m; sourceTree = ""; };
108 | /* End PBXFileReference section */
109 |
110 | /* Begin PBXFrameworksBuildPhase section */
111 | A59CF879261ABFEB00905EF3 /* Frameworks */ = {
112 | isa = PBXFrameworksBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | );
116 | runOnlyForDeploymentPostprocessing = 0;
117 | };
118 | A59CF894261ABFEC00905EF3 /* Frameworks */ = {
119 | isa = PBXFrameworksBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | A59CF89F261ABFEC00905EF3 /* Frameworks */ = {
126 | isa = PBXFrameworksBuildPhase;
127 | buildActionMask = 2147483647;
128 | files = (
129 | );
130 | runOnlyForDeploymentPostprocessing = 0;
131 | };
132 | /* End PBXFrameworksBuildPhase section */
133 |
134 | /* Begin PBXGroup section */
135 | A598D7FB288460FC0013A7C1 /* Momentum */ = {
136 | isa = PBXGroup;
137 | children = (
138 | A598D8022884611A0013A7C1 /* LNMomentumImpulser.h */,
139 | A598D8002884611A0013A7C1 /* LNMomentumImpulser.m */,
140 | A598D8032884611A0013A7C1 /* LNMomentumObj.h */,
141 | A598D8012884611A0013A7C1 /* LNMomentumObj.m */,
142 | A598D7FF2884611A0013A7C1 /* LNRestMomentumDetector.h */,
143 | A598D7FD2884611A0013A7C1 /* LNRestMomentumDetector.m */,
144 | A598D7FC2884611A0013A7C1 /* LNVelocityDynamicItem.h */,
145 | A598D7FE2884611A0013A7C1 /* LNVelocityDynamicItem.m */,
146 | );
147 | path = Momentum;
148 | sourceTree = "";
149 | };
150 | A59CF873261ABFEB00905EF3 = {
151 | isa = PBXGroup;
152 | children = (
153 | A59CF87E261ABFEB00905EF3 /* LNCustomScrollView */,
154 | A59CF89A261ABFEC00905EF3 /* LNCustomScrollViewTests */,
155 | A59CF8A5261ABFEC00905EF3 /* LNCustomScrollViewUITests */,
156 | A59CF87D261ABFEB00905EF3 /* Products */,
157 | );
158 | sourceTree = "";
159 | };
160 | A59CF87D261ABFEB00905EF3 /* Products */ = {
161 | isa = PBXGroup;
162 | children = (
163 | A59CF87C261ABFEB00905EF3 /* LNCustomScrollView.app */,
164 | A59CF897261ABFEC00905EF3 /* LNCustomScrollViewTests.xctest */,
165 | A59CF8A2261ABFEC00905EF3 /* LNCustomScrollViewUITests.xctest */,
166 | );
167 | name = Products;
168 | sourceTree = "";
169 | };
170 | A59CF87E261ABFEB00905EF3 /* LNCustomScrollView */ = {
171 | isa = PBXGroup;
172 | children = (
173 | A59CF8C2261AC1F800905EF3 /* CustomScrollView */,
174 | A59CF8BE261AC16A00905EF3 /* Demo */,
175 | A59CF8BD261AC14A00905EF3 /* Main */,
176 | A59CF87F261ABFEB00905EF3 /* AppDelegate.h */,
177 | A59CF880261ABFEB00905EF3 /* AppDelegate.m */,
178 | A59CF888261ABFEB00905EF3 /* Main.storyboard */,
179 | A59CF88B261ABFEC00905EF3 /* Assets.xcassets */,
180 | A59CF88D261ABFEC00905EF3 /* LaunchScreen.storyboard */,
181 | A59CF890261ABFEC00905EF3 /* Info.plist */,
182 | A59CF891261ABFEC00905EF3 /* main.m */,
183 | );
184 | path = LNCustomScrollView;
185 | sourceTree = "";
186 | };
187 | A59CF89A261ABFEC00905EF3 /* LNCustomScrollViewTests */ = {
188 | isa = PBXGroup;
189 | children = (
190 | A59CF89B261ABFEC00905EF3 /* LNCustomScrollViewTests.m */,
191 | A59CF89D261ABFEC00905EF3 /* Info.plist */,
192 | );
193 | path = LNCustomScrollViewTests;
194 | sourceTree = "";
195 | };
196 | A59CF8A5261ABFEC00905EF3 /* LNCustomScrollViewUITests */ = {
197 | isa = PBXGroup;
198 | children = (
199 | A59CF8A6261ABFEC00905EF3 /* LNCustomScrollViewUITests.m */,
200 | A59CF8A8261ABFEC00905EF3 /* Info.plist */,
201 | );
202 | path = LNCustomScrollViewUITests;
203 | sourceTree = "";
204 | };
205 | A59CF8BD261AC14A00905EF3 /* Main */ = {
206 | isa = PBXGroup;
207 | children = (
208 | A59CF91C261ACA5A00905EF3 /* MainViewController.h */,
209 | A59CF91D261ACA5A00905EF3 /* MainViewController.m */,
210 | A59CF922261ACA6700905EF3 /* DemoItemObj.h */,
211 | A59CF923261ACA6700905EF3 /* DemoItemObj.m */,
212 | A59CF928261ACA7E00905EF3 /* DemoItemCollectionViewCell.h */,
213 | A59CF929261ACA7E00905EF3 /* DemoItemCollectionViewCell.m */,
214 | );
215 | path = Main;
216 | sourceTree = "";
217 | };
218 | A59CF8BE261AC16A00905EF3 /* Demo */ = {
219 | isa = PBXGroup;
220 | children = (
221 | A598D7FB288460FC0013A7C1 /* Momentum */,
222 | A59CF8F4261AC86000905EF3 /* CommonCell */,
223 | A59CF8EA261AC70400905EF3 /* BouncesOptimization */,
224 | A59CF8FE261AC99200905EF3 /* DecelerateObservationViewController.h */,
225 | A59CF8FF261AC99200905EF3 /* DecelerateObservationViewController.m */,
226 | A59CF904261AC9B900905EF3 /* BouncesObservationViewController.h */,
227 | A59CF905261AC9B900905EF3 /* BouncesObservationViewController.m */,
228 | A59CF90A261AC9D200905EF3 /* BouncesOptimizationViewController.h */,
229 | A59CF90B261AC9D200905EF3 /* BouncesOptimizationViewController.m */,
230 | A59CF910261AC9EF00905EF3 /* PanGestureObservationViewController.h */,
231 | A59CF911261AC9EF00905EF3 /* PanGestureObservationViewController.m */,
232 | A59CF916261ACA1000905EF3 /* CustomScrollViewViewController.h */,
233 | A59CF917261ACA1000905EF3 /* CustomScrollViewViewController.m */,
234 | A598D80B288461320013A7C1 /* MomentumTransmitViewController.h */,
235 | A598D80C288461320013A7C1 /* MomentumTransmitViewController.m */,
236 | );
237 | path = Demo;
238 | sourceTree = "";
239 | };
240 | A59CF8C2261AC1F800905EF3 /* CustomScrollView */ = {
241 | isa = PBXGroup;
242 | children = (
243 | A59CF8C3261AC21600905EF3 /* LNCustomScrollViewClockProxy.h */,
244 | A59CF8C4261AC21600905EF3 /* LNCustomScrollViewClockProxy.m */,
245 | A59CF8CC261AC24200905EF3 /* LNCustomScrollViewClock.h */,
246 | A59CF8CD261AC24200905EF3 /* LNCustomScrollViewClock.m */,
247 | A59CF8D2261AC25200905EF3 /* LNCustomScrollView.h */,
248 | A59CF8D3261AC25200905EF3 /* LNCustomScrollView.m */,
249 | );
250 | path = CustomScrollView;
251 | sourceTree = "";
252 | };
253 | A59CF8EA261AC70400905EF3 /* BouncesOptimization */ = {
254 | isa = PBXGroup;
255 | children = (
256 | A59CF8E4261AC44400905EF3 /* BouncesOptimizationTrainer.h */,
257 | A59CF8E5261AC44400905EF3 /* BouncesOptimizationTrainer.m */,
258 | A59CF8EB261AC81300905EF3 /* BouncesOptimizationDataList.h */,
259 | A59CF8EC261AC81300905EF3 /* BouncesOptimizationDataList.m */,
260 | );
261 | path = BouncesOptimization;
262 | sourceTree = "";
263 | };
264 | A59CF8F4261AC86000905EF3 /* CommonCell */ = {
265 | isa = PBXGroup;
266 | children = (
267 | A59CF8F5261AC88100905EF3 /* DemoCommonCell.h */,
268 | A59CF8F6261AC88100905EF3 /* DemoCommonCell.m */,
269 | );
270 | path = CommonCell;
271 | sourceTree = "";
272 | };
273 | /* End PBXGroup section */
274 |
275 | /* Begin PBXNativeTarget section */
276 | A59CF87B261ABFEB00905EF3 /* LNCustomScrollView */ = {
277 | isa = PBXNativeTarget;
278 | buildConfigurationList = A59CF8AB261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollView" */;
279 | buildPhases = (
280 | A59CF878261ABFEB00905EF3 /* Sources */,
281 | A59CF879261ABFEB00905EF3 /* Frameworks */,
282 | A59CF87A261ABFEB00905EF3 /* Resources */,
283 | );
284 | buildRules = (
285 | );
286 | dependencies = (
287 | );
288 | name = LNCustomScrollView;
289 | productName = LNCustomScrollView;
290 | productReference = A59CF87C261ABFEB00905EF3 /* LNCustomScrollView.app */;
291 | productType = "com.apple.product-type.application";
292 | };
293 | A59CF896261ABFEC00905EF3 /* LNCustomScrollViewTests */ = {
294 | isa = PBXNativeTarget;
295 | buildConfigurationList = A59CF8AE261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollViewTests" */;
296 | buildPhases = (
297 | A59CF893261ABFEC00905EF3 /* Sources */,
298 | A59CF894261ABFEC00905EF3 /* Frameworks */,
299 | A59CF895261ABFEC00905EF3 /* Resources */,
300 | );
301 | buildRules = (
302 | );
303 | dependencies = (
304 | A59CF899261ABFEC00905EF3 /* PBXTargetDependency */,
305 | );
306 | name = LNCustomScrollViewTests;
307 | productName = LNCustomScrollViewTests;
308 | productReference = A59CF897261ABFEC00905EF3 /* LNCustomScrollViewTests.xctest */;
309 | productType = "com.apple.product-type.bundle.unit-test";
310 | };
311 | A59CF8A1261ABFEC00905EF3 /* LNCustomScrollViewUITests */ = {
312 | isa = PBXNativeTarget;
313 | buildConfigurationList = A59CF8B1261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollViewUITests" */;
314 | buildPhases = (
315 | A59CF89E261ABFEC00905EF3 /* Sources */,
316 | A59CF89F261ABFEC00905EF3 /* Frameworks */,
317 | A59CF8A0261ABFEC00905EF3 /* Resources */,
318 | );
319 | buildRules = (
320 | );
321 | dependencies = (
322 | A59CF8A4261ABFEC00905EF3 /* PBXTargetDependency */,
323 | );
324 | name = LNCustomScrollViewUITests;
325 | productName = LNCustomScrollViewUITests;
326 | productReference = A59CF8A2261ABFEC00905EF3 /* LNCustomScrollViewUITests.xctest */;
327 | productType = "com.apple.product-type.bundle.ui-testing";
328 | };
329 | /* End PBXNativeTarget section */
330 |
331 | /* Begin PBXProject section */
332 | A59CF874261ABFEB00905EF3 /* Project object */ = {
333 | isa = PBXProject;
334 | attributes = {
335 | LastUpgradeCheck = 1240;
336 | ORGANIZATIONNAME = Levison;
337 | TargetAttributes = {
338 | A59CF87B261ABFEB00905EF3 = {
339 | CreatedOnToolsVersion = 12.4;
340 | };
341 | A59CF896261ABFEC00905EF3 = {
342 | CreatedOnToolsVersion = 12.4;
343 | TestTargetID = A59CF87B261ABFEB00905EF3;
344 | };
345 | A59CF8A1261ABFEC00905EF3 = {
346 | CreatedOnToolsVersion = 12.4;
347 | TestTargetID = A59CF87B261ABFEB00905EF3;
348 | };
349 | };
350 | };
351 | buildConfigurationList = A59CF877261ABFEB00905EF3 /* Build configuration list for PBXProject "LNCustomScrollView" */;
352 | compatibilityVersion = "Xcode 9.3";
353 | developmentRegion = en;
354 | hasScannedForEncodings = 0;
355 | knownRegions = (
356 | en,
357 | Base,
358 | );
359 | mainGroup = A59CF873261ABFEB00905EF3;
360 | productRefGroup = A59CF87D261ABFEB00905EF3 /* Products */;
361 | projectDirPath = "";
362 | projectRoot = "";
363 | targets = (
364 | A59CF87B261ABFEB00905EF3 /* LNCustomScrollView */,
365 | A59CF896261ABFEC00905EF3 /* LNCustomScrollViewTests */,
366 | A59CF8A1261ABFEC00905EF3 /* LNCustomScrollViewUITests */,
367 | );
368 | };
369 | /* End PBXProject section */
370 |
371 | /* Begin PBXResourcesBuildPhase section */
372 | A59CF87A261ABFEB00905EF3 /* Resources */ = {
373 | isa = PBXResourcesBuildPhase;
374 | buildActionMask = 2147483647;
375 | files = (
376 | A59CF88F261ABFEC00905EF3 /* LaunchScreen.storyboard in Resources */,
377 | A59CF88C261ABFEC00905EF3 /* Assets.xcassets in Resources */,
378 | A59CF88A261ABFEB00905EF3 /* Main.storyboard in Resources */,
379 | );
380 | runOnlyForDeploymentPostprocessing = 0;
381 | };
382 | A59CF895261ABFEC00905EF3 /* Resources */ = {
383 | isa = PBXResourcesBuildPhase;
384 | buildActionMask = 2147483647;
385 | files = (
386 | );
387 | runOnlyForDeploymentPostprocessing = 0;
388 | };
389 | A59CF8A0261ABFEC00905EF3 /* Resources */ = {
390 | isa = PBXResourcesBuildPhase;
391 | buildActionMask = 2147483647;
392 | files = (
393 | );
394 | runOnlyForDeploymentPostprocessing = 0;
395 | };
396 | /* End PBXResourcesBuildPhase section */
397 |
398 | /* Begin PBXSourcesBuildPhase section */
399 | A59CF878261ABFEB00905EF3 /* Sources */ = {
400 | isa = PBXSourcesBuildPhase;
401 | buildActionMask = 2147483647;
402 | files = (
403 | A59CF8D4261AC25200905EF3 /* LNCustomScrollView.m in Sources */,
404 | A598D8052884611A0013A7C1 /* LNVelocityDynamicItem.m in Sources */,
405 | A59CF924261ACA6700905EF3 /* DemoItemObj.m in Sources */,
406 | A59CF92A261ACA7E00905EF3 /* DemoItemCollectionViewCell.m in Sources */,
407 | A59CF918261ACA1000905EF3 /* CustomScrollViewViewController.m in Sources */,
408 | A59CF8F7261AC88100905EF3 /* DemoCommonCell.m in Sources */,
409 | A598D8042884611A0013A7C1 /* LNRestMomentumDetector.m in Sources */,
410 | A59CF881261ABFEB00905EF3 /* AppDelegate.m in Sources */,
411 | A59CF906261AC9B900905EF3 /* BouncesObservationViewController.m in Sources */,
412 | A598D8072884611A0013A7C1 /* LNMomentumObj.m in Sources */,
413 | A59CF8E6261AC44400905EF3 /* BouncesOptimizationTrainer.m in Sources */,
414 | A59CF912261AC9EF00905EF3 /* PanGestureObservationViewController.m in Sources */,
415 | A59CF90C261AC9D200905EF3 /* BouncesOptimizationViewController.m in Sources */,
416 | A59CF8ED261AC81300905EF3 /* BouncesOptimizationDataList.m in Sources */,
417 | A59CF8C5261AC21600905EF3 /* LNCustomScrollViewClockProxy.m in Sources */,
418 | A598D80D288461320013A7C1 /* MomentumTransmitViewController.m in Sources */,
419 | A59CF892261ABFEC00905EF3 /* main.m in Sources */,
420 | A59CF91E261ACA5A00905EF3 /* MainViewController.m in Sources */,
421 | A598D8062884611A0013A7C1 /* LNMomentumImpulser.m in Sources */,
422 | A59CF8CE261AC24200905EF3 /* LNCustomScrollViewClock.m in Sources */,
423 | A59CF900261AC99200905EF3 /* DecelerateObservationViewController.m in Sources */,
424 | );
425 | runOnlyForDeploymentPostprocessing = 0;
426 | };
427 | A59CF893261ABFEC00905EF3 /* Sources */ = {
428 | isa = PBXSourcesBuildPhase;
429 | buildActionMask = 2147483647;
430 | files = (
431 | A59CF89C261ABFEC00905EF3 /* LNCustomScrollViewTests.m in Sources */,
432 | );
433 | runOnlyForDeploymentPostprocessing = 0;
434 | };
435 | A59CF89E261ABFEC00905EF3 /* Sources */ = {
436 | isa = PBXSourcesBuildPhase;
437 | buildActionMask = 2147483647;
438 | files = (
439 | A59CF8A7261ABFEC00905EF3 /* LNCustomScrollViewUITests.m in Sources */,
440 | );
441 | runOnlyForDeploymentPostprocessing = 0;
442 | };
443 | /* End PBXSourcesBuildPhase section */
444 |
445 | /* Begin PBXTargetDependency section */
446 | A59CF899261ABFEC00905EF3 /* PBXTargetDependency */ = {
447 | isa = PBXTargetDependency;
448 | target = A59CF87B261ABFEB00905EF3 /* LNCustomScrollView */;
449 | targetProxy = A59CF898261ABFEC00905EF3 /* PBXContainerItemProxy */;
450 | };
451 | A59CF8A4261ABFEC00905EF3 /* PBXTargetDependency */ = {
452 | isa = PBXTargetDependency;
453 | target = A59CF87B261ABFEB00905EF3 /* LNCustomScrollView */;
454 | targetProxy = A59CF8A3261ABFEC00905EF3 /* PBXContainerItemProxy */;
455 | };
456 | /* End PBXTargetDependency section */
457 |
458 | /* Begin PBXVariantGroup section */
459 | A59CF888261ABFEB00905EF3 /* Main.storyboard */ = {
460 | isa = PBXVariantGroup;
461 | children = (
462 | A59CF889261ABFEB00905EF3 /* Base */,
463 | );
464 | name = Main.storyboard;
465 | sourceTree = "";
466 | };
467 | A59CF88D261ABFEC00905EF3 /* LaunchScreen.storyboard */ = {
468 | isa = PBXVariantGroup;
469 | children = (
470 | A59CF88E261ABFEC00905EF3 /* Base */,
471 | );
472 | name = LaunchScreen.storyboard;
473 | sourceTree = "";
474 | };
475 | /* End PBXVariantGroup section */
476 |
477 | /* Begin XCBuildConfiguration section */
478 | A59CF8A9261ABFEC00905EF3 /* Debug */ = {
479 | isa = XCBuildConfiguration;
480 | buildSettings = {
481 | ALWAYS_SEARCH_USER_PATHS = NO;
482 | CLANG_ANALYZER_NONNULL = YES;
483 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
484 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
485 | CLANG_CXX_LIBRARY = "libc++";
486 | CLANG_ENABLE_MODULES = YES;
487 | CLANG_ENABLE_OBJC_ARC = YES;
488 | CLANG_ENABLE_OBJC_WEAK = YES;
489 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
490 | CLANG_WARN_BOOL_CONVERSION = YES;
491 | CLANG_WARN_COMMA = YES;
492 | CLANG_WARN_CONSTANT_CONVERSION = YES;
493 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
494 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
495 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
496 | CLANG_WARN_EMPTY_BODY = YES;
497 | CLANG_WARN_ENUM_CONVERSION = YES;
498 | CLANG_WARN_INFINITE_RECURSION = YES;
499 | CLANG_WARN_INT_CONVERSION = YES;
500 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
501 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
502 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
503 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
504 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
505 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
506 | CLANG_WARN_STRICT_PROTOTYPES = YES;
507 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
508 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
509 | CLANG_WARN_UNREACHABLE_CODE = YES;
510 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
511 | COPY_PHASE_STRIP = NO;
512 | DEBUG_INFORMATION_FORMAT = dwarf;
513 | ENABLE_STRICT_OBJC_MSGSEND = YES;
514 | ENABLE_TESTABILITY = YES;
515 | GCC_C_LANGUAGE_STANDARD = gnu11;
516 | GCC_DYNAMIC_NO_PIC = NO;
517 | GCC_NO_COMMON_BLOCKS = YES;
518 | GCC_OPTIMIZATION_LEVEL = 0;
519 | GCC_PREPROCESSOR_DEFINITIONS = (
520 | "DEBUG=1",
521 | "$(inherited)",
522 | );
523 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
524 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
525 | GCC_WARN_UNDECLARED_SELECTOR = YES;
526 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
527 | GCC_WARN_UNUSED_FUNCTION = YES;
528 | GCC_WARN_UNUSED_VARIABLE = YES;
529 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
530 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
531 | MTL_FAST_MATH = YES;
532 | ONLY_ACTIVE_ARCH = YES;
533 | SDKROOT = iphoneos;
534 | };
535 | name = Debug;
536 | };
537 | A59CF8AA261ABFEC00905EF3 /* Release */ = {
538 | isa = XCBuildConfiguration;
539 | buildSettings = {
540 | ALWAYS_SEARCH_USER_PATHS = NO;
541 | CLANG_ANALYZER_NONNULL = YES;
542 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
543 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
544 | CLANG_CXX_LIBRARY = "libc++";
545 | CLANG_ENABLE_MODULES = YES;
546 | CLANG_ENABLE_OBJC_ARC = YES;
547 | CLANG_ENABLE_OBJC_WEAK = YES;
548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
549 | CLANG_WARN_BOOL_CONVERSION = YES;
550 | CLANG_WARN_COMMA = YES;
551 | CLANG_WARN_CONSTANT_CONVERSION = YES;
552 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
554 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
555 | CLANG_WARN_EMPTY_BODY = YES;
556 | CLANG_WARN_ENUM_CONVERSION = YES;
557 | CLANG_WARN_INFINITE_RECURSION = YES;
558 | CLANG_WARN_INT_CONVERSION = YES;
559 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
560 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
561 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
562 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
563 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
564 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
565 | CLANG_WARN_STRICT_PROTOTYPES = YES;
566 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
567 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
568 | CLANG_WARN_UNREACHABLE_CODE = YES;
569 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
570 | COPY_PHASE_STRIP = NO;
571 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
572 | ENABLE_NS_ASSERTIONS = NO;
573 | ENABLE_STRICT_OBJC_MSGSEND = YES;
574 | GCC_C_LANGUAGE_STANDARD = gnu11;
575 | GCC_NO_COMMON_BLOCKS = YES;
576 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
577 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
578 | GCC_WARN_UNDECLARED_SELECTOR = YES;
579 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
580 | GCC_WARN_UNUSED_FUNCTION = YES;
581 | GCC_WARN_UNUSED_VARIABLE = YES;
582 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
583 | MTL_ENABLE_DEBUG_INFO = NO;
584 | MTL_FAST_MATH = YES;
585 | SDKROOT = iphoneos;
586 | VALIDATE_PRODUCT = YES;
587 | };
588 | name = Release;
589 | };
590 | A59CF8AC261ABFEC00905EF3 /* Debug */ = {
591 | isa = XCBuildConfiguration;
592 | buildSettings = {
593 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
594 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
595 | CODE_SIGN_STYLE = Automatic;
596 | DEVELOPMENT_TEAM = NZ8VB65G3E;
597 | INFOPLIST_FILE = LNCustomScrollView/Info.plist;
598 | LD_RUNPATH_SEARCH_PATHS = (
599 | "$(inherited)",
600 | "@executable_path/Frameworks",
601 | );
602 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollView;
603 | PRODUCT_NAME = "$(TARGET_NAME)";
604 | TARGETED_DEVICE_FAMILY = "1,2";
605 | };
606 | name = Debug;
607 | };
608 | A59CF8AD261ABFEC00905EF3 /* Release */ = {
609 | isa = XCBuildConfiguration;
610 | buildSettings = {
611 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
612 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
613 | CODE_SIGN_STYLE = Automatic;
614 | DEVELOPMENT_TEAM = NZ8VB65G3E;
615 | INFOPLIST_FILE = LNCustomScrollView/Info.plist;
616 | LD_RUNPATH_SEARCH_PATHS = (
617 | "$(inherited)",
618 | "@executable_path/Frameworks",
619 | );
620 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollView;
621 | PRODUCT_NAME = "$(TARGET_NAME)";
622 | TARGETED_DEVICE_FAMILY = "1,2";
623 | };
624 | name = Release;
625 | };
626 | A59CF8AF261ABFEC00905EF3 /* Debug */ = {
627 | isa = XCBuildConfiguration;
628 | buildSettings = {
629 | BUNDLE_LOADER = "$(TEST_HOST)";
630 | CODE_SIGN_STYLE = Automatic;
631 | DEVELOPMENT_TEAM = NZ8VB65G3E;
632 | INFOPLIST_FILE = LNCustomScrollViewTests/Info.plist;
633 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
634 | LD_RUNPATH_SEARCH_PATHS = (
635 | "$(inherited)",
636 | "@executable_path/Frameworks",
637 | "@loader_path/Frameworks",
638 | );
639 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollViewTests;
640 | PRODUCT_NAME = "$(TARGET_NAME)";
641 | TARGETED_DEVICE_FAMILY = "1,2";
642 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LNCustomScrollView.app/LNCustomScrollView";
643 | };
644 | name = Debug;
645 | };
646 | A59CF8B0261ABFEC00905EF3 /* Release */ = {
647 | isa = XCBuildConfiguration;
648 | buildSettings = {
649 | BUNDLE_LOADER = "$(TEST_HOST)";
650 | CODE_SIGN_STYLE = Automatic;
651 | DEVELOPMENT_TEAM = NZ8VB65G3E;
652 | INFOPLIST_FILE = LNCustomScrollViewTests/Info.plist;
653 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
654 | LD_RUNPATH_SEARCH_PATHS = (
655 | "$(inherited)",
656 | "@executable_path/Frameworks",
657 | "@loader_path/Frameworks",
658 | );
659 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollViewTests;
660 | PRODUCT_NAME = "$(TARGET_NAME)";
661 | TARGETED_DEVICE_FAMILY = "1,2";
662 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LNCustomScrollView.app/LNCustomScrollView";
663 | };
664 | name = Release;
665 | };
666 | A59CF8B2261ABFEC00905EF3 /* Debug */ = {
667 | isa = XCBuildConfiguration;
668 | buildSettings = {
669 | CODE_SIGN_STYLE = Automatic;
670 | DEVELOPMENT_TEAM = NZ8VB65G3E;
671 | INFOPLIST_FILE = LNCustomScrollViewUITests/Info.plist;
672 | LD_RUNPATH_SEARCH_PATHS = (
673 | "$(inherited)",
674 | "@executable_path/Frameworks",
675 | "@loader_path/Frameworks",
676 | );
677 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollViewUITests;
678 | PRODUCT_NAME = "$(TARGET_NAME)";
679 | TARGETED_DEVICE_FAMILY = "1,2";
680 | TEST_TARGET_NAME = LNCustomScrollView;
681 | };
682 | name = Debug;
683 | };
684 | A59CF8B3261ABFEC00905EF3 /* Release */ = {
685 | isa = XCBuildConfiguration;
686 | buildSettings = {
687 | CODE_SIGN_STYLE = Automatic;
688 | DEVELOPMENT_TEAM = NZ8VB65G3E;
689 | INFOPLIST_FILE = LNCustomScrollViewUITests/Info.plist;
690 | LD_RUNPATH_SEARCH_PATHS = (
691 | "$(inherited)",
692 | "@executable_path/Frameworks",
693 | "@loader_path/Frameworks",
694 | );
695 | PRODUCT_BUNDLE_IDENTIFIER = Levison.LNCustomScrollViewUITests;
696 | PRODUCT_NAME = "$(TARGET_NAME)";
697 | TARGETED_DEVICE_FAMILY = "1,2";
698 | TEST_TARGET_NAME = LNCustomScrollView;
699 | };
700 | name = Release;
701 | };
702 | /* End XCBuildConfiguration section */
703 |
704 | /* Begin XCConfigurationList section */
705 | A59CF877261ABFEB00905EF3 /* Build configuration list for PBXProject "LNCustomScrollView" */ = {
706 | isa = XCConfigurationList;
707 | buildConfigurations = (
708 | A59CF8A9261ABFEC00905EF3 /* Debug */,
709 | A59CF8AA261ABFEC00905EF3 /* Release */,
710 | );
711 | defaultConfigurationIsVisible = 0;
712 | defaultConfigurationName = Release;
713 | };
714 | A59CF8AB261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollView" */ = {
715 | isa = XCConfigurationList;
716 | buildConfigurations = (
717 | A59CF8AC261ABFEC00905EF3 /* Debug */,
718 | A59CF8AD261ABFEC00905EF3 /* Release */,
719 | );
720 | defaultConfigurationIsVisible = 0;
721 | defaultConfigurationName = Release;
722 | };
723 | A59CF8AE261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollViewTests" */ = {
724 | isa = XCConfigurationList;
725 | buildConfigurations = (
726 | A59CF8AF261ABFEC00905EF3 /* Debug */,
727 | A59CF8B0261ABFEC00905EF3 /* Release */,
728 | );
729 | defaultConfigurationIsVisible = 0;
730 | defaultConfigurationName = Release;
731 | };
732 | A59CF8B1261ABFEC00905EF3 /* Build configuration list for PBXNativeTarget "LNCustomScrollViewUITests" */ = {
733 | isa = XCConfigurationList;
734 | buildConfigurations = (
735 | A59CF8B2261ABFEC00905EF3 /* Debug */,
736 | A59CF8B3261ABFEC00905EF3 /* Release */,
737 | );
738 | defaultConfigurationIsVisible = 0;
739 | defaultConfigurationName = Release;
740 | };
741 | /* End XCConfigurationList section */
742 | };
743 | rootObject = A59CF874261ABFEB00905EF3 /* Project object */;
744 | }
745 |
--------------------------------------------------------------------------------