项目初始化

This commit is contained in:
NSArray
2026-04-21 14:58:48 +08:00
commit 35b5786fb5
553 changed files with 26507 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# CocoaPods
Pods/
# 保留这个
Podfile.lock
# Xcode
DerivedData/
build/
*.xcworkspace
xcuserdata/
*.xcuserstate
*.xcarchive
# macOS
.DS_Store
# SwiftPM
.build/
# Carthage
Carthage/Build/
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
# Binary / large files
*.ipa
*.dSYM
*.zip
*.rar
*.7z
# Assets
*.psd
# Local SDK
TUIKit/
+359
View File
@@ -0,0 +1,359 @@
# Agent 行为准则 & JKCQProjectV2 iOS 开发规范
---
## 一、Agent 行为准则
### 0. 语言强制
- 强制使用简体中文进行所有交互(代码除外)。
### 1. 抽象设计确认
- 涉及抽象设计、架构调整或新功能模块时,必须先用文字或 Mermaid 图对齐设计思路。
- 必须等待用户确认方案后,才能开始编写代码。
### 2. 任务清单确认
- 执行任何实质性任务前,必须先列出详细的任务清单。
- 必须等待用户明确回复(如"好的"、"开始")后,才能进入执行阶段。
### 3. 分步执行与确认
- 代码量较大或逻辑复杂的任务,禁止一次性完成。
- 拆分为多个步骤,每步完成后汇报进度并询问"是否可以进行下一步?"。
### 4. 所有修改必须确认
- 对代码库的任何修改(新建文件、修改文件、删除文件、执行 pod install 等)前,必须先描述变更内容。
- 必须等待用户明确回复"确认"或"同意"后,才能执行。
### 5. 代码注释语言规范
- 所有注释使用简体中文。
- 标识符(变量名、函数名、类名)仍使用英文。
---
## 二、项目技术规范
### 技术栈
- **主语言**: Swift(混编 Objective-CSwift 为主)
- **UI 框架**: UIKit
- **架构模式**: MVC + BasicModule 基础设施层
- **包管理**: CocoaPods,使用 `.xcworkspace` 打开项目
- **最低系统版本**: iOS 13.0
- **布局**: SnapKit(主要)+ frame
- **网络**: Moya + Alamofire
- **图片加载**: Kingfisher
- **主题**: SwiftTheme
---
## 三、项目目录结构
```
JKCQProjectV2/
├── BasicModule/ # 基础设施层(非业务)
│ ├── Base/ # 基类(MktViewController、MktNavigatonController
│ ├── Network/ # 网络层(RequestManager、RequestTarget、NetworkParser
│ ├── Extension/ # Swift 扩展
│ ├── Util/ # 工具类(ThemeManager、Foundation.swift
│ ├── Helper/ # POP 动画 & 自定义辅助类
│ ├── CacheKit/ # 缓存工具
│ └── Configuration/ # APIKey 等配置(敏感,勿外传)
├── Class/ # 业务模块
│ ├── Home/ # 首页(应用)
│ ├── Dangan/ # 档案(健康记录)
│ ├── AI/ # AI 助手
│ ├── Knowledge/ # 知识库
│ ├── Mine/ # 我的(用户中心)
│ ├── Market/ # 市场/产品(未在主 TabBar)
│ ├── Message/ # 消息(TUI IM 集成)
│ └── Login/ # 登录模块
├── Resources/
│ └── Themes/ # 主题配置(blue/red/green/purple.plist
└── JKCQProjectV2-Bridging-Header.h
```
新增业务模块按以下结构组织:
```
Class/ModuleName/
├── ViewController/ # 视图控制器
├── View/ # 自定义视图
└── Model/ # 数据模型(Codable struct
```
---
## 四、命名规范
**类名(PascalCase):**
- 基类及核心组件使用 `Mkt` 前缀,如 `MktViewController``MktNavigatonController`
- 业务类按模块语义命名,如 `HomeViewController``DanganHomeViewController`
- 数据模型用 `Model` 后缀,如 `BannerModel``MarketProductModel`
**文件组织:**
- 扩展文件:`Extension+Feature.swift`,如 `Extension+UIView.swift`
- 模型文件:放在对应模块的 `Model/` 子目录
**方法名(camelCase):**
```swift
func setupUI()
func updateThemeUI()
func loadData(page: Int)
```
**常量:**
```swift
// Swift camelCase
let defaultPageSize = 20
// OC
JPScaleValue(16)
```
---
## 五、代码组织
```swift
// MARK: - Life Cycle
// MARK: - Setup
// MARK: - Network
// MARK: - Actions
// MARK: - ThemeProtocol / Theme
// MARK: - UITableViewDataSource
// MARK: - UITableViewDelegate
```
---
## 六、网络层规范
### 发起请求
项目网络层统一使用 `RequestTarget` + `RequestManager`,通过 Moya 封装:
```swift
// GET
RequestTarget.get("/api/v1/list", query: ["page": 1, "size": 20])
.sendParsed(showHUD: true, type: [ItemModel].self) { success, data, msg in
guard success, let items = data else { return }
self.items = items
self.tableView.reloadData()
}
// POST
RequestTarget.post("/api/v1/submit", body: ["key": "value"])
.sendParsed(showHUD: true, type: ResultModel.self) { success, data, msg in
if success {
Mkt.makeToast("提交成功")
}
}
//
RequestTarget.upload("/api/v1/upload", files: [fileData], names: ["file"])
.sendParsed(showHUD: true, type: UploadResult.self) { success, data, msg in
// ...
}
```
### 响应码说明
| 响应码 | 含义 |
|--------|------|
| `0000` / `200` | 请求成功 |
| `4001` | Token 过期,自动跳转登录 |
| `-888` | 网络不可用 |
| `-999` | 请求超时 |
| `-666` | 服务器异常 |
### 数据模型结构
```swift
//
struct Response<T: Codable> {
var retCode: String //
var retMsg: String //
var retData: T? //
}
// 使 Codable struct
struct ItemModel: Codable {
var id: String
var name: String
var createdAt: String?
}
```
### 注意事项
- API 地址统一定义在 `BasicModule/Configuration/APIKey.swift`,禁止在业务代码中硬编码 URL
- SDK Key / Secret 统一放 `APIKey.swift`,禁止散落在业务文件
- 网络回调默认在主线程,可直接操作 UI
---
## 七、主题系统
项目支持 4 套主题(blue / red / green / purple),配置文件在 `Resources/Themes/*.plist`
### 初始化(AppDelegate
```swift
AppThemeManager.shared.setup()
```
### 切换主题
```swift
AppThemeManager.shared.switchTheme(to: .blue)
```
### 在视图中应用主题
```swift
override func viewDidLoad() {
super.viewDidLoad()
setupTheme()
}
private func setupTheme() {
updateThemeUI()
observeThemeChanges { [weak self] in
self?.updateThemeUI()
}
}
private func updateThemeUI() {
// 使 ThemeKey
view.theme_backgroundColor = ThemeKey.backgroundColor
titleLabel.theme_textColor = ThemeKey.textColor
navBar.theme_backgroundColor = ThemeKey.navBarColor
}
```
### 常用 ThemeKey
```swift
ThemeKey.primaryColor //
ThemeKey.backgroundColor //
ThemeKey.textColor //
ThemeKey.navBarColor //
ThemeKey.navBarTextColor //
ThemeKey.buttonBgColor //
ThemeKey.tabBarSelectedColor // TabBar
```
---
## 八、常用工具速查
### Mkt 全局结构体(`BasicModule/Util/Foundation.swift`
```swift
Mkt.appName // App
Mkt.isDebug // Debug
Mkt.isDevice // vs
Mkt.screenWidth //
Mkt.screenHeight //
Mkt.safe_top //
Mkt.safe_bottom //
Mkt.topBarHeight // +
Mkt.keyWindow // UIWindow
Mkt.makeToast("提示") // Toast
Mkt.jsonToModel() // JSON Codable
```
### 日志(DEBUG 模式有效)
```swift
dlog("请求参数: \(params)") // //
```
### JPConstant.h 宏(OC 层,通过 Bridging Header 可用)
```objc
JPScaleValue(16) // 按屏幕宽度等比缩放数值(基准 375pt)
JPScaleFont(14) // 等比缩放字体
JPRGBColor(r, g, b) // RGB 0-255 创建颜色
JPStringEqual(a, b) // 安全字符串比较
```
### 用户管理
```swift
UserManager.shared.isLoggedIn //
UserManager.shared.currentUser // UserInfo?
UserManager.shared.token // Token
```
---
## 九、UI 布局规范
**优先使用 SnapKit**
```swift
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(JPScaleValue(44))
}
```
**屏幕适配统一用 `JPScaleValue()`**
```swift
// 稿 375pt JPScaleValue
let itemHeight = JPScaleValue(80)
let fontSize = JPScaleFont(14)
```
**UIView 扩展(`Extension+UIView.swift`)常用方法:**
```swift
view.setCornerRadius(8) //
view.setShadow(color: .black, opacity: 0.1) //
view.addGradient(colors: [.blue, .cyan]) //
```
---
## 十、内存管理
Block / 闭包内防循环引用,统一使用 `[weak self]`
```swift
RequestTarget.get("/api/list")
.sendParsed(type: [Item].self) { [weak self] success, data, msg in
guard let self = self else { return }
self.items = data ?? []
self.tableView.reloadData()
}
```
---
## 十一、各模块职责
| 模块 | 路径 | 职责 |
|------|------|------|
| **Home** | `Class/Home/` | 首页 Dashboard、Banner、应用入口 |
| **Dangan** | `Class/Dangan/` | 健康档案、体检记录 |
| **AI** | `Class/AI/` | AI 健康助手对话 |
| **Knowledge** | `Class/Knowledge/` | 健康知识库、文章 |
| **Mine** | `Class/Mine/` | 用户中心、设置、个人信息 |
| **Market** | `Class/Market/` | 产品/服务目录(不在主 TabBar) |
| **Message** | `Class/Message/` | IM 消息列表(TUIKit 集成) |
| **Login** | `Class/Login/` | 登录、注册、忘记密码 |
| **BasicModule/Network** | — | 全局网络请求封装,禁止绕过 |
| **BasicModule/Base** | — | 所有 VC 的基类,提供主题/导航能力 |
---
## 十二、禁止事项
- 禁止将 API Key、SDK AppID 等敏感信息硬编码在业务文件中(统一放 `APIKey.swift`
- 禁止在主线程执行网络请求、数据库读写、文件 IO 等耗时操作
- 禁止绕过 `RequestTarget` / `RequestManager` 直接使用 Alamofire 发起请求
- 禁止在未通知用户的情况下引入新的 Pod 依赖
- 禁止修改 `BasicModule/Base/` 中的基类,除非经用户明确确认
- 禁止在 Swift Extension 中添加存储属性(使用 AssociatedObject 或重构为子类)
- 禁止未经确认自行切换主题或修改 `Resources/Themes/*.plist`
+10
View File
@@ -0,0 +1,10 @@
//
// Untitled.swift
// HealthEmergency
//
// Created by Apple on 2026/3/19.
//
@_exported import SnapKit
@_exported import TUICore
@_exported import TUIChat
@@ -0,0 +1,719 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
18D153C22F87546100C31B33 /* json in Resources */ = {isa = PBXBuildFile; fileRef = 18D153C12F87546100C31B33 /* json */; };
18D1E72C2F6BCCAD00C31B33 /* GlobalImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D1E72A2F6BCC9B00C31B33 /* GlobalImports.swift */; };
DAB20197EF2BD6EAA8E0351C /* Pods_HealthEmergency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C96F703991F454AF01408174 /* Pods_HealthEmergency.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
1822270B2F63DE9E006E4424 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 182226EC2F63DE9B006E4424 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 182226F32F63DE9B006E4424;
remoteInfo = HealthEmergency;
};
182227152F63DE9E006E4424 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 182226EC2F63DE9B006E4424 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 182226F32F63DE9B006E4424;
remoteInfo = HealthEmergency;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
182226F42F63DE9B006E4424 /* HealthEmergency.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HealthEmergency.app; sourceTree = BUILT_PRODUCTS_DIR; };
1822270A2F63DE9E006E4424 /* HealthEmergencyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HealthEmergencyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
182227142F63DE9E006E4424 /* HealthEmergencyUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HealthEmergencyUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
18D153C12F87546100C31B33 /* json */ = {isa = PBXFileReference; lastKnownFileType = text; path = json; sourceTree = "<group>"; };
18D1E72A2F6BCC9B00C31B33 /* GlobalImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalImports.swift; sourceTree = "<group>"; };
53F629DB5A012083B0E4DB58 /* Pods-HealthEmergency.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HealthEmergency.debug.xcconfig"; path = "Target Support Files/Pods-HealthEmergency/Pods-HealthEmergency.debug.xcconfig"; sourceTree = "<group>"; };
5F7DEDEE7D5BD3BDB0596C4A /* Pods-HealthEmergency.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HealthEmergency.release.xcconfig"; path = "Target Support Files/Pods-HealthEmergency/Pods-HealthEmergency.release.xcconfig"; sourceTree = "<group>"; };
C96F703991F454AF01408174 /* Pods_HealthEmergency.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HealthEmergency.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
1822271C2F63DE9E006E4424 /* Exceptions for "HealthEmergency" folder in "HealthEmergency" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Other/Info.plist,
);
target = 182226F32F63DE9B006E4424 /* HealthEmergency */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
182226F62F63DE9B006E4424 /* HealthEmergency */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
1822271C2F63DE9E006E4424 /* Exceptions for "HealthEmergency" folder in "HealthEmergency" target */,
);
path = HealthEmergency;
sourceTree = "<group>";
};
1822270D2F63DE9E006E4424 /* HealthEmergencyTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = HealthEmergencyTests;
sourceTree = "<group>";
};
182227172F63DE9E006E4424 /* HealthEmergencyUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = HealthEmergencyUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
182226F12F63DE9B006E4424 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DAB20197EF2BD6EAA8E0351C /* Pods_HealthEmergency.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
182227072F63DE9E006E4424 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
182227112F63DE9E006E4424 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0CC27B0E77B84230DE40F402 /* Pods */ = {
isa = PBXGroup;
children = (
53F629DB5A012083B0E4DB58 /* Pods-HealthEmergency.debug.xcconfig */,
5F7DEDEE7D5BD3BDB0596C4A /* Pods-HealthEmergency.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
182226EB2F63DE9B006E4424 = {
isa = PBXGroup;
children = (
18D153C12F87546100C31B33 /* json */,
18D1E72A2F6BCC9B00C31B33 /* GlobalImports.swift */,
182226F62F63DE9B006E4424 /* HealthEmergency */,
1822270D2F63DE9E006E4424 /* HealthEmergencyTests */,
182227172F63DE9E006E4424 /* HealthEmergencyUITests */,
182226F52F63DE9B006E4424 /* Products */,
0CC27B0E77B84230DE40F402 /* Pods */,
2A5D3123B79725DC3E5BC2D1 /* Frameworks */,
);
sourceTree = "<group>";
};
182226F52F63DE9B006E4424 /* Products */ = {
isa = PBXGroup;
children = (
182226F42F63DE9B006E4424 /* HealthEmergency.app */,
1822270A2F63DE9E006E4424 /* HealthEmergencyTests.xctest */,
182227142F63DE9E006E4424 /* HealthEmergencyUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
2A5D3123B79725DC3E5BC2D1 /* Frameworks */ = {
isa = PBXGroup;
children = (
C96F703991F454AF01408174 /* Pods_HealthEmergency.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
182226F32F63DE9B006E4424 /* HealthEmergency */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1822271D2F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergency" */;
buildPhases = (
5F7ECF8E2AF0C7FEDE7DC193 /* [CP] Check Pods Manifest.lock */,
182226F02F63DE9B006E4424 /* Sources */,
182226F12F63DE9B006E4424 /* Frameworks */,
182226F22F63DE9B006E4424 /* Resources */,
3015731C47F1722573D9275C /* [CP] Copy Pods Resources */,
DF739E00EE7E13D9338332B1 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
182226F62F63DE9B006E4424 /* HealthEmergency */,
);
name = HealthEmergency;
productName = HealthEmergency;
productReference = 182226F42F63DE9B006E4424 /* HealthEmergency.app */;
productType = "com.apple.product-type.application";
};
182227092F63DE9E006E4424 /* HealthEmergencyTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 182227222F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergencyTests" */;
buildPhases = (
182227062F63DE9E006E4424 /* Sources */,
182227072F63DE9E006E4424 /* Frameworks */,
182227082F63DE9E006E4424 /* Resources */,
);
buildRules = (
);
dependencies = (
1822270C2F63DE9E006E4424 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
1822270D2F63DE9E006E4424 /* HealthEmergencyTests */,
);
name = HealthEmergencyTests;
productName = HealthEmergencyTests;
productReference = 1822270A2F63DE9E006E4424 /* HealthEmergencyTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
182227132F63DE9E006E4424 /* HealthEmergencyUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 182227252F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergencyUITests" */;
buildPhases = (
182227102F63DE9E006E4424 /* Sources */,
182227112F63DE9E006E4424 /* Frameworks */,
182227122F63DE9E006E4424 /* Resources */,
);
buildRules = (
);
dependencies = (
182227162F63DE9E006E4424 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
182227172F63DE9E006E4424 /* HealthEmergencyUITests */,
);
name = HealthEmergencyUITests;
productName = HealthEmergencyUITests;
productReference = 182227142F63DE9E006E4424 /* HealthEmergencyUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
182226EC2F63DE9B006E4424 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2630;
LastUpgradeCheck = 2630;
TargetAttributes = {
182226F32F63DE9B006E4424 = {
CreatedOnToolsVersion = 26.3;
};
182227092F63DE9E006E4424 = {
CreatedOnToolsVersion = 26.3;
TestTargetID = 182226F32F63DE9B006E4424;
};
182227132F63DE9E006E4424 = {
CreatedOnToolsVersion = 26.3;
TestTargetID = 182226F32F63DE9B006E4424;
};
};
};
buildConfigurationList = 182226EF2F63DE9B006E4424 /* Build configuration list for PBXProject "HealthEmergency" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 182226EB2F63DE9B006E4424;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 182226F52F63DE9B006E4424 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
182226F32F63DE9B006E4424 /* HealthEmergency */,
182227092F63DE9E006E4424 /* HealthEmergencyTests */,
182227132F63DE9E006E4424 /* HealthEmergencyUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
182226F22F63DE9B006E4424 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18D153C22F87546100C31B33 /* json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
182227082F63DE9E006E4424 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
182227122F63DE9E006E4424 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3015731C47F1722573D9275C /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HealthEmergency/Pods-HealthEmergency-resources.sh\"\n";
showEnvVarsInLog = 0;
};
5F7ECF8E2AF0C7FEDE7DC193 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-HealthEmergency-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
DF739E00EE7E13D9338332B1 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HealthEmergency/Pods-HealthEmergency-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
182226F02F63DE9B006E4424 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18D1E72C2F6BCCAD00C31B33 /* GlobalImports.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
182227062F63DE9E006E4424 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
182227102F63DE9E006E4424 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
1822270C2F63DE9E006E4424 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 182226F32F63DE9B006E4424 /* HealthEmergency */;
targetProxy = 1822270B2F63DE9E006E4424 /* PBXContainerItemProxy */;
};
182227162F63DE9E006E4424 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 182226F32F63DE9B006E4424 /* HealthEmergency */;
targetProxy = 182227152F63DE9E006E4424 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
1822271E2F63DE9E006E4424 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 53F629DB5A012083B0E4DB58 /* Pods-HealthEmergency.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "$(SRCROOT)/HealthEmergency/Other/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "健康应急";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "健康应急需要使用您的相机来拍摄头像,请允许访问";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "健康应急需要访问本地网络以连接开发服务器,请允许访问";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "健康应急需要获取您的定位权限为您提供就近服务,是否同意?";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "健康应急需要获取您的定位权限为您提供就近服务,是否同意?";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "健康应急需要将图片保存到您的相册,请允许访问";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "健康应急需要访问您的相册来选取头像照片,请允许访问";
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 15;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xianrengkang.HealthEmergency;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "HealthEmergency/HealthEmergency-Bridging-Header.h";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
1822271F2F63DE9E006E4424 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5F7DEDEE7D5BD3BDB0596C4A /* Pods-HealthEmergency.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "$(SRCROOT)/HealthEmergency/Other/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "健康应急";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "健康应急需要使用您的相机来拍摄头像,请允许访问";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "健康应急需要访问本地网络以连接开发服务器,请允许访问";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "健康应急需要获取您的定位权限为您提供就近服务,是否同意?";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "健康应急需要获取您的定位权限为您提供就近服务,是否同意?";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "健康应急需要将图片保存到您的相册,请允许访问";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "健康应急需要访问您的相册来选取头像照片,请允许访问";
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 15;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xianrengkang.HealthEmergency;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "HealthEmergency/HealthEmergency-Bridging-Header.h";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
182227202F63DE9E006E4424 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
182227212F63DE9E006E4424 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
182227232F63DE9E006E4424 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.escortUP.EscortProject.HealthEmergencyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HealthEmergency.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HealthEmergency";
};
name = Debug;
};
182227242F63DE9E006E4424 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.escortUP.EscortProject.HealthEmergencyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HealthEmergency.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HealthEmergency";
};
name = Release;
};
182227262F63DE9E006E4424 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.escortUP.EscortProject.HealthEmergencyUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = HealthEmergency;
};
name = Debug;
};
182227272F63DE9E006E4424 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7CC5LW224;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.escortUP.EscortProject.HealthEmergencyUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = HealthEmergency;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
182226EF2F63DE9B006E4424 /* Build configuration list for PBXProject "HealthEmergency" */ = {
isa = XCConfigurationList;
buildConfigurations = (
182227202F63DE9E006E4424 /* Debug */,
182227212F63DE9E006E4424 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1822271D2F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergency" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1822271E2F63DE9E006E4424 /* Debug */,
1822271F2F63DE9E006E4424 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
182227222F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergencyTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
182227232F63DE9E006E4424 /* Debug */,
182227242F63DE9E006E4424 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
182227252F63DE9E006E4424 /* Build configuration list for PBXNativeTarget "HealthEmergencyUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
182227262F63DE9E006E4424 /* Debug */,
182227272F63DE9E006E4424 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 182226EC2F63DE9B006E4424 /* Project object */;
}
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "182226F32F63DE9B006E4424"
BuildableName = "HealthEmergency.app"
BlueprintName = "HealthEmergency"
ReferencedContainer = "container:HealthEmergency.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "182227092F63DE9E006E4424"
BuildableName = "HealthEmergencyTests.xctest"
BlueprintName = "HealthEmergencyTests"
ReferencedContainer = "container:HealthEmergency.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "182227132F63DE9E006E4424"
BuildableName = "HealthEmergencyUITests.xctest"
BlueprintName = "HealthEmergencyUITests"
ReferencedContainer = "container:HealthEmergency.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "182226F32F63DE9B006E4424"
BuildableName = "HealthEmergency.app"
BlueprintName = "HealthEmergency"
ReferencedContainer = "container:HealthEmergency.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "182226F32F63DE9B006E4424"
BuildableName = "HealthEmergency.app"
BlueprintName = "HealthEmergency"
ReferencedContainer = "container:HealthEmergency.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,186 @@
//
// MainTabBarController.swift
// HealthEmergency
//
// Created by Apple on 2026/3/18.
//
import UIKit
import Lottie
import SwiftTheme
/// MainTabBarController
/// -
/// - + + SafeArea
/// -
/// - Lottie +
class MainTabBarController: UITabBarController, UITabBarControllerDelegate {
// MARK: -
private let itemConfigs: [TabBarItemConfig] = [
TabBarItemConfig(title: "应用", normalIcon: UIImage(named: "tabbar_yingyong_normal_P"), selectedIcon: UIImage(named: "tabbar_yingyong_select_P"), lottieName: nil),
TabBarItemConfig(title: "档案", normalIcon: UIImage(named: "tabbar_dangan_normal_P"), selectedIcon: UIImage(named: "tabbar_dangan_select_P"), lottieName: nil),
TabBarItemConfig(title: "AI助手", normalIcon: UIImage(named: "tabbar_AI_normal_P"), selectedIcon: UIImage(named: "tabbar_AI_select_P"), lottieName: nil),
TabBarItemConfig(title: "知识", normalIcon: UIImage(named: "tabbar_zhishi_normal_P"), selectedIcon: UIImage(named: "tabbar_zhishi_select_P"), lottieName: nil),
TabBarItemConfig(title: "我的", normalIcon: UIImage(named: "tabbar_wode_normal_P"), selectedIcon: UIImage(named: "tabbar_wode_select_P"), lottieName: nil)
]
private let appearanceConfig = MainTabBarAppearanceConfig(
normalColor: .gray,
selectedColor: .systemBlue,
blurStyle: .systemUltraThinMaterial,
cornerRadius: 22,
scaleSelected: 1.2
)
// MARK: - & Lottie
private var badgeStates: [Int: Bool] = [:]
private var lottieViews: [Int: LottieAnimationView] = [:]
// plist key controllers
private let tabNormalKeys = ["tabYingyongNormal", "tabDangAnNormal", "tabAINormal", "tabZhishiNormal", "tabWodeNormal"]
private let tabSelectedKeys = ["tabYingyongSelected", "tabDangAnSelected", "tabAISelected", "tabZhishiSelected", "tabWodeSelected"]
// MARK: -
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// 使 TabBar
let customTabBar = MainTabBarView()
setValue(customTabBar, forKey: "tabBar")
customTabBar.applyAppearance(appearanceConfig)
setupViewControllers()
updateTabBarImages()
// plist
setupTabBarAppearance()
setupLottieAnimations()
// iOS 26+
NotificationCenter.default.addObserver(
self,
selector: #selector(themeChanged),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
}
// MARK: -
private func setupViewControllers() {
let homeVC = HomeViewController()
let danganVC = DanganHomeViewController()
let AIVC = AIHelperViewController()
let zhishiVC = KnowledgeHomeViewController()
let mineVC = MineViewController()
let controllers = [homeVC, danganVC, AIVC, zhishiVC,mineVC].map {
MktNavigatonController(rootViewController: $0)
}
self.viewControllers = controllers
guard let items = tabBar.items else { return }
for i in 0..<items.count {
items[i].title = itemConfigs[i].title
}
}
// MARK: - Lottie
private func setupLottieAnimations() {
guard let items = tabBar.items else { return }
for (i, config) in itemConfigs.enumerated() {
guard let lottieName = config.lottieName else { continue }
let animationView = LottieAnimationView(name: lottieName)
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .playOnce
animationView.translatesAutoresizingMaskIntoConstraints = false
tabBar.addSubview(animationView)
let tabWidth = tabBar.bounds.width / CGFloat(items.count)
let centerX = tabWidth * (CGFloat(i) + 0.5)
NSLayoutConstraint.activate([
animationView.centerXAnchor.constraint(equalTo: tabBar.leadingAnchor, constant: centerX),
animationView.centerYAnchor.constraint(equalTo: tabBar.topAnchor, constant: tabBar.bounds.height/2 - 4),
animationView.widthAnchor.constraint(equalToConstant: 40),
animationView.heightAnchor.constraint(equalToConstant: 40)
])
animationView.isHidden = true
lottieViews[i] = animationView
}
}
private func setupTabBarAppearance() {
if #available(iOS 26.0, *) {
// iOS 26+ plist
let appearance = tabBar.standardAppearance
let normal = appearance.stackedLayoutAppearance.normal
let selected = appearance.stackedLayoutAppearance.selected
normal.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -2)
selected.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -2)
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
} else {
// iOS 26
tabBar.barTintColor = .white
tabBar.isTranslucent = false
}
}
@objc private func themeChanged() {
updateTabBarImages()
if #available(iOS 26.0, *) {
setupTabBarAppearance()
}
}
/// plist .alwaysOriginal UITabBar template
private func updateTabBarImages() {
guard let items = tabBar.items,
let theme = ThemeManager.currentTheme else { return }
for i in 0..<min(items.count, tabNormalKeys.count) {
if let name = theme[tabNormalKeys[i]] as? String {
items[i].image = UIImage(named: name)?.withRenderingMode(.alwaysOriginal)
}
if let name = theme[tabSelectedKeys[i]] as? String {
items[i].selectedImage = UIImage(named: name)?.withRenderingMode(.alwaysOriginal)
}
}
// title /
if let hex = theme["tabBarSelectedColor"] as? String {
tabBar.tintColor = UIColor(rgba: hex)
}
if let hex = theme["tabBarNormalColor"] as? String {
tabBar.unselectedItemTintColor = UIColor(rgba: hex)
}
}
// MARK: -
func showBadge(at index: Int, show: Bool) {
guard let items = tabBar.items, index < items.count else { return }
items[index].badgeValue = show ? " " : nil
badgeStates[index] = show
}
func isBadgeShown(at index: Int) -> Bool {
return badgeStates[index] ?? false
}
// MARK: - Lottie
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let index = viewControllers?.firstIndex(of: viewController) else { return }
for (i, lottie) in lottieViews {
lottie.isHidden = i != index
if i == index { lottie.play() } else { lottie.stop() }
}
}
}
// 访
extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
@@ -0,0 +1,36 @@
//
// MainTabBarView.swift
// HealthEmergency
//
// Created by Apple on 2026/3/18.
//
import UIKit
/// TabBar + + SafeArea
class MainTabBarView: UITabBar {
///
func applyAppearance(_ config: MainTabBarAppearanceConfig) {
if #available(iOS 26.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundEffect = UIBlurEffect(style: config.blurStyle)
appearance.backgroundColor = UIColor.white.withAlphaComponent(0.06)
standardAppearance = appearance
scrollEdgeAppearance = appearance
} else {
barTintColor = .white
isTranslucent = false
tintColor = config.selectedColor
unselectedItemTintColor = config.normalColor
}
layer.cornerRadius = config.cornerRadius
layer.masksToBounds = true
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.08
layer.shadowOffset = CGSize(width: 0, height: 8)
layer.shadowRadius = 20
}
}
@@ -0,0 +1,25 @@
//
// TabBarItemConfig.swift
// HealthEmergency
//
// Created by Apple on 2026/3/18.
//
import UIKit
/// TabBarItem
struct TabBarItemConfig {
let title: String
let normalIcon: UIImage?
let selectedIcon: UIImage?
let lottieName: String? // Lottie
}
/// TabBar
struct MainTabBarAppearanceConfig {
let normalColor: UIColor
let selectedColor: UIColor
let blurStyle: UIBlurEffect.Style
let cornerRadius: CGFloat
let scaleSelected: CGFloat
}
@@ -0,0 +1,39 @@
//
// MktCollectionViewController.swift
// iMarket
//
// Created by on 2023/9/2.
//
import Foundation
import UIKit
class MktCollectionViewController: MktViewController {
//
var pageIndex: Int = 1
//
var pageSize: Int = 10
//
var layout: UICollectionViewFlowLayout?
//
lazy var collectionView: UICollectionView = {
let collection: UICollectionView = UICollectionView.init(frame: .zero, collectionViewLayout: self.layout ?? UICollectionViewFlowLayout())
collection.keyboardDismissMode = .onDrag
collection.backgroundColor = .clear
collection.showsVerticalScrollIndicator = false
collection.contentInsetAdjustmentBehavior = .never
return collection
}()
}
// MARK: collectionView
extension UICollectionView {
//
func register(_ cellClass: AnyClass) {
return self.register(cellClass, forCellWithReuseIdentifier: NSStringFromClass(cellClass))
}
//
func dequeueReusableCell(_ cellClass: AnyClass, _ indexPath: IndexPath) -> UICollectionViewCell? {
return self.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(cellClass), for: indexPath)
}
}
@@ -0,0 +1,287 @@
//
// MktNavigatonController.swift
// iMarket
//
// Created by on 2023/8/31.
//
import UIKit
import SwiftTheme
class MktNavigatonController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
var isHidden = true
fileprivate weak var popGestureDelegate: UIGestureRecognizerDelegate?
fileprivate var interactivePopTransition: UIPercentDrivenInteractiveTransition?
fileprivate var popEdgePanGesture: UIScreenEdgePanGestureRecognizer?
//
var isSystemSlidBack: Bool?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationBar.isHidden = self.isHidden;
self.popGestureDelegate = self.interactivePopGestureRecognizer?.delegate
self.delegate = self;
//
self.interactivePopGestureRecognizer?.isEnabled = true;
self.interactivePopGestureRecognizer?.delegate = self;
//使
popEdgePanGesture = UIScreenEdgePanGestureRecognizer.init(target: self,
action: #selector(handleNavigationTransition(_:)))
popEdgePanGesture?.edges = UIRectEdge.left;
popEdgePanGesture?.isEnabled = false
self.view.addGestureRecognizer(popEdgePanGesture!)
if #available(iOS 13.0, *) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyle.light
} else {
// Fallback on earlier versions
}
//
setupTheme()
//
NotificationCenter.default.addObserver(
self,
selector: #selector(themeDidChange),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
}
private func setupTheme() {
//
let navColor = UIColor.white
let textColor = UIColor(red: 37/255, green: 37/255, blue: 53/255, alpha: 1) // #252535
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = navColor
appearance.shadowColor = UIColor(white: 0, alpha: 0.08)
appearance.titleTextAttributes = [
.foregroundColor: textColor,
.font: UIFont.systemFont(ofSize: 18, weight: .medium)
]
self.navigationBar.standardAppearance = appearance
self.navigationBar.scrollEdgeAppearance = appearance
self.navigationBar.compactAppearance = appearance
} else {
self.navigationBar.barTintColor = navColor
self.navigationBar.titleTextAttributes = [
.foregroundColor: textColor,
.font: UIFont.systemFont(ofSize: 18, weight: .medium)
]
}
self.navigationBar.tintColor = textColor
}
///
func applyTheme() {
setupTheme()
}
@objc private func themeDidChange() {
DispatchQueue.main.async {
self.setupTheme()
// appearance
self.navigationBar.layoutIfNeeded()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let haveMoreThanOneChildViewController = self.viewControllers.count > 1
self.hidesBottomBarWhenPushed = haveMoreThanOneChildViewController ? true : false
self.navigationController?.navigationBar.isHidden = haveMoreThanOneChildViewController ? false : true
}
@objc
func handleNavigationTransition(_ recognizer: UIScreenEdgePanGestureRecognizer) {
let progress = recognizer.translation(in: self.view).x / self.view.bounds.width
let recognizerState = recognizer.state
switch recognizerState {
case .began:
self.interactivePopTransition = UIPercentDrivenInteractiveTransition.init()
self.popViewController(animated: true)
case .changed:
self.interactivePopTransition?.update(progress)
case .ended, .cancelled:
let velocity = recognizer.velocity(in: recognizer.view)
if progress > 0.5 || velocity.x > 100 {
self.interactivePopTransition?.finish()
} else {
self.interactivePopTransition?.cancel()
}
self.interactivePopTransition = nil
default:
break
}
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if self.isSystemSlidBack ?? false {
self.interactivePopGestureRecognizer?.isEnabled = true
self.popEdgePanGesture?.isEnabled = false
}else{
self.interactivePopGestureRecognizer?.isEnabled = false
self.popEdgePanGesture?.isEnabled = true
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return self.viewControllers.count == 1 ? false : true
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
let hasChildViewController = self.viewControllers.count > 0
if hasChildViewController {
viewController.hidesBottomBarWhenPushed = true
}
super.pushViewController(viewController, animated: animated)
if hasChildViewController {
viewController.navigationController?.navigationBar.isHidden = false
}
}
override var childForStatusBarStyle: UIViewController? {
return self.topViewController
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if let topViewController = self.topViewController {
return topViewController.preferredStatusBarStyle
}
return self.preferredStatusBarStyle
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self.interactivePopTransition
}
override var childForHomeIndicatorAutoHidden: UIViewController? {
return topViewController
}
}
extension UINavigationController {
class func configNavigationBarAppearance() {
DispatchQueue.once(NSStringFromClass(UINavigationController.self)) {
let appearance = UINavigationBar.appearance()
appearance.shadowImage = UIImage()
appearance.tintColor = UIColor.textColor //
appearance.isTranslucent = false //
appearance.barTintColor = .white //
appearance.backgroundColor = .white
let titleColor = UIColor.textColor
let titleTextAttributes = [
NSAttributedString.Key.foregroundColor: titleColor,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: .medium)
]
appearance.titleTextAttributes = titleTextAttributes
if #available(iOS 15.0, *) {
let newAppearance = UINavigationBarAppearance()
newAppearance.configureWithOpaqueBackground()
newAppearance.backgroundColor = .white
newAppearance.shadowImage = UIImage()
newAppearance.shadowColor = nil
newAppearance.titleTextAttributes = titleTextAttributes
appearance.standardAppearance = newAppearance
appearance.scrollEdgeAppearance = appearance.standardAppearance
}
}
}
//
fileprivate func setNeedsNavigationBackground(alpha: CGFloat) {
if let barBackgroundView = navigationBar.subviews.first {
let valueForKey = barBackgroundView.getIvar(forKey:)
if let shadowView = valueForKey("_shadowView") as? UIView {
shadowView.alpha = alpha
shadowView.isHidden = alpha == 0
}
if navigationBar.isTranslucent {
if #available(iOS 10.0, *) {
if let backgroundEffectView = valueForKey("_backgroundEffectView") as? UIView, navigationBar.backgroundImage(for: .default) == nil {
backgroundEffectView.alpha = alpha
return
}
} else {
if let adaptiveBackdrop = valueForKey("_adaptiveBackdrop") as? UIView , let backdropEffectView = adaptiveBackdrop.value(forKey: "_backdropEffectView") as? UIView {
backdropEffectView.alpha = alpha
return
}
}
}
barBackgroundView.alpha = alpha
}
}
}
extension UIViewController {
fileprivate struct AssociatedKeys {
static var navBarBgAlpha: CGFloat = 1.0
static var navBarTintColor: UIColor = UIColor.baseColor
}
public var navBarBgAlpha: CGFloat {
get {
guard let alpha = objc_getAssociatedObject(self, &AssociatedKeys.navBarBgAlpha) as? CGFloat else {
return 1.0
}
return alpha
}
set {
let alpha = max(min(newValue, 1), 0) // 0~1
objc_setAssociatedObject(self, &AssociatedKeys.navBarBgAlpha, alpha, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// Update UI
navigationController?.setNeedsNavigationBackground(alpha: alpha)
}
}
public var navBarTintColor: UIColor {
get {
guard let tintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarTintColor) as? UIColor else {
return UIColor.baseColor
}
return tintColor
}
set {
navigationController?.navigationBar.tintColor = newValue
objc_setAssociatedObject(self, &AssociatedKeys.navBarTintColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
extension NSObject {
func getIvar(forKey key: String) -> Any? {
guard let _var = class_getInstanceVariable(type(of: self), key) else {
return nil
}
return object_getIvar(self, _var)
}
}
@@ -0,0 +1,49 @@
//
// MktTableViewController.swift
// iMarket
//
// Created by on 2023/9/2.
//
import Foundation
import UIKit
class MktTableViewController: MktViewController {
//
var page: Int = 1
//
var pageSize: Int = 10
//style
var style: UITableView.Style = .plain
//insert
var insert: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: Mkt.safe_bottom, right: 0)
//
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: style)
tableView.separatorStyle = .none
tableView.separatorInset = .zero
tableView.estimatedRowHeight = 0.0
tableView.estimatedSectionFooterHeight = 0.0
tableView.estimatedSectionHeaderHeight = 0.0
tableView.contentInsetAdjustmentBehavior = .never
tableView.showsVerticalScrollIndicator = false
tableView.backgroundColor = .clear
tableView.scrollsToTop = true
tableView.keyboardDismissMode = .onDrag
tableView.tableFooterView = UIView()
tableView.contentInset = insert
return tableView
}()
}
// MARK: tableivew
extension UITableView {
//
func register(_ cellClass: AnyClass) {
self.register(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifier)
}
//
func dequeueReusableCell(_ cellClass: AnyClass) -> UITableViewCell? {
self.dequeueReusableCell(withIdentifier: cellClass.reuseIdentifier)
}
}
@@ -0,0 +1,184 @@
//
// MktViewController.swift
// iMarket
//
// Created by on 2023/8/31.
//
import UIKit
import SwiftTheme
class MktViewController: UIViewController {
//
public var params: [String: Any]?
//
public var backHandle: ((_ data: [String : Any]?) -> Void)?
// false
var hidesNavBar: Bool { false }
//
required init() {
super.init(nibName: nil, bundle: nil)
}
required init(_ para: [String : Any]?, _ block: (([String : Any]?) -> Void)?) {
super.init(nibName: nil, bundle: nil)
self.params = para
self.backHandle = block
}
required init?(coder: NSCoder) {
super.init(coder: coder)
//fatalError("init(coder:) has not been implemented")
}
//
typealias DidBackHandler = (_ viewController: UIViewController) -> Void
private var backHandler: DidBackHandler?
//
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let count = self.navigationController?.viewControllers.count ?? 0
if hidesNavBar {
self.navigationController?.navigationBar.isHidden = true
} else if count > 1 {
self.navigationController?.navigationBar.isHidden = false
let leftItem = UIBarButtonItem(image: UIImage(named: "black_back"), style: .plain, target: self, action: #selector(backButtonEvent))
self.navigationItem.leftBarButtonItem = leftItem
} else {
self.navigationController?.navigationBar.isHidden = true
}
NotificationCenter.default.addObserver(
self,
selector: #selector(onThemeChanged),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
}
/// base
@objc func onThemeChanged() {}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// TabBarContainer
}
//
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let count = self.navigationController?.viewControllers.count ?? 0
if hidesNavBar {
self.navigationController?.navigationBar.isHidden = true
} else {
self.navigationController?.navigationBar.isHidden = (count > 1) ? false : true
}
}
//view
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
self.view.endEditing(true)
}
//
func didBackHandler(_ backHandler: @escaping DidBackHandler) {
self.backHandler = backHandler
}
//
@objc func backButtonEvent() {
//
if (self.backHandler != nil) {
self.backHandler?(self)
return
}
self.navigationController?.popViewController(animated: true)
}
//
deinit {
dlog(message: "\(NSStringFromClass(Self.self)) dealloc")
}
}
// MARK: -
extension MktViewController {
///
/// - Parameters:
/// - title:
/// - action:
func setLeftBarButton(title: String, action: Selector) {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.titleLabel?.font = .caption
button.backgroundColor = .clear
button.addTarget(self, action: action, for: .touchUpInside)
let item = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = item
}
///
/// - Parameters:
/// - image:
/// - action:
func setLeftBarButton(image: UIImage?, action: Selector) {
let button = UIButton(type: .system)
button.setImage(image, for: .normal)
button.backgroundColor = .clear
button.addTarget(self, action: action, for: .touchUpInside)
let item = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = item
}
///
/// - Parameters:
/// - title:
/// - action:
func setRightBarButton(title: String, action: Selector) {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.titleLabel?.font = .caption
button.backgroundColor = .clear
button.addTarget(self, action: action, for: .touchUpInside)
let item = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = item
}
///
/// - Parameters:
/// - image:
/// - action:
func setRightBarButton(image: UIImage?, action: Selector) {
let button = UIButton(type: .system)
button.setImage(image, for: .normal)
button.backgroundColor = .clear
button.addTarget(self, action: action, for: .touchUpInside)
let item = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = item
}
///
/// - Parameters:
/// - leftTitle:
/// - leftAction:
/// - rightTitle:
/// - rightAction:
func setBarButtons(leftTitle: String, leftAction: Selector, rightTitle: String, rightAction: Selector) {
setLeftBarButton(title: leftTitle, action: leftAction)
setRightBarButton(title: rightTitle, action: rightAction)
}
///
/// - Parameters:
/// - leftImage:
/// - leftAction:
/// - rightImage:
/// - rightAction:
func setBarButtons(leftImage: UIImage?, leftAction: Selector, rightImage: UIImage?, rightAction: Selector) {
setLeftBarButton(image: leftImage, action: leftAction)
setRightBarButton(image: rightImage, action: rightAction)
}
}
@@ -0,0 +1,166 @@
//
// MktWebViewController.swift
// HealthEmergency
//
// Created by Claude Code
//
import UIKit
import WebKit
import SnapKit
import SwiftTheme
class MktWebViewController: MktViewController {
private var webView = WKWebView()
private let progressView = UIProgressView(progressViewStyle: .default)
private var urlString: String = ""
private var pageTitle: String = ""
// MARK: - Initialization
convenience init(urlString: String, title: String = "") {
self.init()
self.urlString = urlString
self.pageTitle = title
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = pageTitle.isEmpty ? "加载中..." : pageTitle
setupUI()
setupTheme()
loadURL()
}
// MARK: - Setup
private func setupUI() {
//
progressView.theme_progressTintColor = ThemeKey.primaryColor
progressView.theme_trackTintColor = ThemeKey.backgroundColor
self.view.addSubview(progressView)
progressView.snp.makeConstraints { make in
make.top.left.right.equalTo(self.view)
make.height.equalTo(2)
}
// WebView
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
webView.uiDelegate = self
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
self.webView = webView
self.view.addSubview(webView)
webView.snp.makeConstraints { make in
make.top.equalTo(progressView.snp.bottom)
make.left.right.bottom.equalTo(self.view)
}
}
private func setupTheme() {
view.theme_backgroundColor = ThemeKey.backgroundColor
}
// MARK: - Load URL
private func loadURL() {
guard !urlString.isEmpty else {
showError("URL 为空")
return
}
var urlToLoad = urlString
if !urlToLoad.hasPrefix("http://") && !urlToLoad.hasPrefix("https://") {
urlToLoad = "https://" + urlToLoad
}
guard let url = URL(string: urlToLoad) else {
showError("无效的 URL")
return
}
let request = URLRequest(url: url)
webView.load(request)
}
// MARK: - KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
progressView.progress = Float(webView.estimatedProgress)
progressView.isHidden = webView.estimatedProgress == 1.0
}
}
deinit {
webView.removeObserver(self, forKeyPath: "estimatedProgress")
}
// MARK: - Helper
private func showError(_ message: String) {
let alert = UIAlertController(title: "错误", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default))
self.present(alert, animated: true)
}
}
// MARK: - WKNavigationDelegate
extension MktWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
progressView.isHidden = false
progressView.progress = 0.1
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
progressView.isHidden = true
progressView.progress = 1.0
//
if self.title == "加载中..." || self.title?.isEmpty ?? true {
self.title = webView.title ?? "网页"
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
progressView.isHidden = true
showError("加载失败: \(error.localizedDescription)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
progressView.isHidden = true
showError("加载失败: \(error.localizedDescription)")
}
}
// MARK: - WKUIDelegate
extension MktWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler()
})
self.present(alert, animated: true)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alert = UIAlertController(title: "确认", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler(true)
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { _ in
completionHandler(false)
})
self.present(alert, animated: true)
}
}
@@ -0,0 +1,168 @@
//
// MktWebViewControllerGuide.swift
// HealthEmergency
//
// MktWebViewController 使
//
import UIKit
/**
# MktWebViewController 使
##
- H5
-
-
-
-
- JavaScript
##
### 1. 使
```swift
//
let webVC = MktWebViewController(urlString: "https://example.com", title: "")
self.navigationController?.pushViewController(webVC, animated: true)
// URL
let webVC = MktWebViewController(urlString: "https://example.com")
self.navigationController?.pushViewController(webVC, animated: true)
```
### 2. TabBar 使
```swift
let webVC = MktWebViewController(urlString: "https://example.com", title: "")
let navVC = MktNavigatonController(rootViewController: webVC)
// TabBar
```
### 3.
```swift
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let url = dataList[indexPath.row].url
let title = dataList[indexPath.row].title
let webVC = MktWebViewController(urlString: url, title: title)
self.navigationController?.pushViewController(webVC, animated: true)
}
```
##
###
-
-
-
###
- "..."
-
-
###
-
-
-
###
-
-
-
### JavaScript
- alert
- confirm
-
##
```swift
class NewsViewController: MktTableViewController {
var newsList: [NewsModel] = []
override func viewDidLoad() {
super.viewDidLoad()
self.title = ""
setupUI()
setupTheme()
}
private func setupUI() {
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(NewsCell.self)
}
private func setupTheme() {
self.view.setThemeBackground()
observeThemeChanges { [weak self] in
self?.tableView.backgroundColor = .themeBackground
self?.tableView.reloadData()
}
}
}
extension NewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(NewsCell.self) as! NewsCell
cell.configure(with: newsList[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let news = newsList[indexPath.row]
let webVC = MktWebViewController(urlString: news.url, title: news.title)
self.navigationController?.pushViewController(webVC, animated: true)
}
}
```
##
```
MktViewController ( ViewController)
MktTableViewController ()
MktCollectionViewController ()
MktWebViewController (H5 )
MktNavigatonController ()
ThemeManager ()
Extension+Theme (UI )
Extension+ViewController ()
```
##
1. **URL ** - https://
2. **** -
3. **** - KVO
4. **** -
##
MktWebViewController
```swift
class CustomWebViewController: MktWebViewController {
//
override func viewDidLoad() {
super.viewDidLoad()
//
}
}
```
*/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
//
// CacheExtension.swift
// CacheKit
//
// Created by hp on 2023/7/27.
//
import Foundation
protocol CacheExtension where Self: Codable {
static func cachedObject(forKey key: String) -> Self?
func cache(forKey key: String) -> Void
}
extension CacheExtension {
static func cachedObject(forKey key: String) -> Self? {
CacheManager[key, Self.self]
}
func cache(forKey key: String) -> Void {
CacheManager[key, Self.self] = self
}
}
extension Int: CacheExtension {}
extension Int8: CacheExtension {}
extension Int16: CacheExtension {}
extension Int32: CacheExtension {}
extension Int64: CacheExtension {}
extension UInt: CacheExtension {}
extension UInt8: CacheExtension {}
extension UInt16: CacheExtension {}
extension UInt32: CacheExtension {}
extension UInt64: CacheExtension {}
extension Float: CacheExtension {}
@available(iOS 14.0, *)
extension Float16: CacheExtension {}
extension Float64: CacheExtension {}
extension Bool: CacheExtension {}
extension String: CacheExtension {}
@@ -0,0 +1,90 @@
//
// CacheManager.swift
// CacheKit
//
// Created by hp on 2023/7/27.
//
import Foundation
public class CacheManager {
public static let shared = CacheManager()
private var cache: Cache?
struct Constant {
static let memoryCostLimit: UInt = 200 * 1024 * 1024
static let diskCostLimit: UInt = 500 * 1024 * 1024
}
private init() {
let cache = Cache.init()
cache?.memoryCache.costLimit = Constant.memoryCostLimit
cache?.diskCache.costLimit = Constant.diskCostLimit
self.cache = cache
}
}
extension CacheManager: Cachable, CacheSize {
public var totalCost: UInt {
return self.cache?.diskCache.totalCost ?? 0
}
public var totalCount: Int {
return self.cache?.diskCache.totalCount ?? 0
}
public func set<T>(object: T, forKey key: String, cost: UInt = 0) where T : Decodable, T : Encodable {
self.cache?.set(object: object, forKey: key, cost: cost)
}
public func set<T>(object: T, forKey key: String, cost: UInt = 0, completion: ((String) -> Void)?) where T : Decodable, T : Encodable {
self.cache?.set(object: object, forKey: key, cost: cost, completion: completion)
}
public func object<T>(forKey key: String, type: T.Type) -> T? where T : Decodable, T : Encodable {
return self.cache?.object(forKey: key, type: type)
}
public func object<T>(forKey key: String, type: T.Type, completion: ((String, T?) -> Void)?) where T : Decodable, T : Encodable {
self.cache?.object(forKey: key, type: type, completion: completion)
}
public func containsObject(forKey key: String) -> Bool {
guard let cache = self.cache else { return false }
return cache.containsObject(forKey: key)
}
public func containsObject(forKey key: String, completion: ((String, Bool) -> Void)?) {
self.cache?.containsObject(forKey: key, completion: completion)
}
public func removeAll() {
self.cache?.removeAll()
}
public func removeAll(completion: (() -> Void)?) {
self.cache?.removeAll(completion: completion)
}
public func removeObject(forKey key: String) {
self.cache?.removeObject(forKey: key)
}
public func removeObject(forKey key: String, completion: (() -> Void)?) {
self.cache?.removeObject(forKey: key, completion: completion)
}
}
extension CacheManager {
class subscript<T: Codable>(_ key: String, _ type: T.Type) -> T? {
set {
CacheManager.shared.set(object: newValue, forKey: key)
}
get {
CacheManager.shared.object(forKey: key, type: type)
}
}
}
@@ -0,0 +1,27 @@
//
// APIKey.h
// SearchV3Demo
//
// Created by songjian on 13-8-14.
// Copyright (c) 2013 songjian. All rights reserved.
//
enum APIKey {
enum Map {
static let key = "xxxxx"
}
enum User {
static let token = "xxxxx"
}
/// IM
/// SDKAppID -> IM -> -> SDKAppID
/// secretKey UserSig使
enum IM {
static let sdkAppID: Int = 0 // TODO: IM SDKAppID
static let secretKey: String = "" // TODO: SecretKey使 UserSig
}
}
@@ -0,0 +1,77 @@
//
// NetworkConfig.swift
// HealthEmergency
//
// - IP
//
//
// - Release UserDefaults
// - Debug UserDefaults8
import Foundation
struct NetworkConfig {
// MARK: -
enum Environment: String, CaseIterable {
case dev = "dev" //
case test = "test" //
case release = "release" //
var displayName: String {
switch self {
case .dev: return "开发环境"
case .test: return "测试环境"
case .release: return "正式环境"
}
}
var apiBaseURL: String {
switch self {
case .dev: return "http://192.168.1.201:24801"
case .test: return "https://dev.yixiong-tech.com:8081"
case .release: return "https://bac.new.hamkke.top"
}
}
var h5BaseURL: String {
switch self {
case .dev: return "http://192.168.1.91:5500"
case .test: return "https://dev.yixiong-tech.com:8085"
case .release: return "https://bac.new.hamkke.top"
}
}
}
// MARK: - UserDefaults Key
private static let envKey = "com.jkcq.network.environment"
// MARK: - Release Debug UserDefaults
static var current: Environment {
#if DEBUG
let raw = UserDefaults.standard.string(forKey: envKey) ?? Environment.dev.rawValue
return Environment(rawValue: raw) ?? .dev
#else
return .release
#endif
}
// MARK: - Debug
static func switchEnvironment(_ env: Environment) {
#if DEBUG
UserDefaults.standard.set(env.rawValue, forKey: envKey)
UserDefaults.standard.synchronize()
#endif
}
// MARK: - 访
static var baseURL: String { current.apiBaseURL }
static var h5BaseURL: String { current.h5BaseURL }
}
@@ -0,0 +1,29 @@
//
// Extension+Codable.swift
// HealthEmergency
//
// Codable
// String / Int / Long / Double
import Foundation
extension KeyedDecodingContainer {
/// String / Int64 / Double String
/// null ""
func flexString(_ key: Key) -> String {
if let s = try? decode(String.self, forKey: key) { return s }
if let n = try? decode(Int64.self, forKey: key) { return String(n) }
if let n = try? decode(Double.self, forKey: key) { return String(n) }
return ""
}
/// String / Int64 / Double String
/// null nil
func flexStringOpt(_ key: Key) -> String? {
if let s = try? decodeIfPresent(String.self, forKey: key) { return s }
if let n = try? decodeIfPresent(Int64.self, forKey: key) { return String(n) }
if let n = try? decodeIfPresent(Double.self, forKey: key) { return String(n) }
return nil
}
}
@@ -0,0 +1,150 @@
//
// Extension+Common.swift
// iMarket
//
// Created by on 2023/8/30.
//
import Foundation
import UIKit
import CommonCrypto
extension String {
///
/// U+FF01~FF5E U+0021~007E U+3000
var halfWidth: String {
String(unicodeScalars.map { scalar -> Character in
switch scalar.value {
case 0xFF01...0xFF5E:
return Character(UnicodeScalar(scalar.value - 0xFF01 + 0x0021)!)
case 0x3000:
return Character(UnicodeScalar(0x0020)!)
default:
return Character(scalar)
}
})
}
var sha256: String {
let utf8 = cString(using: .utf8)
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest)
return digest.reduce("") { $0 + String(format:"%02x", $1) }
}
static func getCurrentDate(dateFormat: String = "yyyy-MM-dd HH:mm:ss") -> String {
let date = NSDate()
let dateformatter = DateFormatter()
dateformatter.dateFormat = dateFormat
let dateString = dateformatter.string(from: date as Date)
return dateString
}
//MARK: -
static func compareToCurrentTime(time: Int) -> Bool {
let currentTime = Date().timeIntervalSince1970
let timeStamp = TimeInterval(time)
return (currentTime > timeStamp)
}
//MARK: -
static func compareCurrentTime(time: Int) -> String {
//
let currentTime = Date().timeIntervalSince1970
//
let timeStamp = TimeInterval(time)
//
let reduceTime = currentTime - timeStamp
//60
if reduceTime < 60 {
return "刚刚"
}
//60
let mins = Int(reduceTime / 60)
if mins < 60 {
return "\(mins)分钟前"
}
let hours = Int(reduceTime / 3600)
if hours < 24 {
return "\(hours)小时前"
}
let days = Int(reduceTime / 3600 / 24)
if days < 30 {
return "\(days)天前"
}
//--------
let date = NSDate(timeIntervalSince1970: timeStamp)
let dfmatter = DateFormatter()
//yyyy-MM-dd HH:mm:ss
dfmatter.dateFormat="yyyy年MM月dd日 HH:mm:ss"
return dfmatter.string(from: date as Date)
}
}
//
extension Int {
/// string
/// - Parameter format:
/// - Returns:
public func timeStampToString(format: String = "yyyy-MM-dd HH:mm:ss") -> String {
let timeSta: TimeInterval = TimeInterval(self)
let date = NSDate(timeIntervalSince1970: timeSta)
let dateformatter = DateFormatter()
dateformatter.dateFormat = format
let dateString = dateformatter.string(from: date as Date)
return dateString
}
/// string
/// - Parameter format:
/// - Returns:
public func millisecondTimeStampToString(format: String = "yyyy-MM-dd HH:mm:ss") -> String {
let timeSta: TimeInterval = TimeInterval(self / 1000)
let date = NSDate(timeIntervalSince1970: timeSta)
let dateformatter = DateFormatter()
dateformatter.dateFormat = format
let dateString = dateformatter.string(from: date as Date)
return dateString
}
}
extension UIControl {
///
/// - Parameters:
/// - target: target
/// - action: action
public func addTarget(_ target: Any?, action: Selector) {
self.addTarget(target, action: action, for: .touchUpInside)
}
// 44*44
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
var bounds: CGRect = self.bounds;
//44x44
let widthDelta: CGFloat = max(44.0 - bounds.size.width, 0)
let heightDelta: CGFloat = max(44.0 - bounds.size.height, 0);
bounds = bounds.insetBy(dx: -0.5*widthDelta, dy: -0.5*heightDelta)
let isContain: Bool = bounds.contains(point)
return isContain;
}
}
extension UITextField {
public func limit(shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard string == "." || string == "0" else {
let newString = (self.text! as NSString).replacingCharacters(in: range, with: string)
let expression = "^[0-9]{0,6}?$*((\\.|,)[0-9]{0,2})?$"
let regex = try! NSRegularExpression(pattern: expression, options: NSRegularExpression.Options.allowCommentsAndWhitespace)
let numberOfMatches = regex.numberOfMatches(in: newString, options:.reportProgress, range: NSMakeRange(0, (newString as NSString).length))
return numberOfMatches != 0
}
guard let text = self.text else { return true }
if text.range(of: ".") != nil && string == "." {
return false
}
if text.range(of: ".") != nil{
let list = self.text!.components(separatedBy: ".")
let last = list.last!
return (last as NSString).length < 2
}
return true
}
}
@@ -0,0 +1,121 @@
//
// Extension+Theme.swift
// HealthEmergency
//
// Created by Claude Code
//
import UIKit
import SwiftTheme
// MARK: - UIColor
extension UIColor {
static var themePrimary: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.primaryColor] as? String else {
return .systemBlue
}
return UIColor(rgba: hex)
}
static var themeSecondary: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.secondaryColor] as? String else {
return .systemBlue
}
return UIColor(rgba: hex)
}
static var themeBackground: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.backgroundColor] as? String else {
return .systemBackground
}
return UIColor(rgba: hex)
}
static var themeText: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.textColor] as? String else {
return .label
}
return UIColor(rgba: hex)
}
static var themeNavigationBar: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.navBarColor] as? String else {
return .systemBlue
}
return UIColor(rgba: hex)
}
static var themeNavigationBarText: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.navBarTextColor] as? String else {
return .white
}
return UIColor(rgba: hex)
}
static var themeButtonBackground: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.buttonBgColor] as? String else {
return .systemBlue
}
return UIColor(rgba: hex)
}
static var themeButtonText: UIColor {
guard let hex = ThemeManager.currentTheme?[ThemeKey.Raw.buttonTextColor] as? String else {
return .white
}
return UIColor(rgba: hex)
}
}
// MARK: - UIView
extension UIView {
func setThemeBackground() {
self.backgroundColor = .themeBackground
}
func setThemePrimaryBackground() {
self.backgroundColor = .themePrimary
}
}
// MARK: - UILabel
extension UILabel {
func setThemeTextColor() {
self.textColor = .themeText
}
func setThemePrimaryColor() {
self.textColor = .themePrimary
}
}
// MARK: - UIButton
extension UIButton {
func setThemeStyle() {
self.backgroundColor = .themeButtonBackground
self.setTitleColor(.themeButtonText, for: .normal)
}
func setThemeOutlineStyle(borderWidth: CGFloat = 1) {
self.backgroundColor = .clear
self.setTitleColor(.themePrimary, for: .normal)
self.layer.borderWidth = borderWidth
self.layer.borderColor = UIColor.themePrimary.cgColor
}
func setThemeTextColor() {
self.setTitleColor(.themeText, for: .normal)
}
func setThemePrimaryTextColor() {
self.setTitleColor(.themePrimary, for: .normal)
}
func setThemeButtonPrimaryBackground() {
self.backgroundColor = .themePrimary
}
func setThemeButtonBackground() {
self.backgroundColor = .themeBackground
}
}
@@ -0,0 +1,168 @@
//
// Extension+UIColor.swift
// iMarket
//
// Created by on 2023/8/30.
//
import Foundation
import UIKit
// MARK: - UIFont
extension UIFont {
/// 34pt
class var title1Bold: UIFont { .systemFont(ofSize: 34, weight: .bold) }
/// 28pt
class var title2Bold: UIFont { .systemFont(ofSize: 28, weight: .bold) }
/// 22pt
class var title3Bold: UIFont { .systemFont(ofSize: 22, weight: .bold) }
/// 18pt
class var navTitle: UIFont { .systemFont(ofSize: 18, weight: .medium) }
/// 16pt
class var bodyBold: UIFont { .systemFont(ofSize: 16, weight: .semibold) }
/// 16pt
class var body: UIFont { .systemFont(ofSize: 16, weight: .regular) }
/// 14pt
class var caption: UIFont { .systemFont(ofSize: 14, weight: .regular) }
/// 14pt
class var captionBold: UIFont { .systemFont(ofSize: 14, weight: .medium) }
/// 12pt
class var small: UIFont { .systemFont(ofSize: 12, weight: .regular) }
/// 12pt
class var smallBold: UIFont { .systemFont(ofSize: 12, weight: .medium) }
/// 16pt
class var button: UIFont { .systemFont(ofSize: 16, weight: .semibold) }
/// 14pt
class var buttonSmall: UIFont { .systemFont(ofSize: 14, weight: .semibold) }
}
extension UIColor {
//
class var textColor: UIColor {
return UIColor.init(red: 51 / 255.0, green: 51 / 255.0, blue: 51 / 255.0, alpha: 1)
}
//
class var subTextColor: UIColor {
UIColor(red: 135 / 255.0, green: 135 / 255.0, blue: 135 / 255.0, alpha: 1)
}
class var greyTextColor: UIColor {
UIColor(red: 153.0 / 255.0, green: 153.0 / 255.0, blue: 153.0 / 255.0, alpha: 1)
}
class var themColor: UIColor {
return UIColor.init(red: 0/255.0, green: 93/255.0, blue: 255/255.0, alpha: 1)
}
//
class var borderColor: UIColor {
return UIColor(red: 244.0/255, green: 244.0/255, blue: 244.0/255, alpha: 1)
}
//线
class var lineViewColor: UIColor {
return UIColor.init(red: 244/255, green: 244/255, blue: 244/255, alpha: 1)
}
//
class var fillBoxColor: UIColor {
return UIColor.init(red: 249/255, green: 250/255, blue: 251/255, alpha: 1)
}
class var B5BCCE: UIColor {
return UIColor.init(red: 181.0/255, green: 188.0/255, blue: 206.0/255, alpha: 1)
}
class var baseColor: UIColor {
return UIColor(red: 247 / 255.0, green: 248 / 255.0, blue: 250 / 255.0, alpha: 1)
}
class var defaultColor: UIColor {
return UIColor.init(red: 1, green: 1, blue: 1, alpha: 1)
}
/// RGB(242, 245, 250)
class var pageBackground: UIColor {
return UIColor(red: 242/255, green: 245/255, blue: 250/255, alpha: 1)
}
class func rgb(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> UIColor {
UIColor.init(r, g, b)
}
}
extension UIColor {
//使rgba
convenience init(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat, a: CGFloat = 1.0) {
let red = r / 255.0
let green = g / 255.0
let blue = b / 255.0
self.init(red: red, green: green, blue: blue, alpha: a)
}
//16
class func colorToHex(_ hex: String, _ alpha: CGFloat = 1.0) -> UIColor {
var colorString = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased()
if colorString.count < 6 {
return UIColor.clear
}
if colorString.hasPrefix("0x") {
colorString = (colorString as NSString).substring(from: 2)
}
if colorString.hasPrefix("#") {
colorString = (colorString as NSString).substring(from: 1)
}
if colorString.count < 6 {
return UIColor.clear
}
var rang = NSRange()
rang.location = 0
rang.length = 2
let rString = (colorString as NSString).substring(with: rang)
rang.location = 2
let gString = (colorString as NSString).substring(with: rang)
rang.location = 4
let bString = (colorString as NSString).substring(with: rang)
var r:UInt64 = 0, g:UInt64 = 0,b: UInt64 = 0
Scanner(string: rString).scanHexInt64(&r)
Scanner(string: gString).scanHexInt64(&g)
Scanner(string: bString).scanHexInt64(&b)
return UIColor.init(CGFloat(r), CGFloat(g), CGFloat(b), a: alpha)
}
//
class var randomColor: UIColor {
get {
let red = CGFloat(arc4random()%256)/255.0
let green = CGFloat(arc4random()%256)/255.0
let blue = CGFloat(arc4random()%256)/255.0
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
}
extension UIColor {
convenience init(hex: String) {
var hexString = hex.replacingOccurrences(of: "#", with: "")
if hexString.count == 6 {
hexString += "FF"
}
var hexNumber: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&hexNumber)
let r = CGFloat((hexNumber & 0xFF000000) >> 24) / 255
let g = CGFloat((hexNumber & 0x00FF0000) >> 16) / 255
let b = CGFloat((hexNumber & 0x0000FF00) >> 8) / 255
let a = CGFloat(hexNumber & 0x000000FF) / 255
self.init(red: r, green: g, blue: b, alpha: a)
}
}
@@ -0,0 +1,445 @@
//
// Extension+UIView.swift
// iMarket
// UIView
// Created by on 2023/8/30.
//
import UIKit
//UIView
fileprivate typealias gesture = ((_ gesture: UITapGestureRecognizer)->())
extension UIView {
@objc private func clickCallBack(_ sender: UITapGestureRecognizer) {
self.actionBlock?(sender)
}
private struct RuntimeKey {
static let actionBlock = UnsafeRawPointer.init(bitPattern: "actionBlock".hashValue)
}
private var actionBlock: gesture? {
set {
objc_setAssociatedObject(self, RuntimeKey.actionBlock!, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, RuntimeKey.actionBlock!) as? gesture
}
}
///
func clickHandle(_ listener: @escaping ((_ sender: UITapGestureRecognizer)->())) {
self.actionBlock = listener
self.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(clickCallBack(_ :)))
self.addGestureRecognizer(tap)
}
}
extension UIView {
/// view
func removeAll() {
self.subviews.forEach {
$0.removeFromSuperview()
}
if self is UIStackView {
(self as! UIStackView).arrangedSubviews.forEach{
$0.removeFromSuperview()
}
}
}
}
extension UIView {
// MARK: - () :addCornerRadius
@IBInspectable var cornerViewRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
@IBInspectable var borderViewWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
///
@IBInspectable var borderViewWidthPixel: CGFloat {
get {
return layer.borderWidth * UIScreen.main.scale
}
set {
layer.borderWidth = newValue / UIScreen.main.scale
}
}
@IBInspectable var borderViewColor: UIColor? {
get {
if let c = layer.borderColor {
return UIColor(cgColor: c)
}
return nil
}
set {
layer.borderColor = newValue?.cgColor
}
}
}
extension UIView {
@objc class var reuseIdentifier: String {
NSStringFromClass(Self.self) + #function
}
}
extension UIView {
func setGradientColor(colors: [CGColor]) -> CAGradientLayer {
//
let gradientLayer = CAGradientLayer()
//
gradientLayer.colors = colors
//
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
gradientLayer.frame = frame
//gradientLayerlayerlayer
return gradientLayer
}
}
extension UIView {
/// View
public func addSubviews(_ views: [UIView]) {
views.forEach { [weak self] eachView in
self?.addSubview(eachView)
}
}
//TODO:
/// 使
public func resizeToFitSubviews() {
var width: CGFloat = 0
var height: CGFloat = 0
for someView in self.subviews {
let aView = someView
let newWidth = aView.x + aView.width
let newHeight = aView.y + aView.height
width = max(width, newWidth)
height = max(height, newHeight)
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
/// 使
public func resizeToFitSubviews(_ tagsToIgnore: [Int]) {
var width: CGFloat = 0
var height: CGFloat = 0
for someView in self.subviews {
let aView = someView
if !tagsToIgnore.contains(someView.tag) {
let newWidth = aView.x + aView.width
let newHeight = aView.y + aView.height
width = max(width, newWidth)
height = max(height, newHeight)
}
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
///
public func resizeToFitWidth() {
let currentHeight = self.height
self.sizeToFit()
self.height = currentHeight
}
///
public func resizeToFitHeight() {
let currentWidth = self.width
self.sizeToFit()
self.width = currentWidth
}
/// xgettersetter
public var x: CGFloat {
get {
return self.frame.origin.x
} set(value) {
self.frame = CGRect(x: value, y: self.y, width: self.width, height: self.height)
}
}
/// ygettersetter
public var y: CGFloat {
get {
return self.frame.origin.y
} set(value) {
self.frame = CGRect(x: self.x, y: value, width: self.width, height: self.height)
}
}
/// gettersetter
public var width: CGFloat {
get {
return self.frame.size.width
} set(value) {
self.frame = CGRect(x: self.x, y: self.y, width: value, height: self.height)
}
}
/// gettersetter
public var height: CGFloat {
get {
return self.frame.size.height
} set(value) {
self.frame = CGRect(x: self.x, y: self.y, width: self.width, height: value)
}
}
/// xgettersetter
public var left: CGFloat {
get {
return self.x
} set(value) {
self.x = value
}
}
/// xgettersetter
public var right: CGFloat {
get {
return self.x + self.width
} set(value) {
self.x = value - self.width
}
}
/// ygettersetter
public var top: CGFloat {
get {
return self.y
} set(value) {
self.y = value
}
}
/// ygettersetter
public var bottom: CGFloat {
get {
return self.y + self.height
} set(value) {
self.y = value - self.height
}
}
///
public var origin: CGPoint {
get {
return self.frame.origin
} set(value) {
self.frame = CGRect(origin: value, size: self.frame.size)
}
}
/// center x
public var centerX: CGFloat {
get {
return self.center.x
} set(value) {
self.center.x = value
}
}
/// center y
public var centerY: CGFloat {
get {
return self.center.y
} set(value) {
self.center.y = value
}
}
/// size
public var size: CGSize {
get {
return self.frame.size
} set(value) {
self.frame = CGRect(origin: self.frame.origin, size: value)
}
}
///
public func leftOffset(_ offset: CGFloat) -> CGFloat {
return self.left - offset
}
///
public func rightOffset(_ offset: CGFloat) -> CGFloat {
return self.right + offset
}
///
public func topOffset(_ offset: CGFloat) -> CGFloat {
return self.top - offset
}
///
public func bottomOffset(_ offset: CGFloat) -> CGFloat {
return self.bottom + offset
}
/// 沿
public func alignRight(_ offset: CGFloat) -> CGFloat {
return self.width - offset
}
///
public func reorderSubViews(_ reorder: Bool = false, tagsToIgnore: [Int] = []) -> CGFloat {
var currentHeight: CGFloat = 0
for someView in subviews {
if !tagsToIgnore.contains(someView.tag) && !(someView ).isHidden {
if reorder {
let aView = someView
aView.frame = CGRect(x: aView.frame.origin.x, y: currentHeight, width: aView.frame.width, height: aView.frame.height)
}
currentHeight += someView.frame.height
}
}
return currentHeight
}
///
public func removeSubviews() {
for subview in subviews {
subview.removeFromSuperview()
}
}
/// superview
public func centerXInSuperView() {
guard let parentView = superview else {
assertionFailure("EZSwiftExtensions Error: The view \(self) doesn't have a superview")
return
}
self.x = parentView.width/2 - self.width/2
}
/// superview
public func centerYInSuperView() {
guard let parentView = superview else {
assertionFailure("EZSwiftExtensions Error: The view \(self) doesn't have a superview")
return
}
self.y = parentView.height/2 - self.height/2
}
/// superview
public func centerInSuperView() {
self.centerXInSuperView()
self.centerYInSuperView()
}
}
//MARK: --- ---
private var unsafe_badge_raw: Int = 0
struct BadgeConfig {
var backgroundColor: UIColor = .red
var font: UIFont = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
var height: CGFloat = 15
var cornerRadius: CGFloat = 7.5
var titleColor: UIColor = .white
var padding:(w: CGFloat, h: CGFloat) = (w: 6, h: 0)
var emptyWidthHeight: CGFloat = 8.0
}
extension UIView {
///
/// value""
/// setBadgeValue("")
/// setBadgeValue("1")
func setBadgeValue(_ value: String?, _ config: BadgeConfig = BadgeConfig()) {
//
objc_setAssociatedObject(self,
&unsafe_badge_raw,
value,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let badgeValue: String = value ?? ""
if !badgeValue.isEmpty {
guard isAllChinese(string: badgeValue) == false else {
clearBadgeValue()
return
}
}
let size = CGSize.init(width: CGFloat(MAXFLOAT) , height: CGFloat(MAXFLOAT))
let rect = badgeValue.boundingRect(
with: size,
options: .usesLineFragmentOrigin,
attributes: [.font: UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)],
context: nil
)
let isEmptyBadge = badgeValue.isEmpty
let width = rect.size.width > config.height ? rect.size.width + config.padding.w : isEmptyBadge ? config.emptyWidthHeight : config.height
let badgeBtn = UIButton(
frame: CGRect(x: 0, y: 0, width: width, height: isEmptyBadge ? config.emptyWidthHeight : (config.height + config.padding.h))
)
badgeBtn.center = CGPoint(x: frame.size.width - 2, y: 2)
badgeBtn.tag = 1008611
badgeBtn.layer.cornerRadius = isEmptyBadge ? (config.emptyWidthHeight)/2.0 : config.cornerRadius
badgeBtn.layer.masksToBounds = true
badgeBtn.titleLabel?.font = config.font
badgeBtn.backgroundColor = config.backgroundColor
badgeBtn.setTitleColor(config.titleColor, for: .normal)
badgeBtn.setTitle(badgeValue, for: .normal)
addSubview(badgeBtn)
bringSubviewToFront(badgeBtn)
}
/// badgeValue
public var badgeValue: String? {
guard let valueStr = objc_getAssociatedObject(self, &unsafe_badge_raw) as? String,
let value = Int(valueStr)
else { return nil }
if value < 0 {
return "0"
} else {
return valueStr
}
}
/// badgeValue
func clearBadgeValue() {
for view in subviews {
if (view is UIButton) && view.tag == 1008611 {
view.removeFromSuperview()
}
}
}
///
func isAllChinese(string: String) -> Bool {
for character:Character in string
{
if "\(character)".lengthOfBytes(using: String.Encoding.utf8) != 3
{
return false
}
}
return true
}
}
@@ -0,0 +1,41 @@
//
// Extension+ViewController.swift
// HealthEmergency
//
// Created by Claude Code
//
import UIKit
import SwiftTheme
extension UIViewController {
///
/// - Parameter updateHandler:
func observeThemeChanges(_ updateHandler: @escaping () -> Void) {
objc_setAssociatedObject(
self,
&ThemeUpdateHandlerKey,
updateHandler,
.OBJC_ASSOCIATION_COPY_NONATOMIC
)
NotificationCenter.default.addObserver(
self,
selector: #selector(_themeDidChange),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
}
@objc private func _themeDidChange() {
if let handler = objc_getAssociatedObject(
self,
&ThemeUpdateHandlerKey
) as? (() -> Void) {
handler()
}
}
}
private var ThemeUpdateHandlerKey: UInt8 = 0
@@ -0,0 +1,37 @@
//
// JPBounceView.h
// Infinitee2.0
//
// Created by Apple on 2017/10/12.
// Copyright © 2017年 Infinitee. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface JPBounceView : UIView
/** 默认0.15 */
@property (nonatomic, assign) NSTimeInterval imageSetterDuration;
@property (nonatomic, strong) UIImage *image;
- (void)setImage:(UIImage *)image animated:(BOOL)animated;
/** 默认YES */
@property (nonatomic, assign) BOOL isBounce;
/** 默认1.13 */
@property (nonatomic, assign) CGFloat scale;
/** 默认0.27 */
@property (nonatomic, assign) NSTimeInterval scaleDuration;
/** 默认20.0 */
@property (nonatomic, assign) CGFloat recoverSpeed;
/** 默认17.0 */
@property (nonatomic, assign) CGFloat recoverBounciness;
/** 默认NO */
@property (nonatomic, assign) BOOL isJudgeBegin;
/** 默认YES */
@property (nonatomic, assign) BOOL isCanTouchesBegan;
@property (nonatomic, copy) void (^viewTouchUpInside)(JPBounceView *bounceView);
- (void)recover;
@property (nonatomic, assign) BOOL isTouching;
@property (nonatomic, copy) void (^touchingDidChanged)(BOOL isTouching);
@end
@@ -0,0 +1,163 @@
//
// JPBounceView.m
// Infinitee2.0
//
// Created by Apple on 2017/10/12.
// Copyright © 2017年 Infinitee. All rights reserved.
//
#import "JPBounceView.h"
#import "POP.h"
@interface JPBounceView ()
@property (nonatomic, assign) BOOL isBegin;
@end
@implementation JPBounceView
{
UIImage *_image;
}
- (instancetype)init {
if (self = [super init]) {
[self baseSetup];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self baseSetup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self baseSetup];
}
return self;
}
- (void)baseSetup {
_isJudgeBegin = NO;
_isCanTouchesBegan = YES;
_isBounce = YES;
_scale = 1.13;
_scaleDuration = 0.27;
_recoverSpeed = 20.0;
_recoverBounciness = 17.0;
_imageSetterDuration = 0.15;
}
- (void)setImage:(UIImage *)image {
[self setImage:image animated:NO];
}
- (void)setImage:(UIImage *)image animated:(BOOL)animated {
if (_image == image) return;
_image = image;
if (animated && self.imageSetterDuration > 0) {
CATransition *transition = [CATransition animation];
transition.duration = self.imageSetterDuration;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:@"JPFadeAnimation"];
}
self.layer.contents = (id)image.CGImage;
}
- (UIImage *)image {
id content = self.layer.contents;
if (content != (id)_image.CGImage) {
CGImageRef ref = (__bridge CGImageRef)(content);
if (ref && CFGetTypeID(ref) == CGImageGetTypeID()) {
_image = [UIImage imageWithCGImage:ref scale:self.layer.contentsScale orientation:UIImageOrientationUp];
} else {
_image = nil;
}
}
return _image;
}
- (void)setIsBounce:(BOOL)isBounce {
if (_isBounce == isBounce) return;
_isBounce = isBounce;
if (!isBounce) [self recover];
}
- (void)setIsTouching:(BOOL)isTouching {
if (!self.isBounce) isTouching = NO;
if (_isTouching == isTouching) return;
_isTouching = isTouching;
if (self.scale == 1) return;
if (isTouching) {
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
anim.toValue = @(CGPointMake(self.scale, self.scale));
anim.duration = self.scaleDuration;
[self.layer pop_addAnimation:anim forKey:kPOPLayerScaleXY];
} else {
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
anim.toValue = @(CGPointMake(1.0, 1.0));
anim.springSpeed = self.recoverSpeed;
anim.springBounciness = self.recoverBounciness;
[self.layer pop_addAnimation:anim forKey:kPOPLayerScaleXY];
}
!self.touchingDidChanged ? : self.touchingDidChanged(isTouching);
}
- (void)setIsCanTouchesBegan:(BOOL)isCanTouchesBegan {
_isCanTouchesBegan = isCanTouchesBegan;
if (!isCanTouchesBegan) {
self.isBegin = isCanTouchesBegan;
self.isTouching = isCanTouchesBegan;
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
self.isBegin = self.isCanTouchesBegan;
self.isTouching = self.isCanTouchesBegan;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
if (self.isJudgeBegin && !self.isBegin) {
self.isTouching = NO;
return;
}
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint point = [touch locationInView:self];
self.isTouching = CGRectContainsPoint(self.bounds, point);
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (self.isTouching && self.viewTouchUpInside) {
if (self.isJudgeBegin) {
if (self.isBegin) self.viewTouchUpInside(self);
} else {
self.viewTouchUpInside(self);
}
}
self.isBegin = NO;
self.isTouching = NO;
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self recover];
}
- (void)recover {
self.isBegin = NO;
if (!_isTouching) return;
_isTouching = NO;
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
anim.toValue = @(CGPointMake(1.0, 1.0));
anim.duration = self.scaleDuration;
[self.layer pop_addAnimation:anim forKey:kPOPLayerScaleXY];
}
@end
@@ -0,0 +1,128 @@
//
// JPConstant.h
// Infinitee2.0
//
// Created by Apple on 2017/9/24.
// Copyright © 2017年 Infinitee. All rights reserved.
//
#import <UIKit/UIKit.h>
#define JPScale [JPConstant UIBasisWidthScale]
#define JPHScale [JPConstant UIBasisHeightScale]
@interface JPConstant : NSObject
+ (CGFloat)UIBasisWidthScale;
+ (CGFloat)UIBasisHeightScale;
@end
#pragma mark - 宏
#define JPRGBColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define JPRGBAColor(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]
#define JPRandomColor JPRGBColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256))
#define JPRandomAColor(a) JPRGBAColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256), a)
#pragma mark - 内联函数
/**
* 获取当前页码
*/
CG_INLINE NSInteger JPGetCurrentPageNumber(CGFloat offsetValue, CGFloat pageSizeValue) {
return (NSInteger)((offsetValue + pageSizeValue * 0.5) / pageSizeValue);
}
/**
* 弧度 --> 角度( π --> 180°
*/
CG_INLINE CGFloat JPRadian2Angle(CGFloat radian) {
return (radian * 180.0) / M_PI;
}
/**
* 角度 --> 弧度( 180° --> π
*/
CG_INLINE CGFloat JPAngle2Radian(CGFloat angle) {
return (angle / 180.0) * M_PI;
}
/**
* 随机整数(from <= number <= to
*/
CG_INLINE NSInteger JPRandomNumber(NSInteger from, NSInteger to) {
return (NSInteger)(from + (arc4random() % (to - from + 1)));
}
/**
* 随机布尔值(YES or NO
*/
CG_INLINE BOOL JPRandomBool(void) {
return JPRandomNumber(0, 1);
}
/**
* 随机比例值(0.0 ~ 1.0
*/
CG_INLINE CGFloat JPRandomUnsignedScale(void) {
return JPRandomNumber(0, 100) * 1.0 / 100.0;
}
/**
* 随机比例值(-1.0 ~ 1.0
*/
CG_INLINE CGFloat JPRandomScale(void) {
return (JPRandomBool() ? 1.0 : -1.0) * JPRandomUnsignedScale();
}
/**
* 随机小写字母(a ~ z
*/
CG_INLINE NSString * JPRandomLowercaseLetters(void) {
char data[1];
data[0] = (char)('a' + JPRandomNumber(0, 25));
return [[NSString alloc] initWithBytes:data length:1 encoding:NSUTF8StringEncoding];
}
/**
* 随机大写字母(A ~ Z
*/
CG_INLINE NSString * JPRandomCapitalLetter(void) {
char data[1];
data[0] = (char)('A' + JPRandomNumber(0, 25));
return [[NSString alloc] initWithBytes:data length:1 encoding:NSUTF8StringEncoding];
}
CG_INLINE CGFloat JPFromSourceToTargetValueByDifferValue(CGFloat sourceValue, CGFloat differValue, CGFloat progress) {
return sourceValue + progress * differValue;
}
CG_INLINE CGFloat JPFromSourceToTargetValue(CGFloat sourceValue, CGFloat targetValue, CGFloat progress) {
return JPFromSourceToTargetValueByDifferValue(sourceValue, (targetValue - sourceValue), progress);
}
CG_INLINE CGFloat JPHalfOfDiff(CGFloat value1, CGFloat value2) {
return (value1 - value2) * 0.5;
}
CG_INLINE CGFloat JPScaleValue(CGFloat value) {
return value * JPScale;
}
CG_INLINE CGFloat JPHScaleValue(CGFloat value) {
return value * JPHScale;
}
CG_INLINE UIFont * JPScaleFont(CGFloat fontSize) {
return [UIFont systemFontOfSize:JPScaleValue(fontSize)];
}
CG_INLINE UIFont * JPScaleBoldFont(CGFloat fontSize) {
return [UIFont boldSystemFontOfSize:JPScaleValue(fontSize)];
}
/**
* 判断两个字符串是否相等(两个都为 nil 也算相等)
*/
CG_INLINE BOOL JPStringEqual(NSString *a, NSString *b) {
return (a == b) || [a isEqualToString:b];
}
@@ -0,0 +1,30 @@
//
// JPConstant.m
// Infinitee2.0
//
// Created by Apple on 2017/9/24.
// Copyright © 2017年 Infinitee. All rights reserved.
//
#import "JPConstant.h"
#import <HealthEmergency-Swift.h>
@implementation JPConstant
static CGFloat uiBasisWScale_ = 1.0;
static CGFloat uiBasisHScale_ = 1.0;
+ (void)initialize {
uiBasisWScale_ = Env.screenWidth / 375.0;
uiBasisHScale_ = Env.screenHeight / 667.0;
}
+ (CGFloat)UIBasisWidthScale {
return uiBasisWScale_;
}
+ (CGFloat)UIBasisHeightScale {
return uiBasisHScale_;
}
@end
@@ -0,0 +1,30 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef POP_POP_H
#define POP_POP_H
#import "POPDefines.h"
#import "POPAnimatableProperty.h"
#import "POPAnimatablePropertyTypes.h"
#import "POPAnimation.h"
#import "POPAnimationEvent.h"
#import "POPAnimationExtras.h"
#import "POPAnimationTracer.h"
#import "POPAnimator.h"
#import "POPBasicAnimation.h"
#import "POPCustomAnimation.h"
#import "POPDecayAnimation.h"
#import "POPGeometry.h"
#import "POPLayerExtras.h"
#import "POPPropertyAnimation.h"
#import "POPSpringAnimation.h"
#endif /* POP_POP_H */
@@ -0,0 +1,67 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef POPACTION_H
#define POPACTION_H
#import <QuartzCore/CATransaction.h>
#import "POPDefines.h"
#ifdef __cplusplus
namespace POP {
/**
@abstract Disables Core Animation actions using RAII.
@discussion The disablement of actions is scoped to the current transaction.
*/
class ActionDisabler
{
BOOL state;
public:
ActionDisabler() POP_NOTHROW
{
state = [CATransaction disableActions];
[CATransaction setDisableActions:YES];
}
~ActionDisabler()
{
[CATransaction setDisableActions:state];
}
};
/**
@abstract Enables Core Animation actions using RAII.
@discussion The enablement of actions is scoped to the current transaction.
*/
class ActionEnabler
{
BOOL state;
public:
ActionEnabler() POP_NOTHROW
{
state = [CATransaction disableActions];
[CATransaction setDisableActions:NO];
}
~ActionEnabler()
{
[CATransaction setDisableActions:state];
}
};
}
#endif /* __cplusplus */
#endif /* POPACTION_H */
@@ -0,0 +1,256 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/NSObject.h>
#import "POPDefines.h"
#import "POPAnimatablePropertyTypes.h"
@class POPMutableAnimatableProperty;
/**
@abstract Describes an animatable property.
*/
@interface POPAnimatableProperty : NSObject <NSCopying, NSMutableCopying>
/**
@abstract Property accessor.
@param name The name of the property.
@return The animatable property with that name or nil if it does not exist.
@discussion Common animatable properties are included by default. Use the provided constants to reference.
*/
+ (id)propertyWithName:(NSString *)name;
/**
@abstract The designated initializer.
@param name The name of the property.
@param block The block used to configure the property on creation.
@return The animatable property with name if it exists, otherwise a newly created instance configured by block.
@discussion Custom properties should use reverse-DNS naming. A newly created instance is only mutable in the scope of block. Once constructed, a property becomes immutable.
*/
+ (id)propertyWithName:(NSString *)name initializer:(void (^)(POPMutableAnimatableProperty *prop))block;
/**
@abstract The name of the property.
@discussion Used to uniquely identify an animatable property.
*/
@property (readonly, nonatomic, copy) NSString *name;
/**
@abstract Block used to read values from a property into an array of floats.
*/
@property (readonly, nonatomic, copy) POPAnimatablePropertyReadBlock readBlock;
/**
@abstract Block used to write values from an array of floats into a property.
*/
@property (readonly, nonatomic, copy) POPAnimatablePropertyWriteBlock writeBlock;
/**
@abstract The threshold value used when determining completion of dynamics simulations.
*/
@property (readonly, nonatomic, assign) CGFloat threshold;
@end
/**
@abstract A mutable animatable property intended for configuration.
*/
@interface POPMutableAnimatableProperty : POPAnimatableProperty
/**
@abstract A read-write version of POPAnimatableProperty name property.
*/
@property (readwrite, nonatomic, copy) NSString *name;
/**
@abstract A read-write version of POPAnimatableProperty readBlock property.
*/
@property (readwrite, nonatomic, copy) POPAnimatablePropertyReadBlock readBlock;
/**
@abstract A read-write version of POPAnimatableProperty writeBlock property.
*/
@property (readwrite, nonatomic, copy) POPAnimatablePropertyWriteBlock writeBlock;
/**
@abstract A read-write version of POPAnimatableProperty threshold property.
*/
@property (readwrite, nonatomic, assign) CGFloat threshold;
@end
POP_EXTERN_C_BEGIN
/**
Common CALayer property names.
*/
extern NSString * const kPOPLayerBackgroundColor;
extern NSString * const kPOPLayerBounds;
extern NSString * const kPOPLayerCornerRadius;
extern NSString * const kPOPLayerBorderWidth;
extern NSString * const kPOPLayerBorderColor;
extern NSString * const kPOPLayerOpacity;
extern NSString * const kPOPLayerPosition;
extern NSString * const kPOPLayerPositionX;
extern NSString * const kPOPLayerPositionY;
extern NSString * const kPOPLayerRotation;
extern NSString * const kPOPLayerRotationX;
extern NSString * const kPOPLayerRotationY;
extern NSString * const kPOPLayerScaleX;
extern NSString * const kPOPLayerScaleXY;
extern NSString * const kPOPLayerScaleY;
extern NSString * const kPOPLayerSize;
extern NSString * const kPOPLayerSubscaleXY;
extern NSString * const kPOPLayerSubtranslationX;
extern NSString * const kPOPLayerSubtranslationXY;
extern NSString * const kPOPLayerSubtranslationY;
extern NSString * const kPOPLayerSubtranslationZ;
extern NSString * const kPOPLayerTranslationX;
extern NSString * const kPOPLayerTranslationXY;
extern NSString * const kPOPLayerTranslationY;
extern NSString * const kPOPLayerTranslationZ;
extern NSString * const kPOPLayerZPosition;
extern NSString * const kPOPLayerShadowColor;
extern NSString * const kPOPLayerShadowOffset;
extern NSString * const kPOPLayerShadowOpacity;
extern NSString * const kPOPLayerShadowRadius;
/**
Common CAShapeLayer property names.
*/
extern NSString * const kPOPShapeLayerStrokeStart;
extern NSString * const kPOPShapeLayerStrokeEnd;
extern NSString * const kPOPShapeLayerStrokeColor;
extern NSString * const kPOPShapeLayerFillColor;
extern NSString * const kPOPShapeLayerLineWidth;
extern NSString * const kPOPShapeLayerLineDashPhase;
/**
Common NSLayoutConstraint property names.
*/
extern NSString * const kPOPLayoutConstraintConstant;
#if TARGET_OS_IPHONE
/**
Common UIView property names.
*/
extern NSString * const kPOPViewAlpha;
extern NSString * const kPOPViewBackgroundColor;
extern NSString * const kPOPViewBounds;
extern NSString * const kPOPViewCenter;
extern NSString * const kPOPViewFrame;
extern NSString * const kPOPViewScaleX;
extern NSString * const kPOPViewScaleXY;
extern NSString * const kPOPViewScaleY;
extern NSString * const kPOPViewSize;
extern NSString * const kPOPViewTintColor;
/**
Common UIScrollView property names.
*/
extern NSString * const kPOPScrollViewContentOffset;
extern NSString * const kPOPScrollViewContentSize;
extern NSString * const kPOPScrollViewZoomScale;
extern NSString * const kPOPScrollViewContentInset;
extern NSString * const kPOPScrollViewScrollIndicatorInsets;
/**
Common UITableView property names.
*/
extern NSString * const kPOPTableViewContentOffset;
extern NSString * const kPOPTableViewContentSize;
/**
Common UICollectionView property names.
*/
extern NSString * const kPOPCollectionViewContentOffset;
extern NSString * const kPOPCollectionViewContentSize;
/**
Common UINavigationBar property names.
*/
extern NSString * const kPOPNavigationBarBarTintColor;
/**
Common UIToolbar property names.
*/
extern NSString * const kPOPToolbarBarTintColor;
/**
Common UITabBar property names.
*/
extern NSString * const kPOPTabBarBarTintColor;
/**
Common UILabel property names.
*/
extern NSString * const kPOPLabelTextColor;
#else
/**
Common NSView property names.
*/
extern NSString * const kPOPViewFrame;
extern NSString * const kPOPViewBounds;
extern NSString * const kPOPViewAlphaValue;
extern NSString * const kPOPViewFrameRotation;
extern NSString * const kPOPViewFrameCenterRotation;
extern NSString * const kPOPViewBoundsRotation;
/**
Common NSWindow property names.
*/
extern NSString * const kPOPWindowFrame;
extern NSString * const kPOPWindowAlphaValue;
extern NSString * const kPOPWindowBackgroundColor;
#endif
#if SCENEKIT_SDK_AVAILABLE
/**
Common SceneKit property names.
*/
extern NSString * const kPOPSCNNodePosition;
extern NSString * const kPOPSCNNodePositionX;
extern NSString * const kPOPSCNNodePositionY;
extern NSString * const kPOPSCNNodePositionZ;
extern NSString * const kPOPSCNNodeTranslation;
extern NSString * const kPOPSCNNodeTranslationX;
extern NSString * const kPOPSCNNodeTranslationY;
extern NSString * const kPOPSCNNodeTranslationZ;
extern NSString * const kPOPSCNNodeRotation;
extern NSString * const kPOPSCNNodeRotationX;
extern NSString * const kPOPSCNNodeRotationY;
extern NSString * const kPOPSCNNodeRotationZ;
extern NSString * const kPOPSCNNodeRotationW;
extern NSString * const kPOPSCNNodeEulerAngles;
extern NSString * const kPOPSCNNodeEulerAnglesX;
extern NSString * const kPOPSCNNodeEulerAnglesY;
extern NSString * const kPOPSCNNodeEulerAnglesZ;
extern NSString * const kPOPSCNNodeOrientation;
extern NSString * const kPOPSCNNodeOrientationX;
extern NSString * const kPOPSCNNodeOrientationY;
extern NSString * const kPOPSCNNodeOrientationZ;
extern NSString * const kPOPSCNNodeOrientationW;
extern NSString * const kPOPSCNNodeScale;
extern NSString * const kPOPSCNNodeScaleX;
extern NSString * const kPOPSCNNodeScaleY;
extern NSString * const kPOPSCNNodeScaleZ;
extern NSString * const kPOPSCNNodeScaleXY;
#endif
POP_EXTERN_C_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
typedef void (^POPAnimatablePropertyReadBlock)(id obj, CGFloat values[]);
typedef void (^POPAnimatablePropertyWriteBlock)(id obj, const CGFloat values[]);
@@ -0,0 +1,188 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/NSObject.h>
#import "POPAnimationTracer.h"
#import "POPGeometry.h"
@class CAMediaTimingFunction;
/**
@abstract The abstract animation base class.
@discussion Instantiate and use one of the concrete animation subclasses.
*/
@interface POPAnimation : NSObject
/**
@abstract The name of the animation.
@discussion Optional property to help identify the animation.
*/
@property (copy, nonatomic) NSString *name;
/**
@abstract The beginTime of the animation in media time.
@discussion Defaults to 0 and starts immediately.
*/
@property (assign, nonatomic) CFTimeInterval beginTime;
/**
@abstract The animation delegate.
@discussion See {@ref POPAnimationDelegate} for details.
*/
@property (weak, nonatomic) id delegate;
/**
@abstract The animation tracer.
@discussion Returns the existing tracer, creating one if needed. Call start/stop on the tracer to toggle event collection.
*/
@property (readonly, nonatomic) POPAnimationTracer *tracer;
/**
@abstract Optional block called on animation start.
*/
@property (copy, nonatomic) void (^animationDidStartBlock)(POPAnimation *anim);
/**
@abstract Optional block called when value meets or exceeds to value.
*/
@property (copy, nonatomic) void (^animationDidReachToValueBlock)(POPAnimation *anim);
/**
@abstract Optional block called on animation completion.
*/
@property (copy, nonatomic) void (^completionBlock)(POPAnimation *anim, BOOL finished);
/**
@abstract Optional block called each frame animation is applied.
*/
@property (copy, nonatomic) void (^animationDidApplyBlock)(POPAnimation *anim);
/**
@abstract Flag indicating whether animation should be removed on completion.
@discussion Setting to NO can facilitate animation reuse. Defaults to YES.
*/
@property (assign, nonatomic) BOOL removedOnCompletion;
/**
@abstract Flag indicating whether animation is paused.
@discussion A paused animation is excluded from the list of active animations. On initial creation, defaults to YES. On animation addition, the animation is implicity unpaused. On animation completion, the animation is implicity paused including for animations with removedOnCompletion set to NO.
*/
@property (assign, nonatomic, getter = isPaused) BOOL paused;
/**
@abstract Flag indicating whether animation autoreverses.
@discussion An animation that autoreverses will have twice the duration before it is considered finished. It will animate to the toValue, stop, then animate back to the original fromValue. The delegate methods are called as follows:
1) animationDidStart: is called at the beginning, as usual, and then after each toValue is reached and the autoreverse is going to start.
2) animationDidReachToValue: is called every time the toValue is reached. The toValue is swapped with the fromValue at the end of each animation segment. This means that with autoreverses set to YES, the animationDidReachToValue: delegate method will be called a minimum of twice.
3) animationDidStop:finished: is called every time the toValue is reached, the finished argument will be NO if the autoreverse is not yet complete.
*/
@property (assign, nonatomic) BOOL autoreverses;
/**
@abstract The number of times to repeat the animation.
@discussion A repeatCount of 0 or 1 means that the animation will not repeat, just like Core Animation. A repeatCount of 2 or greater means that the animation will run that many times before stopping. The delegate methods are called as follows:
1) animationDidStart: is called at the beginning of each animation repeat.
2) animationDidReachToValue: is called every time the toValue is reached.
3) animationDidStop:finished: is called every time the toValue is reached, the finished argument will be NO if the autoreverse is not yet complete.
When combined with the autoreverses property, a singular animation is effectively twice as long.
*/
@property (assign, nonatomic) NSInteger repeatCount;
/**
@abstract Repeat the animation forever.
@discussion This property will make the animation repeat forever. The value of the repeatCount property is undefined when this property is set. The finished parameter of the delegate callback animationDidStop:finished: will always be NO.
*/
@property (assign, nonatomic) BOOL repeatForever;
@end
/**
@abstract The animation delegate.
*/
@protocol POPAnimationDelegate <NSObject>
@optional
/**
@abstract Called on animation start.
@param anim The relevant animation.
*/
- (void)pop_animationDidStart:(POPAnimation *)anim;
/**
@abstract Called when value meets or exceeds to value.
@param anim The relevant animation.
*/
- (void)pop_animationDidReachToValue:(POPAnimation *)anim;
/**
@abstract Called on animation stop.
@param anim The relevant animation.
@param finished Flag indicating finished state. Flag is true if the animation reached completion before being removed.
*/
- (void)pop_animationDidStop:(POPAnimation *)anim finished:(BOOL)finished;
/**
@abstract Called each frame animation is applied.
@param anim The relevant animation.
*/
- (void)pop_animationDidApply:(POPAnimation *)anim;
@end
@interface NSObject (POP)
/**
@abstract Add an animation to the reciver.
@param anim The animation to add.
@param key The key used to identify the animation.
@discussion The 'key' may be any string such that only one animation per unique key is added per object.
*/
- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key;
/**
@abstract Remove all animations attached to the receiver.
*/
- (void)pop_removeAllAnimations;
/**
@abstract Remove any animation attached to the receiver for 'key'.
@param key The key used to identify the animation.
*/
- (void)pop_removeAnimationForKey:(NSString *)key;
/**
@abstract Returns an array containing the keys of all animations currently attached to the receiver.
The order of keys reflects the order in which animations will be applied.
*/
- (NSArray *)pop_animationKeys;
/**
@abstract Returns any animation attached to the receiver.
@param key The key used to identify the animation.
@returns The animation currently attached, or nil if no such animation exists.
*/
- (id)pop_animationForKey:(NSString *)key;
@end
/**
* This implementation of NSCopying does not do any copying of animation's state, but only configuration.
* i.e. you cannot copy an animation and expect to apply it to a view and have the copied animation pick up where the original left off.
* Two common uses of copying animations:
* * you need to apply the same animation to multiple different views.
* * you need to absolutely ensure that the the caller of your function cannot mutate the animation once it's been passed in.
*/
@interface POPAnimation (NSCopying) <NSCopying>
@end
@@ -0,0 +1,303 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationExtras.h"
#import "POPAnimationInternal.h"
#import <objc/runtime.h>
#import "POPAction.h"
#import "POPAnimationRuntime.h"
#import "POPAnimationTracerInternal.h"
#import "POPAnimatorPrivate.h"
using namespace POP;
#pragma mark - POPAnimation
@implementation POPAnimation
@synthesize solver = _solver;
@synthesize currentValue = _currentValue;
@synthesize progressMarkers = _progressMarkers;
#pragma mark - Lifecycle
- (id)init
{
[NSException raise:NSStringFromClass([self class]) format:@"Attempting to instantiate an abstract class. Use a concrete subclass instead."];
return nil;
}
- (id)_init
{
self = [super init];
if (nil != self) {
[self _initState];
}
return self;
}
- (void)_initState
{
_state = new POPAnimationState(self);
}
- (void)dealloc
{
if (_state) {
delete _state;
_state = NULL;
};
}
#pragma mark - Properties
- (id)delegate
{
return _state->delegate;
}
- (void)setDelegate:(id)delegate
{
_state->setDelegate(delegate);
}
- (BOOL)isPaused
{
return _state->paused;
}
- (void)setPaused:(BOOL)paused
{
_state->setPaused(paused ? true : false);
}
- (NSInteger)repeatCount
{
if (_state->autoreverses) {
return _state->repeatCount / 2;
} else {
return _state->repeatCount;
}
}
- (void)setRepeatCount:(NSInteger)repeatCount
{
if (repeatCount > 0) {
if (repeatCount > NSIntegerMax / 2) {
repeatCount = NSIntegerMax / 2;
}
if (_state->autoreverses) {
_state->repeatCount = (repeatCount * 2);
} else {
_state->repeatCount = repeatCount;
}
}
}
- (BOOL)autoreverses
{
return _state->autoreverses;
}
- (void)setAutoreverses:(BOOL)autoreverses
{
_state->autoreverses = autoreverses;
if (autoreverses) {
if (_state->repeatCount == 0) {
[self setRepeatCount:1];
}
}
}
FB_PROPERTY_GET(POPAnimationState, type, POPAnimationType);
DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, animationDidStartBlock, setAnimationDidStartBlock:, POPAnimationDidStartBlock);
DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, animationDidReachToValueBlock, setAnimationDidReachToValueBlock:, POPAnimationDidReachToValueBlock);
DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, completionBlock, setCompletionBlock:, POPAnimationCompletionBlock);
DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, animationDidApplyBlock, setAnimationDidApplyBlock:, POPAnimationDidApplyBlock);
DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, name, setName:, NSString*);
DEFINE_RW_PROPERTY(POPAnimationState, beginTime, setBeginTime:, CFTimeInterval);
DEFINE_RW_FLAG(POPAnimationState, removedOnCompletion, removedOnCompletion, setRemovedOnCompletion:);
DEFINE_RW_FLAG(POPAnimationState, repeatForever, repeatForever, setRepeatForever:);
- (id)valueForUndefinedKey:(NSString *)key
{
return _state->dict[key];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if (!value) {
[_state->dict removeObjectForKey:key];
} else {
if (!_state->dict)
_state->dict = [[NSMutableDictionary alloc] init];
_state->dict[key] = value;
}
}
- (POPAnimationTracer *)tracer
{
if (!_state->tracer) {
_state->tracer = [[POPAnimationTracer alloc] initWithAnimation:self];
}
return _state->tracer;
}
- (NSString *)description
{
NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
[self _appendDescription:s debug:NO];
[s appendString:@">"];
return s;
}
- (NSString *)debugDescription
{
NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
[self _appendDescription:s debug:YES];
[s appendString:@">"];
return s;
}
#pragma mark - Utility
POPAnimationState *POPAnimationGetState(POPAnimation *a)
{
return a->_state;
}
- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime
{
return YES;
}
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
if (_state->name)
[s appendFormat:@"; name = %@", _state->name];
if (!self.removedOnCompletion)
[s appendFormat:@"; removedOnCompletion = %@", POPStringFromBOOL(self.removedOnCompletion)];
if (debug) {
if (_state->active)
[s appendFormat:@"; active = %@", POPStringFromBOOL(_state->active)];
if (_state->paused)
[s appendFormat:@"; paused = %@", POPStringFromBOOL(_state->paused)];
}
if (_state->beginTime) {
[s appendFormat:@"; beginTime = %f", _state->beginTime];
}
for (NSString *key in _state->dict) {
[s appendFormat:@"; %@ = %@", key, _state->dict[key]];
}
}
@end
#pragma mark - POPPropertyAnimation
#pragma mark - POPBasicAnimation
#pragma mark - POPDecayAnimation
@implementation NSObject (POP)
- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key
{
[[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key];
}
- (void)pop_removeAllAnimations
{
[[POPAnimator sharedAnimator] removeAllAnimationsForObject:self];
}
- (void)pop_removeAnimationForKey:(NSString *)key
{
[[POPAnimator sharedAnimator] removeAnimationForObject:self key:key];
}
- (NSArray *)pop_animationKeys
{
return [[POPAnimator sharedAnimator] animationKeysForObject:self];
}
- (id)pop_animationForKey:(NSString *)key
{
return [[POPAnimator sharedAnimator] animationForObject:self key:key];
}
@end
@implementation NSProxy (POP)
- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key
{
[[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key];
}
- (void)pop_removeAllAnimations
{
[[POPAnimator sharedAnimator] removeAllAnimationsForObject:self];
}
- (void)pop_removeAnimationForKey:(NSString *)key
{
[[POPAnimator sharedAnimator] removeAnimationForObject:self key:key];
}
- (NSArray *)pop_animationKeys
{
return [[POPAnimator sharedAnimator] animationKeysForObject:self];
}
- (id)pop_animationForKey:(NSString *)key
{
return [[POPAnimator sharedAnimator] animationForObject:self key:key];
}
@end
@implementation POPAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone
{
/*
* Must use [self class] instead of POPAnimation so that subclasses can call this via super.
* Even though POPAnimation and POPPropertyAnimation throw exceptions on init,
* it's safe to call it since you can only copy objects that have been successfully created.
*/
POPAnimation *copy = [[[self class] allocWithZone:zone] init];
if (copy) {
copy.name = self.name;
copy.beginTime = self.beginTime;
copy.delegate = self.delegate;
copy.animationDidStartBlock = self.animationDidStartBlock;
copy.animationDidReachToValueBlock = self.animationDidReachToValueBlock;
copy.completionBlock = self.completionBlock;
copy.animationDidApplyBlock = self.animationDidApplyBlock;
copy.removedOnCompletion = self.removedOnCompletion;
copy.autoreverses = self.autoreverses;
copy.repeatCount = self.repeatCount;
copy.repeatForever = self.repeatForever;
}
return copy;
}
@end
@@ -0,0 +1,69 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
/**
@abstract Enumeraton of animation event types.
*/
typedef NS_ENUM(NSUInteger, POPAnimationEventType) {
kPOPAnimationEventPropertyRead = 0,
kPOPAnimationEventPropertyWrite,
kPOPAnimationEventToValueUpdate,
kPOPAnimationEventFromValueUpdate,
kPOPAnimationEventVelocityUpdate,
kPOPAnimationEventBouncinessUpdate,
kPOPAnimationEventSpeedUpdate,
kPOPAnimationEventFrictionUpdate,
kPOPAnimationEventMassUpdate,
kPOPAnimationEventTensionUpdate,
kPOPAnimationEventDidStart,
kPOPAnimationEventDidStop,
kPOPAnimationEventDidReachToValue,
kPOPAnimationEventAutoreversed
};
/**
@abstract The base animation event class.
*/
@interface POPAnimationEvent : NSObject
/**
@abstract The event type. See {@ref POPAnimationEventType} for possible values.
*/
@property (readonly, nonatomic, assign) POPAnimationEventType type;
/**
@abstract The time of event.
*/
@property (readonly, nonatomic, assign) CFTimeInterval time;
/**
@abstract Optional string describing the animation at time of event.
*/
@property (readonly, nonatomic, copy) NSString *animationDescription;
@end
/**
@abstract An animation event subclass for recording value and velocity.
*/
@interface POPAnimationValueEvent : POPAnimationEvent
/**
@abstract The value recorded.
*/
@property (readonly, nonatomic, strong) id value;
/**
@abstract The velocity recorded, if any.
*/
@property (readonly, nonatomic, strong) id velocity;
@end
@@ -0,0 +1,108 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationEvent.h"
#import "POPAnimationEventInternal.h"
static NSString *stringFromType(POPAnimationEventType aType)
{
switch (aType) {
case kPOPAnimationEventPropertyRead:
return @"read";
case kPOPAnimationEventPropertyWrite:
return @"write";
case kPOPAnimationEventToValueUpdate:
return @"toValue";
case kPOPAnimationEventFromValueUpdate:
return @"fromValue";
case kPOPAnimationEventVelocityUpdate:
return @"velocity";
case kPOPAnimationEventSpeedUpdate:
return @"speed";
case kPOPAnimationEventBouncinessUpdate:
return @"bounciness";
case kPOPAnimationEventFrictionUpdate:
return @"friction";
case kPOPAnimationEventMassUpdate:
return @"mass";
case kPOPAnimationEventTensionUpdate:
return @"tension";
case kPOPAnimationEventDidStart:
return @"didStart";
case kPOPAnimationEventDidStop:
return @"didStop";
case kPOPAnimationEventDidReachToValue:
return @"didReachToValue";
case kPOPAnimationEventAutoreversed:
return @"autoreversed";
default:
return nil;
}
}
@implementation POPAnimationEvent
@synthesize type = _type;
@synthesize time = _time;
@synthesize animationDescription = _animationDescription;
- (instancetype)initWithType:(POPAnimationEventType)aType time:(CFTimeInterval)aTime
{
self = [super init];
if (nil != self) {
_type = aType;
_time = aTime;
}
return self;
}
- (NSString *)description
{
NSMutableString *s = [NSMutableString stringWithFormat:@"<POPAnimationEvent:%f; type = %@", _time, stringFromType(_type)];
[self _appendDescription:s];
[s appendString:@">"];
return s;
}
// subclass override
- (void)_appendDescription:(NSMutableString *)s
{
if (0 != _animationDescription.length) {
[s appendFormat:@"; animation = %@", _animationDescription];
}
}
@end
@implementation POPAnimationValueEvent
@synthesize value = _value;
@synthesize velocity = _velocity;
- (instancetype)initWithType:(POPAnimationEventType)aType time:(CFTimeInterval)aTime value:(id)aValue
{
self = [self initWithType:aType time:aTime];
if (nil != self) {
_value = aValue;
}
return self;
}
- (void)_appendDescription:(NSMutableString *)s
{
[super _appendDescription:s];
if (nil != _value) {
[s appendFormat:@"; value = %@", _value];
}
if (nil != _velocity) {
[s appendFormat:@"; velocity = %@", _velocity];
}
}
@end
@@ -0,0 +1,41 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "POPAnimationEvent.h"
@interface POPAnimationEvent ()
/**
@abstract Default initializer.
*/
- (instancetype)initWithType:(POPAnimationEventType)type time:(CFTimeInterval)time;
/**
@abstract Readwrite redefinition of public property.
*/
@property (readwrite, nonatomic, copy) NSString *animationDescription;
@end
@interface POPAnimationValueEvent ()
/**
@abstract Default initializer.
*/
- (instancetype)initWithType:(POPAnimationEventType)type time:(CFTimeInterval)time value:(id)value;
/**
@abstract Readwrite redefinition of public property.
*/
@property (readwrite, nonatomic, strong) id velocity;
@end
@@ -0,0 +1,43 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <QuartzCore/CAAnimation.h>
#import "POPDefines.h"
#import "POPSpringAnimation.h"
/**
@abstract The current drag coefficient.
@discussion A value greater than 1.0 indicates Simulator slow-motion animations are enabled. Defaults to 1.0.
*/
extern CGFloat POPAnimationDragCoefficient(void);
@interface CAAnimation (POPAnimationExtras)
/**
@abstract Apply the current drag coefficient to animation speed.
@discussion Convenience utility to respect Simulator slow-motion animation settings.
*/
- (void)pop_applyDragCoefficient;
@end
@interface POPSpringAnimation (POPAnimationExtras)
/**
@abstract Converts from spring bounciness and speed to tension, friction and mass dynamics values.
*/
+ (void)convertBounciness:(CGFloat)bounciness speed:(CGFloat)speed toTension:(CGFloat *)outTension friction:(CGFloat *)outFriction mass:(CGFloat *)outMass;
/**
@abstract Converts from dynamics tension, friction and mass to spring bounciness and speed values.
*/
+ (void)convertTension:(CGFloat)tension friction:(CGFloat)friction toBounciness:(CGFloat *)outBounciness speed:(CGFloat *)outSpeed;
@end
@@ -0,0 +1,117 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationExtras.h"
#import "POPAnimationPrivate.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#if TARGET_IPHONE_SIMULATOR
UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coefficient, use judiciously
#endif
#import "POPMath.h"
CGFloat POPAnimationDragCoefficient()
{
#if TARGET_IPHONE_SIMULATOR
return UIAnimationDragCoefficient();
#else
return 1.0;
#endif
}
@implementation CAAnimation (POPAnimationExtras)
- (void)pop_applyDragCoefficient
{
CGFloat k = POPAnimationDragCoefficient();
if (k != 0 && k != 1)
self.speed = 1 / k;
}
@end
@implementation POPSpringAnimation (POPAnimationExtras)
static const CGFloat POPBouncy3NormalizationRange = 20.0;
static const CGFloat POPBouncy3NormalizationScale = 1.7;
static const CGFloat POPBouncy3BouncinessNormalizedMin = 0.0;
static const CGFloat POPBouncy3BouncinessNormalizedMax = 0.8;
static const CGFloat POPBouncy3SpeedNormalizedMin = 0.5;
static const CGFloat POPBouncy3SpeedNormalizedMax = 200;
static const CGFloat POPBouncy3FrictionInterpolationMax = 0.01;
+ (void)convertBounciness:(CGFloat)bounciness speed:(CGFloat)speed toTension:(CGFloat *)outTension friction:(CGFloat *)outFriction mass:(CGFloat *)outMass
{
double b = POPNormalize(bounciness / POPBouncy3NormalizationScale, 0, POPBouncy3NormalizationRange);
b = POPProjectNormal(b, POPBouncy3BouncinessNormalizedMin, POPBouncy3BouncinessNormalizedMax);
double s = POPNormalize(speed / POPBouncy3NormalizationScale, 0, POPBouncy3NormalizationRange);
CGFloat tension = POPProjectNormal(s, POPBouncy3SpeedNormalizedMin, POPBouncy3SpeedNormalizedMax);
CGFloat friction = POPQuadraticOutInterpolation(b, POPBouncy3NoBounce(tension), POPBouncy3FrictionInterpolationMax);
tension = POP_ANIMATION_TENSION_FOR_QC_TENSION(tension);
friction = POP_ANIMATION_FRICTION_FOR_QC_FRICTION(friction);
if (outTension) {
*outTension = tension;
}
if (outFriction) {
*outFriction = friction;
}
if (outMass) {
*outMass = 1.0;
}
}
+ (void)convertTension:(CGFloat)tension friction:(CGFloat)friction toBounciness:(CGFloat *)outBounciness speed:(CGFloat *)outSpeed
{
// Convert to QC values, in which our calculations are done.
CGFloat qcFriction = QC_FRICTION_FOR_POP_ANIMATION_FRICTION(friction);
CGFloat qcTension = QC_TENSION_FOR_POP_ANIMATION_TENSION(tension);
// Friction is a function of bounciness and tension, according to the following:
// friction = POPQuadraticOutInterpolation(b, POPBouncy3NoBounce(tension), POPBouncy3FrictionInterpolationMax);
// Solve for bounciness, given a tension and friction.
CGFloat nobounceTension = POPBouncy3NoBounce(qcTension);
CGFloat bounciness1, bounciness2;
POPQuadraticSolve((nobounceTension - POPBouncy3FrictionInterpolationMax), // a
2 * (POPBouncy3FrictionInterpolationMax - nobounceTension), // b
(nobounceTension - qcFriction), // c
bounciness1, // x1
bounciness2); // x2
// Choose the quadratic solution within the normalized bounciness range
CGFloat projectedNormalizedBounciness = (bounciness2 < POPBouncy3BouncinessNormalizedMax) ? bounciness2 : bounciness1;
CGFloat projectedNormalizedSpeed = qcTension;
// Reverse projection + normalization
CGFloat bounciness = ((POPBouncy3NormalizationRange * POPBouncy3NormalizationScale) / (POPBouncy3BouncinessNormalizedMax - POPBouncy3BouncinessNormalizedMin)) * (projectedNormalizedBounciness - POPBouncy3BouncinessNormalizedMin);
CGFloat speed = ((POPBouncy3NormalizationRange * POPBouncy3NormalizationScale) / (POPBouncy3SpeedNormalizedMax - POPBouncy3SpeedNormalizedMin)) * (projectedNormalizedSpeed - POPBouncy3SpeedNormalizedMin);
// Write back results
if (outBounciness) {
*outBounciness = bounciness;
}
if (outSpeed) {
*outSpeed = speed;
}
}
@end
@@ -0,0 +1,506 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimation.h"
#import <QuartzCore/CAMediaTimingFunction.h>
#import "POPAction.h"
#import "POPAnimationRuntime.h"
#import "POPAnimationTracerInternal.h"
#import "POPMath.h"
#import "POPSpringSolver.h"
using namespace POP;
/**
Enumeration of supported animation types.
*/
enum POPAnimationType
{
kPOPAnimationSpring,
kPOPAnimationDecay,
kPOPAnimationBasic,
kPOPAnimationCustom,
};
typedef struct
{
CGFloat progress;
bool reached;
} POPProgressMarker;
typedef void (^POPAnimationDidStartBlock)(POPAnimation *anim);
typedef void (^POPAnimationDidReachToValueBlock)(POPAnimation *anim);
typedef void (^POPAnimationCompletionBlock)(POPAnimation *anim, BOOL finished);
typedef void (^POPAnimationDidApplyBlock)(POPAnimation *anim);
@interface POPAnimation()
- (instancetype)_init;
@property (assign, nonatomic) SpringSolver4d *solver;
@property (readonly, nonatomic) POPAnimationType type;
/**
The current animation value, updated while animation is progressing.
*/
@property (copy, nonatomic, readonly) id currentValue;
/**
An array of optional progress markers. For each marker specified, the animation delegate will be informed when progress meets or exceeds the value specified. Specifying values outside of the [0, 1] range will give undefined results.
*/
@property (copy, nonatomic) NSArray *progressMarkers;
/**
Return YES to indicate animation should continue animating.
*/
- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime;
/**
Subclass override point to append animation description.
*/
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug;
@end
NS_INLINE NSString *describe(VectorConstRef vec)
{
return NULL == vec ? @"null" : vec->toString();
}
NS_INLINE Vector4r vector4(VectorConstRef vec)
{
return NULL == vec ? Vector4r::Zero() : vec->vector4r();
}
NS_INLINE Vector4d vector4d(VectorConstRef vec)
{
if (NULL == vec) {
return Vector4d::Zero();
} else {
return vec->vector4r().cast<double>();
}
}
NS_INLINE bool vec_equal(VectorConstRef v1, VectorConstRef v2)
{
if (v1 == v2) {
return true;
}
if (!v1 || !v2) {
return false;
}
return *v1 == *v2;
}
NS_INLINE CGFloat * vec_data(VectorRef vec)
{
return NULL == vec ? NULL : vec->data();
}
template<class T>
struct ComputeProgressFunctor {
CGFloat operator()(const T &value, const T &start, const T &end) const {
return 0;
}
};
template<>
struct ComputeProgressFunctor<Vector4r> {
CGFloat operator()(const Vector4r &value, const Vector4r &start, const Vector4r &end) const {
CGFloat s = (value - start).squaredNorm(); // distance from start
CGFloat e = (value - end).squaredNorm(); // distance from end
CGFloat d = (end - start).squaredNorm(); // distance from start to end
if (0 == d) {
return 1;
} else if (s > e) {
// s -------- p ---- e OR s ------- e ---- p
return sqrtr(s/d);
} else {
// s --- p --------- e OR p ---- s ------- e
return 1 - sqrtr(e/d);
}
}
};
struct _POPAnimationState;
struct _POPDecayAnimationState;
struct _POPPropertyAnimationState;
extern _POPAnimationState *POPAnimationGetState(POPAnimation *a);
#define FB_FLAG_GET(stype, flag, getter) \
- (BOOL)getter { \
return ((stype *)_state)->flag; \
}
#define FB_FLAG_SET(stype, flag, mutator) \
- (void)mutator (BOOL)value { \
if (value == ((stype *)_state)->flag) \
return; \
((stype *)_state)->flag = value; \
}
#define DEFINE_RW_FLAG(stype, flag, getter, mutator) \
FB_FLAG_GET (stype, flag, getter) \
FB_FLAG_SET (stype, flag, mutator)
#define FB_PROPERTY_GET(stype, property, ctype) \
- (ctype)property { \
return ((stype *)_state)->property; \
}
#define FB_PROPERTY_SET(stype, property, mutator, ctype, ...) \
- (void)mutator (ctype)value { \
if (value == ((stype *)_state)->property) \
return; \
((stype *)_state)->property = value; \
__VA_ARGS__ \
}
#define FB_PROPERTY_SET_OBJ_COPY(stype, property, mutator, ctype, ...) \
- (void)mutator (ctype)value { \
if (value == ((stype *)_state)->property) \
return; \
((stype *)_state)->property = [value copy]; \
__VA_ARGS__ \
}
#define DEFINE_RW_PROPERTY(stype, flag, mutator, ctype, ...) \
FB_PROPERTY_GET (stype, flag, ctype) \
FB_PROPERTY_SET (stype, flag, mutator, ctype, __VA_ARGS__)
#define DEFINE_RW_PROPERTY_OBJ(stype, flag, mutator, ctype, ...) \
FB_PROPERTY_GET (stype, flag, ctype) \
FB_PROPERTY_SET (stype, flag, mutator, ctype, __VA_ARGS__)
#define DEFINE_RW_PROPERTY_OBJ_COPY(stype, flag, mutator, ctype, ...) \
FB_PROPERTY_GET (stype, flag, ctype) \
FB_PROPERTY_SET_OBJ_COPY (stype, flag, mutator, ctype, __VA_ARGS__)
/**
Internal delegate definition.
*/
@interface NSObject (POPAnimationDelegateInternal)
- (void)pop_animation:(POPAnimation *)anim didReachProgress:(CGFloat)progress;
@end
struct _POPAnimationState
{
id __unsafe_unretained self;
POPAnimationType type;
NSString *name;
NSUInteger ID;
CFTimeInterval beginTime;
CFTimeInterval startTime;
CFTimeInterval lastTime;
id __weak delegate;
POPAnimationDidStartBlock animationDidStartBlock;
POPAnimationDidReachToValueBlock animationDidReachToValueBlock;
POPAnimationCompletionBlock completionBlock;
POPAnimationDidApplyBlock animationDidApplyBlock;
NSMutableDictionary *dict;
POPAnimationTracer *tracer;
CGFloat progress;
NSInteger repeatCount;
bool active:1;
bool paused:1;
bool removedOnCompletion:1;
bool delegateDidStart:1;
bool delegateDidStop:1;
bool delegateDidProgress:1;
bool delegateDidApply:1;
bool delegateDidReachToValue:1;
bool additive:1;
bool didReachToValue:1;
bool tracing:1; // corresponds to tracer started
bool userSpecifiedDynamics:1;
bool autoreverses:1;
bool repeatForever:1;
bool customFinished:1;
_POPAnimationState(id __unsafe_unretained anim) :
self(anim),
type((POPAnimationType)0),
name(nil),
ID(0),
beginTime(0),
startTime(0),
lastTime(0),
delegate(nil),
animationDidStartBlock(nil),
animationDidReachToValueBlock(nil),
completionBlock(nil),
animationDidApplyBlock(nil),
dict(nil),
tracer(nil),
progress(0),
repeatCount(0),
active(false),
paused(true),
removedOnCompletion(true),
delegateDidStart(false),
delegateDidStop(false),
delegateDidProgress(false),
delegateDidApply(false),
delegateDidReachToValue(false),
additive(false),
didReachToValue(false),
tracing(false),
userSpecifiedDynamics(false),
autoreverses(false),
repeatForever(false),
customFinished(false) {}
virtual ~_POPAnimationState()
{
name = nil;
dict = nil;
tracer = nil;
animationDidStartBlock = NULL;
animationDidReachToValueBlock = NULL;
completionBlock = NULL;
animationDidApplyBlock = NULL;
}
bool isCustom() {
return kPOPAnimationCustom == type;
}
bool isStarted() {
return 0 != startTime;
}
id getDelegate() {
return delegate;
}
void setDelegate(id d) {
if (d != delegate) {
delegate = d;
delegateDidStart = [d respondsToSelector:@selector(pop_animationDidStart:)];
delegateDidStop = [d respondsToSelector:@selector(pop_animationDidStop:finished:)];
delegateDidProgress = [d respondsToSelector:@selector(pop_animation:didReachProgress:)];
delegateDidApply = [d respondsToSelector:@selector(pop_animationDidApply:)];
delegateDidReachToValue = [d respondsToSelector:@selector(pop_animationDidReachToValue:)];
}
}
bool getPaused() {
return paused;
}
void setPaused(bool f) {
if (f != paused) {
paused = f;
if (!paused) {
reset(false);
}
}
}
CGFloat getProgress() {
return progress;
}
/* returns true if started */
bool startIfNeeded(id obj, CFTimeInterval time, CFTimeInterval offset)
{
bool started = false;
// detect start based on time
if (0 == startTime && time >= beginTime + offset) {
// activate & unpause
active = true;
setPaused(false);
// note start time
startTime = lastTime = time;
started = true;
}
// ensure values for running animation
bool running = active && !paused;
if (running) {
willRun(started, obj);
}
// handle start
if (started) {
handleDidStart();
}
return started;
}
void stop(bool removing, bool done) {
if (active)
{
// delegate progress one last time
if (done) {
delegateProgress();
}
if (removing) {
active = false;
}
handleDidStop(done);
} else {
// stopped before even started
// delegate start and stop regardless; matches CA behavior
if (!isStarted()) {
handleDidStart();
handleDidStop(false);
}
}
setPaused(true);
}
virtual void handleDidStart()
{
if (delegateDidStart) {
ActionEnabler enabler;
[delegate pop_animationDidStart:self];
}
POPAnimationDidStartBlock block = animationDidStartBlock;
if (block != NULL) {
ActionEnabler enabler;
block(self);
}
if (tracing) {
[tracer didStart];
}
}
void handleDidStop(BOOL done)
{
if (delegateDidStop) {
ActionEnabler enabler;
[delegate pop_animationDidStop:self finished:done];
}
// add another strong reference to completion block before callout
POPAnimationCompletionBlock block = completionBlock;
if (block != NULL) {
ActionEnabler enabler;
block(self, done);
}
if (tracing) {
[tracer didStop:done];
}
}
/* virtual functions */
virtual bool isDone() {
if (isCustom()) {
return customFinished;
}
return false;
}
bool advanceTime(CFTimeInterval time, id obj) {
bool advanced = false;
bool computedProgress = false;
CFTimeInterval dt = time - lastTime;
switch (type) {
case kPOPAnimationSpring:
advanced = advance(time, dt, obj);
break;
case kPOPAnimationDecay:
advanced = advance(time, dt, obj);
break;
case kPOPAnimationBasic: {
advanced = advance(time, dt, obj);
computedProgress = true;
break;
}
case kPOPAnimationCustom: {
customFinished = [self _advance:obj currentTime:time elapsedTime:dt] ? false : true;
advanced = true;
break;
}
default:
break;
}
if (advanced) {
// estimate progress
if (!computedProgress) {
computeProgress();
}
// delegate progress
delegateProgress();
// update time
lastTime = time;
}
return advanced;
}
virtual void willRun(bool started, id obj) {}
virtual bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { return false; }
virtual void computeProgress() {}
virtual void delegateProgress() {}
virtual void delegateApply() {
if (delegateDidApply) {
ActionEnabler enabler;
[delegate pop_animationDidApply:self];
}
POPAnimationDidApplyBlock block = animationDidApplyBlock;
if (block != NULL) {
ActionEnabler enabler;
block(self);
}
}
virtual void reset(bool all) {
startTime = 0;
lastTime = 0;
}
};
typedef struct _POPAnimationState POPAnimationState;
@interface POPAnimation ()
{
@protected
struct _POPAnimationState *_state;
}
@end
// NSProxy extensions, for testing purposes
@interface NSProxy (POP)
- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key;
- (void)pop_removeAllAnimations;
- (void)pop_removeAnimationForKey:(NSString *)key;
- (NSArray *)pop_animationKeys;
- (POPAnimation *)pop_animationForKey:(NSString *)key;
@end
@@ -0,0 +1,16 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimation.h"
#define POP_ANIMATION_FRICTION_FOR_QC_FRICTION(qcFriction) (25.0 + (((qcFriction - 8.0) / 2.0) * (25.0 - 19.0)))
#define POP_ANIMATION_TENSION_FOR_QC_TENSION(qcTension) (194.0 + (((qcTension - 30.0) / 50.0) * (375.0 - 194.0)))
#define QC_FRICTION_FOR_POP_ANIMATION_FRICTION(fbFriction) (8.0 + 2.0 * ((fbFriction - 25.0)/(25.0 - 19.0)))
#define QC_TENSION_FOR_POP_ANIMATION_TENSION(fbTension) (30.0 + 50.0 * ((fbTension - 194.0)/(375.0 - 194.0)))
@@ -0,0 +1,99 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/runtime.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import "POPAnimatablePropertyTypes.h"
#import "POPVector.h"
enum POPValueType
{
kPOPValueUnknown = 0,
kPOPValueInteger,
kPOPValueFloat,
kPOPValuePoint,
kPOPValueSize,
kPOPValueRect,
kPOPValueEdgeInsets,
kPOPValueAffineTransform,
kPOPValueTransform,
kPOPValueRange,
kPOPValueColor,
kPOPValueSCNVector3,
kPOPValueSCNVector4,
};
using namespace POP;
/**
Returns value type based on objc type description, given list of supported value types and length.
*/
extern POPValueType POPSelectValueType(const char *objctype, const POPValueType *types, size_t length);
/**
Returns value type based on objc object, given a list of supported value types and length.
*/
extern POPValueType POPSelectValueType(id obj, const POPValueType *types, size_t length);
/**
Array of all value types.
*/
extern const POPValueType kPOPAnimatableAllTypes[12];
/**
Array of all value types supported for animation.
*/
extern const POPValueType kPOPAnimatableSupportTypes[10];
/**
Returns a string description of a value type.
*/
extern NSString *POPValueTypeToString(POPValueType t);
/**
Returns a mutable dictionary of weak pointer keys to weak pointer values.
*/
extern CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToWeakPointer(NSUInteger capacity) CF_RETURNS_RETAINED;
/**
Returns a mutable dictionary of weak pointer keys to weak pointer values.
*/
extern CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToStrongObject(NSUInteger capacity) CF_RETURNS_RETAINED;
/**
Box a vector.
*/
extern id POPBox(VectorConstRef vec, POPValueType type, bool force = false);
/**
Unbox a vector.
*/
extern VectorRef POPUnbox(id value, POPValueType &type, NSUInteger &count, bool validate);
/**
Read object value and return a Vector4r.
*/
NS_INLINE Vector4r read_values(POPAnimatablePropertyReadBlock read, id obj, size_t count)
{
Vector4r vec = Vector4r::Zero();
if (0 == count)
return vec;
read(obj, vec.data());
return vec;
}
NS_INLINE NSString *POPStringFromBOOL(BOOL value)
{
return value ? @"YES" : @"NO";
}
@@ -0,0 +1,329 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationRuntime.h"
#import <objc/objc.h>
#import <QuartzCore/QuartzCore.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import "POPCGUtils.h"
#import "POPDefines.h"
#import "POPGeometry.h"
#import "POPVector.h"
static Boolean pointerEqual(const void *ptr1, const void *ptr2) {
return ptr1 == ptr2;
}
static CFHashCode pointerHash(const void *ptr) {
return (CFHashCode)(ptr);
}
CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToWeakPointer(NSUInteger capacity)
{
CFDictionaryKeyCallBacks kcb = kCFTypeDictionaryKeyCallBacks;
// weak, pointer keys
kcb.retain = NULL;
kcb.release = NULL;
kcb.equal = pointerEqual;
kcb.hash = pointerHash;
CFDictionaryValueCallBacks vcb = kCFTypeDictionaryValueCallBacks;
// weak, pointer values
vcb.retain = NULL;
vcb.release = NULL;
vcb.equal = pointerEqual;
return CFDictionaryCreateMutable(NULL, capacity, &kcb, &vcb);
}
CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToStrongObject(NSUInteger capacity)
{
CFDictionaryKeyCallBacks kcb = kCFTypeDictionaryKeyCallBacks;
// weak, pointer keys
kcb.retain = NULL;
kcb.release = NULL;
kcb.equal = pointerEqual;
kcb.hash = pointerHash;
// strong, object values
CFDictionaryValueCallBacks vcb = kCFTypeDictionaryValueCallBacks;
return CFDictionaryCreateMutable(NULL, capacity, &kcb, &vcb);
}
static bool FBCompareTypeEncoding(const char *objctype, POPValueType type)
{
switch (type)
{
case kPOPValueFloat:
return (strcmp(objctype, @encode(float)) == 0
|| strcmp(objctype, @encode(double)) == 0
);
case kPOPValuePoint:
return (strcmp(objctype, @encode(CGPoint)) == 0
#if !TARGET_OS_IPHONE
|| strcmp(objctype, @encode(NSPoint)) == 0
#endif
);
case kPOPValueSize:
return (strcmp(objctype, @encode(CGSize)) == 0
#if !TARGET_OS_IPHONE
|| strcmp(objctype, @encode(NSSize)) == 0
#endif
);
case kPOPValueRect:
return (strcmp(objctype, @encode(CGRect)) == 0
#if !TARGET_OS_IPHONE
|| strcmp(objctype, @encode(NSRect)) == 0
#endif
);
case kPOPValueEdgeInsets:
#if TARGET_OS_IPHONE
return strcmp(objctype, @encode(UIEdgeInsets)) == 0;
#else
return false;
#endif
case kPOPValueAffineTransform:
return strcmp(objctype, @encode(CGAffineTransform)) == 0;
case kPOPValueTransform:
return strcmp(objctype, @encode(CATransform3D)) == 0;
case kPOPValueRange:
return strcmp(objctype, @encode(CFRange)) == 0
|| strcmp(objctype, @encode (NSRange)) == 0;
case kPOPValueInteger:
return (strcmp(objctype, @encode(int)) == 0
|| strcmp(objctype, @encode(unsigned int)) == 0
|| strcmp(objctype, @encode(short)) == 0
|| strcmp(objctype, @encode(unsigned short)) == 0
|| strcmp(objctype, @encode(long)) == 0
|| strcmp(objctype, @encode(unsigned long)) == 0
|| strcmp(objctype, @encode(long long)) == 0
|| strcmp(objctype, @encode(unsigned long long)) == 0
);
case kPOPValueSCNVector3:
#if SCENEKIT_SDK_AVAILABLE
return strcmp(objctype, @encode(SCNVector3)) == 0;
#else
return false;
#endif
case kPOPValueSCNVector4:
#if SCENEKIT_SDK_AVAILABLE
return strcmp(objctype, @encode(SCNVector4)) == 0;
#else
return false;
#endif
default:
return false;
}
}
POPValueType POPSelectValueType(const char *objctype, const POPValueType *types, size_t length)
{
if (NULL != objctype) {
for (size_t idx = 0; idx < length; idx++) {
if (FBCompareTypeEncoding(objctype, types[idx]))
return types[idx];
}
}
return kPOPValueUnknown;
}
POPValueType POPSelectValueType(id obj, const POPValueType *types, size_t length)
{
if ([obj isKindOfClass:[NSValue class]]) {
return POPSelectValueType([obj objCType], types, length);
} else if (NULL != POPCGColorWithColor(obj)) {
return kPOPValueColor;
}
return kPOPValueUnknown;
}
const POPValueType kPOPAnimatableAllTypes[12] = {kPOPValueInteger, kPOPValueFloat, kPOPValuePoint, kPOPValueSize, kPOPValueRect, kPOPValueEdgeInsets, kPOPValueAffineTransform, kPOPValueTransform, kPOPValueRange, kPOPValueColor, kPOPValueSCNVector3, kPOPValueSCNVector4};
const POPValueType kPOPAnimatableSupportTypes[10] = {kPOPValueInteger, kPOPValueFloat, kPOPValuePoint, kPOPValueSize, kPOPValueRect, kPOPValueEdgeInsets, kPOPValueColor, kPOPValueSCNVector3, kPOPValueSCNVector4};
NSString *POPValueTypeToString(POPValueType t)
{
switch (t) {
case kPOPValueUnknown:
return @"unknown";
case kPOPValueInteger:
return @"int";
case kPOPValueFloat:
return @"CGFloat";
case kPOPValuePoint:
return @"CGPoint";
case kPOPValueSize:
return @"CGSize";
case kPOPValueRect:
return @"CGRect";
case kPOPValueEdgeInsets:
return @"UIEdgeInsets";
case kPOPValueAffineTransform:
return @"CGAffineTransform";
case kPOPValueTransform:
return @"CATransform3D";
case kPOPValueRange:
return @"CFRange";
case kPOPValueColor:
return @"CGColorRef";
case kPOPValueSCNVector3:
return @"SCNVector3";
case kPOPValueSCNVector4:
return @"SCNVector4";
default:
return nil;
}
}
id POPBox(VectorConstRef vec, POPValueType type, bool force)
{
if (NULL == vec)
return nil;
switch (type) {
case kPOPValueInteger:
case kPOPValueFloat:
return @(vec->data()[0]);
break;
case kPOPValuePoint:
return [NSValue valueWithCGPoint:vec->cg_point()];
break;
case kPOPValueSize:
return [NSValue valueWithCGSize:vec->cg_size()];
break;
case kPOPValueRect:
return [NSValue valueWithCGRect:vec->cg_rect()];
break;
#if TARGET_OS_IPHONE
case kPOPValueEdgeInsets:
return [NSValue valueWithUIEdgeInsets:vec->ui_edge_insets()];
break;
#endif
case kPOPValueColor: {
return (__bridge_transfer id)vec->cg_color();
break;
}
#if SCENEKIT_SDK_AVAILABLE
case kPOPValueSCNVector3: {
return [NSValue valueWithSCNVector3:vec->scn_vector3()];
break;
}
case kPOPValueSCNVector4: {
return [NSValue valueWithSCNVector4:vec->scn_vector4()];
break;
}
#endif
default:
return force ? [NSValue valueWithCGPoint:vec->cg_point()] : nil;
break;
}
}
static VectorRef vectorize(id value, POPValueType type)
{
Vector *vec = NULL;
switch (type) {
case kPOPValueInteger:
case kPOPValueFloat:
#if CGFLOAT_IS_DOUBLE
vec = Vector::new_cg_float([value doubleValue]);
#else
vec = Vector::new_cg_float([value floatValue]);
#endif
break;
case kPOPValuePoint:
vec = Vector::new_cg_point([value CGPointValue]);
break;
case kPOPValueSize:
vec = Vector::new_cg_size([value CGSizeValue]);
break;
case kPOPValueRect:
vec = Vector::new_cg_rect([value CGRectValue]);
break;
#if TARGET_OS_IPHONE
case kPOPValueEdgeInsets:
vec = Vector::new_ui_edge_insets([value UIEdgeInsetsValue]);
break;
#endif
case kPOPValueAffineTransform:
vec = Vector::new_cg_affine_transform([value CGAffineTransformValue]);
break;
case kPOPValueColor:
vec = Vector::new_cg_color(POPCGColorWithColor(value));
break;
#if SCENEKIT_SDK_AVAILABLE
case kPOPValueSCNVector3:
vec = Vector::new_scn_vector3([value SCNVector3Value]);
break;
case kPOPValueSCNVector4:
vec = Vector::new_scn_vector4([value SCNVector4Value]);
break;
#endif
default:
break;
}
return VectorRef(vec);
}
VectorRef POPUnbox(id value, POPValueType &animationType, NSUInteger &count, bool validate)
{
if (nil == value) {
count = 0;
return VectorRef(NULL);
}
// determine type of value
POPValueType valueType = POPSelectValueType(value, kPOPAnimatableSupportTypes, POP_ARRAY_COUNT(kPOPAnimatableSupportTypes));
// handle unknown types
if (kPOPValueUnknown == valueType) {
NSString *valueDesc = [[value class] description];
[NSException raise:@"Unsuported value" format:@"Animating %@ values is not supported", valueDesc];
}
// vectorize
VectorRef vec = vectorize(value, valueType);
if (kPOPValueUnknown == animationType || 0 == count) {
// update animation type based on value type
animationType = valueType;
if (NULL != vec) {
count = vec->size();
}
} else if (validate) {
// allow for mismatched types, so long as vector size matches
if (count != vec->size()) {
[NSException raise:@"Invalid value" format:@"%@ should be of type %@", value, POPValueTypeToString(animationType)];
}
}
return vec;
}
@@ -0,0 +1,60 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "POPAnimationEvent.h"
@class POPAnimation;
/**
@abstract Tracer of animation events to facilitate unit testing & debugging.
*/
@interface POPAnimationTracer : NSObject
/**
@abstract Start recording events.
*/
- (void)start;
/**
@abstract Stop recording events.
*/
- (void)stop;
/**
@abstract Resets any recoded events. Continues recording events if already started.
*/
- (void)reset;
/**
@abstract Property representing all recorded events.
@discussion Events are returned in order of occurrence.
*/
@property (nonatomic, assign, readonly) NSArray *allEvents;
/**
@abstract Property representing all recorded write events for convenience.
@discussion Events are returned in order of occurrence.
*/
@property (nonatomic, assign, readonly) NSArray *writeEvents;
/**
@abstract Queries for events of specified type.
@param type The type of event to return.
@returns An array of events of specified type in order of occurrence.
*/
- (NSArray *)eventsWithType:(POPAnimationEventType)type;
/**
@abstract Property indicating whether tracer should automatically log events and reset collection on animation completion.
*/
@property (nonatomic, assign) BOOL shouldLogAndResetOnCompletion;
@end
@@ -0,0 +1,192 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationTracer.h"
#import <QuartzCore/QuartzCore.h>
#import "POPAnimationEventInternal.h"
#import "POPAnimationInternal.h"
#import "POPSpringAnimation.h"
@implementation POPAnimationTracer
{
__weak POPAnimation *_animation;
POPAnimationState *_animationState;
NSMutableArray *_events;
BOOL _animationHasVelocity;
}
@synthesize shouldLogAndResetOnCompletion = _shouldLogAndResetOnCompletion;
static POPAnimationEvent *create_event(POPAnimationTracer *self, POPAnimationEventType type, id value = nil, bool recordAnimation = false)
{
bool useLocalTime = 0 != self->_animationState->startTime;
CFTimeInterval time = useLocalTime
? self->_animationState->lastTime - self->_animationState->startTime
: self->_animationState->lastTime;
POPAnimationEvent *event;
__strong POPAnimation* animation = self->_animation;
if (!value) {
event = [[POPAnimationEvent alloc] initWithType:type time:time];
} else {
event = [[POPAnimationValueEvent alloc] initWithType:type time:time value:value];
if (self->_animationHasVelocity) {
[(POPAnimationValueEvent *)event setVelocity:[(POPSpringAnimation *)animation velocity]];
}
}
if (recordAnimation) {
event.animationDescription = [animation description];
}
return event;
}
- (id)initWithAnimation:(POPAnimation *)anAnim
{
self = [super init];
if (nil != self) {
_animation = anAnim;
_animationState = POPAnimationGetState(anAnim);
_events = [[NSMutableArray alloc] initWithCapacity:50];
_animationHasVelocity = [anAnim respondsToSelector:@selector(velocity)];
}
return self;
}
- (void)readPropertyValue:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventPropertyRead, aValue);
[_events addObject:event];
}
- (void)writePropertyValue:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventPropertyWrite, aValue);
[_events addObject:event];
}
- (void)updateToValue:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventToValueUpdate, aValue);
[_events addObject:event];
}
- (void)updateFromValue:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventFromValueUpdate, aValue);
[_events addObject:event];
}
- (void)updateVelocity:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventVelocityUpdate, aValue);
[_events addObject:event];
}
- (void)updateSpeed:(float)aFloat
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventSpeedUpdate, @(aFloat));
[_events addObject:event];
}
- (void)updateBounciness:(float)aFloat
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventBouncinessUpdate, @(aFloat));
[_events addObject:event];
}
- (void)updateFriction:(float)aFloat
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventFrictionUpdate, @(aFloat));
[_events addObject:event];
}
- (void)updateMass:(float)aFloat
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventMassUpdate, @(aFloat));
[_events addObject:event];
}
- (void)updateTension:(float)aFloat
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventTensionUpdate, @(aFloat));
[_events addObject:event];
}
- (void)didStart
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidStart, nil, true);
[_events addObject:event];
}
- (void)didStop:(BOOL)finished
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidStop, @(finished), true);
[_events addObject:event];
if (_shouldLogAndResetOnCompletion) {
NSLog(@"events:%@", self.allEvents);
[self reset];
}
}
- (void)didReachToValue:(id)aValue
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidReachToValue, aValue);
[_events addObject:event];
}
- (void)autoreversed
{
POPAnimationEvent *event = create_event(self, kPOPAnimationEventAutoreversed);
[_events addObject:event];
}
- (void)start
{
POPAnimationState *s = POPAnimationGetState(_animation);
s->tracing = true;
}
- (void)stop
{
POPAnimationState *s = POPAnimationGetState(_animation);
s->tracing = false;
}
- (void)reset
{
[_events removeAllObjects];
}
- (NSArray *)allEvents
{
return [_events copy];
}
- (NSArray *)writeEvents
{
return [self eventsWithType:kPOPAnimationEventPropertyWrite];
}
- (NSArray *)eventsWithType:(POPAnimationEventType)aType
{
NSMutableArray *array = [NSMutableArray array];
for (POPAnimationEvent *event in _events) {
if (aType == event.type) {
[array addObject:event];
}
}
return array;
}
@end
@@ -0,0 +1,96 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "POPAnimationTracer.h"
@interface POPAnimationTracer (Internal)
/**
@abstract Designated initializer. Pass the animation being traced.
*/
- (instancetype)initWithAnimation:(POPAnimation *)anAnim;
/**
@abstract Records read value.
*/
- (void)readPropertyValue:(id)aValue;
/**
@abstract Records write value.
*/
- (void)writePropertyValue:(id)aValue;
/**
Records to value update.
*/
- (void)updateToValue:(id)aValue;
/**
@abstract Records from value update.
*/
- (void)updateFromValue:(id)aValue;
/**
@abstract Records from value update.
*/
- (void)updateVelocity:(id)aValue;
/**
@abstract Records bounciness update.
*/
- (void)updateBounciness:(float)aFloat;
/**
@abstract Records speed update.
*/
- (void)updateSpeed:(float)aFloat;
/**
@abstract Records friction update.
*/
- (void)updateFriction:(float)aFloat;
/**
@abstract Records mass update.
*/
- (void)updateMass:(float)aFloat;
/**
@abstract Records tension update.
*/
- (void)updateTension:(float)aFloat;
/**
@abstract Records did add.
*/
- (void)didAdd;
/**
@abstract Records did start.
*/
- (void)didStart;
/**
@abstract Records did stop.
*/
- (void)didStop:(BOOL)finished;
/**
@abstract Records did reach to value.
*/
- (void)didReachToValue:(id)aValue;
/**
@abstract Records when an autoreverse animation takes place.
*/
- (void)autoreversed;
@end
@@ -0,0 +1,59 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@protocol POPAnimatorDelegate;
/**
@abstract The animator class renders animations.
*/
@interface POPAnimator : NSObject
/**
@abstract The shared animator instance.
@discussion Consumers should generally use the shared instance in lieu of creating new instances.
*/
+ (instancetype)sharedAnimator;
#if !TARGET_OS_IPHONE
/**
@abstract Allows to select display to bind. Returns nil if failed to create the display link.
*/
- (instancetype)initWithDisplayID:(CGDirectDisplayID)displayID;
#endif
/**
@abstract The optional animator delegate.
*/
@property (weak, nonatomic) id<POPAnimatorDelegate> delegate;
/**
@abstract Retrieves the nominal refresh period of a display link. Returns zero if unavailable.
*/
@property (readonly, nonatomic) CFTimeInterval refreshPeriod;
@end
/**
@abstract The animator delegate.
*/
@protocol POPAnimatorDelegate <NSObject>
/**
@abstract Called on each frame before animation application.
*/
- (void)animatorWillAnimate:(POPAnimator *)animator;
/**
@abstract Called on each frame after animation application.
*/
- (void)animatorDidAnimate:(POPAnimator *)animator;
@end
@@ -0,0 +1,909 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimator.h"
#import "POPAnimatorPrivate.h"
#import <list>
#import <vector>
#if !TARGET_OS_IPHONE
#import <libkern/OSAtomic.h>
#endif
#import <objc/objc-auto.h>
#import <QuartzCore/QuartzCore.h>
#import "POPAnimation.h"
#import "POPAnimationExtras.h"
#import "POPBasicAnimationInternal.h"
#import "POPDecayAnimation.h"
using namespace std;
using namespace POP;
#define ENABLE_LOGGING_DEBUG 0
#define ENABLE_LOGGING_INFO 0
#if ENABLE_LOGGING_DEBUG
#define FBLogAnimDebug NSLog
#else
#define FBLogAnimDebug(...)
#endif
#if ENABLE_LOGGING_INFO
#define FBLogAnimInfo NSLog
#else
#define FBLogAnimInfo(...)
#endif
#if !TARGET_OS_IPHONE
static const uint64_t kDisplayTimerFrequency = 60ull; // Hz
#endif
class POPAnimatorItem
{
public:
id __weak object;
NSString *key;
POPAnimation *animation;
NSInteger refCount;
id __unsafe_unretained unretainedObject;
POPAnimatorItem(id o, NSString *k, POPAnimation *a) POP_NOTHROW
{
object = o;
key = [k copy];
animation = a;
refCount = 1;
unretainedObject = o;
}
~POPAnimatorItem()
{
}
bool operator==(const POPAnimatorItem& o) const {
return unretainedObject == o.unretainedObject && animation == o.animation && [key isEqualToString:o.key];
}
};
typedef std::shared_ptr<POPAnimatorItem> POPAnimatorItemRef;
typedef std::shared_ptr<const POPAnimatorItem> POPAnimatorItemConstRef;
typedef std::list<POPAnimatorItemRef> POPAnimatorItemList;
typedef POPAnimatorItemList::iterator POPAnimatorItemListIterator;
typedef POPAnimatorItemList::const_iterator POPAnimatorItemListConstIterator;
#if !TARGET_OS_IPHONE
static BOOL _disableBackgroundThread = YES;
static uint64_t _displayTimerFrequency = kDisplayTimerFrequency;
#endif
@interface POPAnimator ()
{
#if TARGET_OS_IPHONE
CADisplayLink *_displayLink;
#else
CVDisplayLinkRef _displayLink;
dispatch_source_t _displayTimer;
BOOL _displayTimerRunning;
int32_t _enqueuedRender;
#endif
POPAnimatorItemList _list;
CFMutableDictionaryRef _dict;
NSMutableArray *_observers;
POPAnimatorItemList _pendingList;
CFRunLoopObserverRef _pendingListObserver;
CFTimeInterval _slowMotionStartTime;
CFTimeInterval _slowMotionLastTime;
CFTimeInterval _slowMotionAccumulator;
CFTimeInterval _beginTime;
pthread_mutex_t _lock;
BOOL _disableDisplayLink;
}
@end
@implementation POPAnimator
@synthesize delegate = _delegate;
@synthesize disableDisplayLink = _disableDisplayLink;
@synthesize beginTime = _beginTime;
#if !TARGET_OS_IPHONE
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context)
{
if (_disableBackgroundThread) {
__unsafe_unretained POPAnimator *pa = (__bridge POPAnimator *)context;
int32_t* enqueuedRender = &pa->_enqueuedRender;
if (*enqueuedRender == 0) {
OSAtomicIncrement32(enqueuedRender);
dispatch_async(dispatch_get_main_queue(), ^{
[(__bridge POPAnimator*)context render];
OSAtomicDecrement32(enqueuedRender);
});
}
} else {
[(__bridge POPAnimator*)context render];
}
return kCVReturnSuccess;
}
#endif
// call while holding lock
static void updateDisplayLink(POPAnimator *self)
{
BOOL paused = (0 == self->_observers.count && self->_list.empty()) || self->_disableDisplayLink;
#if TARGET_OS_IPHONE
if (paused != self->_displayLink.paused) {
FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link");
self->_displayLink.paused = paused;
}
#else
if (NULL != self->_displayLink) {
if (paused == CVDisplayLinkIsRunning(self->_displayLink)) {
FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link");
if (paused) {
CVDisplayLinkStop(self->_displayLink);
} else {
CVDisplayLinkStart(self->_displayLink);
}
}
} else {
if (paused == self->_displayTimerRunning) {
FBLogAnimInfo(paused ? @"pausing display timer" : @"unpausing display timer");
if (paused) {
self->_displayTimerRunning = NO;
dispatch_suspend(self->_displayTimer);
} else {
self->_displayTimerRunning = YES;
dispatch_resume(self->_displayTimer);
}
}
}
#endif
}
static void updateAnimatable(id obj, POPPropertyAnimationState *anim, bool shouldAvoidExtraneousWrite = false)
{
// handle user-initiated stop or pause; halt animation
if (!anim->active || anim->paused)
return;
if (anim->hasValue()) {
POPAnimatablePropertyWriteBlock write = anim->property.writeBlock;
if (NULL == write)
return;
// current animation value
VectorRef currentVec = anim->currentValue();
if (!anim->additive) {
// if avoiding extraneous writes and we have a read block defined
if (shouldAvoidExtraneousWrite) {
POPAnimatablePropertyReadBlock read = anim->property.readBlock;
if (read) {
// compare current animation value with object value
Vector4r currentValue = currentVec->vector4r();
Vector4r objectValue = read_values(read, obj, anim->valueCount);
if (objectValue == currentValue) {
return;
}
}
}
// update previous values; support animation convergence
anim->previous2Vec = anim->previousVec;
anim->previousVec = currentVec;
// write value
write(obj, currentVec->data());
if (anim->tracing) {
[anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
}
} else {
POPAnimatablePropertyReadBlock read = anim->property.readBlock;
NSCAssert(read, @"additive requires an animatable property readBlock");
if (NULL == read) {
return;
}
// object value
Vector4r objectValue = read_values(read, obj, anim->valueCount);
// current value
Vector4r currentValue = currentVec->vector4r();
// determine animation change
if (anim->previousVec) {
Vector4r previousValue = anim->previousVec->vector4r();
currentValue -= previousValue;
}
// avoid writing no change
if (shouldAvoidExtraneousWrite && currentValue == Vector4r::Zero()) {
return;
}
// add to object value
currentValue += objectValue;
// update previous values; support animation convergence
anim->previous2Vec = anim->previousVec;
anim->previousVec = currentVec;
// write value
write(obj, currentValue.data());
if (anim->tracing) {
[anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
}
}
}
}
static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time)
{
if (!state->advanceTime(time, obj)) {
return;
}
POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state);
if (NULL != ps) {
updateAnimatable(obj, ps);
}
state->delegateApply();
}
static void applyAnimationToValue(id obj, POPAnimationState *state)
{
POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state);
if (NULL != ps) {
// finalize progress
ps->finalizeProgress();
// write to value, updating only if needed
updateAnimatable(obj, ps, true);
}
state->delegateApply();
}
static POPAnimation *deleteDictEntry(POPAnimator *self, id __unsafe_unretained obj, NSString *key, BOOL cleanup = YES)
{
POPAnimation *anim = nil;
// lock
pthread_mutex_lock(&self->_lock);
NSMutableDictionary *keyAnimationsDict = (__bridge id)CFDictionaryGetValue(self->_dict, (__bridge void *)obj);
if (keyAnimationsDict) {
anim = keyAnimationsDict[key];
if (anim) {
// remove key
[keyAnimationsDict removeObjectForKey:key];
// cleanup empty dictionaries
if (cleanup && 0 == keyAnimationsDict.count) {
CFDictionaryRemoveValue(self->_dict, (__bridge void *)obj);
}
}
}
// unlock
pthread_mutex_unlock(&self->_lock);
return anim;
}
static void stopAndCleanup(POPAnimator *self, POPAnimatorItemRef item, bool shouldRemove, bool finished)
{
// remove
if (shouldRemove) {
deleteDictEntry(self, item->unretainedObject, item->key);
}
// stop
POPAnimationState *state = POPAnimationGetState(item->animation);
state->stop(shouldRemove, finished);
if (shouldRemove) {
// lock
pthread_mutex_lock(&self->_lock);
// find item in list
// may have already been removed on animationDidStop:
POPAnimatorItemListIterator find_iter = find(self->_list.begin(), self->_list.end(), item);
BOOL found = find_iter != self->_list.end();
if (found) {
self->_list.erase(find_iter);
}
// unlock
pthread_mutex_unlock(&self->_lock);
}
}
+ (id)sharedAnimator
{
static POPAnimator* _animator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_animator = [[POPAnimator alloc] init];
});
return _animator;
}
#if !TARGET_OS_IPHONE
+ (BOOL)disableBackgroundThread
{
return _disableBackgroundThread;
}
+ (void)setDisableBackgroundThread:(BOOL)flag
{
_disableBackgroundThread = flag;
}
+ (uint64_t)displayTimerFrequency
{
return _displayTimerFrequency;
}
+ (void)setDisplayTimerFrequency:(uint64_t)frequency
{
_displayTimerFrequency = frequency;
}
#endif
#pragma mark - Lifecycle
- (instancetype)init
{
self = [super init];
if (nil == self) return nil;
#if TARGET_OS_IPHONE
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
_displayLink.paused = YES;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#else
CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
if (kCVReturnSuccess != ret) {
ret = CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &_displayLink);
}
if (kCVReturnSuccess == ret) {
CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self);
} else {
FBLogAnimInfo(@"cannot create display link: ret=%ld, falling back to display timer at %llu Hz", (long)ret, _displayTimerFrequency);
// Thanks to Apple, on older OSes DISPATCH_TIMER_STRICT is not supported and dispatch_source_create failed if we use it.
unsigned long mask = (NSFoundationVersionNumber >= NSFoundationVersionNumber10_9) ? DISPATCH_TIMER_STRICT : 0;
_displayTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, mask, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
NSAssert(nil != _displayTimer, @"Cannot create display timer");
dispatch_source_set_timer(_displayTimer, DISPATCH_TIME_NOW, NSEC_PER_SEC / _displayTimerFrequency, 0);
__weak POPAnimator *weakSelf = self;
dispatch_source_set_event_handler(_displayTimer, ^{
__strong POPAnimator *strongSelf = weakSelf;
if (__builtin_expect(nil != strongSelf, 1)) {
(void) displayLinkCallback(NULL, NULL, NULL, 0, NULL, (__bridge void *)strongSelf);
}
});
}
#endif
_dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5);
pthread_mutex_init(&_lock, NULL);
return self;
}
#if !TARGET_OS_IPHONE
- (instancetype)initWithDisplayID:(CGDirectDisplayID)displayID
{
if (kCGNullDirectDisplay == displayID) {
return [self init];
}
self = [super init];
if (nil == self) return nil;
CVReturn ret = CVDisplayLinkCreateWithCGDisplay(displayID, &_displayLink);
if (kCVReturnSuccess != ret) {
return nil;
}
CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self);
_dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5);
pthread_mutex_init(&_lock, NULL);
return self;
}
#endif
- (void)dealloc
{
#if TARGET_OS_IPHONE
[_displayLink invalidate];
#else
if (_displayLink != NULL) {
CVDisplayLinkStop(_displayLink);
CVDisplayLinkRelease(_displayLink);
}
if (_displayTimer != NULL) {
dispatch_source_cancel(_displayTimer);
#if !OS_OBJECT_USE_OBJC
dispatch_release(_displayTimer);
#endif
_displayTimer = NULL;
}
#endif
[self _clearPendingListObserver];
pthread_mutex_destroy(&_lock);
}
#pragma mark - Utility
- (void)_processPendingList
{
// rendering pending animations
CFTimeInterval time = [self _currentRenderTime];
[self _renderTime:(0 != _beginTime) ? _beginTime : time items:_pendingList];
// lock
pthread_mutex_lock(&_lock);
// clear list and observer
_pendingList.clear();
[self _clearPendingListObserver];
// unlock
pthread_mutex_unlock(&_lock);
}
- (void)_clearPendingListObserver
{
if (_pendingListObserver) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes);
CFRelease(_pendingListObserver);
_pendingListObserver = NULL;
}
}
- (void)_scheduleProcessPendingList
{
// see WebKit for magic numbers, eg http://trac.webkit.org/changeset/166540
static const CFIndex CATransactionCommitRunLoopOrder = 2000000;
static const CFIndex POPAnimationApplyRunLoopOrder = CATransactionCommitRunLoopOrder - 1;
// lock
pthread_mutex_lock(&_lock);
if (!_pendingListObserver) {
__weak POPAnimator *weakSelf = self;
_pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[weakSelf _processPendingList];
});
if (_pendingListObserver) {
CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes);
}
}
// unlock
pthread_mutex_unlock(&_lock);
}
- (void)_renderTime:(CFTimeInterval)time items:(std::list<POPAnimatorItemRef>)items
{
// begin transaction with actions disabled
[CATransaction begin];
[CATransaction setDisableActions:YES];
// notify delegate
__strong __typeof__(_delegate) delegate = _delegate;
[delegate animatorWillAnimate:self];
// lock
pthread_mutex_lock(&_lock);
// count active animations
const NSUInteger count = items.size();
if (0 == count) {
// unlock
pthread_mutex_unlock(&_lock);
} else {
// copy list into vector
std::vector<POPAnimatorItemRef> vector{ items.begin(), items.end() };
// unlock
pthread_mutex_unlock(&_lock);
for (auto item : vector) {
[self _renderTime:time item:item];
}
}
// notify observers
for (id observer in self.observers) {
[observer animatorDidAnimate:(id)self];
}
// lock
pthread_mutex_lock(&_lock);
// update display link
updateDisplayLink(self);
// unlock
pthread_mutex_unlock(&_lock);
// notify delegate and commit
[delegate animatorDidAnimate:self];
[CATransaction commit];
}
- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item
{
id obj = item->object;
POPAnimation *anim = item->animation;
POPAnimationState *state = POPAnimationGetState(anim);
if (nil == obj) {
// object exists not; stop animating
NSAssert(item->unretainedObject, @"object should exist");
stopAndCleanup(self, item, true, false);
} else {
// start if needed
state->startIfNeeded(obj, time, _slowMotionAccumulator);
// only run active, not paused animations
if (state->active && !state->paused) {
// object exists; animate
applyAnimationTime(obj, state, time);
FBLogAnimDebug(@"time:%f running:%@", time, item->animation);
if (state->isDone()) {
// set end value
applyAnimationToValue(obj, state);
state->repeatCount--;
if (state->repeatForever || state->repeatCount > 0) {
if ([anim isKindOfClass:[POPPropertyAnimation class]]) {
POPPropertyAnimation *propAnim = (POPPropertyAnimation *)anim;
id oldFromValue = propAnim.fromValue;
propAnim.fromValue = propAnim.toValue;
if (state->autoreverses) {
if (state->tracing) {
[state->tracer autoreversed];
}
if (state->type == kPOPAnimationDecay) {
POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim;
decayAnimation.velocity = [decayAnimation reversedVelocity];
} else {
propAnim.toValue = oldFromValue;
}
} else {
if (state->type == kPOPAnimationDecay) {
POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim;
id originalVelocity = decayAnimation.originalVelocity;
decayAnimation.velocity = originalVelocity;
} else {
propAnim.fromValue = oldFromValue;
}
}
}
state->stop(NO, NO);
state->reset(true);
state->startIfNeeded(obj, time, _slowMotionAccumulator);
} else {
stopAndCleanup(self, item, state->removedOnCompletion, YES);
}
}
}
}
}
#pragma mark - API
- (NSArray *)observers
{
// lock
pthread_mutex_lock(&_lock);
// get observers
NSArray *observers = 0 != _observers.count ? [_observers copy] : nil;
// unlock
pthread_mutex_unlock(&_lock);
return observers;
}
- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key
{
if (!anim || !obj) {
return;
}
// support arbitrarily many nil keys
if (!key) {
key = [[NSUUID UUID] UUIDString];
}
// lock
pthread_mutex_lock(&_lock);
// get key, animation dict associated with object
NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj);
// update associated animation state
if (nil == keyAnimationDict) {
keyAnimationDict = [NSMutableDictionary dictionary];
CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict);
} else {
// if the animation instance already exists, avoid cancelling only to restart
POPAnimation *existingAnim = keyAnimationDict[key];
if (existingAnim) {
// unlock
pthread_mutex_unlock(&_lock);
if (existingAnim == anim) {
return;
}
[self removeAnimationForObject:obj key:key cleanupDict:NO];
// lock
pthread_mutex_lock(&_lock);
}
}
keyAnimationDict[key] = anim;
// create entry after potential removal
POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim));
// add to list and pending list
_list.push_back(item);
_pendingList.push_back(item);
// support animation re-use, reset all animation state
POPAnimationGetState(anim)->reset(true);
// update display link
updateDisplayLink(self);
// unlock
pthread_mutex_unlock(&_lock);
// schedule runloop processing of pending animations
[self _scheduleProcessPendingList];
}
- (void)removeAllAnimationsForObject:(id)obj
{
// lock
pthread_mutex_lock(&_lock);
NSArray *animations = [(__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj) allValues];
CFDictionaryRemoveValue(_dict, (__bridge void *)obj);
// unlock
pthread_mutex_unlock(&_lock);
if (0 == animations.count) {
return;
}
NSHashTable *animationSet = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:animations.count];
for (id animation in animations) {
[animationSet addObject:animation];
}
// lock
pthread_mutex_lock(&_lock);
POPAnimatorItemRef item;
for (auto iter = _list.begin(); iter != _list.end();) {
item = *iter;
if(![animationSet containsObject:item->animation]) {
iter++;
} else {
iter = _list.erase(iter);
}
}
// unlock
pthread_mutex_unlock(&_lock);
for (POPAnimation *anim in animations) {
POPAnimationState *state = POPAnimationGetState(anim);
state->stop(true, !state->active);
}
}
- (void)removeAnimationForObject:(id)obj key:(NSString *)key cleanupDict:(BOOL)cleanupDict
{
POPAnimation *anim = deleteDictEntry(self, obj, key, cleanupDict);
if (nil == anim) {
return;
}
// lock
pthread_mutex_lock(&_lock);
// remove from list
POPAnimatorItemRef item;
for (auto iter = _list.begin(); iter != _list.end();) {
item = *iter;
if(anim == item->animation) {
_list.erase(iter);
break;
} else {
iter++;
}
}
// remove from pending list
for (auto iter = _pendingList.begin(); iter != _pendingList.end();) {
item = *iter;
if(anim == item->animation) {
_pendingList.erase(iter);
break;
} else {
iter++;
}
}
// unlock
pthread_mutex_unlock(&_lock);
// stop animation and callout
POPAnimationState *state = POPAnimationGetState(anim);
state->stop(true, (!state->active && !state->paused));
}
- (void)removeAnimationForObject:(id)obj key:(NSString *)key
{
[self removeAnimationForObject:obj key:key cleanupDict:YES];
}
- (NSArray *)animationKeysForObject:(id)obj
{
// lock
pthread_mutex_lock(&_lock);
// get keys
NSArray *keys = [(__bridge NSDictionary *)CFDictionaryGetValue(_dict, (__bridge void *)obj) allKeys];
// unlock
pthread_mutex_unlock(&_lock);
return keys;
}
- (id)animationForObject:(id)obj key:(NSString *)key
{
// lock
pthread_mutex_lock(&_lock);
// lookup animation
NSDictionary *keyAnimationsDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj);
POPAnimation *animation = keyAnimationsDict[key];
// unlock
pthread_mutex_unlock(&_lock);
return animation;
}
- (CFTimeInterval)refreshPeriod
{
#if TARGET_OS_IPHONE
return self->_displayLink.duration;
#else
if (NULL != self->_displayLink) {
CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(self->_displayLink);
if (period.flags & kCVTimeIsIndefinite) {
return 0;
}
return ((CFTimeInterval)period.timeValue / (CFTimeInterval)period.timeScale);
}
return (1.0 / (CFTimeInterval)_displayTimerFrequency);
#endif
}
- (CFTimeInterval)_currentRenderTime
{
CFTimeInterval time = CACurrentMediaTime();
#if TARGET_IPHONE_SIMULATOR
// support slow-motion animations
time += _slowMotionAccumulator;
float f = POPAnimationDragCoefficient();
if (f > 1.0) {
if (!_slowMotionStartTime) {
_slowMotionStartTime = time;
} else {
time = (time - _slowMotionStartTime) / f + _slowMotionStartTime;
_slowMotionLastTime = time;
}
} else if (_slowMotionStartTime) {
CFTimeInterval dt = (_slowMotionLastTime - time);
time += dt;
_slowMotionAccumulator += dt;
_slowMotionStartTime = 0;
}
#endif
return time;
}
- (void)render
{
CFTimeInterval time = [self _currentRenderTime];
[self renderTime:time];
}
- (void)renderTime:(CFTimeInterval)time
{
[self _renderTime:time items:_list];
}
- (void)addObserver:(id<POPAnimatorObserving>)observer
{
NSAssert(nil != observer, @"attempting to add nil %@ observer", self);
if (nil == observer) {
return;
}
// lock
pthread_mutex_lock(&_lock);
if (!_observers) {
// use ordered collection for deterministic callout
_observers = [[NSMutableArray alloc] initWithCapacity:1];
}
[_observers addObject:observer];
updateDisplayLink(self);
// unlock
pthread_mutex_unlock(&_lock);
}
- (void)removeObserver:(id<POPAnimatorObserving>)observer
{
NSAssert(nil != observer, @"attempting to remove nil %@ observer", self);
if (nil == observer) {
return;
}
// lock
pthread_mutex_lock(&_lock);
[_observers removeObject:observer];
updateDisplayLink(self);
// unlock
pthread_mutex_unlock(&_lock);
}
@end
@@ -0,0 +1,74 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimator.h"
@class POPAnimation;
@protocol POPAnimatorObserving <NSObject>
@required
/**
@abstract Called on each observer after animator has advanced. Core Animation actions are disabled by default.
*/
- (void)animatorDidAnimate:(POPAnimator *)animator;
@end
@interface POPAnimator ()
#if !TARGET_OS_IPHONE
/**
Determines whether or not to use a high priority background thread for animation updates. Using a background thread can result in faster, more responsive updates, but may be less compatible. Defaults to YES.
*/
+ (BOOL)disableBackgroundThread;
+ (void)setDisableBackgroundThread:(BOOL)flag;
/**
Determines the frequency (Hz) of the timer used when no display is available. Defaults to 60Hz.
*/
+ (uint64_t)displayTimerFrequency;
+ (void)setDisplayTimerFrequency:(uint64_t)frequency;
#endif
/**
Used for externally driven animator instances.
*/
@property (assign, nonatomic) BOOL disableDisplayLink;
/**
Time used when starting animations. Defaults to 0 meaning current media time is used. Exposed for unit testing.
*/
@property (assign, nonatomic) CFTimeInterval beginTime;
/**
Exposed for unit testing.
*/
- (void)renderTime:(CFTimeInterval)time;
/**
Funnel methods for category additions.
*/
- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key;
- (void)removeAllAnimationsForObject:(id)obj;
- (void)removeAnimationForObject:(id)obj key:(NSString *)key;
- (NSArray *)animationKeysForObject:(id)obj;
- (POPAnimation *)animationForObject:(id)obj key:(NSString *)key;
/**
@abstract Add an animator observer. Observer will be notified of each subsequent animator advance until removal.
*/
- (void)addObserver:(id<POPAnimatorObserving>)observer;
/**
@abstract Remove an animator observer.
*/
- (void)removeObserver:(id<POPAnimatorObserving>)observer;
@end
@@ -0,0 +1,71 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPPropertyAnimation.h"
/**
@abstract A concrete basic animation class.
@discussion Animation is achieved through interpolation.
*/
@interface POPBasicAnimation : POPPropertyAnimation
/**
@abstract The designated initializer.
@returns An instance of a basic animation.
*/
+ (instancetype)animation;
/**
@abstract Convenience initializer that returns an animation with animatable property of name.
@param name The name of the animatable property.
@returns An instance of a basic animation configured with specified animatable property.
*/
+ (instancetype)animationWithPropertyNamed:(NSString *)name;
/**
@abstract Convenience constructor.
@returns Returns a basic animation with kCAMediaTimingFunctionDefault timing function.
*/
+ (instancetype)defaultAnimation;
/**
@abstract Convenience constructor.
@returns Returns a basic animation with kCAMediaTimingFunctionLinear timing function.
*/
+ (instancetype)linearAnimation;
/**
@abstract Convenience constructor.
@returns Returns a basic animation with kCAMediaTimingFunctionEaseIn timing function.
*/
+ (instancetype)easeInAnimation;
/**
@abstract Convenience constructor.
@returns Returns a basic animation with kCAMediaTimingFunctionEaseOut timing function.
*/
+ (instancetype)easeOutAnimation;
/**
@abstract Convenience constructor.
@returns Returns a basic animation with kCAMediaTimingFunctionEaseInEaseOut timing function.
*/
+ (instancetype)easeInEaseOutAnimation;
/**
@abstract The duration in seconds. Defaults to 0.4.
*/
@property (assign, nonatomic) CFTimeInterval duration;
/**
@abstract A timing function defining the pacing of the animation. Defaults to nil indicating pacing according to kCAMediaTimingFunctionDefault.
*/
@property (strong, nonatomic) CAMediaTimingFunction *timingFunction;
@end
@@ -0,0 +1,106 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPBasicAnimationInternal.h"
@implementation POPBasicAnimation
#undef __state
#define __state ((POPBasicAnimationState *)_state)
#pragma mark - Lifecycle
+ (instancetype)animation
{
return [[self alloc] init];
}
+ (instancetype)animationWithPropertyNamed:(NSString *)aName
{
POPBasicAnimation *anim = [self animation];
anim.property = [POPAnimatableProperty propertyWithName:aName];
return anim;
}
- (void)_initState
{
_state = new POPBasicAnimationState(self);
}
+ (instancetype)linearAnimation
{
POPBasicAnimation *anim = [self animation];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
return anim;
}
+ (instancetype)easeInAnimation
{
POPBasicAnimation *anim = [self animation];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
return anim;
}
+ (instancetype)easeOutAnimation
{
POPBasicAnimation *anim = [self animation];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
return anim;
}
+ (instancetype)easeInEaseOutAnimation
{
POPBasicAnimation *anim = [self animation];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return anim;
}
+ (instancetype)defaultAnimation
{
POPBasicAnimation *anim = [self animation];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
return anim;
}
- (id)init
{
return [self _init];
}
#pragma mark - Properties
DEFINE_RW_PROPERTY(POPBasicAnimationState, duration, setDuration:, CFTimeInterval);
DEFINE_RW_PROPERTY_OBJ(POPBasicAnimationState, timingFunction, setTimingFunction:, CAMediaTimingFunction*, __state->updatedTimingFunction(););
#pragma mark - Utility
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
[super _appendDescription:s debug:debug];
if (__state->duration)
[s appendFormat:@"; duration = %f", __state->duration];
}
@end
@implementation POPBasicAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone {
POPBasicAnimation *copy = [super copyWithZone:zone];
if (copy) {
copy.duration = self.duration;
copy.timingFunction = self.timingFunction; // not a 'copy', but timing functions are publicly immutable.
}
return copy;
}
@end
@@ -0,0 +1,97 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPBasicAnimation.h"
#import "POPPropertyAnimationInternal.h"
// default animation duration
static CGFloat const kPOPAnimationDurationDefault = 0.4;
// progress threshold for computing done
static CGFloat const kPOPProgressThreshold = 1e-6;
static void interpolate(POPValueType valueType, NSUInteger count, const CGFloat *fromVec, const CGFloat *toVec, CGFloat *outVec, CGFloat p)
{
switch (valueType) {
case kPOPValueInteger:
case kPOPValueFloat:
case kPOPValuePoint:
case kPOPValueSize:
case kPOPValueRect:
case kPOPValueEdgeInsets:
case kPOPValueColor:
POPInterpolateVector(count, outVec, fromVec, toVec, p);
break;
default:
NSCAssert(false, @"unhandled type %d", valueType);
break;
}
}
struct _POPBasicAnimationState : _POPPropertyAnimationState
{
CAMediaTimingFunction *timingFunction;
double timingControlPoints[4];
CFTimeInterval duration;
CFTimeInterval timeProgress;
_POPBasicAnimationState(id __unsafe_unretained anim) : _POPPropertyAnimationState(anim),
timingFunction(nil),
timingControlPoints{0.},
duration(kPOPAnimationDurationDefault),
timeProgress(0.)
{
type = kPOPAnimationBasic;
}
bool isDone() {
if (_POPPropertyAnimationState::isDone()) {
return true;
}
return timeProgress + kPOPProgressThreshold >= 1.;
}
void updatedTimingFunction()
{
float vec[4] = {0.};
[timingFunction getControlPointAtIndex:1 values:&vec[0]];
[timingFunction getControlPointAtIndex:2 values:&vec[2]];
for (NSUInteger idx = 0; idx < POP_ARRAY_COUNT(vec); idx++) {
timingControlPoints[idx] = vec[idx];
}
}
bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) {
// default timing function
if (!timingFunction) {
((POPBasicAnimation *)self).timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
}
// solve for normalized time, aka progress [0, 1]
CGFloat p = 1.0f;
if (duration > 0.0f) {
// cap local time to duration
CFTimeInterval t = MIN(time - startTime, duration) / duration;
p = POPTimingFunctionSolve(timingControlPoints, t, SOLVE_EPS(duration));
timeProgress = t;
} else {
timeProgress = 1.;
}
// interpolate and advance
interpolate(valueType, valueCount, fromVec->data(), toVec->data(), currentVec->data(), p);
progress = p;
clampCurrentValue();
return true;
}
};
typedef struct _POPBasicAnimationState POPBasicAnimationState;
@@ -0,0 +1,152 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <CoreGraphics/CoreGraphics.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
#import "POPDefines.h"
#if SCENEKIT_SDK_AVAILABLE
#import <SceneKit/SceneKit.h>
#endif
POP_EXTERN_C_BEGIN
NS_INLINE CGPoint values_to_point(const CGFloat values[])
{
return CGPointMake(values[0], values[1]);
}
NS_INLINE CGSize values_to_size(const CGFloat values[])
{
return CGSizeMake(values[0], values[1]);
}
NS_INLINE CGRect values_to_rect(const CGFloat values[])
{
return CGRectMake(values[0], values[1], values[2], values[3]);
}
#if SCENEKIT_SDK_AVAILABLE
NS_INLINE SCNVector3 values_to_vec3(const CGFloat values[])
{
return SCNVector3Make(values[0], values[1], values[2]);
}
NS_INLINE SCNVector4 values_to_vec4(const CGFloat values[])
{
return SCNVector4Make(values[0], values[1], values[2], values[3]);
}
#endif
#if TARGET_OS_IPHONE
NS_INLINE UIEdgeInsets values_to_edge_insets(const CGFloat values[])
{
return UIEdgeInsetsMake(values[0], values[1], values[2], values[3]);
}
#endif
NS_INLINE void values_from_point(CGFloat values[], CGPoint p)
{
values[0] = p.x;
values[1] = p.y;
}
NS_INLINE void values_from_size(CGFloat values[], CGSize s)
{
values[0] = s.width;
values[1] = s.height;
}
NS_INLINE void values_from_rect(CGFloat values[], CGRect r)
{
values[0] = r.origin.x;
values[1] = r.origin.y;
values[2] = r.size.width;
values[3] = r.size.height;
}
#if SCENEKIT_SDK_AVAILABLE
NS_INLINE void values_from_vec3(CGFloat values[], SCNVector3 v)
{
values[0] = v.x;
values[1] = v.y;
values[2] = v.z;
}
NS_INLINE void values_from_vec4(CGFloat values[], SCNVector4 v)
{
values[0] = v.x;
values[1] = v.y;
values[2] = v.z;
values[3] = v.w;
}
#endif
#if TARGET_OS_IPHONE
NS_INLINE void values_from_edge_insets(CGFloat values[], UIEdgeInsets i)
{
values[0] = i.top;
values[1] = i.left;
values[2] = i.bottom;
values[3] = i.right;
}
#endif
/**
Takes a CGColorRef and converts it into RGBA components, if necessary.
*/
extern void POPCGColorGetRGBAComponents(CGColorRef color, CGFloat components[]);
/**
Takes RGBA components and returns a CGColorRef.
*/
extern CGColorRef POPCGColorRGBACreate(const CGFloat components[]) CF_RETURNS_RETAINED;
/**
Takes a color reference and returns a CGColor.
*/
extern CGColorRef POPCGColorWithColor(id color) CF_RETURNS_NOT_RETAINED;
#if TARGET_OS_IPHONE
/**
Takes a UIColor and converts it into RGBA components, if necessary.
*/
extern void POPUIColorGetRGBAComponents(UIColor *color, CGFloat components[]);
/**
Takes RGBA components and returns a UIColor.
*/
extern UIColor *POPUIColorRGBACreate(const CGFloat components[]) NS_RETURNS_RETAINED;
#else
/**
Takes a NSColor and converts it into RGBA components, if necessary.
*/
extern void POPNSColorGetRGBAComponents(NSColor *color, CGFloat components[]);
/**
Takes RGBA components and returns a NSColor.
*/
extern NSColor *POPNSColorRGBACreate(const CGFloat components[]) NS_RETURNS_RETAINED;
#endif
POP_EXTERN_C_END
@@ -0,0 +1,150 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPCGUtils.h"
#import <objc/runtime.h>
void POPCGColorGetRGBAComponents(CGColorRef color, CGFloat components[])
{
if (color) {
const CGFloat *colors = CGColorGetComponents(color);
size_t count = CGColorGetNumberOfComponents(color);
if (4 == count) {
// RGB colorspace
components[0] = colors[0];
components[1] = colors[1];
components[2] = colors[2];
components[3] = colors[3];
} else if (2 == count) {
// Grey colorspace
components[0] = components[1] = components[2] = colors[0];
components[3] = colors[1];
} else {
// Use CI to convert
CIColor *ciColor = [CIColor colorWithCGColor:color];
components[0] = ciColor.red;
components[1] = ciColor.green;
components[2] = ciColor.blue;
components[3] = ciColor.alpha;
}
} else {
memset(components, 0, 4 * sizeof(components[0]));
}
}
CGColorRef POPCGColorRGBACreate(const CGFloat components[])
{
#if TARGET_OS_IPHONE
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGColorRef color = CGColorCreate(space, components);
CGColorSpaceRelease(space);
return color;
#else
return CGColorCreateGenericRGB(components[0], components[1], components[2], components[3]);
#endif
}
CGColorRef POPCGColorWithColor(id color)
{
if (CFGetTypeID((__bridge CFTypeRef)color) == CGColorGetTypeID()) {
return ((__bridge CGColorRef)color);
}
#if TARGET_OS_IPHONE
else if ([color isKindOfClass:[UIColor class]]) {
return [color CGColor];
}
#else
else if ([color isKindOfClass:[NSColor class]]) {
// -[NSColor CGColor] is only supported since OSX 10.8+
if ([color respondsToSelector:@selector(CGColor)]) {
return [color CGColor];
}
/*
* Otherwise create a CGColorRef manually.
*
* The original accessor is (or would be) declared as:
* @property(readonly) CGColorRef CGColor;
* - (CGColorRef)CGColor NS_RETURNS_INNER_POINTER CF_RETURNS_NOT_RETAINED;
*
* (Please note that OSX' accessor is atomic, while iOS' isn't.)
*
* The access to the NSColor object must thus be synchronized
* and the CGColorRef be stored as an associated object,
* to return a reference which doesn't need to be released manually.
*/
@synchronized(color) {
static const void* key = &key;
CGColorRef colorRef = (__bridge CGColorRef)objc_getAssociatedObject(color, key);
if (!colorRef) {
size_t numberOfComponents = [(NSColor *)color numberOfComponents];
CGFloat components[numberOfComponents];
CGColorSpaceRef colorSpace = [[(NSColor *)color colorSpace] CGColorSpace];
[color getComponents:components];
colorRef = CGColorCreate(colorSpace, components);
objc_setAssociatedObject(color, key, (__bridge id)colorRef, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
CGColorRelease(colorRef);
}
return colorRef;
}
}
#endif
return nil;
}
#if TARGET_OS_IPHONE
void POPUIColorGetRGBAComponents(UIColor *color, CGFloat components[])
{
return POPCGColorGetRGBAComponents(POPCGColorWithColor(color), components);
}
UIColor *POPUIColorRGBACreate(const CGFloat components[])
{
CGColorRef colorRef = POPCGColorRGBACreate(components);
UIColor *color = [[UIColor alloc] initWithCGColor:colorRef];
CGColorRelease(colorRef);
return color;
}
#else
void POPNSColorGetRGBAComponents(NSColor *color, CGFloat components[])
{
return POPCGColorGetRGBAComponents(POPCGColorWithColor(color), components);
}
NSColor *POPNSColorRGBACreate(const CGFloat components[])
{
CGColorRef colorRef = POPCGColorRGBACreate(components);
NSColor *color = nil;
if (colorRef) {
if ([NSColor respondsToSelector:@selector(colorWithCGColor:)]) {
color = [NSColor colorWithCGColor:colorRef];
} else {
color = [NSColor colorWithCIColor:[CIColor colorWithCGColor:colorRef]];
}
CGColorRelease(colorRef);
}
return color;
}
#endif
@@ -0,0 +1,46 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimation.h"
@class POPCustomAnimation;
/**
@abstract POPCustomAnimationBlock is the callback block of a custom animation.
@discussion This block will be executed for each animation frame and should update the property or properties being animated based on current timing.
@param target The object being animated. Reference the passed in target to help avoid retain loops.
@param animation The custom animation instance. Use to determine the current and elapsed time since last callback. Reference the passed in animation to help avoid retain loops.
@return Flag indicating whether the animation should continue animating. Return NO to indicate animation is done.
*/
typedef BOOL (^POPCustomAnimationBlock)(id target, POPCustomAnimation *animation);
/**
@abstract POPCustomAnimation is a concrete animation subclass for custom animations.
*/
@interface POPCustomAnimation : POPAnimation
/**
@abstract Creates and returns an initialized custom animation instance.
@discussion This is the designated initializer.
@param block The custom animation callback block. See {@ref POPCustomAnimationBlock}.
@return The initialized custom animation instance.
*/
+ (instancetype)animationWithBlock:(POPCustomAnimationBlock)block;
/**
@abstract The current animation time at time of callback.
*/
@property (readonly, nonatomic) CFTimeInterval currentTime;
/**
@abstract The elapsed animation time since last callback.
*/
@property (readonly, nonatomic) CFTimeInterval elapsedTime;
@end
@@ -0,0 +1,75 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationInternal.h"
#import "POPCustomAnimation.h"
@interface POPCustomAnimation ()
@property (nonatomic, copy) POPCustomAnimationBlock animate;
@end
@implementation POPCustomAnimation
@synthesize currentTime = _currentTime;
@synthesize elapsedTime = _elapsedTime;
@synthesize animate = _animate;
+ (instancetype)animationWithBlock:(BOOL(^)(id target, POPCustomAnimation *))block
{
POPCustomAnimation *b = [[self alloc] _init];
b.animate = block;
return b;
}
- (id)_init
{
self = [super _init];
if (nil != self) {
_state->type = kPOPAnimationCustom;
}
return self;
}
- (CFTimeInterval)beginTime
{
POPAnimationState *s = POPAnimationGetState(self);
return s->startTime > 0 ? s->startTime : s->beginTime;
}
- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime
{
_currentTime = currentTime;
_elapsedTime = elapsedTime;
return _animate(object, self);
}
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
[s appendFormat:@"; elapsedTime = %f; currentTime = %f;", _elapsedTime, _currentTime];
}
@end
/**
* Note that only the animate block is copied, but not the current/elapsed times
*/
@implementation POPCustomAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone {
POPCustomAnimation *copy = [super copyWithZone:zone];
if (copy) {
copy.animate = self.animate;
}
return copy;
}
@end
@@ -0,0 +1,66 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPPropertyAnimation.h"
/**
@abstract A concrete decay animation class.
@discussion Animation is achieved through gradual decay of animation value.
*/
@interface POPDecayAnimation : POPPropertyAnimation
/**
@abstract The designated initializer.
@returns An instance of a decay animation.
*/
+ (instancetype)animation;
/**
@abstract Convenience initializer that returns an animation with animatable property of name.
@param name The name of the animatable property.
@returns An instance of a decay animation configured with specified animatable property.
*/
+ (instancetype)animationWithPropertyNamed:(NSString *)name;
/**
@abstract The current velocity value.
@discussion Set before animation start to account for initial velocity. Expressed in change of value units per second. The only POPValueTypes supported for velocity are: kPOPValuePoint, kPOPValueInteger, kPOPValueFloat, kPOPValueRect, and kPOPValueSize.
*/
@property (copy, nonatomic) id velocity;
/**
@abstract The original velocity value.
@discussion Since the velocity property is modified as the animation progresses, this property stores the original, passed in velocity to support autoreverse and repeatCount.
*/
@property (copy, nonatomic, readonly) id originalVelocity;
/**
@abstract The deceleration factor.
@discussion Values specifies should be in the range [0, 1]. Lower values results in faster deceleration. Defaults to 0.998.
*/
@property (assign, nonatomic) CGFloat deceleration;
/**
@abstract The expected duration.
@discussion Derived based on input velocity and deceleration values.
*/
@property (readonly, assign, nonatomic) CFTimeInterval duration;
/**
The to value is derived based on input velocity and deceleration.
*/
- (void)setToValue:(id)toValue NS_UNAVAILABLE;
/**
@abstract The reversed velocity.
@discussion The reversed velocity based on the originalVelocity when the animation was set up.
*/
- (id)reversedVelocity;
@end
@@ -0,0 +1,203 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPDecayAnimationInternal.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
const POPValueType supportedVelocityTypes[6] = { kPOPValuePoint, kPOPValueInteger, kPOPValueFloat, kPOPValueRect, kPOPValueSize, kPOPValueEdgeInsets };
@implementation POPDecayAnimation
#pragma mark - Lifecycle
#undef __state
#define __state ((POPDecayAnimationState *)_state)
+ (instancetype)animation
{
return [[self alloc] init];
}
+ (instancetype)animationWithPropertyNamed:(NSString *)aName
{
POPDecayAnimation *anim = [self animation];
anim.property = [POPAnimatableProperty propertyWithName:aName];
return anim;
}
- (id)init
{
return [self _init];
}
- (void)_initState
{
_state = new POPDecayAnimationState(self);
}
#pragma mark - Properties
DEFINE_RW_PROPERTY(POPDecayAnimationState, deceleration, setDeceleration:, CGFloat, __state->toVec = NULL;);
@dynamic velocity;
- (id)toValue
{
[self _ensureComputedProperties];
return POPBox(__state->toVec, __state->valueType);
}
- (CFTimeInterval)duration
{
[self _ensureComputedProperties];
return __state->duration;
}
- (void)setFromValue:(id)fromValue
{
super.fromValue = fromValue;
[self _invalidateComputedProperties];
}
- (void)setToValue:(id)aValue
{
// no-op
NSLog(@"ignoring to value on decay animation %@", self);
}
- (id)reversedVelocity
{
id reversedVelocity = nil;
POPValueType velocityType = POPSelectValueType(self.originalVelocity, supportedVelocityTypes, POP_ARRAY_COUNT(supportedVelocityTypes));
if (velocityType == kPOPValueFloat) {
#if CGFLOAT_IS_DOUBLE
CGFloat originalVelocityFloat = [(NSNumber *)self.originalVelocity doubleValue];
#else
CGFloat originalVelocityFloat = [(NSNumber *)self.originalVelocity floatValue];
#endif
NSNumber *negativeOriginalVelocityNumber = @(-originalVelocityFloat);
reversedVelocity = negativeOriginalVelocityNumber;
} else if (velocityType == kPOPValueInteger) {
NSInteger originalVelocityInteger = [(NSNumber *)self.originalVelocity integerValue];
NSNumber *negativeOriginalVelocityNumber = @(-originalVelocityInteger);
reversedVelocity = negativeOriginalVelocityNumber;
} else if (velocityType == kPOPValuePoint) {
CGPoint originalVelocityPoint = [self.originalVelocity CGPointValue];
CGPoint negativeOriginalVelocityPoint = CGPointMake(-originalVelocityPoint.x, -originalVelocityPoint.y);
reversedVelocity = [NSValue valueWithCGPoint:negativeOriginalVelocityPoint];
} else if (velocityType == kPOPValueRect) {
CGRect originalVelocityRect = [self.originalVelocity CGRectValue];
CGRect negativeOriginalVelocityRect = CGRectMake(-originalVelocityRect.origin.x, -originalVelocityRect.origin.y, -originalVelocityRect.size.width, -originalVelocityRect.size.height);
reversedVelocity = [NSValue valueWithCGRect:negativeOriginalVelocityRect];
} else if (velocityType == kPOPValueSize) {
CGSize originalVelocitySize = [self.originalVelocity CGSizeValue];
CGSize negativeOriginalVelocitySize = CGSizeMake(-originalVelocitySize.width, -originalVelocitySize.height);
reversedVelocity = [NSValue valueWithCGSize:negativeOriginalVelocitySize];
} else if (velocityType == kPOPValueEdgeInsets) {
#if TARGET_OS_IPHONE
UIEdgeInsets originalVelocityInsets = [self.originalVelocity UIEdgeInsetsValue];
UIEdgeInsets negativeOriginalVelocityInsets = UIEdgeInsetsMake(-originalVelocityInsets.top, -originalVelocityInsets.left, -originalVelocityInsets.bottom, -originalVelocityInsets.right);
reversedVelocity = [NSValue valueWithUIEdgeInsets:negativeOriginalVelocityInsets];
#endif
}
return reversedVelocity;
}
- (id)originalVelocity
{
return POPBox(__state->originalVelocityVec, __state->valueType);
}
- (id)velocity
{
return POPBox(__state->velocityVec, __state->valueType);
}
- (void)setVelocity:(id)aValue
{
POPValueType valueType = POPSelectValueType(aValue, supportedVelocityTypes, POP_ARRAY_COUNT(supportedVelocityTypes));
if (valueType != kPOPValueUnknown) {
VectorRef vec = POPUnbox(aValue, __state->valueType, __state->valueCount, YES);
VectorRef origVec = POPUnbox(aValue, __state->valueType, __state->valueCount, YES);
if (!vec_equal(vec, __state->velocityVec)) {
__state->velocityVec = vec;
__state->originalVelocityVec = origVec;
if (__state->tracing) {
[__state->tracer updateVelocity:aValue];
}
[self _invalidateComputedProperties];
// automatically unpause active animations
if (__state->active && __state->paused) {
__state->fromVec = NULL;
__state->setPaused(false);
}
}
} else {
__state->velocityVec = NULL;
NSLog(@"Invalid velocity value for the decayAnimation: %@", aValue);
}
}
#pragma mark - Utility
- (void)_ensureComputedProperties
{
if (NULL == __state->toVec) {
__state->computeDuration();
__state->computeToValue();
}
}
- (void)_invalidateComputedProperties
{
__state->toVec = NULL;
__state->duration = 0;
}
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
[super _appendDescription:s debug:debug];
if (0 != self.duration) {
[s appendFormat:@"; duration = %f", self.duration];
}
if (__state->deceleration) {
[s appendFormat:@"; deceleration = %f", __state->deceleration];
}
}
@end
@implementation POPDecayAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone {
POPDecayAnimation *copy = [super copyWithZone:zone];
if (copy) {
// Set the velocity to the animation's original velocity, not its current.
copy.velocity = self.originalVelocity;
copy.deceleration = self.deceleration;
}
return copy;
}
@end
@@ -0,0 +1,127 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPDecayAnimation.h"
#import <cmath>
#import "POPPropertyAnimationInternal.h"
// minimal velocity factor before decay animation is considered complete, in units / s
static CGFloat kPOPAnimationDecayMinimalVelocityFactor = 5.;
// default decay animation deceleration
static CGFloat kPOPAnimationDecayDecelerationDefault = 0.998;
static void decay_position(CGFloat *x, CGFloat *v, NSUInteger count, CFTimeInterval dt, CGFloat deceleration)
{
dt *= 1000;
// v0 = v / 1000
// v = v0 * powf(deceleration, dt);
// v = v * 1000;
// x0 = x;
// x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration)
float v0[count];
float kv = powf(deceleration, dt);
float kx = deceleration * (1 - kv) / (1 - deceleration);
for (NSUInteger idx = 0; idx < count; idx++) {
v0[idx] = v[idx] / 1000.;
v[idx] = v0[idx] * kv * 1000.;
x[idx] = x[idx] + v0[idx] * kx;
}
}
struct _POPDecayAnimationState : _POPPropertyAnimationState
{
double deceleration;
CFTimeInterval duration;
_POPDecayAnimationState(id __unsafe_unretained anim) :
_POPPropertyAnimationState(anim),
deceleration(kPOPAnimationDecayDecelerationDefault),
duration(0)
{
type = kPOPAnimationDecay;
}
bool isDone() {
if (_POPPropertyAnimationState::isDone()) {
return true;
}
CGFloat f = dynamicsThreshold * kPOPAnimationDecayMinimalVelocityFactor;
const CGFloat *velocityValues = vec_data(velocityVec);
for (NSUInteger idx = 0; idx < valueCount; idx++) {
if (std::abs((velocityValues[idx])) >= f)
return false;
}
return true;
}
void computeDuration() {
// compute duration till threshold velocity
Vector4r scaledVelocity = vector4(velocityVec) / 1000.;
double k = dynamicsThreshold * kPOPAnimationDecayMinimalVelocityFactor / 1000.;
double vx = k / scaledVelocity.x;
double vy = k / scaledVelocity.y;
double vz = k / scaledVelocity.z;
double vw = k / scaledVelocity.w;
double d = log(deceleration) * 1000.;
duration = MAX(MAX(MAX(log(fabs(vx)) / d, log(fabs(vy)) / d), log(fabs(vz)) / d), log(fabs(vw)) / d);
// ensure velocity threshold is exceeded
if (std::isnan(duration) || duration < 0) {
duration = 0;
}
}
void computeToValue() {
// to value assuming final velocity as a factor of dynamics threshold
// derived from v' = v * d^dt used in decay_position
// to compute the to value with maximal dt, p' = p + (v * d) / (1 - d)
VectorRef fromValue = NULL != currentVec ? currentVec : fromVec;
if (!fromValue) {
return;
}
// ensure duration is computed
if (0 == duration) {
computeDuration();
}
// compute to value
VectorRef toValue(Vector::new_vector(fromValue.get()));
Vector4r velocity = velocityVec->vector4r();
decay_position(toValue->data(), velocity.data(), valueCount, duration, deceleration);
toVec = toValue;
}
bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) {
// advance past not yet initialized animations
if (NULL == currentVec) {
return false;
}
decay_position(currentVec->data(), velocityVec->data(), valueCount, dt, deceleration);
// clamp to compute end value; avoid possibility of decaying past
clampCurrentValue(kPOPAnimationClampEnd | clampMode);
return true;
}
};
typedef struct _POPDecayAnimationState POPDecayAnimationState;
@@ -0,0 +1,37 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef POP_POPDefines_h
#define POP_POPDefines_h
#import <Availability.h>
#ifdef __cplusplus
# define POP_EXTERN_C_BEGIN extern "C" {
# define POP_EXTERN_C_END }
#else
# define POP_EXTERN_C_BEGIN
# define POP_EXTERN_C_END
#endif
#define POP_ARRAY_COUNT(x) sizeof(x) / sizeof(x[0])
#if defined (__cplusplus) && defined (__GNUC__)
# define POP_NOTHROW __attribute__ ((nothrow))
#else
# define POP_NOTHROW
#endif
#if defined(POP_USE_SCENEKIT)
# if TARGET_OS_MAC || TARGET_OS_IPHONE
# define SCENEKIT_SDK_AVAILABLE 1
# endif
#endif
#endif
@@ -0,0 +1,73 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIGeometry.h>
#endif
#if !TARGET_OS_IPHONE
/** NSValue extensions to support animatable types. */
@interface NSValue (POP)
/**
@abstract Creates an NSValue given a CGPoint.
*/
+ (NSValue *)valueWithCGPoint:(CGPoint)point;
/**
@abstract Creates an NSValue given a CGSize.
*/
+ (NSValue *)valueWithCGSize:(CGSize)size;
/**
@abstract Creates an NSValue given a CGRect.
*/
+ (NSValue *)valueWithCGRect:(CGRect)rect;
/**
@abstract Creates an NSValue given a CFRange.
*/
+ (NSValue *)valueWithCFRange:(CFRange)range;
/**
@abstract Creates an NSValue given a CGAffineTransform.
*/
+ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform;
/**
@abstract Returns the underlying CGPoint value.
*/
- (CGPoint)CGPointValue;
/**
@abstract Returns the underlying CGSize value.
*/
- (CGSize)CGSizeValue;
/**
@abstract Returns the underlying CGRect value.
*/
- (CGRect)CGRectValue;
/**
@abstract Returns the underlying CFRange value.
*/
- (CFRange)CFRangeValue;
/**
@abstract Returns the underlying CGAffineTransform value.
*/
- (CGAffineTransform)CGAffineTransformValue;
@end
#endif
@@ -0,0 +1,94 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPGeometry.h"
#if !TARGET_OS_IPHONE
@implementation NSValue (POP)
+ (NSValue *)valueWithCGPoint:(CGPoint)point {
return [NSValue valueWithBytes:&point objCType:@encode(CGPoint)];
}
+ (NSValue *)valueWithCGSize:(CGSize)size {
return [NSValue valueWithBytes:&size objCType:@encode(CGSize)];
}
+ (NSValue *)valueWithCGRect:(CGRect)rect {
return [NSValue valueWithBytes:&rect objCType:@encode(CGRect)];
}
+ (NSValue *)valueWithCFRange:(CFRange)range {
return [NSValue valueWithBytes:&range objCType:@encode(CFRange)];
}
+ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform
{
return [NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)];
}
- (CGPoint)CGPointValue {
CGPoint result;
[self getValue:&result];
return result;
}
- (CGSize)CGSizeValue {
CGSize result;
[self getValue:&result];
return result;
}
- (CGRect)CGRectValue {
CGRect result;
[self getValue:&result];
return result;
}
- (CFRange)CFRangeValue {
CFRange result;
[self getValue:&result];
return result;
}
- (CGAffineTransform)CGAffineTransformValue {
CGAffineTransform result;
[self getValue:&result];
return result;
}
@end
#endif
#if TARGET_OS_IPHONE
#import "POPDefines.h"
#if SCENEKIT_SDK_AVAILABLE
#import <SceneKit/SceneKit.h>
/**
Dirty hacks because iOS is weird and decided to define both SCNVector3's and SCNVector4's objCType as "t". However @encode(SCNVector3) and @encode(SCNVector4) both return the proper definition ("{SCNVector3=fff}" and "{SCNVector4=ffff}" respectively)
[[NSValue valueWithSCNVector3:SCNVector3Make(0.0, 0.0, 0.0)] objcType] returns "t", whereas it should return "{SCNVector3=fff}".
*flips table*
*/
@implementation NSValue (SceneKitFixes)
+ (NSValue *)valueWithSCNVector3:(SCNVector3)vec3 {
return [NSValue valueWithBytes:&vec3 objCType:@encode(SCNVector3)];
}
+ (NSValue *)valueWithSCNVector4:(SCNVector4)vec4 {
return [NSValue valueWithBytes:&vec4 objCType:@encode(SCNVector4)];
}
@end
#endif
#endif
@@ -0,0 +1,196 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <QuartzCore/QuartzCore.h>
#import "POPDefines.h"
POP_EXTERN_C_BEGIN
#pragma mark - Scale
/**
@abstract Returns layer scale factor for the x axis.
*/
extern CGFloat POPLayerGetScaleX(CALayer *l);
/**
@abstract Set layer scale factor for the x axis.
*/
extern void POPLayerSetScaleX(CALayer *l, CGFloat f);
/**
@abstract Returns layer scale factor for the y axis.
*/
extern CGFloat POPLayerGetScaleY(CALayer *l);
/**
@abstract Set layer scale factor for the y axis.
*/
extern void POPLayerSetScaleY(CALayer *l, CGFloat f);
/**
@abstract Returns layer scale factor for the z axis.
*/
extern CGFloat POPLayerGetScaleZ(CALayer *l);
/**
@abstract Set layer scale factor for the z axis.
*/
extern void POPLayerSetScaleZ(CALayer *l, CGFloat f);
/**
@abstract Returns layer scale factors for x and y access as point.
*/
extern CGPoint POPLayerGetScaleXY(CALayer *l);
/**
@abstract Sets layer x and y scale factors given point.
*/
extern void POPLayerSetScaleXY(CALayer *l, CGPoint p);
#pragma mark - Translation
/**
@abstract Returns layer translation factor for the x axis.
*/
extern CGFloat POPLayerGetTranslationX(CALayer *l);
/**
@abstract Set layer translation factor for the x axis.
*/
extern void POPLayerSetTranslationX(CALayer *l, CGFloat f);
/**
@abstract Returns layer translation factor for the y axis.
*/
extern CGFloat POPLayerGetTranslationY(CALayer *l);
/**
@abstract Set layer translation factor for the y axis.
*/
extern void POPLayerSetTranslationY(CALayer *l, CGFloat f);
/**
@abstract Returns layer translation factor for the z axis.
*/
extern CGFloat POPLayerGetTranslationZ(CALayer *l);
/**
@abstract Set layer translation factor for the z axis.
*/
extern void POPLayerSetTranslationZ(CALayer *l, CGFloat f);
/**
@abstract Returns layer translation factors for x and y access as point.
*/
extern CGPoint POPLayerGetTranslationXY(CALayer *l);
/**
@abstract Sets layer x and y translation factors given point.
*/
extern void POPLayerSetTranslationXY(CALayer *l, CGPoint p);
#pragma mark - Rotation
/**
@abstract Returns layer rotation, in radians, in the X axis.
*/
extern CGFloat POPLayerGetRotationX(CALayer *l);
/**
@abstract Sets layer rotation, in radians, in the X axis.
*/
extern void POPLayerSetRotationX(CALayer *l, CGFloat f);
/**
@abstract Returns layer rotation, in radians, in the Y axis.
*/
extern CGFloat POPLayerGetRotationY(CALayer *l);
/**
@abstract Sets layer rotation, in radians, in the Y axis.
*/
extern void POPLayerSetRotationY(CALayer *l, CGFloat f);
/**
@abstract Returns layer rotation, in radians, in the Z axis.
*/
extern CGFloat POPLayerGetRotationZ(CALayer *l);
/**
@abstract Sets layer rotation, in radians, in the Z axis.
*/
extern void POPLayerSetRotationZ(CALayer *l, CGFloat f);
/**
@abstract Returns layer rotation, in radians, in the Z axis.
*/
extern CGFloat POPLayerGetRotation(CALayer *l);
/**
@abstract Sets layer rotation, in radians, in the Z axis.
*/
extern void POPLayerSetRotation(CALayer *l, CGFloat f);
#pragma mark - Sublayer Scale
/**
@abstract Returns sublayer scale factors for x and y access as point.
*/
extern CGPoint POPLayerGetSubScaleXY(CALayer *l);
/**
@abstract Sets sublayer x and y scale factors given point.
*/
extern void POPLayerSetSubScaleXY(CALayer *l, CGPoint p);
#pragma mark - Sublayer Translation
/**
@abstract Returns sublayer translation factor for the x axis.
*/
extern CGFloat POPLayerGetSubTranslationX(CALayer *l);
/**
@abstract Set sublayer translation factor for the x axis.
*/
extern void POPLayerSetSubTranslationX(CALayer *l, CGFloat f);
/**
@abstract Returns sublayer translation factor for the y axis.
*/
extern CGFloat POPLayerGetSubTranslationY(CALayer *l);
/**
@abstract Set sublayer translation factor for the y axis.
*/
extern void POPLayerSetSubTranslationY(CALayer *l, CGFloat f);
/**
@abstract Returns sublayer translation factor for the z axis.
*/
extern CGFloat POPLayerGetSubTranslationZ(CALayer *l);
/**
@abstract Set sublayer translation factor for the z axis.
*/
extern void POPLayerSetSubTranslationZ(CALayer *l, CGFloat f);
/**
@abstract Returns sublayer translation factors for x and y access as point.
*/
extern CGPoint POPLayerGetSubTranslationXY(CALayer *l);
/**
@abstract Sets sublayer x and y translation factors given point.
*/
extern void POPLayerSetSubTranslationXY(CALayer *l, CGPoint p);
POP_EXTERN_C_END
@@ -0,0 +1,288 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPLayerExtras.h"
#include "TransformationMatrix.h"
using namespace WebCore;
#define DECOMPOSE_TRANSFORM(L) \
TransformationMatrix _m(L.transform); \
TransformationMatrix::DecomposedType _d; \
_m.decompose(_d);
#define RECOMPOSE_TRANSFORM(L) \
_m.recompose(_d); \
L.transform = _m.transform3d();
#define RECOMPOSE_ROT_TRANSFORM(L) \
_m.recompose(_d, true); \
L.transform = _m.transform3d();
#define DECOMPOSE_SUBLAYER_TRANSFORM(L) \
TransformationMatrix _m(L.sublayerTransform); \
TransformationMatrix::DecomposedType _d; \
_m.decompose(_d);
#define RECOMPOSE_SUBLAYER_TRANSFORM(L) \
_m.recompose(_d); \
L.sublayerTransform = _m.transform3d();
#pragma mark - Scale
NS_INLINE void ensureNonZeroValue(CGFloat &f)
{
if (f == 0) {
f = 1e-6;
}
}
NS_INLINE void ensureNonZeroValue(CGPoint &p)
{
if (p.x == 0 && p.y == 0) {
p.x = 1e-6;
p.y = 1e-6;
}
}
CGFloat POPLayerGetScaleX(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.scaleX;
}
void POPLayerSetScaleX(CALayer *l, CGFloat f)
{
ensureNonZeroValue(f);
DECOMPOSE_TRANSFORM(l);
_d.scaleX = f;
RECOMPOSE_TRANSFORM(l);
}
CGFloat POPLayerGetScaleY(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.scaleY;
}
void POPLayerSetScaleY(CALayer *l, CGFloat f)
{
ensureNonZeroValue(f);
DECOMPOSE_TRANSFORM(l);
_d.scaleY = f;
RECOMPOSE_TRANSFORM(l);
}
CGFloat POPLayerGetScaleZ(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.scaleZ;
}
void POPLayerSetScaleZ(CALayer *l, CGFloat f)
{
ensureNonZeroValue(f);
DECOMPOSE_TRANSFORM(l);
_d.scaleZ = f;
RECOMPOSE_TRANSFORM(l);
}
CGPoint POPLayerGetScaleXY(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return CGPointMake(_d.scaleX, _d.scaleY);
}
void POPLayerSetScaleXY(CALayer *l, CGPoint p)
{
ensureNonZeroValue(p);
DECOMPOSE_TRANSFORM(l);
_d.scaleX = p.x;
_d.scaleY = p.y;
RECOMPOSE_TRANSFORM(l);
}
#pragma mark - Translation
CGFloat POPLayerGetTranslationX(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.translateX;
}
void POPLayerSetTranslationX(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.translateX = f;
RECOMPOSE_TRANSFORM(l);
}
CGFloat POPLayerGetTranslationY(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.translateY;
}
void POPLayerSetTranslationY(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.translateY = f;
RECOMPOSE_TRANSFORM(l);
}
CGFloat POPLayerGetTranslationZ(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.translateZ;
}
void POPLayerSetTranslationZ(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.translateZ = f;
RECOMPOSE_TRANSFORM(l);
}
CGPoint POPLayerGetTranslationXY(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return CGPointMake(_d.translateX, _d.translateY);
}
void POPLayerSetTranslationXY(CALayer *l, CGPoint p)
{
DECOMPOSE_TRANSFORM(l);
_d.translateX = p.x;
_d.translateY = p.y;
RECOMPOSE_TRANSFORM(l);
}
#pragma mark - Rotation
CGFloat POPLayerGetRotationX(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.rotateX;
}
void POPLayerSetRotationX(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.rotateX = f;
RECOMPOSE_ROT_TRANSFORM(l);
}
CGFloat POPLayerGetRotationY(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.rotateY;
}
void POPLayerSetRotationY(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.rotateY = f;
RECOMPOSE_ROT_TRANSFORM(l);
}
CGFloat POPLayerGetRotationZ(CALayer *l)
{
DECOMPOSE_TRANSFORM(l);
return _d.rotateZ;
}
void POPLayerSetRotationZ(CALayer *l, CGFloat f)
{
DECOMPOSE_TRANSFORM(l);
_d.rotateZ = f;
RECOMPOSE_ROT_TRANSFORM(l);
}
CGFloat POPLayerGetRotation(CALayer *l)
{
return POPLayerGetRotationZ(l);
}
void POPLayerSetRotation(CALayer *l, CGFloat f)
{
POPLayerSetRotationZ(l, f);
}
#pragma mark - Sublayer Scale
CGPoint POPLayerGetSubScaleXY(CALayer *l)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
return CGPointMake(_d.scaleX, _d.scaleY);
}
void POPLayerSetSubScaleXY(CALayer *l, CGPoint p)
{
ensureNonZeroValue(p);
DECOMPOSE_SUBLAYER_TRANSFORM(l);
_d.scaleX = p.x;
_d.scaleY = p.y;
RECOMPOSE_SUBLAYER_TRANSFORM(l);
}
#pragma mark - Sublayer Translation
extern CGFloat POPLayerGetSubTranslationX(CALayer *l)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
return _d.translateX;
}
extern void POPLayerSetSubTranslationX(CALayer *l, CGFloat f)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
_d.translateX = f;
RECOMPOSE_SUBLAYER_TRANSFORM(l);
}
extern CGFloat POPLayerGetSubTranslationY(CALayer *l)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
return _d.translateY;
}
extern void POPLayerSetSubTranslationY(CALayer *l, CGFloat f)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
_d.translateY = f;
RECOMPOSE_SUBLAYER_TRANSFORM(l);
}
extern CGFloat POPLayerGetSubTranslationZ(CALayer *l)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
return _d.translateZ;
}
extern void POPLayerSetSubTranslationZ(CALayer *l, CGFloat f)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
_d.translateZ = f;
RECOMPOSE_SUBLAYER_TRANSFORM(l);
}
extern CGPoint POPLayerGetSubTranslationXY(CALayer *l)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
return CGPointMake(_d.translateX, _d.translateY);
}
extern void POPLayerSetSubTranslationXY(CALayer *l, CGPoint p)
{
DECOMPOSE_SUBLAYER_TRANSFORM(l);
_d.translateX = p.x;
_d.translateY = p.y;
RECOMPOSE_SUBLAYER_TRANSFORM(l);
}
@@ -0,0 +1,55 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "POPDefines.h"
NS_INLINE CGFloat sqrtr(CGFloat f)
{
#if CGFLOAT_IS_DOUBLE
return sqrt(f);
#else
return sqrtf(f);
#endif
}
// round to nearest sub; pass 2.0 to round to every 0.5 (eg: retina pixels)
NS_INLINE CGFloat POPSubRound(CGFloat f, CGFloat sub)
{
return round(f * sub) / sub;
}
#define MIX(a, b, f) ((a) + (f) * ((b) - (a)))
// the longer the duration, the higher the necessary precision
#define SOLVE_EPS(dur) (1. / (1000. * (dur)))
#define _EQLF_(x, y, epsilon) (fabsf ((x) - (y)) < epsilon)
extern void POPInterpolateVector(NSUInteger count, CGFloat *dst, const CGFloat *from, const CGFloat *to, CGFloat f);
extern double POPTimingFunctionSolve(const double vec[4], double t, double eps);
// quadratic mapping of t [0, 1] to [start, end]
extern double POPQuadraticOutInterpolation(double t, double start, double end);
// normalize value to [0, 1] based on its range [startValue, endValue]
extern double POPNormalize(double value, double startValue, double endValue);
// project a normalized value [0, 1] to a given range [start, end]
extern double POPProjectNormal(double n, double start, double end);
// solve a quadratic equation of the form a * x^2 + b * x + c = 0
extern void POPQuadraticSolve(CGFloat a, CGFloat b, CGFloat c, CGFloat &x1, CGFloat &x2);
// for a given tension return the bouncy 3 friction that produces no bounce
extern double POPBouncy3NoBounce(double tension);
@@ -0,0 +1,83 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPMath.h"
#import "POPAnimationPrivate.h"
#import "UnitBezier.h"
void POPInterpolateVector(NSUInteger count, CGFloat *dst, const CGFloat *from, const CGFloat *to, CGFloat f)
{
for (NSUInteger idx = 0; idx < count; idx++) {
dst[idx] = MIX(from[idx], to[idx], f);
}
}
double POPTimingFunctionSolve(const double vec[4], double t, double eps)
{
WebCore::UnitBezier bezier(vec[0], vec[1], vec[2], vec[3]);
return bezier.solve(t, eps);
}
double POPNormalize(double value, double startValue, double endValue)
{
return (value - startValue) / (endValue - startValue);
}
double POPProjectNormal(double n, double start, double end)
{
return start + (n * (end - start));
}
static double linear_interpolation(double t, double start, double end)
{
return t * end + (1.f - t) * start;
}
double POPQuadraticOutInterpolation(double t, double start, double end)
{
return linear_interpolation(2*t - t*t, start, end);
}
static double b3_friction1(double x)
{
return (0.0007 * pow(x, 3)) - (0.031 * pow(x, 2)) + 0.64 * x + 1.28;
}
static double b3_friction2(double x)
{
return (0.000044 * pow(x, 3)) - (0.006 * pow(x, 2)) + 0.36 * x + 2.;
}
static double b3_friction3(double x)
{
return (0.00000045 * pow(x, 3)) - (0.000332 * pow(x, 2)) + 0.1078 * x + 5.84;
}
double POPBouncy3NoBounce(double tension)
{
double friction = 0;
if (tension <= 18.) {
friction = b3_friction1(tension);
} else if (tension > 18 && tension <= 44) {
friction = b3_friction2(tension);
} else if (tension > 44) {
friction = b3_friction3(tension);
} else {
assert(false);
}
return friction;
}
void POPQuadraticSolve(CGFloat a, CGFloat b, CGFloat c, CGFloat &x1, CGFloat &x2)
{
CGFloat discriminant = sqrt(b * b - 4 * a * c);
x1 = (-b + discriminant) / (2 * a);
x2 = (-b - discriminant) / (2 * a);
}
@@ -0,0 +1,76 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimatableProperty.h"
#import "POPAnimation.h"
/**
@abstract Flags for clamping animation values.
@discussion Animation values can optionally be clamped to avoid overshoot. kPOPAnimationClampStart ensures values are more than fromValue and kPOPAnimationClampEnd ensures values are less than toValue.
*/
typedef NS_OPTIONS(NSUInteger, POPAnimationClampFlags)
{
kPOPAnimationClampNone = 0,
kPOPAnimationClampStart = 1UL << 0,
kPOPAnimationClampEnd = 1UL << 1,
kPOPAnimationClampBoth = kPOPAnimationClampStart | kPOPAnimationClampEnd,
};
/**
@abstract The semi-concrete property animation subclass.
*/
@interface POPPropertyAnimation : POPAnimation
/**
@abstract The property to animate.
*/
@property (strong, nonatomic) POPAnimatableProperty *property;
/**
@abstract The value to animate from.
@discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start.
*/
@property (copy, nonatomic) id fromValue;
/**
@abstract The value to animate to.
@discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start.
*/
@property (copy, nonatomic) id toValue;
/**
@abstract The rounding factor applied to the current animated value.
@discussion Specify 1.0 to animate between integral values. Defaults to 0 meaning no rounding.
*/
@property (assign, nonatomic) CGFloat roundingFactor;
/**
@abstract The clamp mode applied to the current animated value.
@discussion See {@ref POPAnimationClampFlags} for possible values. Defaults to kPOPAnimationClampNone.
*/
@property (assign, nonatomic) NSUInteger clampMode;
/**
@abstract The flag indicating whether values should be "added" each frame, rather than set.
@discussion Addition may be type dependent. Defaults to NO.
*/
@property (assign, nonatomic, getter = isAdditive) BOOL additive;
@end
@interface POPPropertyAnimation (CustomProperty)
+ (instancetype)animationWithCustomPropertyNamed:(NSString *)name
readBlock:(POPAnimatablePropertyReadBlock)readBlock
writeBlock:(POPAnimatablePropertyWriteBlock)writeBlock;
+ (instancetype)animationWithCustomPropertyReadBlock:(POPAnimatablePropertyReadBlock)readBlock
writeBlock:(POPAnimatablePropertyWriteBlock)writeBlock;
@end
@@ -0,0 +1,149 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPPropertyAnimationInternal.h"
@implementation POPPropertyAnimation
#pragma mark - Lifecycle
#undef __state
#define __state ((POPPropertyAnimationState *)_state)
- (void)_initState
{
_state = new POPPropertyAnimationState(self);
}
#pragma mark - Properties
DEFINE_RW_FLAG(POPPropertyAnimationState, additive, isAdditive, setAdditive:);
DEFINE_RW_PROPERTY(POPPropertyAnimationState, roundingFactor, setRoundingFactor:, CGFloat);
DEFINE_RW_PROPERTY(POPPropertyAnimationState, clampMode, setClampMode:, NSUInteger);
DEFINE_RW_PROPERTY_OBJ(POPPropertyAnimationState, property, setProperty:, POPAnimatableProperty*, ((POPPropertyAnimationState*)_state)->updatedDynamicsThreshold(););
DEFINE_RW_PROPERTY_OBJ_COPY(POPPropertyAnimationState, progressMarkers, setProgressMarkers:, NSArray*, ((POPPropertyAnimationState*)_state)->updatedProgressMarkers(););
- (id)fromValue
{
return POPBox(__state->fromVec, __state->valueType);
}
- (void)setFromValue:(id)aValue
{
POPPropertyAnimationState *s = __state;
VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES);
if (!vec_equal(vec, s->fromVec)) {
s->fromVec = vec;
if (s->tracing) {
[s->tracer updateFromValue:aValue];
}
}
}
- (id)toValue
{
return POPBox(__state->toVec, __state->valueType);
}
- (void)setToValue:(id)aValue
{
POPPropertyAnimationState *s = __state;
VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES);
if (!vec_equal(vec, s->toVec)) {
s->toVec = vec;
// invalidate to dependent state
s->didReachToValue = false;
s->distanceVec = NULL;
if (s->tracing) {
[s->tracer updateToValue:aValue];
}
// automatically unpause active animations
if (s->active && s->paused) {
s->setPaused(false);
}
}
}
- (id)currentValue
{
return POPBox(__state->currentValue(), __state->valueType);
}
#pragma mark - Utility
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
[s appendFormat:@"; from = %@; to = %@", describe(__state->fromVec), describe(__state->toVec)];
if (_state->active)
[s appendFormat:@"; currentValue = %@", describe(__state->currentValue())];
if (__state->velocityVec && 0 != __state->velocityVec->norm())
[s appendFormat:@"; velocity = %@", describe(__state->velocityVec)];
if (!self.removedOnCompletion)
[s appendFormat:@"; removedOnCompletion = %@", POPStringFromBOOL(self.removedOnCompletion)];
if (__state->progressMarkers)
[s appendFormat:@"; progressMarkers = [%@]", [__state->progressMarkers componentsJoinedByString:@", "]];
if (_state->active)
[s appendFormat:@"; progress = %f", __state->progress];
}
@end
@implementation POPPropertyAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone {
POPPropertyAnimation *copy = [super copyWithZone:zone];
if (copy) {
copy.property = [self.property copyWithZone:zone];
copy.fromValue = self.fromValue;
copy.toValue = self.toValue;
copy.roundingFactor = self.roundingFactor;
copy.clampMode = self.clampMode;
copy.additive = self.additive;
}
return copy;
}
@end
@implementation POPPropertyAnimation (CustomProperty)
+ (instancetype)animationWithCustomPropertyNamed:(NSString *)name
readBlock:(POPAnimatablePropertyReadBlock)readBlock
writeBlock:(POPAnimatablePropertyWriteBlock)writeBlock
{
POPPropertyAnimation *animation = [[self alloc] init];
animation.property = [POPAnimatableProperty propertyWithName:name initializer:^(POPMutableAnimatableProperty *prop) {
prop.readBlock = readBlock;
prop.writeBlock = writeBlock;
}];
return animation;
}
+ (instancetype)animationWithCustomPropertyReadBlock:(POPAnimatablePropertyReadBlock)readBlock
writeBlock:(POPAnimatablePropertyWriteBlock)writeBlock
{
return [self animationWithCustomPropertyNamed:[NSUUID UUID].UUIDString
readBlock:readBlock
writeBlock:writeBlock];
}
@end
@@ -0,0 +1,359 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPAnimationInternal.h"
#import "POPPropertyAnimation.h"
static void clampValue(CGFloat &value, CGFloat fromValue, CGFloat toValue, NSUInteger clamp)
{
BOOL increasing = (toValue > fromValue);
// Clamp start of animation.
if ((kPOPAnimationClampStart & clamp) &&
((increasing && (value < fromValue)) || (!increasing && (value > fromValue)))) {
value = fromValue;
}
// Clamp end of animation.
if ((kPOPAnimationClampEnd & clamp) &&
((increasing && (value > toValue)) || (!increasing && (value < toValue)))) {
value = toValue;
}
}
struct _POPPropertyAnimationState : _POPAnimationState
{
POPAnimatableProperty *property;
POPValueType valueType;
NSUInteger valueCount;
VectorRef fromVec;
VectorRef toVec;
VectorRef currentVec;
VectorRef previousVec;
VectorRef previous2Vec;
VectorRef velocityVec;
VectorRef originalVelocityVec;
VectorRef distanceVec;
CGFloat roundingFactor;
NSUInteger clampMode;
NSArray *progressMarkers;
POPProgressMarker *progressMarkerState;
NSUInteger progressMarkerCount;
NSUInteger nextProgressMarkerIdx;
CGFloat dynamicsThreshold;
_POPPropertyAnimationState(id __unsafe_unretained anim) : _POPAnimationState(anim),
property(nil),
valueType((POPValueType)0),
valueCount(0),
fromVec(nullptr),
toVec(nullptr),
currentVec(nullptr),
previousVec(nullptr),
previous2Vec(nullptr),
velocityVec(nullptr),
originalVelocityVec(nullptr),
distanceVec(nullptr),
roundingFactor(0),
clampMode(0),
progressMarkers(nil),
progressMarkerState(nil),
progressMarkerCount(0),
nextProgressMarkerIdx(0),
dynamicsThreshold(0)
{
type = kPOPAnimationBasic;
}
~_POPPropertyAnimationState()
{
if (progressMarkerState) {
free(progressMarkerState);
progressMarkerState = NULL;
}
}
bool canProgress() {
return hasValue();
}
bool shouldRound() {
return 0 != roundingFactor;
}
bool hasValue() {
return 0 != valueCount;
}
bool isDone() {
// inherit done
if (_POPAnimationState::isDone()) {
return true;
}
// consider an animation with no values done
if (!hasValue() && !isCustom()) {
return true;
}
return false;
}
// returns a copy of the currentVec, rounding if needed
VectorRef currentValue() {
VectorRef vec = VectorRef(Vector::new_vector(currentVec.get()));
if (shouldRound()) {
vec->subRound(1 / roundingFactor);
}
return vec;
}
void resetProgressMarkerState()
{
for (NSUInteger idx = 0; idx < progressMarkerCount; idx++)
progressMarkerState[idx].reached = false;
nextProgressMarkerIdx = 0;
}
void updatedProgressMarkers()
{
if (progressMarkerState) {
free(progressMarkerState);
progressMarkerState = NULL;
}
progressMarkerCount = progressMarkers.count;
if (0 != progressMarkerCount) {
progressMarkerState = (POPProgressMarker *)malloc(progressMarkerCount * sizeof(POPProgressMarker));
[progressMarkers enumerateObjectsUsingBlock:^(NSNumber *progressMarker, NSUInteger idx, BOOL *stop) {
progressMarkerState[idx].reached = false;
progressMarkerState[idx].progress = [progressMarker floatValue];
}];
}
nextProgressMarkerIdx = 0;
}
virtual void updatedDynamicsThreshold()
{
dynamicsThreshold = property.threshold;
}
void finalizeProgress()
{
progress = 1.0;
NSUInteger count = valueCount;
VectorRef outVec(Vector::new_vector(count, NULL));
if (outVec && toVec) {
*outVec = *toVec;
}
currentVec = outVec;
clampCurrentValue();
delegateProgress();
}
void computeProgress() {
if (!canProgress()) {
return;
}
static ComputeProgressFunctor<Vector4r> func;
Vector4r v = vector4(currentVec);
Vector4r f = vector4(fromVec);
Vector4r t = vector4(toVec);
progress = func(v, f, t);
}
void delegateProgress() {
if (!canProgress()) {
return;
}
if (delegateDidProgress && progressMarkerState) {
while (nextProgressMarkerIdx < progressMarkerCount) {
if (progress < progressMarkerState[nextProgressMarkerIdx].progress)
break;
if (!progressMarkerState[nextProgressMarkerIdx].reached) {
ActionEnabler enabler;
[delegate pop_animation:self didReachProgress:progressMarkerState[nextProgressMarkerIdx].progress];
progressMarkerState[nextProgressMarkerIdx].reached = true;
}
nextProgressMarkerIdx++;
}
}
if (!didReachToValue) {
bool didReachToValue = false;
if (0 == valueCount) {
didReachToValue = true;
} else {
Vector4r distance = toVec->vector4r();
distance -= currentVec->vector4r();
if (0 == distance.squaredNorm()) {
didReachToValue = true;
} else {
// components
if (distanceVec) {
didReachToValue = true;
const CGFloat *distanceValues = distanceVec->data();
for (NSUInteger idx = 0; idx < valueCount; idx++) {
didReachToValue &= (signbit(distance[idx]) != signbit(distanceValues[idx]));
}
}
}
}
if (didReachToValue) {
handleDidReachToValue();
}
}
}
void handleDidReachToValue() {
didReachToValue = true;
if (delegateDidReachToValue) {
ActionEnabler enabler;
[delegate pop_animationDidReachToValue:self];
}
POPAnimationDidReachToValueBlock block = animationDidReachToValueBlock;
if (block != NULL) {
ActionEnabler enabler;
block(self);
}
if (tracing) {
[tracer didReachToValue:POPBox(currentValue(), valueType, true)];
}
}
void readObjectValue(VectorRef *ptrVec, id obj)
{
// use current object value as from value
POPAnimatablePropertyReadBlock read = property.readBlock;
if (NULL != read) {
Vector4r vec = read_values(read, obj, valueCount);
*ptrVec = VectorRef(Vector::new_vector(valueCount, vec));
if (tracing) {
[tracer readPropertyValue:POPBox(*ptrVec, valueType, true)];
}
}
}
virtual void willRun(bool started, id obj) {
// ensure from value initialized
if (NULL == fromVec) {
readObjectValue(&fromVec, obj);
}
// ensure to value initialized
if (NULL == toVec) {
// compute decay to value
if (kPOPAnimationDecay == type) {
[self toValue];
} else {
// read to value
readObjectValue(&toVec, obj);
}
}
// handle one time value initialization on start
if (started) {
// initialize current vec
if (!currentVec) {
currentVec = VectorRef(Vector::new_vector(valueCount, NULL));
// initialize current value with from value
// only do this on initial creation to avoid overwriting current value
// on paused animation continuation
if (currentVec && fromVec) {
*currentVec = *fromVec;
}
}
// ensure velocity values
if (!velocityVec) {
velocityVec = VectorRef(Vector::new_vector(valueCount, NULL));
}
if (!originalVelocityVec) {
originalVelocityVec = VectorRef(Vector::new_vector(valueCount, NULL));
}
}
// ensure distance value initialized
// depends on current value set on one time start
if (NULL == distanceVec) {
// not yet started animations may not have current value
VectorRef fromVec2 = NULL != currentVec ? currentVec : fromVec;
if (fromVec2 && toVec) {
Vector4r distance = toVec->vector4r();
distance -= fromVec2->vector4r();
if (0 != distance.squaredNorm()) {
distanceVec = VectorRef(Vector::new_vector(valueCount, distance));
}
}
}
}
virtual void reset(bool all) {
_POPAnimationState::reset(all);
if (all) {
currentVec = NULL;
previousVec = NULL;
previous2Vec = NULL;
}
progress = 0;
resetProgressMarkerState();
didReachToValue = false;
distanceVec = NULL;
}
void clampCurrentValue(NSUInteger clamp)
{
if (kPOPAnimationClampNone == clamp)
return;
// Clamp all vector values
CGFloat *currentValues = currentVec->data();
const CGFloat *fromValues = fromVec->data();
const CGFloat *toValues = toVec->data();
for (NSUInteger idx = 0; idx < valueCount; idx++) {
clampValue(currentValues[idx], fromValues[idx], toValues[idx], clamp);
}
}
void clampCurrentValue()
{
clampCurrentValue(clampMode);
}
};
typedef struct _POPPropertyAnimationState POPPropertyAnimationState;
@interface POPPropertyAnimation ()
@end
@@ -0,0 +1,67 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPPropertyAnimation.h"
/**
@abstract A concrete spring animation class.
@discussion Animation is achieved through modeling spring dynamics.
*/
@interface POPSpringAnimation : POPPropertyAnimation
/**
@abstract The designated initializer.
@returns An instance of a spring animation.
*/
+ (instancetype)animation;
/**
@abstract Convenience initializer that returns an animation with animatable property of name.
@param name The name of the animatable property.
@returns An instance of a spring animation configured with specified animatable property.
*/
+ (instancetype)animationWithPropertyNamed:(NSString *)name;
/**
@abstract The current velocity value.
@discussion Set before animation start to account for initial velocity. Expressed in change of value units per second.
*/
@property (copy, nonatomic) id velocity;
/**
@abstract The effective bounciness.
@discussion Use in conjunction with 'springSpeed' to change animation effect. Values are converted into corresponding dynamics constants. Higher values increase spring movement range resulting in more oscillations and springiness. Defined as a value in the range [0, 20]. Defaults to 4.
*/
@property (assign, nonatomic) CGFloat springBounciness;
/**
@abstract The effective speed.
@discussion Use in conjunction with 'springBounciness' to change animation effect. Values are converted into corresponding dynamics constants. Higher values increase the dampening power of the spring resulting in a faster initial velocity and more rapid bounce slowdown. Defined as a value in the range [0, 20]. Defaults to 12.
*/
@property (assign, nonatomic) CGFloat springSpeed;
/**
@abstract The tension used in the dynamics simulation.
@discussion Can be used over bounciness and speed for finer grain tweaking of animation effect.
*/
@property (assign, nonatomic) CGFloat dynamicsTension;
/**
@abstract The friction used in the dynamics simulation.
@discussion Can be used over bounciness and speed for finer grain tweaking of animation effect.
*/
@property (assign, nonatomic) CGFloat dynamicsFriction;
/**
@abstract The mass used in the dynamics simulation.
@discussion Can be used over bounciness and speed for finer grain tweaking of animation effect.
*/
@property (assign, nonatomic) CGFloat dynamicsMass;
@end
@@ -0,0 +1,192 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPSpringAnimationInternal.h"
@implementation POPSpringAnimation
#pragma mark - Lifecycle
#undef __state
#define __state ((POPSpringAnimationState *)_state)
+ (instancetype)animation
{
return [[self alloc] init];
}
+ (instancetype)animationWithPropertyNamed:(NSString *)aName
{
POPSpringAnimation *anim = [self animation];
anim.property = [POPAnimatableProperty propertyWithName:aName];
return anim;
}
- (void)_initState
{
_state = new POPSpringAnimationState(self);
}
- (id)init
{
self = [super _init];
if (nil != self) {
__state->solver = new SpringSolver4d(1, 1, 1);
__state->updatedDynamicsThreshold();
__state->updatedBouncinessAndSpeed();
}
return self;
}
- (void)dealloc
{
if (__state) {
delete __state->solver;
__state->solver = NULL;
}
}
#pragma mark - Properties
- (id)velocity
{
return POPBox(__state->velocityVec, __state->valueType);
}
- (void)setVelocity:(id)aValue
{
POPPropertyAnimationState *s = __state;
VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES);
VectorRef origVec = POPUnbox(aValue, s->valueType, s->valueCount, YES);
if (!vec_equal(vec, s->velocityVec)) {
s->velocityVec = vec;
s->originalVelocityVec = origVec;
if (s->tracing) {
[s->tracer updateVelocity:aValue];
}
}
}
DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsTension, setDynamicsTension:, CGFloat, [self _updatedDynamicsTension];);
DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsFriction, setDynamicsFriction:, CGFloat, [self _updatedDynamicsFriction];);
DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsMass, setDynamicsMass:, CGFloat, [self _updatedDynamicsMass];);
FB_PROPERTY_GET(POPSpringAnimationState, springSpeed, CGFloat);
- (void)setSpringSpeed:(CGFloat)aFloat
{
POPSpringAnimationState *s = __state;
if (s->userSpecifiedDynamics || aFloat != s->springSpeed) {
s->springSpeed = aFloat;
s->userSpecifiedDynamics = false;
s->updatedBouncinessAndSpeed();
if (s->tracing) {
[s->tracer updateSpeed:aFloat];
}
}
}
FB_PROPERTY_GET(POPSpringAnimationState, springBounciness, CGFloat);
- (void)setSpringBounciness:(CGFloat)aFloat
{
POPSpringAnimationState *s = __state;
if (s->userSpecifiedDynamics || aFloat != s->springBounciness) {
s->springBounciness = aFloat;
s->userSpecifiedDynamics = false;
s->updatedBouncinessAndSpeed();
if (s->tracing) {
[s->tracer updateBounciness:aFloat];
}
}
}
- (SpringSolver4d *)solver
{
return __state->solver;
}
- (void)setSolver:(SpringSolver4d *)aSolver
{
if (aSolver != __state->solver) {
if (__state->solver) {
delete(__state->solver);
}
__state->solver = aSolver;
}
}
#pragma mark - Utility
- (void)_updatedDynamicsTension
{
__state->userSpecifiedDynamics = true;
if(__state->tracing) {
[__state->tracer updateTension:__state->dynamicsTension];
}
__state->updatedDynamics();
}
- (void)_updatedDynamicsFriction
{
__state->userSpecifiedDynamics = true;
if(__state->tracing) {
[__state->tracer updateFriction:__state->dynamicsFriction];
}
__state->updatedDynamics();
}
- (void)_updatedDynamicsMass
{
__state->userSpecifiedDynamics = true;
if(__state->tracing) {
[__state->tracer updateMass:__state->dynamicsMass];
}
__state->updatedDynamics();
}
- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug
{
[super _appendDescription:s debug:debug];
if (debug) {
if (_state->userSpecifiedDynamics) {
[s appendFormat:@"; dynamics = (tension:%f, friction:%f, mass:%f)", __state->dynamicsTension, __state->dynamicsFriction, __state->dynamicsMass];
} else {
[s appendFormat:@"; bounciness = %f; speed = %f", __state->springBounciness, __state->springSpeed];
}
}
}
@end
@implementation POPSpringAnimation (NSCopying)
- (instancetype)copyWithZone:(NSZone *)zone {
POPSpringAnimation *copy = [super copyWithZone:zone];
if (copy) {
id velocity = POPBox(__state->originalVelocityVec, __state->valueType);
// If velocity never gets set, then POPBox will return nil, messing up __state->valueCount.
if (velocity) {
copy.velocity = velocity;
}
copy.springBounciness = self.springBounciness;
copy.springSpeed = self.springSpeed;
copy.dynamicsTension = self.dynamicsTension;
copy.dynamicsFriction = self.dynamicsFriction;
copy.dynamicsMass = self.dynamicsMass;
}
return copy;
}
@end
@@ -0,0 +1,132 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <cmath>
#import "POPAnimationExtras.h"
#import "POPPropertyAnimationInternal.h"
struct _POPSpringAnimationState : _POPPropertyAnimationState
{
SpringSolver4d *solver;
CGFloat springSpeed;
CGFloat springBounciness; // normalized springiness
CGFloat dynamicsTension; // tension
CGFloat dynamicsFriction; // friction
CGFloat dynamicsMass; // mass
_POPSpringAnimationState(id __unsafe_unretained anim) : _POPPropertyAnimationState(anim),
solver(nullptr),
springSpeed(12.),
springBounciness(4.),
dynamicsTension(0),
dynamicsFriction(0),
dynamicsMass(0)
{
type = kPOPAnimationSpring;
}
bool hasConverged()
{
NSUInteger count = valueCount;
if (shouldRound()) {
return vec_equal(previous2Vec, previousVec) && vec_equal(previousVec, toVec);
} else {
if (!previousVec || !previous2Vec)
return false;
CGFloat t = dynamicsThreshold / 5;
const CGFloat *toValues = toVec->data();
const CGFloat *previousValues = previousVec->data();
const CGFloat *previous2Values = previous2Vec->data();
for (NSUInteger idx = 0; idx < count; idx++) {
if ((std::abs(toValues[idx] - previousValues[idx]) >= t) || (std::abs(previous2Values[idx] - previousValues[idx]) >= t)) {
return false;
}
}
return true;
}
}
bool isDone() {
if (_POPPropertyAnimationState::isDone()) {
return true;
}
return solver->started() && (hasConverged() || solver->hasConverged());
}
void updatedDynamics()
{
if (NULL != solver) {
solver->setConstants(dynamicsTension, dynamicsFriction, dynamicsMass);
}
}
void updatedDynamicsThreshold()
{
_POPPropertyAnimationState::updatedDynamicsThreshold();
if (NULL != solver) {
solver->setThreshold(dynamicsThreshold);
}
}
void updatedBouncinessAndSpeed() {
[POPSpringAnimation convertBounciness:springBounciness speed:springSpeed toTension:&dynamicsTension friction:&dynamicsFriction mass:&dynamicsMass];
updatedDynamics();
}
bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) {
// advance past not yet initialized animations
if (NULL == currentVec) {
return false;
}
CFTimeInterval localTime = time - startTime;
Vector4d value = vector4d(currentVec);
Vector4d toValue = vector4d(toVec);
Vector4d velocity = vector4d(velocityVec);
SSState4d state;
state.p = toValue - value;
// the solver assumes a spring of size zero
// flip the velocity from user perspective to solver perspective
state.v = velocity * -1;
solver->advance(state, localTime, dt);
value = toValue - state.p;
// flip velocity back to user perspective
velocity = state.v * -1;
*currentVec = value;
if (velocityVec) {
*velocityVec = velocity;
}
clampCurrentValue();
return true;
}
virtual void reset(bool all) {
_POPPropertyAnimationState::reset(all);
if (solver) {
solver->setConstants(dynamicsTension, dynamicsFriction, dynamicsMass);
solver->reset();
}
}
};
typedef struct _POPSpringAnimationState POPSpringAnimationState;
@@ -0,0 +1,190 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "POPVector.h"
namespace POP {
template <typename T>
struct SSState
{
T p;
T v;
};
template <typename T>
struct SSDerivative
{
T dp;
T dv;
};
typedef SSState<Vector4d> SSState4d;
typedef SSDerivative<Vector4d> SSDerivative4d;
const CFTimeInterval solverDt = 0.001f;
const CFTimeInterval maxSolverDt = 30.0f;
/**
Templated spring solver class.
*/
template <typename T>
class SpringSolver
{
double _k; // stiffness
double _b; // dampening
double _m; // mass
double _tp; // threshold
double _tv; // threshold velocity
double _ta; // threshold acceleration
CFTimeInterval _accumulatedTime;
SSState<T> _lastState;
T _lastDv;
bool _started;
public:
SpringSolver(double k, double b, double m = 1) : _k(k), _b(b), _m(m), _started(false)
{
_accumulatedTime = 0;
_lastState.p = T::Zero();
_lastState.v = T::Zero();
_lastDv = T::Zero();
setThreshold(1.);
}
~SpringSolver()
{
}
bool started()
{
return _started;
}
void setConstants(double k, double b, double m)
{
_k = k;
_b = b;
_m = m;
}
void setThreshold(double t)
{
_tp = t / 2; // half a unit
_tv = 25.0 * t; // 5 units per second, squared for comparison
_ta = 625.0 * t * t; // 5 units per second squared, squared for comparison
}
T acceleration(const SSState<T> &state, double t)
{
return state.p*(-_k/_m) - state.v*(_b/_m);
}
SSDerivative<T> evaluate(const SSState<T> &initial, double t)
{
SSDerivative<T> output;
output.dp = initial.v;
output.dv = acceleration(initial, t);
return output;
}
SSDerivative<T> evaluate(const SSState<T> &initial, double t, double dt, const SSDerivative<T> &d)
{
SSState<T> state;
state.p = initial.p + d.dp*dt;
state.v = initial.v + d.dv*dt;
SSDerivative<T> output;
output.dp = state.v;
output.dv = acceleration(state, t+dt);
return output;
}
void integrate(SSState<T> &state, double t, double dt)
{
SSDerivative<T> a = evaluate(state, t);
SSDerivative<T> b = evaluate(state, t, dt*0.5, a);
SSDerivative<T> c = evaluate(state, t, dt*0.5, b);
SSDerivative<T> d = evaluate(state, t, dt, c);
T dpdt = (a.dp + (b.dp + c.dp)*2.0 + d.dp) * (1.0/6.0);
T dvdt = (a.dv + (b.dv + c.dv)*2.0 + d.dv) * (1.0/6.0);
state.p = state.p + dpdt*dt;
state.v = state.v + dvdt*dt;
_lastDv = dvdt;
}
SSState<T> interpolate(const SSState<T> &previous, const SSState<T> &current, double alpha)
{
SSState<T> state;
state.p = current.p*alpha + previous.p*(1-alpha);
state.v = current.v*alpha + previous.v*(1-alpha);
return state;
}
void advance(SSState<T> &state, double t, double dt)
{
_started = true;
if (dt > maxSolverDt) {
// excessive time step, force shut down
_lastDv = _lastState.v = _lastState.p = T::Zero();
} else {
_accumulatedTime += dt;
SSState<T> previousState = state, currentState = state;
while (_accumulatedTime >= solverDt) {
previousState = currentState;
this->integrate(currentState, t, solverDt);
t += solverDt;
_accumulatedTime -= solverDt;
}
CFTimeInterval alpha = _accumulatedTime / solverDt;
_lastState = state = this->interpolate(previousState, currentState, alpha);
}
}
bool hasConverged()
{
if (!_started) {
return false;
}
for (size_t idx = 0; idx < _lastState.p.size(); idx++) {
if (fabs(_lastState.p(idx)) >= _tp) {
return false;
}
}
return (_lastState.v.squaredNorm() < _tv) && (_lastDv.squaredNorm() < _ta);
}
void reset()
{
_accumulatedTime = 0;
_lastState.p = T::Zero();
_lastState.v = T::Zero();
_lastDv = T::Zero();
_started = false;
}
};
/**
Convenience spring solver type definitions.
*/
typedef SpringSolver<Vector2d> SpringSolver2d;
typedef SpringSolver<Vector3d> SpringSolver3d;
typedef SpringSolver<Vector4d> SpringSolver4d;
}
@@ -0,0 +1,396 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef __POP__FBVector__
#define __POP__FBVector__
#ifdef __cplusplus
#include <iostream>
#include <vector>
#import <objc/NSObjCRuntime.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/NSException.h>
#import "POPDefines.h"
#if SCENEKIT_SDK_AVAILABLE
#import <SceneKit/SceneKit.h>
#endif
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
namespace POP {
/** Fixed two-size vector class */
template <typename T>
struct Vector2
{
private:
typedef T Vector2<T>::* const _data[2];
static const _data _v;
public:
T x;
T y;
// Zero vector
static const Vector2 Zero() { return Vector2(0); }
// Constructors
Vector2() {}
explicit Vector2(T v) { x = v; y = v; };
explicit Vector2(T x0, T y0) : x(x0), y(y0) {};
explicit Vector2(const CGPoint &p) : x(p.x), y (p.y) {}
explicit Vector2(const CGSize &s) : x(s.width), y (s.height) {}
// Copy constructor
template<typename U> explicit Vector2(const Vector2<U> &v) : x(v.x), y(v.y) {}
// Index operators
const T& operator[](size_t i) const { return this->*_v[i]; }
T& operator[](size_t i) { return this->*_v[i]; }
const T& operator()(size_t i) const { return this->*_v[i]; }
T& operator()(size_t i) { return this->*_v[i]; }
// Backing data
T * data() { return &(this->*_v[0]); }
const T * data() const { return &(this->*_v[0]); }
// Size
inline size_t size() const { return 2; }
// Assignment
Vector2 &operator= (T v) { x = v; y = v; return *this;}
template<typename U> Vector2 &operator= (const Vector2<U> &v) { x = v.x; y = v.y; return *this;}
// Negation
Vector2 operator- (void) const { return Vector2<T>(-x, -y); }
// Equality
bool operator== (T v) const { return (x == v && y == v); }
bool operator== (const Vector2 &v) const { return (x == v.x && y == v.y); }
// Inequality
bool operator!= (T v) const {return (x != v || y != v); }
bool operator!= (const Vector2 &v) const { return (x != v.x || y != v.y); }
// Scalar Math
Vector2 operator+ (T v) const { return Vector2(x + v, y + v); }
Vector2 operator- (T v) const { return Vector2(x - v, y - v); }
Vector2 operator* (T v) const { return Vector2(x * v, y * v); }
Vector2 operator/ (T v) const { return Vector2(x / v, y / v); }
Vector2 &operator+= (T v) { x += v; y += v; return *this; };
Vector2 &operator-= (T v) { x -= v; y -= v; return *this; };
Vector2 &operator*= (T v) { x *= v; y *= v; return *this; };
Vector2 &operator/= (T v) { x /= v; y /= v; return *this; };
// Vector Math
Vector2 operator+ (const Vector2 &v) const { return Vector2(x + v.x, y + v.y); }
Vector2 operator- (const Vector2 &v) const { return Vector2(x - v.x, y - v.y); }
Vector2 &operator+= (const Vector2 &v) { x += v.x; y += v.y; return *this; };
Vector2 &operator-= (const Vector2 &v) { x -= v.x; y -= v.y; return *this; };
// Norms
CGFloat norm() const { return sqrtr(squaredNorm()); }
CGFloat squaredNorm() const { return x * x + y * y; }
// Cast
template<typename U> Vector2<U> cast() const { return Vector2<U>(x, y); }
CGPoint cg_point() const { return CGPointMake(x, y); };
};
template<typename T>
const typename Vector2<T>::_data Vector2<T>::_v = { &Vector2<T>::x, &Vector2<T>::y };
/** Fixed three-size vector class */
template <typename T>
struct Vector3
{
private:
typedef T Vector3<T>::* const _data[3];
static const _data _v;
public:
T x;
T y;
T z;
// Zero vector
static const Vector3 Zero() { return Vector3(0); };
// Constructors
Vector3() {}
explicit Vector3(T v) : x(v), y(v), z(v) {};
explicit Vector3(T x0, T y0, T z0) : x(x0), y(y0), z(z0) {};
// Copy constructor
template<typename U> explicit Vector3(const Vector3<U> &v) : x(v.x), y(v.y), z(v.z) {}
// Index operators
const T& operator[](size_t i) const { return this->*_v[i]; }
T& operator[](size_t i) { return this->*_v[i]; }
const T& operator()(size_t i) const { return this->*_v[i]; }
T& operator()(size_t i) { return this->*_v[i]; }
// Backing data
T * data() { return &(this->*_v[0]); }
const T * data() const { return &(this->*_v[0]); }
// Size
inline size_t size() const { return 3; }
// Assignment
Vector3 &operator= (T v) { x = v; y = v; z = v; return *this;}
template<typename U> Vector3 &operator= (const Vector3<U> &v) { x = v.x; y = v.y; z = v.z; return *this;}
// Negation
Vector3 operator- (void) const { return Vector3<T>(-x, -y, -z); }
// Equality
bool operator== (T v) const { return (x == v && y == v && z = v); }
bool operator== (const Vector3 &v) const { return (x == v.x && y == v.y && z == v.z); }
// Inequality
bool operator!= (T v) const {return (x != v || y != v || z != v); }
bool operator!= (const Vector3 &v) const { return (x != v.x || y != v.y || z != v.z); }
// Scalar Math
Vector3 operator+ (T v) const { return Vector3(x + v, y + v, z + v); }
Vector3 operator- (T v) const { return Vector3(x - v, y - v, z - v); }
Vector3 operator* (T v) const { return Vector3(x * v, y * v, z * v); }
Vector3 operator/ (T v) const { return Vector3(x / v, y / v, z / v); }
Vector3 &operator+= (T v) { x += v; y += v; z += v; return *this; };
Vector3 &operator-= (T v) { x -= v; y -= v; z -= v; return *this; };
Vector3 &operator*= (T v) { x *= v; y *= v; z *= v; return *this; };
Vector3 &operator/= (T v) { x /= v; y /= v; z /= v; return *this; };
// Vector Math
Vector3 operator+ (const Vector3 &v) const { return Vector3(x + v.x, y + v.y, z + v.z); }
Vector3 operator- (const Vector3 &v) const { return Vector3(x - v.x, y - v.y, z - v.z); }
Vector3 &operator+= (const Vector3 &v) { x += v.x; y += v.y; z += v.z; return *this; };
Vector3 &operator-= (const Vector3 &v) { x -= v.x; y -= v.y; z -= v.z; return *this; };
// Norms
CGFloat norm() const { return sqrtr(squaredNorm()); }
CGFloat squaredNorm() const { return x * x + y * y + z * z; }
// Cast
template<typename U> Vector3<U> cast() const { return Vector3<U>(x, y, z); }
};
template<typename T>
const typename Vector3<T>::_data Vector3<T>::_v = { &Vector3<T>::x, &Vector3<T>::y, &Vector3<T>::z };
/** Fixed four-size vector class */
template <typename T>
struct Vector4
{
private:
typedef T Vector4<T>::* const _data[4];
static const _data _v;
public:
T x;
T y;
T z;
T w;
// Zero vector
static const Vector4 Zero() { return Vector4(0); };
// Constructors
Vector4() {}
explicit Vector4(T v) : x(v), y(v), z(v), w(v) {};
explicit Vector4(T x0, T y0, T z0, T w0) : x(x0), y(y0), z(z0), w(w0) {};
// Copy constructor
template<typename U> explicit Vector4(const Vector4<U> &v) : x(v.x), y(v.y), z(v.z), w(v.w) {}
// Index operators
const T& operator[](size_t i) const { return this->*_v[i]; }
T& operator[](size_t i) { return this->*_v[i]; }
const T& operator()(size_t i) const { return this->*_v[i]; }
T& operator()(size_t i) { return this->*_v[i]; }
// Backing data
T * data() { return &(this->*_v[0]); }
const T * data() const { return &(this->*_v[0]); }
// Size
inline size_t size() const { return 4; }
// Assignment
Vector4 &operator= (T v) { x = v; y = v; z = v; w = v; return *this;}
template<typename U> Vector4 &operator= (const Vector4<U> &v) { x = v.x; y = v.y; z = v.z; w = v.w; return *this;}
// Negation
Vector4 operator- (void) const { return Vector4<T>(-x, -y, -z, -w); }
// Equality
bool operator== (T v) const { return (x == v && y == v && z = v, w = v); }
bool operator== (const Vector4 &v) const { return (x == v.x && y == v.y && z == v.z && w == v.w); }
// Inequality
bool operator!= (T v) const {return (x != v || y != v || z != v || w != v); }
bool operator!= (const Vector4 &v) const { return (x != v.x || y != v.y || z != v.z || w != v.w); }
// Scalar Math
Vector4 operator+ (T v) const { return Vector4(x + v, y + v, z + v, w + v); }
Vector4 operator- (T v) const { return Vector4(x - v, y - v, z - v, w - v); }
Vector4 operator* (T v) const { return Vector4(x * v, y * v, z * v, w * v); }
Vector4 operator/ (T v) const { return Vector4(x / v, y / v, z / v, w / v); }
Vector4 &operator+= (T v) { x += v; y += v; z += v; w += v; return *this; };
Vector4 &operator-= (T v) { x -= v; y -= v; z -= v; w -= v; return *this; };
Vector4 &operator*= (T v) { x *= v; y *= v; z *= v; w *= v; return *this; };
Vector4 &operator/= (T v) { x /= v; y /= v; z /= v; w /= v; return *this; };
// Vector Math
Vector4 operator+ (const Vector4 &v) const { return Vector4(x + v.x, y + v.y, z + v.z, w + v.w); }
Vector4 operator- (const Vector4 &v) const { return Vector4(x - v.x, y - v.y, z - v.z, w - v.w); }
Vector4 &operator+= (const Vector4 &v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; };
Vector4 &operator-= (const Vector4 &v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; };
// Norms
CGFloat norm() const { return sqrtr(squaredNorm()); }
CGFloat squaredNorm() const { return x * x + y * y + z * z + w * w; }
// Cast
template<typename U> Vector4<U> cast() const { return Vector4<U>(x, y, z, w); }
};
template<typename T>
const typename Vector4<T>::_data Vector4<T>::_v = { &Vector4<T>::x, &Vector4<T>::y, &Vector4<T>::z, &Vector4<T>::w };
/** Convenience typedefs */
typedef Vector2<float> Vector2f;
typedef Vector2<double> Vector2d;
typedef Vector2<CGFloat> Vector2r;
typedef Vector3<float> Vector3f;
typedef Vector3<double> Vector3d;
typedef Vector3<CGFloat> Vector3r;
typedef Vector4<float> Vector4f;
typedef Vector4<double> Vector4d;
typedef Vector4<CGFloat> Vector4r;
/** Variable-sized vector class */
class Vector
{
size_t _count;
CGFloat *_values;
private:
Vector(size_t);
Vector(const Vector& other);
public:
~Vector();
// Creates a new vector instance of count with values. Initializing a vector of size 0 returns NULL.
static Vector *new_vector(NSUInteger count, const CGFloat *values);
// Creates a new vector given a pointer to another. Can return NULL.
static Vector *new_vector(const Vector * const other);
// Creates a variable size vector given a static vector and count.
static Vector *new_vector(NSUInteger count, Vector4r vec);
// Size of vector
NSUInteger size() const { return _count; }
// Returns array of values
CGFloat *data () { return _values; }
const CGFloat *data () const { return _values; };
// Vector2r support
Vector2r vector2r() const;
// Vector4r support
Vector4r vector4r() const;
// CGFloat support
static Vector *new_cg_float(CGFloat f);
// CGPoint support
CGPoint cg_point() const;
static Vector *new_cg_point(const CGPoint &p);
// CGSize support
CGSize cg_size() const;
static Vector *new_cg_size(const CGSize &s);
// CGRect support
CGRect cg_rect() const;
static Vector *new_cg_rect(const CGRect &r);
#if TARGET_OS_IPHONE
// UIEdgeInsets support
UIEdgeInsets ui_edge_insets() const;
static Vector *new_ui_edge_insets(const UIEdgeInsets &i);
#endif
// CGAffineTransform support
CGAffineTransform cg_affine_transform() const;
static Vector *new_cg_affine_transform(const CGAffineTransform &t);
// CGColorRef support
CGColorRef cg_color() const CF_RETURNS_RETAINED;
static Vector *new_cg_color(CGColorRef color);
#if SCENEKIT_SDK_AVAILABLE
// SCNVector3 support
SCNVector3 scn_vector3() const;
static Vector *new_scn_vector3(const SCNVector3 &vec3);
// SCNVector4 support
SCNVector4 scn_vector4() const;
static Vector *new_scn_vector4(const SCNVector4 &vec4);
#endif
// operator overloads
CGFloat &operator[](size_t i) const {
NSCAssert(size() > i, @"unexpected vector size:%lu", (unsigned long)size());
return _values[i];
}
// Returns the mathematical length
CGFloat norm() const;
CGFloat squaredNorm() const;
// Round to nearest sub
void subRound(CGFloat sub);
// Returns string description
NSString * toString() const;
// Operator overloads
template<typename U> Vector& operator= (const Vector4<U>& other) {
size_t count = MIN(_count, other.size());
for (size_t i = 0; i < count; i++) {
_values[i] = other[i];
}
return *this;
}
Vector& operator= (const Vector& other);
void swap(Vector &first, Vector &second);
bool operator==(const Vector &other) const;
bool operator!=(const Vector &other) const;
};
/** Convenience typedefs */
typedef std::shared_ptr<Vector> VectorRef;
typedef std::shared_ptr<const Vector> VectorConstRef;
}
#endif /* __cplusplus */
#endif /* defined(__POP__FBVector__) */
@@ -0,0 +1,335 @@
/**
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
#import "POPVector.h"
#import "POPDefines.h"
#import "POPCGUtils.h"
#import "POPMath.h"
namespace POP
{
Vector::Vector(const size_t count)
{
_count = count;
_values = 0 != count ? (CGFloat *)calloc(count, sizeof(CGFloat)) : NULL;
}
Vector::Vector(const Vector& other)
{
_count = other.size();
_values = 0 != _count ? (CGFloat *)calloc(_count, sizeof(CGFloat)) : NULL;
if (0 != _count) {
memcpy(_values, other.data(), _count * sizeof(CGFloat));
}
}
Vector::~Vector()
{
if (NULL != _values) {
free(_values);
_values = NULL;
}
_count = 0;
}
void Vector::swap(Vector &first, Vector &second)
{
using std::swap;
swap(first._count, second._count);
swap(first._values, second._values);
}
Vector& Vector::operator=(const Vector& other)
{
Vector temp(other);
swap(*this, temp);
return *this;
}
bool Vector::operator==(const Vector &other) const {
if (_count != other.size()) {
return false;
}
const CGFloat * const values = other.data();
for (NSUInteger idx = 0; idx < _count; idx++) {
if (_values[idx] != values[idx]) {
return false;
}
}
return true;
}
bool Vector::operator!=(const Vector &other) const {
if (_count == other.size()) {
return false;
}
const CGFloat * const values = other.data();
for (NSUInteger idx = 0; idx < _count; idx++) {
if (_values[idx] != values[idx]) {
return false;
}
}
return true;
}
Vector *Vector::new_vector(NSUInteger count, const CGFloat *values)
{
if (0 == count) {
return NULL;
}
Vector *v = new Vector(count);
if (NULL != values) {
memcpy(v->_values, values, count * sizeof(CGFloat));
}
return v;
}
Vector *Vector::new_vector(const Vector * const other)
{
if (NULL == other) {
return NULL;
}
return Vector::new_vector(other->size(), other->data());
}
Vector *Vector::new_vector(NSUInteger count, Vector4r vec)
{
if (0 == count) {
return NULL;
}
Vector *v = new Vector(count);
NSCAssert(count <= 4, @"unexpected count %lu", (unsigned long)count);
for (NSUInteger i = 0; i < MIN(count, (NSUInteger)4); i++) {
v->_values[i] = vec[i];
}
return v;
}
Vector4r Vector::vector4r() const
{
Vector4r v = Vector4r::Zero();
for (size_t i = 0; i < _count; i++) {
v(i) = _values[i];
}
return v;
}
Vector2r Vector::vector2r() const
{
Vector2r v = Vector2r::Zero();
if (_count > 0) v(0) = _values[0];
if (_count > 1) v(1) = _values[1];
return v;
}
Vector *Vector::new_cg_float(CGFloat f)
{
Vector *v = new Vector(1);
v->_values[0] = f;
return v;
}
CGPoint Vector::cg_point () const
{
Vector2r v = vector2r();
return CGPointMake(v(0), v(1));
}
Vector *Vector::new_cg_point(const CGPoint &p)
{
Vector *v = new Vector(2);
v->_values[0] = p.x;
v->_values[1] = p.y;
return v;
}
CGSize Vector::cg_size () const
{
Vector2r v = vector2r();
return CGSizeMake(v(0), v(1));
}
Vector *Vector::new_cg_size(const CGSize &s)
{
Vector *v = new Vector(2);
v->_values[0] = s.width;
v->_values[1] = s.height;
return v;
}
CGRect Vector::cg_rect() const
{
return _count < 4 ? CGRectZero : CGRectMake(_values[0], _values[1], _values[2], _values[3]);
}
Vector *Vector::new_cg_rect(const CGRect &r)
{
Vector *v = new Vector(4);
v->_values[0] = r.origin.x;
v->_values[1] = r.origin.y;
v->_values[2] = r.size.width;
v->_values[3] = r.size.height;
return v;
}
#if TARGET_OS_IPHONE
UIEdgeInsets Vector::ui_edge_insets() const
{
return _count < 4 ? UIEdgeInsetsZero : UIEdgeInsetsMake(_values[0], _values[1], _values[2], _values[3]);
}
Vector *Vector::new_ui_edge_insets(const UIEdgeInsets &i)
{
Vector *v = new Vector(4);
v->_values[0] = i.top;
v->_values[1] = i.left;
v->_values[2] = i.bottom;
v->_values[3] = i.right;
return v;
}
#endif
CGAffineTransform Vector::cg_affine_transform() const
{
if (_count < 6) {
return CGAffineTransformIdentity;
}
NSCAssert(size() >= 6, @"unexpected vector size:%lu", (unsigned long)size());
CGAffineTransform t;
t.a = _values[0];
t.b = _values[1];
t.c = _values[2];
t.d = _values[3];
t.tx = _values[4];
t.ty = _values[5];
return t;
}
Vector *Vector::new_cg_affine_transform(const CGAffineTransform &t)
{
Vector *v = new Vector(6);
v->_values[0] = t.a;
v->_values[1] = t.b;
v->_values[2] = t.c;
v->_values[3] = t.d;
v->_values[4] = t.tx;
v->_values[5] = t.ty;
return v;
}
CGColorRef Vector::cg_color() const
{
if (_count < 4) {
return NULL;
}
return POPCGColorRGBACreate(_values);
}
Vector *Vector::new_cg_color(CGColorRef color)
{
CGFloat rgba[4];
POPCGColorGetRGBAComponents(color, rgba);
return new_vector(4, rgba);
}
#if SCENEKIT_SDK_AVAILABLE
SCNVector3 Vector::scn_vector3() const
{
return _count < 3 ? SCNVector3Make(0.0, 0.0, 0.0) : SCNVector3Make(_values[0], _values[1], _values[2]);
}
Vector *Vector::new_scn_vector3(const SCNVector3 &vec3)
{
Vector *v = new Vector(3);
v->_values[0] = vec3.x;
v->_values[1] = vec3.y;
v->_values[2] = vec3.z;
return v;
}
SCNVector4 Vector::scn_vector4() const
{
return _count < 4 ? SCNVector4Make(0.0, 0.0, 0.0, 0.0) : SCNVector4Make(_values[0], _values[1], _values[2], _values[3]);
}
Vector *Vector::new_scn_vector4(const SCNVector4 &vec4)
{
Vector *v = new Vector(4);
v->_values[0] = vec4.x;
v->_values[1] = vec4.y;
v->_values[2] = vec4.z;
v->_values[3] = vec4.w;
return v;
}
#endif
void Vector::subRound(CGFloat sub)
{
for (NSUInteger idx = 0; idx < _count; idx++) {
_values[idx] = POPSubRound(_values[idx], sub);
}
}
CGFloat Vector::norm() const
{
return sqrtr(squaredNorm());
}
CGFloat Vector::squaredNorm() const
{
CGFloat d = 0;
for (NSUInteger idx = 0; idx < _count; idx++) {
d += (_values[idx] * _values[idx]);
}
return d;
}
NSString * Vector::toString() const
{
if (0 == _count)
return @"()";
if (1 == _count)
return [NSString stringWithFormat:@"%f", _values[0]];
if (2 == _count)
return [NSString stringWithFormat:@"(%.3f, %.3f)", _values[0], _values[1]];
NSMutableString *s = [NSMutableString stringWithCapacity:10];
for (NSUInteger idx = 0; idx < _count; idx++) {
if (0 == idx) {
[s appendFormat:@"[%.3f", _values[idx]];
} else if (idx == _count - 1) {
[s appendFormat:@", %.3f]", _values[idx]];
} else {
[s appendFormat:@", %.3f", _values[idx]];
}
}
return s;
}
}
@@ -0,0 +1,188 @@
//
// UIView+JPPOP.h
// WoLive
//
// Created by Apple on 2019/8/23.
// Copyright © 2019 zhoujianping. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "POP.h"
/**
* kCAMediaTimingFunctionLinear
* kCAMediaTimingFunctionEaseIn
* kCAMediaTimingFunctionEaseOut
* kCAMediaTimingFunctionEaseInEaseOut
* kCAMediaTimingFunctionDefault
*/
typedef void(^JPPOPCompletionBlock)(POPAnimation *anim, BOOL finished);
@interface UIView (JPPOP)
#pragma mark - pop basic
/** toValue、duration */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration;
/** toValue、duration、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** toValue、duration、beginTime、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、duration、beginTime、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、timingFunctionName、duration、beginTime、key、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
timingFunctionName:(NSString *)timingFunctionName
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock;
#pragma mark - pop spring
/** toValue、springSpeed、springBounciness */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness;
/** toValue、springSpeed、springBounciness、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** toValue、springSpeed、springBounciness、beginTime、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、springSpeed、springBounciness、beginTime、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、springSpeed、springBounciness、beginTime、key、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock;
@end
@interface CALayer (JPPOP)
#pragma mark - pop basic
/** toValue、duration */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration;
/** toValue、duration、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** toValue、duration、beginTime、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、duration、beginTime、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、timingFunctionName、duration、beginTime、key、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
timingFunctionName:(NSString *)timingFunctionName
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock;
#pragma mark - pop spring
/** toValue、springSpeed、springBounciness */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness;
/** toValue、springSpeed、springBounciness、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** toValue、springSpeed、springBounciness、beginTime、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、springSpeed、springBounciness、beginTime、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock;
/** fromValue、toValue、springSpeed、springBounciness、beginTime、key、completionBlock */
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock;
@end
@@ -0,0 +1,343 @@
//
// UIView+JPPOP.m
// WoLive
//
// Created by Apple on 2019/8/23.
// Copyright © 2019 zhoujianping. All rights reserved.
//
#import "UIView+JPPOP.h"
@implementation UIView (JPPOP)
#pragma mark - pop basic
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:0
key:nil
completionBlock:nil];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:0
key:nil
completionBlock:completionBlock];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:fromValue
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
/** fromValue、toValue、timingFunctionName、duration、beginTime、key、completionBlock */
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
timingFunctionName:(NSString *)timingFunctionName
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock {
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:propertyNamed];
anim.duration = duration;
anim.toValue = toValue;
if (fromValue) anim.fromValue = fromValue;
if (timingFunctionName) anim.timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
if (beginTime > 0) anim.beginTime = CACurrentMediaTime() + beginTime;
if (completionBlock) anim.completionBlock = completionBlock;
[self pop_addAnimation:anim forKey:(key ? key : propertyNamed)];
return anim;
}
#pragma mark - pop spring
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:0
key:nil
completionBlock:nil];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:0
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:fromValue
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock {
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:propertyNamed];
anim.springSpeed = springSpeed;
anim.springBounciness = springBounciness;
anim.toValue = toValue;
if (fromValue) anim.fromValue = fromValue;
if (beginTime > 0) anim.beginTime = CACurrentMediaTime() + beginTime;
if (completionBlock) anim.completionBlock = completionBlock;
[self pop_addAnimation:anim forKey:(key ? key : propertyNamed)];
return anim;
}
@end
@implementation CALayer (JPPOP)
#pragma mark - pop basic
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:0
key:nil
completionBlock:nil];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:0
key:nil
completionBlock:completionBlock];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPBasicAnimationWithPropertyNamed:propertyNamed
fromValue:fromValue
toValue:toValue
timingFunctionName:nil
duration:duration
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPBasicAnimation *)jp_addPOPBasicAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
timingFunctionName:(NSString *)timingFunctionName
duration:(NSTimeInterval)duration
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock {
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:propertyNamed];
anim.duration = duration;
anim.toValue = toValue;
if (fromValue) anim.fromValue = fromValue;
if (timingFunctionName) anim.timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
if (beginTime > 0) anim.beginTime = CACurrentMediaTime() + beginTime;
if (completionBlock) anim.completionBlock = completionBlock;
[self pop_addAnimation:anim forKey:(key ? key : propertyNamed)];
return anim;
}
#pragma mark - pop spring
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:0
key:nil
completionBlock:nil];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:0
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:nil
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
completionBlock:(JPPOPCompletionBlock)completionBlock {
return [self jp_addPOPSpringAnimationWithPropertyNamed:propertyNamed
fromValue:fromValue
toValue:toValue
springSpeed:springSpeed
springBounciness:springBounciness
beginTime:beginTime
key:nil
completionBlock:completionBlock];
}
- (POPSpringAnimation *)jp_addPOPSpringAnimationWithPropertyNamed:(NSString *)propertyNamed
fromValue:(id)fromValue
toValue:(id)toValue
springSpeed:(CGFloat)springSpeed
springBounciness:(CGFloat)springBounciness
beginTime:(NSTimeInterval)beginTime
key:(NSString *)key
completionBlock:(JPPOPCompletionBlock)completionBlock {
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:propertyNamed];
anim.springSpeed = springSpeed;
anim.springBounciness = springBounciness;
anim.toValue = toValue;
if (fromValue) anim.fromValue = fromValue;
if (beginTime > 0) anim.beginTime = CACurrentMediaTime() + beginTime;
if (completionBlock) anim.completionBlock = completionBlock;
[self pop_addAnimation:anim forKey:(key ? key : propertyNamed)];
return anim;
}
@end
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2007 Apple Inc. 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 Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS CONTRIBUTORS 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.
*/
#ifndef FloatConversion_h
#define FloatConversion_h
#include <CoreGraphics/CGBase.h>
namespace WebCore {
template<typename T>
float narrowPrecisionToFloat(T);
template<>
inline float narrowPrecisionToFloat(double number)
{
return static_cast<float>(number);
}
template<typename T>
CGFloat narrowPrecisionToCGFloat(T);
template<>
inline CGFloat narrowPrecisionToCGFloat(double number)
{
return static_cast<CGFloat>(number);
}
} // namespace WebCore
#endif // FloatConversion_h
@@ -0,0 +1,279 @@
/*
* Copyright (C) 2005, 2006 Apple Computer, Inc. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. OR
* CONTRIBUTORS 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.
*/
#ifndef TransformationMatrix_h
#define TransformationMatrix_h
#include <string.h> //for memcpy
#include <CoreGraphics/CGAffineTransform.h>
#include <QuartzCore/QuartzCore.h>
namespace WebCore {
class TransformationMatrix {
public:
typedef double Matrix4[4][4];
TransformationMatrix() { makeIdentity(); }
TransformationMatrix(const TransformationMatrix& t) { *this = t; }
TransformationMatrix(double a, double b, double c, double d, double e, double f) { setMatrix(a, b, c, d, e, f); }
TransformationMatrix(double m11, double m12, double m13, double m14,
double m21, double m22, double m23, double m24,
double m31, double m32, double m33, double m34,
double m41, double m42, double m43, double m44)
{
setMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44);
}
void setMatrix(double a, double b, double c, double d, double e, double f)
{
m_matrix[0][0] = a; m_matrix[0][1] = b; m_matrix[0][2] = 0; m_matrix[0][3] = 0;
m_matrix[1][0] = c; m_matrix[1][1] = d; m_matrix[1][2] = 0; m_matrix[1][3] = 0;
m_matrix[2][0] = 0; m_matrix[2][1] = 0; m_matrix[2][2] = 1; m_matrix[2][3] = 0;
m_matrix[3][0] = e; m_matrix[3][1] = f; m_matrix[3][2] = 0; m_matrix[3][3] = 1;
}
void setMatrix(double m11, double m12, double m13, double m14,
double m21, double m22, double m23, double m24,
double m31, double m32, double m33, double m34,
double m41, double m42, double m43, double m44)
{
m_matrix[0][0] = m11; m_matrix[0][1] = m12; m_matrix[0][2] = m13; m_matrix[0][3] = m14;
m_matrix[1][0] = m21; m_matrix[1][1] = m22; m_matrix[1][2] = m23; m_matrix[1][3] = m24;
m_matrix[2][0] = m31; m_matrix[2][1] = m32; m_matrix[2][2] = m33; m_matrix[2][3] = m34;
m_matrix[3][0] = m41; m_matrix[3][1] = m42; m_matrix[3][2] = m43; m_matrix[3][3] = m44;
}
TransformationMatrix& operator =(const TransformationMatrix &t)
{
setMatrix(t.m_matrix);
return *this;
}
TransformationMatrix& makeIdentity()
{
setMatrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
return *this;
}
bool isIdentity() const
{
return m_matrix[0][0] == 1 && m_matrix[0][1] == 0 && m_matrix[0][2] == 0 && m_matrix[0][3] == 0 &&
m_matrix[1][0] == 0 && m_matrix[1][1] == 1 && m_matrix[1][2] == 0 && m_matrix[1][3] == 0 &&
m_matrix[2][0] == 0 && m_matrix[2][1] == 0 && m_matrix[2][2] == 1 && m_matrix[2][3] == 0 &&
m_matrix[3][0] == 0 && m_matrix[3][1] == 0 && m_matrix[3][2] == 0 && m_matrix[3][3] == 1;
}
// This form preserves the double math from input to output
void map(double x, double y, double& x2, double& y2) const { multVecMatrix(x, y, x2, y2); }
double m11() const { return m_matrix[0][0]; }
void setM11(double f) { m_matrix[0][0] = f; }
double m12() const { return m_matrix[0][1]; }
void setM12(double f) { m_matrix[0][1] = f; }
double m13() const { return m_matrix[0][2]; }
void setM13(double f) { m_matrix[0][2] = f; }
double m14() const { return m_matrix[0][3]; }
void setM14(double f) { m_matrix[0][3] = f; }
double m21() const { return m_matrix[1][0]; }
void setM21(double f) { m_matrix[1][0] = f; }
double m22() const { return m_matrix[1][1]; }
void setM22(double f) { m_matrix[1][1] = f; }
double m23() const { return m_matrix[1][2]; }
void setM23(double f) { m_matrix[1][2] = f; }
double m24() const { return m_matrix[1][3]; }
void setM24(double f) { m_matrix[1][3] = f; }
double m31() const { return m_matrix[2][0]; }
void setM31(double f) { m_matrix[2][0] = f; }
double m32() const { return m_matrix[2][1]; }
void setM32(double f) { m_matrix[2][1] = f; }
double m33() const { return m_matrix[2][2]; }
void setM33(double f) { m_matrix[2][2] = f; }
double m34() const { return m_matrix[2][3]; }
void setM34(double f) { m_matrix[2][3] = f; }
double m41() const { return m_matrix[3][0]; }
void setM41(double f) { m_matrix[3][0] = f; }
double m42() const { return m_matrix[3][1]; }
void setM42(double f) { m_matrix[3][1] = f; }
double m43() const { return m_matrix[3][2]; }
void setM43(double f) { m_matrix[3][2] = f; }
double m44() const { return m_matrix[3][3]; }
void setM44(double f) { m_matrix[3][3] = f; }
double a() const { return m_matrix[0][0]; }
void setA(double a) { m_matrix[0][0] = a; }
double b() const { return m_matrix[0][1]; }
void setB(double b) { m_matrix[0][1] = b; }
double c() const { return m_matrix[1][0]; }
void setC(double c) { m_matrix[1][0] = c; }
double d() const { return m_matrix[1][1]; }
void setD(double d) { m_matrix[1][1] = d; }
double e() const { return m_matrix[3][0]; }
void setE(double e) { m_matrix[3][0] = e; }
double f() const { return m_matrix[3][1]; }
void setF(double f) { m_matrix[3][1] = f; }
// this = this * mat
TransformationMatrix& multiply(const TransformationMatrix&);
TransformationMatrix& scale(double);
TransformationMatrix& scaleNonUniform(double sx, double sy);
TransformationMatrix& scale3d(double sx, double sy, double sz);
TransformationMatrix& rotate(double d) { return rotate3d(0, 0, d); }
TransformationMatrix& rotateFromVector(double x, double y);
TransformationMatrix& rotate3d(double rx, double ry, double rz);
// The vector (x,y,z) is normalized if it's not already. A vector of
// (0,0,0) uses a vector of (0,0,1).
TransformationMatrix& rotate3d(double x, double y, double z, double angle);
TransformationMatrix& translate(double tx, double ty);
TransformationMatrix& translate3d(double tx, double ty, double tz);
// translation added with a post-multiply
TransformationMatrix& translateRight(double tx, double ty);
TransformationMatrix& translateRight3d(double tx, double ty, double tz);
TransformationMatrix& flipX();
TransformationMatrix& flipY();
TransformationMatrix& skew(double angleX, double angleY);
TransformationMatrix& skewX(double angle) { return skew(angle, 0); }
TransformationMatrix& skewY(double angle) { return skew(0, angle); }
TransformationMatrix& applyPerspective(double p);
bool hasPerspective() const { return m_matrix[2][3] != 0.0f; }
bool isInvertible() const;
// This method returns the identity matrix if it is not invertible.
// Use isInvertible() before calling this if you need to know.
TransformationMatrix inverse() const;
// decompose the matrix into its component parts
typedef struct {
double scaleX, scaleY, scaleZ;
double skewXY, skewXZ, skewYZ;
double rotateX, rotateY, rotateZ;
double quaternionX, quaternionY, quaternionZ, quaternionW;
double translateX, translateY, translateZ;
double perspectiveX, perspectiveY, perspectiveZ, perspectiveW;
} DecomposedType;
bool decompose(DecomposedType& decomp) const;
void recompose(const DecomposedType& decomp, bool useEulerAngle = false);
void blend(const TransformationMatrix& from, double progress);
bool isAffine() const
{
return (m13() == 0 && m14() == 0 && m23() == 0 && m24() == 0 &&
m31() == 0 && m32() == 0 && m33() == 1 && m34() == 0 && m43() == 0 && m44() == 1);
}
// Throw away the non-affine parts of the matrix (lossy!)
void makeAffine();
bool operator==(const TransformationMatrix& m2) const
{
return (m_matrix[0][0] == m2.m_matrix[0][0] &&
m_matrix[0][1] == m2.m_matrix[0][1] &&
m_matrix[0][2] == m2.m_matrix[0][2] &&
m_matrix[0][3] == m2.m_matrix[0][3] &&
m_matrix[1][0] == m2.m_matrix[1][0] &&
m_matrix[1][1] == m2.m_matrix[1][1] &&
m_matrix[1][2] == m2.m_matrix[1][2] &&
m_matrix[1][3] == m2.m_matrix[1][3] &&
m_matrix[2][0] == m2.m_matrix[2][0] &&
m_matrix[2][1] == m2.m_matrix[2][1] &&
m_matrix[2][2] == m2.m_matrix[2][2] &&
m_matrix[2][3] == m2.m_matrix[2][3] &&
m_matrix[3][0] == m2.m_matrix[3][0] &&
m_matrix[3][1] == m2.m_matrix[3][1] &&
m_matrix[3][2] == m2.m_matrix[3][2] &&
m_matrix[3][3] == m2.m_matrix[3][3]);
}
bool operator!=(const TransformationMatrix& other) const { return !(*this == other); }
// *this = *this * t (i.e., a multRight)
TransformationMatrix& operator*=(const TransformationMatrix& t)
{
return multiply(t);
}
// result = *this * t (i.e., a multRight)
TransformationMatrix operator*(const TransformationMatrix& t)
{
TransformationMatrix result = *this;
result.multiply(t);
return result;
}
CATransform3D transform3d () const;
CGAffineTransform affineTransform () const;
TransformationMatrix(const CATransform3D&);
operator CATransform3D() const;
TransformationMatrix(const CGAffineTransform&);
operator CGAffineTransform() const;
private:
// multiply passed 2D point by matrix (assume z=0)
void multVecMatrix(double x, double y, double& dstX, double& dstY) const;
// multiply passed 3D point by matrix
void multVecMatrix(double x, double y, double z, double& dstX, double& dstY, double& dstZ) const;
void setMatrix(const Matrix4 m)
{
if (m && m != m_matrix)
memcpy(m_matrix, m, sizeof(Matrix4));
}
bool isIdentityOrTranslation() const
{
return m_matrix[0][0] == 1 && m_matrix[0][1] == 0 && m_matrix[0][2] == 0 && m_matrix[0][3] == 0 &&
m_matrix[1][0] == 0 && m_matrix[1][1] == 1 && m_matrix[1][2] == 0 && m_matrix[1][3] == 0 &&
m_matrix[2][0] == 0 && m_matrix[2][1] == 0 && m_matrix[2][2] == 1 && m_matrix[2][3] == 0 &&
m_matrix[3][3] == 1;
}
Matrix4 m_matrix;
};
} // namespace WebCore
#endif // TransformationMatrix_h
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2008 Apple Inc. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. OR
* CONTRIBUTORS 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.
*/
#ifndef UnitBezier_h
#define UnitBezier_h
#include <math.h>
namespace WebCore {
struct UnitBezier {
UnitBezier(double p1x, double p1y, double p2x, double p2y)
{
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
cx = 3.0 * p1x;
bx = 3.0 * (p2x - p1x) - cx;
ax = 1.0 - cx -bx;
cy = 3.0 * p1y;
by = 3.0 * (p2y - p1y) - cy;
ay = 1.0 - cy - by;
}
double sampleCurveX(double t)
{
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
}
double sampleCurveY(double t)
{
return ((ay * t + by) * t + cy) * t;
}
double sampleCurveDerivativeX(double t)
{
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
// Given an x value, find a parametric value it came from.
double solveCurveX(double x, double epsilon)
{
double t0;
double t1;
double t2;
double x2;
double d2;
int i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++) {
x2 = sampleCurveX(t2) - x;
if (fabs (x2) < epsilon)
return t2;
d2 = sampleCurveDerivativeX(t2);
if (fabs(d2) < 1e-6)
break;
t2 = t2 - x2 / d2;
}
// Fall back to the bisection method for reliability.
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0)
return t0;
if (t2 > t1)
return t1;
while (t0 < t1) {
x2 = sampleCurveX(t2);
if (fabs(x2 - x) < epsilon)
return t2;
if (x > x2)
t0 = t2;
else
t1 = t2;
t2 = (t1 - t0) * .5 + t0;
}
// Failure.
return t2;
}
double solve(double x, double epsilon)
{
return sampleCurveY(solveCurveX(x, epsilon));
}
private:
double ax;
double bx;
double cx;
double ay;
double by;
double cy;
};
}
#endif
@@ -0,0 +1,149 @@
//
// EmptyContentView.swift
// iMarket
//
// Created by on 2023/9/1.
//
import UIKit
import SnapKit
//View
struct EmptyConstant {
//MARK:
var coverImage: String = "empty_cover"
//
var imageOffsetY: CGFloat = 120.0
//
var imageTextMargin: CGFloat = 12.0
//MARK:
var describe: String = "暂无数据~"
//
var describeColor: UIColor = .subTextColor
//
var describeOffsetX: CGFloat = 0.0
//
var buttonTextMargin: CGFloat = 24.0
//title
var buttonName = "重新加载"
//
var buttonType: EmptyActionType = .refresh
//
var buttonHeight: CGFloat = 32.0
//
var buttonTextColor: UIColor = .themColor
}
extension EmptyContentView {
//
func show(delegate: MktEmptyProtocol, constant: EmptyConstant = EmptyConstant(), isCustom: Bool = false) -> EmptyContentView {
self.delegate = delegate
self.constant = constant
if RequestManager.isNetworkConnect == false {
self.constant.coverImage = "empty_cover"
self.constant.describe = "数据加载失败,点击重试~"
self.constant.buttonName = "检查网络"
self.constant.buttonType = .checkNetwork
} else if !Mkt.APP.isLogin {
self.constant.coverImage = "empty_cover"
self.constant.describe = "未登录,点击去登录~"
self.constant.buttonName = "去登录"
self.constant.buttonType = .goLogin
} else if isCustom {
self.constant.coverImage = "empty_cover"
self.constant.describe = "自定义描述~"
self.constant.buttonName = "自定义按钮"
self.constant.buttonType = .refresh
} else {
self.constant.coverImage = "empty_cover"
self.constant.describe = "暂无数据~"
self.constant.buttonName = "重新获取"
self.constant.buttonType = .refresh
}
return EmptyContentView().commonInit()
}
}
class EmptyContentView: UIView {
private var constant = EmptyConstant()
//
private var delegate: MktEmptyProtocol?
private func commonInit() -> EmptyContentView {
self.addSubview(self.coverImg)
self.coverImg.snp.makeConstraints { make in
make.centerY.equalTo(self).offset(-self.constant.imageOffsetY)
make.centerX.equalTo(self)
}
self.addSubview(self.descLabel)
self.descLabel.snp.makeConstraints { make in
make.top.equalTo(self.coverImg.snp.bottom).offset(self.constant.imageTextMargin)
make.left.equalTo(16 + self.constant.describeOffsetX)
make.right.equalTo(-16)
}
self.addSubview(self.actionButton)
self.actionButton.snp.makeConstraints { make in
make.top.equalTo(self.descLabel.snp.bottom).offset(self.constant.buttonTextMargin)
make.centerX.equalTo(self)
make.width.equalTo(80)
make.height.equalTo(self.constant.buttonHeight)
}
return self
}
public override func layoutSubviews() {
super.layoutSubviews()
let button_text = self.constant.buttonName
var width = Mkt.labelWithWidth(text: button_text, font: .systemFont(ofSize: 14)) + 32
if width > Mkt.screenWidth - 32 {
width = Mkt.screenWidth - 32
}
self.actionButton.snp.updateConstraints { make in
make.width.equalTo(width)
}
}
//
@objc func clickButtonAction() {
switch self.constant.buttonType {
case .goLogin:
Mkt.APP.pushLoginViewController()
case .checkNetwork:
Mkt.openWifi()
case .refresh:
self.delegate?.didReloadData()
}
}
//
lazy var coverImg: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: self.constant.coverImage)
return imageView
}()
//
lazy var descLabel: UILabel = {
let descLabel = UILabel()
descLabel.text = self.constant.describe
descLabel.textColor = .subTextColor
descLabel.textAlignment = .center
descLabel.font = .systemFont(ofSize: 14)
descLabel.numberOfLines = 4
descLabel.clipsToBounds = true
return descLabel
}()
//
lazy var actionButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle(self.constant.buttonName, for: .normal)
button.setTitleColor(self.constant.buttonTextColor, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14)
button.layer.borderColor = self.constant.buttonTextColor.cgColor
button.layer.borderWidth = 1.0
button.layer.cornerRadius = self.constant.buttonHeight/2.0
button.layer.masksToBounds = false
button.addTarget(self, action: #selector(clickButtonAction), for: .touchUpInside)
return button
}()
}
@@ -0,0 +1,119 @@
//
// MktTableViewProtocol.swift
// iMarket
//
// Created by on 2023/9/1.
//
import Foundation
import UIKit
extension UITableView {
/// 使UITableViewreloadData
/// - Parameter mkt: MktTableViewProtocol
func mkt_reloadData(_ mkt: any MktTableViewProtocol) {
mkt.reloadTableViewData()
}
}
extension UICollectionView {
/// 使UICollectionViewreloadData
/// - Parameter mkt: MktCollectionViewProtocol
func mkt_reloadData(_ mkt: any MktCollectionViewProtocol) {
mkt.reloadCollectionViewData()
}
}
public protocol MktEmptyProtocol {
//View
func makeEmptyView() -> UIView?
//View
func enableScrollToEmpryView() -> Bool
//
func didReloadData()
}
public protocol MktTableViewProtocol: MktEmptyProtocol {
//
func numberInTableView() -> Int
//tableview
func makeTableView() -> UITableView
//tableView
func reloadTableViewData()
}
//
extension MktTableViewProtocol {
//
func numberInTableView() -> Int {
return 0
}
//
func reloadTableViewData() {
//tableView
let tableView = self.makeTableView()
//tableView
tableView.isScrollEnabled = self.enableScrollToEmpryView()
//tableView
tableView.reloadData()
//
guard let emptyView = self.makeEmptyView() else { return }
if self.numberInTableView() == 0 && !tableView.isDescendant(of: emptyView) {
emptyView.frame = tableView.frame
tableView.addSubview(emptyView)
return
}
emptyView.removeFromSuperview()
}
//
func enableScrollToEmpryView() -> Bool {
return true
}
}
public protocol MktCollectionViewProtocol: MktEmptyProtocol {
//
func numberInCollectionView() -> Int
//collectionview
func makeCollectionView() -> UICollectionView
//collectionView
func reloadCollectionViewData()
}
extension MktCollectionViewProtocol {
func numberInCollectionView() -> Int {
return 0
}
func reloadCollectionViewData() {
let collectionView = self.makeCollectionView()
collectionView.isScrollEnabled = self.enableScrollToEmpryView()
collectionView.reloadData()
guard let emptyView = self.makeEmptyView() else { return }
if self.numberInCollectionView() == 0 && !collectionView.isDescendant(of: emptyView) {
emptyView.frame = collectionView.frame
collectionView.addSubview(emptyView)
return
}
emptyView.removeFromSuperview()
}
//
func enableScrollToEmpryView() -> Bool {
return true
}
}
//
public enum EmptyActionType: Int {
case goLogin = 0
case checkNetwork = 1
case refresh = 2
}
@@ -0,0 +1,62 @@
//
// RefreshScrollView.swift
// iMarket
//
// Created by on 2023/9/1.
//
import Foundation
import UIKit
import MJRefresh
//
extension UIScrollView {
func addPullDownRefresh(_ refreshHandler: @escaping () -> Void) {
if self.mj_header == nil {
let header = MJRefreshNormalHeader.init(refreshingBlock: refreshHandler)
header.lastUpdatedTimeLabel?.isHidden = false
header.stateLabel?.font = UIFont.systemFont(ofSize: 14)
header.stateLabel?.textColor = .textColor
header.loadingView?.color = .textColor
self.mj_header = header
} else {
self.mj_header?.refreshingBlock = refreshHandler
}
}
func addPullUpMore(_ moreHandler: @escaping () -> Void) {
if self.mj_footer == nil {
let footer = MJRefreshAutoNormalFooter.init(refreshingBlock: moreHandler)
footer.stateLabel?.font = UIFont.systemFont(ofSize: 14)
footer.setTitle("", for: .idle)
footer.setTitle("- 已经到底了 -", for: .noMoreData)
footer.stateLabel?.textColor = .textColor
footer.loadingView?.color = .textColor
footer.isRefreshingTitleHidden = true
self.mj_footer = footer
} else {
self.mj_footer?.refreshingBlock = moreHandler
}
}
func beginPullDownRefresh() {
self.mj_header?.beginRefreshing()
}
func endPullDownRefresh() {
if self.mj_header?.isRefreshing ?? false {
self.mj_header?.endRefreshing()
}
}
func endPullUpMore(_ hasMore: Bool) {
if self.mj_footer?.isRefreshing ?? false {
if hasMore {
self.mj_footer?.resetNoMoreData()
self.mj_footer?.endRefreshing()
} else {
self.mj_footer?.endRefreshingWithNoMoreData()
}
}
}
}
@@ -0,0 +1,220 @@
//
// FlexibleDecoder.swift
// HealthEmergency
//
// JSON String Int Double
// id Int String
//
// Swift JSONDecoder Int? JSON "123"
// typeMismatch使 optional
// decode
//
import Foundation
// MARK: -
enum FlexibleDecoder {
static func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
let json = try JSONSerialization.jsonObject(with: data, options: [])
return try T(from: _Decoder(value: json, codingPath: []))
}
}
// MARK: - _Decoder
private final class _Decoder: Decoder {
let value: Any
var codingPath: [CodingKey]
var userInfo: [CodingUserInfoKey: Any] = [:]
init(value: Any, codingPath: [CodingKey]) {
self.value = value
self.codingPath = codingPath
}
func container<Key: CodingKey>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard let dict = value as? [String: Any] else {
throw DecodingError.typeMismatch([String: Any].self,
.init(codingPath: codingPath, debugDescription: "Expected keyed container"))
}
return KeyedDecodingContainer(_KeyedContainer<Key>(dict: dict, codingPath: codingPath))
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard let arr = value as? [Any] else {
throw DecodingError.typeMismatch([Any].self,
.init(codingPath: codingPath, debugDescription: "Expected unkeyed container"))
}
return _UnkeyedContainer(array: arr, codingPath: codingPath)
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
return _SingleValueContainer(value: value, codingPath: codingPath)
}
}
// MARK: -
private func flexString(_ v: Any) -> String? {
if let s = v as? String { return s }
if let n = v as? NSNumber {
let d = n.doubleValue
return d.truncatingRemainder(dividingBy: 1) == 0 ? String(n.intValue) : String(d)
}
return nil
}
private func flexInt(_ v: Any) -> Int? {
if let n = v as? Int { return n }
if let n = v as? NSNumber { return n.intValue }
if let s = v as? String { return Int(s) }
return nil
}
private func flexDouble(_ v: Any) -> Double? {
if let n = v as? Double { return n }
if let n = v as? NSNumber { return n.doubleValue }
if let s = v as? String { return Double(s) }
return nil
}
private func flexBool(_ v: Any) -> Bool? {
if let b = v as? Bool { return b }
if let n = v as? NSNumber { return n.boolValue }
if let s = v as? String { return s == "true" || s == "1" }
return nil
}
private func mismatch<T>(_ t: T.Type, path: [CodingKey], key: CodingKey? = nil) -> DecodingError {
var p = path; if let k = key { p.append(k) }
return DecodingError.typeMismatch(t, .init(codingPath: p, debugDescription: "Cannot convert to \(t)"))
}
// MARK: - _KeyedContainer
private struct _KeyedContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
let dict: [String: Any]
var codingPath: [CodingKey]
var allKeys: [K] { dict.keys.compactMap { K(stringValue: $0) } }
func contains(_ key: K) -> Bool { dict[key.stringValue] != nil }
func decodeNil(forKey key: K) throws -> Bool {
guard let v = dict[key.stringValue] else { return true }
return v is NSNull
}
private func raw(_ key: K) throws -> Any {
guard let v = dict[key.stringValue], !(v is NSNull) else {
throw DecodingError.keyNotFound(key,
.init(codingPath: codingPath, debugDescription: "Key '\(key.stringValue)' not found or null"))
}
return v
}
func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { guard let v = flexBool(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return v }
func decode(_ type: String.Type, forKey key: K) throws -> String { guard let v = flexString(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return v }
func decode(_ type: Double.Type, forKey key: K) throws -> Double { guard let v = flexDouble(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return v }
func decode(_ type: Float.Type, forKey key: K) throws -> Float { guard let v = flexDouble(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return Float(v) }
func decode(_ type: Int.Type, forKey key: K) throws -> Int { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return v }
func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return Int8(v) }
func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return Int16(v) }
func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return Int32(v) }
func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return Int64(v) }
func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return UInt(v) }
func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return UInt8(v) }
func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return UInt16(v) }
func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return UInt32(v) }
func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { guard let v = flexInt(try raw(key)) else { throw mismatch(type, path: codingPath, key: key) }; return UInt64(v) }
func decode<T: Decodable>(_ type: T.Type, forKey key: K) throws -> T {
let v = try raw(key)
return try T(from: _Decoder(value: v, codingPath: codingPath + [key]))
}
func nestedContainer<NK: CodingKey>(keyedBy type: NK.Type, forKey key: K) throws -> KeyedDecodingContainer<NK> {
let v = try raw(key)
return try _Decoder(value: v, codingPath: codingPath + [key]).container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
let v = try raw(key)
return try _Decoder(value: v, codingPath: codingPath + [key]).unkeyedContainer()
}
func superDecoder() throws -> Decoder { _Decoder(value: dict, codingPath: codingPath) }
func superDecoder(forKey key: K) throws -> Decoder {
_Decoder(value: (dict[key.stringValue] ?? NSNull()), codingPath: codingPath + [key])
}
}
// MARK: - _UnkeyedContainer
private struct _UnkeyedContainer: UnkeyedDecodingContainer {
let array: [Any]
var codingPath: [CodingKey]
var count: Int? { array.count }
var isAtEnd: Bool { currentIndex >= array.count }
var currentIndex: Int = 0
private mutating func next() throws -> Any {
guard !isAtEnd else {
throw DecodingError.valueNotFound(Any.self,
.init(codingPath: codingPath, debugDescription: "Unkeyed container is at end"))
}
let v = array[currentIndex]; currentIndex += 1; return v
}
mutating func decodeNil() throws -> Bool {
if array[currentIndex] is NSNull { currentIndex += 1; return true }
return false
}
mutating func decode(_ type: Bool.Type) throws -> Bool { guard let v = flexBool(try next()) else { throw mismatch(type, path: codingPath) }; return v }
mutating func decode(_ type: String.Type) throws -> String { guard let v = flexString(try next()) else { throw mismatch(type, path: codingPath) }; return v }
mutating func decode(_ type: Double.Type) throws -> Double { guard let v = flexDouble(try next()) else { throw mismatch(type, path: codingPath) }; return v }
mutating func decode(_ type: Float.Type) throws -> Float { guard let v = flexDouble(try next()) else { throw mismatch(type, path: codingPath) }; return Float(v) }
mutating func decode(_ type: Int.Type) throws -> Int { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return v }
mutating func decode(_ type: Int8.Type) throws -> Int8 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return Int8(v) }
mutating func decode(_ type: Int16.Type) throws -> Int16 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return Int16(v) }
mutating func decode(_ type: Int32.Type) throws -> Int32 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return Int32(v) }
mutating func decode(_ type: Int64.Type) throws -> Int64 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return Int64(v) }
mutating func decode(_ type: UInt.Type) throws -> UInt { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return UInt(v) }
mutating func decode(_ type: UInt8.Type) throws -> UInt8 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return UInt8(v) }
mutating func decode(_ type: UInt16.Type) throws -> UInt16 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return UInt16(v) }
mutating func decode(_ type: UInt32.Type) throws -> UInt32 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return UInt32(v) }
mutating func decode(_ type: UInt64.Type) throws -> UInt64 { guard let v = flexInt(try next()) else { throw mismatch(type, path: codingPath) }; return UInt64(v) }
mutating func decode<T: Decodable>(_ type: T.Type) throws -> T {
let v = try next()
return try T(from: _Decoder(value: v, codingPath: codingPath))
}
mutating func nestedContainer<NK: CodingKey>(keyedBy type: NK.Type) throws -> KeyedDecodingContainer<NK> {
try _Decoder(value: try next(), codingPath: codingPath).container(keyedBy: type)
}
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
try _Decoder(value: try next(), codingPath: codingPath).unkeyedContainer()
}
mutating func superDecoder() throws -> Decoder {
_Decoder(value: try next(), codingPath: codingPath)
}
}
// MARK: - _SingleValueContainer
private struct _SingleValueContainer: SingleValueDecodingContainer {
let value: Any
var codingPath: [CodingKey]
func decodeNil() -> Bool { value is NSNull }
func decode(_ type: Bool.Type) throws -> Bool { guard let v = flexBool(value) else { throw mismatch(type, path: codingPath) }; return v }
func decode(_ type: String.Type) throws -> String { guard let v = flexString(value) else { throw mismatch(type, path: codingPath) }; return v }
func decode(_ type: Double.Type) throws -> Double { guard let v = flexDouble(value) else { throw mismatch(type, path: codingPath) }; return v }
func decode(_ type: Float.Type) throws -> Float { guard let v = flexDouble(value) else { throw mismatch(type, path: codingPath) }; return Float(v) }
func decode(_ type: Int.Type) throws -> Int { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return v }
func decode(_ type: Int8.Type) throws -> Int8 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return Int8(v) }
func decode(_ type: Int16.Type) throws -> Int16 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return Int16(v) }
func decode(_ type: Int32.Type) throws -> Int32 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return Int32(v) }
func decode(_ type: Int64.Type) throws -> Int64 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return Int64(v) }
func decode(_ type: UInt.Type) throws -> UInt { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return UInt(v) }
func decode(_ type: UInt8.Type) throws -> UInt8 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return UInt8(v) }
func decode(_ type: UInt16.Type) throws -> UInt16 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return UInt16(v) }
func decode(_ type: UInt32.Type) throws -> UInt32 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return UInt32(v) }
func decode(_ type: UInt64.Type) throws -> UInt64 { guard let v = flexInt(value) else { throw mismatch(type, path: codingPath) }; return UInt64(v) }
func decode<T: Decodable>(_ type: T.Type) throws -> T {
try T(from: _Decoder(value: value, codingPath: codingPath))
}
}
@@ -0,0 +1,30 @@
//
// HomeRequestPath.swift
// HealthEmergency
//
// Home
import Foundation
enum HomeRequestPath: RequestPath {
case bannerList
case recommendProducts
case categories
case activities
case search
var path: String {
switch self {
case .bannerList:
return "/api/home/banners"
case .recommendProducts:
return "/api/home/recommend"
case .categories:
return "/api/home/categories"
case .activities:
return "/api/home/activities"
case .search:
return "/api/home/search"
}
}
}
@@ -0,0 +1,36 @@
//
// MarketRequestPath.swift
// HealthEmergency
//
// Market
import Foundation
enum MarketRequestPath: RequestPath {
case productList
case productDetail
case productReviews
case addToCart
case cartList
case removeFromCart
case createOrder
var path: String {
switch self {
case .productList:
return "/api/market/products"
case .productDetail:
return "/api/market/product/detail"
case .productReviews:
return "/api/market/product/reviews"
case .addToCart:
return "/api/market/cart/add"
case .cartList:
return "/api/market/cart/list"
case .removeFromCart:
return "/api/market/cart/remove"
case .createOrder:
return "/api/market/order/create"
}
}
}
@@ -0,0 +1,33 @@
//
// MessageRequestPath.swift
// HealthEmergency
//
// Message
import Foundation
enum MessageRequestPath: RequestPath {
case messageList
case messageDetail
case sendMessage
case deleteMessage
case markAsRead
case notificationList
var path: String {
switch self {
case .messageList:
return "/api/message/list"
case .messageDetail:
return "/api/message/detail"
case .sendMessage:
return "/api/message/send"
case .deleteMessage:
return "/api/message/delete"
case .markAsRead:
return "/api/message/mark-read"
case .notificationList:
return "/api/message/notifications"
}
}
}
@@ -0,0 +1,48 @@
//
// MineRequestPath.swift
// HealthEmergency
//
// Mine
import Foundation
enum MineRequestPath: RequestPath {
case postAvterFile // post
case postAvterImageUrl // post
case getUserPhoneList // get
case addUserPhone // post
case delectUserPhine // post
case setDefaultUserInfo //APP- post
case userVerificationIDCard //APP- post
case verifyPhoneCode //APP- post
case changePasswordVerify // post
case chageePasswordReset // post
case ModifyPasswordOld // post
var path: String {
switch self {
case .postAvterFile:
return "/sys/file/upload"
case .postAvterImageUrl:
return "/sys/sys-user/app/avatar"
case .getUserPhoneList:
return "/sys/sys-user/app/phoneList"
case .addUserPhone:
return "/sys/sys-user/app/phone/add"
case .delectUserPhine:
return "sys/sys-user/app/phone/delete"
case .setDefaultUserInfo:
return "/sys/sys-user/app/phone/setDefault"
case .userVerificationIDCard:
return "/sys/sys-user/app/idCard/verify"
case .verifyPhoneCode:
return "/sys/sys-user/app/phone/verifyCode"
case .changePasswordVerify:
return "/sys/sys-user/app/password/verify"
case .chageePasswordReset:
return "/sys/sys-user/app/password/reset"
case .ModifyPasswordOld:
return "/sys/sys-user/app/password/change"
}
}
}
@@ -0,0 +1,264 @@
//
// NetworkExample.swift
// HealthEmergency
//
//
import Foundation
// MARK: - JSON
/*
============ Home - ============
: GET /api/home/banners
JSON:
{
"code": 200,
"message": "success",
"data": [
{
"id": "banner_001",
"title": "",
"imageUrl": "https://example.com/banner1.jpg",
"linkUrl": "https://example.com/product/spring"
},
{
"id": "banner_002",
"title": "",
"imageUrl": "https://example.com/banner2.jpg",
"linkUrl": "https://example.com/activity/discount"
}
]
}
============ Mine - ============
: GET /api/mine/user/info?userId=user_123
JSON:
{
"code": 200,
"message": "success",
"data": {
"userId": "user_123",
"username": "",
"phone": "13800138000",
"avatar": "https://example.com/avatar/user_123.jpg",
"email": "zhangsan@example.com"
}
}
============ Market - ============
: GET /api/market/products?page=1&pageSize=10
JSON:
{
"code": 200,
"message": "success",
"data": {
"products": [
{
"id": "prod_001",
"name": "iPhone 15",
"price": 5999,
"originalPrice": 6999,
"imageUrl": "https://example.com/product/iphone15.jpg",
"description": "",
"rating": 4.8
}
],
"total": 100,
"page": 1
}
}
============ Message - ============
: GET /api/message/list?page=1&pageSize=20
JSON:
{
"code": 200,
"message": "success",
"data": [
{
"id": "msg_001",
"senderId": "user_456",
"senderName": "",
"senderAvatar": "https://example.com/avatar/user_456.jpg",
"content": "",
"createTime": "2026-03-17 10:30:00",
"isRead": false
}
]
}
============ ============
{
"code": 401,
"message": "",
"data": null
}
{
"code": 404,
"message": "",
"data": null
}
*/
// MARK: - Model
// Home
struct BannerItemModel: Codable {
let id: String
let title: String
let imageUrl: String
let linkUrl: String?
}
// Mine
struct UserInfoModel: Codable {
let userId: String
let username: String
let phone: String
let avatar: String
let email: String?
}
// Market
struct ProductModel: Codable {
let id: String
let name: String
let price: Double
let originalPrice: Double?
let imageUrl: String
let description: String?
let rating: Double?
}
struct ProductListModel: Codable {
let products: [ProductModel]
let total: Int
let page: Int
}
// Message
struct MessageModel: Codable {
let id: String
let senderId: String
let senderName: String
let senderAvatar: String?
let content: String
let createTime: String
let isRead: Bool
}
// MARK: - ViewController 使
/*
// ===== Home ViewController =====
class HomeViewController: UIViewController {
func loadBanners() {
//
RequestTarget.get(HomeRequestPath.bannerList)
.sendParsed(showHUD: false, type: [BannerItemModel].self) { success, banners, message in
if success, let banners = banners {
// code == 200
self.updateUI(with: banners)
} else {
print(": \(message)")
}
} failure: { error in
print(": \(error.localizedDescription)")
}
}
private func updateUI(with banners: [BannerItemModel]) {
// UI
self.bannerList = banners
self.tableView.reloadData()
}
}
// ===== Mine ViewController =====
class MineViewController: UIViewController {
func loadUserInfo() {
let params: [String: Any] = ["userId": UserManager.shared.getUserId() ?? ""]
RequestTarget.get(MineRequestPath.userInfo, params)
.sendParsed(showHUD: false, type: UserInfoModel.self) { success, userInfo, message in
if success, let userInfo = userInfo {
self.userModel = userInfo
self.updateUserUI(userInfo)
} else {
print(": \(message)")
}
} failure: { error in
print(": \(error.localizedDescription)")
}
}
private func updateUserUI(_ user: UserInfoModel) {
// UI
self.titleLabel.text = user.username
// ... UI
}
}
// ===== Market ViewController =====
class MarketViewController: UIViewController {
func loadProducts() {
let params: [String: Any] = ["page": 1, "pageSize": 10]
RequestTarget.get(MarketRequestPath.productList, params)
.sendParsed(showHUD: true, type: ProductListModel.self) { success, productList, message in
if success, let productList = productList {
self.products = productList.products
self.totalCount = productList.total
self.tableView.reloadData()
} else {
print(": \(message)")
}
} failure: { error in
print(": \(error.localizedDescription)")
}
}
}
// ===== Message ViewController =====
class MessageViewController: UIViewController {
func loadMessages() {
let params: [String: Any] = ["page": 1, "pageSize": 20]
RequestTarget.get(MessageRequestPath.messageList, params)
.sendParsed(showHUD: true, type: [MessageModel].self) { success, messages, message in
if success, let messages = messages {
self.messageList = messages
self.tableView.reloadData()
} else {
print(": \(message)")
}
} failure: { error in
print(": \(error.localizedDescription)")
}
}
}
*/
@@ -0,0 +1,78 @@
//
// NetworkResponse.swift
// HealthEmergency
//
//
import Foundation
// MARK: -
struct EmptyData: Codable {}
// MARK: -
class NetworkParser {
/// JSON Model
static func parse<T: Codable>(_ jsonString: String, to type: T.Type) -> T? {
guard let data = jsonString.data(using: .utf8) else { return nil }
return try? FlexibleDecoder.decode(T.self, from: data)
}
/// Model
static func parse<T: Codable>(_ dict: [String: Any], to type: T.Type) -> T? {
guard let jsonData = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
return try? FlexibleDecoder.decode(T.self, from: jsonData)
}
/// code == "00000" data
///
/// data
/// 1. [String: Any] -
/// 2. [[String: Any]] -
/// 3. String -
/// 4. Int/Double -
/// 5. null / -
static func parseResponse<T: Codable>(_ dict: [String: Any], to type: T.Type) -> (success: Bool, data: T?, message: String) {
guard let code = dict["code"] as? String else {
return (false, nil, "响应格式错误")
}
let message = dict["msg"] as? String ?? ""
// A0401 500
if ResponseCodeHandler.handle(code, message: message) != nil {
return (false, nil, message)
}
// code == "00000" data
// 1data
if let dataDict = dict["data"] as? [String: Any],
let result = parse(dataDict, to: type) {
return (true, result, message)
}
// 2data
if let dataArray = dict["data"] as? [[String: Any]],
let jsonData = try? JSONSerialization.data(withJSONObject: dataArray),
let result = try? FlexibleDecoder.decode(type, from: jsonData) {
return (true, result, message)
}
// 3data
if let dataString = dict["data"] as? String,
let jsonData = dataString.data(using: .utf8),
let result = try? FlexibleDecoder.decode(type, from: jsonData) {
return (true, result, message)
}
// 4data Int / Double T
if let data = dict["data"] as? T {
return (true, data, message)
}
// 5data null
return (true, nil, message)
}
}
@@ -0,0 +1,279 @@
//
// RequestManager.swift
// iMarket
//
// Created by on 2023/8/31.
//
import Foundation
import Alamofire
import Moya
//Code
enum ResponseCode: String {
case success = "00000" //
case requestFail = "99999" //
case systemError = "A0500" //
case frontendHidden = "A0501" //
case paramError = "A0400" //
case tokenExpired = "A0401" // token
case paramEmpty = "A0402" //
case notFound = "A0404" //
case noneNetwork = "-999" //
case dataParseFail = "-666" //
}
struct RequestManager {
static let shared = RequestManager()
//
private init() {
let configuration = URLSessionConfiguration.default
configuration.headers = .default
configuration.timeoutIntervalForRequest = Constant.timeout
let session = Session.init(configuration: configuration, startRequestsImmediately: false)
self.provider = MoyaProvider<RequestTarget>(session: session, plugins: [Plugin()])
}
private var provider: MoyaProvider<RequestTarget>
struct Plugin { }
/// BasicModule/Configuration/NetworkConfig.swift
private struct Constant {
static let timeout: Double = 30
static let requestErrorDomain = "com.iOS.mkt.requestError"
struct HeaderFieldKey {
static let Authorization = "Authorization"
}
}
}
extension RequestManager {
static var isNetworkConnect: Bool {
let network = NetworkReachabilityManager()
return network?.isReachable ?? true //
}
func httpHeader() -> [String: String] {
// token tokenName: tokenValue
var header: [String: String] = [
Constant.HeaderFieldKey.Authorization: "",
]
let tokenHeader = UserManager.shared.authHeader()
header.merge(tokenHeader) { _, new in new }
return header
}
func parameters() -> [String: Any] {
//
var param: [String: String] = [:]
return param
}
}
extension RequestManager.Plugin: PluginType {
//header
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
//
#if DEBUG
print("========================================")
if let _ = request.httpBody {
let content = "URL: \(request.url!)" + "\n" + "Method: \(request.httpMethod ?? "")" + "\n" + "Body: " + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")"
print("\(content)")
} else {
let content = "URL: \(request.url!)" + "\n" + "Method: \(request.httpMethod ?? "")"
print("\(content)")
}
if let headerView = request.allHTTPHeaderFields {
print("Header: \(headerView)")
}
print("========================================")
#endif
return request
}
}
//request
extension RequestManager {
@discardableResult
/// BlockData
/// - Parameters:
/// - request:
/// - isShow: HUD
/// - success:
/// - failure:
/// - Returns: Task()
func request(_ request: RequestTarget,
showHUD isShow: Bool = true,
success: @escaping CompletionCallback,
failure: ErrorCallback?) -> Cancellable? {
guard RequestManager.isNetworkConnect == true else {
DispatchQueue.main {
Mkt.makeToast("网络连接失败,请检查网络")
}
let domain = Bundle.main.bundleIdentifier ?? Constant.requestErrorDomain
let error = NSError.init(domain: domain, code: -999)
failure?(error)
return nil
}
if isShow {
ProgressHUD.show("加载中...", interaction: false)
}
let task = self.provider.request(request) { result in
if isShow {
// 使 remove() dismiss() alpha
ProgressHUD.remove()
}
switch result {
case let .success(response):
#if DEBUG
let jsonData = Mkt.dataToDictionary(response.data)
dlog(message: "返回结果是:\(prettyJSON(jsonData as Any))")
#endif
// HTTP 401 Token A0401 Toast +
if response.statusCode == 401 {
_ = ResponseCodeHandler.handle("A0401", message: "")
let domain = Bundle.main.bundleIdentifier ?? Constant.requestErrorDomain
failure?(NSError(domain: domain, code: 401))
return
}
success(response.data)
case let .failure(error as NSError):
DispatchQueue.main {
if error.code == NSURLErrorTimedOut {
Mkt.makeToast("请求超时,请检查网络")
} else if error.code == NSURLErrorNotConnectedToInternet
|| error.code == NSURLErrorNetworkConnectionLost {
Mkt.makeToast("网络连接失败,请检查网络")
} else {
Mkt.makeToast("服务器开小差啦,请稍候重试~")
}
}
failure?(error)
}
}
return task
}
@discardableResult
/// Block
/// - Parameters:
/// - request:
/// - type:
/// - isShow: HUD
/// - successHandler:
/// - failureHandler:
/// - Returns: Task()
func request<T: Codable>(_ request: RequestTarget,
type: T.Type,
showHUD isShow: Bool = true,
successHandler: @escaping ElementCallback<T>,
failureHandler: ErrorCallback?) -> Cancellable? {
let task = self.request(request, showHUD: isShow) { data in
if let model = Mkt.jsonToModel(Response<T>.self, data) {
let code = model.retCode
let message = model.retMsg
if let error = self.filterResponseCode(code, message: message) {
failureHandler?(error)
} else {
successHandler(model)
}
} else {
DispatchQueue.main {
Mkt.makeToast("数据解析失败")
}
let domain = Bundle.main.bundleIdentifier ?? Constant.requestErrorDomain
let error = NSError.init(domain: domain, code: -666)
failureHandler?(error)
}
} failure: { error in
failureHandler?(error)
}
return task
}
@discardableResult
/// Block
/// - Parameters:
/// - request:
/// - isShow: HUD
/// - success:
/// - failure:
/// - Returns: Task()
func dictionaryRequest(_ request: RequestTarget, showHUD isShow: Bool = true, success: @escaping DictionaryCallback, failure: ErrorCallback?) -> Cancellable? {
self.request(request, showHUD: isShow, success: { data in
let responseDic = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let responseDic = responseDic {
//
if let code = responseDic["code"] as? String {
if ResponseCodeHandler.handle(code, message: responseDic["msg"] as? String ?? "") != nil {
//
failure?(NSError(domain: "com.response.error", code: -1))
return
}
} else {
// code 404/503
Mkt.makeToast("服务器开小差啦,请稍候重试~")
failure?(NSError(domain: "com.response.error", code: -500))
return
}
success(responseDic)
} else {
DispatchQueue.main {
#if DEBUG
Mkt.makeToast("数据解析失败")
#else
Mkt.makeToast("网络连接失败,请检查网络")
#endif
}
let domain = Bundle.main.bundleIdentifier ?? Constant.requestErrorDomain
let error = NSError.init(domain: domain, code: -666)
failure?(error)
}
}, failure: failure)
}
@discardableResult
/// BlockJSON
/// - Parameters:
/// - request:
/// - isShow: HUD
/// - success:
/// - failure:
/// - Returns: Task()
func stringRequest(_ request: RequestTarget, showHUD isShow: Bool = true, success: @escaping StringCallback, failure: ErrorCallback?) -> Cancellable? {
self.request(request, showHUD: isShow, success: { data in
if let result = String(data: data, encoding: .utf8) {
success(result)
} else {
DispatchQueue.main {
#if DEBUG
Mkt.makeToast("数据解析失败")
#else
Mkt.makeToast("网络连接失败,请检查网络")
#endif
}
let domain = Bundle.main.bundleIdentifier ?? Constant.requestErrorDomain
let error = NSError.init(domain: domain, code: -666)
failure?(error)
}
}, failure: failure)
}
}
extension RequestManager {
///
/// - Parameters:
/// - code:
/// - message:
/// - Returns: nil
private func filterResponseCode(_ code: String, message: String) -> NSError? {
return ResponseCodeHandler.handle(code, message: message)
}
}
@@ -0,0 +1,261 @@
//
// RequestTarget.swift
// iMarket
//
// Created by on 2023/8/31.
//
import Foundation
import Alamofire
import Moya
///
typealias ErrorCallback = ((_ error: Swift.Error) -> Void)
//callback
typealias ElementCallback<T: Codable> = ((_ response: Response<T>) -> Void)
//Datacallback
typealias CompletionCallback = ((_ data: Data) -> Void)
//callback
typealias DictionaryCallback = ((_ response: NSDictionary) -> Void)
//json callback
typealias StringCallback = ((_ response: String) -> Void)
//
struct Response<T: Codable>: Codable {
var retCode: String = "00000"
var retMsg: String = ""
var retData: T?
enum CodingKeys: String, CodingKey {
case retCode = "code"
case retMsg = "msg"
case retData = "data"
}
}
//
struct MktNull: Codable {}
//
protocol NetworkTarget: TargetType {
// var headers: [String : String]? { get }
}
//
protocol RequestPath {
var path: String { get }
}
extension NetworkTarget {
var baseURL: URL {
URL(string: NetworkConfig.baseURL)!
}
var headers: [String : String]? {
var headers = RequestManager.shared.httpHeader()
if let tempHeaders = self.headers {
headers.merge(tempHeaders) { _, value in
return value
}
}
return headers
}
}
struct RequestTarget: NetworkTarget {
var path: String
enum Method {
case get
case post
case put
case delete
case uploadFiles([URL])
case uploadFileDatas(([Data]))
/// Data + abtoken
case uploadAvatarData(Data, abtoken: String)
}
var headers: [String : String]?
//
private let requestMethod: Method
//
private let parameters: [String : Any]?
//
var bodyEncoding: ParameterEncoding = JSONEncoding.default
//
init(_ method: Method, _ path: RequestPath, _ parameters: [String : Any]? = nil, _ encoding: ParameterEncoding) {
self.requestMethod = method
self.path = path.path
self.parameters = parameters
self.bodyEncoding = encoding
}
var method: Moya.Method {
var method: Moya.Method
switch requestMethod {
case .get:
method = .get
case .post:
method = .post
case .put:
method = .put
case .delete:
method = .delete
case .uploadFiles, .uploadFileDatas, .uploadAvatarData:
method = .post
}
return method
}
var task: Task {
var task: Task
let parameters: [String : Any] = parameters ?? [:]
switch requestMethod {
case .get:
task = .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .post, .put, .delete:
task = .requestParameters(parameters: parameters, encoding: bodyEncoding)
case .uploadFiles(let files):
let datas: [Moya.MultipartFormData] = files.map { url in
MultipartFormData(provider: .file(url),
name: "file",
fileName: "pictrue.png",
mimeType: "image/jpg/png/jpeg/gif")
}
task = .uploadCompositeMultipart(datas, urlParameters: parameters)
case .uploadFileDatas(let datas):
let datas: [Moya.MultipartFormData] = datas.map { data in
MultipartFormData(provider: .data(data),
name: "file",
fileName: "pictrue.png",
mimeType: "image/jpg/png/jpeg/gif")
}
task = .uploadCompositeMultipart(datas, urlParameters: parameters)
case .uploadAvatarData(let imageData, let abtoken):
// multipart/form-data abtoken file
let tokenField = Moya.MultipartFormData(
provider: .data(abtoken.data(using: .utf8) ?? Data()),
name: "abtoken"
)
let fileField = Moya.MultipartFormData(
provider: .data(imageData),
name: "file",
fileName: "avatar.jpg",
mimeType: "image/jpeg"
)
task = .uploadMultipart([tokenField, fileField])
}
return task
}
}
//
extension RequestTarget {
/// get
/// - Parameters:
/// - path:
/// - query:
/// - Returns:
static func get(_ path: RequestPath, _ query: [String: Any] = [:]) -> Self {
return Self.init(.get, path, query, URLEncoding.queryString)
}
/// post
/// - Parameters:
/// - path:
/// - body:
/// - Returns:
static func post(_ path: RequestPath, _ body: [String: Any] = [:], _ encoding: ParameterEncoding = JSONEncoding.default) -> Self {
return Self.init(.post, path, body, encoding)
}
/// put
/// - Parameters:
/// - path:
/// - body:
/// - Returns:
static func put(_ path: RequestPath, _ body: [String : Any] = [:], _ encoding: ParameterEncoding = JSONEncoding.default) -> Self {
Self.init(.put, path, body, encoding)
}
/// delete
/// - Parameters:
/// - path:
/// - body:
/// - Returns:
static func delete(_ path: RequestPath, _ body: [String : Any] = [:], _ encoding: ParameterEncoding = JSONEncoding.default) -> Self {
Self.init(.delete, path, body, encoding)
}
///
/// - Parameters:
/// - path:
/// - image:
/// - Returns:
static func upload(_ path: RequestPath, _ images: [UIImage]) -> Self {
var datas: [Data] = []
for image in images {
let data = image.jpegData(compressionQuality: 0.1)!
datas.append(data)
}
let upload = Method.uploadFileDatas(datas)
return Self.init(upload, path, nil, JSONEncoding.default)
}
/// URL
/// - Parameters:
/// - path:
/// - files: url
/// - Returns:
static func uploadFiles(_ path: RequestPath, _ files: [URL]) -> Self {
let uploadFileURLs = Method.uploadFiles(files)
return Self.init(uploadFileURLs, path, nil, JSONEncoding.default)
}
/// multipart/form-data abtoken file
/// - Parameters:
/// - path:
/// - image:
/// - Returns:
static func uploadAvatar(_ path: RequestPath, _ image: UIImage) -> Self {
let imageData = image.jpegData(compressionQuality: 0.8) ?? Data()
let token = UserManager.shared.getTokenValue() ?? ""
let method = Method.uploadAvatarData(imageData, abtoken: token)
return Self.init(method, path, nil, JSONEncoding.default)
}
}
//
extension RequestTarget {
/// code == 200
/// - Parameters:
/// - showHUD:
/// - type:
/// - completion: (success, data, message)
/// - failure:
@discardableResult
func sendParsed<T: Codable>(
showHUD: Bool = true,
type: T.Type,
completion: @escaping (_ success: Bool, _ data: T?, _ message: String) -> Void,
failure: ErrorCallback? = nil
) -> Cancellable? {
return RequestManager.shared.dictionaryRequest(self, showHUD: showHUD, success: { dict in
let (success, data, message) = NetworkParser.parseResponse(dict as! [String: Any], to: type)
completion(success, data, message)
}, failure: { error in
failure?(error)
})
}
}
@@ -0,0 +1,73 @@
//
// ResponseCodeHandler.swift
// HealthEmergency
//
//
import Foundation
struct ResponseCodeHandler {
/// nil
static func handle(_ code: String, message: String) -> NSError? {
switch code {
case "00000":
return nil
case "99999":
Mkt.makeToast("请求失败")
return NSError(domain: "com.response.error", code: -999)
case "A0500":
#if DEBUG
Mkt.makeToast("\(message)")
#else
Mkt.makeToast("服务器开小差啦,请稍候重试~")
#endif
return NSError(domain: "com.response.error", code: -500)
case "A0501":
//
Mkt.makeToast("服务器开小差啦,请稍候重试~")
return NSError(domain: "com.response.error", code: -501)
case "A0400":
#if DEBUG
Mkt.makeToast("请求参数错误: \(message)")
#else
Mkt.makeToast("请求参数有误,请稍候重试")
#endif
return NSError(domain: "com.response.error", code: -400)
case "A0402":
#if DEBUG
Mkt.makeToast("必填参数为空: \(message)")
#else
Mkt.makeToast("请求参数不完整,请稍候重试")
#endif
return NSError(domain: "com.response.error", code: -402)
case "A0404":
#if DEBUG
Mkt.makeToast("资源不存在: \(message)")
#else
Mkt.makeToast("请求的资源不存在")
#endif
return NSError(domain: "com.response.error", code: -404)
case "A0401":
Mkt.makeToast("~登录过期,请重新登录~")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
UserManager.shared.clearUserInfo()
let loginVC = LoginViewController()
let navVC = UINavigationController(rootViewController: loginVC)
UIApplication.shared.keyWindow?.rootViewController = navVC
}
return NSError(domain: "com.response.error", code: -401)
default:
Mkt.makeToast(message.isEmpty ? "请求失败" : message)
return NSError(domain: "com.response.error", code: -888)
}
}
}
@@ -0,0 +1,36 @@
//
// SystemRequestPath.swift
// HealthEmergency
//
// - token
import Foundation
enum SystemRequestPath: RequestPath {
case login
case userInfo
case sendSMSCode
case refreshToken
case resetPassword
case verifyCode
case postAvterImg
var path: String {
switch self {
case .login:
return "/sys/auth/appLogin"
case .userInfo:
return "/sys/sys-user/app/userInfo"
case .sendSMSCode:
return "/sys/auth/sms/sendCode"
case .refreshToken:
return "/api/system/refresh-token"
case .resetPassword:
return "/api/system/reset-password"
case .verifyCode:
return "/api/system/verify-code"
case .postAvterImg:
return "/sys/sys-user/app/uploadAvatar"
}
}
}
@@ -0,0 +1,74 @@
//
// PingguRequestPath.swift
// HealthEmergency
//
// Created by Apple on 2026/4/8.
//
import Foundation
enum PingguRequestPath: RequestPath {
case currentSataus
case questionKey // key
case questionWithKeyConnet(String) // key key
case questionPostSubmit // post
case weekQuestionResult // APP- post
case queryQuetsionResultList // post 1 2 3 4
case surveyAnswer(Int) // type 3= 4=
case riskLatestResult // getpageNum/pageSize query
case interveneWatchData // - get
// MARK: - H5 NetworkConfig.h5BaseURL
// H5 URL id token
// token UserManager.shared.getTokenValue() H5
/// H5
/// type: 1= 2= 3= 4= type H5
static func psychologicalReportH5URL(type: Int, id: String) -> String {
let path: String
switch type {
case 2: path = "/subHealthAssessment/routineEvaluationModule/stressAssessment"
case 3: path = "/subHealthAssessment/routineEvaluationModule/anxietyAssessment"
case 4: path = "/subHealthAssessment/routineEvaluationModule/depressionAssessment"
default: path = "/subHealthAssessment/routineEvaluationModule/comprehensiveHealthReport"
}
let token = UserManager.shared.getTokenValue() ?? ""
return "\(NetworkConfig.h5BaseURL)\(path)?id=\(id)&token=\(token)"
}
/// H5
static func riskReportH5URL(id: String) -> String {
let path = "/subHealthAssessment/routineEvaluationModule/comprehensiveHealthReport"
let token = UserManager.shared.getTokenValue() ?? ""
return "\(NetworkConfig.h5BaseURL)\(path)?id=\(id)&token=\(token)"
}
/// - H5
static func routineQuestionnaireReportH5URL(id: String) -> String {
let path = "/subHealthAssessment/routineEvaluationModule/routineAssessmentQuestionnaire"
let token = UserManager.shared.getTokenValue() ?? ""
return "\(NetworkConfig.h5BaseURL)\(path)?id=\(id)&token=\(token)"
}
var path: String {
switch self {
case .currentSataus:
return "/platform-assessment/ass-group-user/app/myGroup"
case .questionKey:
return "/platform-assessment/ass-questions/keys"
case .questionWithKeyConnet(let key):
return "/platform-assessment/ass-questions/\(key)"
case .questionPostSubmit:
return "/platform-assessment/ass-questions/app/submit"
case .weekQuestionResult:
return "/platform-assessment/ass-daily-routine/app/paper/queryWeekPaperData"
case .queryQuetsionResultList:
return "/platform-assessment/ass-psychology/app/pageList"
case .surveyAnswer(let type):
return "/platform-assessment/ass-questions/app/survey-answer/\(type)"
case .riskLatestResult:
return "/platform-assessment/ass-risk-evaluation/app/latestResult"
case .interveneWatchData:
return "/platform-watch/wat-result/interveneWatchData"
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
//
// Asyncs.swift
// HealthEmergency
//
// Created by Apple on 2026/3/18.
//
import Foundation
struct Asyncs {
public typealias BaseTask = () -> Void
public typealias DelayTask = (_ isCancelled: Bool) -> Void
///
public static func async(_ task: @escaping BaseTask) {
_async(task)
}
/// +
public static func async(_ task: @escaping BaseTask, mainTask: @escaping BaseTask) {
_async(task, mainTask)
}
///
public static func main(_ task: @escaping BaseTask) {
DispatchQueue.main.async(execute: task)
}
///
@discardableResult
public static func mainDelay(
_ seconds: Double,
_ task: @escaping BaseTask
) -> DispatchWorkItem {
let item = DispatchWorkItem(block: task)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
return item
}
///
@discardableResult
public static func asyncDelay(
_ seconds: Double,
_ task: @escaping BaseTask
) -> DispatchWorkItem {
_asyncDelay(seconds, task)
}
/// +
@discardableResult
public static func asyncDelay(
_ seconds: Double,
_ task: @escaping BaseTask,
mainTask: @escaping BaseTask
) -> DispatchWorkItem {
_asyncDelay(seconds, task, mainTask)
}
/// + isCancelled
@discardableResult
public static func asyncDelay(
_ seconds: Double,
_ task: @escaping BaseTask,
mainTask: @escaping DelayTask
) -> DispatchWorkItem {
let item = DispatchWorkItem(block: task)
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
item.notify(queue: DispatchQueue.main) {
mainTask(item.isCancelled)
}
return item
}
}
private extension Asyncs {
static func _async(_ task: @escaping BaseTask, _ mainTask: BaseTask? = nil) {
let item = DispatchWorkItem(block: task)
DispatchQueue.global().async(execute: item)
if let mainTask {
item.notify(queue: DispatchQueue.main, execute: mainTask)
}
}
static func _asyncDelay(
_ seconds: Double,
_ task: @escaping BaseTask,
_ mainTask: BaseTask? = nil
) -> DispatchWorkItem {
let item = DispatchWorkItem(block: task)
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
if let mainTask {
item.notify(queue: DispatchQueue.main, execute: mainTask)
}
return item
}
}

Some files were not shown because too many files have changed in this diff Show More