Skip to content

Commit

Permalink
feat(iOS): add iOS custom scroll time
Browse files Browse the repository at this point in the history
  • Loading branch information
v_zxingli committed Sep 6, 2023
1 parent eec0d79 commit 3da1b67
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface DemoCustomScrollViewController : UIViewController

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

#import "DemoCustomScrollViewController.h"
#import "HippyCustomScrollView.h"

@interface DemoCustomScrollViewController () <UITableViewDelegate ,UITableViewDataSource>

@property (nonatomic ,strong) UITableView *tableView;

@end

@implementation DemoCustomScrollViewController

- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}

- (void)click {
[self.tableView setContentOffset:CGPointMake(0, 500) duration:2.25 completion:^{

}];
}

/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/

- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *identifier = @"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
return cell;
}

- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 100;
}

@end
59 changes: 59 additions & 0 deletions framework/examples/ios-demo/HippyDemo/HippyCustomScrollView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

#import <UIKit/UIKit.h>

typedef enum
{
linear = 0,
quadIn,
quadOut,
quadInOut,
cubicIn,
cubicOut,
cubicInOut,
quartIn,
quartOut,
quartInOut,
quintIn,
quintOut,
quintInOut,
sineIn,
sineOut,
sineInOut,
expoIn,
expoOut,
expoInOut,
circleIn,
circleOut,
circleInOut
} HippyScrollTimingEnum;

NS_ASSUME_NONNULL_BEGIN

@class HippyScrollTimingFunction;
@interface UIScrollView (HippyCustomOffsetAnimation)

- (void)setContentOffset:(CGPoint)contentOffset
duration:(NSTimeInterval)duration
completion:(void(^)(void))block;

@end

@interface HippyScrollTimingFunction : NSObject

@property (nonatomic,assign) HippyScrollTimingEnum type;

@end

@interface HippyScrollViewAnimator : NSObject

@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, copy) void(^block)(void);

- (void)setContentOffset:(CGPoint)contentOffset duration:(NSTimeInterval)duration;
- (instancetype)initWithScrollView:(UIScrollView *)scrollView
timingFunction:(HippyScrollTimingFunction *)timingFunction
type:(HippyScrollTimingEnum)type;

@end

NS_ASSUME_NONNULL_END
214 changes: 214 additions & 0 deletions framework/examples/ios-demo/HippyDemo/HippyCustomScrollView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@

#import "HippyCustomScrollView.h"
#import <objc/runtime.h>

@class HippyScrollViewAnimator;
@implementation UIScrollView (HippyCustomOffsetAnimation)

static NSString *HippyCustomAnimatorKey = @"QModelOverlayParamsKey"; //定义一个key值

- (void)setAnimator:(HippyScrollViewAnimator *)animator
{
objc_setAssociatedObject(self, &HippyCustomAnimatorKey, animator, OBJC_ASSOCIATION_RETAIN);
}

- (HippyScrollViewAnimator *)animator
{
return objc_getAssociatedObject(self, &HippyCustomAnimatorKey);
}

- (void)setContentOffset:(CGPoint)contentOffset
duration:(NSTimeInterval)duration
completion:(void(^)(void))block {
if (!self.animator) {
self.animator = [[HippyScrollViewAnimator alloc] initWithScrollView:self timingFunction:[HippyScrollTimingFunction new] type:sineInOut];
}
__weak __typeof(self) weakSelf = self;
self.animator.block = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
dispatch_async(dispatch_get_main_queue(), ^{
if (strongSelf) {
strongSelf.animator = nil;
}
});
block();
};

[self.animator setContentOffset:contentOffset duration:duration];
block();
}

@end

@implementation HippyScrollTimingFunction
///
/// - Parameters:
/// - t: time
/// - b: begin
/// - c: change
/// - d: duration
- (CGFloat)compute:(CGFloat)t b:(CGFloat)b c:(CGFloat)c d:(CGFloat)d {
switch (self.type) {
case linear:
return c * t / d + b;
case quadIn:
t /= d;
return c * t * t + b;
case quadOut:
t /= d;
return -c * t * (t - 2) + b;
case quadInOut:
t /= d / 2;
if (t < 1) {
return c / 2 * t * t + b;
}
t -= 1;
return -c / 2 * (t * (t - 2) - 1) + b;
case cubicIn:
t /= d;
return c * t * t * t + b;
case cubicOut:
t = t / d - 1;
return c * (t * t * t + 1) + b;
case cubicInOut:
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t + b;
}
t -= 2;
return c / 2 * (t * t * t + 2) + b;
case quartIn:
t /= d;
return c * t * t * t * t + b;
case quartOut:
t = t / d - 1;
return -c * (t * t * t * t - 1) + b;
case quartInOut:
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t * t + b;
}
t -= 2;
return -c / 2 * (t * t * t * t - 2) + b;
case quintIn:
t /= d;
return c * t * t * t * t * t + b;
case quintOut:
t = t / d - 1;
return c * ( t * t * t * t * t + 1) + b;
case quintInOut:
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t * t * t + b;
}
t -= 2;
return c / 2 * (t * t * t * t * t + 2) + b;
case sineIn:
return -c * cos(t / d * (M_PI / 2)) + c + b;
case sineOut:
return c * sin(t / d * (M_PI / 2)) + b;
case sineInOut:
return -c / 2 * (cos(M_PI * t / d) - 1) + b;
case expoIn:
return (t == 0) ? b : c * pow(2, 10 * (t / d - 1)) + b;
case expoOut:
return (t == d) ? b + c : c * (-pow(2, -10 * t / d) + 1) + b;
case expoInOut:
if (t == 0) {
return b;
}
if (t == d) {
return b + c;
}
t /= d / 2;
if (t < 1) {
return c / 2 * pow(2, 10 * (t - 1)) + b;
}
t -= 1;
return c / 2 * (-pow(2, -10 * t) + 2) + b;
case circleIn:
t /= d;
return -c * (sqrt(1 - t * t) - 1) + b;
case circleOut:
t = t / d - 1;
return c * sqrt(1 - t * t) + b;
case circleInOut:
t /= d / 2;
if (t < 1) {
return -c / 2 * (sqrt(1 - t * t) - 1) + b;
}
t -= 2;
return c / 2 * (sqrt(1 - t * t) + 1) + b;
}
}

@end

@interface HippyScrollViewAnimator ()

@property (nonatomic, assign) HippyScrollTimingEnum type;
@property (nonatomic, strong) HippyScrollTimingFunction *timingFunction;
@property (nonatomic, assign) NSTimeInterval startTime;
@property (nonatomic, assign) CGPoint startOffset;
@property (nonatomic, assign) CGPoint destinationOffset;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) NSTimeInterval runTime;
@property (nonatomic, strong) CADisplayLink *timer;

@end

@implementation HippyScrollViewAnimator

- (instancetype)initWithScrollView:(UIScrollView *)scrollView
timingFunction:(HippyScrollTimingFunction *)timingFunction
type:(HippyScrollTimingEnum)type {
if (self = [super init]) {
self.scrollView = scrollView;
self.timingFunction = timingFunction;
self.type = type;
}
return self;
}

- (void)setContentOffset:(CGPoint)contentOffset duration:(NSTimeInterval)duration {

if (!self.scrollView) return;

self.startTime = [[NSDate date] timeIntervalSince1970];
self.startOffset = self.scrollView.contentOffset;
self.destinationOffset = contentOffset;
self.duration = duration;
self.runTime = 0;

if (self.duration <= 0) {
[self.scrollView setContentOffset:contentOffset animated:NO];
return;
}
if (!self.timer) {
self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animtedScroll)];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}

- (void)animtedScroll {

if (!self.timer) return;
if (!self.scrollView) return;

self.runTime += self.timer.duration;

if (self.runTime >= self.duration) {
[self.scrollView setContentOffset:self.destinationOffset animated:NO];
[self.timer invalidate];
self.timer = nil;
self.block();
return;
}

CGPoint offset = self.scrollView.contentOffset;
offset.x = [self.timingFunction compute:self.runTime b:self.startOffset.x c:self.destinationOffset.x - self.startOffset.x d:self.duration];
offset.y = [self.timingFunction compute:self.runTime b:self.startOffset.y c:self.destinationOffset.y - self.startOffset.y d:self.duration];
[self.scrollView setContentOffset:offset animated:NO];
}

@end

0 comments on commit 3da1b67

Please sign in to comment.