Commit 4f91ddfa cgx

引入Lottie

1 个父辈 d34fb20f
正在显示 169 个修改的文件 包含 12683 行增加37 行删除
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
D04B3DC327F6F9390022F8DF /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D04B3DC227F6F9390022F8DF /* HomeViewController.m */; }; D04B3DC327F6F9390022F8DF /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D04B3DC227F6F9390022F8DF /* HomeViewController.m */; };
D076C14327F49DC000340B46 /* TmpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D076C14227F49DC000340B46 /* TmpViewController.m */; }; D076C14327F49DC000340B46 /* TmpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D076C14227F49DC000340B46 /* TmpViewController.m */; };
D07F9DE927F4683B0036372F /* DKColorTable.txt in Resources */ = {isa = PBXBuildFile; fileRef = D07F9DE827F4683B0036372F /* DKColorTable.txt */; }; D07F9DE927F4683B0036372F /* DKColorTable.txt in Resources */ = {isa = PBXBuildFile; fileRef = D07F9DE827F4683B0036372F /* DKColorTable.txt */; };
D0930F122801124E006B497A /* BaseNaviController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0930F112801124E006B497A /* BaseNaviController.m */; };
D0B5ECA627F2D9DE003EDFE3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */; }; D0B5ECA627F2D9DE003EDFE3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */; };
D0B5ECAC27F2D9DE003EDFE3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECAB27F2D9DE003EDFE3 /* ViewController.m */; }; D0B5ECAC27F2D9DE003EDFE3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECAB27F2D9DE003EDFE3 /* ViewController.m */; };
D0B5ECAF27F2D9DE003EDFE3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D0B5ECAD27F2D9DE003EDFE3 /* Main.storyboard */; }; D0B5ECAF27F2D9DE003EDFE3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D0B5ECAD27F2D9DE003EDFE3 /* Main.storyboard */; };
...@@ -40,6 +41,11 @@ ...@@ -40,6 +41,11 @@
D0B5ECC827F2E97A003EDFE3 /* MacroFuncUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECC727F2E97A003EDFE3 /* MacroFuncUtil.m */; }; D0B5ECC827F2E97A003EDFE3 /* MacroFuncUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECC727F2E97A003EDFE3 /* MacroFuncUtil.m */; };
D0B5ECD527F2F0B2003EDFE3 /* AdaptationUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECD427F2F0B2003EDFE3 /* AdaptationUtil.m */; }; D0B5ECD527F2F0B2003EDFE3 /* AdaptationUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECD427F2F0B2003EDFE3 /* AdaptationUtil.m */; };
D0B5ECD827F2F1B0003EDFE3 /* ServerAPIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECD727F2F1B0003EDFE3 /* ServerAPIUtil.m */; }; D0B5ECD827F2F1B0003EDFE3 /* ServerAPIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B5ECD727F2F1B0003EDFE3 /* ServerAPIUtil.m */; };
D0C09ED728007D9100709D4C /* relax_normal_lottie.json in Resources */ = {isa = PBXBuildFile; fileRef = D0C09ED328007D9100709D4C /* relax_normal_lottie.json */; };
D0C09ED828007D9100709D4C /* 478_lottie.json in Resources */ = {isa = PBXBuildFile; fileRef = D0C09ED428007D9100709D4C /* 478_lottie.json */; };
D0C09ED928007D9100709D4C /* 478normal_lottie.json in Resources */ = {isa = PBXBuildFile; fileRef = D0C09ED528007D9100709D4C /* 478normal_lottie.json */; };
D0C09EDA28007D9100709D4C /* relax_lottie.json in Resources */ = {isa = PBXBuildFile; fileRef = D0C09ED628007D9100709D4C /* relax_lottie.json */; };
D0C09EE028007E5F00709D4C /* BreatheController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C09EDF28007E5F00709D4C /* BreatheController.m */; };
D0C50B3027FD1BEB00DC68F0 /* PrivacyView.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B2F27FD1BEB00DC68F0 /* PrivacyView.m */; }; D0C50B3027FD1BEB00DC68F0 /* PrivacyView.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B2F27FD1BEB00DC68F0 /* PrivacyView.m */; };
D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B3B27FD2EFD00DC68F0 /* PrivacyViewController.m */; }; D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B3B27FD2EFD00DC68F0 /* PrivacyViewController.m */; };
D0C50B3F27FD381000DC68F0 /* UIView+Extras.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B3E27FD381000DC68F0 /* UIView+Extras.m */; }; D0C50B3F27FD381000DC68F0 /* UIView+Extras.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C50B3E27FD381000DC68F0 /* UIView+Extras.m */; };
...@@ -100,6 +106,8 @@ ...@@ -100,6 +106,8 @@
D076C14227F49DC000340B46 /* TmpViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TmpViewController.m; sourceTree = "<group>"; }; D076C14227F49DC000340B46 /* TmpViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TmpViewController.m; sourceTree = "<group>"; };
D07F9D0627F45CB20036372F /* DKNightVersion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DKNightVersion.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D07F9D0627F45CB20036372F /* DKNightVersion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DKNightVersion.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D07F9DE827F4683B0036372F /* DKColorTable.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DKColorTable.txt; sourceTree = "<group>"; }; D07F9DE827F4683B0036372F /* DKColorTable.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DKColorTable.txt; sourceTree = "<group>"; };
D0930F102801124E006B497A /* BaseNaviController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseNaviController.h; sourceTree = "<group>"; };
D0930F112801124E006B497A /* BaseNaviController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseNaviController.m; sourceTree = "<group>"; };
D0B5ECA127F2D9DE003EDFE3 /* DreamSleep.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DreamSleep.app; sourceTree = BUILT_PRODUCTS_DIR; }; D0B5ECA127F2D9DE003EDFE3 /* DreamSleep.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DreamSleep.app; sourceTree = BUILT_PRODUCTS_DIR; };
D0B5ECA427F2D9DE003EDFE3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; D0B5ECA427F2D9DE003EDFE3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
...@@ -120,6 +128,12 @@ ...@@ -120,6 +128,12 @@
D0B5ECD427F2F0B2003EDFE3 /* AdaptationUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AdaptationUtil.m; sourceTree = "<group>"; }; D0B5ECD427F2F0B2003EDFE3 /* AdaptationUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AdaptationUtil.m; sourceTree = "<group>"; };
D0B5ECD627F2F1B0003EDFE3 /* ServerAPIUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ServerAPIUtil.h; sourceTree = "<group>"; }; D0B5ECD627F2F1B0003EDFE3 /* ServerAPIUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ServerAPIUtil.h; sourceTree = "<group>"; };
D0B5ECD727F2F1B0003EDFE3 /* ServerAPIUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ServerAPIUtil.m; sourceTree = "<group>"; }; D0B5ECD727F2F1B0003EDFE3 /* ServerAPIUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ServerAPIUtil.m; sourceTree = "<group>"; };
D0C09ED328007D9100709D4C /* relax_normal_lottie.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = relax_normal_lottie.json; sourceTree = "<group>"; };
D0C09ED428007D9100709D4C /* 478_lottie.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = 478_lottie.json; sourceTree = "<group>"; };
D0C09ED528007D9100709D4C /* 478normal_lottie.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = 478normal_lottie.json; sourceTree = "<group>"; };
D0C09ED628007D9100709D4C /* relax_lottie.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = relax_lottie.json; sourceTree = "<group>"; };
D0C09EDE28007E5F00709D4C /* BreatheController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BreatheController.h; sourceTree = "<group>"; };
D0C09EDF28007E5F00709D4C /* BreatheController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BreatheController.m; sourceTree = "<group>"; };
D0C50B2E27FD1BEB00DC68F0 /* PrivacyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrivacyView.h; sourceTree = "<group>"; }; D0C50B2E27FD1BEB00DC68F0 /* PrivacyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrivacyView.h; sourceTree = "<group>"; };
D0C50B2F27FD1BEB00DC68F0 /* PrivacyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivacyView.m; sourceTree = "<group>"; }; D0C50B2F27FD1BEB00DC68F0 /* PrivacyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivacyView.m; sourceTree = "<group>"; };
D0C50B3A27FD2EFD00DC68F0 /* PrivacyViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrivacyViewController.h; sourceTree = "<group>"; }; D0C50B3A27FD2EFD00DC68F0 /* PrivacyViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrivacyViewController.h; sourceTree = "<group>"; };
...@@ -216,6 +230,10 @@ ...@@ -216,6 +230,10 @@
D04567A827F6D018009F0A82 /* Resource */ = { D04567A827F6D018009F0A82 /* Resource */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0C09ED428007D9100709D4C /* 478_lottie.json */,
D0C09ED528007D9100709D4C /* 478normal_lottie.json */,
D0C09ED628007D9100709D4C /* relax_lottie.json */,
D0C09ED328007D9100709D4C /* relax_normal_lottie.json */,
D027EE3127FB5464004BBA61 /* pull_down.gif */, D027EE3127FB5464004BBA61 /* pull_down.gif */,
D0B5ECB027F2D9E0003EDFE3 /* Assets.xcassets */, D0B5ECB027F2D9E0003EDFE3 /* Assets.xcassets */,
D07F9DE827F4683B0036372F /* DKColorTable.txt */, D07F9DE827F4683B0036372F /* DKColorTable.txt */,
...@@ -247,6 +265,8 @@ ...@@ -247,6 +265,8 @@
D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */, D0B5ECA527F2D9DE003EDFE3 /* AppDelegate.m */,
D04B3DA927F6EEB40022F8DF /* DSTabBarController.h */, D04B3DA927F6EEB40022F8DF /* DSTabBarController.h */,
D04B3DAA27F6EEB40022F8DF /* DSTabBarController.m */, D04B3DAA27F6EEB40022F8DF /* DSTabBarController.m */,
D0930F102801124E006B497A /* BaseNaviController.h */,
D0930F112801124E006B497A /* BaseNaviController.m */,
); );
path = Main; path = Main;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -267,6 +287,8 @@ ...@@ -267,6 +287,8 @@
D01814D427FFCCFA00583D4E /* HomeTableView.m */, D01814D427FFCCFA00583D4E /* HomeTableView.m */,
D01814DD27FFDB6A00583D4E /* HomeTableViewCell.h */, D01814DD27FFDB6A00583D4E /* HomeTableViewCell.h */,
D01814DE27FFDB6A00583D4E /* HomeTableViewCell.m */, D01814DE27FFDB6A00583D4E /* HomeTableViewCell.m */,
D0C09EDE28007E5F00709D4C /* BreatheController.h */,
D0C09EDF28007E5F00709D4C /* BreatheController.m */,
); );
path = Home; path = Home;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -461,13 +483,17 @@ ...@@ -461,13 +483,17 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0C09ED728007D9100709D4C /* relax_normal_lottie.json in Resources */,
D0C09ED828007D9100709D4C /* 478_lottie.json in Resources */,
D04B3DB327F6F6070022F8DF /* Home.storyboard in Resources */, D04B3DB327F6F6070022F8DF /* Home.storyboard in Resources */,
D027EE3227FB5464004BBA61 /* pull_down.gif in Resources */, D027EE3227FB5464004BBA61 /* pull_down.gif in Resources */,
D07F9DE927F4683B0036372F /* DKColorTable.txt in Resources */, D07F9DE927F4683B0036372F /* DKColorTable.txt in Resources */,
D0B5ECB427F2D9E0003EDFE3 /* LaunchScreen.storyboard in Resources */, D0B5ECB427F2D9E0003EDFE3 /* LaunchScreen.storyboard in Resources */,
D0B5ECB127F2D9E0003EDFE3 /* Assets.xcassets in Resources */, D0B5ECB127F2D9E0003EDFE3 /* Assets.xcassets in Resources */,
D04B3DBD27F6F8090022F8DF /* Profile.storyboard in Resources */, D04B3DBD27F6F8090022F8DF /* Profile.storyboard in Resources */,
D0C09EDA28007D9100709D4C /* relax_lottie.json in Resources */,
D0CFD3D027FB3B920002982B /* launcher@3x.png in Resources */, D0CFD3D027FB3B920002982B /* launcher@3x.png in Resources */,
D0C09ED928007D9100709D4C /* 478normal_lottie.json in Resources */,
D0B5ECAF27F2D9DE003EDFE3 /* Main.storyboard in Resources */, D0B5ECAF27F2D9DE003EDFE3 /* Main.storyboard in Resources */,
D0CFD3D127FB3B920002982B /* launcher.png in Resources */, D0CFD3D127FB3B920002982B /* launcher.png in Resources */,
D0CFD3CF27FB3B920002982B /* launcher@2x.png in Resources */, D0CFD3CF27FB3B920002982B /* launcher@2x.png in Resources */,
...@@ -536,6 +562,7 @@ ...@@ -536,6 +562,7 @@
D0C50B3F27FD381000DC68F0 /* UIView+Extras.m in Sources */, D0C50B3F27FD381000DC68F0 /* UIView+Extras.m in Sources */,
D04B3DBB27F6F7940022F8DF /* AISleepCoachController.m in Sources */, D04B3DBB27F6F7940022F8DF /* AISleepCoachController.m in Sources */,
D027EE2627FB3DC0004BBA61 /* NetLoadingStateView.m in Sources */, D027EE2627FB3DC0004BBA61 /* NetLoadingStateView.m in Sources */,
D0C09EE028007E5F00709D4C /* BreatheController.m in Sources */,
D0C50B4627FD66FB00DC68F0 /* DSConstUtil.m in Sources */, D0C50B4627FD66FB00DC68F0 /* DSConstUtil.m in Sources */,
D01814D227FFCBAF00583D4E /* CWFlowLayout.m in Sources */, D01814D227FFCBAF00583D4E /* CWFlowLayout.m in Sources */,
D01814DF27FFDB6A00583D4E /* HomeTableViewCell.m in Sources */, D01814DF27FFDB6A00583D4E /* HomeTableViewCell.m in Sources */,
...@@ -551,6 +578,7 @@ ...@@ -551,6 +578,7 @@
D0B5ECC827F2E97A003EDFE3 /* MacroFuncUtil.m in Sources */, D0B5ECC827F2E97A003EDFE3 /* MacroFuncUtil.m in Sources */,
D01814E227FFDBB800583D4E /* HomeHeaderView.m in Sources */, D01814E227FFDBB800583D4E /* HomeHeaderView.m in Sources */,
D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */, D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */,
D0930F122801124E006B497A /* BaseNaviController.m in Sources */,
D01814E8280020F900583D4E /* CWPageControl.m in Sources */, D01814E8280020F900583D4E /* CWPageControl.m in Sources */,
D027EE3027FB52DA004BBA61 /* UIImage+Extras.m in Sources */, D027EE3027FB52DA004BBA61 /* UIImage+Extras.m in Sources */,
D04B3DC327F6F9390022F8DF /* HomeViewController.m in Sources */, D04B3DC327F6F9390022F8DF /* HomeViewController.m in Sources */,
......
//
// BreatheController.h
// DreamSleep
//
// Created by peter on 2022/4/8.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, LottieStyle) {
// 均衡呼吸法
LottieStyleBalance,
// 舒睡呼吸法
LottieStyleComfortable
};
///  均衡呼吸法和舒睡4-7-8呼吸法
@interface BreatheController : UIViewController
@property (nonatomic, assign) LottieStyle style;
@end
NS_ASSUME_NONNULL_END
//
// BreatheController.m
// DreamSleep
//
// Created by peter on 2022/4/8.
//
#import "BreatheController.h"
#import <Lottie.h>
@interface BreatheController () <UIPickerViewDataSource, UIPickerViewDelegate>
@property (nonatomic, strong) LOTAnimationView *zoomView;
@property (nonatomic, strong) LOTAnimationView *rotateView;
@property (nonatomic, strong) UIPickerView *pickerView;
@property (nonatomic, strong) UILabel *timeLb;
@property (nonatomic, strong) UILabel *minuteLb;
@property (nonatomic, strong) UIButton *starBtn;
@property (nonatomic, strong) NSArray *dataArr;
@property (nonatomic, assign) NSInteger timeAccount;
@property (nonatomic, copy) NSString *rotateLottieFile;
@property (nonatomic, copy) NSString *zoomLottieFile;
@end
@implementation BreatheController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = ColorFromHex(0x161E38);
self.dataArr = @[@"1",@"2",@"3",@"4",@"5"];
self.rotateLottieFile = self.style == LottieStyleBalance ? @"relax_normal_lottie.json" : @"478normal_lottie.json";
self.zoomLottieFile = self.style == LottieStyleBalance ? @"relax_lottie.json" : @"478_lottie.json";
[self initView];
}
- (void)initView {
[self.view addSubview:self.rotateView];
[self.view addSubview:self.zoomView];
[self.view addSubview:self.pickerView];
self.timeLb = [UILabel new];
self.timeLb.text = @"放松时长";
[self.view addSubview:self.timeLb];
self.timeLb.textColor = [UIColor whiteColor];
self.timeLb.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[self.timeLb sizeToFit];
self.minuteLb = [UILabel new];
self.minuteLb.text = @"分钟";
[self.view addSubview:self.minuteLb];
self.minuteLb.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
self.minuteLb.textColor = [UIColor whiteColor];
[self.minuteLb sizeToFit];
[self.view addSubview:self.starBtn];
[self.rotateView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.equalTo(@162);
make.centerX.equalTo(self.view);
make.topMargin.equalTo(self.view).offset(100);
}];
[self.zoomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.leftMargin.equalTo(self.view).offset(90);
make.rightMargin.equalTo(self.view).offset(-90);
make.height.equalTo(@300);
}];
[self.pickerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.rotateView.mas_bottom).offset(30);
make.left.equalTo(self.timeLb.mas_right);
make.right.equalTo(self.minuteLb.mas_left);
make.height.equalTo(@65);
}];
[self.timeLb mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.rotateView);
make.centerY.equalTo(self.pickerView.mas_centerY);
}];
[self.minuteLb mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.rotateView);
make.centerY.equalTo(self.pickerView.mas_centerY);
}];
[self.starBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.pickerView.mas_bottom).offset(50);
make.width.equalTo(@160);
make.centerX.equalTo(self.zoomView.mas_centerX);
make.height.equalTo(@40);
}];
}
- (void)startBtnClick:(UIButton *)sender {
sender.selected = !sender.selected;
self.rotateView.hidden = YES;
self.timeLb.hidden = YES;
self.minuteLb.hidden = YES;
self.starBtn.hidden = YES;
self.pickerView.hidden = YES;
self.zoomView.hidden = NO;
// self.allAccount = self.timeAccount * 60;
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerd:) userInfo:nil repeats:YES];
// [self.streamer play];
}
#pragma mark - UIPickerViewDataSource && UIPickerViewDelegate
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return self.dataArr.count;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
self.timeAccount = [[self.dataArr objectAtIndex:row] integerValue];
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
return 35;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
return 70;
}
- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *titleString = [self.dataArr objectAtIndex:row];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:titleString];
NSRange range = [titleString rangeOfString:titleString];
[attributedString addAttribute:NSForegroundColorAttributeName value:BrandColor range:range];
[attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18] range:range];
if (@available(iOS 14.0, *)) {
pickerView.subviews[1].backgroundColor = DSClearColor;
}
return attributedString;
}
- (LOTAnimationView *)rotateView {
if(!_rotateView) {
_rotateView = [LOTAnimationView animationNamed:self.rotateLottieFile];
_rotateView.contentMode = UIViewContentModeScaleAspectFit;
_rotateView.loopAnimation = YES;
[_rotateView playWithCompletion:^(BOOL animationFinished) {
DSLog(@"rotateView...");
}];
_rotateView.hidden = NO;
}
return _rotateView;
}
- (LOTAnimationView *)zoomView {
if(!_zoomView) {
_zoomView = [LOTAnimationView animationNamed:self.zoomLottieFile];
_zoomView.contentMode = UIViewContentModeScaleAspectFit;
_zoomView.loopAnimation = YES;
_zoomView.hidden = YES;
[_zoomView playWithCompletion:^(BOOL animationFinished) {
DSLog(@"zoomView...");
}];
}
return _zoomView;
}
- (UIPickerView *)pickerView {
if (!_pickerView) {
_pickerView = [UIPickerView new];
_pickerView.delegate = self;
_pickerView.dataSource = self;
}
return _pickerView;
}
- (UIButton *)starBtn {
if (!_starBtn) {
_starBtn = [UIButton new];
_starBtn.layer.cornerRadius = 20;
_starBtn.layer.masksToBounds = YES;
[_starBtn addTarget:self action:@selector(startBtnClick:) forControlEvents:UIControlEventTouchUpInside];
_starBtn.titleLabel.font = [UIFont systemFontOfSize:16];
_starBtn.backgroundColor = ColorFromHex(0x62C3D5);
[_starBtn setTitle:@"开始放松" forState:UIControlStateNormal];
}
return _starBtn;
}
@end
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import "DsMiddleView.h" #import "DsMiddleView.h"
#import "HeaderDataModel.h" #import "HeaderDataModel.h"
#import "BreatheController.h"
@interface DsMiddleView () @interface DsMiddleView ()
@property (nonatomic, strong) UIButton *unityBtn; @property (nonatomic, strong) UIButton *unityBtn;
...@@ -48,8 +49,7 @@ ...@@ -48,8 +49,7 @@
for (int i = 0; i < self.middleImgs.count; i++) { for (int i = 0; i < self.middleImgs.count; i++) {
UIButton *btn = [UIButton new]; UIButton *btn = [UIButton new];
[btn setImage:[UIImage imageNamed:self.middleImgs[i]] forState:UIControlStateNormal]; [btn setBackgroundImage:[UIImage imageNamed:self.middleImgs[i]] forState:UIControlStateNormal];
btn.imageView.contentMode = UIViewContentModeScaleAspectFit;
btn.tag = i; btn.tag = i;
[btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside]; [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn]; [self addSubview:btn];
...@@ -68,7 +68,15 @@ ...@@ -68,7 +68,15 @@
- (void)btnAction:(UIButton *)sender { - (void)btnAction:(UIButton *)sender {
DSLog(@"tag:%ld", sender.tag); DSLog(@"tag:%ld", sender.tag);
self.height = 3; if (sender.tag != 0) {
BreatheController *breatheVC = [[BreatheController alloc] init];
breatheVC.style = sender.tag == 1 ? LottieStyleBalance : LottieStyleComfortable;
breatheVC.title = sender.tag == 1 ? @"均衡呼吸法" : @"舒睡4-7-8呼吸法";
self.ds_viewController.hidesBottomBarWhenPushed = YES;
[self.ds_viewController.navigationController pushViewController:breatheVC animated:YES];
self.ds_viewController.hidesBottomBarWhenPushed = NO;
}
} }
@end @end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
#import "HomeTableViewCell.h" #import "HomeTableViewCell.h"
#import "HomeHeaderView.h" #import "HomeHeaderView.h"
@interface HomeTableView () @interface HomeTableView () <UITableViewDelegate>
@property (nonatomic, strong) HomeHeaderView *headerView; @property (nonatomic, strong) HomeHeaderView *headerView;
@property (nonatomic, strong) DSDataSource *homeDataSource; @property (nonatomic, strong) DSDataSource *homeDataSource;
@end @end
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
- (instancetype)initDemo { - (instancetype)initDemo {
if (self = [super initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight) style:UITableViewStylePlain]) { if (self = [super initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight) style:UITableViewStylePlain]) {
self.dk_backgroundColorPicker = DKColorPickerWithKey(VCViewBG); self.dk_backgroundColorPicker = DKColorPickerWithKey(VCViewBG);
[self.homeDataSource addDataArray:@[]]; [self.homeDataSource addDataArray:@[@1, @2, @3]];
self.mj_header = [DSGifHeader headerWithRefreshingBlock:^{ self.mj_header = [DSGifHeader headerWithRefreshingBlock:^{
dispatch_after(5.0, dispatch_get_main_queue(), ^{ dispatch_after(5.0, dispatch_get_main_queue(), ^{
[self.mj_header endRefreshing]; [self.mj_header endRefreshing];
...@@ -30,29 +30,17 @@ ...@@ -30,29 +30,17 @@
return self; return self;
} }
- (void)layoutSubviews {
[super layoutSubviews];
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
// banner高度
CGFloat bannerH = 2*(kScreenWidth - 48)/5.0;
// 三分钟即刻入睡按钮宽度
CGFloat width = (kScreenWidth - 2*15 - 14)/2;
CGFloat height = 15 + bannerH + 24 + 133*width/165.0;
make.top.equalTo(self);
make.width.mas_equalTo(self);
make.height.mas_equalTo(height);
}];
}
- (DSDataSource *)homeDataSource { - (DSDataSource *)homeDataSource {
if (!_homeDataSource) { if (!_homeDataSource) {
CellConfigureBlock cellBlock = ^(id cell, id model, NSIndexPath * indexPath) { CellConfigureBlock cellBlock = ^(HomeTableViewCell * cell, id model, NSIndexPath * indexPath) {
cell.textLabel.text = @"chengx";
cell.backgroundColor = BrandColor;
// [cell configureCell:model]; // [cell configureCell:model];
}; };
NSString * const homeCellIdentifier = @"HomeCellIdentifier"; NSString * const homeCellIdentifier = @"HomeCellIdentifier";
_homeDataSource = [[DSDataSource alloc] initWithIdentifier:homeCellIdentifier datas:@[] isSection:NO configureBlock:cellBlock]; _homeDataSource = [[DSDataSource alloc] initWithIdentifier:homeCellIdentifier datas:@[] isSection:NO configureBlock:cellBlock];
self.dataSource = _homeDataSource; self.dataSource = _homeDataSource;
self.delegate = self;
[self registerClass:[HomeTableViewCell class] forCellReuseIdentifier:homeCellIdentifier]; [self registerClass:[HomeTableViewCell class] forCellReuseIdentifier:homeCellIdentifier];
self.tableHeaderView = self.headerView; self.tableHeaderView = self.headerView;
...@@ -62,10 +50,36 @@ ...@@ -62,10 +50,36 @@
- (HomeHeaderView *)headerView { - (HomeHeaderView *)headerView {
if (!_headerView) { if (!_headerView) {
_headerView = [[HomeHeaderView alloc] init]; // banner高度
// [_headerView debugViewShowBorder]; CGFloat bannerH = 2*(kScreenWidth - 48)/5.0;
// 三分钟即刻入睡按钮宽度
CGFloat width = (kScreenWidth - 2*15 - 14)/2;
CGFloat height = 15 + bannerH + 24 + 133*width/165.0;
_headerView = [[HomeHeaderView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, height)];
_headerView.backgroundColor = self.backgroundColor;
} }
return _headerView; return _headerView;
} }
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 70;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 0.001;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.001;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 0.001)];
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 0.001)];
}
@end @end
...@@ -15,11 +15,13 @@ ...@@ -15,11 +15,13 @@
@implementation HomeViewController @implementation HomeViewController
- (void)loadView {
self.view = self.homeTV;
}
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.view = self.homeTV;
// 导航栏背景色 // 导航栏背景色
// self.navigationController.navigationBar.dk_barTintColorPicker = DKColorPickerWithKey(NaviBG); // self.navigationController.navigationBar.dk_barTintColorPicker = DKColorPickerWithKey(NaviBG);
......
//
// BaseNaviController.h
// DreamSleep
//
// Created by peter on 2022/4/9.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface BaseNaviController : UINavigationController
@end
NS_ASSUME_NONNULL_END
//
// BaseNaviController.m
// DreamSleep
//
// Created by peter on 2022/4/9.
//
#import "BaseNaviController.h"
@interface BaseNaviController ()
@end
@implementation BaseNaviController
- (void)viewDidLoad {
[super viewDidLoad];
}
@end
{"v":"5.6.9","fr":25,"ip":0,"op":375,"w":1000,"h":1000,"nm":"合成 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“呼气”轮廓","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":216,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":221,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":370,"s":[100],"e":[0]},{"t":375}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[506,556,0],"ix":2},"a":{"a":0,"k":[63,-20,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.171,0.171,0.667],"y":[1,1,1]},"o":{"x":[0.465,0.465,0.333],"y":[0,0,0]},"t":221,"s":[200,200,100],"e":[100,100,100]},{"t":375}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[2.054,-3.746],[0,0],[-1.45,5.438]],"o":[[-1.511,5.74],[0,0],[2.175,-4.23],[0,0]],"v":[[52.69,-39.034],[47.373,-24.774],[50.334,-23.687],[55.712,-38.188]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.571,3.686],[0,0],[-1.148,-4.351],[0,0]],"o":[[0,0],[1.752,3.988],[0,0],[-1.269,-4.713]],"v":[[28.4,-36.92],[25.499,-35.953],[29.85,-23.505],[32.69,-24.291]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,3.142],[0,0],[0,0],[0,0],[0,0],[0,0],[-4.532,1.208],[0,0],[11.904,-0.665],[0,0],[-4.471,0.544],[0,0],[0,0],[0,0],[0,0],[0,0],[2.175,0],[2.175,0.121],[0,0]],"o":[[3.565,0],[0,0],[0,0],[0,0],[0,0],[0,0],[4.955,-0.725],[0,0],[-8.459,2.417],[0,0],[4.713,-0.242],[0,0],[0,0],[0,0],[0,0],[0,0],[0,1.752],[-1.934,0],[0,0],[0,0]],"v":[[36.315,5.68],[41.693,0.967],[41.693,-16.738],[57.645,-16.738],[57.645,-19.819],[41.693,-19.819],[41.693,-43.022],[55.893,-45.923],[54.261,-48.763],[23.687,-44.171],[24.714,-41.331],[38.43,-42.539],[38.43,-19.819],[22.841,-19.819],[22.841,-16.738],[38.43,-16.738],[38.43,0.121],[35.167,2.78],[29.004,2.538],[29.729,5.68]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[8.52,-4.109],[16.738,-4.109],[16.738,-0.242],[19.88,-0.242],[19.88,-43.929],[5.378,-43.929],[5.378,1.148],[8.52,1.148]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16.738,-7.13],[8.52,-7.13],[8.52,-40.787],[16.738,-40.787]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"呼","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.78,4.955],[0,0],[0,0],[0,0],[-0.786,1.934],[0,0],[5.378,-5.257],[0,0]],"o":[[0,0],[0,0],[0,0],[0.846,-1.692],[0,0],[-3.142,8.036],[0,0],[3.746,-3.686]],"v":[[75.227,-40.122],[112.872,-40.122],[112.872,-43.264],[76.859,-43.264],[79.276,-48.763],[76.013,-49.548],[63.203,-29.608],[65.378,-27.131]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.021,-0.06],[-1.027,1.631],[-0.846,5.076],[0,0],[0.483,-1.934],[0.967,0],[0.846,2.296],[0,8.339],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.45,-3.021]],"o":[[1.269,0],[1.148,-1.813],[0,0],[-0.604,2.961],[-0.725,2.175],[-1.752,0],[-0.967,-2.357],[0,0],[0,0],[0,0],[0,0],[0,0],[0,9.305],[1.329,3.021]],"v":[[111.422,5.74],[114.866,3.263],[117.887,-7.13],[115.168,-8.459],[113.536,-1.088],[111.059,2.175],[107.192,-1.329],[105.802,-17.402],[105.802,-24.472],[68.218,-24.472],[68.218,-21.39],[102.72,-21.39],[102.72,-17.402],[104.896,1.148]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.925,-33.898],[74.925,-30.817],[109.73,-30.817],[109.73,-33.898]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"气","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":375,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“屏气”轮廓","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":80,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":85,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":211,"s":[100],"e":[0]},{"t":216}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[506,556,0],"ix":2},"a":{"a":0,"k":[63,-20,0],"ix":1},"s":{"a":0,"k":[200,200,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-0.06,2.054],[0,0]],"o":[[0,0],[0,0],[0.242,-1.813],[0,0],[0,0]],"v":[[42.297,-20.484],[42.297,-11.481],[29.729,-11.481],[30.152,-17.221],[30.152,-20.484]],"c":true},"ix":2},"nm":"屏","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.329,2.417],[0,0],[1.934,-2.417],[0,0],[1.571,2.236],[0,0],[-1.269,-2.236],[0,0],[0,0],[0,0],[0,0],[0.242,-1.813],[0,0],[0,0],[0,0],[0.604,-1.269],[4.351,-1.813],[0,0],[-2.054,3.505],[-0.544,2.417]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1.692,-1.934],[0,0],[-1.329,2.719],[0,0],[-1.39,-2.659],[0,0],[1.269,1.692],[0,0],[0,0],[0,0],[0,0],[-0.06,2.054],[0,0],[0,0],[0,0],[-0.423,1.45],[-1.873,3.263],[0,0],[4.592,-1.994],[0.967,-1.813],[0,0]],"v":[[42.297,-8.399],[42.297,5.438],[45.5,5.438],[45.5,-8.399],[56.981,-8.399],[56.981,-11.481],[45.5,-11.481],[45.5,-20.484],[54.564,-20.484],[54.564,-23.626],[43.989,-23.626],[48.521,-30.212],[45.439,-31.36],[40.545,-23.626],[30.515,-23.626],[26.043,-30.938],[23.264,-29.548],[27.07,-23.626],[16.677,-23.626],[16.677,-20.484],[26.949,-20.484],[26.949,-17.221],[26.466,-11.481],[14.502,-11.481],[14.502,-8.399],[25.862,-8.399],[24.351,-4.29],[15.046,3.263],[16.919,6.163],[26.949,-2.115],[29.246,-8.399]],"c":true},"ix":2},"nm":"屏","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[50.334,-43.687],[50.334,-36.376],[12.75,-36.376],[12.75,-43.687]],"c":true},"ix":2},"nm":"屏","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[4.109,-7.855],[0,0],[-0.06,12.75]],"o":[[0,0],[0,0],[0,0],[0,0],[-0.06,12.387],[0,0],[4.411,-9.185],[0,0]],"v":[[12.75,-33.294],[53.597,-33.294],[53.597,-46.829],[9.547,-46.829],[9.547,-27.131],[3.263,3.263],[6.042,5.74],[12.75,-27.131]],"c":true},"ix":2},"nm":"屏","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"屏","np":7,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.78,4.955],[0,0],[0,0],[0,0],[-0.786,1.934],[0,0],[5.378,-5.257],[0,0]],"o":[[0,0],[0,0],[0,0],[0.846,-1.692],[0,0],[-3.142,8.036],[0,0],[3.746,-3.686]],"v":[[75.227,-40.122],[112.872,-40.122],[112.872,-43.264],[76.859,-43.264],[79.276,-48.763],[76.013,-49.548],[63.203,-29.608],[65.378,-27.131]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.021,-0.06],[-1.027,1.631],[-0.846,5.076],[0,0],[0.483,-1.934],[0.967,0],[0.846,2.296],[0,8.339],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.45,-3.021]],"o":[[1.269,0],[1.148,-1.813],[0,0],[-0.604,2.961],[-0.725,2.175],[-1.752,0],[-0.967,-2.357],[0,0],[0,0],[0,0],[0,0],[0,0],[0,9.305],[1.329,3.021]],"v":[[111.422,5.74],[114.866,3.263],[117.887,-7.13],[115.168,-8.459],[113.536,-1.088],[111.059,2.175],[107.192,-1.329],[105.802,-17.402],[105.802,-24.472],[68.218,-24.472],[68.218,-21.39],[102.72,-21.39],[102.72,-17.402],[104.896,1.148]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.925,-33.898],[74.925,-30.817],[109.73,-30.817],[109.73,-33.898]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"气","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":375,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"“吸气”轮廓","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":75,"s":[100],"e":[0]},{"t":80}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[506,556,0],"ix":2},"a":{"a":0,"k":[63,-20,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.524,0.524,0.667],"y":[1,1,1]},"o":{"x":[0.767,0.767,0.333],"y":[0,0,0]},"t":5,"s":[100,100,100],"e":[200,200,100]},{"t":75}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[20.424,-43.748],[5.257,-43.748],[5.257,-3.323],[8.58,-3.323],[8.58,-7.855],[20.424,-7.855]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.58,-10.997],[8.58,-40.605],[17.221,-40.605],[17.221,-10.997]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[8.339,-8.339],[0,0],[-1.631,13.293],[-4.169,-5.257],[5.499,-2.598],[0,0],[-3.867,4.048],[-4.774,-2.9],[0,0],[3.384,3.625],[-1.269,8.339],[0,0],[0,0],[-1.088,5.68],[0,0],[0,0],[0,0]],"o":[[0,0],[-0.121,18.369],[0,0],[6.768,-6.888],[1.994,6.707],[-3.625,3.867],[0,0],[5.559,-2.659],[3.444,3.746],[0,0],[-4.774,-2.9],[4.532,-5.74],[0,0],[0,0],[1.088,-3.988],[0,0],[0,0],[0,0],[0,0]],"v":[[29.91,-43.204],[29.91,-36.859],[17.221,3.203],[19.819,5.438],[32.388,-24.835],[41.633,-6.888],[27.916,2.78],[29.789,5.74],[43.868,-4.29],[56.195,5.74],[58.31,2.961],[46.044,-6.768],[54.745,-27.856],[54.745,-29.789],[47.433,-29.789],[50.757,-44.352],[50.757,-46.467],[23.747,-46.467],[23.747,-43.204]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[3.867,-4.955],[1.45,9.547],[0,0.967],[0,0],[0,0],[1.148,-3.686],[0,0]],"o":[[-5.68,-6.888],[0,-0.906],[0,0],[0,0],[-1.45,7.251],[0,0],[-1.329,6.707]],"v":[[43.748,-9.366],[33.052,-34.08],[33.113,-36.859],[33.113,-43.204],[47.252,-43.204],[43.385,-26.829],[51.542,-26.829]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"吸","np":7,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.78,4.955],[0,0],[0,0],[0,0],[-0.786,1.934],[0,0],[5.378,-5.257],[0,0]],"o":[[0,0],[0,0],[0,0],[0.846,-1.692],[0,0],[-3.142,8.036],[0,0],[3.746,-3.686]],"v":[[75.227,-40.122],[112.872,-40.122],[112.872,-43.264],[76.859,-43.264],[79.276,-48.763],[76.013,-49.548],[63.203,-29.608],[65.378,-27.131]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.021,-0.06],[-1.027,1.631],[-0.846,5.076],[0,0],[0.483,-1.934],[0.967,0],[0.846,2.296],[0,8.339],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.45,-3.021]],"o":[[1.269,0],[1.148,-1.813],[0,0],[-0.604,2.961],[-0.725,2.175],[-1.752,0],[-0.967,-2.357],[0,0],[0,0],[0,0],[0,0],[0,0],[0,9.305],[1.329,3.021]],"v":[[111.422,5.74],[114.866,3.263],[117.887,-7.13],[115.168,-8.459],[113.536,-1.088],[111.059,2.175],[107.192,-1.329],[105.802,-17.402],[105.802,-24.472],[68.218,-24.472],[68.218,-21.39],[102.72,-21.39],[102.72,-17.402],[104.896,1.148]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.925,-33.898],[74.925,-30.817],[109.73,-30.817],[109.73,-33.898]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"气","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":375,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"“图层 2/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":25,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[120],"e":[420]},{"i":{"x":[0.669],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":216,"s":[420],"e":[720]},{"t":374}],"ix":10},"p":{"a":0,"k":[500,553.75,0],"ix":2},"a":{"a":0,"k":[245.5,232.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.242,0.242,0.667],"y":[1,1,1]},"o":{"x":[0.382,0.382,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100],"e":[163,163,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":81,"s":[163,163,100],"e":[163,163,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.516,0.516,0.167],"y":[0,0,0]},"t":216,"s":[163,163,100],"e":[100,100,100]},{"t":374}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-46.174,18.172],[-32.738,-11.102],[-27.86,-43.558],[-16.9,-42.921],[27.789,-35.426],[45.467,-13.505],[37.123,6.152],[31.537,24.183],[18.173,34.153],[-5.585,41.366],[-30.972,22.345]],"o":[[46.033,-18.173],[32.669,11.031],[27.931,43.628],[16.83,42.851],[-27.789,35.426],[-45.468,13.505],[-36.982,-6.152],[-31.608,-24.112],[-18.102,-34.225],[5.516,-41.436],[30.971,-22.344]],"v":[[-61.872,-179.393],[65.123,-219.132],[142.693,-109.743],[226.273,9.192],[210.222,140.785],[86.337,207.253],[-36.7,224.082],[-138.876,171.968],[-218.072,87.116],[-237.518,-30.335],[-182.999,-135.058]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.104,232.234],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":393,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"“图层 3/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":50,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0],"e":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[120],"e":[420]},{"i":{"x":[0.674],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":216,"s":[420],"e":[720]},{"t":374}],"ix":10},"p":{"a":0,"k":[500,553.75,0],"ix":2},"a":{"a":0,"k":[244.5,224,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.593,0.593,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.406,0.406,0.667],"y":[1,1,1]},"o":{"x":[0.548,0.548,0.333],"y":[0,0,0]},"t":5,"s":[100,100,100],"e":[163,163,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":81,"s":[163,163,100],"e":[163,163,100]},{"i":{"x":[0.101,0.101,0.667],"y":[1,1,1]},"o":{"x":[0.677,0.677,0.167],"y":[0,0,0]},"t":216,"s":[163,163,100],"e":[100,100,100]},{"t":374}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[19.8,-45.5],[31,-15.3],[50.501,11.1],[42.3,18.4],[5.4,44.7],[-22.6,41.7],[-30.6,21.9],[-39.4,5.2],[-37,-11.3],[-25.3,-33.2],[6.1,-37.7]],"o":[[-19.7,45.4],[-30.9,15.3],[-50.6,-11.1],[-42.2,-18.4],[-5.4,-44.7],[22.6,-41.7],[30.5,-21.8],[39.4,-5.3],[37,11.4],[25.4,33.2],[-6.1,37.7]],"v":[[181.9,88.5],[120.2,206.4],[-12,183.9],[-155.2,158.9],[-236.9,54.5],[-196.3,-80.1],[-121.2,-179],[-12.1,-214.4],[103.9,-210.4],[200.7,-141.1],[236.2,-28.5]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[244.3,223.7],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":402,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"“图层 4/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":75,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0],"e":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[120],"e":[420]},{"i":{"x":[0.692],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":216,"s":[420],"e":[720]},{"t":374}],"ix":10},"p":{"a":0,"k":[500,553.75,0],"ix":2},"a":{"a":0,"k":[232.5,245.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.578,0.578,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.606,0.606,0.667],"y":[1,1,1]},"o":{"x":[0.688,0.688,0.333],"y":[0,0,0]},"t":10,"s":[100,100,100],"e":[163,163,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":81,"s":[163,163,100],"e":[163,163,100]},{"i":{"x":[0.29,0.29,0.667],"y":[1,1,1]},"o":{"x":[0.872,0.872,0.167],"y":[0,0,0]},"t":216,"s":[163,163,100],"e":[100,100,100]},{"t":374}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.172,46.174],[-11.101,32.74],[-43.558,27.86],[-42.921,16.9],[-35.426,-27.789],[-13.506,-45.468],[6.152,-37.123],[24.183,-31.537],[34.153,-18.173],[41.366,5.585],[22.345,30.972]],"o":[[-18.173,-46.032],[11.032,-32.668],[43.628,-27.931],[42.851,-16.829],[35.426,27.79],[13.505,45.466],[-6.152,36.982],[-24.112,31.607],[-34.225,18.102],[-41.436,-5.516],[-22.344,-30.971]],"v":[[-179.393,61.872],[-219.133,-65.125],[-109.743,-142.694],[9.192,-226.274],[140.785,-210.223],[207.253,-86.337],[224.082,36.699],[171.968,138.876],[87.116,218.072],[-30.335,237.518],[-135.058,182.999]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[232.234,245.103],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":391,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"“图层 5/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0],"e":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[120],"e":[420]},{"i":{"x":[0.686],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":216,"s":[420],"e":[720]},{"t":374}],"ix":10},"p":{"a":0,"k":[500,553.75,0],"ix":2},"a":{"a":0,"k":[224,244.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.543,0.543,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.879,0.879,0.667],"y":[1,1,1]},"o":{"x":[0.698,0.698,0.333],"y":[0,0,0]},"t":15,"s":[100,100,100],"e":[163,163,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":81,"s":[163,163,100],"e":[163,163,100]},{"i":{"x":[0.479,0.479,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.167],"y":[0,0,0]},"t":216,"s":[163,163,100],"e":[100,100,100]},{"t":374}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-45.5,-19.8],[-15.3,-31],[11.1,-50.499],[18.4,-42.3],[44.7,-5.401],[41.7,22.6],[21.9,30.6],[5.2,39.399],[-11.3,37],[-33.2,25.3],[-37.7,-6.1]],"o":[[45.4,19.7],[15.3,30.9],[-11.1,50.601],[-18.4,42.2],[-44.7,5.4],[-41.7,-22.6],[-21.8,-30.5],[-5.3,-39.4],[11.4,-37],[33.2,-25.4],[37.7,6.1]],"v":[[88.5,-181.9],[206.4,-120.2],[183.9,11.999],[158.9,155.2],[54.5,236.9],[-80.1,196.3],[-179,121.2],[-214.4,12.1],[-210.4,-103.9],[-141.1,-200.7],[-28.5,-236.2]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.7,244.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":400,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file \ No newline at end of file
{"v":"5.6.9","fr":25,"ip":0,"op":187,"w":1000,"h":1000,"nm":"合成 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“图层 2/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":25,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[360]},{"t":187}],"ix":10},"p":{"a":0,"k":[500,513.75,0],"ix":2},"a":{"a":0,"k":[245.5,232.5,0],"ix":1},"s":{"a":0,"k":[163,163,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-46.174,18.172],[-32.738,-11.102],[-27.86,-43.558],[-16.9,-42.921],[27.789,-35.426],[45.467,-13.505],[37.123,6.152],[31.537,24.183],[18.173,34.153],[-5.585,41.366],[-30.972,22.345]],"o":[[46.033,-18.173],[32.669,11.031],[27.931,43.628],[16.83,42.851],[-27.789,35.426],[-45.468,13.505],[-36.982,-6.152],[-31.608,-24.112],[-18.102,-34.225],[5.516,-41.436],[30.971,-22.344]],"v":[[-61.872,-179.393],[65.123,-219.132],[142.693,-109.743],[226.273,9.192],[210.222,140.785],[86.337,207.253],[-36.7,224.082],[-138.876,171.968],[-218.072,87.116],[-237.518,-30.335],[-182.999,-135.058]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.104,232.234],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":187,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“图层 3/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":50,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[360]},{"t":187}],"ix":10},"p":{"a":0,"k":[500,513.75,0],"ix":2},"a":{"a":0,"k":[244.5,224,0],"ix":1},"s":{"a":0,"k":[163,163,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[19.8,-45.5],[31,-15.3],[50.501,11.1],[42.3,18.4],[5.4,44.7],[-22.6,41.7],[-30.6,21.9],[-39.4,5.2],[-37,-11.3],[-25.3,-33.2],[6.1,-37.7]],"o":[[-19.7,45.4],[-30.9,15.3],[-50.6,-11.1],[-42.2,-18.4],[-5.4,-44.7],[22.6,-41.7],[30.5,-21.8],[39.4,-5.3],[37,11.4],[25.4,33.2],[-6.1,37.7]],"v":[[181.9,88.5],[120.2,206.4],[-12,183.9],[-155.2,158.9],[-236.9,54.5],[-196.3,-80.1],[-121.2,-179],[-12.1,-214.4],[103.9,-210.4],[200.7,-141.1],[236.2,-28.5]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[244.3,223.7],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":187,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"“图层 4/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":75,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[360]},{"t":187}],"ix":10},"p":{"a":0,"k":[500,513.75,0],"ix":2},"a":{"a":0,"k":[232.5,245.5,0],"ix":1},"s":{"a":0,"k":[163,163,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.172,46.174],[-11.101,32.74],[-43.558,27.86],[-42.921,16.9],[-35.426,-27.789],[-13.506,-45.468],[6.152,-37.123],[24.183,-31.537],[34.153,-18.173],[41.366,5.585],[22.345,30.972]],"o":[[-18.173,-46.032],[11.032,-32.668],[43.628,-27.931],[42.851,-16.829],[35.426,27.79],[13.505,45.466],[-6.152,36.982],[-24.112,31.607],[-34.225,18.102],[-41.436,-5.516],[-22.344,-30.971]],"v":[[-179.393,61.872],[-219.133,-65.125],[-109.743,-142.694],[9.192,-226.274],[140.785,-210.223],[207.253,-86.337],[224.082,36.699],[171.968,138.876],[87.116,218.072],[-30.335,237.518],[-135.058,182.999]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[232.234,245.103],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":187,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"“图层 5/478呼吸的副本”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[360]},{"t":187}],"ix":10},"p":{"a":0,"k":[500,513.75,0],"ix":2},"a":{"a":0,"k":[224,244.5,0],"ix":1},"s":{"a":0,"k":[163,163,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-45.5,-19.8],[-15.3,-31],[11.1,-50.499],[18.4,-42.3],[44.7,-5.401],[41.7,22.6],[21.9,30.6],[5.2,39.399],[-11.3,37],[-33.2,25.3],[-37.7,-6.1]],"o":[[45.4,19.7],[15.3,30.9],[-11.1,50.601],[-18.4,42.2],[-44.7,5.4],[-41.7,-22.6],[-21.8,-30.5],[-5.3,-39.4],[11.4,-37],[33.2,-25.4],[37.7,6.1]],"v":[[88.5,-181.9],[206.4,-120.2],[183.9,11.999],[158.9,155.2],[54.5,236.9],[-80.1,196.3],[-179,121.2],[-214.4,12.1],[-210.4,-103.9],[-141.1,-200.7],[-28.5,-236.2]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.7,244.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":187,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file \ No newline at end of file
{"v":"5.6.9","fr":25,"ip":0,"op":250,"w":800,"h":1000,"nm":"放松大图ai","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“呼气”轮廓","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":125,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":225,"s":[100],"e":[0]},{"t":235}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[388.379,95.913,0],"ix":2},"a":{"a":0,"k":[42,-13.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.934,-3.746],[0,0],[-1.329,5.559]],"o":[[-1.45,5.74],[0,0],[2.175,-4.23],[0,0]],"v":[[52.147,-39.155],[47.01,-24.955],[50.878,-23.505],[56.135,-38.128]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.631,3.625],[0,0],[-1.148,-4.351],[0,0]],"o":[[0,0],[1.692,3.988],[0,0],[-1.269,-4.834]],"v":[[28.943,-37.04],[25.137,-35.832],[29.427,-23.324],[33.294,-24.351]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,3.565],[0,0],[0,0],[0,0],[0,0],[0,0],[-4.471,1.148],[0,0],[12.206,-0.665],[0,0],[-4.23,0.544],[0,0],[0,0],[0,0],[0,0],[0,0],[2.054,0],[2.054,0.181],[0,0]],"o":[[3.928,0],[0,0],[0,0],[0,0],[0,0],[0,0],[4.834,-0.725],[0,0],[-8.218,2.296],[0,0],[4.471,-0.242],[0,0],[0,0],[0,0],[0,0],[0,0],[0,1.692],[-1.934,0],[0,0],[0,0]],"v":[[36.376,5.861],[42.297,0.483],[42.297,-16.194],[57.766,-16.194],[57.766,-20.363],[42.297,-20.363],[42.297,-42.539],[56.195,-45.379],[54.141,-49.005],[23.505,-44.594],[24.835,-40.787],[37.947,-41.935],[37.947,-20.363],[22.841,-20.363],[22.841,-16.194],[37.947,-16.194],[37.947,-0.604],[34.805,1.994],[28.883,1.692],[29.789,5.861]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[9.124,-3.746],[16.133,-3.746],[16.133,-0.121],[20.303,-0.121],[20.303,-44.352],[4.955,-44.352],[4.955,1.329],[9.124,1.329]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16.133,-7.795],[9.124,-7.795],[9.124,-40.364],[16.133,-40.364]],"c":true},"ix":2},"nm":"呼","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"呼","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.84,4.955],[0,0],[0,0],[0,0],[-0.725,1.752],[0,0],[5.076,-5.015],[0,0]],"o":[[0,0],[0,0],[0,0],[0.725,-1.571],[0,0],[-3.263,8.097],[0,0],[3.686,-3.625]],"v":[[75.469,-39.578],[112.932,-39.578],[112.932,-43.808],[77.704,-43.808],[79.819,-48.763],[75.408,-49.73],[62.9,-30.031],[65.68,-26.647]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.082,-0.06],[-1.148,1.692],[-0.967,5.197],[0,0],[0.483,-1.813],[0.786,0],[0.725,2.175],[0,7.855],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.45,-3.021]],"o":[[1.39,0],[1.148,-1.813],[0,0],[-0.544,2.719],[-0.665,2.175],[-1.571,0],[-0.846,-2.175],[0,0],[0,0],[0,0],[0,0],[0,0],[0,9.305],[1.45,3.021]],"v":[[111.301,5.922],[115.107,3.384],[118.25,-7.191],[114.624,-8.943],[113.113,-2.115],[110.938,1.148],[107.494,-2.175],[106.225,-17.161],[106.225,-24.955],[68.097,-24.955],[68.097,-20.786],[102.297,-20.786],[102.297,-17.161],[104.473,1.329]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.744,-34.261],[74.744,-30.212],[109.971,-30.212],[109.971,-34.261]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"气","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":250,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“吸气”轮廓","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0],"e":[100]},{"t":250}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[388.379,95.913,0],"ix":2},"a":{"a":0,"k":[42,-13.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[20.847,-44.171],[4.894,-44.171],[4.894,-3.142],[9.185,-3.142],[9.185,-7.432],[20.847,-7.432]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9.185,-11.541],[9.185,-40.062],[16.677,-40.062],[16.677,-11.541]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[8.036,-8.218],[0,0],[-1.813,12.206],[-3.625,-4.592],[5.257,-2.477],[0,0],[-3.746,3.867],[-4.532,-2.78],[0,0],[3.323,3.444],[-1.329,8.218],[0,0],[0,0],[-1.088,5.378],[0,0],[0,0],[0,0]],"o":[[0,0],[-0.121,18.127],[0,0],[6.345,-6.586],[1.934,5.68],[-3.565,3.625],[0,0],[5.438,-2.538],[3.263,3.565],[0,0],[-4.471,-2.659],[4.351,-5.68],[0,0],[0,0],[1.088,-3.867],[0,0],[0,0],[0,0],[0,0]],"v":[[29.306,-42.66],[29.306,-36.859],[17.04,2.659],[20.424,5.62],[32.69,-22.599],[40.968,-7.13],[27.735,1.994],[30.152,5.922],[43.929,-3.686],[55.651,5.861],[58.431,2.175],[46.708,-7.009],[55.228,-27.795],[55.228,-30.212],[47.977,-30.212],[51.24,-44.11],[51.24,-46.95],[23.566,-46.95],[23.566,-42.66]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[3.565,-4.653],[1.571,8.701],[0,1.329],[0,0],[0,0],[1.148,-3.686],[0,0]],"o":[[-5.197,-6.405],[0,-1.269],[0,0],[0,0],[-1.39,7.13],[0,0],[-1.208,6.042]],"v":[[43.748,-10.333],[33.657,-32.992],[33.717,-36.859],[33.717,-42.66],[46.527,-42.66],[42.72,-26.406],[50.878,-26.406]],"c":true},"ix":2},"nm":"吸","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"吸","np":7,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.84,4.955],[0,0],[0,0],[0,0],[-0.725,1.752],[0,0],[5.076,-5.015],[0,0]],"o":[[0,0],[0,0],[0,0],[0.725,-1.571],[0,0],[-3.263,8.097],[0,0],[3.686,-3.625]],"v":[[75.469,-39.578],[112.932,-39.578],[112.932,-43.808],[77.704,-43.808],[79.819,-48.763],[75.408,-49.73],[62.9,-30.031],[65.68,-26.647]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.082,-0.06],[-1.148,1.692],[-0.967,5.197],[0,0],[0.483,-1.813],[0.786,0],[0.725,2.175],[0,7.855],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.45,-3.021]],"o":[[1.39,0],[1.148,-1.813],[0,0],[-0.544,2.719],[-0.665,2.175],[-1.571,0],[-0.846,-2.175],[0,0],[0,0],[0,0],[0,0],[0,0],[0,9.305],[1.45,3.021]],"v":[[111.301,5.922],[115.107,3.384],[118.25,-7.191],[114.624,-8.943],[113.113,-2.115],[110.938,1.148],[107.494,-2.175],[106.225,-17.161],[106.225,-24.955],[68.097,-24.955],[68.097,-20.786],[102.297,-20.786],[102.297,-17.161],[104.473,1.329]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.744,-34.261],[74.744,-30.212],[109.971,-30.212],[109.971,-34.261]],"c":true},"ix":2},"nm":"气","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"气","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":250,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"“放松大图ai”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.467],"y":[1]},"o":{"x":[0.556],"y":[0]},"t":0,"s":[0],"e":[180]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":100,"s":[180],"e":[180]},{"i":{"x":[0.431],"y":[1]},"o":{"x":[0.574],"y":[0]},"t":125,"s":[180],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[1],"y":[0]},"t":225,"s":[0],"e":[0]},{"t":250}],"ix":10},"p":{"a":0,"k":[400,591.928,0],"ix":2},"a":{"a":0,"k":[400,400,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[80,80,100],"e":[163,163,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":100,"s":[163,163,100],"e":[163,163,100]},{"i":{"x":[0.431,0.431,0.667],"y":[1,1,1]},"o":{"x":[0.574,0.574,0.333],"y":[0,0,0]},"t":125,"s":[163,163,100],"e":[80,80,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":225,"s":[80,80,100],"e":[80,80,100]},{"t":250}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-57.395,33.138],[33.138,57.395],[57.395,-33.137],[-33.137,-57.395]],"o":[[57.396,-33.137],[-33.137,-57.395],[-57.395,33.137],[33.137,57.396]],"v":[[60,103.923],[103.922,-60],[-60,-103.923],[-103.923,60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[340,296.134],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,66.274],[66.274,0],[0,-66.274],[-66.274,0]],"o":[[0,-66.274],[-66.274,0],[0,66.274],[66.274,0]],"v":[[120,0],[0,-120],[-120,0],[0,120]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[280,400.057],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[57.396,33.137],[33.137,-57.394],[-57.395,-33.138],[-33.137,57.396]],"o":[[-57.395,-33.137],[-33.137,57.396],[57.395,33.137],[33.138,-57.394]],"v":[[60,-103.923],[-103.923,-60.001],[-60,103.923],[103.922,59.999]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[340,503.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[57.396,-33.137],[-33.137,-57.395],[-57.396,33.136],[33.138,57.395]],"o":[[-57.395,33.138],[33.137,57.395],[57.396,-33.138],[-33.137,-57.395]],"v":[[-60,-103.923],[-103.923,60],[60,103.924],[103.922,-60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[460,503.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-66.274],[-66.274,0],[0,66.274],[66.274,0]],"o":[[0,66.274],[66.274,0],[0,-66.274],[-66.274,0]],"v":[[-120,0],[0,120],[120,0],[0,-120]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[520,400.057],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-57.395,-33.137],[-33.137,57.395],[57.396,33.137],[33.137,-57.395]],"o":[[57.396,33.137],[33.138,-57.395],[-57.396,-33.137],[-33.137,57.395]],"v":[[-60,103.923],[103.922,60],[60,-103.923],[-103.923,-60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[460,296.134],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":250,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file \ No newline at end of file
{"v":"5.6.9","fr":25,"ip":0,"op":150,"w":500,"h":500,"nm":"放松大图ai","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“放松大图ai”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0],"e":[360]},{"t":150}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[400,400,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-57.395,33.138],[33.138,57.395],[57.395,-33.137],[-33.137,-57.395]],"o":[[57.396,-33.137],[-33.137,-57.395],[-57.395,33.137],[33.137,57.396]],"v":[[60,103.923],[103.922,-60],[-60,-103.923],[-103.923,60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[340,296.134],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,66.274],[66.274,0],[0,-66.274],[-66.274,0]],"o":[[0,-66.274],[-66.274,0],[0,66.274],[66.274,0]],"v":[[120,0],[0,-120],[-120,0],[0,120]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[280,400.057],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[57.396,33.137],[33.137,-57.394],[-57.395,-33.138],[-33.137,57.396]],"o":[[-57.395,-33.137],[-33.137,57.396],[57.395,33.137],[33.138,-57.394]],"v":[[60,-103.923],[-103.923,-60.001],[-60,103.923],[103.922,59.999]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[340,503.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[57.396,-33.137],[-33.137,-57.395],[-57.396,33.136],[33.138,57.395]],"o":[[-57.395,33.138],[33.137,57.395],[57.396,-33.138],[-33.137,-57.395]],"v":[[-60,-103.923],[-103.923,60],[60,103.924],[103.922,-60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[460,503.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-66.274],[-66.274,0],[0,66.274],[66.274,0]],"o":[[0,66.274],[66.274,0],[0,-66.274],[-66.274,0]],"v":[[-120,0],[0,120],[120,0],[0,-120]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[520,400.057],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-57.395,-33.137],[-33.137,57.395],[57.396,33.137],[33.137,-57.395]],"o":[[57.396,33.137],[33.138,-57.395],[-57.396,-33.137],[-33.137,57.395]],"v":[[-60,103.923],[103.922,60],[60,-103.923],[-103.923,-60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.569000004787,0.827000038297,0.875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[460,296.134],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"组 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file \ No newline at end of file
...@@ -47,6 +47,8 @@ ...@@ -47,6 +47,8 @@
#define DSRed [UIColor redColor] #define DSRed [UIColor redColor]
// 白色 // 白色
#define DSWhite [UIColor whiteColor] #define DSWhite [UIColor whiteColor]
// clearColor
#define DSClearColor [UIColor clearColor]
/** 主title颜色 */ /** 主title颜色 */
#define MainTextColor ColorFromHex(0x333333) #define MainTextColor ColorFromHex(0x333333)
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
typedef void (^CellConfigureBlock)(id cell, id model, NSIndexPath * indexPath); typedef void (^CellConfigureBlock)(id _Nullable cell, id _Nullable model, NSIndexPath * _Nullable indexPath);
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
/** /**
......
...@@ -6,6 +6,7 @@ target 'DreamSleep' do ...@@ -6,6 +6,7 @@ target 'DreamSleep' do
pod 'DKNightVersion', '~> 2.4.3' pod 'DKNightVersion', '~> 2.4.3'
pod 'MJRefresh', '~> 3.7.5' pod 'MJRefresh', '~> 3.7.5'
pod 'Masonry', '~> 1.1.0' pod 'Masonry', '~> 1.1.0'
pod 'lottie-ios', '~> 2.5.3'
end end
# AFNetworking (4.0.1) # AFNetworking (4.0.1)
...@@ -13,3 +14,4 @@ end ...@@ -13,3 +14,4 @@ end
# DKNightVersion (2.4.3) # DKNightVersion (2.4.3)
# MJRefresh (3.7.5) # MJRefresh (3.7.5)
# Masonry (1.1.0) # Masonry (1.1.0)
# lottie-ios (2.5.3)
...@@ -19,6 +19,7 @@ PODS: ...@@ -19,6 +19,7 @@ PODS:
- DKNightVersion/Core - DKNightVersion/Core
- DKNightVersion/UIKit (2.4.3): - DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core - DKNightVersion/Core
- lottie-ios (2.5.3)
- Masonry (1.1.0) - Masonry (1.1.0)
- MJRefresh (3.7.5) - MJRefresh (3.7.5)
- YTKNetwork (3.0.6): - YTKNetwork (3.0.6):
...@@ -26,6 +27,7 @@ PODS: ...@@ -26,6 +27,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- DKNightVersion (~> 2.4.3) - DKNightVersion (~> 2.4.3)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0) - Masonry (~> 1.1.0)
- MJRefresh (~> 3.7.5) - MJRefresh (~> 3.7.5)
- YTKNetwork (~> 3.0.6) - YTKNetwork (~> 3.0.6)
...@@ -34,6 +36,7 @@ SPEC REPOS: ...@@ -34,6 +36,7 @@ SPEC REPOS:
trunk: trunk:
- AFNetworking - AFNetworking
- DKNightVersion - DKNightVersion
- lottie-ios
- Masonry - Masonry
- MJRefresh - MJRefresh
- YTKNetwork - YTKNetwork
...@@ -41,10 +44,11 @@ SPEC REPOS: ...@@ -41,10 +44,11 @@ SPEC REPOS:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3 DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961 MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04 YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
PODFILE CHECKSUM: c13215e6e4a9ac525d48e4cd5cd0a1c66a74b643 PODFILE CHECKSUM: da4f3494cbd800091ea38764f7f43754ca9369ea
COCOAPODS: 1.11.3 COCOAPODS: 1.11.3
...@@ -19,6 +19,7 @@ PODS: ...@@ -19,6 +19,7 @@ PODS:
- DKNightVersion/Core - DKNightVersion/Core
- DKNightVersion/UIKit (2.4.3): - DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core - DKNightVersion/Core
- lottie-ios (2.5.3)
- Masonry (1.1.0) - Masonry (1.1.0)
- MJRefresh (3.7.5) - MJRefresh (3.7.5)
- YTKNetwork (3.0.6): - YTKNetwork (3.0.6):
...@@ -26,6 +27,7 @@ PODS: ...@@ -26,6 +27,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- DKNightVersion (~> 2.4.3) - DKNightVersion (~> 2.4.3)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0) - Masonry (~> 1.1.0)
- MJRefresh (~> 3.7.5) - MJRefresh (~> 3.7.5)
- YTKNetwork (~> 3.0.6) - YTKNetwork (~> 3.0.6)
...@@ -34,6 +36,7 @@ SPEC REPOS: ...@@ -34,6 +36,7 @@ SPEC REPOS:
trunk: trunk:
- AFNetworking - AFNetworking
- DKNightVersion - DKNightVersion
- lottie-ios
- Masonry - Masonry
- MJRefresh - MJRefresh
- YTKNetwork - YTKNetwork
...@@ -41,10 +44,11 @@ SPEC REPOS: ...@@ -41,10 +44,11 @@ SPEC REPOS:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3 DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961 MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04 YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
PODFILE CHECKSUM: c13215e6e4a9ac525d48e4cd5cd0a1c66a74b643 PODFILE CHECKSUM: da4f3494cbd800091ea38764f7f43754ca9369ea
COCOAPODS: 1.11.3 COCOAPODS: 1.11.3
...@@ -118,4 +118,209 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ...@@ -118,4 +118,209 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## lottie-ios
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Airbnb, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Generated by CocoaPods - https://cocoapods.org Generated by CocoaPods - https://cocoapods.org
...@@ -162,6 +162,217 @@ THE SOFTWARE. ...@@ -162,6 +162,217 @@ THE SOFTWARE.
</dict> </dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string> Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Airbnb, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</string>
<key>License</key>
<string>Apache</string>
<key>Title</key>
<string>lottie-ios</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Generated by CocoaPods - https://cocoapods.org</string> <string>Generated by CocoaPods - https://cocoapods.org</string>
<key>Title</key> <key>Title</key>
<string></string> <string></string>
......
...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework ...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework ${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework ${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework ${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework ...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework ...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework ${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework ${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework ${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework ...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework ...@@ -4,3 +4,4 @@ ${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework ${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework ${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework ${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework ...@@ -3,3 +3,4 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework
\ No newline at end of file \ No newline at end of file
...@@ -181,6 +181,7 @@ if [[ "$CONFIGURATION" == "Beta" ]]; then ...@@ -181,6 +181,7 @@ if [[ "$CONFIGURATION" == "Beta" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework" install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework" install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework"
fi fi
if [[ "$CONFIGURATION" == "Debug" ]]; then if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
...@@ -188,6 +189,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then ...@@ -188,6 +189,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework" install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework" install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework"
fi fi
if [[ "$CONFIGURATION" == "Release" ]]; then if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
...@@ -195,6 +197,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then ...@@ -195,6 +197,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework" install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework" install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework"
fi fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait wait
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork" OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork" OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork" OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.5.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_lottie_ios : NSObject
@end
@implementation PodsDummy_lottie_ios
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "LOTAnimatedControl.h"
#import "LOTAnimatedSwitch.h"
#import "LOTAnimationCache.h"
#import "LOTAnimationTransitionController.h"
#import "LOTAnimationView.h"
#import "LOTAnimationView_Compat.h"
#import "LOTBlockCallback.h"
#import "LOTCacheProvider.h"
#import "LOTComposition.h"
#import "LOTInterpolatorCallback.h"
#import "LOTKeypath.h"
#import "Lottie.h"
#import "LOTValueCallback.h"
#import "LOTValueDelegate.h"
FOUNDATION_EXPORT double LottieVersionNumber;
FOUNDATION_EXPORT const unsigned char LottieVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/lottie-ios
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module Lottie {
umbrella header "lottie-ios-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/lottie-ios
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Airbnb, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# Lottie for iOS, macOS (and [Android](https://github.com/airbnb/lottie-android) and [React Native](https://github.com/airbnb/lottie-react-native))
### Table of Contents
- [Introduction](#introduction)
- [Installing Lottie](#installing-lottie)
- [iOS Sample App](#ios-sample-app)
- [macOS Sample App](#macos-sample-app)
- [Objective C Examples](#objective-c-examples)
- [Swift Examples](#swift-examples)
- [Debugging Lottie](#debugging)
- [iOS View Controller Transitioning](#ios-view-controller-transitioning)
- [Changing Animations At Runtime](#changing-animations-at-runtime)
- [Animated Controls and Switches](#animated-controls-and-switches)
- [Adding Subviews to Animation](#adding-views-to-an-animation-at-runtime)
- [Supported After Effects Features](#supported-after-effects-features)
- [Currently Unsupported After Effects Features](#currently-unsupported-after-effects-features)
- [Community Contributions](#community-contributions)
- [Alternatives](#alternatives)
- [Why is it called Lottie?](#why-is-it-called-lottie)
- [Contributing](#contributing)
- [Issues or feature requests?](#issues-or-feature-requests)
## Introduction
Lottie is a mobile library for Android and iOS that parses [Adobe After Effects](http://www.adobe.com/products/aftereffects.html) animations exported as json with [bodymovin](https://github.com/bodymovin/bodymovin) and renders the vector animations natively on mobile and through React Native!
For the first time, designers can create **and ship** beautiful animations without an engineer painstakingly recreating it by hand.
Since the animation is backed by JSON they are extremely small in size but can be large in complexity!
Animations can be played, resized, looped, sped up, slowed down, reversed, and even interactively scrubbed.
Lottie can play or loop just a portion of the animation as well, the possibilities are endless!
Animations can even be ***changed at runtime*** in various ways! Change the color, position or any keyframable value!
Lottie also supports native UIViewController Transitions out of the box!
Here is just a small sampling of the power of Lottie
![Example1](_Gifs/Examples1.gif)
![Example2](_Gifs/Examples2.gif)
<img src="_Gifs/Community 2_3.gif" />
![Example3](_Gifs/Examples3.gif)
![Abcs](_Gifs/Examples4.gif)
## Installing Lottie
### Github Repo
You can pull the [Lottie Github Repo](https://github.com/airbnb/lottie-ios/) and include the Lottie.xcodeproj to build a dynamic or static library.
### Cocoapods
Get [Cocoapods](https://cocoapods.org/)
Add the pod to your podfile
```
pod 'lottie-ios'
```
run
```
pod install
```
After installing the cocoapod into your project import Lottie with
Objective C
`#import <Lottie/Lottie.h>`
Swift
`import Lottie`
### Carthage
Get [Carthage](https://github.com/Carthage/Carthage)
Add Lottie to your Cartfile
```
github "airbnb/lottie-ios" "master"
```
run
```
carthage update
```
In your application targets “General” tab under the “Linked Frameworks and Libraries” section, drag and drop lottie-ios.framework from the Carthage/Build/iOS directory that `carthage update` produced.
## iOS Sample App
Clone this repo and try out [the Sample App](https://github.com/airbnb/lottie-ios/tree/master/Example)
The repo can build a macOS Example and an iOS Example
The iOS Example App demos several of the features of Lottie
![Example 1](_Gifs/iosexample1.png)![Example 2](_Gifs/iosexample2.png)
![Example 3](_Gifs/iosexample3.png)
The animation Explorer allows you to scrub, play, loop, and resize animations.
Animations can be loaded from the app bundle or from [Lottie Files](http://www.lottiefiles.com) using the built in QR Code reader.
## macOS Sample App
Clone this repo and try out [the Sample App](https://github.com/airbnb/lottie-ios/tree/master/Example)
The repo can build a macOS Example and an iOS Example
![Lottie Viewer](_Gifs/macexample.png)
The Lottie Viewer for macOS allows you to drag and drop JSON files to open, play, scrub and loop animations. This app is backed by the same animation code as the iOS app, so you will get an accurate representation of Mac and iOS animations.
## Objective C Examples
Lottie animations can be loaded from bundled JSON or from a URL
To bundle JSON just add it and any images that the animation requires to your target in xcode.
```objective-c
LOTAnimationView *animation = [LOTAnimationView animationNamed:@"Lottie"];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
// Do Something
}];
```
If you are working with multiple bundles you can use.
```objective-c
LOTAnimationView *animation = [LOTAnimationView animationNamed:@"Lottie" inBundle:[NSBundle YOUR_BUNDLE]];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
// Do Something
}];
```
Or you can load it programmatically from a NSURL
```objective-c
LOTAnimationView *animation = [[LOTAnimationView alloc] initWithContentsOfURL:[NSURL URLWithString:URL]];
[self.view addSubview:animation];
```
Lottie supports the iOS `UIViewContentModes` aspectFit, aspectFill and scaleFill
You can also set the animation progress interactively.
```objective-c
CGPoint translation = [gesture getTranslationInView:self.view];
CGFloat progress = translation.y / self.view.bounds.size.height;
animationView.animationProgress = progress;
```
Or you can play just a portion of the animation:
```objective-c
[lottieAnimation playFromProgress:0.25 toProgress:0.5 withCompletion:^(BOOL animationFinished) {
// Do Something
}];
```
## Swift Examples
Lottie animations can be loaded from bundled JSON or from a URL
To bundle JSON just add it and any images that the animation requires to your target in xcode.
```swift
let animationView = LOTAnimationView(name: "LottieLogo")
self.view.addSubview(animationView)
animationView.play{ (finished) in
// Do Something
}
```
If your animation is in another bundle you can use
```swift
let animationView = LOTAnimationView(name: "LottieLogo", bundle: yourBundle)
self.view.addSubview(animationView)
animationView.play()
```
Or you can load it asynchronously from a URL
```swift
let animationView = LOTAnimationView(contentsOf: WebURL)
self.view.addSubview(animationView)
animationView.play()
```
You can also set the animation progress interactively.
```swift
let translation = gesture.getTranslationInView(self.view)
let progress = translation.y / self.view.bounds.size.height
animationView.animationProgress = progress
```
Or you can play just a portion of the animation:
```swift
animationView.play(fromProgress: 0.25, toProgress: 0.5, withCompletion: nil)
```
## iOS View Controller Transitioning
Lottie comes with a `UIViewController` animation-controller for making custom viewController transitions!
![Transition1](_Gifs/transitionMasked.gif)
![Transition2](_Gifs/transitionPosition.gif)
Just become the delegate for a transition
```objective-c
- (void)_showTransitionA {
ToAnimationViewController *vc = [[ToAnimationViewController alloc] init];
vc.transitioningDelegate = self;
[self presentViewController:vc animated:YES completion:NULL];
}
```
And implement the delegate methods with a `LOTAnimationTransitionController`
```objective-c
#pragma mark -- View Controller Transitioning
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
LOTAnimationTransitionController *animationController = [[LOTAnimationTransitionController alloc] initWithAnimationNamed:@"vcTransition1" fromLayerNamed:@"outLayer" toLayerNamed:@"inLayer" applyAnimationTransform:NO];
return animationController;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
LOTAnimationTransitionController *animationController = [[LOTAnimationTransitionController alloc] initWithAnimationNamed:@"vcTransition2" fromLayerNamed:@"outLayer" toLayerNamed:@"inLayer" applyAnimationTransform:NO];
return animationController;
}
```
By setting `applyAnimationTransform` to YES you can make the Lottie animation move the from and to view controllers. They will be positioned at the origin of the layer. When set to NO Lottie just masks the view controller with the specified layer while respecting z order.
## Debugging
Lottie has a couple of debugging features to know about.
When an animation is loaded unsupported features are logged out in the console with their function names.
If you checkout LOTHelpers.h you will see two debug flags. `ENABLE_DEBUG_LOGGING` and `ENABLE_DEBUG_SHAPES`.
`ENABLE_DEBUG_LOGGING` increases the verbosity of Lottie Logging. It logs anytime an animation node is set during animation. If your animation if not working, turn this on and play your animation. The console log might give you some clues as to whats going on.
`ENABLE_DEBUG_SHAPES` Draws a colored square for the anchor-point of every layer and shape. This is helpful to see if anything is on screen.
### Keypaths
LOTAnimationView provides `- (void)logHierarchyKeypaths` which will recursively log all settable keypaths for the animation. This is helpful for changing animations at runtime.
## Adding Views to an Animation at Runtime
Not only can you [change animations at runtime](#changing-animations-at-runtime) with Lottie, you can also add custom UI to a LOTAnimation at runtime.
The example below shows some advance uses of this to create a dynamic image loader.
## A Dynamic Image Loading Spinner
![Spinner](/_Gifs/spinner.gif)
The example above shows a single LOTAnimationView that is set with a loading spinner animation. The loading spinner loops a portion of its animation while an image is downloaded asynchronously. When the download is complete, the image is added to the animation and the rest of the animation is played seamlessly. The image is cleanly animated in and a completion block is called.
![Spinner_Alt](/_Gifs/spinner_Alternative.gif)
Now, the animation has been changed by a designer and needs to be updated. All that is required is updating the JSON file in the bundle. No code change needed!
![Spinner_Dark](/_Gifs/spinner_DarkMode.gif)
Here, the design has decided to add a 'Dark Mode' to the app. Just a few lines of code change the color of the animation at runtime.
Pretty powerful eh?
Check out the code below for an example!
```swift
import UIKit
import Lottie
class ViewController: UIViewController {
var animationView: LOTAnimationView = LOTAnimationView(name: "SpinnerSpin");
override func viewDidLoad() {
super.viewDidLoad()
// Setup our animation view
animationView.contentMode = .scaleAspectFill
animationView.frame = CGRect(x: 20, y: 20, width: 200, height: 200)
self.view.addSubview(animationView)
// Lets change some of the properties of the animation
// We aren't going to use the MaskLayer, so let's just hide it
animationView.setValue(0, forKeypath: "MaskLayer.Ellipse 1.Transform.Opacity", atFrame: 0)
// All of the strokes and fills are white, lets make them DarkGrey
animationView.setValue(UIColor.darkGray, forKeypath: "OuterRing.Stroke.Color", atFrame: 0)
animationView.setValue(UIColor.darkGray, forKeypath: "InnerRing.Stroke.Color", atFrame: 0)
animationView.setValue(UIColor.darkGray, forKeypath: "InnerRing.Fill.Color", atFrame: 0)
// Lets turn looping on, since we want it to repeat while the image is 'Downloading'
animationView.loopAnimation = true
// Now play from 0 to 0.5 progress and loop indefinitely.
animationView.play(fromProgress: 0, toProgress: 0.5, withCompletion: nil)
// Lets simulate a download that finishes in 4 seconds.
let dispatchTime = DispatchTime.now() + 4.0
DispatchQueue.main.asyncAfter(deadline: dispatchTime) {
self.simulateImageDownloaded()
}
}
func simulateImageDownloaded() {
// Our downloaded image
let image = UIImage(named: "avatar.jpg")
let imageView = UIImageView(image: image)
// We want the image to show up centered in the animation view at 150Px150P
// Convert that rect to the animations coordinate space
// The origin is set to -75, -75 because the origin is centered in the animation view
let imageRect = animationView.convert(CGRect(x: -75, y: -75, width: 150, height: 150), toLayerNamed: nil)
// Setup our image view with the rect and add rounded corners
imageView.frame = imageRect
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = imageRect.width / 2;
// Now we set the completion block on the currently running animation
animationView.completionBlock = { (result: Bool) in ()
// Add the image view to the layer named "TransformLayer"
self.animationView.addSubview(imageView, toLayerNamed: "TransformLayer", applyTransform: true)
// Now play the last half of the animation
self.animationView.play(fromProgress: 0.5, toProgress: 1, withCompletion: { (complete: Bool) in
// Now the animation has finished and our image is displayed on screen
print("Image Downloaded and Displayed")
})
}
// Turn looping off. Once the current loop finishes the animation will stop
// and the completion block will be called.
animationView.loopAnimation = false
}
}
```
## Changing Animations At Runtime
Lottie can do more than just play beautiful animations. Lottie allows you to **change** animations at runtime.
### Say we want to create 4 toggle switches.
![Toggle](_Gifs/switch_Normal.gif)
Its easy to create the four switches and play them:
```swift
let animationView = LOTAnimationView(name: "toggle");
self.view.addSubview(animationView)
animationView.frame.origin.x = 40
animationView.frame.origin.y = 20
animationView.autoReverseAnimation = true
animationView.loopAnimation = true
animationView.play()
let animationView2 = LOTAnimationView(name: "toggle");
self.view.addSubview(animationView2)
animationView2.frame.origin.x = 40
animationView2.frame.origin.y = animationView.frame.maxY + 4
animationView2.autoReverseAnimation = true
animationView2.loopAnimation = true
animationView2.play()
let animationView3 = LOTAnimationView(name: "toggle");
self.view.addSubview(animationView3)
animationView3.frame.origin.x = 40
animationView3.frame.origin.y = animationView2.frame.maxY + 4
animationView3.autoReverseAnimation = true
animationView3.loopAnimation = true
animationView3.play()
let animationView4 = LOTAnimationView(name: "toggle");
self.view.addSubview(animationView4)
animationView4.frame.origin.x = 40
animationView4.frame.origin.y = animationView3.frame.maxY + 4
animationView4.autoReverseAnimation = true
animationView4.loopAnimation = true
animationView4.play()
```
### Now lets change their colors
![Recolored Toggle](_Gifs/switch_BgColors.gif)
**NB**: `animationView.setValue(YOUR_COLOR, forKeypath: "YOUR_PATH.Color", atFrame: 0)` is now deprecated.
```swift
class GreenDelegate : NSObject, LOTColorValueDelegate {
func color(forFrame currentFrame: CGFloat, startKeyframe: CGFloat, endKeyframe: CGFloat, interpolatedProgress: CGFloat, start startColor: CGColor!, end endColor: CGColor!, currentColor interpolatedColor: CGColor!) -> Unmanaged<CGColor>! {
return Unmanaged.passRetained(UIColor.green.cgColor)
}
}
animationView2.setValueDelegate(GreenDelegate(), for: LOTKeypath(string: "BG-On.Group 1.Fill 1.Color"))
class RedDelegate : NSObject, LOTColorValueDelegate {
func color(forFrame currentFrame: CGFloat, startKeyframe: CGFloat, endKeyframe: CGFloat, interpolatedProgress: CGFloat, start startColor: CGColor!, end endColor: CGColor!, currentColor interpolatedColor: CGColor!) -> Unmanaged<CGColor>! {
return Unmanaged.passRetained(UIColor.red.cgColor)
}
}
animationView3.setValueDelegate(RedDelegate(), for: LOTKeypath(string: "BG-On.Group 1.Fill 1.Color"))
class OrangeDelegate : NSObject, LOTColorValueDelegate {
func color(forFrame currentFrame: CGFloat, startKeyframe: CGFloat, endKeyframe: CGFloat, interpolatedProgress: CGFloat, start startColor: CGColor!, end endColor: CGColor!, currentColor interpolatedColor: CGColor!) -> Unmanaged<CGColor>! {
return Unmanaged.passRetained(UIColor.orange.cgColor)
}
}
animationView4.setValueDelegate(OrangeDelegate(), for: LOTKeypath(string: "BG-On.Group 1.Fill 1.Color"))
```
The keyPath is a dot separated path of layer and property names from After Effects.
LOTAnimationView provides `- (void)logHierarchyKeypaths` which will recursively log all settable keypaths for the animation.
![Key Path](_Gifs/aftereffectskeypath.png)
"BG-On.Group 1.Fill 1.Color"
### Now lets change a couple of properties
![Multiple Colors](_Gifs/switch_MultipleBgs.gif)
```swift
animationView2.setValueDelegate(delegate, for: LOTKeypath(string: YOUR_PATH))
```
Lottie allows you to change **any** property that is animatable in After Effects. If a keyframe does not exist, a linear keyframe is created for you. If a keyframe does exist then just its data is replaced.
For this you need to use a `LOTValueDelegate` there are many already available:
* `LOTColorValueDelegate`
* `LOTNumberValueDelegate`
* `LOTPointValueDelegate`
* `LOTSizeValueDelegate`
* `LOTPathValueDelegate`
## Animated Controls and Switches
![Animated Buttons](_Gifs/switchTest.gif)
Lottie also has a custom subclass of UIControl for creating custom animatable interactive controls.
Currently Lottie has `LOTAnimatedSwitch` which is a toggle style switch control. Tapping on the switch plays either the On-Off or Off-On animation and sends out a UIControlStateValueChanged broadcast to all targets. It is used in the same way UISwitch is used with a few additions to setup the animation with Lottie.
You initialize the switch either using the convenience method or by supplying the animation directly.
```
// Convenience
LOTAnimatedSwitch *toggle1 = [LOTAnimatedSwitch switchNamed:@"Switch"];
// Manually
LOTComposition *comp = [LOTComposition animationNamed:@"Switch"];
LOTAnimatedSwitch *toggle1 = [[LOTAnimatedSwitch alloc] initWithFrame:CGRectZero];
[toggle1 setAnimationComp:comp];
```
You can also specify a specific portion of the animation's timeline for the On and Off animation.
By default `LOTAnimatedSwitch` will play the animation forward for On and backwards for off.
Lets say that the supplied animation animates ON from 0.5-1 progress and OFF from 0-0.5:
```
/// On animation is 0.5 to 1 progress.
[toggle1 setProgressRangeForOnState:0.5 toProgress:1];
/// Off animation is 0 to 0.5 progress.
[toggle1 setProgressRangeForOffState:0 toProgress:0.5];
```
Also, all LOTAnimatedControls add support for changing appearance for state changes. This requires some setup in After Effects. Lottie will switch visible animated layers based on the controls state. This can be used to have Disabled, selected, or Highlighted states. These states are associated with layer names in After Effects, and are dynamically displayed as the control changes states.
Lets say we have a toggle switch with a Normal and Disabled state. In Effects we have a composition that contains Precomps of the regular "Button" and disabled "Disabled" states. They have different visual styles.
![Regular](_Gifs/switch_enabled.png)
![Disabled](_Gifs/switch_disabled.png)
Now in code we can associate `UIControlState` with these layers
```
// Specify the layer names for different states
[statefulSwitch setLayerName:@"Button" forState:UIControlStateNormal];
[statefulSwitch setLayerName:@"Disabled" forState:UIControlStateDisabled];
// Changes visual appearance by switching animation layer to "Disabled"
statefulSwitch.enabled = NO;
// Changes visual appearance by switching animation layer to "Button"
statefulSwitch.enabled = YES;
```
## Supported After Effects Features
### Keyframe Interpolation
---
* Linear Interpolation
* Bezier Interpolation
* Hold Interpolation
* Rove Across Time
* Spatial Bezier
### Solids
---
* Transform Anchor Point
* Transform Position
* Transform Scale
* Transform Rotation
* Transform Opacity
### Masks
---
* Path
* Opacity
* Multiple Masks (additive, subtractive and intersection)
### Track Mattes
---
* Alpha Matte
### Parenting
---
* Multiple Parenting
* Nulls
### Shape Layers
---
* Anchor Point
* Position
* Scale
* Rotation
* Opacity
* Path
* Group Transforms (Anchor point, position, scale etc)
* Rectangle (All properties)
* Eclipse (All properties)
* Multiple paths in one group
* Even-Odd winding paths
* Reverse Fill Rule
#### Stroke (shape layer)
---
* Stroke Color
* Stroke Opacity
* Stroke Width
* Line Cap
* Dashes (Now Animated!)
#### Fill (shape layer)
---
* Fill Color
* Fill Opacity
#### Trim Paths (shape layer)
---
* Trim Paths Start
* Trim Paths End
* Trim Paths Offset
### Repeaters
---
* Supports repeater transforms
* Offset currently not supported.
### Gradients
---
* Support for Linear Gradients
* Support for Radial Gradients
### Polystar and Polygon
---
* Supported! Theres a known bug if the roundness is greater than 100 percent.
#### Layer Features
---
* Precomps
* Image Layers
* Shape Layers
* Null Layers
* Solid Layers
* Parenting Layers
* Alpha Matte Layers
## Currently Unsupported After Effects Features
* Merge Shapes
* Alpha Inverted Masks
* Trim Shapes Individually feature of Trim Paths
* Expressions
* 3d Layer support
* Time remapping / Layer Reverse
* Layer Blend Modes
* Layer Effects
## Community Contributions
* [Xamarin bindings](https://github.com/martijn00/LottieXamarin)
* [NativeScript bindings](https://github.com/bradmartin/nativescript-lottie)
* [Appcelerator Titanium bindings](https://github.com/m1ga/ti.animation)
* macOS Support added by [Alex Pawlowski](https://github.com/pawlowskialex)
## Alternatives
1. Build animations by hand. Building animations by hand is a huge time commitment for design and engineering across Android and iOS. It's often hard or even impossible to justify spending so much time to get an animation right.
2. [Facebook Keyframes](https://github.com/facebookincubator/Keyframes). Keyframes is a wonderful new library from Facebook that they built for reactions. However, Keyframes doesn't support some of Lottie's features such as masks, mattes, trim paths, dash patterns, and more.
2. Gifs. Gifs are more than double the size of a bodymovin JSON and are rendered at a fixed size that can't be scaled up to match large and high density screens.
3. Png sequences. Png sequences are even worse than gifs in that their file sizes are often 30-50x the size of the bodymovin json and also can't be scaled up.
## Why is it called Lottie?
Lottie is named after a German film director and the foremost pioneer of silhouette animation. Her best known films are The Adventures of Prince Achmed (1926) – the oldest surviving feature-length animated film, preceding Walt Disney's feature-length Snow White and the Seven Dwarfs (1937) by over ten years
[The art of Lotte Reineger](https://www.youtube.com/watch?v=LvU55CUw5Ck&feature=youtu.be)
## Contributing
Contributors are more than welcome. Just upload a PR with a description of your changes.
If you would like to add more JSON files feel free to do so!
## Issues or feature requests?
File github issues for anything that is unexpectedly broken. If an After Effects file is not working, please attach it to your issue. Debugging without the original file is much more difficult. Lottie is developed and maintained by [Brandon Withrow](mailto:brandon@withrow.io). Feel free to reach out via email or [Twitter](https://twitter.com/theWithra)
## Roadmap (In no particular order)
- Add support for interactive animated transitions
//
// LOTCompositionContainer.h
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTLayerContainer.h"
#import "LOTAssetGroup.h"
@interface LOTCompositionContainer : LOTLayerContainer
- (instancetype _Nonnull)initWithModel:(LOTLayer * _Nullable)layer
inLayerGroup:(LOTLayerGroup * _Nullable)layerGroup
withLayerGroup:(LOTLayerGroup * _Nullable)childLayerGroup
withAssestGroup:(LOTAssetGroup * _Nullable)assetGroup;
- (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath;
- (CGPoint)convertPoint:(CGPoint)point
toKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent;
- (CGRect)convertRect:(CGRect)rect
toKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent;
- (CGPoint)convertPoint:(CGPoint)point
fromKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent;
- (CGRect)convertRect:(CGRect)rect
fromKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent;
- (void)addSublayer:(nonnull CALayer *)subLayer
toKeypathLayer:(nonnull LOTKeypath *)keypath;
- (void)maskSublayer:(nonnull CALayer *)subLayer
toKeypathLayer:(nonnull LOTKeypath *)keypath;
@property (nonatomic, readonly, nonnull) NSArray<LOTLayerContainer *> *childLayers;
@property (nonatomic, readonly, nonnull) NSDictionary *childMap;
@end
//
// LOTCompositionContainer.m
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTCompositionContainer.h"
#import "LOTAsset.h"
#import "CGGeometry+LOTAdditions.h"
#import "LOTHelpers.h"
#import "LOTValueInterpolator.h"
#import "LOTAnimatorNode.h"
#import "LOTRenderNode.h"
#import "LOTRenderGroup.h"
#import "LOTNumberInterpolator.h"
@implementation LOTCompositionContainer {
NSNumber *_frameOffset;
CALayer *DEBUG_Center;
NSMutableDictionary *_keypathCache;
LOTNumberInterpolator *_timeInterpolator;
}
- (instancetype)initWithModel:(LOTLayer *)layer
inLayerGroup:(LOTLayerGroup *)layerGroup
withLayerGroup:(LOTLayerGroup *)childLayerGroup
withAssestGroup:(LOTAssetGroup *)assetGroup {
self = [super initWithModel:layer inLayerGroup:layerGroup];
if (self) {
DEBUG_Center = [CALayer layer];
DEBUG_Center.bounds = CGRectMake(0, 0, 20, 20);
DEBUG_Center.borderColor = [UIColor orangeColor].CGColor;
DEBUG_Center.borderWidth = 2;
DEBUG_Center.masksToBounds = YES;
if (ENABLE_DEBUG_SHAPES) {
[self.wrapperLayer addSublayer:DEBUG_Center];
}
if (layer.startFrame != nil) {
_frameOffset = layer.startFrame;
} else {
_frameOffset = @0;
}
if (layer.timeRemapping) {
_timeInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:layer.timeRemapping.keyframes];
}
[self initializeWithChildGroup:childLayerGroup withAssetGroup:assetGroup];
}
return self;
}
- (void)initializeWithChildGroup:(LOTLayerGroup *)childGroup
withAssetGroup:(LOTAssetGroup *)assetGroup {
NSMutableDictionary *childMap = [NSMutableDictionary dictionary];
NSMutableArray *children = [NSMutableArray array];
NSArray *reversedItems = [[childGroup.layers reverseObjectEnumerator] allObjects];
CALayer *maskedLayer = nil;
for (LOTLayer *layer in reversedItems) {
LOTAsset *asset;
if (layer.referenceID) {
// Get relevant Asset
asset = [assetGroup assetModelForID:layer.referenceID];
}
LOTLayerContainer *child = nil;
if (asset.layerGroup) {
// Layer is a precomp
LOTCompositionContainer *compLayer = [[LOTCompositionContainer alloc] initWithModel:layer inLayerGroup:childGroup withLayerGroup:asset.layerGroup withAssestGroup:assetGroup];
child = compLayer;
} else {
child = [[LOTLayerContainer alloc] initWithModel:layer inLayerGroup:childGroup];
}
if (maskedLayer) {
maskedLayer.mask = child;
maskedLayer = nil;
} else {
if (layer.matteType == LOTMatteTypeAdd) {
maskedLayer = child;
}
[self.wrapperLayer addSublayer:child];
}
[children addObject:child];
if (child.layerName) {
[childMap setObject:child forKey:child.layerName];
}
}
_childMap = childMap;
_childLayers = children;
}
- (void)displayWithFrame:(NSNumber *)frame forceUpdate:(BOOL)forceUpdate {
if (ENABLE_DEBUG_LOGGING) NSLog(@"-------------------- Composition Displaying Frame %@ --------------------", frame);
[super displayWithFrame:frame forceUpdate:forceUpdate];
NSNumber *newFrame = @((frame.floatValue - _frameOffset.floatValue) / self.timeStretchFactor.floatValue);
if (_timeInterpolator) {
newFrame = @([_timeInterpolator floatValueForFrame:newFrame]);
}
for (LOTLayerContainer *child in _childLayers) {
[child displayWithFrame:newFrame forceUpdate:forceUpdate];
}
if (ENABLE_DEBUG_LOGGING) NSLog(@"-------------------- ------------------------------- --------------------");
if (ENABLE_DEBUG_LOGGING) NSLog(@"-------------------- ------------------------------- --------------------");
}
- (void)setViewportBounds:(CGRect)viewportBounds {
[super setViewportBounds:viewportBounds];
for (LOTLayerContainer *layer in _childLayers) {
layer.viewportBounds = viewportBounds;
}
}
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
if (self.layerName != nil) {
[super searchNodesForKeypath:keypath];
}
if (self.layerName == nil ||
[keypath pushKey:self.layerName]) {
for (LOTLayerContainer *child in _childLayers) {
[child searchNodesForKeypath:keypath];
}
if (self.layerName != nil) {
[keypath popKey];
}
}
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath {
if (self.layerName != nil) {
[super setValueDelegate:delegate forKeypath:keypath];
}
if (self.layerName == nil ||
[keypath pushKey:self.layerName]) {
for (LOTLayerContainer *child in _childLayers) {
[child setValueDelegate:delegate forKeypath:keypath];
}
if (self.layerName != nil) {
[keypath popKey];
}
}
}
- (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath {
if (_keypathCache == nil) {
_keypathCache = [NSMutableDictionary dictionary];
}
[self searchNodesForKeypath:keypath];
[_keypathCache addEntriesFromDictionary:keypath.searchResults];
return keypath.searchResults.allKeys;
}
- (CALayer *)_layerForKeypath:(nonnull LOTKeypath *)keypath {
id node = _keypathCache[keypath.absoluteKeypath];
if (node == nil) {
[self keysForKeyPath:keypath];
node = _keypathCache[keypath.absoluteKeypath];
}
if (node == nil) {
NSLog(@"LOTComposition could not find layer for keypath:%@", keypath.absoluteKeypath);
return nil;
}
if ([node isKindOfClass:[CALayer class]]) {
return (CALayer *)node;
}
if (![node isKindOfClass:[LOTRenderNode class]]) {
NSLog(@"LOTComposition: Keypath return non-layer node:%@ ", keypath.absoluteKeypath);
return nil;
}
if ([node isKindOfClass:[LOTRenderGroup class]]) {
return [(LOTRenderGroup *)node containerLayer];
}
LOTRenderNode *renderNode = (LOTRenderNode *)node;
return renderNode.outputLayer;
}
- (CGPoint)convertPoint:(CGPoint)point
toKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent{
CALayer *layer = [self _layerForKeypath:keypath];
if (!layer) {
return CGPointZero;
}
return [parent convertPoint:point toLayer:layer];
}
- (CGRect)convertRect:(CGRect)rect
toKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent{
CALayer *layer = [self _layerForKeypath:keypath];
if (!layer) {
return CGRectZero;
}
return [parent convertRect:rect toLayer:layer];
}
- (CGPoint)convertPoint:(CGPoint)point
fromKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent{
CALayer *layer = [self _layerForKeypath:keypath];
if (!layer) {
return CGPointZero;
}
return [parent convertPoint:point fromLayer:layer];
}
- (CGRect)convertRect:(CGRect)rect
fromKeypathLayer:(nonnull LOTKeypath *)keypath
withParentLayer:(CALayer *_Nonnull)parent{
CALayer *layer = [self _layerForKeypath:keypath];
if (!layer) {
return CGRectZero;
}
return [parent convertRect:rect fromLayer:layer];
}
- (void)addSublayer:(nonnull CALayer *)subLayer
toKeypathLayer:(nonnull LOTKeypath *)keypath {
CALayer *layer = [self _layerForKeypath:keypath];
if (layer) {
[layer addSublayer:subLayer];
}
}
- (void)maskSublayer:(nonnull CALayer *)subLayer
toKeypathLayer:(nonnull LOTKeypath *)keypath {
CALayer *layer = [self _layerForKeypath:keypath];
if (layer) {
[layer.superlayer addSublayer:subLayer];
[layer removeFromSuperlayer];
subLayer.mask = layer;
}
}
@end
//
// LOTLayerContainer.h
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPlatformCompat.h"
#import "LOTLayer.h"
#import "LOTLayerGroup.h"
#import "LOTKeypath.h"
#import "LOTValueDelegate.h"
@class LOTValueCallback;
@interface LOTLayerContainer : CALayer
- (instancetype _Nonnull)initWithModel:(LOTLayer * _Nullable)layer
inLayerGroup:(LOTLayerGroup * _Nullable)layerGroup;
@property (nonatomic, readonly, strong, nullable) NSString *layerName;
@property (nonatomic, nullable) NSNumber *currentFrame;
@property (nonatomic, readonly, nonnull) NSNumber *timeStretchFactor;
@property (nonatomic, assign) CGRect viewportBounds;
@property (nonatomic, readonly, nonnull) CALayer *wrapperLayer;
@property (nonatomic, readonly, nonnull) NSDictionary *valueInterpolators;
- (void)displayWithFrame:(NSNumber * _Nonnull)frame;
- (void)displayWithFrame:(NSNumber * _Nonnull)frame forceUpdate:(BOOL)forceUpdate;
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath;
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath;
@end
//
// LOTLayerContainer.m
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTLayerContainer.h"
#import "LOTTransformInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
#import "LOTRenderGroup.h"
#import "LOTHelpers.h"
#import "LOTMaskContainer.h"
#import "LOTAsset.h"
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "LOTCacheProvider.h"
#endif
@implementation LOTLayerContainer {
LOTTransformInterpolator *_transformInterpolator;
LOTNumberInterpolator *_opacityInterpolator;
NSNumber *_inFrame;
NSNumber *_outFrame;
CALayer *DEBUG_Center;
LOTRenderGroup *_contentsGroup;
LOTMaskContainer *_maskLayer;
}
@dynamic currentFrame;
- (instancetype)initWithModel:(LOTLayer *)layer
inLayerGroup:(LOTLayerGroup *)layerGroup {
self = [super init];
if (self) {
_wrapperLayer = [CALayer new];
[self addSublayer:_wrapperLayer];
DEBUG_Center = [CALayer layer];
DEBUG_Center.bounds = CGRectMake(0, 0, 20, 20);
DEBUG_Center.borderColor = [UIColor blueColor].CGColor;
DEBUG_Center.borderWidth = 2;
DEBUG_Center.masksToBounds = YES;
if (ENABLE_DEBUG_SHAPES) {
[_wrapperLayer addSublayer:DEBUG_Center];
}
self.actions = @{@"hidden" : [NSNull null], @"opacity" : [NSNull null], @"transform" : [NSNull null]};
_wrapperLayer.actions = [self.actions copy];
_timeStretchFactor = @1;
[self commonInitializeWith:layer inLayerGroup:layerGroup];
}
return self;
}
- (void)commonInitializeWith:(LOTLayer *)layer
inLayerGroup:(LOTLayerGroup *)layerGroup {
if (layer == nil) {
return;
}
_layerName = layer.layerName;
if (layer.layerType == LOTLayerTypeImage ||
layer.layerType == LOTLayerTypeSolid ||
layer.layerType == LOTLayerTypePrecomp) {
_wrapperLayer.bounds = CGRectMake(0, 0, layer.layerWidth.floatValue, layer.layerHeight.floatValue);
_wrapperLayer.anchorPoint = CGPointMake(0, 0);
_wrapperLayer.masksToBounds = YES;
DEBUG_Center.position = LOT_RectGetCenterPoint(self.bounds);
}
if (layer.layerType == LOTLayerTypeImage) {
[self _setImageForAsset:layer.imageAsset];
}
_inFrame = [layer.inFrame copy];
_outFrame = [layer.outFrame copy];
_timeStretchFactor = [layer.timeStretch copy];
_transformInterpolator = [LOTTransformInterpolator transformForLayer:layer];
if (layer.parentID != nil) {
NSNumber *parentID = layer.parentID;
LOTTransformInterpolator *childInterpolator = _transformInterpolator;
while (parentID != nil) {
LOTLayer *parentModel = [layerGroup layerModelForID:parentID];
LOTTransformInterpolator *interpolator = [LOTTransformInterpolator transformForLayer:parentModel];
childInterpolator.inputNode = interpolator;
childInterpolator = interpolator;
parentID = parentModel.parentID;
}
}
_opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:layer.opacity.keyframes];
if (layer.layerType == LOTLayerTypeShape &&
layer.shapes.count) {
[self buildContents:layer.shapes];
}
if (layer.layerType == LOTLayerTypeSolid) {
_wrapperLayer.backgroundColor = layer.solidColor.CGColor;
}
if (layer.masks.count) {
_maskLayer = [[LOTMaskContainer alloc] initWithMasks:layer.masks];
_wrapperLayer.mask = _maskLayer;
}
NSMutableDictionary *interpolators = [NSMutableDictionary dictionary];
interpolators[@"Opacity"] = _opacityInterpolator;
interpolators[@"Anchor Point"] = _transformInterpolator.anchorInterpolator;
interpolators[@"Scale"] = _transformInterpolator.scaleInterpolator;
interpolators[@"Rotation"] = _transformInterpolator.rotationInterpolator;
if (_transformInterpolator.positionXInterpolator &&
_transformInterpolator.positionYInterpolator) {
interpolators[@"X Position"] = _transformInterpolator.positionXInterpolator;
interpolators[@"Y Position"] = _transformInterpolator.positionYInterpolator;
} else if (_transformInterpolator.positionInterpolator) {
interpolators[@"Position"] = _transformInterpolator.positionInterpolator;
}
// Deprecated
interpolators[@"Transform.Opacity"] = _opacityInterpolator;
interpolators[@"Transform.Anchor Point"] = _transformInterpolator.anchorInterpolator;
interpolators[@"Transform.Scale"] = _transformInterpolator.scaleInterpolator;
interpolators[@"Transform.Rotation"] = _transformInterpolator.rotationInterpolator;
if (_transformInterpolator.positionXInterpolator &&
_transformInterpolator.positionYInterpolator) {
interpolators[@"Transform.X Position"] = _transformInterpolator.positionXInterpolator;
interpolators[@"Transform.Y Position"] = _transformInterpolator.positionYInterpolator;
} else if (_transformInterpolator.positionInterpolator) {
interpolators[@"Transform.Position"] = _transformInterpolator.positionInterpolator;
}
_valueInterpolators = interpolators;
}
- (void)buildContents:(NSArray *)contents {
_contentsGroup = [[LOTRenderGroup alloc] initWithInputNode:nil contents:contents keyname:_layerName];
[_wrapperLayer addSublayer:_contentsGroup.containerLayer];
}
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- (void)_setImageForAsset:(LOTAsset *)asset {
if (asset.imageName) {
UIImage *image;
if ([asset.imageName hasPrefix:@"data:"]) {
// Contents look like a data: URL. Ignore asset.imageDirectory and simply load the image directly.
NSURL *imageUrl = [NSURL URLWithString:asset.imageName];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
image = [UIImage imageWithData:imageData];
} else if (asset.rootDirectory.length > 0) {
NSString *rootDirectory = asset.rootDirectory;
if (asset.imageDirectory.length > 0) {
rootDirectory = [rootDirectory stringByAppendingPathComponent:asset.imageDirectory];
}
NSString *imagePath = [rootDirectory stringByAppendingPathComponent:asset.imageName];
id<LOTImageCache> imageCache = [LOTCacheProvider imageCache];
if (imageCache) {
image = [imageCache imageForKey:imagePath];
if (!image) {
image = [UIImage imageWithContentsOfFile:imagePath];
[imageCache setImage:image forKey:imagePath];
}
} else {
image = [UIImage imageWithContentsOfFile:imagePath];
}
} else {
NSString *imagePath = [asset.assetBundle pathForResource:asset.imageName ofType:nil];
image = [UIImage imageWithContentsOfFile:imagePath];
}
//try loading from asset catalogue instead if all else fails
if (!image) {
image = [UIImage imageNamed:asset.imageName inBundle: asset.assetBundle compatibleWithTraitCollection:nil];
}
if (image) {
_wrapperLayer.contents = (__bridge id _Nullable)(image.CGImage);
} else {
NSLog(@"%s: Warn: image not found: %@", __PRETTY_FUNCTION__, asset.imageName);
}
}
}
#else
- (void)_setImageForAsset:(LOTAsset *)asset {
if (asset.imageName) {
NSArray *components = [asset.imageName componentsSeparatedByString:@"."];
NSImage *image = [NSImage imageNamed:components.firstObject];
if (image == nil) {
if (asset.rootDirectory.length > 0 && asset.imageDirectory.length > 0) {
NSString *imagePath = [[asset.rootDirectory stringByAppendingPathComponent:asset.imageDirectory] stringByAppendingPathComponent:asset.imageName];
image = [[NSImage alloc] initWithContentsOfFile:imagePath];
}
}
if (image) {
NSWindow *window = [NSApp mainWindow];
CGFloat desiredScaleFactor = [window backingScaleFactor];
CGFloat actualScaleFactor = [image recommendedLayerContentsScale:desiredScaleFactor];
id layerContents = [image layerContentsForContentsScale:actualScaleFactor];
_wrapperLayer.contents = layerContents;
}
}
}
#endif
// MARK - Animation
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:@"currentFrame"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (id<CAAction>)actionForKey:(NSString *)event {
if ([event isEqualToString:@"currentFrame"]) {
CABasicAnimation *theAnimation = [CABasicAnimation
animationWithKeyPath:event];
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
return theAnimation;
}
return [super actionForKey:event];
}
- (id)initWithLayer:(id)layer {
if (self = [super initWithLayer:layer]) {
if ([layer isKindOfClass:[LOTLayerContainer class]]) {
LOTLayerContainer *other = (LOTLayerContainer *)layer;
self.currentFrame = [other.currentFrame copy];
}
}
return self;
}
- (void)display {
@synchronized(self) {
LOTLayerContainer *presentation = self;
if (self.animationKeys.count &&
self.presentationLayer) {
presentation = (LOTLayerContainer *)self.presentationLayer;
}
[self displayWithFrame:presentation.currentFrame];
}
}
- (void)displayWithFrame:(NSNumber *)frame {
[self displayWithFrame:frame forceUpdate:NO];
}
- (void)displayWithFrame:(NSNumber *)frame forceUpdate:(BOOL)forceUpdate {
NSNumber *newFrame = @(frame.floatValue / self.timeStretchFactor.floatValue);
if (ENABLE_DEBUG_LOGGING) NSLog(@"View %@ Displaying Frame %@, with local time %@", self, frame, newFrame);
BOOL hidden = NO;
if (_inFrame && _outFrame) {
hidden = (frame.floatValue < _inFrame.floatValue ||
frame.floatValue > _outFrame.floatValue);
}
self.hidden = hidden;
if (hidden) {
return;
}
if (_opacityInterpolator && [_opacityInterpolator hasUpdateForFrame:newFrame]) {
self.opacity = [_opacityInterpolator floatValueForFrame:newFrame];
}
if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
_wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
}
[_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];
_maskLayer.currentFrame = newFrame;
}
- (void)setViewportBounds:(CGRect)viewportBounds {
_viewportBounds = viewportBounds;
if (_maskLayer) {
CGPoint center = LOT_RectGetCenterPoint(viewportBounds);
viewportBounds.origin = CGPointMake(-center.x, -center.y);
_maskLayer.bounds = viewportBounds;
}
}
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
if (_contentsGroup == nil && [keypath pushKey:self.layerName]) {
// Matches self.
if ([keypath pushKey:@"Transform"]) {
// Is a transform node, check interpolators
LOTValueInterpolator *interpolator = _valueInterpolators[keypath.currentKey];
if (interpolator) {
// We have a match!
[keypath pushKey:keypath.currentKey];
[keypath addSearchResultForCurrentPath:_wrapperLayer];
[keypath popKey];
}
if (keypath.endOfKeypath) {
[keypath addSearchResultForCurrentPath:_wrapperLayer];
}
[keypath popKey];
}
if (keypath.endOfKeypath) {
[keypath addSearchResultForCurrentPath:_wrapperLayer];
}
[keypath popKey];
}
[_contentsGroup searchNodesForKeypath:keypath];
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath {
if ([keypath pushKey:self.layerName]) {
// Matches self.
if ([keypath pushKey:@"Transform"]) {
// Is a transform node, check interpolators
LOTValueInterpolator *interpolator = _valueInterpolators[keypath.currentKey];
if (interpolator) {
// We have a match!
[interpolator setValueDelegate:delegate];
}
[keypath popKey];
}
[keypath popKey];
}
[_contentsGroup setValueDelegate:delegate forKeypath:keypath];
}
@end
//
// LOTMaskContainer.h
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "LOTMask.h"
@interface LOTMaskContainer : CALayer
- (instancetype _Nonnull)initWithMasks:(NSArray<LOTMask *> * _Nonnull)masks;
@property (nonatomic, strong, nullable) NSNumber *currentFrame;
@end
//
// LOTMaskContainer.m
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTMaskContainer.h"
#import "LOTPathInterpolator.h"
#import "LOTNumberInterpolator.h"
@interface LOTMaskNodeLayer : CAShapeLayer
@property (nonatomic, readonly) LOTMask *maskNode;
- (instancetype)initWithMask:(LOTMask *)maskNode;
- (BOOL)hasUpdateForFrame:(NSNumber *)frame;
@end
@implementation LOTMaskNodeLayer {
LOTPathInterpolator *_pathInterpolator;
LOTNumberInterpolator *_opacityInterpolator;
LOTNumberInterpolator *_expansionInterpolator;
}
- (instancetype)initWithMask:(LOTMask *)maskNode {
self = [super init];
if (self) {
_pathInterpolator = [[LOTPathInterpolator alloc] initWithKeyframes:maskNode.maskPath.keyframes];
_opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:maskNode.opacity.keyframes];
_expansionInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:maskNode.expansion.keyframes];
_maskNode = maskNode;
self.fillColor = [UIColor blueColor].CGColor;
}
return self;
}
- (void)updateForFrame:(NSNumber *)frame withViewBounds:(CGRect)viewBounds {
if ([self hasUpdateForFrame:frame]) {
LOTBezierPath *path = [_pathInterpolator pathForFrame:frame cacheLengths:NO];
if (self.maskNode.maskMode == LOTMaskModeSubtract) {
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, viewBounds);
CGPathAddPath(pathRef, NULL, path.CGPath);
self.path = pathRef;
self.fillRule = @"even-odd";
CGPathRelease(pathRef);
} else {
self.path = path.CGPath;
}
self.opacity = [_opacityInterpolator floatValueForFrame:frame];
}
}
- (BOOL)hasUpdateForFrame:(NSNumber *)frame {
return ([_pathInterpolator hasUpdateForFrame:frame] ||
[_opacityInterpolator hasUpdateForFrame:frame]);
}
@end
@implementation LOTMaskContainer {
NSArray<LOTMaskNodeLayer *> *_masks;
}
- (instancetype)initWithMasks:(NSArray<LOTMask *> *)masks {
self = [super init];
if (self) {
NSMutableArray *maskNodes = [NSMutableArray array];
CALayer *containerLayer = [CALayer layer];
for (LOTMask *mask in masks) {
LOTMaskNodeLayer *node = [[LOTMaskNodeLayer alloc] initWithMask:mask];
[maskNodes addObject:node];
if (mask.maskMode == LOTMaskModeAdd ||
mask == masks.firstObject) {
[containerLayer addSublayer:node];
} else {
containerLayer.mask = node;
CALayer *newContainer = [CALayer layer];
[newContainer addSublayer:containerLayer];
containerLayer = newContainer;
}
}
[self addSublayer:containerLayer];
_masks = maskNodes;
}
return self;
}
- (void)setCurrentFrame:(NSNumber *)currentFrame {
if (_currentFrame == currentFrame) {
return;
}
_currentFrame = currentFrame;
for (LOTMaskNodeLayer *nodeLayer in _masks) {
[nodeLayer updateForFrame:currentFrame withViewBounds:self.bounds];
}
}
@end
//
// LOTBezierData.h
// Lottie
//
// Created by brandon_withrow on 7/10/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
NS_ASSUME_NONNULL_BEGIN
@interface LOTBezierData : NSObject
- (instancetype)initWithData:(NSDictionary *)bezierData;
@property (nonatomic, readonly) NSInteger count;
@property (nonatomic, readonly) BOOL closed;
- (CGPoint)vertexAtIndex:(NSInteger)index;
- (CGPoint)inTangentAtIndex:(NSInteger)index;
- (CGPoint)outTangentAtIndex:(NSInteger)index;
@end
NS_ASSUME_NONNULL_END
//
// LOTBezierData.m
// Lottie
//
// Created by brandon_withrow on 7/10/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTBezierData.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTBezierData {
CGPoint *_vertices;
CGPoint *_inTangents;
CGPoint *_outTangents;
}
- (instancetype)initWithData:(NSDictionary *)bezierData
{
self = [super init];
if (self) {
[self initializeData:bezierData];
}
return self;
}
- (void)dealloc {
free(_vertices);
free(_inTangents);
free(_outTangents);
}
- (CGPoint)vertexAtIndex:(NSInteger)index {
NSAssert((index < _count &&
index >= 0),
@"Lottie: Index out of bounds");
return _vertices[index];
}
- (CGPoint)inTangentAtIndex:(NSInteger)index {
NSAssert((index < _count &&
index >= 0),
@"Lottie: Index out of bounds");
return _inTangents[index];
}
- (CGPoint)outTangentAtIndex:(NSInteger)index {
NSAssert((index < _count &&
index >= 0),
@"Lottie: Index out of bounds");
return _outTangents[index];
}
- (void)initializeData:(NSDictionary *)bezierData {
NSArray *pointsArray = bezierData[@"v"];
NSArray *inTangents = bezierData[@"i"];
NSArray *outTangents = bezierData[@"o"];
if (pointsArray.count == 0) {
NSLog(@"%s: Warning: shape has no vertices", __PRETTY_FUNCTION__);
return ;
}
NSAssert((pointsArray.count == inTangents.count &&
pointsArray.count == outTangents.count),
@"Lottie: Incorrect number of points and tangents");
_count = pointsArray.count;
_vertices = (CGPoint *)malloc(sizeof(CGPoint) * pointsArray.count);
_inTangents = (CGPoint *)malloc(sizeof(CGPoint) * pointsArray.count);
_outTangents = (CGPoint *)malloc(sizeof(CGPoint) * pointsArray.count);
if (bezierData[@"c"]) {
_closed = [bezierData[@"c"] boolValue];
}
for (int i = 0; i < pointsArray.count; i ++) {
CGPoint vertex = [self _vertexAtIndex:i inArray:pointsArray];
CGPoint outTan = LOT_PointAddedToPoint(vertex, [self _vertexAtIndex:i inArray:outTangents]);
CGPoint inTan = LOT_PointAddedToPoint(vertex, [self _vertexAtIndex:i inArray:inTangents]);
// BW BUG Straight Lines - Test Later
// Bake fix for lines here
_vertices[i] = vertex;
_inTangents[i] = inTan;
_outTangents[i] = outTan;
}
}
- (CGPoint)_vertexAtIndex:(NSInteger)idx inArray:(NSArray *)points {
NSAssert((idx < points.count),
@"Lottie: Vertex Point out of bounds");
NSArray *pointArray = points[idx];
NSAssert((pointArray.count >= 2 &&
[pointArray.firstObject isKindOfClass:[NSNumber class]]),
@"Lottie: Point Data Malformed");
return CGPointMake([(NSNumber *)pointArray[0] floatValue], [(NSNumber *)pointArray[1] floatValue]);
}
@end
//
// LOTKeyframe.h
// Pods
//
// Created by brandon_withrow on 7/10/17.
//
//
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import "LOTPlatformCompat.h"
#import "LOTBezierData.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTKeyframe : NSObject
- (instancetype)initWithKeyframe:(NSDictionary *)keyframe;
- (instancetype)initWithValue:(id)value;
- (void)remapValueWithBlock:(CGFloat (^)(CGFloat inValue))remapBlock;
- (LOTKeyframe *)copyWithData:(id)data;
@property (nonatomic, readonly) NSNumber *keyframeTime;
@property (nonatomic, readonly) BOOL isHold;
@property (nonatomic, readonly) CGPoint inTangent;
@property (nonatomic, readonly) CGPoint outTangent;
@property (nonatomic, readonly) CGPoint spatialInTangent;
@property (nonatomic, readonly) CGPoint spatialOutTangent;
@property (nonatomic, readonly) CGFloat floatValue;
@property (nonatomic, readonly) CGPoint pointValue;
@property (nonatomic, readonly) CGSize sizeValue;
@property (nonatomic, readonly) UIColor *colorValue;
@property (nonatomic, readonly, nullable) LOTBezierData *pathData;
@property (nonatomic, readonly) NSArray *arrayValue;
@end
@interface LOTKeyframeGroup : NSObject
- (instancetype)initWithData:(id)data;
- (void)remapKeyframesWithBlock:(CGFloat (^)(CGFloat inValue))remapBlock;
@property (nonatomic, readonly) NSArray<LOTKeyframe *> *keyframes;
@end
NS_ASSUME_NONNULL_END
//
// LOTKeyframe.m
// Pods
//
// Created by brandon_withrow on 7/10/17.
//
//
#import "LOTKeyframe.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTKeyframe
- (instancetype)initWithKeyframe:(NSDictionary *)keyframe {
self = [super init];
if (self) {
_keyframeTime = keyframe[@"t"];
_inTangent = CGPointZero;
_outTangent = CGPointZero;
_spatialInTangent = CGPointZero;
_spatialOutTangent = CGPointZero;
NSDictionary *timingOutTangent = keyframe[@"o"];
NSDictionary *timingInTangent = keyframe[@"i"];
if (timingInTangent) {
_inTangent = [self _pointFromValueDict:timingInTangent];
}
if (timingOutTangent) {
_outTangent = [self _pointFromValueDict:timingOutTangent];
}
if ([keyframe[@"h"] boolValue]) {
_isHold = YES;
}
if (keyframe[@"to"]) {
NSArray *to = keyframe[@"to"];
_spatialOutTangent = [self _pointFromValueArray:to];
}
if (keyframe[@"ti"]) {
NSArray *ti = keyframe[@"ti"];
_spatialInTangent = [self _pointFromValueArray:ti];
}
id data = keyframe[@"s"];
if (data) {
[self setupOutputWithData:data];
}
}
return self;
}
- (instancetype)initWithValue:(id)value {
self = [super init];
if (self) {
_keyframeTime = @0;
_isHold = YES;
[self setupOutputWithData:value];
}
return self;
}
- (instancetype)initWithLOTKeyframe:(LOTKeyframe *)keyframe {
self = [super init];
if (self) {
_keyframeTime = [keyframe.keyframeTime copy];
_inTangent = keyframe.inTangent;
_outTangent = keyframe.outTangent;
_spatialInTangent = keyframe.spatialInTangent;
_spatialOutTangent = keyframe.spatialOutTangent;
_isHold = keyframe.isHold;
}
return self;
}
- (LOTKeyframe *)copyWithData:(id)data {
LOTKeyframe *newFrame = [[LOTKeyframe alloc] initWithLOTKeyframe:self];
[newFrame setData:data];
return newFrame;
}
- (void)setData:(id)data {
[self setupOutputWithData:data];
}
- (void)remapValueWithBlock:(CGFloat (^)(CGFloat inValue))remapBlock {
_floatValue = remapBlock(_floatValue);
_pointValue = CGPointMake(remapBlock(_pointValue.x), remapBlock(_pointValue.y));
_sizeValue = CGSizeMake(remapBlock(_sizeValue.width), remapBlock(_sizeValue.height));
}
- (void)setupOutputWithData:(id)data {
if ([data isKindOfClass:[NSNumber class]]) {
_floatValue = [(NSNumber *)data floatValue];
}
if ([data isKindOfClass:[NSArray class]] &&
[[(NSArray *)data firstObject] isKindOfClass:[NSNumber class]]) {
NSArray *numberArray = (NSArray *)data;
if (numberArray.count > 0) {
_floatValue = [(NSNumber *)numberArray[0] floatValue];
}
if (numberArray.count > 1) {
_pointValue = CGPointMake(_floatValue = [(NSNumber *)numberArray[0] floatValue],
_floatValue = [(NSNumber *)numberArray[1] floatValue]);
_sizeValue = CGSizeMake(_pointValue.x, _pointValue.y);
}
if (numberArray.count > 3) {
_colorValue = [self _colorValueFromArray:numberArray];
}
_arrayValue = numberArray;
} else if ([data isKindOfClass:[NSArray class]] &&
[[(NSArray *)data firstObject] isKindOfClass:[NSDictionary class]]) {
_pathData = [[LOTBezierData alloc] initWithData:[(NSArray *)data firstObject]];
} else if ([data isKindOfClass:[NSDictionary class]]) {
_pathData = [[LOTBezierData alloc] initWithData:data];
}
}
- (CGPoint)_pointFromValueArray:(NSArray *)values {
CGPoint returnPoint = CGPointZero;
if (values.count > 1) {
returnPoint.x = [(NSNumber *)values[0] floatValue];
returnPoint.y = [(NSNumber *)values[1] floatValue];
}
return returnPoint;
}
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
NSNumber *xValue = @0, *yValue = @0;
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
xValue = values[@"x"];
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
xValue = values[@"x"][0];
}
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
yValue = values[@"y"];
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
yValue = values[@"y"][0];
}
return CGPointMake([xValue floatValue], [yValue floatValue]);
}
- (UIColor *)_colorValueFromArray:(NSArray<NSNumber *> *)colorArray {
if (colorArray.count == 4) {
BOOL shouldUse255 = NO;
for (NSNumber *number in colorArray) {
if (number.floatValue > 1) {
shouldUse255 = YES;
}
}
return [UIColor colorWithRed:colorArray[0].floatValue / (shouldUse255 ? 255.f : 1.f)
green:colorArray[1].floatValue / (shouldUse255 ? 255.f : 1.f)
blue:colorArray[2].floatValue / (shouldUse255 ? 255.f : 1.f)
alpha:colorArray[3].floatValue / (shouldUse255 ? 255.f : 1.f)];
}
return nil;
}
@end
@implementation LOTKeyframeGroup
- (instancetype)initWithData:(id)data {
self = [super init];
if (self) {
if ([data isKindOfClass:[NSDictionary class]] &&
[(NSDictionary *)data valueForKey:@"k"]) {
[self buildKeyframesFromData:[(NSDictionary *)data valueForKey:@"k"]];
} else {
[self buildKeyframesFromData:data];
}
}
return self;
}
- (void)buildKeyframesFromData:(id)data {
if ([data isKindOfClass:[NSArray class]] &&
[[(NSArray *)data firstObject] isKindOfClass:[NSDictionary class]] &&
[(NSArray *)data firstObject][@"t"]) {
// Array of Keyframes
NSArray *keyframes = (NSArray *)data;
NSMutableArray *keys = [NSMutableArray array];
NSDictionary *previousFrame = nil;
for (NSDictionary *keyframe in keyframes) {
NSMutableDictionary *currentFrame = [NSMutableDictionary dictionary];
if (keyframe[@"t"]) {
// Set time
currentFrame[@"t"] = keyframe[@"t"];
}
if (keyframe[@"s"]) {
// Set Value for Keyframe
currentFrame[@"s"] = keyframe[@"s"];
} else if (previousFrame[@"e"]) {
// Set Value for Keyframe
currentFrame[@"s"] = previousFrame[@"e"];
}
if (keyframe[@"o"]) {
// Set out tangent
currentFrame[@"o"] = keyframe[@"o"];
}
if (previousFrame[@"i"]) {
currentFrame[@"i"] = previousFrame[@"i"];
}
if (keyframe[@"to"]) {
// Set out tangent
currentFrame[@"to"] = keyframe[@"to"];
}
if (previousFrame[@"ti"]) {
currentFrame[@"ti"] = previousFrame[@"ti"];
}
if (keyframe[@"h"]) {
currentFrame[@"h"] = keyframe[@"h"];
}
LOTKeyframe *key = [[LOTKeyframe alloc] initWithKeyframe:currentFrame];
[keys addObject:key];
previousFrame = keyframe;
}
_keyframes = keys;
} else {
LOTKeyframe *key = [[LOTKeyframe alloc] initWithValue:data];
_keyframes = @[key];
}
}
- (void)remapKeyframesWithBlock:(CGFloat (^)(CGFloat))remapBlock {
for (LOTKeyframe *keyframe in _keyframes) {
[keyframe remapValueWithBlock:remapBlock];
}
}
@end
/*
+KeyFrameObject has
+ i (PointObject) // Timing curve in tangent
+ o (PointObject) // Timing curve out tangent
+ n (array of string) // String representation of timing curve
+ t (integer) // Keyframe time for start of keyframe
+ s (float or array of float or PathObject) // The key information
+ e (float or array of float or PathObject) // The end key information
+ to (array of float) // For spacial bezier path interpolation, the in tangent
+ ti (array of float) // For spacial bezier path interpolation, the out tangent
+ h (integer) // If the keyframe is a Hold keyframe or not
*/
#import "LOTPlatformCompat.h"
#import <CoreGraphics/CoreGraphics.h>
//
// Core Graphics Geometry Additions
//
extern const CGSize CGSizeMax;
CGRect LOT_RectIntegral(CGRect rect);
// Centering
// Returns a rectangle of the given size, centered at a point
CGRect LOT_RectCenteredAtPoint(CGPoint center, CGSize size, BOOL integral);
// Returns the center point of a CGRect
CGPoint LOT_RectGetCenterPoint(CGRect rect);
// Insetting
// Inset the rectangle on a single edge
CGRect LOT_RectInsetLeft(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetRight(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetTop(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetBottom(CGRect rect, CGFloat inset);
// Inset the rectangle on two edges
CGRect LOT_RectInsetHorizontal(CGRect rect, CGFloat leftInset, CGFloat rightInset);
CGRect LOT_RectInsetVertical(CGRect rect, CGFloat topInset, CGFloat bottomInset);
// Inset the rectangle on all edges
CGRect LOT_RectInsetAll(CGRect rect, CGFloat leftInset, CGFloat rightInset, CGFloat topInset, CGFloat bottomInset);
// Framing
// Returns a rectangle of size framed in the center of the given rectangle
CGRect LOT_RectFramedCenteredInRect(CGRect rect, CGSize size, BOOL integral);
// Returns a rectangle of size framed in the given rectangle and inset
CGRect LOT_RectFramedLeftInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedRightInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedTopInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedBottomInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedTopLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedTopRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedBottomLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedBottomRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
// Divides a rect into sections and returns the section at specified index
CGRect LOT_RectDividedSection(CGRect rect, NSInteger sections, NSInteger index, CGRectEdge fromEdge);
// Returns a rectangle of size attached to the given rectangle
CGRect LOT_RectAttachedLeftToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedRightToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedTopToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedBottomToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedBottomLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedBottomRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedTopRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedTopLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
BOOL LOT_CGPointIsZero(CGPoint point);
// Combining
// Adds all values of the 2nd rect to the first rect
CGRect LOT_RectAddRect(CGRect rect, CGRect other);
CGRect LOT_RectAddPoint(CGRect rect, CGPoint point);
CGRect LOT_RectAddSize(CGRect rect, CGSize size);
CGRect LOT_RectBounded(CGRect rect);
CGPoint LOT_PointAddedToPoint(CGPoint point1, CGPoint point2);
CGRect LOT_RectSetHeight(CGRect rect, CGFloat height);
CGFloat LOT_PointDistanceFromPoint(CGPoint point1, CGPoint point2);
CGFloat LOT_DegreesToRadians(CGFloat degrees);
CGFloat LOT_RemapValue(CGFloat value, CGFloat low1, CGFloat high1, CGFloat low2, CGFloat high2 );
CGPoint LOT_PointByLerpingPoints(CGPoint point1, CGPoint point2, CGFloat value);
CGPoint LOT_PointInLine(CGPoint A, CGPoint B, CGFloat T);
CGPoint LOT_PointInCubicCurve(CGPoint start, CGPoint cp1, CGPoint cp2, CGPoint end, CGFloat T);
CGFloat LOT_CubicBezierInterpolate(CGPoint P0, CGPoint P1, CGPoint P2, CGPoint P3, CGFloat x);
CGFloat LOT_SolveCubic(CGFloat a, CGFloat b, CGFloat c, CGFloat d);
CGFloat LOT_SolveQuadratic(CGFloat a, CGFloat b, CGFloat c);
CGFloat LOT_Squared(CGFloat f);
CGFloat LOT_Cubed(CGFloat f);
CGFloat LOT_CubicRoot(CGFloat f);
CGFloat LOT_CubicLength(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2);
CGFloat LOT_CubicLengthWithPrecision(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2, CGFloat iterations);
#import "CGGeometry+LOTAdditions.h"
const CGSize CGSizeMax = {CGFLOAT_MAX, CGFLOAT_MAX};
//
// Core Graphics Geometry Additions
//
// CGRectIntegral returns a rectangle with the smallest integer values for its origin and size that contains the source rectangle.
// For a rect with .origin={5, 5.5}, .size=(10, 10), it will return .origin={5,5}, .size={10, 11};
// LOT_RectIntegral will return {5,5}, {10, 10}.
CGRect LOT_RectIntegral(CGRect rect) {
rect.origin = CGPointMake(rintf(rect.origin.x), rintf(rect.origin.y));
rect.size = CGSizeMake(ceilf(rect.size.width), ceil(rect.size.height));
return rect;
}
//
// Centering
// Returns a rectangle of the given size, centered at a point
CGRect LOT_RectCenteredAtPoint(CGPoint center, CGSize size, BOOL integral) {
CGRect result;
result.origin.x = center.x - 0.5f * size.width;
result.origin.y = center.y - 0.5f * size.height;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
// Returns the center point of a CGRect
CGPoint LOT_RectGetCenterPoint(CGRect rect) {
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}
//
// Insetting
// Inset the rectangle on a single edge
CGRect LOT_RectInsetLeft(CGRect rect, CGFloat inset) {
rect.origin.x += inset;
rect.size.width -= inset;
return rect;
}
CGRect LOT_RectInsetRight(CGRect rect, CGFloat inset) {
rect.size.width -= inset;
return rect;
}
CGRect LOT_RectInsetTop(CGRect rect, CGFloat inset) {
rect.origin.y += inset;
rect.size.height -= inset;
return rect;
}
CGRect LOT_RectInsetBottom(CGRect rect, CGFloat inset) {
rect.size.height -= inset;
return rect;
}
// Inset the rectangle on two edges
CGRect LOT_RectInsetHorizontal(CGRect rect, CGFloat leftInset, CGFloat rightInset) {
rect.origin.x += leftInset;
rect.size.width -= (leftInset + rightInset);
return rect;
}
CGRect LOT_RectInsetVertical(CGRect rect, CGFloat topInset, CGFloat bottomInset) {
rect.origin.y += topInset;
rect.size.height -= (topInset + bottomInset);
return rect;
}
// Inset the rectangle on all edges
CGRect LOT_RectInsetAll(CGRect rect, CGFloat leftInset, CGFloat rightInset, CGFloat topInset, CGFloat bottomInset) {
rect.origin.x += leftInset;
rect.origin.y += topInset;
rect.size.width -= (leftInset + rightInset);
rect.size.height -= (topInset + bottomInset);
return rect;
}
//
// Framing
// Returns a rectangle of size framed in the center of the given rectangle
CGRect LOT_RectFramedCenteredInRect(CGRect rect, CGSize size, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
// Returns a rectangle of size framed in the given rectangle and inset
CGRect LOT_RectFramedLeftInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + inset;
result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedRightInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width - size.width - inset;
result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedTopInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
result.origin.y = rect.origin.y + inset;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedBottomInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
result.origin.y = rect.origin.y + rect.size.height - size.height - inset;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedTopLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + insetWidth;
result.origin.y = rect.origin.y + insetHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedTopRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width - size.width - insetWidth;
result.origin.y = rect.origin.y + insetHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedBottomLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + insetWidth;
result.origin.y = rect.origin.y + rect.size.height - size.height - insetHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectFramedBottomRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width - size.width - insetWidth;
result.origin.y = rect.origin.y + rect.size.height - size.height - insetHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
// Returns a rectangle of size attached to the given rectangle
CGRect LOT_RectAttachedLeftToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x - size.width - margin;
result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedRightToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width + margin;
result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedTopToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
result.origin.y = rect.origin.y - size.height - margin;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedTopLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + marginWidth;
result.origin.y = rect.origin.y - size.height - marginHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedTopRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width - size.width - marginWidth;
result.origin.y = rect.origin.y - rect.size.height - marginHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedBottomToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
result.origin.y = rect.origin.y + rect.size.height + margin;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedBottomLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + marginWidth;
result.origin.y = rect.origin.y + rect.size.height + marginHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
CGRect LOT_RectAttachedBottomRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
CGRect result;
result.origin.x = rect.origin.x + rect.size.width - size.width - marginWidth;
result.origin.y = rect.origin.y + rect.size.height + marginHeight;
result.size = size;
if (integral) { result = LOT_RectIntegral(result); }
return result;
}
// Divides a rect into sections and returns the section at specified index
CGRect LOT_RectDividedSection(CGRect rect, NSInteger sections, NSInteger index, CGRectEdge fromEdge) {
if (sections == 0) {
return CGRectZero;
}
CGRect r = rect;
if (fromEdge == CGRectMaxXEdge || fromEdge == CGRectMinXEdge) {
r.size.width = rect.size.width / sections;
r.origin.x += r.size.width * index;
} else {
r.size.height = rect.size.height / sections;
r.origin.y += r.size.height * index;
}
return r;
}
CGRect LOT_RectAddRect(CGRect rect, CGRect other) {
return CGRectMake(rect.origin.x + other.origin.x, rect.origin.y + other.origin.y,
rect.size.width + other.size.width, rect.size.height + other.size.height);
}
CGRect LOT_RectAddPoint(CGRect rect, CGPoint point) {
return CGRectMake(rect.origin.x + point.x, rect.origin.y + point.y,
rect.size.width, rect.size.height);
}
CGRect LOT_RectAddSize(CGRect rect, CGSize size) {
return CGRectMake(rect.origin.x, rect.origin.y,
rect.size.width + size.width, rect.size.height + size.height);
}
CGRect LOT_RectBounded(CGRect rect) {
CGRect returnRect = rect;
returnRect.origin = CGPointZero;
return returnRect;
}
CGPoint LOT_PointAddedToPoint(CGPoint point1, CGPoint point2) {
CGPoint returnPoint = point1;
returnPoint.x += point2.x;
returnPoint.y += point2.y;
return returnPoint;
}
CGRect LOT_RectSetHeight(CGRect rect, CGFloat height) {
return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, height);
}
CGFloat LOT_DegreesToRadians(CGFloat degrees) {
return degrees * M_PI / 180;
}
CGFloat LOT_PointDistanceFromPoint(CGPoint point1, CGPoint point2) {
CGFloat xDist = (point2.x - point1.x);
CGFloat yDist = (point2.y - point1.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
return distance;
}
CGFloat LOT_RemapValue(CGFloat value, CGFloat low1, CGFloat high1, CGFloat low2, CGFloat high2 ) {
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
CGPoint LOT_PointByLerpingPoints(CGPoint point1, CGPoint point2, CGFloat value) {
CGFloat xDiff = point2.x - point1.x;
CGFloat yDiff = point2.y - point1.y;
CGPoint transposed = CGPointMake(fabs(xDiff), fabs(yDiff));
CGPoint returnPoint;
if (xDiff == 0 || yDiff == 0) {
returnPoint.x = xDiff == 0 ? point1.x : LOT_RemapValue(value, 0, 1, point1.x, point2.x);
returnPoint.y = yDiff == 0 ? point1.y : LOT_RemapValue(value, 0, 1, point1.y, point2.y);
} else {
CGFloat rx = transposed.x / transposed.y;
CGFloat yLerp = LOT_RemapValue(value, 0, 1, 0, transposed.y);
CGFloat xLerp = yLerp * rx;
CGPoint interpolatedPoint = CGPointMake(point2.x < point1.x ? xLerp * -1 : xLerp,
point2.y < point1.y ? yLerp * -1 : yLerp);
returnPoint = LOT_PointAddedToPoint(point1, interpolatedPoint);
}
return returnPoint;
}
CGPoint LOT_PointInLine(CGPoint A, CGPoint B, CGFloat T) {
CGPoint C;
C.x = A.x - ((A.x - B.x) * T);
C.y = A.y - ((A.y - B.y) * T);
return C;
}
CGFloat LOT_CubicBezierGetY(CGPoint cp1, CGPoint cp2, CGFloat T) {
// (1-x)^3 * y0 + 3*(1-x)^2 * x * y1 + 3*(1-x) * x^2 * y2 + x^3 * y3
return 3 * powf(1.f - T, 2.f) * T * cp1.y + 3.f * (1.f - T) * powf(T, 2.f) * cp2.y + powf(T, 3.f);
}
CGPoint LOT_PointInCubicCurve(CGPoint start, CGPoint cp1, CGPoint cp2, CGPoint end, CGFloat T) {
CGPoint A = LOT_PointInLine(start, cp1, T);
CGPoint B = LOT_PointInLine(cp1, cp2, T);
CGPoint C = LOT_PointInLine(cp2, end, T);
CGPoint D = LOT_PointInLine(A, B, T);
CGPoint E = LOT_PointInLine(B, C, T);
CGPoint F = LOT_PointInLine(D, E, T);
return F;
}
CGFloat LOT_SolveCubic(CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
if (a == 0) return LOT_SolveQuadratic(b, c, d);
if (d == 0) return 0;
b /= a;
c /= a;
d /= a;
CGFloat q = (3.0 * c - LOT_Squared(b)) / 9.0;
CGFloat r = (-27.0 * d + b * (9.0 * c - 2.0 * LOT_Squared(b))) / 54.0;
CGFloat disc = LOT_Cubed(q) + LOT_Squared(r);
CGFloat term1 = b / 3.0;
if (disc > 0) {
double s = r + sqrtf(disc);
s = (s < 0) ? - LOT_CubicRoot(-s) : LOT_CubicRoot(s);
double t = r - sqrtf(disc);
t = (t < 0) ? - LOT_CubicRoot(-t) : LOT_CubicRoot(t);
double result = -term1 + s + t;
if (result >= 0 && result <= 1) return result;
} else if (disc == 0) {
double r13 = (r < 0) ? - LOT_CubicRoot(-r) : LOT_CubicRoot(r);
double result = -term1 + 2.0 * r13;
if (result >= 0 && result <= 1) return result;
result = -(r13 + term1);
if (result >= 0 && result <= 1) return result;
} else {
q = -q;
double dum1 = q * q * q;
dum1 = acosf(r / sqrtf(dum1));
double r13 = 2.0 * sqrtf(q);
double result = -term1 + r13 * cos(dum1 / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0);
if (result >= 0 && result <= 1) return result;
}
return -1;
}
CGFloat LOT_SolveQuadratic(CGFloat a, CGFloat b, CGFloat c) {
CGFloat result = (-b + sqrtf(LOT_Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
result = (-b - sqrtf(LOT_Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
return -1;
}
CGFloat LOT_Squared(CGFloat f) { return f * f; }
CGFloat LOT_Cubed(CGFloat f) { return f * f * f; }
CGFloat LOT_CubicRoot(CGFloat f) { return powf(f, 1.0 / 3.0); }
CGFloat LOT_CubicBezierInterpolate(CGPoint P0, CGPoint P1, CGPoint P2, CGPoint P3, CGFloat x) {
CGFloat t;
if (x == P0.x) {
// Handle corner cases explicitly to prevent rounding errors
t = 0;
} else if (x == P3.x) {
t = 1;
} else {
// Calculate t
CGFloat a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x;
CGFloat b = 3 * P0.x - 6 * P1.x + 3 * P2.x;
CGFloat c = -3 * P0.x + 3 * P1.x;
CGFloat d = P0.x - x;
CGFloat tTemp = LOT_SolveCubic(a, b, c, d);
if (tTemp == -1) return -1;
t = tTemp;
}
// Calculate y from t
return LOT_Cubed(1 - t) * P0.y + 3 * t * LOT_Squared(1 - t) * P1.y + 3 * LOT_Squared(t) * (1 - t) * P2.y + LOT_Cubed(t) * P3.y;
}
CGFloat LOT_CubicLengthWithPrecision(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2, CGFloat iterations) {
CGFloat length = 0;
CGPoint previousPoint = fromPoint;
iterations = ceilf(iterations);
for (int i = 1; i <= iterations; ++i) {
float s = (float)i / iterations;
CGPoint p = LOT_PointInCubicCurve(fromPoint, controlPoint1, controlPoint2, toPoint, s);
length += LOT_PointDistanceFromPoint(previousPoint, p);
previousPoint = p;
}
return length;
}
CGFloat LOT_CubicLength(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2) {
return LOT_CubicLengthWithPrecision(fromPoint, toPoint, controlPoint1, controlPoint2, 20);
}
BOOL LOT_CGPointIsZero(CGPoint point) {
return CGPointEqualToPoint(point, CGPointZero);
}
//
// LOTBezierPath.h
// Lottie
//
// Created by brandon_withrow on 7/20/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPlatformCompat.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTBezierPath : NSObject
+ (instancetype)pathWithCGPath:(CGPathRef)path;
+ (instancetype)newPath;
- (void)LOT_moveToPoint:(CGPoint)point;
- (void)LOT_addLineToPoint:(CGPoint)point;
- (void)LOT_addCurveToPoint:(CGPoint)point
controlPoint1:(CGPoint)cp1
controlPoint2:(CGPoint)cp2;
- (void)LOT_closePath;
- (void)LOT_removeAllPoints;
- (void)LOT_appendPath:(LOTBezierPath *)bezierPath;
- (void)trimPathFromT:(CGFloat)fromT toT:(CGFloat)toT offset:(CGFloat)offset;
- (void)LOT_applyTransform:(CGAffineTransform)transform;
@property (nonatomic, assign) BOOL cacheLengths;
@property (nonatomic, readonly) CGFloat length;
@property (nonatomic, readonly) CGPathRef CGPath;
@property (nonatomic, readonly) CGPoint currentPoint;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) CGLineCap lineCapStyle;
@property (nonatomic) CGLineJoin lineJoinStyle;
@property (nonatomic) CGFloat miterLimit;
@property (nonatomic) CGFloat flatness;
@property (nonatomic) BOOL usesEvenOddFillRule;
@property (readonly, getter=isEmpty) BOOL empty;
@property (nonatomic, readonly) CGRect bounds;
@end
NS_ASSUME_NONNULL_END
//
// LOTBezierPath.m
// Lottie
//
// Created by brandon_withrow on 7/20/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTBezierPath.h"
#import "CGGeometry+LOTAdditions.h"
typedef struct LOT_Subpath LOT_Subpath;
typedef void(^LOTBezierPathEnumerationHandler)(const CGPathElement *element);
struct LOT_Subpath {
CGPathElementType type;
CGFloat length;
CGPoint endPoint;
CGPoint controlPoint1;
CGPoint controlPoint2;
LOT_Subpath *nextSubpath;
};
@interface LOTBezierPath ()
@property (nonatomic, readonly) LOT_Subpath *headSubpath;
@end
@implementation LOTBezierPath {
LOT_Subpath *headSubpath_;
LOT_Subpath *tailSubpath_;
CGPoint subpathStartPoint_;
CGFloat *_lineDashPattern;
NSInteger _lineDashCount;
CGFloat _lineDashPhase;
CGMutablePathRef _path;
}
// MARK - Lifecycle
+ (instancetype)pathWithCGPath:(CGPathRef)path {
LOTBezierPath *returnPath = [LOTBezierPath newPath];
[returnPath setWithCGPath:path];
return returnPath;
}
+ (instancetype)newPath {
return [[LOTBezierPath alloc] init];
}
- (instancetype)init
{
self = [super init];
if (self) {
_length = 0;
headSubpath_ = NULL;
tailSubpath_ = NULL;
_path = CGPathCreateMutable();
_lineWidth = 1;
_lineCapStyle = kCGLineCapButt;
_lineJoinStyle = kCGLineJoinMiter;
_miterLimit = 10;
_flatness = 0.6;
_usesEvenOddFillRule = NO;
_lineDashPattern = NULL;
_lineDashCount = 0;
_lineDashPhase = 0;
_cacheLengths = NO;
}
return self;
}
- (void)dealloc {
[self removeAllSubpaths];
if (_path) CGPathRelease(_path);
}
- (id)copyWithZone:(NSZone *)zone {
LOTBezierPath *copy = [[self class] new];
copy.cacheLengths = self.cacheLengths;
copy.lineWidth = self.lineWidth;
copy.lineCapStyle = self.lineCapStyle;
copy.lineJoinStyle = self.lineJoinStyle;
copy.miterLimit = self.miterLimit;
copy.flatness = self.flatness;
copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
[copy LOT_appendPath:self];
return copy;
}
// MARK - Subpaths List
- (void)removeAllSubpaths {
LOT_Subpath *node = headSubpath_;
while (node) {
LOT_Subpath *nextNode = node->nextSubpath;
node->nextSubpath = NULL;
free(node);
node = nextNode;
}
headSubpath_ = NULL;
tailSubpath_ = NULL;
}
- (void)addSubpathWithType:(CGPathElementType)type
length:(CGFloat)length
endPoint:(CGPoint)endPoint
controlPoint1:(CGPoint)controlPoint1
controlPoint1:(CGPoint)controlPoint2 {
LOT_Subpath *subPath = (LOT_Subpath *)malloc(sizeof(LOT_Subpath));
subPath->type = type;
subPath->length = length;
subPath->endPoint = endPoint;
subPath->controlPoint1 = controlPoint1;
subPath->controlPoint2 = controlPoint2;
subPath->nextSubpath = NULL;
if (tailSubpath_ == NULL) {
headSubpath_ = subPath;
tailSubpath_ = subPath;
} else {
tailSubpath_->nextSubpath = subPath;
tailSubpath_ = subPath;
}
}
// MARK Getters Setters
- (CGPoint)currentPoint {
CGPoint previousPoint = tailSubpath_ ? tailSubpath_->endPoint : CGPointZero;
return previousPoint;
}
- (CGPathRef)CGPath {
return _path;
}
- (LOT_Subpath *)headSubpath {
return headSubpath_;
}
// MARK - External
- (void)LOT_moveToPoint:(CGPoint)point {
subpathStartPoint_ = point;
[self addSubpathWithType:kCGPathElementMoveToPoint length:0 endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
CGPathMoveToPoint(_path, NULL, point.x, point.y);
}
- (void)LOT_addLineToPoint:(CGPoint)point {
CGFloat length = 0;
if (_cacheLengths) {
length = LOT_PointDistanceFromPoint(self.currentPoint, point);
_length = _length + length;
}
[self addSubpathWithType:kCGPathElementAddLineToPoint length:length endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
CGPathAddLineToPoint(_path, NULL, point.x, point.y);
}
- (void)LOT_addCurveToPoint:(CGPoint)point
controlPoint1:(CGPoint)cp1
controlPoint2:(CGPoint)cp2 {
CGFloat length = 0;
if (_cacheLengths) {
length = LOT_CubicLengthWithPrecision(self.currentPoint, point, cp1, cp2, 5);
_length = _length + length;
}
[self addSubpathWithType:kCGPathElementAddCurveToPoint length:length endPoint:point controlPoint1:cp1 controlPoint1:cp2];
CGPathAddCurveToPoint(_path, NULL, cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y);
}
- (void)LOT_closePath {
CGFloat length = 0;
if (_cacheLengths) {
length = LOT_PointDistanceFromPoint(self.currentPoint, subpathStartPoint_);
_length = _length + length;
}
[self addSubpathWithType:kCGPathElementCloseSubpath length:length endPoint:subpathStartPoint_ controlPoint1:CGPointZero controlPoint1:CGPointZero];
CGPathCloseSubpath(_path);
}
- (void)_clearPathData {
_length = 0;
subpathStartPoint_ = CGPointZero;
CGPathRelease(_path);
_path = CGPathCreateMutable();
}
- (void)LOT_removeAllPoints {
[self removeAllSubpaths];
[self _clearPathData];
}
- (BOOL)containsPoint:(CGPoint)point {
return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
}
- (BOOL)isEmpty {
return CGPathIsEmpty(_path);
}
- (CGRect)bounds {
return CGPathGetBoundingBox(_path);
}
- (void)LOT_applyTransform:(CGAffineTransform)transform {
CGMutablePathRef mutablePath = CGPathCreateMutable();
CGPathAddPath(mutablePath, &transform, _path);
CGPathRelease(_path);
_path = mutablePath;
}
- (void)LOT_appendPath:(LOTBezierPath *)bezierPath {
CGPathAddPath(_path, NULL, bezierPath.CGPath);
LOT_Subpath *nextSubpath = bezierPath.headSubpath;
while (nextSubpath) {
CGFloat length = 0;
if (self.cacheLengths) {
if (bezierPath.cacheLengths) {
length = nextSubpath->length;
} else {
// No previous length data, measure.
if (nextSubpath->type == kCGPathElementAddLineToPoint) {
length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
} else if (nextSubpath->type == kCGPathElementAddCurveToPoint) {
length = LOT_CubicLengthWithPrecision(self.currentPoint, nextSubpath->endPoint, nextSubpath->controlPoint1, nextSubpath->controlPoint2, 5);
} else if (nextSubpath->type == kCGPathElementCloseSubpath) {
length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
}
}
}
_length = _length + length;
[self addSubpathWithType:nextSubpath->type
length:length
endPoint:nextSubpath->endPoint
controlPoint1:nextSubpath->controlPoint1
controlPoint1:nextSubpath->controlPoint2];
nextSubpath = nextSubpath->nextSubpath;
}
}
- (void)trimPathFromT:(CGFloat)fromT toT:(CGFloat)toT offset:(CGFloat)offset {
fromT = MIN(MAX(0, fromT), 1);
toT = MIN(MAX(0, toT), 1);
if (fromT > toT) {
CGFloat to = fromT;
fromT = toT;
toT = to;
}
offset = offset - floor(offset);
CGFloat fromLength = fromT + offset;
CGFloat toLength = toT + offset;
if (toT - fromT == 1) {
// Do Nothing, Full Path returned.
return;
}
if (fromLength == toLength) {
// Empty Path
[self LOT_removeAllPoints];
return;
}
if (fromLength >= 1) {
fromLength = fromLength - floor(fromLength);
}
if (toLength > 1) {
toLength = toLength - floor(toLength);
}
if (fromLength == 0 &&
toLength == 1) {
// Do Nothing. Full Path returned.
return;
}
if (fromLength == toLength) {
// Empty Path
[self LOT_removeAllPoints];
return;
}
CGFloat totalLength = _length;
[self _clearPathData];
LOT_Subpath *subpath = headSubpath_;
headSubpath_ = NULL;
tailSubpath_ = NULL;
fromLength = fromLength * totalLength;
toLength = toLength * totalLength;
CGFloat currentStartLength = fromLength < toLength ? fromLength : 0;
CGFloat currentEndLength = toLength;
CGFloat subpathBeginningLength = 0;
CGPoint currentPoint = CGPointZero;
while (subpath) {
CGFloat pathLength = subpath->length;
if (!_cacheLengths) {
if (subpath->type == kCGPathElementAddLineToPoint) {
pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
} else if (subpath->type == kCGPathElementAddCurveToPoint) {
pathLength = LOT_CubicLengthWithPrecision(currentPoint, subpath->endPoint, subpath->controlPoint1, subpath->controlPoint2, 5);
} else if (subpath->type == kCGPathElementCloseSubpath) {
pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
}
}
CGFloat subpathEndLength = subpathBeginningLength + pathLength;
if (subpath->type != kCGPathElementMoveToPoint &&
subpathEndLength > currentStartLength) {
// The end of this path overlaps the current drawing region
// x x x x
// ---------------ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
// Start |currentStartLength currentEndLength| End
CGFloat currentSpanStartT = LOT_RemapValue(currentStartLength, subpathBeginningLength, subpathEndLength, 0, 1);
CGFloat currentSpanEndT = LOT_RemapValue(currentEndLength, subpathBeginningLength, subpathEndLength, 0, 1);
// At this point currentSpan start and end T can be less than 0 or greater than 1
if (subpath->type == kCGPathElementAddLineToPoint) {
if (currentSpanStartT >= 0) {
// The current drawable span either starts with this subpath or along this subpath.
// If this is the middle of a segment then currentSpanStartT would be less than 0
if (currentSpanStartT > 0) {
currentPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanStartT);
}
[self LOT_moveToPoint:currentPoint];
// Now we are ready to draw a line
}
CGPoint toPoint = subpath->endPoint;
if (currentSpanEndT < 1) {
// The end of the span is inside of the current subpath. Find it.
toPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanEndT);
}
[self LOT_addLineToPoint:toPoint];
currentPoint = toPoint;
} else if (subpath->type == kCGPathElementAddCurveToPoint) {
CGPoint cp1, cp2, end;
cp1 = subpath->controlPoint1;
cp2 = subpath->controlPoint2;
end = subpath->endPoint;
if (currentSpanStartT >= 0) {
// The current drawable span either starts with this subpath or along this subpath.
// If this is the middle of a segment then currentSpanStartT would be less than 0
// Beginning of a segment Move start point and calculate cp1 and 2 is necessary
if (currentSpanStartT > 0) {
CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanStartT);
CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanStartT);
CGPoint C = LOT_PointInLine(cp2, end, currentSpanStartT);
CGPoint D = LOT_PointInLine(A, B, currentSpanStartT);
CGPoint E = LOT_PointInLine(B, C, currentSpanStartT);
CGPoint F = LOT_PointInLine(D, E, currentSpanStartT);
currentPoint = F;
cp1 = E;
cp2 = C;
currentSpanEndT = LOT_RemapValue(currentSpanEndT, currentSpanStartT, 1, 0, 1);
}
[self LOT_moveToPoint:currentPoint];
}
if (currentSpanEndT < 1) {
CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanEndT);
CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanEndT);
CGPoint C = LOT_PointInLine(cp2, end, currentSpanEndT);
CGPoint D = LOT_PointInLine(A, B, currentSpanEndT);
CGPoint E = LOT_PointInLine(B, C, currentSpanEndT);
CGPoint F = LOT_PointInLine(D, E, currentSpanEndT);
cp1 = A;
cp2 = D;
end = F;
}
[self LOT_addCurveToPoint:end controlPoint1:cp1 controlPoint2:cp2];
}
if (currentSpanEndT <= 1) {
// We have possibly reached the end.
// Current From and To will possibly need to be reset.
if (fromLength < toLength) {
while (subpath) {
LOT_Subpath *nextNode = subpath->nextSubpath;
subpath->nextSubpath = NULL;
free(subpath);
subpath = nextNode;
}
break;
} else {
currentStartLength = fromLength;
currentEndLength = totalLength;
if (fromLength < (subpathBeginningLength + pathLength) &&
fromLength > subpathBeginningLength &&
currentSpanEndT < 1) {
// Loop over this subpath one more time.
// In this case the path start and end trim fall within this subpath bounds
continue;
}
}
}
}
currentPoint = subpath->endPoint;
subpathBeginningLength = subpathEndLength;
LOT_Subpath *nextNode = subpath->nextSubpath;
subpath->nextSubpath = NULL;
free(subpath);
subpath = nextNode;
}
}
#pragma mark - From CGPath
- (void)setWithCGPath:(CGPathRef)path {
[self lot_enumeratePath:path elementsUsingBlock:^(const CGPathElement *element) {
switch (element->type) {
case kCGPathElementMoveToPoint: {
CGPoint point = element ->points[0];
[self LOT_moveToPoint:point];
break;
}
case kCGPathElementAddLineToPoint: {
CGPoint point = element ->points[0];
[self LOT_addLineToPoint:point];
break;
}
case kCGPathElementAddQuadCurveToPoint: {
break;
}
case kCGPathElementAddCurveToPoint: {
CGPoint point1 = element->points[0];
CGPoint point2 = element->points[1];
CGPoint point3 = element->points[2];
[self LOT_addCurveToPoint:point3 controlPoint1:point1 controlPoint2:point2];
break;
}
case kCGPathElementCloseSubpath: {
[self LOT_closePath];
break;
}
}
}];
}
- (void)lot_enumeratePath:(CGPathRef)cgPath elementsUsingBlock:(LOTBezierPathEnumerationHandler)handler {
void CGPathEnumerationCallback(void *info, const CGPathElement *element);
CGPathApply(cgPath, (__bridge void * _Nullable)(handler), CGPathEnumerationCallback);
}
@end
void CGPathEnumerationCallback(void *info, const CGPathElement *element)
{
LOTBezierPathEnumerationHandler handler = (__bridge LOTBezierPathEnumerationHandler)(info);
if (handler) {
handler(element);
}
}
//
// LOTHelpers.h
// Lottie
//
// Created by Brandon Withrow on 7/28/16.
// Copyright © 2016 Brandon Withrow. All rights reserved.
//
#ifndef LOTHelpers_h
#define LOTHelpers_h
#import "UIColor+Expanded.h"
#import "CGGeometry+LOTAdditions.h"
#import "LOTBezierPath.h"
#define ENABLE_DEBUG_LOGGING NO
#define ENABLE_DEBUG_SHAPES NO
#endif /* LOTHelpers_h */
// TODO Feature Phase
/*
- Trim Path individually
- Image Cache Support
- Skew transform
*/
//
// LOTAnimationView
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <Foundation/Foundation.h>
@interface LOTRadialGradientLayer : CALayer
@property CGPoint startPoint;
@property CGPoint endPoint;
@property (nonatomic, copy) NSArray *colors;
@property (nonatomic, copy) NSArray<NSNumber *> *locations;
@property (nonatomic, assign) BOOL isRadial;
@end
//
// LOTAnimationView
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTRadialGradientLayer.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTRadialGradientLayer
@dynamic isRadial;
@dynamic startPoint;
@dynamic endPoint;
@dynamic colors;
@dynamic locations;
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:@"startPoint"] ||
[key isEqualToString:@"endPoint"] ||
[key isEqualToString:@"colors"] ||
[key isEqualToString:@"locations"] ||
[key isEqualToString:@"isRadial"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (id)actionForKey:(NSString *)key {
id action = self.actions[key];
if (action) {
if (action == [NSNull null]) {
return nil;
}
return action;
}
if ([key isEqualToString:@"startPoint"] ||
[key isEqualToString:@"endPoint"] ||
[key isEqualToString:@"colors"] ||
[key isEqualToString:@"locations"] ||
[key isEqualToString:@"isRadial"]) {
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
theAnimation.fromValue = [self.presentationLayer valueForKey:key];
return theAnimation;
}
return [super actionForKey:key];
}
- (void)drawInContext:(CGContextRef)ctx {
if (self.colors.count == 0) {
return;
}
NSInteger numberOfLocations = self.locations.count;
CGColorRef colorRef = (__bridge CGColorRef)[self.colors objectAtIndex:0];
NSInteger numberOfComponents = CGColorGetNumberOfComponents(colorRef);
CGColorSpaceRef colorSpace = CGColorGetColorSpace(colorRef);
CGPoint origin = self.startPoint;
CGFloat radius = LOT_PointDistanceFromPoint(self.startPoint, self.endPoint);
CGFloat gradientLocations[numberOfLocations];
CGFloat gradientComponents[numberOfLocations * numberOfComponents];
for (NSInteger locationIndex = 0; locationIndex < numberOfLocations; locationIndex++) {
gradientLocations[locationIndex] = [self.locations[locationIndex] floatValue];
const CGFloat *colorComponents = CGColorGetComponents((__bridge CGColorRef)self.colors[locationIndex]);
for (NSInteger componentIndex = 0; componentIndex < numberOfComponents; componentIndex++) {
gradientComponents[numberOfComponents * locationIndex + componentIndex] = colorComponents[componentIndex];
}
}
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientComponents, gradientLocations, numberOfLocations);
if (self.isRadial) {
CGContextDrawRadialGradient(ctx, gradient, origin, 0, origin, radius, kCGGradientDrawsAfterEndLocation);
} else {
CGContextDrawLinearGradient(ctx, gradient, self.startPoint, self.endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
}
CGGradientRelease(gradient);
}
@end
#import "LOTPlatformCompat.h"
// From http://github.com/ars/uicolor-utilities
#define CLAMP(val,min,max) MIN(MAX(val,min),max)
@interface UIColor (UIColor_Expanded)
@property (nonatomic, readonly) CGColorSpaceModel colorSpaceModel;
@property (nonatomic, readonly) BOOL canProvideRGBComponents;
@property (nonatomic, readonly) CGFloat red; // Only valid if canProvideRGBComponents is YES
@property (nonatomic, readonly) CGFloat green; // Only valid if canProvideRGBComponents is YES
@property (nonatomic, readonly) CGFloat blue; // Only valid if canProvideRGBComponents is YES
@property (nonatomic, readonly) CGFloat white; // Only valid if colorSpaceModel == kCGColorSpaceModelMonochrome
@property (nonatomic, readonly) CGFloat alpha;
@property (nonatomic, readonly) UInt32 rgbHex;
- (NSString *)LOT_colorSpaceString;
- (NSArray *)LOT_arrayFromRGBAComponents;
- (BOOL)LOT_red:(CGFloat *)r green:(CGFloat *)g blue:(CGFloat *)b alpha:(CGFloat *)a;
- (UIColor *)LOT_colorByLuminanceMapping;
- (UIColor *)LOT_colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *) LOT_colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *) LOT_colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *) LOT_colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *)LOT_colorByMultiplyingBy:(CGFloat)f;
- (UIColor *) LOT_colorByAdding:(CGFloat)f;
- (UIColor *) LOT_colorByLighteningTo:(CGFloat)f;
- (UIColor *) LOT_colorByDarkeningTo:(CGFloat)f;
- (UIColor *)LOT_colorByMultiplyingByColor:(UIColor *)color;
- (UIColor *) LOT_colorByAddingColor:(UIColor *)color;
- (UIColor *) LOT_colorByLighteningToColor:(UIColor *)color;
- (UIColor *) LOT_colorByDarkeningToColor:(UIColor *)color;
- (NSString *)LOT_stringFromColor;
- (NSString *)LOT_hexStringValue;
+ (UIColor *)LOT_randomColor;
+ (UIColor *)LOT_colorWithString:(NSString *)stringToConvert;
+ (UIColor *)LOT_colorWithRGBHex:(UInt32)hex;
+ (UIColor *)LOT_colorWithHexString:(NSString *)stringToConvert;
+ (UIColor *)LOT_colorWithName:(NSString *)cssColorName;
+ (UIColor *)LOT_colorByLerpingFromColor:(UIColor *)fromColor toColor:(UIColor *)toColor amount:(CGFloat)amount;
@end
#import "UIColor+Expanded.h"
/*
Thanks to Poltras, Millenomi, Eridius, Nownot, WhatAHam, jberry,
and everyone else who helped out but whose name is inadvertently omitted
*/
/*
Current outstanding request list:
- PolarBearFarm - color descriptions ([UIColor warmGrayWithHintOfBlueTouchOfRedAndSplashOfYellowColor])
- Crayola color set
- Eridius - UIColor needs a method that takes 2 colors and gives a third complementary one
- Consider UIMutableColor that can be adjusted (brighter, cooler, warmer, thicker-alpha, etc)
*/
/*
FOR REFERENCE: Color Space Models: enum CGColorSpaceModel {
kCGColorSpaceModelUnknown = -1,
kCGColorSpaceModelMonochrome,
kCGColorSpaceModelRGB,
kCGColorSpaceModelCMYK,
kCGColorSpaceModelLab,
kCGColorSpaceModelDeviceN,
kCGColorSpaceModelIndexed,
kCGColorSpaceModelPattern
};
*/
// Static cache of looked up color names. Used with +LOT_colorWithName:
static NSMutableDictionary *colorNameCache = nil;
@interface UIColor (UIColor_Expanded_Support)
+ (UIColor *)searchForColorByName:(NSString *)cssColorName;
@end
#pragma mark -
@implementation UIColor (UIColor_Expanded)
- (CGColorSpaceModel)colorSpaceModel {
return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
}
- (NSString *)LOT_colorSpaceString {
switch (self.colorSpaceModel) {
case kCGColorSpaceModelUnknown:
return @"kCGColorSpaceModelUnknown";
case kCGColorSpaceModelMonochrome:
return @"kCGColorSpaceModelMonochrome";
case kCGColorSpaceModelRGB:
return @"kCGColorSpaceModelRGB";
case kCGColorSpaceModelCMYK:
return @"kCGColorSpaceModelCMYK";
case kCGColorSpaceModelLab:
return @"kCGColorSpaceModelLab";
case kCGColorSpaceModelDeviceN:
return @"kCGColorSpaceModelDeviceN";
case kCGColorSpaceModelIndexed:
return @"kCGColorSpaceModelIndexed";
case kCGColorSpaceModelPattern:
return @"kCGColorSpaceModelPattern";
default:
return @"Not a valid color space";
}
}
- (BOOL)canProvideRGBComponents {
switch (self.colorSpaceModel) {
case kCGColorSpaceModelRGB:
case kCGColorSpaceModelMonochrome:
return YES;
default:
return NO;
}
}
- (NSArray *)LOT_arrayFromRGBAComponents {
NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -LOT_arrayFromRGBAComponents");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:r],
[NSNumber numberWithFloat:g],
[NSNumber numberWithFloat:b],
[NSNumber numberWithFloat:a],
nil];
}
- (BOOL)LOT_red:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
const CGFloat *components = CGColorGetComponents(self.CGColor);
CGFloat r,g,b,a;
switch (self.colorSpaceModel) {
case kCGColorSpaceModelMonochrome:
r = g = b = components[0];
a = components[1];
break;
case kCGColorSpaceModelRGB:
r = components[0];
g = components[1];
b = components[2];
a = components[3];
break;
default: // We don't know how to handle this model
return NO;
}
if (red) *red = r;
if (green) *green = g;
if (blue) *blue = b;
if (alpha) *alpha = a;
return YES;
}
- (CGFloat)red {
NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -red");
const CGFloat *c = CGColorGetComponents(self.CGColor);
return c[0];
}
- (CGFloat)green {
NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -green");
const CGFloat *c = CGColorGetComponents(self.CGColor);
if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0];
return c[1];
}
- (CGFloat)blue {
NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -blue");
const CGFloat *c = CGColorGetComponents(self.CGColor);
if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0];
return c[2];
}
- (CGFloat)white {
NSAssert(self.colorSpaceModel == kCGColorSpaceModelMonochrome, @"Must be a Monochrome color to use -white");
const CGFloat *c = CGColorGetComponents(self.CGColor);
return c[0];
}
- (CGFloat)alpha {
return CGColorGetAlpha(self.CGColor);
}
- (UInt32)rgbHex {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use rgbHex");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return 0;
r = MIN(MAX(self.red, 0.0f), 1.0f);
g = MIN(MAX(self.green, 0.0f), 1.0f);
b = MIN(MAX(self.blue, 0.0f), 1.0f);
return (((int)roundf(r * 255)) << 16)
| (((int)roundf(g * 255)) << 8)
| (((int)roundf(b * 255)));
}
#pragma mark Arithmetic operations
- (UIColor *)LOT_colorByLuminanceMapping {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
// http://en.wikipedia.org/wiki/Luma_(video)
// Y = 0.2126 R + 0.7152 G + 0.0722 B
return [UIColor colorWithWhite:r*0.2126f + g*0.7152f + b*0.0722f
alpha:a];
}
- (UIColor *)LOT_colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [UIColor colorWithRed:MAX(0.0, MIN(1.0, r * red))
green:MAX(0.0, MIN(1.0, g * green))
blue:MAX(0.0, MIN(1.0, b * blue))
alpha:MAX(0.0, MIN(1.0, a * alpha))];
}
- (UIColor *)LOT_colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [UIColor colorWithRed:MAX(0.0, MIN(1.0, r + red))
green:MAX(0.0, MIN(1.0, g + green))
blue:MAX(0.0, MIN(1.0, b + blue))
alpha:MAX(0.0, MIN(1.0, a + alpha))];
}
- (UIColor *)LOT_colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [UIColor colorWithRed:MAX(r, red)
green:MAX(g, green)
blue:MAX(b, blue)
alpha:MAX(a, alpha)];
}
- (UIColor *)LOT_colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [UIColor colorWithRed:MIN(r, red)
green:MIN(g, green)
blue:MIN(b, blue)
alpha:MIN(a, alpha)];
}
- (UIColor *)LOT_colorByMultiplyingBy:(CGFloat)f {
return [self LOT_colorByMultiplyingByRed:f green:f blue:f alpha:1.0f];
}
- (UIColor *)LOT_colorByAdding:(CGFloat)f {
return [self LOT_colorByMultiplyingByRed:f green:f blue:f alpha:0.0f];
}
- (UIColor *)LOT_colorByLighteningTo:(CGFloat)f {
return [self LOT_colorByLighteningToRed:f green:f blue:f alpha:0.0f];
}
- (UIColor *)LOT_colorByDarkeningTo:(CGFloat)f {
return [self LOT_colorByDarkeningToRed:f green:f blue:f alpha:1.0f];
}
- (UIColor *)LOT_colorByMultiplyingByColor:(UIColor *)color {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [self LOT_colorByMultiplyingByRed:r green:g blue:b alpha:1.0f];
}
- (UIColor *)LOT_colorByAddingColor:(UIColor *)color {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [self LOT_colorByAddingRed:r green:g blue:b alpha:0.0f];
}
- (UIColor *)LOT_colorByLighteningToColor:(UIColor *)color {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [self LOT_colorByLighteningToRed:r green:g blue:b alpha:0.0f];
}
- (UIColor *)LOT_colorByDarkeningToColor:(UIColor *)color {
NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmetic operations");
CGFloat r,g,b,a;
if (![self LOT_red:&r green:&g blue:&b alpha:&a]) return nil;
return [self LOT_colorByDarkeningToRed:r green:g blue:b alpha:1.0f];
}
#pragma mark String utilities
- (NSString *)LOT_stringFromColor {
NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -LOT_stringFromColor");
NSString *result;
switch (self.colorSpaceModel) {
case kCGColorSpaceModelRGB:
result = [NSString stringWithFormat:@"{%0.3f, %0.3f, %0.3f, %0.3f}", self.red, self.green, self.blue, self.alpha];
break;
case kCGColorSpaceModelMonochrome:
result = [NSString stringWithFormat:@"{%0.3f, %0.3f}", self.white, self.alpha];
break;
default:
result = nil;
}
return result;
}
- (NSString *)LOT_hexStringValue {
return [NSString stringWithFormat:@"%0.6X", (unsigned int)self.rgbHex];
}
+ (UIColor *)LOT_colorWithString:(NSString *)stringToConvert {
NSScanner *scanner = [NSScanner scannerWithString:stringToConvert];
if (![scanner scanString:@"{" intoString:NULL]) return nil;
const NSUInteger kMaxComponents = 4;
float c[kMaxComponents];
NSUInteger i = 0;
if (![scanner scanFloat:&c[i++]]) return nil;
while (1) {
if ([scanner scanString:@"}" intoString:NULL]) break;
if (i >= kMaxComponents) return nil;
if ([scanner scanString:@"," intoString:NULL]) {
if (![scanner scanFloat:&c[i++]]) return nil;
} else {
// either we're at the end of there's an unexpected character here
// both cases are error conditions
return nil;
}
}
if (![scanner isAtEnd]) return nil;
UIColor *color;
switch (i) {
case 2: // monochrome
color = [UIColor colorWithWhite:c[0] alpha:c[1]];
break;
case 4: // RGB
color = [UIColor colorWithRed:c[0] green:c[1] blue:c[2] alpha:c[3]];
break;
default:
color = nil;
}
return color;
}
#pragma mark Class methods
+ (UIColor *)LOT_randomColor {
return [UIColor colorWithRed:(CGFloat)random() / (CGFloat)RAND_MAX
green:(CGFloat)random() / (CGFloat)RAND_MAX
blue:(CGFloat)random() / (CGFloat)RAND_MAX
alpha:1.0f];
}
+ (UIColor *)LOT_colorWithRGBHex:(UInt32)hex {
int r = (hex >> 16) & 0xFF;
int g = (hex >> 8) & 0xFF;
int b = (hex) & 0xFF;
return [UIColor colorWithRed:r / 255.0f
green:g / 255.0f
blue:b / 255.0f
alpha:1.0f];
}
// Returns a UIColor by scanning the string for a hex number and passing that to +[UIColor LOT_colorWithRGBHex:]
// Skips any leading whitespace and ignores any trailing characters
+ (UIColor *)LOT_colorWithHexString:(NSString *)stringToConvert {
NSString *strippedString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
NSScanner *scanner = [NSScanner scannerWithString:strippedString];
unsigned hexNum;
if (![scanner scanHexInt:&hexNum]) return nil;
return [UIColor LOT_colorWithRGBHex:hexNum];
}
// Lookup a color using css 3/svg color name
+ (UIColor *)LOT_colorWithName:(NSString *)cssColorName {
UIColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colorNameCache = [[NSMutableDictionary alloc] init];
});
@synchronized(colorNameCache) {
// Look for the color in the cache
color = [colorNameCache objectForKey:cssColorName];
if ((id)color == [NSNull null]) {
// If it wasn't there previously, it's still not there now
color = nil;
} else if (!color) {
// Color not in cache, so search for it now
color = [self searchForColorByName:cssColorName];
// Set the value in cache, storing NSNull on failure
[colorNameCache setObject:(color ?: (id)[NSNull null])
forKey:cssColorName];
}
}
return color;
}
+ (UIColor *)LOT_colorByLerpingFromColor:(UIColor *)fromColor toColor:(UIColor *)toColor amount:(CGFloat)amount {
NSAssert((toColor != nil && fromColor != nil), @"Passing Nil Color");
amount = CLAMP(amount, 0.f, 1.f);
const CGFloat *fromComponents = CGColorGetComponents(fromColor.CGColor);
const CGFloat *toComponents = CGColorGetComponents(toColor.CGColor);
float r = fromComponents[0] + ((toComponents[0] - fromComponents[0]) * amount);
float g = fromComponents[1] + ((toComponents[1] - fromComponents[1]) * amount);
float b = fromComponents[2] + ((toComponents[2] - fromComponents[2]) * amount);
float a = fromComponents[3] + ((toComponents[3] - fromComponents[3]) * amount);
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
@end
#pragma mark -
@implementation UIColor (UIColor_Expanded_Support)
/*
* Database of color names and hex rgb values, derived
* from the css 3 color spec:
* http://www.w3.org/TR/css3-color/
*
* We think this is a very compact way of storing
* this information, and relatively cheap to lookup.
*
* Note that we search for color names starting with ','
* and terminated by '#', so that we don't get false matches.
* For this reason, the database begins with ','.
*/
static const char *colorNameDB = ","
"aliceblue#f0f8ff,antiquewhite#faebd7,aqua#00ffff,aquamarine#7fffd4,azure#f0ffff,"
"beige#f5f5dc,bisque#ffe4c4,black#000000,blanchedalmond#ffebcd,blue#0000ff,"
"blueviolet#8a2be2,brown#a52a2a,burlywood#deb887,cadetblue#5f9ea0,chartreuse#7fff00,"
"chocolate#d2691e,coral#ff7f50,cornflowerblue#6495ed,cornsilk#fff8dc,crimson#dc143c,"
"cyan#00ffff,darkblue#00008b,darkcyan#008b8b,darkgoldenrod#b8860b,darkgray#a9a9a9,"
"darkgreen#006400,darkgrey#a9a9a9,darkkhaki#bdb76b,darkmagenta#8b008b,"
"darkolivegreen#556b2f,darkorange#ff8c00,darkorchid#9932cc,darkred#8b0000,"
"darksalmon#e9967a,darkseagreen#8fbc8f,darkslateblue#483d8b,darkslategray#2f4f4f,"
"darkslategrey#2f4f4f,darkturquoise#00ced1,darkviolet#9400d3,deeppink#ff1493,"
"deepskyblue#00bfff,dimgray#696969,dimgrey#696969,dodgerblue#1e90ff,"
"firebrick#b22222,floralwhite#fffaf0,forestgreen#228b22,fuchsia#ff00ff,"
"gainsboro#dcdcdc,ghostwhite#f8f8ff,gold#ffd700,goldenrod#daa520,gray#808080,"
"green#008000,greenyellow#adff2f,grey#808080,honeydew#f0fff0,hotpink#ff69b4,"
"indianred#cd5c5c,indigo#4b0082,ivory#fffff0,khaki#f0e68c,lavender#e6e6fa,"
"lavenderblush#fff0f5,lawngreen#7cfc00,lemonchiffon#fffacd,lightblue#add8e6,"
"lightcoral#f08080,lightcyan#e0ffff,lightgoldenrodyellow#fafad2,lightgray#d3d3d3,"
"lightgreen#90ee90,lightgrey#d3d3d3,lightpink#ffb6c1,lightsalmon#ffa07a,"
"lightseagreen#20b2aa,lightskyblue#87cefa,lightslategray#778899,"
"lightslategrey#778899,lightsteelblue#b0c4de,lightyellow#ffffe0,lime#00ff00,"
"limegreen#32cd32,linen#faf0e6,magenta#ff00ff,maroon#800000,mediumaquamarine#66cdaa,"
"mediumblue#0000cd,mediumorchid#ba55d3,mediumpurple#9370db,mediumseagreen#3cb371,"
"mediumslateblue#7b68ee,mediumspringgreen#00fa9a,mediumturquoise#48d1cc,"
"mediumvioletred#c71585,midnightblue#191970,mintcream#f5fffa,mistyrose#ffe4e1,"
"moccasin#ffe4b5,navajowhite#ffdead,navy#000080,oldlace#fdf5e6,olive#808000,"
"olivedrab#6b8e23,orange#ffa500,orangered#ff4500,orchid#da70d6,palegoldenrod#eee8aa,"
"palegreen#98fb98,paleturquoise#afeeee,palevioletred#db7093,papayawhip#ffefd5,"
"peachpuff#ffdab9,peru#cd853f,pink#ffc0cb,plum#dda0dd,powderblue#b0e0e6,"
"purple#800080,red#ff0000,rosybrown#bc8f8f,royalblue#4169e1,saddlebrown#8b4513,"
"salmon#fa8072,sandybrown#f4a460,seagreen#2e8b57,seashell#fff5ee,sienna#a0522d,"
"silver#c0c0c0,skyblue#87ceeb,slateblue#6a5acd,slategray#708090,slategrey#708090,"
"snow#fffafa,springgreen#00ff7f,steelblue#4682b4,tan#d2b48c,teal#008080,"
"thistle#d8bfd8,tomato#ff6347,turquoise#40e0d0,violet#ee82ee,wheat#f5deb3,"
"white#ffffff,whitesmoke#f5f5f5,yellow#ffff00,yellowgreen#9acd32";
+ (UIColor *)searchForColorByName:(NSString *)cssColorName {
UIColor *result = nil;
// Compile the string we'll use to search against the database
// We search for ",<colorname>#" to avoid false matches
const char *searchString = [[NSString stringWithFormat:@",%@#", cssColorName] UTF8String];
// Search for the color name
const char *found = strstr(colorNameDB, searchString);
// If found, step past the search string and grab the hex representation
if (found) {
const char *after = found + strlen(searchString);
int hex;
if (sscanf(after, "%x", &hex) == 1) {
result = [self LOT_colorWithRGBHex:hex];
}
}
return result;
}
@end
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
@interface CALayer (Compat)
@property (nonatomic, assign) BOOL allowsEdgeAntialiasing;
@end
#endif
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import "CALayer+Compat.h"
@implementation CALayer (Compat)
- (BOOL)allowsEdgeAntialiasing { return NO; }
- (void)setAllowsEdgeAntialiasing:(BOOL)allowsEdgeAntialiasing { }
@end
#endif
//
// LOTPlatformCompat.h
// Lottie
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#ifndef LOTPlatformCompat_h
#define LOTPlatformCompat_h
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#import "UIColor.h"
#import "CALayer+Compat.h"
#import "NSValue+Compat.h"
#import "UIBezierPath.h"
NS_INLINE NSString *NSStringFromCGRect(CGRect rect) {
return NSStringFromRect(rect);
}
NS_INLINE NSString *NSStringFromCGPoint(CGPoint point) {
return NSStringFromPoint(point);
}
typedef NSEdgeInsets UIEdgeInsets;
#endif
#endif
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Foundation/Foundation.h>
@interface NSValue (Compat)
+ (NSValue *)valueWithCGRect:(CGRect)rect;
+ (NSValue *)valueWithCGPoint:(CGPoint)point;
@property (nonatomic, readonly) CGRect CGRectValue;
@property(nonatomic, readonly) CGPoint CGPointValue;
@property (nonatomic, readonly) CGSize CGSizeValue;
@end
#endif
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import "NSValue+Compat.h"
@implementation NSValue (Compat)
+ (NSValue *)valueWithCGRect:(CGRect)rect {
return [self valueWithRect:rect];
}
+ (NSValue *)valueWithCGPoint:(CGPoint)point {
return [self valueWithPoint:point];
}
- (CGRect)CGRectValue {
return self.rectValue;
}
- (CGPoint)CGPointValue {
return self.pointValue;
}
- (CGSize)CGSizeValue {
return self.sizeValue;
}
@end
#endif
// Kindly stolen from https://github.com/BigZaphod/Chameleon
/*
* Copyright (c) 2011, The Iconfactory. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of The Iconfactory nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
UIRectCornerTopLeft = 1 << 0,
UIRectCornerTopRight = 1 << 1,
UIRectCornerBottomLeft = 1 << 2,
UIRectCornerBottomRight = 1 << 3,
UIRectCornerAllCorners = UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight
};
@interface UIBezierPath : NSObject <NSCopying>
+ (UIBezierPath *)bezierPath;
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect;
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
+ (UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
+ (UIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath;
- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)closePath;
- (void)removeAllPoints;
- (void)appendPath:(UIBezierPath *)bezierPath;
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase;
- (BOOL)containsPoint:(CGPoint)point;
- (void)applyTransform:(CGAffineTransform)transform;
@property (nonatomic) CGPathRef CGPath;
@property (nonatomic, readonly) CGPoint currentPoint;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) CGLineCap lineCapStyle;
@property (nonatomic) CGLineJoin lineJoinStyle;
@property (nonatomic) CGFloat miterLimit;
@property (nonatomic) CGFloat flatness;
@property (nonatomic) BOOL usesEvenOddFillRule;
@property (readonly, getter=isEmpty) BOOL empty;
@property (nonatomic, readonly) CGRect bounds;
@end
#endif
// Kindly stolen from https://github.com/BigZaphod/Chameleon
/*
* Copyright (c) 2011, The Iconfactory. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of The Iconfactory nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import "UIBezierPath.h"
@implementation UIBezierPath {
CGFloat *_lineDashPattern;
NSInteger _lineDashCount;
CGFloat _lineDashPhase;
}
@synthesize CGPath = _path;
- (id)init {
self = [super init];
if (self) {
_path = CGPathCreateMutable();
_lineWidth = 1;
_lineCapStyle = kCGLineCapButt;
_lineJoinStyle = kCGLineJoinMiter;
_miterLimit = 10;
_flatness = 0.6;
_usesEvenOddFillRule = NO;
_lineDashPattern = NULL;
_lineDashCount = 0;
_lineDashPhase = 0;
}
return self;
}
- (void)dealloc {
if (_path) CGPathRelease(_path);
}
- (id)copyWithZone:(NSZone *)zone {
UIBezierPath *copy = [[self class] new];
copy.CGPath = self.CGPath;
copy.lineWidth = self.lineWidth;
copy.lineCapStyle = self.lineCapStyle;
copy.lineJoinStyle = self.lineJoinStyle;
copy.miterLimit = self.miterLimit;
copy.flatness = self.flatness;
copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
NSInteger lineDashCount = 0;
[self getLineDash:NULL count:&lineDashCount phase:NULL];
if (lineDashCount > 0) {
CGFloat *lineDashPattern = malloc(sizeof(CGFloat) * lineDashCount);
CGFloat lineDashPhase = 0;
[self getLineDash:lineDashPattern count:NULL phase:&lineDashPhase];
[copy setLineDash:lineDashPattern count:lineDashCount phase:lineDashPhase];
free(lineDashPattern);
}
return copy;
}
+ (UIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath {
NSAssert(CGPath != NULL, @"CGPath must not be NULL");
UIBezierPath *bezierPath = [[self alloc] init];
bezierPath.CGPath = CGPath;
return bezierPath;
}
+ (UIBezierPath *)bezierPath {
UIBezierPath *bezierPath = [[self alloc] init];
return bezierPath;
}
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect);
UIBezierPath *bezierPath = [[self alloc] init];
bezierPath->_path = path;
return bezierPath;
}
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, rect);
UIBezierPath *bezierPath = [[self alloc] init];
bezierPath->_path = path;
return bezierPath;
}
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect
cornerRadius:(CGFloat)cornerRadius {
return [self bezierPathWithRoundedRect:rect
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
}
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect
byRoundingCorners:(UIRectCorner)corners
cornerRadii:(CGSize)cornerRadii {
CGMutablePathRef path = CGPathCreateMutable();
const CGPoint topLeft = rect.origin;
const CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
const CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
const CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
if (corners & UIRectCornerTopLeft) {
CGPathMoveToPoint(path, NULL, topLeft.x + cornerRadii.width, topLeft.y);
} else {
CGPathMoveToPoint(path, NULL, topLeft.x, topLeft.y);
}
if (corners & UIRectCornerTopRight) {
CGPathAddLineToPoint(path, NULL, topRight.x - cornerRadii.width, topRight.y);
CGPathAddCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y + cornerRadii.height, topRight.x, topRight.y + cornerRadii.height);
} else {
CGPathAddLineToPoint(path, NULL, topRight.x, topRight.y);
}
if (corners & UIRectCornerBottomRight) {
CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y - cornerRadii.height);
CGPathAddCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x - cornerRadii.width, bottomRight.y, bottomRight.x - cornerRadii.width, bottomRight.y);
} else {
CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y);
}
if (corners & UIRectCornerBottomLeft) {
CGPathAddLineToPoint(path, NULL, bottomLeft.x + cornerRadii.width, bottomLeft.y);
CGPathAddCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y - cornerRadii.height, bottomLeft.x, bottomLeft.y - cornerRadii.height);
} else {
CGPathAddLineToPoint(path, NULL, bottomLeft.x, bottomLeft.y);
}
if (corners & UIRectCornerTopLeft) {
CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y + cornerRadii.height);
CGPathAddCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x + cornerRadii.width, topLeft.y, topLeft.x + cornerRadii.width, topLeft.y);
} else {
CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y);
}
CGPathCloseSubpath(path);
UIBezierPath *bezierPath = [[self alloc] init];
bezierPath->_path = path;
return bezierPath;
}
+ (UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center
radius:(CGFloat)radius
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
clockwise:(BOOL)clockwise {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise);
UIBezierPath *bezierPath = [[self alloc] init];
bezierPath->_path = path;
return bezierPath;
}
- (void)moveToPoint:(CGPoint)point {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathMoveToPoint(mutablePath, NULL, point.x, point.y);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)addLineToPoint:(CGPoint)point {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathAddLineToPoint(mutablePath, NULL, point.x, point.y);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathAddArc(mutablePath, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathAddCurveToPoint(mutablePath, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathAddQuadCurveToPoint(mutablePath, NULL, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)closePath {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathCloseSubpath(mutablePath);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)removeAllPoints {
CGMutablePathRef mutablePath = CGPathCreateMutable();
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
- (void)appendPath:(UIBezierPath *)bezierPath {
if (bezierPath) {
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
CGPathAddPath(mutablePath, NULL, bezierPath.CGPath);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
}
- (void)setCGPath:(CGPathRef)path {
NSAssert(path != NULL, @"path must not be NULL");
if (path != _path) {
if (_path) CGPathRelease(_path);
_path = CGPathCreateCopy(path);
}
}
- (CGPoint)currentPoint {
return CGPathGetCurrentPoint(_path);
}
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase {
free(_lineDashPattern);
if (pattern && count > 0) {
const size_t size = sizeof(CGFloat) * count;
_lineDashPattern = malloc(size);
bcopy(pattern, _lineDashPattern, size);
} else {
_lineDashPattern = NULL;
}
_lineDashCount = count;
_lineDashPhase = phase;
}
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase {
if (pattern && _lineDashPattern && _lineDashCount > 0) {
const size_t size = sizeof(CGFloat) * _lineDashCount;
bcopy(_lineDashPattern, pattern, size);
}
if (count) {
*count = _lineDashCount;
}
if (phase) {
*phase = _lineDashPhase;
}
}
- (BOOL)containsPoint:(CGPoint)point {
return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
}
- (BOOL)isEmpty {
return CGPathIsEmpty(_path);
}
- (CGRect)bounds {
return CGPathGetBoundingBox(_path);
}
- (void)applyTransform:(CGAffineTransform)transform {
CGMutablePathRef mutablePath = CGPathCreateMutable();
CGPathAddPath(mutablePath, &transform, _path);
self.CGPath = mutablePath;
CGPathRelease(mutablePath);
}
@end
#endif
//
// UIColor.h
// Lottie
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface UIColor : NSObject <NSCopying>
+ (UIColor *)colorWithWhite:(CGFloat)white alpha:(CGFloat)alpha;
+ (UIColor *)colorWithHue:(CGFloat)hue saturation:(CGFloat)saturation brightness:(CGFloat)brightness alpha:(CGFloat)alpha;
+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
+ (UIColor *)colorWithCGColor:(CGColorRef)cgColor;
+ (UIColor *)blackColor;
+ (UIColor *)darkGrayColor;
+ (UIColor *)lightGrayColor;
+ (UIColor *)whiteColor;
+ (UIColor *)grayColor;
+ (UIColor *)redColor;
+ (UIColor *)greenColor;
+ (UIColor *)blueColor;
+ (UIColor *)cyanColor;
+ (UIColor *)yellowColor;
+ (UIColor *)magentaColor;
+ (UIColor *)orangeColor;
+ (UIColor *)purpleColor;
+ (UIColor *)brownColor;
+ (UIColor *)clearColor;
- (UIColor *)colorWithAlphaComponent:(CGFloat)alpha;
@property (nonatomic, readonly) CGColorRef CGColor;
@end
#endif
//
// UIColor.m
// Lottie
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import "UIColor.h"
#import <AppKit/AppKit.h>
#define StaticColor(staticColor) \
static UIColor *color = nil; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
color = NSColor.staticColor.UIColor; \
}); \
return color; \
@interface UIColor ()
@property (nonatomic, strong) NSColor *color;
- (instancetype)initWithNSColor:(NSColor *)color;
@end
@interface NSColor (UIColor)
@property (nonatomic, readonly) UIColor *UIColor;
@end
@implementation UIColor
- (instancetype)initWithNSColor:(NSColor *)color {
self = [super init];
if (self) {
self.color = color;
}
return self;
}
+ (UIColor *)colorWithNSColor:(NSColor *)color {
return [[self alloc] initWithNSColor:color];
}
+ (UIColor *)colorWithWhite:(CGFloat)white alpha:(CGFloat)alpha {
return [[NSColor colorWithWhite:white alpha:alpha] UIColor];
}
+ (UIColor *)colorWithHue:(CGFloat)hue
saturation:(CGFloat)saturation
brightness:(CGFloat)brightness
alpha:(CGFloat)alpha {
return [[NSColor colorWithHue:hue
saturation:saturation
brightness:brightness
alpha:alpha] UIColor];
}
+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
return [[NSColor colorWithRed:red
green:green
blue:blue
alpha:alpha] UIColor];
}
+ (UIColor *)colorWithCGColor:(CGColorRef)cgColor {
return [[NSColor colorWithCGColor:cgColor] UIColor];
}
+ (UIColor *)blackColor {
StaticColor(blackColor)
}
+ (UIColor *)darkGrayColor {
StaticColor(darkGrayColor)
}
+ (UIColor *)lightGrayColor {
StaticColor(lightGrayColor)
}
+ (UIColor *)whiteColor {
StaticColor(whiteColor)
}
+ (UIColor *)grayColor {
StaticColor(grayColor)
}
+ (UIColor *)redColor {
StaticColor(redColor)
}
+ (UIColor *)greenColor {
StaticColor(greenColor)
}
+ (UIColor *)blueColor {
StaticColor(blueColor)
}
+ (UIColor *)cyanColor {
StaticColor(cyanColor)
}
+ (UIColor *)yellowColor {
StaticColor(yellowColor)
}
+ (UIColor *)magentaColor {
StaticColor(magentaColor)
}
+ (UIColor *)orangeColor {
StaticColor(orangeColor)
}
+ (UIColor *)purpleColor {
StaticColor(purpleColor)
}
+ (UIColor *)brownColor {
StaticColor(brownColor)
}
+ (UIColor *)clearColor {
StaticColor(clearColor)
}
- (CGColorRef)CGColor {
return self.color.CGColor;
}
- (UIColor *)colorWithAlphaComponent:(CGFloat)alpha {
return [self.color colorWithAlphaComponent:alpha].UIColor;
}
- (id)copyWithZone:(NSZone *)zone {
return [[self.color copyWithZone:zone] UIColor];
}
@end
@implementation NSColor (UIColor)
- (UIColor *)UIColor {
return [UIColor colorWithNSColor:self];
}
@end
#endif
//
// LOTAsset.h
// Pods
//
// Created by Brandon Withrow on 2/16/17.
//
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
NS_ASSUME_NONNULL_BEGIN
@class LOTLayerGroup;
@class LOTLayer;
@class LOTAssetGroup;
@interface LOTAsset : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withAssetBundle:(NSBundle *_Nonnull)bundle
withFramerate:(NSNumber *)framerate;
@property (nonatomic, readonly, nullable) NSString *referenceID;
@property (nonatomic, readonly, nullable) NSNumber *assetWidth;
@property (nonatomic, readonly, nullable) NSNumber *assetHeight;
@property (nonatomic, readonly, nullable) NSString *imageName;
@property (nonatomic, readonly, nullable) NSString *imageDirectory;
@property (nonatomic, readonly, nullable) LOTLayerGroup *layerGroup;
@property (nonatomic, readwrite) NSString *rootDirectory;
@property (nonatomic, readonly) NSBundle *assetBundle;
@end
NS_ASSUME_NONNULL_END
//
// LOTAsset.m
// Pods
//
// Created by Brandon Withrow on 2/16/17.
//
//
#import "LOTAsset.h"
#import "LOTLayer.h"
#import "LOTLayerGroup.h"
#import "LOTAssetGroup.h"
@implementation LOTAsset
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withAssetBundle:(NSBundle *_Nonnull)bundle
withFramerate:(NSNumber *)framerate {
self = [super init];
if (self) {
_assetBundle = bundle;
[self _mapFromJSON:jsonDictionary
withAssetGroup:assetGroup
withFramerate:framerate];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withFramerate:(NSNumber *)framerate {
_referenceID = [jsonDictionary[@"id"] copy];
if (jsonDictionary[@"w"]) {
_assetWidth = [jsonDictionary[@"w"] copy];
}
if (jsonDictionary[@"h"]) {
_assetHeight = [jsonDictionary[@"h"] copy];
}
if (jsonDictionary[@"u"]) {
_imageDirectory = [jsonDictionary[@"u"] copy];
}
if (jsonDictionary[@"p"]) {
_imageName = [jsonDictionary[@"p"] copy];
}
NSArray *layersJSON = jsonDictionary[@"layers"];
if (layersJSON) {
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
withAssetGroup:assetGroup
withFramerate:framerate];
}
}
@end
//
// LOTAssetGroup.h
// Pods
//
// Created by Brandon Withrow on 2/17/17.
//
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@class LOTAsset;
@class LOTLayerGroup;
@interface LOTAssetGroup : NSObject
@property (nonatomic, readwrite) NSString * _Nullable rootDirectory;
@property (nonatomic, readonly, nullable) NSBundle *assetBundle;
- (instancetype _Nonnull)initWithJSON:(NSArray * _Nonnull)jsonArray
withAssetBundle:(NSBundle *_Nullable)bundle
withFramerate:(NSNumber * _Nonnull)framerate;
- (void)buildAssetNamed:(NSString * _Nonnull)refID withFramerate:(NSNumber * _Nonnull)framerate;
- (void)finalizeInitializationWithFramerate:(NSNumber * _Nonnull)framerate;
- (LOTAsset * _Nullable)assetModelForID:(NSString * _Nonnull)assetID;
@end
//
// LOTAssetGroup.m
// Pods
//
// Created by Brandon Withrow on 2/17/17.
//
//
#import "LOTAssetGroup.h"
#import "LOTAsset.h"
@implementation LOTAssetGroup {
NSMutableDictionary<NSString *, LOTAsset *> *_assetMap;
NSDictionary<NSString *, NSDictionary *> *_assetJSONMap;
}
- (instancetype _Nonnull)initWithJSON:(NSArray * _Nonnull)jsonArray
withAssetBundle:(NSBundle * _Nullable)bundle
withFramerate:(NSNumber * _Nonnull)framerate {
self = [super init];
if (self) {
_assetBundle = bundle;
_assetMap = [NSMutableDictionary dictionary];
NSMutableDictionary *assetJSONMap = [NSMutableDictionary dictionary];
for (NSDictionary<NSString *, NSString *> *assetDictionary in jsonArray) {
NSString *referenceID = assetDictionary[@"id"];
if (referenceID) {
assetJSONMap[referenceID] = assetDictionary;
}
}
_assetJSONMap = assetJSONMap;
}
return self;
}
- (void)buildAssetNamed:(NSString *)refID
withFramerate:(NSNumber * _Nonnull)framerate {
if ([self assetModelForID:refID]) {
return;
}
NSDictionary *assetDictionary = _assetJSONMap[refID];
if (assetDictionary) {
LOTAsset *asset = [[LOTAsset alloc] initWithJSON:assetDictionary
withAssetGroup:self
withAssetBundle:_assetBundle
withFramerate:framerate];
_assetMap[refID] = asset;
}
}
- (void)finalizeInitializationWithFramerate:(NSNumber * _Nonnull)framerate {
for (NSString *refID in _assetJSONMap.allKeys) {
[self buildAssetNamed:refID withFramerate:framerate];
}
_assetJSONMap = nil;
}
- (LOTAsset *)assetModelForID:(NSString *)assetID {
return _assetMap[assetID];
}
- (void)setRootDirectory:(NSString *)rootDirectory {
_rootDirectory = rootDirectory;
[_assetMap enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, LOTAsset * _Nonnull obj, BOOL * _Nonnull stop) {
obj.rootDirectory = rootDirectory;
}];
}
@end
//
// LOTLayer.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTPlatformCompat.h"
#import "LOTKeyframe.h"
@class LOTShapeGroup;
@class LOTMask;
@class LOTAsset;
@class LOTAssetGroup;
typedef enum : NSInteger {
LOTLayerTypePrecomp,
LOTLayerTypeSolid,
LOTLayerTypeImage,
LOTLayerTypeNull,
LOTLayerTypeShape,
LOTLayerTypeUnknown
} LOTLayerType;
typedef enum : NSInteger {
LOTMatteTypeNone,
LOTMatteTypeAdd,
LOTMatteTypeInvert,
LOTMatteTypeUnknown
} LOTMatteType;
NS_ASSUME_NONNULL_BEGIN
@interface LOTLayer : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withFramerate:(NSNumber *)framerate;
@property (nonatomic, readonly) NSString *layerName;
@property (nonatomic, readonly, nullable) NSString *referenceID;
@property (nonatomic, readonly) NSNumber *layerID;
@property (nonatomic, readonly) LOTLayerType layerType;
@property (nonatomic, readonly, nullable) NSNumber *parentID;
@property (nonatomic, readonly) NSNumber *startFrame;
@property (nonatomic, readonly) NSNumber *inFrame;
@property (nonatomic, readonly) NSNumber *outFrame;
@property (nonatomic, readonly) NSNumber *timeStretch;
@property (nonatomic, readonly) CGRect layerBounds;
@property (nonatomic, readonly, nullable) NSArray<LOTShapeGroup *> *shapes;
@property (nonatomic, readonly, nullable) NSArray<LOTMask *> *masks;
@property (nonatomic, readonly, nullable) NSNumber *layerWidth;
@property (nonatomic, readonly, nullable) NSNumber *layerHeight;
@property (nonatomic, readonly, nullable) UIColor *solidColor;
@property (nonatomic, readonly, nullable) LOTAsset *imageAsset;
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *timeRemapping;
@property (nonatomic, readonly) LOTKeyframeGroup *rotation;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *position;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *positionX;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *positionY;
@property (nonatomic, readonly) LOTKeyframeGroup *anchor;
@property (nonatomic, readonly) LOTKeyframeGroup *scale;
@property (nonatomic, readonly) LOTMatteType matteType;
@end
NS_ASSUME_NONNULL_END
//
// LOTLayer.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTLayer.h"
#import "LOTAsset.h"
#import "LOTAssetGroup.h"
#import "LOTShapeGroup.h"
#import "LOTComposition.h"
#import "LOTHelpers.h"
#import "LOTMask.h"
#import "LOTHelpers.h"
@implementation LOTLayer
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup *)assetGroup
withFramerate:(NSNumber *)framerate {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary
withAssetGroup:assetGroup
withFramerate:framerate];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
withAssetGroup:(LOTAssetGroup *)assetGroup
withFramerate:(NSNumber *)framerate {
_layerName = [jsonDictionary[@"nm"] copy];
_layerID = [jsonDictionary[@"ind"] copy];
NSNumber *layerType = jsonDictionary[@"ty"];
_layerType = layerType.integerValue;
if (jsonDictionary[@"refId"]) {
_referenceID = [jsonDictionary[@"refId"] copy];
}
_parentID = [jsonDictionary[@"parent"] copy];
if (jsonDictionary[@"st"]) {
_startFrame = [jsonDictionary[@"st"] copy];
}
_inFrame = [jsonDictionary[@"ip"] copy];
_outFrame = [jsonDictionary[@"op"] copy];
if (jsonDictionary[@"sr"]) {
_timeStretch = [jsonDictionary[@"sr"] copy];
} else {
_timeStretch = @1;
}
if (_layerType == LOTLayerTypePrecomp) {
_layerHeight = [jsonDictionary[@"h"] copy];
_layerWidth = [jsonDictionary[@"w"] copy];
[assetGroup buildAssetNamed:_referenceID withFramerate:framerate];
} else if (_layerType == LOTLayerTypeImage) {
[assetGroup buildAssetNamed:_referenceID withFramerate:framerate];
_imageAsset = [assetGroup assetModelForID:_referenceID];
_layerWidth = [_imageAsset.assetWidth copy];
_layerHeight = [_imageAsset.assetHeight copy];
} else if (_layerType == LOTLayerTypeSolid) {
_layerWidth = jsonDictionary[@"sw"];
_layerHeight = jsonDictionary[@"sh"];
NSString *solidColor = jsonDictionary[@"sc"];
_solidColor = [UIColor LOT_colorWithHexString:solidColor];
}
_layerBounds = CGRectMake(0, 0, _layerWidth.floatValue, _layerHeight.floatValue);
NSDictionary *ks = jsonDictionary[@"ks"];
NSDictionary *opacity = ks[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSDictionary *timeRemap = jsonDictionary[@"tm"];
if (timeRemap) {
_timeRemapping = [[LOTKeyframeGroup alloc] initWithData:timeRemap];
[_timeRemapping remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return inValue * framerate.doubleValue;
}];
}
NSDictionary *rotation = ks[@"r"];
if (rotation == nil) {
rotation = ks[@"rz"];
}
if (rotation) {
_rotation = [[LOTKeyframeGroup alloc] initWithData:rotation];
[_rotation remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_DegreesToRadians(inValue);
}];
}
NSDictionary *position = ks[@"p"];
if ([position[@"s"] boolValue]) {
// Separate dimensions
_positionX = [[LOTKeyframeGroup alloc] initWithData:position[@"x"]];
_positionY = [[LOTKeyframeGroup alloc] initWithData:position[@"y"]];
} else {
_position = [[LOTKeyframeGroup alloc] initWithData:position ];
}
NSDictionary *anchor = ks[@"a"];
if (anchor) {
_anchor = [[LOTKeyframeGroup alloc] initWithData:anchor];
}
NSDictionary *scale = ks[@"s"];
if (scale) {
_scale = [[LOTKeyframeGroup alloc] initWithData:scale];
[_scale remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, -100, 100, -1, 1);
}];
}
_matteType = [jsonDictionary[@"tt"] integerValue];
NSMutableArray *masks = [NSMutableArray array];
for (NSDictionary *maskJSON in jsonDictionary[@"masksProperties"]) {
LOTMask *mask = [[LOTMask alloc] initWithJSON:maskJSON];
[masks addObject:mask];
}
_masks = masks.count ? masks : nil;
NSMutableArray *shapes = [NSMutableArray array];
for (NSDictionary *shapeJSON in jsonDictionary[@"shapes"]) {
id shapeItem = [LOTShapeGroup shapeItemWithJSON:shapeJSON];
if (shapeItem) {
[shapes addObject:shapeItem];
}
}
_shapes = shapes;
NSArray *effects = jsonDictionary[@"ef"];
if (effects.count > 0) {
NSDictionary *effectNames = @{ @0: @"slider",
@1: @"angle",
@2: @"color",
@3: @"point",
@4: @"checkbox",
@5: @"group",
@6: @"noValue",
@7: @"dropDown",
@9: @"customValue",
@10: @"layerIndex",
@20: @"tint",
@21: @"fill" };
for (NSDictionary *effect in effects) {
NSNumber *typeNumber = effect[@"ty"];
NSString *name = effect[@"nm"];
NSString *internalName = effect[@"mn"];
NSString *typeString = effectNames[typeNumber];
if (typeString) {
NSLog(@"%s: Warning: %@ effect not supported: %@ / %@", __PRETTY_FUNCTION__, typeString, internalName, name);
}
}
}
}
- (NSString *)description {
NSMutableString *text = [[super description] mutableCopy];
[text appendFormat:@" %@ id: %d pid: %d frames: %d-%d", _layerName, (int)_layerID.integerValue, (int)_parentID.integerValue,
(int)_inFrame.integerValue, (int)_outFrame.integerValue];
return text;
}
@end
//
// LOTLayerGroup.h
// Pods
//
// Created by Brandon Withrow on 2/16/17.
//
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
NS_ASSUME_NONNULL_BEGIN
@class LOTLayer;
@class LOTAssetGroup;
@interface LOTLayerGroup : NSObject
- (instancetype)initWithLayerJSON:(NSArray *)layersJSON
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withFramerate:(NSNumber *)framerate;
@property (nonatomic, readonly) NSArray <LOTLayer *> *layers;
- (LOTLayer *)layerModelForID:(NSNumber *)layerID;
- (LOTLayer *)layerForReferenceID:(NSString *)referenceID;
@end
NS_ASSUME_NONNULL_END
//
// LOTLayerGroup.m
// Pods
//
// Created by Brandon Withrow on 2/16/17.
//
//
#import "LOTLayerGroup.h"
#import "LOTLayer.h"
#import "LOTAssetGroup.h"
@implementation LOTLayerGroup {
NSDictionary *_modelMap;
NSDictionary *_referenceIDMap;
}
- (instancetype)initWithLayerJSON:(NSArray *)layersJSON
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withFramerate:(NSNumber *)framerate {
self = [super init];
if (self) {
[self _mapFromJSON:layersJSON withAssetGroup:assetGroup withFramerate:framerate];
}
return self;
}
- (void)_mapFromJSON:(NSArray *)layersJSON
withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
withFramerate:(NSNumber *)framerate {
NSMutableArray *layers = [NSMutableArray array];
NSMutableDictionary *modelMap = [NSMutableDictionary dictionary];
NSMutableDictionary *referenceMap = [NSMutableDictionary dictionary];
for (NSDictionary *layerJSON in layersJSON) {
LOTLayer *layer = [[LOTLayer alloc] initWithJSON:layerJSON
withAssetGroup:assetGroup
withFramerate:framerate];
[layers addObject:layer];
modelMap[layer.layerID] = layer;
if (layer.referenceID) {
referenceMap[layer.referenceID] = layer;
}
}
_referenceIDMap = referenceMap;
_modelMap = modelMap;
_layers = layers;
}
- (LOTLayer *)layerModelForID:(NSNumber *)layerID {
return _modelMap[layerID];
}
- (LOTLayer *)layerForReferenceID:(NSString *)referenceID {
return _referenceIDMap[referenceID];
}
@end
//
// LOTMask.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
typedef enum : NSUInteger {
LOTMaskModeAdd,
LOTMaskModeSubtract,
LOTMaskModeIntersect,
LOTMaskModeUnknown
} LOTMaskMode;
@interface LOTMask : NSObject
- (instancetype _Nonnull)initWithJSON:(NSDictionary * _Nonnull)jsonDictionary;
@property (nonatomic, readonly) BOOL closed;
@property (nonatomic, readonly) BOOL inverted;
@property (nonatomic, readonly) LOTMaskMode maskMode;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *maskPath;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *opacity;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *expansion;
@end
//
// LOTMask.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTMask.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTMask
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
NSNumber *closed = jsonDictionary[@"cl"];
_closed = closed.boolValue;
NSNumber *inverted = jsonDictionary[@"inv"];
_inverted = inverted.boolValue;
NSString *mode = jsonDictionary[@"mode"];
if ([mode isEqualToString:@"a"]) {
_maskMode = LOTMaskModeAdd;
} else if ([mode isEqualToString:@"s"]) {
_maskMode = LOTMaskModeSubtract;
} else if ([mode isEqualToString:@"i"]) {
_maskMode = LOTMaskModeIntersect;
} else {
_maskMode = LOTMaskModeUnknown;
}
NSDictionary *maskshape = jsonDictionary[@"pt"];
if (maskshape) {
_maskPath = [[LOTKeyframeGroup alloc] initWithData:maskshape];
}
NSDictionary *opacity = jsonDictionary[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSDictionary *expansion = jsonDictionary[@"x"];
if (expansion) {
_expansion = [[LOTKeyframeGroup alloc] initWithData:expansion];
}
}
@end
//
// LOTModels.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#ifndef LOTModels_h
#define LOTModels_h
#import "LOTKeyframe.h"
#import "LOTComposition.h"
#import "LOTLayer.h"
#import "LOTMask.h"
#import "LOTShapeCircle.h"
#import "LOTShapeFill.h"
#import "LOTShapeGroup.h"
#import "LOTShapePath.h"
#import "LOTShapeRectangle.h"
#import "LOTShapeStroke.h"
#import "LOTShapeTransform.h"
#import "LOTShapeTrimPath.h"
#import "LOTLayerGroup.h"
#import "LOTAsset.h"
#import "LOTShapeGradientFill.h"
#endif /* LOTModels_h */
//
// LOTShapeCircle.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTShapeCircle : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) LOTKeyframeGroup *position;
@property (nonatomic, readonly) LOTKeyframeGroup *size;
@property (nonatomic, readonly) BOOL reversed;
@end
NS_ASSUME_NONNULL_END
//
// LOTShapeCircle.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeCircle.h"
@implementation LOTShapeCircle
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *position = jsonDictionary[@"p"];
if (position) {
_position = [[LOTKeyframeGroup alloc] initWithData:position];
}
NSDictionary *size= jsonDictionary[@"s"];
if (size) {
_size = [[LOTKeyframeGroup alloc] initWithData:size];
}
NSNumber *reversed = jsonDictionary[@"d"];
_reversed = (reversed.integerValue == 3);
}
@end
//
// LOTShapeFill.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTShapeFill : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) BOOL fillEnabled;
@property (nonatomic, readonly) LOTKeyframeGroup *color;
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;
@property (nonatomic, readonly) BOOL evenOddFillRule;
@end
NS_ASSUME_NONNULL_END
//
// LOTShapeFill.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeFill.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTShapeFill
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *color = jsonDictionary[@"c"];
if (color) {
_color = [[LOTKeyframeGroup alloc] initWithData:color];
}
NSDictionary *opacity = jsonDictionary[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSNumber *evenOdd = jsonDictionary[@"r"];
if (evenOdd.integerValue == 2) {
_evenOddFillRule = YES;
} else {
_evenOddFillRule = NO;
}
NSNumber *fillEnabled = jsonDictionary[@"fillEnabled"];
_fillEnabled = fillEnabled.boolValue;
}
@end
//
// LOTShapeGradientFill.h
// Lottie
//
// Created by brandon_withrow on 7/26/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
NS_ASSUME_NONNULL_BEGIN
typedef enum : NSUInteger {
LOTGradientTypeLinear,
LOTGradientTypeRadial
} LOTGradientType;
@interface LOTShapeGradientFill : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) NSNumber *numberOfColors;
@property (nonatomic, readonly) LOTKeyframeGroup *startPoint;
@property (nonatomic, readonly) LOTKeyframeGroup *endPoint;
@property (nonatomic, readonly) LOTKeyframeGroup *gradient;
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;
@property (nonatomic, readonly) BOOL evenOddFillRule;
@property (nonatomic, readonly) LOTGradientType type;
@end
NS_ASSUME_NONNULL_END
//
// LOTShapeGradientFill.m
// Lottie
//
// Created by brandon_withrow on 7/26/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTShapeGradientFill.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTShapeGradientFill
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSNumber *type = jsonDictionary[@"t"];
if (type.integerValue != 1) {
_type = LOTGradientTypeRadial;
} else {
_type = LOTGradientTypeLinear;
}
NSDictionary *start = jsonDictionary[@"s"];
if (start) {
_startPoint = [[LOTKeyframeGroup alloc] initWithData:start];
}
NSDictionary *end = jsonDictionary[@"e"];
if (end) {
_endPoint = [[LOTKeyframeGroup alloc] initWithData:end];
}
NSDictionary *gradient = jsonDictionary[@"g"];
if (gradient) {
NSDictionary *unwrappedGradient = gradient[@"k"];
_numberOfColors = gradient[@"p"];
_gradient = [[LOTKeyframeGroup alloc] initWithData:unwrappedGradient];
}
NSDictionary *opacity = jsonDictionary[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSNumber *evenOdd = jsonDictionary[@"r"];
if (evenOdd.integerValue == 2) {
_evenOddFillRule = YES;
} else {
_evenOddFillRule = NO;
}
}
@end
//
// LOTShape.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface LOTShapeGroup : NSObject
- (instancetype _Nonnull)initWithJSON:(NSDictionary *_Nonnull)jsonDictionary;
@property (nonatomic, readonly, nonnull) NSString *keyname;
@property (nonatomic, readonly, nonnull) NSArray *items;
+ (id _Nullable)shapeItemWithJSON:(NSDictionary * _Nonnull)itemJSON;
@end
//
// LOTShape.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeGroup.h"
#import "LOTShapeFill.h"
#import "LOTShapePath.h"
#import "LOTShapeCircle.h"
#import "LOTShapeStroke.h"
#import "LOTShapeTransform.h"
#import "LOTShapeRectangle.h"
#import "LOTShapeTrimPath.h"
#import "LOTShapeGradientFill.h"
#import "LOTShapeStar.h"
#import "LOTShapeRepeater.h"
@implementation LOTShapeGroup
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSArray *itemsJSON = jsonDictionary[@"it"];
NSMutableArray *items = [NSMutableArray array];
for (NSDictionary *itemJSON in itemsJSON) {
id newItem = [LOTShapeGroup shapeItemWithJSON:itemJSON];
if (newItem) {
[items addObject:newItem];
}
}
_items = items;
}
+ (id)shapeItemWithJSON:(NSDictionary *)itemJSON {
NSString *type = itemJSON[@"ty"];
if ([type isEqualToString:@"gr"]) {
LOTShapeGroup *group = [[LOTShapeGroup alloc] initWithJSON:itemJSON];
return group;
} else if ([type isEqualToString:@"st"]) {
LOTShapeStroke *stroke = [[LOTShapeStroke alloc] initWithJSON:itemJSON];
return stroke;
} else if ([type isEqualToString:@"fl"]) {
LOTShapeFill *fill = [[LOTShapeFill alloc] initWithJSON:itemJSON];
return fill;
} else if ([type isEqualToString:@"tr"]) {
LOTShapeTransform *transform = [[LOTShapeTransform alloc] initWithJSON:itemJSON];
return transform;
} else if ([type isEqualToString:@"sh"]) {
LOTShapePath *path = [[LOTShapePath alloc] initWithJSON:itemJSON];
return path;
} else if ([type isEqualToString:@"el"]) {
LOTShapeCircle *circle = [[LOTShapeCircle alloc] initWithJSON:itemJSON];
return circle;
} else if ([type isEqualToString:@"rc"]) {
LOTShapeRectangle *rectangle = [[LOTShapeRectangle alloc] initWithJSON:itemJSON];
return rectangle;
} else if ([type isEqualToString:@"tm"]) {
LOTShapeTrimPath *trim = [[LOTShapeTrimPath alloc] initWithJSON:itemJSON];
return trim;
} else if ([type isEqualToString:@"gs"]) {
NSLog(@"%s: Warning: gradient strokes are not supported", __PRETTY_FUNCTION__);
} else if ([type isEqualToString:@"gf"]) {
LOTShapeGradientFill *gradientFill = [[LOTShapeGradientFill alloc] initWithJSON:itemJSON];
return gradientFill;
} else if ([type isEqualToString:@"sr"]) {
LOTShapeStar *star = [[LOTShapeStar alloc] initWithJSON:itemJSON];
return star;
} else if ([type isEqualToString:@"mm"]) {
NSString *name = itemJSON[@"nm"];
NSLog(@"%s: Warning: merge shape is not supported. name: %@", __PRETTY_FUNCTION__, name);
} else if ([type isEqualToString:@"rp"]) {
LOTShapeRepeater *repeater = [[LOTShapeRepeater alloc] initWithJSON:itemJSON];
return repeater;
} else {
NSString *name = itemJSON[@"nm"];
NSLog(@"%s: Unsupported shape: %@ name: %@", __PRETTY_FUNCTION__, type, name);
}
return nil;
}
- (NSString *)description {
NSMutableString *text = [[super description] mutableCopy];
[text appendFormat:@" items: %@", self.items];
return text;
}
@end
//
// LOTShapePath.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
@interface LOTShapePath : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) BOOL closed;
@property (nonatomic, readonly) NSNumber *index;
@property (nonatomic, readonly) LOTKeyframeGroup *shapePath;
@end
//
// LOTShapePath.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapePath.h"
@implementation LOTShapePath
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
_index = jsonDictionary[@"ind"];
_closed = [jsonDictionary[@"closed"] boolValue];
NSDictionary *shape = jsonDictionary[@"ks"];
if (shape) {
_shapePath = [[LOTKeyframeGroup alloc] initWithData:shape];
}
}
@end
//
// LOTShapeRectangle.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
@interface LOTShapeRectangle : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) LOTKeyframeGroup *position;
@property (nonatomic, readonly) LOTKeyframeGroup *size;
@property (nonatomic, readonly) LOTKeyframeGroup *cornerRadius;
@property (nonatomic, readonly) BOOL reversed;
@end
//
// LOTShapeRectangle.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeRectangle.h"
@implementation LOTShapeRectangle
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *position = jsonDictionary[@"p"];
if (position) {
_position = [[LOTKeyframeGroup alloc] initWithData:position];
}
NSDictionary *cornerRadius = jsonDictionary[@"r"];
if (cornerRadius) {
_cornerRadius = [[LOTKeyframeGroup alloc] initWithData:cornerRadius];
}
NSDictionary *size = jsonDictionary[@"s"];
if (size) {
_size = [[LOTKeyframeGroup alloc] initWithData:size];
}
NSNumber *reversed = jsonDictionary[@"d"];
_reversed = (reversed.integerValue == 3);
}
@end
//
// LOTShapeRepeater.h
// Lottie
//
// Created by brandon_withrow on 7/28/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTShapeRepeater : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *copies;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *offset;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *anchorPoint;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *scale;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *position;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *rotation;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *startOpacity;
@property (nonatomic, readonly, nullable) LOTKeyframeGroup *endOpacity;
@end
NS_ASSUME_NONNULL_END
//
// LOTShapeRepeater.m
// Lottie
//
// Created by brandon_withrow on 7/28/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTShapeRepeater.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTShapeRepeater
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *copies = jsonDictionary[@"c"];
if (copies) {
_copies = [[LOTKeyframeGroup alloc] initWithData:copies];
}
NSDictionary *offset = jsonDictionary[@"o"];
if (offset) {
_offset = [[LOTKeyframeGroup alloc] initWithData:offset];
}
NSDictionary *transform = jsonDictionary[@"tr"];
NSDictionary *rotation = transform[@"r"];
if (rotation) {
_rotation = [[LOTKeyframeGroup alloc] initWithData:rotation];
[_rotation remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_DegreesToRadians(inValue);
}];
}
NSDictionary *startOpacity = transform[@"so"];
if (startOpacity) {
_startOpacity = [[LOTKeyframeGroup alloc] initWithData:startOpacity];
[_startOpacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSDictionary *endOpacity = transform[@"eo"];
if (endOpacity) {
_endOpacity = [[LOTKeyframeGroup alloc] initWithData:endOpacity];
[_endOpacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSDictionary *anchorPoint = transform[@"a"];
if (anchorPoint) {
_anchorPoint = [[LOTKeyframeGroup alloc] initWithData:anchorPoint];
}
NSDictionary *position = transform[@"p"];
if (position) {
_position = [[LOTKeyframeGroup alloc] initWithData:position];
}
NSDictionary *scale = transform[@"s"];
if (scale) {
_scale = [[LOTKeyframeGroup alloc] initWithData:scale];
[_scale remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, -100, 100, -1, 1);
}];
}
}
@end
//
// LOTShapeStar.h
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
typedef enum : NSUInteger {
LOTPolystarShapeNone,
LOTPolystarShapeStar,
LOTPolystarShapePolygon
} LOTPolystarShape;
@interface LOTShapeStar : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) LOTKeyframeGroup *outerRadius;
@property (nonatomic, readonly) LOTKeyframeGroup *outerRoundness;
@property (nonatomic, readonly) LOTKeyframeGroup *innerRadius;
@property (nonatomic, readonly) LOTKeyframeGroup *innerRoundness;
@property (nonatomic, readonly) LOTKeyframeGroup *position;
@property (nonatomic, readonly) LOTKeyframeGroup *numberOfPoints;
@property (nonatomic, readonly) LOTKeyframeGroup *rotation;
@property (nonatomic, readonly) LOTPolystarShape type;
@end
//
// LOTShapeStar.m
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTShapeStar.h"
@implementation LOTShapeStar
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *outerRadius = jsonDictionary[@"or"];
if (outerRadius) {
_outerRadius = [[LOTKeyframeGroup alloc] initWithData:outerRadius];
}
NSDictionary *outerRoundness = jsonDictionary[@"os"];
if (outerRoundness) {
_outerRoundness = [[LOTKeyframeGroup alloc] initWithData:outerRoundness];
}
NSDictionary *innerRadius = jsonDictionary[@"ir"];
if (innerRadius) {
_innerRadius = [[LOTKeyframeGroup alloc] initWithData:innerRadius];
}
NSDictionary *innerRoundness = jsonDictionary[@"is"];
if (innerRoundness) {
_innerRoundness = [[LOTKeyframeGroup alloc] initWithData:innerRoundness];
}
NSDictionary *position = jsonDictionary[@"p"];
if (position) {
_position = [[LOTKeyframeGroup alloc] initWithData:position];
}
NSDictionary *numberOfPoints = jsonDictionary[@"pt"];
if (numberOfPoints) {
_numberOfPoints = [[LOTKeyframeGroup alloc] initWithData:numberOfPoints];
}
NSDictionary *rotation = jsonDictionary[@"r"];
if (rotation) {
_rotation = [[LOTKeyframeGroup alloc] initWithData:rotation];
}
NSNumber *type = jsonDictionary[@"sy"];
_type = type.integerValue;
}
@end
//
// LOTShapeStroke.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
typedef enum : NSUInteger {
LOTLineCapTypeButt,
LOTLineCapTypeRound,
LOTLineCapTypeUnknown
} LOTLineCapType;
typedef enum : NSUInteger {
LOTLineJoinTypeMiter,
LOTLineJoinTypeRound,
LOTLineJoinTypeBevel
} LOTLineJoinType;
@interface LOTShapeStroke : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) BOOL fillEnabled;
@property (nonatomic, readonly) LOTKeyframeGroup *color;
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;
@property (nonatomic, readonly) LOTKeyframeGroup *width;
@property (nonatomic, readonly) LOTKeyframeGroup *dashOffset;
@property (nonatomic, readonly) LOTLineCapType capType;
@property (nonatomic, readonly) LOTLineJoinType joinType;
@property (nonatomic, readonly) NSArray *lineDashPattern;
@end
//
// LOTShapeStroke.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeStroke.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTShapeStroke
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *color = jsonDictionary[@"c"];
if (color) {
_color = [[LOTKeyframeGroup alloc] initWithData:color];
}
NSDictionary *width = jsonDictionary[@"w"];
if (width) {
_width = [[LOTKeyframeGroup alloc] initWithData:width];
}
NSDictionary *opacity = jsonDictionary[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
_capType = [jsonDictionary[@"lc"] integerValue] - 1;
_joinType = [jsonDictionary[@"lj"] integerValue] - 1;
NSNumber *fillEnabled = jsonDictionary[@"fillEnabled"];
_fillEnabled = fillEnabled.boolValue;
NSDictionary *dashOffset = nil;
NSArray *dashes = jsonDictionary[@"d"];
if (dashes) {
NSMutableArray *dashPattern = [NSMutableArray array];
for (NSDictionary *dash in dashes) {
if ([dash[@"n"] isEqualToString:@"o"]) {
dashOffset = dash[@"v"];
continue;
}
// TODO DASH PATTERNS
NSDictionary *value = dash[@"v"];
LOTKeyframeGroup *keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
[dashPattern addObject:keyframeGroup];
}
_lineDashPattern = dashPattern;
}
if (dashOffset) {
_dashOffset = [[LOTKeyframeGroup alloc] initWithData:dashOffset];
}
}
@end
//
// LOTShapeTransform.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
#import "LOTKeyframe.h"
@interface LOTShapeTransform : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) LOTKeyframeGroup *position;
@property (nonatomic, readonly) LOTKeyframeGroup *anchor;
@property (nonatomic, readonly) LOTKeyframeGroup *scale;
@property (nonatomic, readonly) LOTKeyframeGroup *rotation;
@property (nonatomic, readonly) LOTKeyframeGroup *opacity;
@end
//
// LOTShapeTransform.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/15/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTShapeTransform.h"
#import "LOTHelpers.h"
@implementation LOTShapeTransform
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *position = jsonDictionary[@"p"];
if (position) {
_position = [[LOTKeyframeGroup alloc] initWithData:position];
}
NSDictionary *anchor = jsonDictionary[@"a"];
if (anchor) {
_anchor = [[LOTKeyframeGroup alloc] initWithData:anchor];
}
NSDictionary *scale = jsonDictionary[@"s"];
if (scale) {
_scale = [[LOTKeyframeGroup alloc] initWithData:scale];
[_scale remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, -100, 100, -1, 1);
}];
}
NSDictionary *rotation = jsonDictionary[@"r"];
if (rotation) {
_rotation = [[LOTKeyframeGroup alloc] initWithData:rotation];
[_rotation remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_DegreesToRadians(inValue);
}];
}
NSDictionary *opacity = jsonDictionary[@"o"];
if (opacity) {
_opacity = [[LOTKeyframeGroup alloc] initWithData:opacity];
[_opacity remapKeyframesWithBlock:^CGFloat(CGFloat inValue) {
return LOT_RemapValue(inValue, 0, 100, 0, 1);
}];
}
NSString *name = jsonDictionary[@"nm"];
NSDictionary *skew = jsonDictionary[@"sk"];
BOOL hasSkew = (skew && [skew[@"k"] isEqual:@0] == NO);
NSDictionary *skewAxis = jsonDictionary[@"sa"];
BOOL hasSkewAxis = (skewAxis && [skewAxis[@"k"] isEqual:@0] == NO);
if (hasSkew || hasSkewAxis) {
NSLog(@"%s: Warning: skew is not supported: %@", __PRETTY_FUNCTION__, name);
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"LOTShapeTransform \"Position: %@ Anchor: %@ Scale: %@ Rotation: %@ Opacity: %@\"", _position.description, _anchor.description, _scale.description, _rotation.description, _opacity.description];
}
@end
//
// LOTShapeTrimPath.h
// LottieAnimator
//
// Created by brandon_withrow on 7/26/16.
// Copyright © 2016 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
@interface LOTShapeTrimPath : NSObject
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
@property (nonatomic, readonly) NSString *keyname;
@property (nonatomic, readonly) LOTKeyframeGroup *start;
@property (nonatomic, readonly) LOTKeyframeGroup *end;
@property (nonatomic, readonly) LOTKeyframeGroup *offset;
@end
//
// LOTShapeTrimPath.m
// LottieAnimator
//
// Created by brandon_withrow on 7/26/16.
// Copyright © 2016 Brandon Withrow. All rights reserved.
//
#import "LOTShapeTrimPath.h"
@implementation LOTShapeTrimPath
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
if (self) {
[self _mapFromJSON:jsonDictionary];
}
return self;
}
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary {
if (jsonDictionary[@"nm"] ) {
_keyname = [jsonDictionary[@"nm"] copy];
}
NSDictionary *start = jsonDictionary[@"s"];
if (start) {
_start = [[LOTKeyframeGroup alloc] initWithData:start];
}
NSDictionary *end = jsonDictionary[@"e"];
if (end) {
_end = [[LOTKeyframeGroup alloc] initWithData:end];
}
NSDictionary *offset = jsonDictionary[@"o"];
if (offset) {
_offset = [[LOTKeyframeGroup alloc] initWithData:offset];
}
}
@end
//
// LOTAnimatedControl.m
// Lottie
//
// Created by brandon_withrow on 8/25/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatedControl.h"
#import "LOTAnimationView_Internal.h"
@implementation LOTAnimatedControl {
UIControlState _priorState;
NSMutableDictionary *_layerMap;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self _commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self _commonInit];
}
return self;
}
- (void)_commonInit {
_animationView = [[LOTAnimationView alloc] init];
_animationView.contentMode = UIViewContentModeScaleAspectFit;
_animationView.userInteractionEnabled = NO;
[self addSubview:_animationView];
_layerMap = [NSMutableDictionary dictionary];
}
- (LOTComposition *)animationComp {
return _animationView.sceneModel;
}
- (void)setAnimationComp:(LOTComposition *)animationComp {
[_animationView setSceneModel:animationComp];
[self checkStateChangedAndUpdate:YES];
}
- (void)setLayerName:(NSString * _Nonnull)layerName forState:(UIControlState)state {
_layerMap[@(state)] = layerName;
[self checkStateChangedAndUpdate:YES];
}
#pragma mark - Setter Overrides
- (void)setEnabled:(BOOL)enabled {
_priorState = self.state;
[super setEnabled:enabled];
[self checkStateChangedAndUpdate:NO];
}
- (void)setSelected:(BOOL)selected {
_priorState = self.state;
[super setSelected:selected];
[self checkStateChangedAndUpdate:NO];
}
- (void)setHighlighted:(BOOL)highlighted {
_priorState = self.state;
[super setHighlighted:highlighted];
[self checkStateChangedAndUpdate:NO];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_priorState = self.state;
[super touchesBegan:touches withEvent:event];
[self checkStateChangedAndUpdate:NO];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
_priorState = self.state;
[super touchesMoved:touches withEvent:event];
[self checkStateChangedAndUpdate:NO];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
_priorState = self.state;
[super touchesEnded:touches withEvent:event];
[self checkStateChangedAndUpdate:NO];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
_priorState = self.state;
[super touchesCancelled:touches withEvent:event];
[self checkStateChangedAndUpdate:NO];
}
- (CGSize)intrinsicContentSize {
return _animationView.intrinsicContentSize;
}
- (void)layoutSubviews {
[super layoutSubviews];
_animationView.frame = self.bounds;
}
- (UIAccessibilityTraits)accessibilityTraits {
return UIAccessibilityTraitButton;
}
- (BOOL)isAccessibilityElement
{
return YES;
}
#pragma mark - Private interface implementation
- (void)checkStateChangedAndUpdate:(BOOL)forceUpdate {
if (self.state == _priorState && !forceUpdate) {
return;
}
_priorState = self.state;
NSString *name = _layerMap[@(self.state)];
if (!name) {
return;
}
CALayer *layer = [_animationView layerForKey:name];
if (!layer) {
return;
}
for (CALayer *child in [_animationView compositionLayers]) {
child.hidden = YES;
}
layer.hidden = NO;
}
@end
//
// LOTAnimatedSwitch.m
// Lottie
//
// Created by brandon_withrow on 8/25/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatedSwitch.h"
#import "LOTAnimationView.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTAnimatedSwitch {
CGFloat _onStartProgress;
CGFloat _onEndProgress;
CGFloat _offStartProgress;
CGFloat _offEndProgress;
CGPoint _touchTrackingStart;
BOOL _on;
BOOL _suppressToggle;
BOOL _toggleToState;
}
/// Convenience method to initialize a control from the Main Bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName {
return [self switchNamed:toggleName inBundle:[NSBundle mainBundle]];
}
/// Convenience method to initialize a control from the specified bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName inBundle:(NSBundle * _Nonnull)bundle {
LOTComposition *composition = [LOTComposition animationNamed:toggleName inBundle:bundle];
LOTAnimatedSwitch *animatedControl = [[self alloc] initWithFrame:CGRectZero];
if (composition) {
[animatedControl setAnimationComp:composition];
animatedControl.bounds = composition.compBounds;
}
return animatedControl;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.accessibilityHint = NSLocalizedString(@"Double tap to toggle setting.", @"Double tap to toggle setting.");
_onStartProgress = 0;
_onEndProgress = 1;
_offStartProgress = 1;
_offEndProgress = 0;
_on = NO;
[self addTarget:self action:@selector(_toggle) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)setAnimationComp:(LOTComposition *)animationComp {
[super setAnimationComp:animationComp];
[self setOn:_on animated:NO];
}
#pragma mark - External Methods
- (void)setProgressRangeForOnState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress {
_onStartProgress = fromProgress;
_onEndProgress = toProgress;
[self setOn:_on animated:NO];
}
- (void)setProgressRangeForOffState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress {
_offStartProgress = fromProgress;
_offEndProgress = toProgress;
[self setOn:_on animated:NO];
}
- (void)setOn:(BOOL)on {
[self setOn:on animated:NO];
}
- (void)setOn:(BOOL)on animated:(BOOL)animated {
_on = on;
CGFloat startProgress = on ? _onStartProgress : _offStartProgress;
CGFloat endProgress = on ? _onEndProgress : _offEndProgress;
CGFloat finalProgress = endProgress;
if (self.animationView.animationProgress < MIN(startProgress, endProgress) ||
self.animationView.animationProgress > MAX(startProgress, endProgress)) {
if (self.animationView.animationProgress != (!_on ? _onEndProgress : _offEndProgress)) {
// Current progress is in the wrong timeline. Switch.
endProgress = on ? _offStartProgress : _onStartProgress;
startProgress = on ? _offEndProgress : _onEndProgress;
}
}
if (finalProgress == self.animationView.animationProgress) {
return;
}
if (animated) {
[self.animationView pause];
[self.animationView playFromProgress:startProgress toProgress:endProgress withCompletion:^(BOOL animationFinished) {
if (animationFinished) {
self.animationView.animationProgress = finalProgress;
}
}];
} else {
self.animationView.animationProgress = endProgress;
}
}
- (NSString *)accessibilityValue {
return self.isOn ? NSLocalizedString(@"On", @"On") : NSLocalizedString(@"Off", @"Off");
}
#pragma mark - Internal Methods
- (void)_toggle {
if (!_suppressToggle) {
[self _toggleAndSendActions];
}
}
- (void)_toggleAndSendActions {
if (self.isEnabled) {
#ifndef TARGET_OS_TV
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[generator impactOccurred];
}
#endif
[self setOn:!_on animated:YES];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super beginTrackingWithTouch:touch withEvent:event];
_suppressToggle = NO;
_touchTrackingStart = [touch locationInView:self];
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
BOOL superContinue = [super continueTrackingWithTouch:touch withEvent:event];
if (!_interactiveGesture) {
return superContinue;
}
CGPoint location = [touch locationInView:self];
CGFloat diff = location.x - _touchTrackingStart.x;
if (LOT_PointDistanceFromPoint(_touchTrackingStart, location) > self.bounds.size.width * 0.25) {
// The touch has moved enough to register as its own gesture. Suppress the touch up toggle.
_suppressToggle = YES;
}
#ifdef __IPHONE_11_0
// Xcode 9+
if (@available(iOS 9.0, *)) {
#else
// Xcode 8-
if ([UIView respondsToSelector:@selector(userInterfaceLayoutDirectionForSemanticContentAttribute:)]) {
#endif
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
diff = diff * -1;
}
}
if (_on) {
diff = diff * -1;
if (diff <= 0) {
self.animationView.animationProgress = _onEndProgress;
_toggleToState = YES;
} else {
diff = MAX(MIN(self.bounds.size.width, diff), 0);
self.animationView.animationProgress = LOT_RemapValue(diff, 0, self.bounds.size.width, _offStartProgress, _offEndProgress);
_toggleToState = (diff / self.bounds.size.width) > 0.5 ? NO : YES;
}
} else {
if (diff <= 0) {
self.animationView.animationProgress = _offEndProgress;
_toggleToState = NO;
} else {
diff = MAX(MIN(self.bounds.size.width, diff), 0);
self.animationView.animationProgress = LOT_RemapValue(diff, 0, self.bounds.size.width, _onStartProgress, _onEndProgress);
_toggleToState = (diff / self.bounds.size.width) > 0.5 ? YES : NO;
}
}
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super endTrackingWithTouch:touch withEvent:event];
if (!_interactiveGesture) {
return;
}
if (_suppressToggle) {
if (_toggleToState != _on) {
[self _toggleAndSendActions];
} else {
[self setOn:_toggleToState animated:YES];
}
}
}
@end
//
// LOTAnimationCache.m
// Lottie
//
// Created by Brandon Withrow on 1/9/17.
// Copyright © 2017 Brandon Withrow. All rights reserved.
//
#import "LOTAnimationCache.h"
const NSInteger kLOTCacheSize = 50;
@implementation LOTAnimationCache {
NSMutableDictionary *animationsCache_;
NSMutableArray *lruOrderArray_;
}
+ (instancetype)sharedCache {
static LOTAnimationCache *sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCache = [[self alloc] init];
});
return sharedCache;
}
- (instancetype)init {
self = [super init];
if (self) {
animationsCache_ = [[NSMutableDictionary alloc] init];
lruOrderArray_ = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key {
if (lruOrderArray_.count >= kLOTCacheSize) {
NSString *oldKey = lruOrderArray_[0];
[animationsCache_ removeObjectForKey:oldKey];
[lruOrderArray_ removeObject:oldKey];
}
[lruOrderArray_ removeObject:key];
[lruOrderArray_ addObject:key];
[animationsCache_ setObject:animation forKey:key];
}
- (LOTComposition *)animationForKey:(NSString *)key {
if (!key) {
return nil;
}
LOTComposition *animation = [animationsCache_ objectForKey:key];
[lruOrderArray_ removeObject:key];
[lruOrderArray_ addObject:key];
return animation;
}
- (void)clearCache {
[animationsCache_ removeAllObjects];
[lruOrderArray_ removeAllObjects];
}
- (void)removeAnimationForKey:(NSString *)key {
[lruOrderArray_ removeObject:key];
[animationsCache_ removeObjectForKey:key];
}
- (void)disableCaching {
[self clearCache];
animationsCache_ = nil;
lruOrderArray_ = nil;
}
@end
//
// LOTAnimationTransitionController.m
// Lottie
//
// Created by Brandon Withrow on 1/18/17.
// Copyright © 2017 Brandon Withrow. All rights reserved.
//
#import "LOTAnimationTransitionController.h"
#import "LOTAnimationView.h"
@implementation LOTAnimationTransitionController {
LOTAnimationView *transitionAnimationView_;
NSString *fromLayerName_;
NSString *toLayerName_;
NSBundle *inBundle_;
BOOL _applyTransform;
}
- (nonnull instancetype)initWithAnimationNamed:(nonnull NSString *)animation
fromLayerNamed:(nullable NSString *)fromLayer
toLayerNamed:(nullable NSString *)toLayer
applyAnimationTransform:(BOOL)applyAnimationTransform {
return [self initWithAnimationNamed:animation
fromLayerNamed:fromLayer
toLayerNamed:toLayer
applyAnimationTransform:applyAnimationTransform
inBundle:[NSBundle mainBundle]];
}
- (instancetype)initWithAnimationNamed:(NSString *)animation
fromLayerNamed:(NSString *)fromLayer
toLayerNamed:(NSString *)toLayer
applyAnimationTransform:(BOOL)applyAnimationTransform
inBundle:(NSBundle *)bundle {
self = [super init];
if (self) {
transitionAnimationView_ = [LOTAnimationView animationNamed:animation inBundle:bundle];
fromLayerName_ = fromLayer;
toLayerName_ = toLayer;
_applyTransform = applyAnimationTransform;
}
return self;
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return transitionAnimationView_.animationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
UIView *toSnapshot = [toVC.view resizableSnapshotViewFromRect:containerView.bounds
afterScreenUpdates:YES
withCapInsets:UIEdgeInsetsZero];
toSnapshot.frame = containerView.bounds;
UIView *fromSnapshot = [fromVC.view resizableSnapshotViewFromRect:containerView.bounds
afterScreenUpdates:NO
withCapInsets:UIEdgeInsetsZero];
fromSnapshot.frame = containerView.bounds;
transitionAnimationView_.frame = containerView.bounds;
transitionAnimationView_.contentMode = UIViewContentModeScaleAspectFill;
[containerView addSubview:transitionAnimationView_];
BOOL crossFadeViews = NO;
if (toLayerName_.length) {
LOTKeypath *toKeypath = [LOTKeypath keypathWithString:toLayerName_];
CGRect convertedBounds = [transitionAnimationView_ convertRect:containerView.bounds toKeypathLayer:toKeypath];
toSnapshot.frame = convertedBounds;
if (_applyTransform) {
[transitionAnimationView_ addSubview:toSnapshot toKeypathLayer:toKeypath];
} else {
[transitionAnimationView_ maskSubview:toSnapshot toKeypathLayer:toKeypath];
}
} else {
[containerView addSubview:toSnapshot];
[containerView sendSubviewToBack:toSnapshot];
toSnapshot.alpha = 0;
crossFadeViews = YES;
}
if (fromLayerName_.length) {
LOTKeypath *fromKeypath = [LOTKeypath keypathWithString:fromLayerName_];
CGRect convertedBounds = [transitionAnimationView_ convertRect:containerView.bounds fromKeypathLayer:fromKeypath];
fromSnapshot.frame = convertedBounds;
if (_applyTransform) {
[transitionAnimationView_ addSubview:fromSnapshot toKeypathLayer:fromKeypath];
} else {
[transitionAnimationView_ maskSubview:fromSnapshot toKeypathLayer:fromKeypath];
}
} else {
[containerView addSubview:fromSnapshot];
[containerView sendSubviewToBack:fromSnapshot];
}
[containerView addSubview:toVC.view];
toVC.view.hidden = YES;
if (crossFadeViews) {
CGFloat duration = transitionAnimationView_.animationDuration * 0.25;
CGFloat delay = (transitionAnimationView_.animationDuration - duration) / 2.f;
[UIView animateWithDuration:duration
delay:delay
options:(UIViewAnimationOptionCurveEaseInOut)
animations:^{
toSnapshot.alpha = 1;
} completion:^(BOOL finished) {
}];
}
[transitionAnimationView_ playWithCompletion:^(BOOL animationFinished) {
toVC.view.hidden = false;
[self->transitionAnimationView_ removeFromSuperview];
[transitionContext completeTransition:animationFinished];
}];
}
@end
//
// LOTAnimationView
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTAnimationView.h"
#import "LOTPlatformCompat.h"
#import "LOTModels.h"
#import "LOTHelpers.h"
#import "LOTAnimationView_Internal.h"
#import "LOTAnimationCache.h"
#import "LOTCompositionContainer.h"
static NSString * const kCompContainerAnimationKey = @"play";
@implementation LOTAnimationView {
LOTCompositionContainer *_compContainer;
NSNumber *_playRangeStartFrame;
NSNumber *_playRangeEndFrame;
CGFloat _playRangeStartProgress;
CGFloat _playRangeEndProgress;
NSBundle *_bundle;
CGFloat _animationProgress;
// Properties for tracking automatic restoration of animation.
BOOL _shouldRestoreStateWhenAttachedToWindow;
LOTAnimationCompletionBlock _completionBlockToRestoreWhenAttachedToWindow;
}
# pragma mark - Convenience Initializers
+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName {
return [self animationNamed:animationName inBundle:[NSBundle mainBundle]];
}
+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
return [[self alloc] initWithModel:comp inBundle:bundle];
}
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON {
return [self animationFromJSON:animationJSON inBundle:[NSBundle mainBundle]];
}
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle {
LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON inBundle:bundle];
return [[self alloc] initWithModel:comp inBundle:bundle];
}
+ (nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath {
LOTComposition *comp = [LOTComposition animationWithFilePath:filePath];
return [[self alloc] initWithModel:comp inBundle:[NSBundle mainBundle]];
}
# pragma mark - Initializers
- (instancetype)initWithContentsOfURL:(NSURL *)url {
self = [self initWithFrame:CGRectZero];
if (self) {
LOTComposition *laScene = [[LOTAnimationCache sharedCache] animationForKey:url.absoluteString];
if (laScene) {
laScene.cacheKey = url.absoluteString;
[self _initializeAnimationContainer];
[self _setupWithSceneModel:laScene];
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSData *animationData = [NSData dataWithContentsOfURL:url];
if (!animationData) {
return;
}
NSError *error;
NSDictionary *animationJSON = [NSJSONSerialization JSONObjectWithData:animationData
options:0 error:&error];
if (error || !animationJSON) {
return;
}
LOTComposition *laScene = [[LOTComposition alloc] initWithJSON:animationJSON withAssetBundle:[NSBundle mainBundle]];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[[LOTAnimationCache sharedCache] addAnimation:laScene forKey:url.absoluteString];
laScene.cacheKey = url.absoluteString;
[self _initializeAnimationContainer];
[self _setupWithSceneModel:laScene];
});
});
}
}
return self;
}
- (instancetype)initWithModel:(LOTComposition *)model inBundle:(NSBundle *)bundle {
self = [self initWithFrame:model.compBounds];
if (self) {
_bundle = bundle;
[self _initializeAnimationContainer];
[self _setupWithSceneModel:model];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self _commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self _commonInit];
}
return self;
}
# pragma mark - Inspectables
- (void)setAnimation:(NSString *)animationName {
_animation = animationName;
[self setAnimationNamed:animationName];
}
# pragma mark - Internal Methods
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- (void)_initializeAnimationContainer {
self.clipsToBounds = YES;
}
- (void)_commonInit {
_animationSpeed = 1;
_animationProgress = 0;
_loopAnimation = NO;
_autoReverseAnimation = NO;
_playRangeEndFrame = nil;
_playRangeStartFrame = nil;
_playRangeEndProgress = 0;
_playRangeStartProgress = 0;
_shouldRasterizeWhenIdle = NO;
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleWillEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
#else
- (void)_initializeAnimationContainer {
self.wantsLayer = YES;
}
- (void)_commonInit {
_animationSpeed = 1;
_animationProgress = 0;
_loopAnimation = NO;
_autoReverseAnimation = NO;
_playRangeEndFrame = nil;
_playRangeStartFrame = nil;
_playRangeEndProgress = 0;
_playRangeStartProgress = 0;
_shouldRasterizeWhenIdle = NO;
}
#endif
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (void)_setupWithSceneModel:(LOTComposition *)model {
if (_sceneModel) {
[self _removeCurrentAnimationIfNecessary];
[self _callCompletionIfNecessary:NO];
[_compContainer removeFromSuperlayer];
_compContainer = nil;
_sceneModel = nil;
[self _commonInit];
}
_sceneModel = model;
_compContainer = [[LOTCompositionContainer alloc] initWithModel:nil inLayerGroup:nil withLayerGroup:_sceneModel.layerGroup withAssestGroup:_sceneModel.assetGroup];
[self.layer addSublayer:_compContainer];
[self _restoreState];
[self setNeedsLayout];
}
- (void)_restoreState {
if (_isAnimationPlaying) {
_isAnimationPlaying = NO;
if (_playRangeStartFrame && _playRangeEndFrame) {
[self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
} else if (_playRangeEndProgress != _playRangeStartProgress) {
[self playFromProgress:_playRangeStartProgress toProgress:_playRangeEndProgress withCompletion:self.completionBlock];
} else {
[self playWithCompletion:self.completionBlock];
}
} else {
self.animationProgress = _animationProgress;
}
}
- (void)_removeCurrentAnimationIfNecessary {
_isAnimationPlaying = NO;
[_compContainer removeAllAnimations];
_compContainer.shouldRasterize = _shouldRasterizeWhenIdle;
}
- (CGFloat)_progressForFrame:(NSNumber *)frame {
if (!_sceneModel) {
return 0;
}
return ((frame.floatValue - _sceneModel.startFrame.floatValue) / (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue));
}
- (NSNumber *)_frameForProgress:(CGFloat)progress {
if (!_sceneModel) {
return @0;
}
return @(((_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue) * progress) + _sceneModel.startFrame.floatValue);
}
- (BOOL)_isSpeedNegative {
// If the animation speed is negative, then we're moving backwards.
return _animationSpeed >= 0;
}
- (void)_handleWindowChanges:(BOOL)hasNewWindow
{
// When this view or its superview is leaving the screen, e.g. a modal is presented or another
// screen is pushed, this method will get called with newWindow value set to nil - indicating that
// this view will be detached from the visible window.
// When a view is detached, animations will stop - but will not automatically resumed when it's
// re-attached back to window, e.g. when the presented modal is dismissed or another screen is
// pop.
if (hasNewWindow) {
// The view is being re-attached, resume animation if needed.
if (_shouldRestoreStateWhenAttachedToWindow) {
_shouldRestoreStateWhenAttachedToWindow = NO;
_isAnimationPlaying = YES;
_completionBlock = _completionBlockToRestoreWhenAttachedToWindow;
_completionBlockToRestoreWhenAttachedToWindow = nil;
[self performSelector:@selector(_restoreState) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
}
} else {
// The view is being detached, capture information that need to be restored later.
if (_isAnimationPlaying) {
LOTAnimationCompletionBlock completion = _completionBlock;
[self pause];
_shouldRestoreStateWhenAttachedToWindow = YES;
_completionBlockToRestoreWhenAttachedToWindow = completion;
_completionBlock = nil;
}
}
}
- (void)_handleWillEnterBackground {
[self _handleWindowChanges: false];
}
- (void)_handleWillEnterForeground {
[self _handleWindowChanges: (self.window != nil)];
}
# pragma mark - Completion Block
- (void)_callCompletionIfNecessary:(BOOL)complete {
if (self.completionBlock) {
LOTAnimationCompletionBlock completion = self.completionBlock;
self.completionBlock = nil;
completion(complete);
}
}
# pragma mark - External Methods
- (void)setAnimationNamed:(nonnull NSString *)animationName {
LOTComposition *comp = [LOTComposition animationNamed:animationName];
[self _initializeAnimationContainer];
[self _setupWithSceneModel:comp];
}
- (void)setAnimationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle {
LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
[self _initializeAnimationContainer];
[self _setupWithSceneModel:comp];
}
- (void)setAnimationFromJSON:(nonnull NSDictionary *)animationJSON {
LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON];
[self _initializeAnimationContainer];
[self _setupWithSceneModel:comp];
}
- (void)setAnimationFromJSON:(NSDictionary *)animationJSON inBundle:(NSBundle *)bundle {
LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON inBundle:bundle];
[self _initializeAnimationContainer];
[self _setupWithSceneModel:comp];
}
# pragma mark - External Methods - Model
- (void)setSceneModel:(LOTComposition *)sceneModel {
[self _setupWithSceneModel:sceneModel];
}
# pragma mark - External Methods - Play Control
- (void)play {
if (!_sceneModel) {
_isAnimationPlaying = YES;
return;
}
[self playFromFrame:_sceneModel.startFrame toFrame:_sceneModel.endFrame withCompletion:nil];
}
- (void)playWithCompletion:(LOTAnimationCompletionBlock)completion {
if (!_sceneModel) {
_isAnimationPlaying = YES;
self.completionBlock = completion;
return;
}
[self playFromFrame:_sceneModel.startFrame toFrame:_sceneModel.endFrame withCompletion:completion];
}
- (void)playToProgress:(CGFloat)progress withCompletion:(nullable LOTAnimationCompletionBlock)completion {
[self playFromProgress:0 toProgress:progress withCompletion:completion];
}
- (void)playFromProgress:(CGFloat)fromStartProgress
toProgress:(CGFloat)toEndProgress
withCompletion:(nullable LOTAnimationCompletionBlock)completion {
if (!_sceneModel) {
_isAnimationPlaying = YES;
self.completionBlock = completion;
_playRangeStartProgress = fromStartProgress;
_playRangeEndProgress = toEndProgress;
return;
}
[self playFromFrame:[self _frameForProgress:fromStartProgress]
toFrame:[self _frameForProgress:toEndProgress]
withCompletion:completion];
}
- (void)playToFrame:(nonnull NSNumber *)toFrame
withCompletion:(nullable LOTAnimationCompletionBlock)completion {
[self playFromFrame:_sceneModel.startFrame toFrame:toFrame withCompletion:completion];
}
- (void)playFromFrame:(nonnull NSNumber *)fromStartFrame
toFrame:(nonnull NSNumber *)toEndFrame
withCompletion:(nullable LOTAnimationCompletionBlock)completion {
if (_isAnimationPlaying) {
return;
}
_playRangeStartFrame = fromStartFrame;
_playRangeEndFrame = toEndFrame;
if (completion) {
self.completionBlock = completion;
}
if (!_sceneModel) {
_isAnimationPlaying = YES;
return;
}
BOOL playingForward = ((_animationSpeed > 0) && (toEndFrame.floatValue > fromStartFrame.floatValue))
|| ((_animationSpeed < 0) && (fromStartFrame.floatValue > toEndFrame.floatValue));
CGFloat leftFrameValue = MIN(fromStartFrame.floatValue, toEndFrame.floatValue);
CGFloat rightFrameValue = MAX(fromStartFrame.floatValue, toEndFrame.floatValue);
NSNumber *currentFrame = [self _frameForProgress:_animationProgress];
currentFrame = @(MAX(MIN(currentFrame.floatValue, rightFrameValue), leftFrameValue));
if (currentFrame.floatValue == rightFrameValue && playingForward) {
currentFrame = @(leftFrameValue);
} else if (currentFrame.floatValue == leftFrameValue && !playingForward) {
currentFrame = @(rightFrameValue);
}
_animationProgress = [self _progressForFrame:currentFrame];
CGFloat currentProgress = _animationProgress * (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue);
CGFloat skipProgress;
if (playingForward) {
skipProgress = currentProgress - leftFrameValue;
} else {
skipProgress = rightFrameValue - currentProgress;
}
NSTimeInterval offset = MAX(0, skipProgress) / _sceneModel.framerate.floatValue;
if (!self.window) {
_shouldRestoreStateWhenAttachedToWindow = YES;
_completionBlockToRestoreWhenAttachedToWindow = self.completionBlock;
self.completionBlock = nil;
} else {
NSTimeInterval duration = (ABS(toEndFrame.floatValue - fromStartFrame.floatValue) / _sceneModel.framerate.floatValue);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"currentFrame"];
animation.speed = _animationSpeed;
animation.fromValue = fromStartFrame;
animation.toValue = toEndFrame;
animation.duration = duration;
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = _loopAnimation ? HUGE_VALF : 1;
animation.autoreverses = _autoReverseAnimation;
animation.delegate = self;
animation.removedOnCompletion = NO;
if (offset != 0) {
CFTimeInterval currentTime = CACurrentMediaTime();
CFTimeInterval currentLayerTime = [self.layer convertTime:currentTime fromLayer:nil];
animation.beginTime = currentLayerTime - (offset * 1 / _animationSpeed);
}
[_compContainer addAnimation:animation forKey:kCompContainerAnimationKey];
_compContainer.shouldRasterize = NO;
}
_isAnimationPlaying = YES;
}
#pragma mark - Other Time Controls
- (void)stop {
_isAnimationPlaying = NO;
if (_sceneModel) {
[self setProgressWithFrame:_sceneModel.startFrame callCompletionIfNecessary:YES];
}
}
- (void)pause {
if (!_sceneModel ||
!_isAnimationPlaying) {
_isAnimationPlaying = NO;
return;
}
NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
[self setProgressWithFrame:frame callCompletionIfNecessary:YES];
}
- (void)setAnimationProgress:(CGFloat)animationProgress {
if (!_sceneModel) {
_animationProgress = animationProgress;
return;
}
[self setProgressWithFrame:[self _frameForProgress:animationProgress] callCompletionIfNecessary:YES];
}
- (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame {
[self setProgressWithFrame:currentFrame callCompletionIfNecessary:YES];
}
- (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame callCompletionIfNecessary:(BOOL)callCompletion {
[self _removeCurrentAnimationIfNecessary];
if (_shouldRestoreStateWhenAttachedToWindow) {
_shouldRestoreStateWhenAttachedToWindow = NO;
self.completionBlock = _completionBlockToRestoreWhenAttachedToWindow;
_completionBlockToRestoreWhenAttachedToWindow = nil;
}
_animationProgress = [self _progressForFrame:currentFrame];
[CATransaction begin];
[CATransaction setDisableActions:YES];
_compContainer.currentFrame = currentFrame;
[_compContainer setNeedsDisplay];
[CATransaction commit];
if (callCompletion) {
[self _callCompletionIfNecessary:NO];
}
}
- (void)setLoopAnimation:(BOOL)loopAnimation {
_loopAnimation = loopAnimation;
if (_isAnimationPlaying && _sceneModel) {
NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
[self setProgressWithFrame:frame callCompletionIfNecessary:NO];
[self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
}
}
- (void)setAnimationSpeed:(CGFloat)animationSpeed {
_animationSpeed = animationSpeed;
if (_isAnimationPlaying && _sceneModel) {
NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
[self setProgressWithFrame:frame callCompletionIfNecessary:NO];
[self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
}
}
- (void)forceDrawingUpdate {
[self _layoutAndForceUpdate];
}
# pragma mark - External Methods - Idle Rasterization
- (void)setShouldRasterizeWhenIdle:(BOOL)shouldRasterize {
_shouldRasterizeWhenIdle = shouldRasterize;
if (!_isAnimationPlaying) {
_compContainer.shouldRasterize = _shouldRasterizeWhenIdle;
}
}
# pragma mark - External Methods - Cache
- (void)setCacheEnable:(BOOL)cacheEnable {
_cacheEnable = cacheEnable;
if (!self.sceneModel.cacheKey) {
return;
}
if (cacheEnable) {
[[LOTAnimationCache sharedCache] addAnimation:_sceneModel forKey:self.sceneModel.cacheKey];
} else {
[[LOTAnimationCache sharedCache] removeAnimationForKey:self.sceneModel.cacheKey];
}
}
# pragma mark - External Methods - Interactive Controls
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath {
[_compContainer setValueDelegate:delegate forKeypath:keypath];
[self _layoutAndForceUpdate];
}
- (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath {
return [_compContainer keysForKeyPath:keypath];
}
- (CGPoint)convertPoint:(CGPoint)point
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
return [_compContainer convertPoint:point toKeypathLayer:keypath withParentLayer:self.layer];
}
- (CGRect)convertRect:(CGRect)rect
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
return [_compContainer convertRect:rect toKeypathLayer:keypath withParentLayer:self.layer];
}
- (CGPoint)convertPoint:(CGPoint)point
fromKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
return [_compContainer convertPoint:point fromKeypathLayer:keypath withParentLayer:self.layer];
}
- (CGRect)convertRect:(CGRect)rect
fromKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
return [_compContainer convertRect:rect fromKeypathLayer:keypath withParentLayer:self.layer];
}
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- (void)addSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
CGRect viewRect = view.frame;
LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
view.frame = view.bounds;
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[wrapperView addSubview:view];
[self addSubview:wrapperView];
[_compContainer addSublayer:wrapperView.layer toKeypathLayer:keypath];
}
- (void)maskSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layoutAndForceUpdate];
CGRect viewRect = view.frame;
LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
view.frame = view.bounds;
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[wrapperView addSubview:view];
[self addSubview:wrapperView];
[_compContainer maskSublayer:wrapperView.layer toKeypathLayer:keypath];
}
#else
- (void)addSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layout];
CGRect viewRect = view.frame;
LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
view.frame = view.bounds;
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[wrapperView addSubview:view];
[self addSubview:wrapperView];
[_compContainer addSublayer:wrapperView.layer toKeypathLayer:keypath];
}
- (void)maskSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath {
[self _layout];
CGRect viewRect = view.frame;
LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
view.frame = view.bounds;
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[wrapperView addSubview:view];
[self addSubview:wrapperView];
[_compContainer maskSublayer:wrapperView.layer toKeypathLayer:keypath];
}
#endif
# pragma mark - Semi-Private Methods
- (CALayer * _Nullable)layerForKey:(NSString * _Nonnull)keyname {
return _compContainer.childMap[keyname];
}
- (NSArray * _Nonnull)compositionLayers {
return _compContainer.childLayers;
}
# pragma mark - Getters and Setters
- (CGFloat)animationDuration {
if (!_sceneModel) {
return 0;
}
CAAnimation *play = [_compContainer animationForKey:kCompContainerAnimationKey];
if (play) {
return play.duration;
}
return (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue) / _sceneModel.framerate.floatValue;
}
- (CGFloat)animationProgress {
if (_isAnimationPlaying &&
_compContainer.presentationLayer) {
CGFloat activeProgress = [self _progressForFrame:[(LOTCompositionContainer *)_compContainer.presentationLayer currentFrame]];
return activeProgress;
}
return _animationProgress;
}
# pragma mark - Overrides
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#define LOTViewContentMode UIViewContentMode
#define LOTViewContentModeScaleToFill UIViewContentModeScaleToFill
#define LOTViewContentModeScaleAspectFit UIViewContentModeScaleAspectFit
#define LOTViewContentModeScaleAspectFill UIViewContentModeScaleAspectFill
#define LOTViewContentModeRedraw UIViewContentModeRedraw
#define LOTViewContentModeCenter UIViewContentModeCenter
#define LOTViewContentModeTop UIViewContentModeTop
#define LOTViewContentModeBottom UIViewContentModeBottom
#define LOTViewContentModeLeft UIViewContentModeLeft
#define LOTViewContentModeRight UIViewContentModeRight
#define LOTViewContentModeTopLeft UIViewContentModeTopLeft
#define LOTViewContentModeTopRight UIViewContentModeTopRight
#define LOTViewContentModeBottomLeft UIViewContentModeBottomLeft
#define LOTViewContentModeBottomRight UIViewContentModeBottomRight
- (CGSize)intrinsicContentSize {
if (!_sceneModel) {
return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
}
return _sceneModel.compBounds.size;
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
if (self.superview == nil) {
[self _callCompletionIfNecessary:NO];
}
}
- (void)willMoveToWindow:(UIWindow *)newWindow {
[self _handleWindowChanges:(newWindow != nil)];
}
- (void)didMoveToWindow {
_compContainer.rasterizationScale = self.window.screen.scale;
}
- (void)setContentMode:(LOTViewContentMode)contentMode {
[super setContentMode:contentMode];
[self setNeedsLayout];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self _layout];
}
#else
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
[self _handleWindowChanges:(newWindow != nil)];
}
- (void)viewDidMoveToWindow {
_compContainer.rasterizationScale = self.window.screen.backingScaleFactor;
}
- (void)setCompletionBlock:(LOTAnimationCompletionBlock)completionBlock {
if (completionBlock) {
_completionBlock = ^(BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(finished); });
};
}
else {
_completionBlock = nil;
}
}
- (void)setContentMode:(LOTViewContentMode)contentMode {
_contentMode = contentMode;
[self setNeedsLayout];
}
- (void)setNeedsLayout {
self.needsLayout = YES;
}
- (BOOL)isFlipped {
return YES;
}
- (BOOL)wantsUpdateLayer {
return YES;
}
- (void)layout {
[super layout];
[self _layout];
}
#endif
- (void)_layoutAndForceUpdate {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self _layout];
[_compContainer displayWithFrame:_compContainer.currentFrame forceUpdate:YES];
[CATransaction commit];
}
- (void)_layout {
CGPoint centerPoint = LOT_RectGetCenterPoint(self.bounds);
CATransform3D xform;
if (self.contentMode == LOTViewContentModeScaleToFill) {
CGSize scaleSize = CGSizeMake(self.bounds.size.width / self.sceneModel.compBounds.size.width,
self.bounds.size.height / self.sceneModel.compBounds.size.height);
xform = CATransform3DMakeScale(scaleSize.width, scaleSize.height, 1);
} else if (self.contentMode == LOTViewContentModeScaleAspectFit) {
CGFloat compAspect = self.sceneModel.compBounds.size.width / self.sceneModel.compBounds.size.height;
CGFloat viewAspect = self.bounds.size.width / self.bounds.size.height;
BOOL scaleWidth = compAspect > viewAspect;
CGFloat dominantDimension = scaleWidth ? self.bounds.size.width : self.bounds.size.height;
CGFloat compDimension = scaleWidth ? self.sceneModel.compBounds.size.width : self.sceneModel.compBounds.size.height;
CGFloat scale = dominantDimension / compDimension;
xform = CATransform3DMakeScale(scale, scale, 1);
} else if (self.contentMode == LOTViewContentModeScaleAspectFill) {
CGFloat compAspect = self.sceneModel.compBounds.size.width / self.sceneModel.compBounds.size.height;
CGFloat viewAspect = self.bounds.size.width / self.bounds.size.height;
BOOL scaleWidth = compAspect < viewAspect;
CGFloat dominantDimension = scaleWidth ? self.bounds.size.width : self.bounds.size.height;
CGFloat compDimension = scaleWidth ? self.sceneModel.compBounds.size.width : self.sceneModel.compBounds.size.height;
CGFloat scale = dominantDimension / compDimension;
xform = CATransform3DMakeScale(scale, scale, 1);
} else {
xform = CATransform3DIdentity;
}
[CATransaction begin];
[CATransaction setDisableActions:YES];
_compContainer.transform = CATransform3DIdentity;
_compContainer.bounds = _sceneModel.compBounds;
_compContainer.viewportBounds = _sceneModel.compBounds;
_compContainer.transform = xform;
_compContainer.position = centerPoint;
[CATransaction commit];
}
# pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)complete {
if ([_compContainer animationForKey:kCompContainerAnimationKey] == anim &&
[anim isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *playAnimation = (CABasicAnimation *)anim;
NSNumber *frame = _compContainer.presentationLayer.currentFrame;
if (complete) {
// Set the final frame based on the animation to/from values. If playing forward, use the
// toValue otherwise we want to end on the fromValue.
frame = [self _isSpeedNegative] ? (NSNumber *)playAnimation.toValue : (NSNumber *)playAnimation.fromValue;
}
[self _removeCurrentAnimationIfNecessary];
[self setProgressWithFrame:frame callCompletionIfNecessary:NO];
[self _callCompletionIfNecessary:complete];
}
}
# pragma mark - DEPRECATED
- (void)addSubview:(nonnull LOTView *)view
toLayerNamed:(nonnull NSString *)layer
applyTransform:(BOOL)applyTransform {
NSLog(@"%s: Function is DEPRECATED. Please use addSubview:forKeypathLayer:", __PRETTY_FUNCTION__);
LOTKeypath *keypath = [LOTKeypath keypathWithString:layer];
if (applyTransform) {
[self addSubview:view toKeypathLayer:keypath];
} else {
[self maskSubview:view toKeypathLayer:keypath];
}
}
- (CGRect)convertRect:(CGRect)rect
toLayerNamed:(NSString *_Nullable)layerName {
NSLog(@"%s: Function is DEPRECATED. Please use convertRect:forKeypathLayer:", __PRETTY_FUNCTION__);
LOTKeypath *keypath = [LOTKeypath keypathWithString:layerName];
return [self convertRect:rect toKeypathLayer:keypath];
}
- (void)setValue:(nonnull id)value
forKeypath:(nonnull NSString *)keypath
atFrame:(nullable NSNumber *)frame {
NSLog(@"%s: Function is DEPRECATED and no longer functional. Please use setValueCallback:forKeypath:", __PRETTY_FUNCTION__);
}
- (void)logHierarchyKeypaths {
NSArray *keypaths = [self keysForKeyPath:[LOTKeypath keypathWithString:@"**"]];
for (NSString *keypath in keypaths) {
NSLog(@"%@", keypath);
}
}
@end
//
// LOTAnimationView_Internal.h
// Lottie
//
// Created by Brandon Withrow on 12/7/16.
// Copyright © 2016 Brandon Withrow. All rights reserved.
//
#import "LOTAnimationView.h"
typedef enum : NSUInteger {
LOTConstraintTypeAlignToBounds,
LOTConstraintTypeAlignToLayer,
LOTConstraintTypeNone
} LOTConstraintType;
@interface LOTAnimationView () <CAAnimationDelegate>
- (CALayer * _Nullable)layerForKey:(NSString * _Nonnull)keyname;
- (NSArray * _Nonnull)compositionLayers;
@end
//
// LOTBlockCallback.m
// Lottie
//
// Created by brandon_withrow on 12/15/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTBlockCallback.h"
@implementation LOTColorBlockCallback
+ (instancetype)withBlock:(LOTColorValueCallbackBlock)block {
LOTColorBlockCallback *colorCallback = [[self alloc] init];
colorCallback.callback = block;
return colorCallback;
}
- (CGColorRef)colorForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor currentColor:(CGColorRef)interpolatedColor {
return self.callback(currentFrame, startKeyframe, endKeyframe, interpolatedProgress, startColor, endColor, interpolatedColor);
}
@end
@implementation LOTNumberBlockCallback
+ (instancetype)withBlock:(LOTNumberValueCallbackBlock)block {
LOTNumberBlockCallback *numberCallback = [[self alloc] init];
numberCallback.callback = block;
return numberCallback;
}
- (CGFloat)floatValueForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startValue:(CGFloat)startValue endValue:(CGFloat)endValue currentValue:(CGFloat)interpolatedValue {
return self.callback(currentFrame, startKeyframe, endKeyframe, interpolatedProgress, startValue, endValue, interpolatedValue);
}
@end
@implementation LOTPointBlockCallback
+ (instancetype)withBlock:(LOTPointValueCallbackBlock)block {
LOTPointBlockCallback *callback = [[self alloc] init];
callback.callback = block;
return callback;
}
- (CGPoint)pointForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint currentPoint:(CGPoint)interpolatedPoint {
return self.callback(currentFrame, startKeyframe, endKeyframe, interpolatedProgress, startPoint, endPoint, interpolatedPoint);
}
@end
@implementation LOTSizeBlockCallback
+ (instancetype)withBlock:(LOTSizeValueCallbackBlock)block {
LOTSizeBlockCallback *callback = [[self alloc] init];
callback.callback = block;
return callback;
}
- (CGSize)sizeForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startSize:(CGSize)startSize endSize:(CGSize)endSize currentSize:(CGSize)interpolatedSize {
return self.callback(currentFrame, startKeyframe, endKeyframe, interpolatedProgress, startSize, endSize, interpolatedSize);
}
@end
@implementation LOTPathBlockCallback
+ (instancetype)withBlock:(LOTPathValueCallbackBlock)block {
LOTPathBlockCallback *callback = [[self alloc] init];
callback.callback = block;
return callback;
}
- (CGPathRef)pathForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress {
return self.callback(currentFrame, startKeyframe, endKeyframe, interpolatedProgress);
}
@end
//
// LOTCacheProvider.m
// Lottie
//
// Created by punmy on 2017/7/8.
//
//
#import "LOTCacheProvider.h"
@implementation LOTCacheProvider
static id<LOTImageCache> _imageCache;
+ (id<LOTImageCache>)imageCache {
return _imageCache;
}
+ (void)setImageCache:(id<LOTImageCache>)cache {
_imageCache = cache;
}
@end
//
// LOTScene.m
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import "LOTComposition.h"
#import "LOTLayer.h"
#import "LOTAssetGroup.h"
#import "LOTLayerGroup.h"
#import "LOTAnimationCache.h"
@implementation LOTComposition
# pragma mark - Convenience Initializers
+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName {
return [self animationNamed:animationName inBundle:[NSBundle mainBundle]];
}
+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
if (!animationName) {
return nil;
}
NSArray *components = [animationName componentsSeparatedByString:@"."];
animationName = components.firstObject;
LOTComposition *comp = [[LOTAnimationCache sharedCache] animationForKey:animationName];
if (comp) {
return comp;
}
NSError *error;
NSString *filePath = [bundle pathForResource:animationName ofType:@"json"];
NSData *jsonData = [[NSData alloc] initWithContentsOfFile:filePath];
if (@available(iOS 9.0, *)) {
if (!jsonData) {
jsonData = [[NSDataAsset alloc] initWithName:animationName bundle:bundle].data;
}
}
NSDictionary *JSONObject = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData
options:0 error:&error] : nil;
if (JSONObject && !error) {
LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:bundle];
[[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
laScene.cacheKey = animationName;
return laScene;
}
NSLog(@"%s: Animation Not Found", __PRETTY_FUNCTION__);
return nil;
}
+ (nullable instancetype)animationWithFilePath:(nonnull NSString *)filePath {
NSString *animationName = filePath;
LOTComposition *comp = [[LOTAnimationCache sharedCache] animationForKey:animationName];
if (comp) {
return comp;
}
NSError *error;
NSData *jsonData = [[NSData alloc] initWithContentsOfFile:filePath];
NSDictionary *JSONObject = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData
options:0 error:&error] : nil;
if (JSONObject && !error) {
LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:[NSBundle mainBundle]];
laScene.rootDirectory = [filePath stringByDeletingLastPathComponent];
[[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
laScene.cacheKey = animationName;
return laScene;
}
NSLog(@"%s: Animation Not Found", __PRETTY_FUNCTION__);
return nil;
}
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON {
return [self animationFromJSON:animationJSON inBundle:[NSBundle mainBundle]];
}
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle {
return [[self alloc] initWithJSON:animationJSON withAssetBundle:bundle];
}
#pragma mark - Initializer
- (instancetype _Nonnull)initWithJSON:(NSDictionary * _Nullable)jsonDictionary
withAssetBundle:(NSBundle * _Nullable)bundle {
self = [super init];
if (self) {
if (jsonDictionary) {
[self _mapFromJSON:jsonDictionary withAssetBundle:bundle];
}
}
return self;
}
#pragma mark - Internal Methods
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
withAssetBundle:(NSBundle *)bundle {
NSNumber *width = jsonDictionary[@"w"];
NSNumber *height = jsonDictionary[@"h"];
if (width && height) {
CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
_compBounds = bounds;
}
_startFrame = [jsonDictionary[@"ip"] copy];
_endFrame = [jsonDictionary[@"op"] copy];
_framerate = [jsonDictionary[@"fr"] copy];
if (_startFrame && _endFrame && _framerate) {
NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
_timeDuration = timeDuration;
}
NSArray *assetArray = jsonDictionary[@"assets"];
if (assetArray.count) {
_assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
}
NSArray *layersJSON = jsonDictionary[@"layers"];
if (layersJSON) {
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
withAssetGroup:_assetGroup
withFramerate:_framerate];
}
[_assetGroup finalizeInitializationWithFramerate:_framerate];
}
- (void)setRootDirectory:(NSString *)rootDirectory {
_rootDirectory = rootDirectory;
self.assetGroup.rootDirectory = rootDirectory;
}
@end
//
// LOTInterpolatorCallback.m
// Lottie
//
// Created by brandon_withrow on 1/5/18.
// Copyright © 2018 Airbnb. All rights reserved.
//
#import "LOTInterpolatorCallback.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTFloatInterpolatorCallback
+ (instancetype _Nonnull)withFromFloat:(CGFloat)fromFloat toFloat:(CGFloat)toFloat {
LOTFloatInterpolatorCallback *interpolator = [[self alloc] init];
interpolator.fromFloat = fromFloat;
interpolator.toFloat = toFloat;
return interpolator;
}
- (CGFloat)floatValueForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startValue:(CGFloat)startValue endValue:(CGFloat)endValue currentValue:(CGFloat)interpolatedValue {
return LOT_RemapValue(self.currentProgress, 0, 1, self.fromFloat, self.toFloat);
}
@end
@implementation LOTPointInterpolatorCallback
+ (instancetype _Nonnull)withFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint {
LOTPointInterpolatorCallback *interpolator = [[self alloc] init];
interpolator.fromPoint = fromPoint;
interpolator.toPoint = toPoint;
return interpolator;
}
- (CGPoint)pointForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint currentPoint:(CGPoint)interpolatedPoint {
return LOT_PointInLine(self.fromPoint, self.toPoint, self.currentProgress);
}
@end
@implementation LOTSizeInterpolatorCallback
+ (instancetype)withFromSize:(CGSize)fromSize toSize:(CGSize)toSize {
LOTSizeInterpolatorCallback *interpolator = [[self alloc] init];
interpolator.fromSize = fromSize;
interpolator.toSize = toSize;
return interpolator;
}
- (CGSize)sizeForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startSize:(CGSize)startSize endSize:(CGSize)endSize currentSize:(CGSize)interpolatedSize {
CGPoint from = CGPointMake(self.fromSize.width, self.fromSize.height);
CGPoint to = CGPointMake(self.toSize.width, self.toSize.height);
CGPoint returnPoint = LOT_PointInLine(from, to, self.currentProgress);
return CGSizeMake(returnPoint.x, returnPoint.y);
}
@end
//
// LOTKeypath.m
// Lottie_iOS
//
// Created by brandon_withrow on 12/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTKeypath.h"
NSString *const kLOTKeypathEnd = @"LOTENDKEYPATH";
@implementation LOTKeypath {
NSInteger _currentDepth;
NSMutableArray<NSNumber *> *_fuzzyDepthStack;
NSMutableArray *_currentStack;
NSArray *_keys;
NSMutableDictionary *_searchResults;
}
+ (nonnull LOTKeypath *)keypathWithString:(nonnull NSString *)keypath {
return [[self alloc] initWithKeys:[keypath componentsSeparatedByString:@"."]];
}
+ (nonnull LOTKeypath *)keypathWithKeys:(nonnull NSString *)firstKey, ... {
NSMutableArray *keys = [NSMutableArray array];
va_list args;
va_start(args, firstKey);
for (NSString *arg = firstKey; arg != nil; arg = va_arg(args, NSString*))
{
[keys addObject:arg];
}
va_end(args);
return [[self alloc] initWithKeys:keys];
}
- (instancetype)initWithKeys:(NSArray *)keys {
self = [super init];
if (self) {
_keys = [NSArray arrayWithArray:keys];
NSMutableString *absolutePath = [NSMutableString string];
for (int i = 0; i < _keys.count; i++) {
if (i > 0) {
[absolutePath appendString:@"."];
}
[absolutePath appendString:_keys[i]];
}
_currentStack = [NSMutableArray array];
_absoluteKeypath = absolutePath;
_currentDepth = 0;
_fuzzyDepthStack = [NSMutableArray array];
_searchResults = [NSMutableDictionary dictionary];
}
return self;
}
- (BOOL)pushKey:(nonnull NSString *)key {
if (_currentDepth == _keys.count &&
self.hasFuzzyWildcard == NO) {
return NO;
}
NSString *current = self.currentKey;
if (self.hasWildcard ||
[current isEqualToString:key]) {
[_currentStack addObject:[key copy]];
_currentDepth ++;
if (self.hasFuzzyWildcard) {
[_fuzzyDepthStack addObject:@(_currentDepth)];
}
return YES;
} else if (self.hasFuzzyWildcard) {
[_currentStack addObject:[key copy]];
return YES;
}
return NO;
}
- (void)popKey {
if (_currentDepth == 0) {
return;
}
NSInteger stackCount = _currentStack.count;
[_currentStack removeLastObject];
if (self.hasFuzzyWildcard ) {
if (stackCount == _fuzzyDepthStack.lastObject.integerValue) {
[_fuzzyDepthStack removeLastObject];
} else {
return;
}
}
_currentDepth --;
}
- (void)popToRootKey {
_currentDepth = 0;
[_currentStack removeAllObjects];
[_fuzzyDepthStack removeAllObjects];
}
- (NSString *)currentKey {
if (_currentDepth == _keys.count) {
return kLOTKeypathEnd;
}
return _keys[_currentDepth];
}
- (NSString *)currentKeyPath {
return [_currentStack componentsJoinedByString:@"."];
}
- (BOOL)hasWildcard {
if (_currentDepth == _keys.count) {
return NO;
}
return ([_keys[_currentDepth] isEqualToString:@"**"] ||
[_keys[_currentDepth] isEqualToString:@"*"]);
}
- (BOOL)hasFuzzyWildcard {
if (_currentDepth == 0 ||
_currentDepth > _keys.count) {
return NO;
}
return [_keys[_currentDepth - 1] isEqualToString:@"**"];
}
- (BOOL)endOfKeypath {
return (_currentDepth == _keys.count);
}
- (void)addSearchResultForCurrentPath:(id _Nonnull)result {
[_searchResults setObject:result forKey:self.currentKeyPath];
}
- (NSDictionary *)searchResults {
return _searchResults;
}
@end
//
// LOTValueCallback.m
// Lottie
//
// Created by brandon_withrow on 12/15/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueCallback.h"
@implementation LOTColorValueCallback
+ (instancetype _Nonnull)withCGColor:(CGColorRef _Nonnull)color {
LOTColorValueCallback *colorCallback = [[self alloc] init];
colorCallback.colorValue = color;
return colorCallback;
}
- (CGColorRef)colorForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor currentColor:(CGColorRef)interpolatedColor {
return self.colorValue;
}
@end
@implementation LOTNumberValueCallback
+ (instancetype _Nonnull)withFloatValue:(CGFloat)numberValue {
LOTNumberValueCallback *numberCallback = [[self alloc] init];
numberCallback.numberValue = numberValue;
return numberCallback;
}
- (CGFloat)floatValueForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startValue:(CGFloat)startValue endValue:(CGFloat)endValue currentValue:(CGFloat)interpolatedValue {
return self.numberValue;
}
@end
@implementation LOTPointValueCallback
+ (instancetype _Nonnull)withPointValue:(CGPoint)pointValue {
LOTPointValueCallback *callback = [[self alloc] init];
callback.pointValue = pointValue;
return callback;
}
- (CGPoint)pointForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint currentPoint:(CGPoint)interpolatedPoint {
return self.pointValue;
}
@end
@implementation LOTSizeValueCallback
+ (instancetype _Nonnull)withPointValue:(CGSize)sizeValue {
LOTSizeValueCallback *callback = [[self alloc] init];
callback.sizeValue = sizeValue;
return callback;
}
- (CGSize)sizeForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startSize:(CGSize)startSize endSize:(CGSize)endSize currentSize:(CGSize)interpolatedSize {
return self.sizeValue;
}
@end
@implementation LOTPathValueCallback
+ (instancetype _Nonnull)withCGPath:(CGPathRef _Nonnull)path {
LOTPathValueCallback *callback = [[self alloc] init];
callback.pathValue = path;
return callback;
}
- (CGPathRef)pathForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress {
return self.pathValue;
}
@end
//
// LOTAnimatedControl.h
// Lottie
//
// Created by brandon_withrow on 8/25/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <UIKit/UIKit.h>
@class LOTAnimationView;
@class LOTComposition;
@interface LOTAnimatedControl : UIControl
// This class is a base class that is intended to be subclassed
/**
* Map a specific animation layer to a control state.
* When the state is set all layers will be hidden except the specified layer.
**/
- (void)setLayerName:(NSString * _Nonnull)layerName forState:(UIControlState)state;
@property (nonatomic, strong, readonly, nonnull) LOTAnimationView *animationView;
@property (nonatomic, strong, nullable) LOTComposition *animationComp;
@end
//
// LOTAnimatedSwitch.h
// Lottie
//
// Created by brandon_withrow on 8/25/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatedControl.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTAnimatedSwitch : LOTAnimatedControl
/// Convenience method to initialize a control from the Main Bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName;
/// Convenience method to initialize a control from the specified bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName inBundle:(NSBundle * _Nonnull)bundle;
/// The ON/OFF state of the control. Setting will toggle without animation
@property (nonatomic, getter=isOn) BOOL on;
/// Enable interactive sliding gesture for toggle
@property (nonatomic) BOOL interactiveGesture;
/// Set the state of the control with animation
- (void)setOn:(BOOL)on animated:(BOOL)animated; // does not send action
/// Styling
/**
* Sets the animation play range for the ON state animation.
* fromProgress is the start of the animation
* toProgress is the end of the animation and also the ON static state
* Defaults 0-1
**/
- (void)setProgressRangeForOnState:(CGFloat)fromProgress
toProgress:(CGFloat)toProgress NS_SWIFT_NAME(setProgressRangeForOnState(fromProgress:toProgress:));
/**
* Sets the animation play range for the OFF state animation.
* fromProgress is the start of the animation
* toProgress is the end of the animation and also the OFF static state
* Defaults 1-0
**/
- (void)setProgressRangeForOffState:(CGFloat)fromProgress
toProgress:(CGFloat)toProgress NS_SWIFT_NAME(setProgressRangeForOffState(fromProgress:toProgress:));
@end
NS_ASSUME_NONNULL_END
//
// LOTAnimationCache.h
// Lottie
//
// Created by Brandon Withrow on 1/9/17.
// Copyright © 2017 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class LOTComposition;
@interface LOTAnimationCache : NSObject
/// Global Cache
+ (instancetype)sharedCache;
/// Adds animation to the cache
- (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key;
/// Returns animation from cache.
- (LOTComposition * _Nullable)animationForKey:(NSString *)key;
/// Removes a specific animation from the cache
- (void)removeAnimationForKey:(NSString *)key;
/// Clears Everything from the Cache
- (void)clearCache;
/// Disables Caching Animation Model Objects
- (void)disableCaching;
@end
NS_ASSUME_NONNULL_END
//
// LOTAnimationTransitionController.h
// Lottie
//
// Created by Brandon Withrow on 1/18/17.
// Copyright © 2017 Brandon Withrow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
/** LOTAnimationTransitionController
*
* This class creates a custom UIViewController transition animation
* using a Lottie animation to transition between two view controllers
* The transition can use custom defined layers in After Effects for to/from
*
* When referencing After Effects layers the animator masks or transforms the to/from viewController
* with the referenced layer.
*
*/
@interface LOTAnimationTransitionController : NSObject <UIViewControllerAnimatedTransitioning>
/**
The initializer to create a new transition animation.
@param animation The name of the Lottie Animation to load for the transition
@param fromLayer The name of the custom layer to mask the fromVC screenshot with.
If no layer is specified then the screenshot is added behind the Lottie Animation
@param toLayer The name of the custom layer to mask the toVC screenshot with.
If no layer is specified then the screenshot is added behind the Lottie Animation
and a fade transition is performed along with the Lottie animation.
@param applyAnimationTransform A boolean that determines if the custom layer should
have the transform animation from the After Effects layer applied to it. If NO the
layer will be masked by the After Effects Layer
*/
- (nonnull instancetype)initWithAnimationNamed:(nonnull NSString *)animation
fromLayerNamed:(nullable NSString *)fromLayer
toLayerNamed:(nullable NSString *)toLayer
applyAnimationTransform:(BOOL)applyAnimationTransform;
/**
The initializer to create a new transition animation.
@param animation The name of the Lottie Animation to load for the transition
@param fromLayer The name of the custom layer to mask the fromVC screenshot with.
If no layer is specified then the screenshot is added behind the Lottie Animation
@param toLayer The name of the custom layer to mask the toVC screenshot with.
If no layer is specified then the screenshot is added behind the Lottie Animation
and a fade transition is performed along with the Lottie animation.
@param applyAnimationTransform A boolean that determines if the custom layer should
have the transform animation from the After Effects layer applied to it. If NO the
layer will be masked by the After Effects Layer
@param bundle custom bundle to load animation and images, if no bundle is specified will load
from mainBundle
*/
- (instancetype _Nonnull)initWithAnimationNamed:(NSString *_Nonnull)animation
fromLayerNamed:(NSString *_Nullable)fromLayer
toLayerNamed:(NSString *_Nullable)toLayer
applyAnimationTransform:(BOOL)applyAnimationTransform
inBundle:(NSBundle *_Nonnull)bundle;
@end
//
// LOTAnimationView
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
// Dream Big.
#import <Foundation/Foundation.h>
#import "LOTAnimationView_Compat.h"
#import "LOTComposition.h"
#import "LOTKeypath.h"
#import "LOTValueDelegate.h"
typedef void (^LOTAnimationCompletionBlock)(BOOL animationFinished);
@interface LOTAnimationView : LOTView
/// Load animation by name from the default bundle, Images are also loaded from the bundle
+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:));
/// Loads animation by name from specified bundle, Images are also loaded from the bundle
+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle NS_SWIFT_NAME(init(name:bundle:));
/// Creates an animation from the deserialized JSON Dictionary
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
/// Loads an animation from a specific file path. WARNING Do not use a web URL for file path.
+ (nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath NS_SWIFT_NAME(init(filePath:));
/// Creates an animation from the deserialized JSON Dictionary, images are loaded from the specified bundle
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(init(json:bundle:));
/// Creates an animation from the LOTComposition, images are loaded from the specified bundle
- (nonnull instancetype)initWithModel:(nullable LOTComposition *)model inBundle:(nullable NSBundle *)bundle;
/// Loads animation asynchronously from the specified URL
- (nonnull instancetype)initWithContentsOfURL:(nonnull NSURL *)url;
/// Set animation name from Interface Builder
@property (nonatomic, strong) IBInspectable NSString * _Nullable animation;
/// Load animation by name from the default bundle. Use when loading LOTAnimationView via Interface Builder.
- (void)setAnimationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(setAnimation(named:));
/// Load animation by name from a specific bundle.
- (void)setAnimationNamed:(nonnull NSString *)animationName inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(setAnimation(named:bundle:));
/// Load animation from a JSON dictionary
- (void)setAnimationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(setAnimation(json:));
/// Load animation from a JSON dictionary from a specific bundle
- (void)setAnimationFromJSON:(nonnull NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(setAnimation(json:bundle:));
/// Flag is YES when the animation is playing
@property (nonatomic, readonly) BOOL isAnimationPlaying;
/// Tells the animation to loop indefinitely. Defaults to NO.
@property (nonatomic, assign) BOOL loopAnimation;
/// The animation will play forward and then backwards if loopAnimation is also YES
@property (nonatomic, assign) BOOL autoReverseAnimation;
/// Sets a progress from 0 - 1 of the animation. If the animation is playing it will stop and the completion block will be called.
/// The current progress of the animation in absolute time.
/// e.g. a value of 0.75 always represents the same point in the animation, regardless of positive
/// or negative speed.
@property (nonatomic, assign) CGFloat animationProgress;
/// Sets the speed of the animation. Accepts a negative value for reversing animation.
@property (nonatomic, assign) CGFloat animationSpeed;
/// Read only of the duration in seconds of the animation at speed of 1
@property (nonatomic, readonly) CGFloat animationDuration;
/// Enables or disables caching of the backing animation model. Defaults to YES
@property (nonatomic, assign) BOOL cacheEnable;
/// Sets a completion block to call when the animation has completed
@property (nonatomic, copy, nullable) LOTAnimationCompletionBlock completionBlock;
/// Set the animation data
@property (nonatomic, strong, nullable) LOTComposition *sceneModel;
/// Sets sholdRasterize to YES on the animation layer to improve compositioning performance when not animating.
/// Note this will not produce crisp results at resolutions above the animations set resolution.
/// Defaults to NO
@property (nonatomic, assign) BOOL shouldRasterizeWhenIdle;
/*
* Plays the animation from its current position to a specific progress.
* The animation will start from its current position.
* If loopAnimation is YES the animation will loop from start position to toProgress indefinitely.
* If loopAnimation is NO the animation will stop and the completion block will be called.
*/
- (void)playToProgress:(CGFloat)toProgress
withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
* Plays the animation from specific progress to a specific progress
* The animation will start from its current position..
* If loopAnimation is YES the animation will loop from the startProgress to the endProgress indefinitely
* If loopAnimation is NO the animation will stop and the completion block will be called.
*/
- (void)playFromProgress:(CGFloat)fromStartProgress
toProgress:(CGFloat)toEndProgress
withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
* Plays the animation from its current position to a specific frame.
* The animation will start from its current position.
* If loopAnimation is YES the animation will loop from beginning to toFrame indefinitely.
* If loopAnimation is NO the animation will stop and the completion block will be called.
*/
- (void)playToFrame:(nonnull NSNumber *)toFrame
withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/*
* Plays the animation from specific frame to a specific frame.
* The animation will start from its current position.
* If loopAnimation is YES the animation will loop start frame to end frame indefinitely.
* If loopAnimation is NO the animation will stop and the completion block will be called.
*/
- (void)playFromFrame:(nonnull NSNumber *)fromStartFrame
toFrame:(nonnull NSNumber *)toEndFrame
withCompletion:(nullable LOTAnimationCompletionBlock)completion;
/**
* Plays the animation from its current position to the end of the animation.
* The animation will start from its current position.
* If loopAnimation is YES the animation will loop from beginning to end indefinitely.
* If loopAnimation is NO the animation will stop and the completion block will be called.
**/
- (void)playWithCompletion:(nullable LOTAnimationCompletionBlock)completion;
/// Plays the animation
- (void)play;
/// Stops the animation at the current frame. The completion block will be called.
- (void)pause;
/// Stops the animation and rewinds to the beginning. The completion block will be called.
- (void)stop;
/// Sets progress of animation to a specific frame. If the animation is playing it will stop and the completion block will be called.
- (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame;
/// Forces a layout and drawing update for the current frame.
- (void)forceDrawingUpdate;
/// Logs all child keypaths
- (void)logHierarchyKeypaths;
/*!
@brief Sets a LOTValueDelegate for each animation property returned from the LOTKeypath search. LOTKeypath matches views inside of LOTAnimationView to their After Effects counterparts. The LOTValueDelegate is called every frame as the animation plays to override animation values. A delegate can be any object that conforms to the LOTValueDelegate protocol, or one of the prebuilt delegate classes found in LOTBlockCallback, LOTInterpolatorCallback, and LOTValueCallback.
@discussion
Example that sets an animated stroke to Red using a LOTColorValueCallback.
@code
LOTKeypath *keypath = [LOTKeypath keypathWithKeys:@"Layer 1", @"Ellipse 1", @"Stroke 1", @"Color", nil];
LOTColorValueCallback *colorCallback = [LOTColorBlockCallback withColor:[UIColor redColor]];
[animationView setValueDelegate:colorCallback forKeypath:keypath];
@endcode
See the documentation for LOTValueDelegate to see how to create LOTValueCallbacks. A delegate can be any object that conforms to the LOTValueDelegate protocol, or one of the prebuilt delegate classes found in LOTBlockCallback, LOTInterpolatorCallback, and LOTValueCallback.
See the documentation for LOTKeypath to learn more about how to create keypaths.
NOTE: The delegate is weakly retained. Be sure that the creator of a delegate is retained.
Read More at http://airbnb.io/lottie/ios/dynamic.html
*/
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegates
forKeypath:(LOTKeypath * _Nonnull)keypath;
/*!
@brief returns the string representation of every keypath matching the LOTKeypath search.
*/
- (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath;
/*!
@brief Converts a CGPoint from the Animation views top coordinate space into the coordinate space of the specified renderable animation node.
*/
- (CGPoint)convertPoint:(CGPoint)point
toKeypathLayer:(nonnull LOTKeypath *)keypath;
/*!
@brief Converts a CGRect from the Animation views top coordinate space into the coordinate space of the specified renderable animation node.
*/
- (CGRect)convertRect:(CGRect)rect
toKeypathLayer:(nonnull LOTKeypath *)keypath;
/*!
@brief Converts a CGPoint to the Animation views top coordinate space from the coordinate space of the specified renderable animation node.
*/
- (CGPoint)convertPoint:(CGPoint)point
fromKeypathLayer:(nonnull LOTKeypath *)keypath;
/*!
@brief Converts a CGRect to the Animation views top coordinate space from the coordinate space of the specified renderable animation node.
*/
- (CGRect)convertRect:(CGRect)rect
fromKeypathLayer:(nonnull LOTKeypath *)keypath;
/*!
@brief Adds a UIView, or NSView, to the renderable layer found at the Keypath
*/
- (void)addSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath;
/*!
@brief Adds a UIView, or NSView, to the parent renderable layer found at the Keypath and then masks the view with layer found at the keypath.
*/
- (void)maskSubview:(nonnull LOTView *)view
toKeypathLayer:(nonnull LOTKeypath *)keypath;
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
@property (nonatomic) LOTViewContentMode contentMode;
#endif
/*!
@brief Sets the keyframe value for a specific After Effects property at a given time. NOTE: Deprecated. Use setValueDelegate:forKeypath:
@discussion NOTE: Deprecated and non functioning. Use setValueDelegate:forKeypath:
@param value Value is the color, point, or number object that should be set at given time
@param keypath NSString . separate keypath The Keypath is a dot separated key path that specifies the location of the key to be set from the After Effects file. This will begin with the Layer Name. EG "Layer 1.Shape 1.Fill 1.Color"
@param frame The frame is the frame to be set. If the keyframe exists it will be overwritten, if it does not exist a new linearly interpolated keyframe will be added
*/
- (void)setValue:(nonnull id)value
forKeypath:(nonnull NSString *)keypath
atFrame:(nullable NSNumber *)frame __deprecated;
/*!
@brief Adds a custom subview to the animation using a LayerName from After Effect as a reference point.
@discussion NOTE: Deprecated. Use addSubview:toKeypathLayer: or maskSubview:toKeypathLayer:
@param view The custom view instance to be added
@param layer The string name of the After Effects layer to be referenced.
@param applyTransform If YES the custom view will be animated to move with the specified After Effects layer. If NO the custom view will be masked by the After Effects layer
*/
- (void)addSubview:(nonnull LOTView *)view
toLayerNamed:(nonnull NSString *)layer
applyTransform:(BOOL)applyTransform __deprecated;
/*!
@brief Converts the given CGRect from the receiving animation view's coordinate space to the supplied layer's coordinate space If layerName is null then the rect will be converted to the composition coordinate system. This is helpful when adding custom subviews to a LOTAnimationView
@discussion NOTE: Deprecated. Use convertRect:fromKeypathLayer:
*/
- (CGRect)convertRect:(CGRect)rect
toLayerNamed:(NSString *_Nullable)layerName __deprecated;
@end
//
// LOTAnimationView_Compat.h
// Lottie
//
// Created by Oleksii Pavlovskyi on 2/2/17.
// Copyright (c) 2017 Airbnb. All rights reserved.
//
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import <UIKit/UIKit.h>
@compatibility_alias LOTView UIView;
#else
#import <AppKit/AppKit.h>
@compatibility_alias LOTView NSView;
typedef NS_ENUM(NSInteger, LOTViewContentMode) {
LOTViewContentModeScaleToFill,
LOTViewContentModeScaleAspectFit,
LOTViewContentModeScaleAspectFill,
LOTViewContentModeRedraw,
LOTViewContentModeCenter,
LOTViewContentModeTop,
LOTViewContentModeBottom,
LOTViewContentModeLeft,
LOTViewContentModeRight,
LOTViewContentModeTopLeft,
LOTViewContentModeTopRight,
LOTViewContentModeBottomLeft,
LOTViewContentModeBottomRight,
};
#endif
//
// LOTBlockCallback.h
// Lottie
//
// Created by brandon_withrow on 12/15/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "LOTValueDelegate.h"
/*!
@brief A block that is used to change a Color value at keytime, the block is called continuously for a keypath while the animation plays.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyFrame When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyFrame When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startColor The color from the previous keyframe in relation to the current time.
@param endColor The color from the next keyframe in relation to the current time.
@param interpolatedColor The color interpolated at the current time between startColor and endColor. This represents the keypaths current color for the current time.
@return CGColorRef the color to set the keypath node for the current frame
*/
typedef CGColorRef _Nonnull (^LOTColorValueCallbackBlock)(CGFloat currentFrame,
CGFloat startKeyFrame,
CGFloat endKeyFrame,
CGFloat interpolatedProgress,
CGColorRef _Nullable startColor,
CGColorRef _Nullable endColor,
CGColorRef _Nullable interpolatedColor);
/*!
@brief A block that is used to change a Number value at keytime, the block is called continuously for a keypath while the animation plays.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyFrame When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyFrame When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startValue The Number from the previous keyframe in relation to the current time.
@param endValue The Number from the next keyframe in relation to the current time.
@param interpolatedValue The Number interpolated at the current time between startValue and endValue. This represents the keypaths current Number for the current time.
@return CGFloat the number to set the keypath node for the current frame
*/
typedef CGFloat (^LOTNumberValueCallbackBlock)(CGFloat currentFrame,
CGFloat startKeyFrame,
CGFloat endKeyFrame,
CGFloat interpolatedProgress,
CGFloat startValue,
CGFloat endValue,
CGFloat interpolatedValue);
/*!
@brief A block that is used to change a Point value at keytime, the block is called continuously for a keypath while the animation plays.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyFrame When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyFrame When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startPoint The Point from the previous keyframe in relation to the current time.
@param endPoint The Point from the next keyframe in relation to the current time.
@param interpolatedPoint The Point interpolated at the current time between startPoint and endPoint. This represents the keypaths current Point for the current time.
@return CGPoint the point to set the keypath node for the current frame.
*/
typedef CGPoint (^LOTPointValueCallbackBlock)(CGFloat currentFrame,
CGFloat startKeyFrame,
CGFloat endKeyFrame,
CGFloat interpolatedProgress,
CGPoint startPoint,
CGPoint endPoint,
CGPoint interpolatedPoint);
/*!
@brief A block that is used to change a Size value at keytime, the block is called continuously for a keypath while the animation plays.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyFrame When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyFrame When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startSize The Size from the previous keyframe in relation to the current time.
@param endSize The Size from the next keyframe in relation to the current time.
@param interpolatedSize The Size interpolated at the current time between startSize and endSize. This represents the keypaths current Size for the current time.
@return CGSize the size to set the keypath node for the current frame.
*/
typedef CGSize (^LOTSizeValueCallbackBlock)(CGFloat currentFrame,
CGFloat startKeyFrame,
CGFloat endKeyFrame,
CGFloat interpolatedProgress,
CGSize startSize,
CGSize endSize,
CGSize interpolatedSize);
/*!
@brief A block that is used to change a Path value at keytime, the block is called continuously for a keypath while the animation plays.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyFrame When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyFrame When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@return UIBezierPath the path to set the keypath node for the current frame.
*/
typedef CGPathRef _Nonnull (^LOTPathValueCallbackBlock)(CGFloat currentFrame,
CGFloat startKeyFrame,
CGFloat endKeyFrame,
CGFloat interpolatedProgress);
/*!
@brief LOTColorValueCallback is wrapper around a LOTColorValueCallbackBlock. This block can be used in conjunction with LOTAnimationView setValueDelegate:forKeypath to dynamically change an animation's color keypath at runtime.
*/
@interface LOTColorBlockCallback : NSObject <LOTColorValueDelegate>
+ (instancetype _Nonnull)withBlock:(LOTColorValueCallbackBlock _Nonnull )block NS_SWIFT_NAME(init(block:));
@property (nonatomic, copy, nonnull) LOTColorValueCallbackBlock callback;
@end
/*!
@brief LOTNumberValueCallback is wrapper around a LOTNumberValueCallbackBlock. This block can be used in conjunction with LOTAnimationView setValueDelegate:forKeypath to dynamically change an animation's number keypath at runtime.
*/
@interface LOTNumberBlockCallback : NSObject <LOTNumberValueDelegate>
+ (instancetype _Nonnull)withBlock:(LOTNumberValueCallbackBlock _Nonnull)block NS_SWIFT_NAME(init(block:));
@property (nonatomic, copy, nonnull) LOTNumberValueCallbackBlock callback;
@end
/*!
@brief LOTPointValueCallback is wrapper around a LOTPointValueCallbackBlock. This block can be used in conjunction with LOTAnimationView setValueDelegate:forKeypath to dynamically change an animation's point keypath at runtime.
*/
@interface LOTPointBlockCallback : NSObject <LOTPointValueDelegate>
+ (instancetype _Nonnull)withBlock:(LOTPointValueCallbackBlock _Nonnull)block NS_SWIFT_NAME(init(block:));
@property (nonatomic, copy, nonnull) LOTPointValueCallbackBlock callback;
@end
/*!
@brief LOTSizeValueCallback is wrapper around a LOTSizeValueCallbackBlock. This block can be used in conjunction with LOTAnimationView setValueDelegate:forKeypath to dynamically change an animation's size keypath at runtime.
*/
@interface LOTSizeBlockCallback : NSObject <LOTSizeValueDelegate>
+ (instancetype _Nonnull)withBlock:(LOTSizeValueCallbackBlock _Nonnull)block NS_SWIFT_NAME(init(block:));
@property (nonatomic, copy, nonnull) LOTSizeValueCallbackBlock callback;
@end
/*!
@brief LOTPathValueCallback is wrapper around a LOTPathValueCallbackBlock. This block can be used in conjunction with LOTAnimationView setValueDelegate:forKeypath to dynamically change an animation's path keypath at runtime.
*/
@interface LOTPathBlockCallback : NSObject <LOTPathValueDelegate>
+ (instancetype _Nonnull)withBlock:(LOTPathValueCallbackBlock _Nonnull)block NS_SWIFT_NAME(init(block:));
@property (nonatomic, copy, nonnull) LOTPathValueCallbackBlock callback;
@end
//
// LOTCacheProvider.h
// Lottie
//
// Created by punmy on 2017/7/8.
//
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import <UIKit/UIKit.h>
@compatibility_alias LOTImage UIImage;
@protocol LOTImageCache;
#pragma mark - LOTCacheProvider
@interface LOTCacheProvider : NSObject
+ (id<LOTImageCache>)imageCache;
+ (void)setImageCache:(id<LOTImageCache>)cache;
@end
#pragma mark - LOTImageCache
/**
This protocol represent the interface of a image cache which lottie can use.
*/
@protocol LOTImageCache <NSObject>
@required
- (LOTImage *)imageForKey:(NSString *)key;
- (void)setImage:(LOTImage *)image forKey:(NSString *)key;
@end
#endif
//
// LOTScene.h
// LottieAnimator
//
// Created by Brandon Withrow on 12/14/15.
// Copyright © 2015 Brandon Withrow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@class LOTLayerGroup;
@class LOTLayer;
@class LOTAssetGroup;
@interface LOTComposition : NSObject
/// Load animation by name from the default bundle, Images are also loaded from the bundle
+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:));
/// Loads animation by name from specified bundle, Images are also loaded from the bundle
+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName
inBundle:(nonnull NSBundle *)bundle NS_SWIFT_NAME(init(name:bundle:));
/// Loads an animation from a specific file path. WARNING Do not use a web URL for file path.
+ (nullable instancetype)animationWithFilePath:(nonnull NSString *)filePath NS_SWIFT_NAME(init(filePath:));
/// Creates an animation from the deserialized JSON Dictionary
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
/// Creates an animation from the deserialized JSON Dictionary, images are loaded from the specified bundle
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON
inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(init(json:bundle:));
- (instancetype _Nonnull)initWithJSON:(NSDictionary * _Nullable)jsonDictionary
withAssetBundle:(NSBundle * _Nullable)bundle;
@property (nonatomic, readonly) CGRect compBounds;
@property (nonatomic, strong, readonly, nullable) NSNumber *startFrame;
@property (nonatomic, strong, readonly, nullable) NSNumber *endFrame;
@property (nonatomic, strong, readonly, nullable) NSNumber *framerate;
@property (nonatomic, readonly) NSTimeInterval timeDuration;
@property (nonatomic, strong, readonly, nullable) LOTLayerGroup *layerGroup;
@property (nonatomic, strong, readonly, nullable) LOTAssetGroup *assetGroup;
@property (nonatomic, strong, readwrite, nullable) NSString *rootDirectory;
@property (nonatomic, strong, readonly, nullable) NSBundle *assetBundle;
@property (nonatomic, copy, nullable) NSString *cacheKey;
@end
//
// LOTInterpolatorCallback.h
// Lottie
//
// Created by brandon_withrow on 12/15/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "LOTValueDelegate.h"
/*!
@brief LOTPointInterpolatorCallback is a container for a CGPointRef. This container is a LOTPointValueDelegate that will return the point interpolated at currentProgress between fromPoint and toPoint. Externally changing currentProgress will change the point of the animation.
@discussion LOTPointInterpolatorCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypoint to set a point value of an animation property.
*/
@interface LOTPointInterpolatorCallback : NSObject <LOTPointValueDelegate>
+ (instancetype _Nonnull)withFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint NS_SWIFT_NAME(init(from:to:));
@property (nonatomic) CGPoint fromPoint;
@property (nonatomic) CGPoint toPoint;
/*!
@brief As currentProgress changes from 0 to 1 the point sent to the animation view is interpolated between fromPoint and toPoint.
*/
@property (nonatomic, assign) CGFloat currentProgress;
@end
/*!
@brief LOTSizeInterpolatorCallback is a container for a CGSizeRef. This container is a LOTSizeValueDelegate that will return the size interpolated at currentProgress between fromSize and toSize. Externally changing currentProgress will change the size of the animation.
@discussion LOTSizeInterpolatorCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeysize to set a size value of an animation property.
*/
@interface LOTSizeInterpolatorCallback : NSObject <LOTSizeValueDelegate>
+ (instancetype _Nonnull)withFromSize:(CGSize)fromSize toSize:(CGSize)toSize NS_SWIFT_NAME(init(from:to:));
@property (nonatomic) CGSize fromSize;
@property (nonatomic) CGSize toSize;
/*!
@brief As currentProgress changes from 0 to 1 the size sent to the animation view is interpolated between fromSize and toSize.
*/
@property (nonatomic, assign) CGFloat currentProgress;
@end
/*!
@brief LOTFloatInterpolatorCallback is a container for a CGFloatRef. This container is a LOTFloatValueDelegate that will return the float interpolated at currentProgress between fromFloat and toFloat. Externally changing currentProgress will change the float of the animation.
@discussion LOTFloatInterpolatorCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeyfloat to set a float value of an animation property.
*/
@interface LOTFloatInterpolatorCallback : NSObject <LOTNumberValueDelegate>
+ (instancetype _Nonnull)withFromFloat:(CGFloat)fromFloat toFloat:(CGFloat)toFloat NS_SWIFT_NAME(init(from:to:));
@property (nonatomic) CGFloat fromFloat;
@property (nonatomic) CGFloat toFloat;
/*!
@brief As currentProgress changes from 0 to 1 the float sent to the animation view is interpolated between fromFloat and toFloat.
*/
@property (nonatomic, assign) CGFloat currentProgress;
@end
//
// LOTKeypath.h
// Lottie_iOS
//
// Created by brandon_withrow on 12/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString * _Nonnull const kLOTKeypathEnd;
/*!
@brief LOTKeypath is an object that describes a keypath search for nodes in the animation JSON. LOTKeypath matches views inside of LOTAnimationView to their After Effects counterparts.
@discussion
LOTKeypath is used with LOTAnimationView to set animation properties dynamically at runtime, to add or mask subviews, converting geometry, and numerous other functions.
LOTKeypath can describe a specific object, or can use wildcards for fuzzy matching of objects. Acceptable wildcards are either "*" (star) or "**" (double star). Single star will search a single depth for the next object, double star will search any depth.
Read More at http://airbnb.io/lottie/ios/dynamic.html
EG:
@"Layer.Shape Group.Stroke 1.Color"
Represents a specific color node on a specific stroke.
@"**.Stroke 1.Color"
Represents the color node for every "Stroke 1" in the animation scene.
*/
@interface LOTKeypath : NSObject
/*!
@brief Creates a LOTKeypath from a dot separated string, can use wildcards @"*" and fuzzy depth wild cards @"**".
@discussion LOTKeypath is an object that describes a keypath search for nodes in the animation JSON.
LOTKeypath is used with LOTAnimationView to set animation properties dynamically at runtime, to add or mask subviews, converting geometry, and numerous other functions.
LOTKeypath can describe a specific object, or can use wildcards for fuzzy matching of objects. Acceptable wildcards are either "*" (star) or "**" (double star). Single star will search a single depth for the next object, double star will search any depth.
@param keypath A dot separated string describing a keypath from the JSON animation. EG @"Layer.Shape Group.Stroke 1.Color"
@return A new LOTKeypath
*/
+ (nonnull LOTKeypath *)keypathWithString:(nonnull NSString *)keypath;
/*!
@brief Creates a LOTKeypath from a list of keypath string objects, can use wildcards @"*" and fuzzy depth wild cards @"**".
@discussion LOTKeypath is an object that describes a keypath search for nodes in the animation JSON.
LOTKeypath is used with LOTAnimationView to set animation properties dynamically at runtime, to add or mask subviews, converting geometry, and numerous other functions.
LOTKeypath can describe a specific object, or can use wildcards for fuzzy matching of objects. Acceptable wildcards are either "*" (star) or "**" (double star). Single star will search a single depth for the next object, double star will search any depth.
@param firstKey A nil terminated list of strings describing a keypath. EG @"Layer", @"Shape Group", @"Stroke 1", @"Color", nil
@return A new LOTKeypath
*/
+ (nonnull LOTKeypath *)keypathWithKeys:(nonnull NSString *)firstKey, ...
NS_REQUIRES_NIL_TERMINATION;
@property (nonatomic, readonly, nonnull) NSString *absoluteKeypath;
@property (nonatomic, readonly, nonnull) NSString *currentKey;
@property (nonatomic, readonly, nonnull) NSString *currentKeyPath;
@property (nonatomic, readonly, nonnull) NSDictionary *searchResults;
@property (nonatomic, readonly) BOOL hasFuzzyWildcard;
@property (nonatomic, readonly) BOOL hasWildcard;
@property (nonatomic, readonly) BOOL endOfKeypath;
- (BOOL)pushKey:(nonnull NSString *)key;
- (void)popKey;
- (void)popToRootKey;
- (void)addSearchResultForCurrentPath:(id _Nonnull)result;
@end
//
// LOTValueCallback.h
// Lottie
//
// Created by brandon_withrow on 12/15/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "LOTValueDelegate.h"
/*!
@brief LOTColorValueCallback is a container for a CGColorRef. This container is a LOTColorValueDelegate that always returns the colorValue property to its animation delegate.
@discussion LOTColorValueCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypath to set a color value of an animation property.
*/
@interface LOTColorValueCallback : NSObject <LOTColorValueDelegate>
+ (instancetype _Nonnull)withCGColor:(CGColorRef _Nonnull)color NS_SWIFT_NAME(init(color:));
@property (nonatomic, nonnull) CGColorRef colorValue;
@end
/*!
@brief LOTNumberValueCallback is a container for a CGFloat value. This container is a LOTNumberValueDelegate that always returns the numberValue property to its animation delegate.
@discussion LOTNumberValueCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypath to set a number value of an animation property.
*/
@interface LOTNumberValueCallback : NSObject <LOTNumberValueDelegate>
+ (instancetype _Nonnull)withFloatValue:(CGFloat)numberValue NS_SWIFT_NAME(init(number:));
@property (nonatomic, assign) CGFloat numberValue;
@end
/*!
@brief LOTPointValueCallback is a container for a CGPoint value. This container is a LOTPointValueDelegate that always returns the pointValue property to its animation delegate.
@discussion LOTPointValueCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypath to set a point value of an animation property.
*/
@interface LOTPointValueCallback : NSObject <LOTPointValueDelegate>
+ (instancetype _Nonnull)withPointValue:(CGPoint)pointValue;
@property (nonatomic, assign) CGPoint pointValue;
@end
/*!
@brief LOTSizeValueCallback is a container for a CGSize value. This container is a LOTSizeValueDelegate that always returns the sizeValue property to its animation delegate.
@discussion LOTSizeValueCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypath to set a size value of an animation property.
*/
@interface LOTSizeValueCallback : NSObject <LOTSizeValueDelegate>
+ (instancetype _Nonnull)withPointValue:(CGSize)sizeValue NS_SWIFT_NAME(init(size:));
@property (nonatomic, assign) CGSize sizeValue;
@end
/*!
@brief LOTPathValueCallback is a container for a CGPathRef value. This container is a LOTPathValueDelegate that always returns the pathValue property to its animation delegate.
@discussion LOTPathValueCallback is used in conjunction with LOTAnimationView setValueDelegate:forKeypath to set a path value of an animation property.
*/
@interface LOTPathValueCallback : NSObject <LOTPathValueDelegate>
+ (instancetype _Nonnull)withCGPath:(CGPathRef _Nonnull)path NS_SWIFT_NAME(init(path:));
@property (nonatomic, nonnull) CGPathRef pathValue;
@end
//
// LOTValueDelegate.h
// Lottie
//
// Created by brandon_withrow on 1/5/18.
// Copyright © 2018 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
/*!
@brief LOTValueDelegate is not intended to be used directly. It is used for type safety.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
Prebuild delegates can be found in LOTBlockCallback, LOTInterpolatorCallback, and LOTValueCallback. These delegates allow direct setting and driving of an animated value.
See LOTColorValueDelegate, LOTNumberValueDelegate, LOTPointValueDelegate, LOTSizeValueDelegate, LOTPathValueDelegate.
*/
@protocol LOTValueDelegate <NSObject>
@end
@protocol LOTColorValueDelegate <LOTValueDelegate>
@required
/*!
@brief LOTColorValueDelegate is called at runtime to override the color value of a property in a LOTAnimation. The property is defined by at LOTKeypath. The delegate is set via setValueDelegate:forKeypath on LOTAnimationView.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyframe When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyframe When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startColor The color from the previous keyframe in relation to the current time.
@param endColor The color from the next keyframe in relation to the current time.
@param interpolatedColor The color interpolated at the current time between startColor and endColor. This represents the keypaths current color for the current time.
@return CGColorRef the color to set the keypath node for the current frame
*/
- (CGColorRef)colorForFrame:(CGFloat)currentFrame
startKeyframe:(CGFloat)startKeyframe
endKeyframe:(CGFloat)endKeyframe
interpolatedProgress:(CGFloat)interpolatedProgress
startColor:(CGColorRef)startColor
endColor:(CGColorRef)endColor
currentColor:(CGColorRef)interpolatedColor;
@end
@protocol LOTNumberValueDelegate <LOTValueDelegate>
@required
/*!
@brief LOTNumberValueDelegate is called at runtime to override the number value of a property in a LOTAnimation. The property is defined by at LOTKeypath. The delegate is set via setValueDelegate:forKeypath on LOTAnimationView.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyframe When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyframe When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startValue The number from the previous keyframe in relation to the current time.
@param endValue The number from the next keyframe in relation to the current time.
@param interpolatedValue The number interpolated at the current time between startNumber and endNumber. This represents the keypaths current number for the current time.
@return CGFloat the number to set the keypath node for the current frame
*/
- (CGFloat)floatValueForFrame:(CGFloat)currentFrame
startKeyframe:(CGFloat)startKeyframe
endKeyframe:(CGFloat)endKeyframe
interpolatedProgress:(CGFloat)interpolatedProgress
startValue:(CGFloat)startValue
endValue:(CGFloat)endValue
currentValue:(CGFloat)interpolatedValue;
@end
@protocol LOTPointValueDelegate <LOTValueDelegate>
@required
/*!
@brief LOTPointValueDelegate is called at runtime to override the point value of a property in a LOTAnimation. The property is defined by at LOTKeypath. The delegate is set via setValueDelegate:forKeypath on LOTAnimationView.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyframe When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyframe When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startPoint The point from the previous keyframe in relation to the current time.
@param endPoint The point from the next keyframe in relation to the current time.
@param interpolatedPoint The point interpolated at the current time between startPoint and endPoint. This represents the keypaths current point for the current time.
@return CGPoint the point to set the keypath node for the current frame
*/
- (CGPoint)pointForFrame:(CGFloat)currentFrame
startKeyframe:(CGFloat)startKeyframe
endKeyframe:(CGFloat)endKeyframe
interpolatedProgress:(CGFloat)interpolatedProgress
startPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
currentPoint:(CGPoint)interpolatedPoint;
@end
@protocol LOTSizeValueDelegate <LOTValueDelegate>
@required
/*!
@brief LOTSizeValueDelegate is called at runtime to override the size value of a property in a LOTAnimation. The property is defined by at LOTKeypath. The delegate is set via setValueDelegate:forKeypath on LOTAnimationView.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyframe When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyframe When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@param startSize The size from the previous keyframe in relation to the current time.
@param endSize The size from the next keyframe in relation to the current time.
@param interpolatedSize The size interpolated at the current time between startSize and endSize. This represents the keypaths current size for the current time.
@return CGSize the size to set the keypath node for the current frame
*/
- (CGSize)sizeForFrame:(CGFloat)currentFrame
startKeyframe:(CGFloat)startKeyframe
endKeyframe:(CGFloat)endKeyframe
interpolatedProgress:(CGFloat)interpolatedProgress
startSize:(CGSize)startSize
endSize:(CGSize)endSize
currentSize:(CGSize)interpolatedSize;
@end
@protocol LOTPathValueDelegate <LOTValueDelegate>
@required
/*!
@brief LOTPathValueDelegate is called at runtime to override the path value of a property in a LOTAnimation. The property is defined by at LOTKeypath. The delegate is set via setValueDelegate:forKeypath on LOTAnimationView.
@discussion LOTValueDelegates are used to dynamically change animation data at runtime. A delegate is set for a keypath, defined by LOTKeypath. While the animation is running the delegate is asked for the value for the keypath at each frame of the animation. The delegate is given the computed animation value for the the current frame. See LOTKeypath and the setValueDelegate:forKeypath method on LOTAnimationView.
@param currentFrame The current frame of the animation in the parent compositions time space.
@param startKeyframe When the block is called, startFrame is the most recent keyframe for the keypath in relation to the current time.
@param endKeyframe When the block is called, endFrame is the next keyframe for the keypath in relation to the current time.
@param interpolatedProgress A value from 0-1 that represents the current progress between keyframes. It respects the keyframes current easing curves.
@return CGPathRef the path to set the keypath node for the current frame
*/
- (CGPathRef)pathForFrame:(CGFloat)currentFrame
startKeyframe:(CGFloat)startKeyframe
endKeyframe:(CGFloat)endKeyframe
interpolatedProgress:(CGFloat)interpolatedProgress;
@end
//
// Lottie.h
// Pods
//
// Created by brandon_withrow on 1/27/17.
//
// Dream Big.
#if __has_feature(modules)
@import Foundation;
#else
#import <Foundation/Foundation.h>
#endif
#ifndef Lottie_h
#define Lottie_h
//! Project version number for Lottie.
FOUNDATION_EXPORT double LottieVersionNumber;
//! Project version string for Lottie.
FOUNDATION_EXPORT const unsigned char LottieVersionString[];
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "LOTAnimationTransitionController.h"
#import "LOTAnimatedSwitch.h"
#import "LOTAnimatedControl.h"
#endif
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "LOTCacheProvider.h"
#endif
#import "LOTAnimationView.h"
#import "LOTAnimationCache.h"
#import "LOTComposition.h"
#import "LOTBlockCallback.h"
#import "LOTInterpolatorCallback.h"
#import "LOTValueCallback.h"
#import "LOTValueDelegate.h"
#endif /* Lottie_h */
//
// LOTCircleAnimator.h
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatorNode.h"
#import "LOTShapeCircle.h"
@interface LOTCircleAnimator : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeCircle:(LOTShapeCircle *_Nonnull)shapeCircle;
@end
//
// LOTCircleAnimator.m
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTCircleAnimator.h"
#import "LOTPointInterpolator.h"
const CGFloat kLOTEllipseControlPointPercentage = 0.55228;
@implementation LOTCircleAnimator {
LOTPointInterpolator *_centerInterpolator;
LOTPointInterpolator *_sizeInterpolator;
BOOL _reversed;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeCircle:(LOTShapeCircle *_Nonnull)shapeCircle {
self = [super initWithInputNode:inputNode keyName:shapeCircle.keyname];
if (self) {
_centerInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeCircle.position.keyframes];
_sizeInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeCircle.size.keyframes];
_reversed = shapeCircle.reversed;
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Size" : _sizeInterpolator,
@"Position" : _centerInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return [_centerInterpolator hasUpdateForFrame:frame] || [_sizeInterpolator hasUpdateForFrame:frame];
}
- (void)performLocalUpdate {
// Unfortunately we HAVE to manually build out the ellipse.
// Every Apple method constructs from the 3 o-clock position
// After effects constructs from the Noon position.
// After effects does clockwise, but also has a flag for reversed.
CGPoint center = [_centerInterpolator pointValueForFrame:self.currentFrame];
CGPoint size = [_sizeInterpolator pointValueForFrame:self.currentFrame];
CGFloat halfWidth = size.x / 2;
CGFloat halfHeight = size.y / 2;
if (_reversed) {
halfWidth = halfWidth * -1;
}
CGPoint circleQ1 = CGPointMake(center.x, center.y - halfHeight);
CGPoint circleQ2 = CGPointMake(center.x + halfWidth, center.y);
CGPoint circleQ3 = CGPointMake(center.x, center.y + halfHeight);
CGPoint circleQ4 = CGPointMake(center.x - halfWidth, center.y);
CGFloat cpW = halfWidth * kLOTEllipseControlPointPercentage;
CGFloat cpH = halfHeight * kLOTEllipseControlPointPercentage;
LOTBezierPath *path = [[LOTBezierPath alloc] init];
path.cacheLengths = self.pathShouldCacheLengths;
[path LOT_moveToPoint:circleQ1];
[path LOT_addCurveToPoint:circleQ2 controlPoint1:CGPointMake(circleQ1.x + cpW, circleQ1.y) controlPoint2:CGPointMake(circleQ2.x, circleQ2.y - cpH)];
[path LOT_addCurveToPoint:circleQ3 controlPoint1:CGPointMake(circleQ2.x, circleQ2.y + cpH) controlPoint2:CGPointMake(circleQ3.x + cpW, circleQ3.y)];
[path LOT_addCurveToPoint:circleQ4 controlPoint1:CGPointMake(circleQ3.x - cpW, circleQ3.y) controlPoint2:CGPointMake(circleQ4.x, circleQ4.y + cpH)];
[path LOT_addCurveToPoint:circleQ1 controlPoint1:CGPointMake(circleQ4.x, circleQ4.y - cpH) controlPoint2:CGPointMake(circleQ1.x - cpW, circleQ1.y)];
self.localPath = path;
}
@end
//
// LOTPathAnimator.h
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import "LOTAnimatorNode.h"
#import "LOTShapePath.h"
@interface LOTPathAnimator : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapePath:(LOTShapePath *_Nonnull)shapePath;
@end
//
// LOTPathAnimator.m
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import "LOTPathAnimator.h"
#import "LOTPathInterpolator.h"
@implementation LOTPathAnimator {
LOTShapePath *_pathConent;
LOTPathInterpolator *_interpolator;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapePath:(LOTShapePath *_Nonnull)shapePath {
self = [super initWithInputNode:inputNode keyName:shapePath.keyname];
if (self) {
_pathConent = shapePath;
_interpolator = [[LOTPathInterpolator alloc] initWithKeyframes:_pathConent.shapePath.keyframes];
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Path" : _interpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return [_interpolator hasUpdateForFrame:frame];
}
- (void)performLocalUpdate {
self.localPath = [_interpolator pathForFrame:self.currentFrame cacheLengths:self.pathShouldCacheLengths];
}
@end
//
// LOTPolygonAnimator.h
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatorNode.h"
#import "LOTShapeStar.h"
@interface LOTPolygonAnimator : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapePolygon:(LOTShapeStar *_Nonnull)shapeStar;
@end
//
// LOTPolygonAnimator.m
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPolygonAnimator.h"
#import "LOTKeyframe.h"
#import "LOTPointInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "LOTBezierPath.h"
#import "CGGeometry+LOTAdditions.h"
const CGFloat kPOLYGON_MAGIC_NUMBER = .25f;
@implementation LOTPolygonAnimator {
LOTNumberInterpolator *_outerRadiusInterpolator;
LOTNumberInterpolator *_outerRoundnessInterpolator;
LOTPointInterpolator *_positionInterpolator;
LOTNumberInterpolator *_pointsInterpolator;
LOTNumberInterpolator *_rotationInterpolator;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapePolygon:(LOTShapeStar *_Nonnull)shapeStar {
self = [super initWithInputNode:inputNode keyName:shapeStar.keyname];
if (self) {
_outerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRadius.keyframes];
_outerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRoundness.keyframes];
_pointsInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.numberOfPoints.keyframes];
_rotationInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.rotation.keyframes];
_positionInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeStar.position.keyframes];
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Points" : _pointsInterpolator,
@"Position" : _positionInterpolator,
@"Rotation" : _rotationInterpolator,
@"Outer Radius" : _outerRadiusInterpolator,
@"Outer Roundness" : _outerRoundnessInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return ([_outerRadiusInterpolator hasUpdateForFrame:frame] ||
[_outerRoundnessInterpolator hasUpdateForFrame:frame] ||
[_pointsInterpolator hasUpdateForFrame:frame] ||
[_rotationInterpolator hasUpdateForFrame:frame] ||
[_positionInterpolator hasUpdateForFrame:frame]);
}
- (void)performLocalUpdate {
CGFloat outerRadius = [_outerRadiusInterpolator floatValueForFrame:self.currentFrame];
CGFloat outerRoundness = [_outerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
CGFloat points = [_pointsInterpolator floatValueForFrame:self.currentFrame];
CGFloat rotation = [_rotationInterpolator floatValueForFrame:self.currentFrame];
CGPoint position = [_positionInterpolator pointValueForFrame:self.currentFrame];
LOTBezierPath *path = [[LOTBezierPath alloc] init];
path.cacheLengths = self.pathShouldCacheLengths;
CGFloat currentAngle = LOT_DegreesToRadians(rotation - 90);
CGFloat anglePerPoint = (CGFloat)((2 * M_PI) / points);
CGFloat x;
CGFloat y;
CGFloat previousX;
CGFloat previousY;
x = (CGFloat) (outerRadius * cosf(currentAngle));
y = (CGFloat) (outerRadius * sinf(currentAngle));
[path LOT_moveToPoint:CGPointMake(x, y)];
currentAngle += anglePerPoint;
double numPoints = ceil(points);
for (int i = 0; i < numPoints; i++) {
previousX = x;
previousY = y;
x = (CGFloat) (outerRadius * cosf(currentAngle));
y = (CGFloat) (outerRadius * sinf(currentAngle));
if (outerRoundness != 0) {
CGFloat cp1Theta = (CGFloat) (atan2(previousY, previousX) - M_PI / 2.f);
CGFloat cp1Dx = (CGFloat) cosf(cp1Theta);
CGFloat cp1Dy = (CGFloat) sinf(cp1Theta);
CGFloat cp2Theta = (CGFloat) (atan2(y, x) - M_PI / 2.f);
CGFloat cp2Dx = (CGFloat) cosf(cp2Theta);
CGFloat cp2Dy = (CGFloat) sinf(cp2Theta);
CGFloat cp1x = outerRadius * outerRoundness * kPOLYGON_MAGIC_NUMBER * cp1Dx;
CGFloat cp1y = outerRadius * outerRoundness * kPOLYGON_MAGIC_NUMBER * cp1Dy;
CGFloat cp2x = outerRadius * outerRoundness * kPOLYGON_MAGIC_NUMBER * cp2Dx;
CGFloat cp2y = outerRadius * outerRoundness * kPOLYGON_MAGIC_NUMBER * cp2Dy;
[path LOT_addCurveToPoint:CGPointMake(x, y)
controlPoint1:CGPointMake(previousX - cp1x, previousY - cp1y)
controlPoint2:CGPointMake(x + cp2x, y + cp2y)];
} else {
[path LOT_addLineToPoint:CGPointMake(x, y)];
}
currentAngle += anglePerPoint;
}
[path LOT_closePath];
[path LOT_applyTransform:CGAffineTransformMakeTranslation(position.x, position.y)];
self.localPath = path;
}
@end
//
// LOTPolystarAnimator.h
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatorNode.h"
#import "LOTShapeStar.h"
@interface LOTPolystarAnimator : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeStar:(LOTShapeStar *_Nonnull)shapeStar;
@end
//
// LOTPolystarAnimator.m
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPolystarAnimator.h"
#import "LOTPointInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "LOTBezierPath.h"
#import "CGGeometry+LOTAdditions.h"
const CGFloat kPOLYSTAR_MAGIC_NUMBER = .47829f;
@implementation LOTPolystarAnimator {
LOTNumberInterpolator *_outerRadiusInterpolator;
LOTNumberInterpolator *_innerRadiusInterpolator;
LOTNumberInterpolator *_outerRoundnessInterpolator;
LOTNumberInterpolator *_innerRoundnessInterpolator;
LOTPointInterpolator *_positionInterpolator;
LOTNumberInterpolator *_pointsInterpolator;
LOTNumberInterpolator *_rotationInterpolator;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeStar:(LOTShapeStar *_Nonnull)shapeStar {
self = [super initWithInputNode:inputNode keyName:shapeStar.keyname];
if (self) {
_outerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRadius.keyframes];
_innerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRadius.keyframes];
_outerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRoundness.keyframes];
_innerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRoundness.keyframes];
_pointsInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.numberOfPoints.keyframes];
_rotationInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.rotation.keyframes];
_positionInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeStar.position.keyframes];
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Points" : _pointsInterpolator,
@"Position" : _positionInterpolator,
@"Rotation" : _rotationInterpolator,
@"Inner Radius" : _innerRadiusInterpolator,
@"Outer Radius" : _outerRadiusInterpolator,
@"Inner Roundness" : _innerRoundnessInterpolator,
@"Outer Roundness" : _outerRoundnessInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return ([_outerRadiusInterpolator hasUpdateForFrame:frame] ||
[_innerRadiusInterpolator hasUpdateForFrame:frame] ||
[_outerRoundnessInterpolator hasUpdateForFrame:frame] ||
[_innerRoundnessInterpolator hasUpdateForFrame:frame] ||
[_pointsInterpolator hasUpdateForFrame:frame] ||
[_rotationInterpolator hasUpdateForFrame:frame] ||
[_positionInterpolator hasUpdateForFrame:frame]);
}
- (void)performLocalUpdate {
CGFloat outerRadius = [_outerRadiusInterpolator floatValueForFrame:self.currentFrame];
CGFloat innerRadius = [_innerRadiusInterpolator floatValueForFrame:self.currentFrame];
CGFloat outerRoundness = [_outerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
CGFloat innerRoundness = [_innerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
CGFloat points = [_pointsInterpolator floatValueForFrame:self.currentFrame];
CGFloat rotation = [_rotationInterpolator floatValueForFrame:self.currentFrame];
CGPoint position = [_positionInterpolator pointValueForFrame:self.currentFrame];
LOTBezierPath *path = [[LOTBezierPath alloc] init];
path.cacheLengths = self.pathShouldCacheLengths;
CGFloat currentAngle = LOT_DegreesToRadians(rotation - 90);
CGFloat anglePerPoint = (CGFloat)((2 * M_PI) / points);
CGFloat halfAnglePerPoint = anglePerPoint / 2.0f;
CGFloat partialPointAmount = points - floor(points);
if (partialPointAmount != 0) {
currentAngle += halfAnglePerPoint * (1.f - partialPointAmount);
}
CGFloat x;
CGFloat y;
CGFloat previousX;
CGFloat previousY;
CGFloat partialPointRadius = 0;
if (partialPointAmount != 0) {
partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
x = (CGFloat) (partialPointRadius * cosf(currentAngle));
y = (CGFloat) (partialPointRadius * sinf(currentAngle));
[path LOT_moveToPoint:CGPointMake(x, y)];
currentAngle += anglePerPoint * partialPointAmount / 2.f;
} else {
x = (float) (outerRadius * cosf(currentAngle));
y = (float) (outerRadius * sinf(currentAngle));
[path LOT_moveToPoint:CGPointMake(x, y)];
currentAngle += halfAnglePerPoint;
}
// True means the line will go to outer radius. False means inner radius.
BOOL longSegment = false;
CGFloat numPoints = ceil(points) * 2;
for (int i = 0; i < numPoints; i++) {
CGFloat radius = longSegment ? outerRadius : innerRadius;
CGFloat dTheta = halfAnglePerPoint;
if (partialPointRadius != 0 && i == numPoints - 2) {
dTheta = anglePerPoint * partialPointAmount / 2.f;
}
if (partialPointRadius != 0 && i == numPoints - 1) {
radius = partialPointRadius;
}
previousX = x;
previousY = y;
x = (CGFloat) (radius * cosf(currentAngle));
y = (CGFloat) (radius * sinf(currentAngle));
if (innerRoundness == 0 && outerRoundness == 0) {
[path LOT_addLineToPoint:CGPointMake(x, y)];
} else {
CGFloat cp1Theta = (CGFloat) (atan2f(previousY, previousX) - M_PI / 2.f);
CGFloat cp1Dx = (CGFloat) cosf(cp1Theta);
CGFloat cp1Dy = (CGFloat) sinf(cp1Theta);
CGFloat cp2Theta = (CGFloat) (atan2f(y, x) - M_PI / 2.f);
CGFloat cp2Dx = (CGFloat) cosf(cp2Theta);
CGFloat cp2Dy = (CGFloat) sinf(cp2Theta);
CGFloat cp1Roundedness = longSegment ? innerRoundness : outerRoundness;
CGFloat cp2Roundedness = longSegment ? outerRoundness : innerRoundness;
CGFloat cp1Radius = longSegment ? innerRadius : outerRadius;
CGFloat cp2Radius = longSegment ? outerRadius : innerRadius;
CGFloat cp1x = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dx;
CGFloat cp1y = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dy;
CGFloat cp2x = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dx;
CGFloat cp2y = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dy;
if (partialPointAmount != 0) {
if (i == 0) {
cp1x *= partialPointAmount;
cp1y *= partialPointAmount;
} else if (i == numPoints - 1) {
cp2x *= partialPointAmount;
cp2y *= partialPointAmount;
}
}
[path LOT_addCurveToPoint:CGPointMake(x, y)
controlPoint1:CGPointMake(previousX - cp1x, previousY - cp1y)
controlPoint2:CGPointMake(x + cp2x, y + cp2y)];
}
currentAngle += dTheta;
longSegment = !longSegment;
}
[path LOT_closePath];
[path LOT_applyTransform:CGAffineTransformMakeTranslation(position.x, position.y)];
self.localPath = path;
}
@end
//
// LOTRoundedRectAnimator.h
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatorNode.h"
#import "LOTShapeRectangle.h"
@interface LOTRoundedRectAnimator : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeRectangle:(LOTShapeRectangle *_Nonnull)shapeRectangle;
@end
//
// LOTRoundedRectAnimator.m
// Lottie
//
// Created by brandon_withrow on 7/19/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRoundedRectAnimator.h"
#import "LOTPointInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTRoundedRectAnimator {
LOTPointInterpolator *_centerInterpolator;
LOTPointInterpolator *_sizeInterpolator;
LOTNumberInterpolator *_cornerRadiusInterpolator;
BOOL _reversed;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeRectangle:(LOTShapeRectangle *_Nonnull)shapeRectangle {
self = [super initWithInputNode:inputNode keyName:shapeRectangle.keyname];
if (self) {
_centerInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.position.keyframes];
_sizeInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.size.keyframes];
_cornerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeRectangle.cornerRadius.keyframes];
_reversed = shapeRectangle.reversed;
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Size" : _sizeInterpolator,
@"Position" : _centerInterpolator,
@"Roundness" : _cornerRadiusInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return [_centerInterpolator hasUpdateForFrame:frame] || [_sizeInterpolator hasUpdateForFrame:frame] || [_cornerRadiusInterpolator hasUpdateForFrame:frame];
}
- (void)addCorner:(CGPoint)cornerPoint withRadius:(CGFloat)radius toPath:(LOTBezierPath *)path clockwise:(BOOL)clockwise {
CGPoint currentPoint = path.currentPoint;
CGFloat ellipseControlPointPercentage = 0.55228;
if (cornerPoint.y == currentPoint.y) {
// Moving east/west
if (cornerPoint.x < currentPoint.x) {
// Moving west
CGPoint corner = CGPointMake(cornerPoint.x + radius, currentPoint.y);
[path LOT_addLineToPoint:corner];
if (radius) {
CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y - radius) : CGPointMake(cornerPoint.x, cornerPoint.y + radius);
CGPoint cp1 = CGPointMake(corner.x - (radius * ellipseControlPointPercentage), corner.y);
CGPoint cp2 = (clockwise ?
CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage)) :
CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage)));
[path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
}
} else {
// Moving east
CGPoint corner = CGPointMake(cornerPoint.x - radius, currentPoint.y);
[path LOT_addLineToPoint:corner];
if (radius) {
CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y + radius) : CGPointMake(cornerPoint.x, cornerPoint.y - radius);
CGPoint cp1 = CGPointMake(corner.x + (radius * ellipseControlPointPercentage), corner.y);
CGPoint cp2 = (clockwise ?
CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage)) :
CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage)));
[path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
}
}
} else {
// Moving North/South
if (cornerPoint.y < currentPoint.y) {
// Moving North
CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y + radius);
[path LOT_addLineToPoint:corner];
if (radius) {
CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x + radius, cornerPoint.y) : CGPointMake(cornerPoint.x - radius, cornerPoint.y);
CGPoint cp1 = CGPointMake(corner.x, corner.y - (radius * ellipseControlPointPercentage));
CGPoint cp2 = (clockwise ?
CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y) :
CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y));
[path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
}
} else {
// moving south
CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y - radius);
[path LOT_addLineToPoint:corner];
if (radius) {
CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x - radius, cornerPoint.y) : CGPointMake(cornerPoint.x + radius, cornerPoint.y);
CGPoint cp1 = CGPointMake(corner.x, corner.y + (radius * ellipseControlPointPercentage));
CGPoint cp2 = (clockwise ?
CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y) :
CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y));
[path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
}
}
}
}
- (void)performLocalUpdate {
CGFloat cornerRadius = [_cornerRadiusInterpolator floatValueForFrame:self.currentFrame];
CGPoint size = [_sizeInterpolator pointValueForFrame:self.currentFrame];
CGPoint position = [_centerInterpolator pointValueForFrame:self.currentFrame];
CGFloat halfWidth = size.x / 2;
CGFloat halfHeight = size.y / 2;
CGRect rectFrame = CGRectMake(position.x - halfWidth, position.y - halfHeight, size.x, size.y);
CGPoint topLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMinY(rectFrame));
CGPoint topRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMinY(rectFrame));
CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMaxY(rectFrame));
CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMaxY(rectFrame));
// UIBezierPath Draws rects from the top left corner, After Effects draws them from the top right.
// Switching to manual drawing.
CGFloat radius = MIN(MIN(halfWidth, halfHeight), cornerRadius);
BOOL clockWise = !_reversed;
LOTBezierPath *path1 = [[LOTBezierPath alloc] init];
path1.cacheLengths = self.pathShouldCacheLengths;
CGPoint startPoint = (clockWise ?
CGPointMake(topRight.x, topRight.y + radius) :
CGPointMake(topRight.x - radius, topRight.y));
[path1 LOT_moveToPoint:startPoint];
if (clockWise) {
[self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise];
} else {
[self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise];
[self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise];
}
[path1 LOT_closePath];
self.localPath = path1;
}
@end
//
// LOTArrayInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueInterpolator.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTArrayInterpolator : LOTValueInterpolator
- (NSArray *)numberArrayForFrame:(NSNumber *)frame;
@end
NS_ASSUME_NONNULL_END
//
// LOTArrayInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTArrayInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTArrayInterpolator
- (NSArray *)numberArrayForFrame:(NSNumber *)frame {
CGFloat progress = [self progressForFrame:frame];
if (progress == 0) {
return self.leadingKeyframe.arrayValue;
}
if (progress == 1) {
return self.trailingKeyframe.arrayValue;
}
NSMutableArray *returnArray = [NSMutableArray array];
for (int i = 0; i < self.leadingKeyframe.arrayValue.count; i ++) {
CGFloat from = [(NSNumber *)self.leadingKeyframe.arrayValue[i] floatValue];
CGFloat to = [(NSNumber *)self.trailingKeyframe.arrayValue[i] floatValue];
CGFloat value = LOT_RemapValue(progress, 0, 1, from, to);
[returnArray addObject:@(value)];
}
return returnArray;
}
@end
//
// LOTColorInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueInterpolator.h"
#import "LOTPlatformCompat.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTColorInterpolator : LOTValueInterpolator
- (CGColorRef)colorForFrame:(NSNumber *)frame;
@property (nonatomic, weak, nullable) id<LOTColorValueDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
//
// LOTColorInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTColorInterpolator.h"
#import "LOTPlatformCompat.h"
#import "UIColor+Expanded.h"
@implementation LOTColorInterpolator
- (CGColorRef)colorForFrame:(NSNumber *)frame {
CGFloat progress = [self progressForFrame:frame];
UIColor *returnColor;
if (progress == 0) {
returnColor = self.leadingKeyframe.colorValue;
} else if (progress == 1) {
returnColor = self.trailingKeyframe.colorValue;
} else {
returnColor = [UIColor LOT_colorByLerpingFromColor:self.leadingKeyframe.colorValue toColor:self.trailingKeyframe.colorValue amount:progress];
}
if (self.hasDelegateOverride) {
return [self.delegate colorForFrame:frame.floatValue
startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
interpolatedProgress:progress
startColor:self.leadingKeyframe.colorValue.CGColor
endColor:self.trailingKeyframe.colorValue.CGColor
currentColor:returnColor.CGColor];
}
return returnColor.CGColor;
}
- (void)setValueDelegate:(id<LOTValueDelegate>)delegate {
NSAssert(([delegate conformsToProtocol:@protocol(LOTColorValueDelegate)]), @"Color Interpolator set with incorrect callback type. Expected LOTColorValueDelegate");
self.delegate = (id<LOTColorValueDelegate>)delegate;
}
- (BOOL)hasDelegateOverride {
return self.delegate != nil;
}
@end
//
// LOTNumberInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/11/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTValueInterpolator.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTNumberInterpolator : LOTValueInterpolator
- (CGFloat)floatValueForFrame:(NSNumber *)frame;
@property (nonatomic, weak, nullable) id<LOTNumberValueDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
//
// LOTNumberInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/11/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTNumberInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTNumberInterpolator
- (CGFloat)floatValueForFrame:(NSNumber *)frame {
CGFloat progress = [self progressForFrame:frame];
CGFloat returnValue;
if (progress == 0) {
returnValue = self.leadingKeyframe.floatValue;
} else if (progress == 1) {
returnValue = self.trailingKeyframe.floatValue;
} else {
returnValue = LOT_RemapValue(progress, 0, 1, self.leadingKeyframe.floatValue, self.trailingKeyframe.floatValue);
}
if (self.hasDelegateOverride) {
return [self.delegate floatValueForFrame:frame.floatValue
startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
interpolatedProgress:progress
startValue:self.leadingKeyframe.floatValue
endValue:self.trailingKeyframe.floatValue
currentValue:returnValue];
}
return returnValue;
}
- (BOOL)hasDelegateOverride {
return self.delegate != nil;
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate {
NSAssert(([delegate conformsToProtocol:@protocol(LOTNumberValueDelegate)]), @"Number Interpolator set with incorrect callback type. Expected LOTNumberValueDelegate");
self.delegate = (id<LOTNumberValueDelegate>)delegate;
}
@end
//
// LOTPathInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueInterpolator.h"
#import "LOTPlatformCompat.h"
#import "LOTBezierPath.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTPathInterpolator : LOTValueInterpolator
- (LOTBezierPath *)pathForFrame:(NSNumber *)frame cacheLengths:(BOOL)cacheLengths;
@property (nonatomic, weak, nullable) id<LOTPathValueDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
//
// LOTPathInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPathInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTPathInterpolator
- (LOTBezierPath *)pathForFrame:(NSNumber *)frame cacheLengths:(BOOL)cacheLengths {
CGFloat progress = [self progressForFrame:frame];
if (self.hasDelegateOverride) {
CGPathRef callBackPath = [self.delegate pathForFrame:frame.floatValue
startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
interpolatedProgress:progress];
return [LOTBezierPath pathWithCGPath:callBackPath];
}
LOTBezierPath *returnPath = [[LOTBezierPath alloc] init];
returnPath.cacheLengths = cacheLengths;
LOTBezierData *leadingData = self.leadingKeyframe.pathData;
LOTBezierData *trailingData = self.trailingKeyframe.pathData;
NSInteger vertexCount = leadingData ? leadingData.count : trailingData.count;
BOOL closePath = leadingData ? leadingData.closed : trailingData.closed;
CGPoint cp1 = CGPointMake(0, 0);
CGPoint cp2, p1, cp3 = CGPointZero;
CGPoint startPoint = CGPointMake(0, 0);
CGPoint startInTangent = CGPointMake(0, 0);
for (int i = 0; i < vertexCount; i++) {
if (progress == 0) {
cp2 = [leadingData inTangentAtIndex:i];
p1 = [leadingData vertexAtIndex:i];
cp3 = [leadingData outTangentAtIndex:i];
} else if (progress == 1) {
cp2 = [trailingData inTangentAtIndex:i];
p1 = [trailingData vertexAtIndex:i];
cp3 = [trailingData outTangentAtIndex:i];
} else {
cp2 = LOT_PointInLine([leadingData inTangentAtIndex:i],
[trailingData inTangentAtIndex:i],
progress);
p1 = LOT_PointInLine([leadingData vertexAtIndex:i],
[trailingData vertexAtIndex:i],
progress);
cp3 = LOT_PointInLine([leadingData outTangentAtIndex:i],
[trailingData outTangentAtIndex:i],
progress);
}
if (i == 0) {
startPoint = p1;
startInTangent = cp2;
[returnPath LOT_moveToPoint:p1];
} else {
[returnPath LOT_addCurveToPoint:p1 controlPoint1:cp1 controlPoint2:cp2];
}
cp1 = cp3;
}
if (closePath) {
[returnPath LOT_addCurveToPoint:startPoint controlPoint1:cp3 controlPoint2:startInTangent];
[returnPath LOT_closePath];
}
return returnPath;
}
- (void)setValueDelegate:(id<LOTValueDelegate>)delegate {
NSAssert(([delegate conformsToProtocol:@protocol(LOTPathValueDelegate)]), @"Path Interpolator set with incorrect callback type. Expected LOTPathValueDelegate");
self.delegate = (id<LOTPathValueDelegate>)delegate;
}
- (BOOL)hasDelegateOverride {
return self.delegate != nil;
}
@end
//
// LOTPointInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/12/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueInterpolator.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTPointInterpolator : LOTValueInterpolator
- (CGPoint)pointValueForFrame:(NSNumber *)frame;
@property (nonatomic, weak, nullable) id<LOTPointValueDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
//
// LOTPointInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/12/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPointInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTPointInterpolator
- (CGPoint)pointValueForFrame:(NSNumber *)frame {
CGFloat progress = [self progressForFrame:frame];
CGPoint returnPoint;
if (progress == 0) {
returnPoint = self.leadingKeyframe.pointValue;
} else if (progress == 1) {
returnPoint = self.trailingKeyframe.pointValue;
} else if (!CGPointEqualToPoint(self.leadingKeyframe.spatialOutTangent, CGPointZero) ||
!CGPointEqualToPoint(self.trailingKeyframe.spatialInTangent, CGPointZero)) {
// Spatial Bezier path
CGPoint outTan = LOT_PointAddedToPoint(self.leadingKeyframe.pointValue, self.leadingKeyframe.spatialOutTangent);
CGPoint inTan = LOT_PointAddedToPoint(self.trailingKeyframe.pointValue, self.trailingKeyframe.spatialInTangent);
returnPoint = LOT_PointInCubicCurve(self.leadingKeyframe.pointValue, outTan, inTan, self.trailingKeyframe.pointValue, progress);
} else {
returnPoint = LOT_PointInLine(self.leadingKeyframe.pointValue, self.trailingKeyframe.pointValue, progress);
}
if (self.hasDelegateOverride) {
return [self.delegate pointForFrame:frame.floatValue
startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
interpolatedProgress:progress
startPoint:self.leadingKeyframe.pointValue
endPoint:self.trailingKeyframe.pointValue
currentPoint:returnPoint];
}
return returnPoint;
}
- (BOOL)hasDelegateOverride {
return self.delegate != nil;
}
- (void)setValueDelegate:(id<LOTValueDelegate>)delegate {
NSAssert(([delegate conformsToProtocol:@protocol(LOTPointValueDelegate)]), @"Point Interpolator set with incorrect callback type. Expected LOTPointValueDelegate");
self.delegate = (id<LOTPointValueDelegate>)delegate;
}
@end
//
// LOTSizeInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTValueInterpolator.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTSizeInterpolator : LOTValueInterpolator
- (CGSize)sizeValueForFrame:(NSNumber *)frame;
@property (nonatomic, weak, nullable) id<LOTSizeValueDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
//
// LOTSizeInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/13/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTPlatformCompat.h"
#import "LOTSizeInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTSizeInterpolator
- (CGSize)sizeValueForFrame:(NSNumber *)frame {
CGFloat progress = [self progressForFrame:frame];
CGSize returnSize;
if (progress == 0) {
returnSize = self.leadingKeyframe.sizeValue;
}else if (progress == 1) {
returnSize = self.trailingKeyframe.sizeValue;
} else {
returnSize = CGSizeMake(LOT_RemapValue(progress, 0, 1, self.leadingKeyframe.sizeValue.width, self.trailingKeyframe.sizeValue.width),
LOT_RemapValue(progress, 0, 1, self.leadingKeyframe.sizeValue.height, self.trailingKeyframe.sizeValue.height));
}
if (self.hasDelegateOverride) {
return [self.delegate sizeForFrame:frame.floatValue
startKeyframe:self.leadingKeyframe.keyframeTime.floatValue
endKeyframe:self.trailingKeyframe.keyframeTime.floatValue
interpolatedProgress:progress startSize:self.leadingKeyframe.sizeValue
endSize:self.trailingKeyframe.sizeValue
currentSize:returnSize];
}
return returnSize;
}
- (BOOL)hasDelegateOverride {
return self.delegate != nil;
}
- (void)setValueDelegate:(id<LOTValueDelegate>)delegate {
NSAssert(([delegate conformsToProtocol:@protocol(LOTSizeValueDelegate)]), @"Size Interpolator set with incorrect callback type. Expected LOTSizeValueDelegate");
self.delegate = (id<LOTSizeValueDelegate>)delegate;
}
@end
//
// LOTTransformInterpolator.h
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LOTNumberInterpolator.h"
#import "LOTPointInterpolator.h"
#import "LOTSizeInterpolator.h"
#import "LOTKeyframe.h"
#import "LOTLayer.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTTransformInterpolator : NSObject
+ (instancetype)transformForLayer:(LOTLayer *)layer;
- (instancetype)initWithPosition:(NSArray <LOTKeyframe *> *)position
rotation:(NSArray <LOTKeyframe *> *)rotation
anchor:(NSArray <LOTKeyframe *> *)anchor
scale:(NSArray <LOTKeyframe *> *)scale;
- (instancetype)initWithPositionX:(NSArray <LOTKeyframe *> *)positionX
positionY:(NSArray <LOTKeyframe *> *)positionY
rotation:(NSArray <LOTKeyframe *> *)rotation
anchor:(NSArray <LOTKeyframe *> *)anchor
scale:(NSArray <LOTKeyframe *> *)scale;
@property (nonatomic, strong) LOTTransformInterpolator * inputNode;
@property (nonatomic, readonly) LOTPointInterpolator *positionInterpolator;
@property (nonatomic, readonly) LOTPointInterpolator *anchorInterpolator;
@property (nonatomic, readonly) LOTSizeInterpolator *scaleInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *rotationInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionXInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionYInterpolator;
@property (nonatomic, strong, nullable) NSString *parentKeyName;
- (CATransform3D)transformForFrame:(NSNumber *)frame;
- (BOOL)hasUpdateForFrame:(NSNumber *)frame;
@end
NS_ASSUME_NONNULL_END
//
// LOTTransformInterpolator.m
// Lottie
//
// Created by brandon_withrow on 7/18/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTTransformInterpolator.h"
// TODO BW Perf update, Cache transform
@implementation LOTTransformInterpolator {
LOTPointInterpolator *_positionInterpolator;
LOTPointInterpolator *_anchorInterpolator;
LOTSizeInterpolator *_scaleInterpolator;
LOTNumberInterpolator *_rotationInterpolator;
LOTNumberInterpolator *_positionXInterpolator;
LOTNumberInterpolator *_positionYInterpolator;
}
+ (instancetype)transformForLayer:(LOTLayer *)layer {
LOTTransformInterpolator *interpolator = nil;
if (layer.position) {
interpolator = [[LOTTransformInterpolator alloc] initWithPosition:layer.position.keyframes
rotation:layer.rotation.keyframes
anchor:layer.anchor.keyframes
scale:layer.scale.keyframes];
} else {
interpolator = [[LOTTransformInterpolator alloc] initWithPositionX:layer.positionX.keyframes
positionY:layer.positionY.keyframes
rotation:layer.rotation.keyframes
anchor:layer.anchor.keyframes
scale:layer.scale.keyframes];
}
interpolator.parentKeyName = [layer.layerName copy];
return interpolator;
}
- (instancetype)initWithPosition:(NSArray <LOTKeyframe *> *)position
rotation:(NSArray <LOTKeyframe *> *)rotation
anchor:(NSArray <LOTKeyframe *> *)anchor
scale:(NSArray <LOTKeyframe *> *)scale {
self = [super init];
if (self) {
[self initializeWithPositionX:nil positionY:nil position:position rotation:rotation anchor:anchor scale:scale];
}
return self;
}
- (instancetype)initWithPositionX:(NSArray <LOTKeyframe *> *)positionX
positionY:(NSArray <LOTKeyframe *> *)positionY
rotation:(NSArray <LOTKeyframe *> *)rotation
anchor:(NSArray <LOTKeyframe *> *)anchor
scale:(NSArray <LOTKeyframe *> *)scale {
self = [super init];
if (self) {
[self initializeWithPositionX:positionX positionY:positionY position:nil rotation:rotation anchor:anchor scale:scale];
}
return self;
}
- (void)initializeWithPositionX:(NSArray <LOTKeyframe *> *)positionX
positionY:(NSArray <LOTKeyframe *> *)positionY
position:(NSArray <LOTKeyframe *> *)position
rotation:(NSArray <LOTKeyframe *> *)rotation
anchor:(NSArray <LOTKeyframe *> *)anchor
scale:(NSArray <LOTKeyframe *> *)scale {
if (position) {
_positionInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:position];
}
if (positionY) {
_positionYInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:positionY];
}
if (positionX) {
_positionXInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:positionX];
}
_anchorInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:anchor];
_scaleInterpolator = [[LOTSizeInterpolator alloc] initWithKeyframes:scale];
_rotationInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:rotation];
}
- (BOOL)hasUpdateForFrame:(NSNumber *)frame {
BOOL inputUpdate = _inputNode ? [_inputNode hasUpdateForFrame:frame] : NO;
if (inputUpdate) {
return inputUpdate;
}
if (_positionInterpolator) {
return ([_positionInterpolator hasUpdateForFrame:frame] ||
[_anchorInterpolator hasUpdateForFrame:frame] ||
[_scaleInterpolator hasUpdateForFrame:frame] ||
[_rotationInterpolator hasUpdateForFrame:frame]);
}
return ([_positionXInterpolator hasUpdateForFrame:frame] ||
[_positionYInterpolator hasUpdateForFrame:frame] ||
[_anchorInterpolator hasUpdateForFrame:frame] ||
[_scaleInterpolator hasUpdateForFrame:frame] ||
[_rotationInterpolator hasUpdateForFrame:frame]);
}
- (CATransform3D)transformForFrame:(NSNumber *)frame {
CATransform3D baseXform = CATransform3DIdentity;
if (_inputNode) {
baseXform = [_inputNode transformForFrame:frame];
}
CGPoint position = CGPointZero;
if (_positionInterpolator) {
position = [_positionInterpolator pointValueForFrame:frame];
}
if (_positionXInterpolator &&
_positionYInterpolator) {
position.x = [_positionXInterpolator floatValueForFrame:frame];
position.y = [_positionYInterpolator floatValueForFrame:frame];
}
CGPoint anchor = [_anchorInterpolator pointValueForFrame:frame];
CGSize scale = [_scaleInterpolator sizeValueForFrame:frame];
CGFloat rotation = [_rotationInterpolator floatValueForFrame:frame];
CATransform3D translateXform = CATransform3DTranslate(baseXform, position.x, position.y, 0);
CATransform3D rotateXform = CATransform3DRotate(translateXform, rotation, 0, 0, 1);
CATransform3D scaleXform = CATransform3DScale(rotateXform, scale.width, scale.height, 1);
CATransform3D anchorXform = CATransform3DTranslate(scaleXform, -1 * anchor.x, -1 * anchor.y, 0);
return anchorXform;
}
@end
//
// LOTValueInterpolator.h
// Pods
//
// Created by brandon_withrow on 7/10/17.
//
//
#import <Foundation/Foundation.h>
#import "LOTKeyframe.h"
#import "LOTValueDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface LOTValueInterpolator : NSObject
- (instancetype)initWithKeyframes:(NSArray <LOTKeyframe *> *)keyframes;
@property (nonatomic, weak, nullable) LOTKeyframe *leadingKeyframe;
@property (nonatomic, weak, nullable) LOTKeyframe *trailingKeyframe;
@property (nonatomic, readonly) BOOL hasDelegateOverride;
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate;
- (BOOL)hasUpdateForFrame:(NSNumber *)frame;
- (CGFloat)progressForFrame:(NSNumber *)frame;
@end
NS_ASSUME_NONNULL_END
//
// LOTValueInterpolator.m
// Pods
//
// Created by brandon_withrow on 7/10/17.
//
//
#import "LOTValueInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
@interface LOTValueInterpolator ()
@property (nonatomic, strong) NSArray<LOTKeyframe *> *keyframes;
@end
@implementation LOTValueInterpolator
- (instancetype)initWithKeyframes:(NSArray <LOTKeyframe *> *)keyframes {
self = [super init];
if (self) {
_keyframes = keyframes;
}
return self;
}
- (BOOL)hasUpdateForFrame:(NSNumber *)frame {
if (self.hasDelegateOverride) {
return YES;
}
/*
Cases we dont update keyframe
if time is in span and leading keyframe is hold
if trailing keyframe is nil and time is after leading
if leading keyframe is nil and time is before trailing
*/
if (self.leadingKeyframe &&
self.trailingKeyframe == nil &&
self.leadingKeyframe.keyframeTime.floatValue < frame.floatValue) {
// Frame is after bounds of keyframes. Clip
return NO;
}
if (self.trailingKeyframe &&
self.leadingKeyframe == nil &&
self.trailingKeyframe.keyframeTime.floatValue > frame.floatValue) {
// Frame is before keyframes bounds. Clip.
return NO;
}
if (self.leadingKeyframe && self.trailingKeyframe &&
self.leadingKeyframe.isHold &&
self.leadingKeyframe.keyframeTime.floatValue < frame.floatValue &&
self.trailingKeyframe.keyframeTime.floatValue > frame.floatValue) {
// Frame is in span and current span is a hold keyframe
return NO;
}
return YES;
}
- (void)updateKeyframeSpanForFrame:(NSNumber *)frame {
if (self.leadingKeyframe == nil &&
self.trailingKeyframe == nil) {
// Set Initial Keyframes
LOTKeyframe *first = _keyframes.firstObject;
if (first.keyframeTime.floatValue > 0) {
self.trailingKeyframe = first;
} else {
self.leadingKeyframe = first;
if (_keyframes.count > 1) {
self.trailingKeyframe = _keyframes[1];
}
}
}
if (self.trailingKeyframe && frame.floatValue >= self.trailingKeyframe.keyframeTime.floatValue) {
// Frame is after current span, can move forward
NSInteger index = [_keyframes indexOfObject:self.trailingKeyframe];
BOOL keyframeFound = NO;
LOTKeyframe *testLeading = self.trailingKeyframe;
LOTKeyframe *testTrailing = nil;
while (keyframeFound == NO) {
index ++;
if (index < _keyframes.count) {
testTrailing = _keyframes[index];
if (frame.floatValue < testTrailing.keyframeTime.floatValue) {
// This is the span.
keyframeFound = YES;
} else {
testLeading = testTrailing;
}
} else {
// Leading is Last object
testTrailing = nil;
keyframeFound = YES;
}
}
self.leadingKeyframe = testLeading;
self.trailingKeyframe = testTrailing;
} else if (self.leadingKeyframe && frame.floatValue < self.leadingKeyframe.keyframeTime.floatValue) {
// Frame is before current span, can move back a span
NSInteger index = [_keyframes indexOfObject:self.leadingKeyframe];
BOOL keyframeFound = NO;
LOTKeyframe *testLeading = nil;
LOTKeyframe *testTrailing = self.leadingKeyframe;
while (keyframeFound == NO) {
index --;
if (index >= 0) {
testLeading = _keyframes[index];
if (frame.floatValue >= testLeading.keyframeTime.floatValue) {
// This is the span.
keyframeFound = YES;
} else {
testTrailing = testLeading;
}
} else {
// Trailing is first object
testLeading = nil;
keyframeFound = YES;
}
}
self.leadingKeyframe = testLeading;
self.trailingKeyframe = testTrailing;
}
}
- (CGFloat)progressForFrame:(NSNumber *)frame {
[self updateKeyframeSpanForFrame:frame];
// At this point frame definitely exists between leading and trailing keyframes
if (self.leadingKeyframe.keyframeTime == frame) {
// Frame is leading keyframe
return 0;
}
if (self.trailingKeyframe == nil) {
// Frame is after end of keyframe timeline
return 0;
}
if (self.leadingKeyframe.isHold) {
// Hold Keyframe
return 0;
}
if (self.leadingKeyframe == nil) {
// Frame is before start of keyframe timeline
return 1;
}
CGFloat progression = LOT_RemapValue(frame.floatValue, self.leadingKeyframe.keyframeTime.floatValue, self.trailingKeyframe.keyframeTime.floatValue, 0, 1);
if ((self.leadingKeyframe.outTangent.x != self.leadingKeyframe.outTangent.y ||
self.trailingKeyframe.inTangent.x != self.trailingKeyframe.inTangent.y) &&
(!LOT_CGPointIsZero(self.leadingKeyframe.outTangent) &&
!LOT_CGPointIsZero(self.trailingKeyframe.inTangent))) {
// Bezier Time Curve
progression = LOT_CubicBezierInterpolate(CGPointMake(0, 0), self.leadingKeyframe.outTangent, self.trailingKeyframe.inTangent, CGPointMake(1, 1), progression);
}
return progression;
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate {
NSAssert((NO), @"Interpolator does not support value callbacks");
}
@end
//
// LOTAnimatorNode.h
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import <Foundation/Foundation.h>
#import "LOTPlatformCompat.h"
#import "LOTBezierPath.h"
#import "LOTKeypath.h"
#import "LOTValueDelegate.h"
extern NSInteger indentation_level;
@interface LOTAnimatorNode : NSObject
/// Initializes the node with and optional input node and keyname.
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
keyName:(NSString *_Nullable)keyname;
/// A dictionary of the value interpolators this node controls
@property (nonatomic, readonly, strong) NSDictionary * _Nullable valueInterpolators;
/// The keyname of the node. Used for dynamically setting keyframe data.
@property (nonatomic, readonly, strong) NSString * _Nullable keyname;
/// The current time in frames
@property (nonatomic, readonly, strong) NSNumber * _Nullable currentFrame;
/// The upstream animator node
@property (nonatomic, readonly, strong) LOTAnimatorNode * _Nullable inputNode;
/// This nodes path in local object space
@property (nonatomic, strong) LOTBezierPath * _Nonnull localPath;
/// The sum of all paths in the tree including this node
@property (nonatomic, strong) LOTBezierPath * _Nonnull outputPath;
/// Returns true if this node needs to update its contents for the given frame. To be overwritten by subclasses.
- (BOOL)needsUpdateForFrame:(NSNumber *_Nonnull)frame;
/// Sets the current frame and performs any updates. Returns true if any updates were performed, locally or upstream.
- (BOOL)updateWithFrame:(NSNumber *_Nonnull)frame;
- (BOOL)updateWithFrame:(NSNumber *_Nonnull)frame
withModifierBlock:(void (^_Nullable)(LOTAnimatorNode * _Nonnull inputNode))modifier
forceLocalUpdate:(BOOL)forceUpdate;
- (void)forceSetCurrentFrame:(NSNumber *_Nonnull)frame;
@property (nonatomic, assign) BOOL pathShouldCacheLengths;
/// Update the local content for the frame.
- (void)performLocalUpdate;
/// Rebuild all outputs for the node. This is called after upstream updates have been performed.
- (void)rebuildOutputs;
- (void)logString:(NSString *_Nonnull)string;
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath;
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath;
@end
//
// LOTAnimatorNode.m
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import "LOTAnimatorNode.h"
#import "LOTHelpers.h"
#import "LOTValueInterpolator.h"
NSInteger indentation_level = 0;
@implementation LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
keyName:(NSString *_Nullable)keyname {
self = [super init];
if (self) {
_keyname = keyname;
_inputNode = inputNode;
}
return self;
}
/// To be overwritten by subclass. Defaults to YES
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return YES;
}
/// The node checks if local update or if upstream update required. If upstream update outputs are rebuilt. If local update local update is performed. Returns no if no action
- (BOOL)updateWithFrame:(NSNumber *_Nonnull)frame {
return [self updateWithFrame:frame withModifierBlock:NULL forceLocalUpdate:NO];
}
- (BOOL)updateWithFrame:(NSNumber *_Nonnull)frame
withModifierBlock:(void (^_Nullable)(LOTAnimatorNode * _Nonnull inputNode))modifier
forceLocalUpdate:(BOOL)forceUpdate {
if ([_currentFrame isEqual:frame] && !forceUpdate) {
return NO;
}
if (ENABLE_DEBUG_LOGGING) [self logString:[NSString stringWithFormat:@"%lu %@ Checking for update", (unsigned long)self.hash, self.keyname]];
BOOL localUpdate = [self needsUpdateForFrame:frame] || forceUpdate;
if (localUpdate && ENABLE_DEBUG_LOGGING) {
[self logString:[NSString stringWithFormat:@"%lu %@ Performing update", (unsigned long)self.hash, self.keyname]];
}
BOOL inputUpdated = [_inputNode updateWithFrame:frame
withModifierBlock:modifier
forceLocalUpdate:forceUpdate];
_currentFrame = frame;
if (localUpdate) {
[self performLocalUpdate];
if (modifier) {
modifier(self);
}
}
if (inputUpdated || localUpdate) {
[self rebuildOutputs];
}
return (inputUpdated || localUpdate);
}
- (void)forceSetCurrentFrame:(NSNumber *_Nonnull)frame {
_currentFrame = frame;
}
- (void)logString:(NSString *)string {
NSMutableString *logString = [NSMutableString string];
[logString appendString:@"|"];
for (int i = 0; i < indentation_level; i ++) {
[logString appendString:@" "];
}
[logString appendString:string];
NSLog(@"%@ %@", NSStringFromClass([self class]), logString);
}
// TOBO BW Perf, make updates perform only when necessary. Currently everything in a node is updated
/// Performs any local content update and updates self.localPath
- (void)performLocalUpdate {
self.localPath = [[LOTBezierPath alloc] init];
}
/// Rebuilds outputs by adding localPath to inputNodes output path.
- (void)rebuildOutputs {
if (self.inputNode) {
self.outputPath = [self.inputNode.outputPath copy];
[self.outputPath LOT_appendPath:self.localPath];
} else {
self.outputPath = self.localPath;
}
}
- (void)setPathShouldCacheLengths:(BOOL)pathShouldCacheLengths {
_pathShouldCacheLengths = pathShouldCacheLengths;
self.inputNode.pathShouldCacheLengths = pathShouldCacheLengths;
}
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
[self.inputNode searchNodesForKeypath:keypath];
if ([keypath pushKey:self.keyname]) {
// Matches self. Check interpolators
if (keypath.endOfKeypath) {
// Add self
[keypath addSearchResultForCurrentPath:self];
} else if (self.valueInterpolators[keypath.currentKey] != nil) {
[keypath pushKey:keypath.currentKey];
// We have a match!
[keypath addSearchResultForCurrentPath:self];
[keypath popKey];
}
[keypath popKey];
}
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath {
if ([keypath pushKey:self.keyname]) {
// Matches self. Check interpolators
LOTValueInterpolator *interpolator = self.valueInterpolators[keypath.currentKey];
if (interpolator) {
// We have a match!
[interpolator setValueDelegate:delegate];
}
[keypath popKey];
}
[self.inputNode setValueDelegate:delegate forKeypath:keypath];
}
@end
//
// LOTRenderNode.h
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import "LOTAnimatorNode.h"
@interface LOTRenderNode : LOTAnimatorNode
@property (nonatomic, readonly, strong) CAShapeLayer * _Nonnull outputLayer;
- (NSDictionary * _Nonnull)actionsForRenderLayer;
@end
//
// LOTRenderNode.m
// Pods
//
// Created by brandon_withrow on 6/27/17.
//
//
#import "LOTRenderNode.h"
@implementation LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
keyName:(NSString * _Nullable)keyname {
self = [super initWithInputNode:inputNode keyName:keyname];
if (self) {
_outputLayer = [CAShapeLayer new];
_outputLayer.actions = [self actionsForRenderLayer];
}
return self;
}
/// Layer Properties that need to disable implicit animations
- (NSDictionary * _Nonnull)actionsForRenderLayer {
return @{@"path": [NSNull null]};
}
/// Local interpolators have changed. Update layer specific properties.
- (void)performLocalUpdate {
}
/// The path for rendering has changed. Do any rendering required.
- (void)rebuildOutputs {
}
- (LOTBezierPath *)localPath {
return self.inputNode.localPath;
}
/// Forwards its input node's output path forwards downstream
- (LOTBezierPath *)outputPath {
return self.inputNode.outputPath;
}
@end
//
// LOTTrimPathNode.h
// Lottie
//
// Created by brandon_withrow on 7/21/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatorNode.h"
#import "LOTShapeTrimPath.h"
@interface LOTTrimPathNode : LOTAnimatorNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
trimPath:(LOTShapeTrimPath *_Nonnull)trimPath;
@end
//
// LOTTrimPathNode.m
// Lottie
//
// Created by brandon_withrow on 7/21/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTTrimPathNode.h"
#import "LOTNumberInterpolator.h"
#import "LOTPathAnimator.h"
#import "LOTCircleAnimator.h"
#import "LOTRoundedRectAnimator.h"
#import "LOTRenderGroup.h"
@implementation LOTTrimPathNode {
LOTNumberInterpolator *_startInterpolator;
LOTNumberInterpolator *_endInterpolator;
LOTNumberInterpolator *_offsetInterpolator;
CGFloat _startT;
CGFloat _endT;
CGFloat _offsetT;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
trimPath:(LOTShapeTrimPath *_Nonnull)trimPath {
self = [super initWithInputNode:inputNode keyName:trimPath.keyname];
if (self) {
inputNode.pathShouldCacheLengths = YES;
_startInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:trimPath.start.keyframes];
_endInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:trimPath.end.keyframes];
_offsetInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:trimPath.offset.keyframes];
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Start" : _startInterpolator,
@"End" : _endInterpolator,
@"Offset" : _offsetInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return ([_startInterpolator hasUpdateForFrame:frame] ||
[_endInterpolator hasUpdateForFrame:frame] ||
[_offsetInterpolator hasUpdateForFrame:frame]);
}
- (BOOL)updateWithFrame:(NSNumber *)frame
withModifierBlock:(void (^ _Nullable)(LOTAnimatorNode * _Nonnull))modifier
forceLocalUpdate:(BOOL)forceUpdate {
BOOL localUpdate = [self needsUpdateForFrame:frame];
[self forceSetCurrentFrame:frame];
if (localUpdate) {
[self performLocalUpdate];
}
if (self.inputNode == nil) {
return localUpdate;
}
BOOL inputUpdated = [self.inputNode updateWithFrame:frame withModifierBlock:^(LOTAnimatorNode * _Nonnull inputNode) {
if ([inputNode isKindOfClass:[LOTPathAnimator class]] ||
[inputNode isKindOfClass:[LOTCircleAnimator class]] ||
[inputNode isKindOfClass:[LOTRoundedRectAnimator class]]) {
[inputNode.localPath trimPathFromT:self->_startT toT:self->_endT offset:self->_offsetT];
}
if (modifier) {
modifier(inputNode);
}
} forceLocalUpdate:(localUpdate || forceUpdate)];
return inputUpdated;
}
- (void)performLocalUpdate {
_startT = [_startInterpolator floatValueForFrame:self.currentFrame] / 100;
_endT = [_endInterpolator floatValueForFrame:self.currentFrame] / 100;
_offsetT = [_offsetInterpolator floatValueForFrame:self.currentFrame] / 360;
}
- (void)rebuildOutputs {
// Skip this step.
}
- (LOTBezierPath *)localPath {
return self.inputNode.localPath;
}
/// Forwards its input node's output path forwards downstream
- (LOTBezierPath *)outputPath {
return self.inputNode.outputPath;
}
@end
//
// LOTFillRenderer.h
// Lottie
//
// Created by brandon_withrow on 6/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderNode.h"
#import "LOTShapeFill.h"
@interface LOTFillRenderer : LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeFill:(LOTShapeFill *_Nonnull)fill;
@end
//
// LOTFillRenderer.m
// Lottie
//
// Created by brandon_withrow on 6/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTFillRenderer.h"
#import "LOTColorInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "LOTHelpers.h"
@implementation LOTFillRenderer {
LOTColorInterpolator *colorInterpolator_;
LOTNumberInterpolator *opacityInterpolator_;
BOOL _evenOddFillRule;
CALayer *centerPoint_DEBUG;
}
- (instancetype)initWithInputNode:(LOTAnimatorNode *)inputNode
shapeFill:(LOTShapeFill *)fill {
self = [super initWithInputNode:inputNode keyName:fill.keyname];
if (self) {
colorInterpolator_ = [[LOTColorInterpolator alloc] initWithKeyframes:fill.color.keyframes];
opacityInterpolator_ = [[LOTNumberInterpolator alloc] initWithKeyframes:fill.opacity.keyframes];
centerPoint_DEBUG = [CALayer layer];
centerPoint_DEBUG.bounds = CGRectMake(0, 0, 20, 20);
if (ENABLE_DEBUG_SHAPES) {
[self.outputLayer addSublayer:centerPoint_DEBUG];
}
_evenOddFillRule = fill.evenOddFillRule;
self.outputLayer.fillRule = _evenOddFillRule ? @"even-odd" : @"non-zero";
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Color" : colorInterpolator_,
@"Opacity" : opacityInterpolator_};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return [colorInterpolator_ hasUpdateForFrame:frame] || [opacityInterpolator_ hasUpdateForFrame:frame];
}
- (void)performLocalUpdate {
centerPoint_DEBUG.backgroundColor = [colorInterpolator_ colorForFrame:self.currentFrame];
centerPoint_DEBUG.borderColor = [UIColor lightGrayColor].CGColor;
centerPoint_DEBUG.borderWidth = 2.f;
self.outputLayer.fillColor = [colorInterpolator_ colorForFrame:self.currentFrame];
self.outputLayer.opacity = [opacityInterpolator_ floatValueForFrame:self.currentFrame];
}
- (void)rebuildOutputs {
self.outputLayer.path = self.inputNode.outputPath.CGPath;
}
- (NSDictionary *)actionsForRenderLayer {
return @{@"backgroundColor": [NSNull null],
@"fillColor": [NSNull null],
@"opacity" : [NSNull null]};
}
@end
//
// LOTGradientFillRender.h
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderNode.h"
#import "LOTShapeGradientFill.h"
@interface LOTGradientFillRender : LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeGradientFill:(LOTShapeGradientFill *_Nonnull)fill;
@end
//
// LOTGradientFillRender.m
// Lottie
//
// Created by brandon_withrow on 7/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTGradientFillRender.h"
#import "LOTArrayInterpolator.h"
#import "LOTPointInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "CGGeometry+LOTAdditions.h"
#import "LOTHelpers.h"
#import "LOTRadialGradientLayer.h"
@implementation LOTGradientFillRender {
BOOL _evenOddFillRule;
CALayer *centerPoint_DEBUG;
CAShapeLayer *_maskShape;
LOTRadialGradientLayer *_gradientOpacityLayer;
LOTRadialGradientLayer *_gradientLayer;
NSInteger _numberOfPositions;
CGPoint _startPoint;
CGPoint _endPoint;
LOTArrayInterpolator *_gradientInterpolator;
LOTPointInterpolator *_startPointInterpolator;
LOTPointInterpolator *_endPointInterpolator;
LOTNumberInterpolator *_opacityInterpolator;
}
- (instancetype)initWithInputNode:(LOTAnimatorNode *)inputNode
shapeGradientFill:(LOTShapeGradientFill *)fill {
self = [super initWithInputNode:inputNode keyName:fill.keyname];
if (self) {
_gradientInterpolator = [[LOTArrayInterpolator alloc] initWithKeyframes:fill.gradient.keyframes];
_startPointInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:fill.startPoint.keyframes];
_endPointInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:fill.endPoint.keyframes];
_opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:fill.opacity.keyframes];
_numberOfPositions = fill.numberOfColors.integerValue;
_evenOddFillRule = fill.evenOddFillRule;
CALayer *wrapperLayer = [CALayer new];
_maskShape = [CAShapeLayer new];
_maskShape.fillRule = _evenOddFillRule ? @"even-odd" : @"non-zero";
_maskShape.fillColor = [UIColor whiteColor].CGColor;
_maskShape.actions = @{@"path": [NSNull null]};
_gradientOpacityLayer = [LOTRadialGradientLayer new];
_gradientOpacityLayer.isRadial = (fill.type == LOTGradientTypeRadial);
_gradientOpacityLayer.actions = @{@"startPoint" : [NSNull null],
@"endPoint" : [NSNull null],
@"opacity" : [NSNull null],
@"locations" : [NSNull null],
@"colors" : [NSNull null],
@"bounds" : [NSNull null],
@"anchorPoint" : [NSNull null],
@"isRadial" : [NSNull null]};
_gradientOpacityLayer.mask = _maskShape;
[wrapperLayer addSublayer:_gradientOpacityLayer];
_gradientLayer = [LOTRadialGradientLayer new];
_gradientLayer.isRadial = (fill.type == LOTGradientTypeRadial);
_gradientLayer.mask = wrapperLayer;
_gradientLayer.actions = [_gradientOpacityLayer.actions copy];
[self.outputLayer addSublayer:_gradientLayer];
centerPoint_DEBUG = [CALayer layer];
centerPoint_DEBUG.bounds = CGRectMake(0, 0, 20, 20);
if (ENABLE_DEBUG_SHAPES) {
[self.outputLayer addSublayer:centerPoint_DEBUG];
}
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Start Point" : _startPointInterpolator,
@"End Point" : _endPointInterpolator,
@"Opacity" : _opacityInterpolator};
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return ([_gradientInterpolator hasUpdateForFrame:frame] ||
[_startPointInterpolator hasUpdateForFrame:frame] ||
[_endPointInterpolator hasUpdateForFrame:frame] ||
[_opacityInterpolator hasUpdateForFrame:frame]);
}
- (void)performLocalUpdate {
centerPoint_DEBUG.backgroundColor = [UIColor magentaColor].CGColor;
centerPoint_DEBUG.borderColor = [UIColor lightGrayColor].CGColor;
centerPoint_DEBUG.borderWidth = 2.f;
_startPoint = [_startPointInterpolator pointValueForFrame:self.currentFrame];
_endPoint = [_endPointInterpolator pointValueForFrame:self.currentFrame];
self.outputLayer.opacity = [_opacityInterpolator floatValueForFrame:self.currentFrame];
NSArray *numberArray = [_gradientInterpolator numberArrayForFrame:self.currentFrame];
NSMutableArray *colorArray = [NSMutableArray array];
NSMutableArray *locationsArray = [NSMutableArray array];
NSMutableArray *opacityArray = [NSMutableArray array];
NSMutableArray *opacitylocationsArray = [NSMutableArray array];
for (int i = 0; i < _numberOfPositions; i++) {
int ix = i * 4;
NSNumber *location = numberArray[ix];
NSNumber *r = numberArray[(ix + 1)];
NSNumber *g = numberArray[(ix + 2)];
NSNumber *b = numberArray[(ix + 3)];
[locationsArray addObject:location];
UIColor *color = [UIColor colorWithRed:r.floatValue green:g.floatValue blue:b.floatValue alpha:1];
[colorArray addObject:(id)(color.CGColor)];
}
for (NSInteger i = (_numberOfPositions * 4); i < numberArray.count; i = i + 2) {
NSNumber *opacityLocation = numberArray[i];
[opacitylocationsArray addObject:opacityLocation];
NSNumber *opacity = numberArray[i + 1];
UIColor *opacityColor = [UIColor colorWithWhite:1 alpha:opacity.floatValue];
[opacityArray addObject:(id)(opacityColor.CGColor)];
}
if (opacityArray.count == 0) {
_gradientOpacityLayer.backgroundColor = [UIColor whiteColor].CGColor;
} else {
_gradientOpacityLayer.startPoint = _startPoint;
_gradientOpacityLayer.endPoint = _endPoint;
_gradientOpacityLayer.locations = opacitylocationsArray;
_gradientOpacityLayer.colors = opacityArray;
}
_gradientLayer.startPoint = _startPoint;
_gradientLayer.endPoint = _endPoint;
_gradientLayer.locations = locationsArray;
_gradientLayer.colors = colorArray;
}
- (void)rebuildOutputs {
CGRect frame = [self.inputNode.outputPath bounds];
CGPoint modifiedAnchor = CGPointMake(-frame.origin.x / frame.size.width,
-frame.origin.y / frame.size.height);
_maskShape.path = self.inputNode.outputPath.CGPath;
_gradientOpacityLayer.bounds = frame;
_gradientOpacityLayer.anchorPoint = modifiedAnchor;
_gradientLayer.bounds = frame;
_gradientLayer.anchorPoint = modifiedAnchor;
}
- (NSDictionary *)actionsForRenderLayer {
return @{@"backgroundColor": [NSNull null],
@"fillColor": [NSNull null],
@"opacity" : [NSNull null]};
}
@end
//
// LOTRenderGroup.h
// Lottie
//
// Created by brandon_withrow on 6/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderNode.h"
@interface LOTRenderGroup : LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode * _Nullable)inputNode
contents:(NSArray * _Nonnull)contents
keyname:(NSString * _Nullable)keyname;
@property (nonatomic, strong, readonly) CALayer * _Nonnull containerLayer;
@end
//
// LOTRenderGroup.m
// Lottie
//
// Created by brandon_withrow on 6/27/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderGroup.h"
#import "LOTModels.h"
#import "LOTPathAnimator.h"
#import "LOTFillRenderer.h"
#import "LOTStrokeRenderer.h"
#import "LOTNumberInterpolator.h"
#import "LOTTransformInterpolator.h"
#import "LOTCircleAnimator.h"
#import "LOTRoundedRectAnimator.h"
#import "LOTTrimPathNode.h"
#import "LOTShapeStar.h"
#import "LOTPolygonAnimator.h"
#import "LOTPolystarAnimator.h"
#import "LOTShapeGradientFill.h"
#import "LOTGradientFillRender.h"
#import "LOTRepeaterRenderer.h"
#import "LOTShapeRepeater.h"
@implementation LOTRenderGroup {
LOTAnimatorNode *_rootNode;
LOTBezierPath *_outputPath;
LOTBezierPath *_localPath;
BOOL _rootNodeHasUpdate;
LOTNumberInterpolator *_opacityInterpolator;
LOTTransformInterpolator *_transformInterpolator;
}
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode * _Nullable)inputNode
contents:(NSArray * _Nonnull)contents
keyname:(NSString * _Nullable)keyname {
self = [super initWithInputNode:inputNode keyName:keyname];
if (self) {
_containerLayer = [CALayer layer];
_containerLayer.actions = @{@"transform": [NSNull null],
@"opacity": [NSNull null]};
[self buildContents:contents];
}
return self;
}
- (NSDictionary *)valueInterpolators {
if (_opacityInterpolator && _transformInterpolator) {
return @{@"Opacity" : _opacityInterpolator,
@"Position" : _transformInterpolator.positionInterpolator,
@"Scale" : _transformInterpolator.scaleInterpolator,
@"Rotation" : _transformInterpolator.scaleInterpolator,
@"Anchor Point" : _transformInterpolator.anchorInterpolator,
// Deprecated
@"Transform.Opacity" : _opacityInterpolator,
@"Transform.Position" : _transformInterpolator.positionInterpolator,
@"Transform.Scale" : _transformInterpolator.scaleInterpolator,
@"Transform.Rotation" : _transformInterpolator.scaleInterpolator,
@"Transform.Anchor Point" : _transformInterpolator.anchorInterpolator
};
}
return nil;
}
- (void)buildContents:(NSArray *)contents {
LOTAnimatorNode *previousNode = nil;
LOTShapeTransform *transform;
for (id item in contents) {
if ([item isKindOfClass:[LOTShapeFill class]]) {
LOTFillRenderer *fillRenderer = [[LOTFillRenderer alloc] initWithInputNode:previousNode
shapeFill:(LOTShapeFill *)item];
[self.containerLayer insertSublayer:fillRenderer.outputLayer atIndex:0];
previousNode = fillRenderer;
} else if ([item isKindOfClass:[LOTShapeStroke class]]) {
LOTStrokeRenderer *strokRenderer = [[LOTStrokeRenderer alloc] initWithInputNode:previousNode
shapeStroke:(LOTShapeStroke *)item];
[self.containerLayer insertSublayer:strokRenderer.outputLayer atIndex:0];
previousNode = strokRenderer;
} else if ([item isKindOfClass:[LOTShapePath class]]) {
LOTPathAnimator *pathAnimator = [[LOTPathAnimator alloc] initWithInputNode:previousNode
shapePath:(LOTShapePath *)item];
previousNode = pathAnimator;
} else if ([item isKindOfClass:[LOTShapeRectangle class]]) {
LOTRoundedRectAnimator *rectAnimator = [[LOTRoundedRectAnimator alloc] initWithInputNode:previousNode
shapeRectangle:(LOTShapeRectangle *)item];
previousNode = rectAnimator;
} else if ([item isKindOfClass:[LOTShapeCircle class]]) {
LOTCircleAnimator *circleAnimator = [[LOTCircleAnimator alloc] initWithInputNode:previousNode
shapeCircle:(LOTShapeCircle *)item];
previousNode = circleAnimator;
} else if ([item isKindOfClass:[LOTShapeGroup class]]) {
LOTShapeGroup *shapeGroup = (LOTShapeGroup *)item;
LOTRenderGroup *renderGroup = [[LOTRenderGroup alloc] initWithInputNode:previousNode contents:shapeGroup.items keyname:shapeGroup.keyname];
[self.containerLayer insertSublayer:renderGroup.containerLayer atIndex:0];
previousNode = renderGroup;
} else if ([item isKindOfClass:[LOTShapeTransform class]]) {
transform = (LOTShapeTransform *)item;
} else if ([item isKindOfClass:[LOTShapeTrimPath class]]) {
LOTTrimPathNode *trim = [[LOTTrimPathNode alloc] initWithInputNode:previousNode trimPath:(LOTShapeTrimPath *)item];
previousNode = trim;
} else if ([item isKindOfClass:[LOTShapeStar class]]) {
LOTShapeStar *star = (LOTShapeStar *)item;
if (star.type == LOTPolystarShapeStar) {
LOTPolystarAnimator *starAnimator = [[LOTPolystarAnimator alloc] initWithInputNode:previousNode shapeStar:star];
previousNode = starAnimator;
}
if (star.type == LOTPolystarShapePolygon) {
LOTPolygonAnimator *polygonAnimator = [[LOTPolygonAnimator alloc] initWithInputNode:previousNode shapePolygon:star];
previousNode = polygonAnimator;
}
} else if ([item isKindOfClass:[LOTShapeGradientFill class]]) {
LOTGradientFillRender *gradientFill = [[LOTGradientFillRender alloc] initWithInputNode:previousNode shapeGradientFill:(LOTShapeGradientFill *)item];
previousNode = gradientFill;
[self.containerLayer insertSublayer:gradientFill.outputLayer atIndex:0];
} else if ([item isKindOfClass:[LOTShapeRepeater class]]) {
LOTRepeaterRenderer *repeater = [[LOTRepeaterRenderer alloc] initWithInputNode:previousNode shapeRepeater:(LOTShapeRepeater *)item];
previousNode = repeater;
[self.containerLayer insertSublayer:repeater.outputLayer atIndex:0];
}
}
if (transform) {
_opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:transform.opacity.keyframes];
_transformInterpolator = [[LOTTransformInterpolator alloc] initWithPosition:transform.position.keyframes
rotation:transform.rotation.keyframes
anchor:transform.anchor.keyframes
scale:transform.scale.keyframes];
}
_rootNode = previousNode;
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
return ([_opacityInterpolator hasUpdateForFrame:frame] ||
[_transformInterpolator hasUpdateForFrame:frame] ||
_rootNodeHasUpdate);
}
- (BOOL)updateWithFrame:(NSNumber *)frame withModifierBlock:(void (^ _Nullable)(LOTAnimatorNode * _Nonnull))modifier forceLocalUpdate:(BOOL)forceUpdate {
indentation_level = indentation_level + 1;
_rootNodeHasUpdate = [_rootNode updateWithFrame:frame withModifierBlock:modifier forceLocalUpdate:forceUpdate];
indentation_level = indentation_level - 1;
BOOL update = [super updateWithFrame:frame withModifierBlock:modifier forceLocalUpdate:forceUpdate];
return update;
}
- (void)performLocalUpdate {
if (_opacityInterpolator) {
self.containerLayer.opacity = [_opacityInterpolator floatValueForFrame:self.currentFrame];
}
if (_transformInterpolator) {
CATransform3D xform = [_transformInterpolator transformForFrame:self.currentFrame];
self.containerLayer.transform = xform;
CGAffineTransform appliedXform = CATransform3DGetAffineTransform(xform);
_localPath = [_rootNode.outputPath copy];
[_localPath LOT_applyTransform:appliedXform];
} else {
_localPath = [_rootNode.outputPath copy];
}
}
- (void)rebuildOutputs {
if (self.inputNode) {
_outputPath = [self.inputNode.outputPath copy];
[_outputPath LOT_appendPath:self.localPath];
} else {
_outputPath = self.localPath;
}
}
- (void)setPathShouldCacheLengths:(BOOL)pathShouldCacheLengths {
[super setPathShouldCacheLengths:pathShouldCacheLengths];
_rootNode.pathShouldCacheLengths = pathShouldCacheLengths;
}
- (LOTBezierPath *)localPath {
return _localPath;
}
- (LOTBezierPath *)outputPath {
return _outputPath;
}
- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
[self.inputNode searchNodesForKeypath:keypath];
if ([keypath pushKey:self.keyname]) {
// Matches self. Dig deeper.
// Check interpolators
if ([keypath pushKey:@"Transform"]) {
// Matches a Transform interpolator!
if (self.valueInterpolators[keypath.currentKey] != nil) {
[keypath pushKey:keypath.currentKey];
[keypath addSearchResultForCurrentPath:self];
[keypath popKey];
}
[keypath popKey];
}
if (keypath.endOfKeypath) {
// We have a match!
[keypath addSearchResultForCurrentPath:self];
}
// Check child nodes
[_rootNode searchNodesForKeypath:keypath];
[keypath popKey];
}
}
- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
forKeypath:(LOTKeypath * _Nonnull)keypath {
if ([keypath pushKey:self.keyname]) {
// Matches self. Dig deeper.
// Check interpolators
if ([keypath pushKey:@"Transform"]) {
// Matches a Transform interpolator!
LOTValueInterpolator *interpolator = self.valueInterpolators[keypath.currentKey];
if (interpolator) {
// We have a match!
[interpolator setValueDelegate:delegate];
}
[keypath popKey];
}
// Check child nodes
[_rootNode setValueDelegate:delegate forKeypath:keypath];
[keypath popKey];
}
// Check upstream
[self.inputNode setValueDelegate:delegate forKeypath:keypath];
}
@end
//
// LOTRepeaterRenderer.h
// Lottie
//
// Created by brandon_withrow on 7/28/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderNode.h"
#import "LOTShapeRepeater.h"
@interface LOTRepeaterRenderer : LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeRepeater:(LOTShapeRepeater *_Nonnull)repeater;
@end
//
// LOTRepeaterRenderer.m
// Lottie
//
// Created by brandon_withrow on 7/28/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRepeaterRenderer.h"
#import "LOTTransformInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "LOTHelpers.h"
@implementation LOTRepeaterRenderer {
LOTTransformInterpolator *_transformInterpolator;
LOTNumberInterpolator *_copiesInterpolator;
LOTNumberInterpolator *_offsetInterpolator;
LOTNumberInterpolator *_startOpacityInterpolator;
LOTNumberInterpolator *_endOpacityInterpolator;
CALayer *_instanceLayer;
CAReplicatorLayer *_replicatorLayer;
CALayer *centerPoint_DEBUG;
}
- (instancetype)initWithInputNode:(LOTAnimatorNode *)inputNode
shapeRepeater:(LOTShapeRepeater *)repeater {
self = [super initWithInputNode:inputNode keyName:repeater.keyname];
if (self) {
_transformInterpolator = [[LOTTransformInterpolator alloc] initWithPosition:repeater.position.keyframes
rotation:repeater.rotation.keyframes
anchor:repeater.anchorPoint.keyframes
scale:repeater.scale.keyframes];
_copiesInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:repeater.copies.keyframes];
_offsetInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:repeater.offset.keyframes];
_startOpacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:repeater.startOpacity.keyframes];
_endOpacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:repeater.endOpacity.keyframes];
_instanceLayer = [CALayer layer];
[self recursivelyAddChildLayers:inputNode];
_replicatorLayer = [CAReplicatorLayer layer];
_replicatorLayer.actions = @{@"instanceCount" : [NSNull null],
@"instanceTransform" : [NSNull null],
@"instanceAlphaOffset" : [NSNull null]};
[_replicatorLayer addSublayer:_instanceLayer];
[self.outputLayer addSublayer:_replicatorLayer];
centerPoint_DEBUG = [CALayer layer];
centerPoint_DEBUG.bounds = CGRectMake(0, 0, 20, 20);
if (ENABLE_DEBUG_SHAPES) {
[self.outputLayer addSublayer:centerPoint_DEBUG];
}
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Copies" : _copiesInterpolator,
@"Offset" : _offsetInterpolator,
@"Transform.Anchor Point" : _transformInterpolator.anchorInterpolator,
@"Transform.Position" : _transformInterpolator.positionInterpolator,
@"Transform.Scale" : _transformInterpolator.scaleInterpolator,
@"Transform.Rotation" : _transformInterpolator.rotationInterpolator,
@"Transform.Start Opacity" : _startOpacityInterpolator,
@"Transform.End Opacity" : _endOpacityInterpolator};
}
- (void)recursivelyAddChildLayers:(LOTAnimatorNode *)node {
if ([node isKindOfClass:[LOTRenderNode class]]) {
[_instanceLayer addSublayer:[(LOTRenderNode *)node outputLayer]];
}
if (![node isKindOfClass:[LOTRepeaterRenderer class]] &&
node.inputNode) {
[self recursivelyAddChildLayers:node.inputNode];
}
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
// TODO BW Add offset ability
return ([_transformInterpolator hasUpdateForFrame:frame] ||
[_copiesInterpolator hasUpdateForFrame:frame] ||
[_startOpacityInterpolator hasUpdateForFrame:frame] ||
[_endOpacityInterpolator hasUpdateForFrame:frame]);
}
- (void)performLocalUpdate {
centerPoint_DEBUG.backgroundColor = [UIColor greenColor].CGColor;
centerPoint_DEBUG.borderColor = [UIColor lightGrayColor].CGColor;
centerPoint_DEBUG.borderWidth = 2.f;
CGFloat copies = ceilf([_copiesInterpolator floatValueForFrame:self.currentFrame]);
_replicatorLayer.instanceCount = (NSInteger)copies;
_replicatorLayer.instanceTransform = [_transformInterpolator transformForFrame:self.currentFrame];
CGFloat startOpacity = [_startOpacityInterpolator floatValueForFrame:self.currentFrame];
CGFloat endOpacity = [_endOpacityInterpolator floatValueForFrame:self.currentFrame];
CGFloat opacityStep = (endOpacity - startOpacity) / copies;
_instanceLayer.opacity = startOpacity;
_replicatorLayer.instanceAlphaOffset = opacityStep;
}
@end
//
// LOTStrokeRenderer.h
// Lottie
//
// Created by brandon_withrow on 7/17/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTRenderNode.h"
#import "LOTShapeStroke.h"
@interface LOTStrokeRenderer : LOTRenderNode
- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
shapeStroke:(LOTShapeStroke *_Nonnull)stroke;
@end
//
// LOTStrokeRenderer.m
// Lottie
//
// Created by brandon_withrow on 7/17/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTStrokeRenderer.h"
#import "LOTColorInterpolator.h"
#import "LOTNumberInterpolator.h"
@implementation LOTStrokeRenderer {
LOTColorInterpolator *_colorInterpolator;
LOTNumberInterpolator *_opacityInterpolator;
LOTNumberInterpolator *_widthInterpolator;
LOTNumberInterpolator *_dashOffsetInterpolator;
NSArray *_dashPatternInterpolators;
}
- (instancetype)initWithInputNode:(LOTAnimatorNode *)inputNode
shapeStroke:(LOTShapeStroke *)stroke {
self = [super initWithInputNode:inputNode keyName:stroke.keyname];
if (self) {
_colorInterpolator = [[LOTColorInterpolator alloc] initWithKeyframes:stroke.color.keyframes];
_opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:stroke.opacity.keyframes];
_widthInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:stroke.width.keyframes];
NSMutableArray *dashPatternIntpolators = [NSMutableArray array];
NSMutableArray *dashPatterns = [NSMutableArray array];
for (LOTKeyframeGroup *keyframegroup in stroke.lineDashPattern) {
LOTNumberInterpolator *interpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:keyframegroup.keyframes];
[dashPatternIntpolators addObject:interpolator];
if (dashPatterns && keyframegroup.keyframes.count == 1) {
LOTKeyframe *first = keyframegroup.keyframes.firstObject;
[dashPatterns addObject:@(first.floatValue)];
}
if (keyframegroup.keyframes.count > 1) {
dashPatterns = nil;
}
}
if (dashPatterns.count) {
self.outputLayer.lineDashPattern = dashPatterns;
} else {
_dashPatternInterpolators = dashPatternIntpolators;
}
if (stroke.dashOffset) {
_dashOffsetInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:stroke.dashOffset.keyframes];
}
self.outputLayer.fillColor = nil;
self.outputLayer.lineCap = stroke.capType == LOTLineCapTypeRound ? kCALineCapRound : kCALineCapButt;
switch (stroke.joinType) {
case LOTLineJoinTypeBevel:
self.outputLayer.lineJoin = kCALineJoinBevel;
break;
case LOTLineJoinTypeMiter:
self.outputLayer.lineJoin = kCALineJoinMiter;
break;
case LOTLineJoinTypeRound:
self.outputLayer.lineJoin = kCALineJoinRound;
break;
default:
break;
}
}
return self;
}
- (NSDictionary *)valueInterpolators {
return @{@"Color" : _colorInterpolator,
@"Opacity" : _opacityInterpolator,
@"Stroke Width" : _widthInterpolator};
}
- (void)_updateLineDashPatternsForFrame:(NSNumber *)frame {
if (_dashPatternInterpolators.count) {
NSMutableArray *lineDashPatterns = [NSMutableArray array];
CGFloat dashTotal = 0;
for (LOTNumberInterpolator *interpolator in _dashPatternInterpolators) {
CGFloat patternValue = [interpolator floatValueForFrame:frame];
dashTotal = dashTotal + patternValue;
[lineDashPatterns addObject:@(patternValue)];
}
if (dashTotal > 0) {
self.outputLayer.lineDashPattern = lineDashPatterns;
}
}
}
- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
[self _updateLineDashPatternsForFrame:frame];
BOOL dashOffset = NO;
if (_dashOffsetInterpolator) {
dashOffset = [_dashOffsetInterpolator hasUpdateForFrame:frame];
}
return (dashOffset ||
[_colorInterpolator hasUpdateForFrame:frame] ||
[_opacityInterpolator hasUpdateForFrame:frame] ||
[_widthInterpolator hasUpdateForFrame:frame]);
}
- (void)performLocalUpdate {
self.outputLayer.lineDashPhase = [_dashOffsetInterpolator floatValueForFrame:self.currentFrame];
self.outputLayer.strokeColor = [_colorInterpolator colorForFrame:self.currentFrame];
self.outputLayer.lineWidth = [_widthInterpolator floatValueForFrame:self.currentFrame];
self.outputLayer.opacity = [_opacityInterpolator floatValueForFrame:self.currentFrame];
}
- (void)rebuildOutputs {
self.outputLayer.path = self.inputNode.outputPath.CGPath;
}
- (NSDictionary *)actionsForRenderLayer {
return @{@"strokeColor": [NSNull null],
@"lineWidth": [NSNull null],
@"opacity" : [NSNull null]};
}
@end
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!