Compare commits

...

7 Commits

Author SHA1 Message Date
shenlei
22e7e44220 feat: chapter runtime refactoring and related updates
- Refactor chapter runtime: replace window coordinator/snapshot with warmup orchestrator
- Update EPUB core: parser, reading session, JS bridge, navigator layout
- Update reader controller: data source, location resolution, persistence
- Update chapter runtime: data cache, loader, runtime store, disk cache, warmup orchestrator
- Remove deprecated navigation state machine and pagination state
- Update text rendering: book cache, HTML normalizer
- Update UI: text content view, dark image adjuster, text selection controller
- Update settings and reader configuration
- Add CODE_REVIEW.md and AUDIT_FINAL.md documentation
- Update pod dependencies (remove SSAlertSwift, SnapKit)
- Update podspec and pod configuration files

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 18:50:07 +09:00
shenlei
b8aa10c535 Add diagnostic logs for total pages not updating investigation
Add RDEPUBBackgroundTrace.log at key decision points in the
reconciliation pipeline to trace why pageMap updates get rejected:

1. refreshBookPageMapInPlace — logs when a full pageMap update is enqueued
2. evaluateTakeover — logs when keepCurrentWindow is returned due to:
   - jumpSession coverage ratio < 0.8
   - adjacent coverage missing (hasPrev/hasNext)
   - renderSignature mismatch
3. evaluateFullPageMapTakeover — logs when keepCurrentWindow is returned due to:
   - currentSpineIndex not in candidate chapters
   - adjacent coverage missing
4. commitPendingPageMapUpdate — logs when keepCurrentWindow decision is
   applied and pending update is removed from queue

Also fix unused variable warning for currentPageNumber in extendPartial case.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 18:57:16 +08:00
shenlei
15b15d0e11 Remove all RDEPUBBackgroundTrace.log and bare print diagnostic calls
Keep only the evaluateFullPageMapTakeover: fullReplace diagnostic log
in RDEPUBPageMapReconciliationCoordinator.swift for future debugging.

Simplified RDEPUBBackgroundTrace to just the log method (removed measure
and debug gating). Removed all [EPUB][...], [ReadViewDemo], and
[EPUB][Pagination] print statements across the project.

Also includes earlier bug fixes:
- Fix stale currentPageNumber in extendPartial commit
- Fix pageCurl rebindVisiblePage during transition
- Fix keepCurrentWindow not removing pending update from queue
- Fix right-aligned text (巫鸿 bug) via avoidPageBreakInside,
  tail merger, and continuation paragraph normalization
- Add text-indent reset for aligned blocks in CSS compatibility layer

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 18:25:22 +08:00
shenlei
8c5059e72f chore: RDEPUBBackgroundTrace 日志默认关闭
Release 构建下完全静默(isEnabled 固定 false),
Debug 构建下通过 _enabled 开关控制,默认开启。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 17:36:28 +08:00
shenlei
2fa58b1026 diag: 总页数未更新问题 — 增加Reconciliation诊断日志 + 修复keepCurrentWindow后pending不移除
1. RDEPUBPageMapReconciliationCoordinator: 在evaluateTakeover、
   evaluateSegmentTakeover、evaluateFullPageMapTakeover的每个返回点
   增加RDEPUBBackgroundTrace日志,记录关键决策参数:
   - currentSpineIndex、candidateChapters/pages、currentChapters/pages
   - hasPrev/hasNext、lastBuildableSpineIndex
   - jumpSession protected coverage ratio
   用于确认fullMap被拒绝的具体原因

2. RDEPUBPresentationRuntime: .keepCurrentWindow分支增加
   removePendingPageMapUpdate(at: index),防止被拒绝的
   reconcileFullMap update无限期留在pending队列中不被消费。
   之前return false不移除,导致:
   - 后续commitPendingPageMapUpdateIfNeeded重试时条件不变
     仍然keepCurrentWindow,形成无效循环
   - 如果用户不再翻页,总页数永远不更新

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 17:33:16 +08:00
shenlei
c555329948 fix: pageCurl翻页后偶尔刷新回章节第一页
根因:.extendPartial pageMap更新提交时使用了异步扩展开始时捕获的
currentPageNumber/currentLocation,在pageCurl模式下用户可能已经翻了几页,
导致过时值通过transitionToPage把用户拉回旧位置。

修复三点:
1. PresentationRuntime: .extendPartial fallback不再使用捕获的
   currentPageNumber,改用readerView.currentPage实时值
2. ChapterWarmupOrchestrator: 异步加载完成回调中使用实时位置
   (readerView.currentPage + locationCoordinator.currentVisibleLocation())
   而非异步开始时捕获的旧值
3. PresentationRuntime: rebindVisiblePage在pageCurl模式下,
   如果正在翻页动画中(isPageCurlTransitioning),延迟到动画结束后
   再用实时currentPage执行transitionToPage,避免在pageNum回调栈
   内同步触发transitionToPage导致重入

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 16:52:38 +08:00
shenlei
d7f28c3f10 fix: 修复右对齐短文本显示不完整及多项功能改进
右对齐文本显示修复(如"巫鸿"只显示"鸿"的问题):
- RDEPUBChapterTailNormalizer: 短尾页合并时检查 avoidPageBreakInside 语义,
  避免将带有此标记的短文本(如 right-info 署名)错误合并回上一页
- RDEPUBTextContentView: 续段归一化时跳过右对齐/居中对齐的段落,
  防止错误修改 firstLineHeadIndent 导致首字不可见
- RDEPUBCSSCompatibilityLayer: 为含 text-align:right/center 的 CSS 块
  自动注入 text-indent:0 !important,确保右对齐/居中文本无首行缩进
- RDEPUBTextRendererSupport: 排版属性归一化时将右对齐/居中段落的
  firstLineHeadIndent 重置为 0

图片查看器及脚注点击功能:
- 新增 RDEPUBImageViewController 和 RDEPUBImageViewerCoordinator,
  支持从 WebView 和 TextPage 两种模式查看图片
- epub-bridge.js: 检测图片和脚注图片的点击事件,脚注图片显示弹窗
- RDEPUBJavaScriptBridge: 新增 imageDidTap/footnoteDidTap 桥接消息
- RDEPUBWebView: 新增图片和脚注点击的 delegate 方法
- RDEPUBAttachmentNormalizer: 改进脚注检测,优先使用 alt 文本判断
- RDEPUBPaginationModels: 新增 .footnote 附件类型
- RDEPUBPageLayoutSnapshot: 运行时动态解析附件类型,脚注优先级高于图片

位置解析改进:
- RDEPUBReaderController+LocationResolution: 利用 rangeInfo 提升页码定位精度

其他:
- RDEPUBTextBookCache: schema 版本升级至 13
- RDEPUBReaderTheme: 主题更新
- Pod 项目文件更新

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 15:39:58 +08:00
145 changed files with 4638 additions and 9650 deletions

151
CODE_REVIEW.md Normal file
View File

@ -0,0 +1,151 @@
# ReadViewSDK 代码审查报告
> 审查日期2026-06-26
> 审查范围Sources/RDReaderView 全部源码
> 审查方法:逐文件阅读 + 交叉验证 + 线程模型分析
---
## ✅ 确认成立的问题
### P1-1 WKURLSchemeHandler 同步文件 I/O 阻塞调用线程
**文件**: `EPUBCore/RDEPUBResourceURLSchemeHandler.swift:51-174`
**现象**: `webView(_:start:)` 回调中,所有文件读取均同步执行:
- `respondWithInMemoryData`第119行使用 `Data(contentsOf:)` 全量读入 ≤512KB 的文件
- `respondWithStreaming`第143行`while true` 循环中同步读 64KB 块并逐块回调 `urlSchemeTask.didReceive(data)`
Apple 未保证 WKURLSchemeHandler 回调线程为主线程,实际在 WKWebView 内部队列上执行。同步阻塞该线程会:
- 若在主线程:直接造成 UI 卡顿/ANR
- 若在 WKWebView 内部队列:阻塞资源加载管线,影响渲染时序
**建议**: 将文件读取移至后台队列,通过 `DispatchQueue.main.async` 或回调队列回传 `didReceive`/`didFinish`。流式场景可改为分批异步读取。
---
### P1-2 EPUB 解压缓存目录无清理策略
**文件**: `EPUBCore/RDEPUBParser+Archive.swift:79-90`
**现象**: `temporaryExtractionDirectory(for:)``(slug)-(fileSize)-(modificationTimestamp)` 生成缓存目录,存放在 `Caches/ssreaderview-epub/`。全局搜索确认不存在任何清理逻辑——无 LRU 淘汰、无总量上限、无 `removeItem` 调用指向该目录。
一本 50MB 的 EPUB 解压后约 100-200MB用户打开多本书后缓存目录无限增长。
**建议**: 添加缓存清理策略:
1. 提供 `clearCache()` 公开方法供宿主 App 在低存储时调用
2. 每次打开新书时按访问时间淘汰超出上限的旧缓存
3. 或采用 `FileManager.default.urls(for: .cachesDirectory:)` 依赖系统自动清理
---
### P1-3 搜索在主线程同步执行
**文件**: `EPUBUI/ReaderController/RDEPUBReaderSearchCoordinator.swift:15-36`
**现象**: `search(keyword:)` 直接调用 `resolvedSearchMatches(for:)`,该方法遍历整本书的所有 spine 条目执行 HTML→文本转换或章节同步加载 + NSRange 搜索,全程在主线程完成。
对于大型 EPUB数百章
- UI 完全冻结直到搜索完成
- 用户无法取消或看到进度
- 可能触发 iOS 看门狗终止0x8badf00d
`resolvedOnDemandSearchMatches(for:)`第125行更严重它同步加载每个章节`loadChapterSynchronouslyForMigration`),整本书搜索意味着逐章同步加载。
**建议**: 将搜索逻辑移至后台队列,对大型书籍实施分批搜索 + 渐进结果回调,并在 UI 层添加搜索进度指示和取消能力。
---
### P2-1 ZIP 无效条目导致整本 EPUB 打开失败
**文件**: `EPUBCore/RDEPUBParser+Archive.swift:41-44`
**现象**: 解压循环中,若 `validatedExtractionDestination` 返回 `nil`(条目路径验证失败),直接 `throw` 终止整本书的解析。实际 EPUB 中常包含无害条目macOS `__MACOSX/` 目录、`.DS_Store`、Thumbs.db 等),这些不应阻止打开。
```swift
for entry in archive {
guard let destinationURL = validatedExtractionDestination(for: entry.path, ...) else {
throw RDEPUBParserError.invalidArchiveEntryPath(entry.path) // 应改为 continue
}
```
**建议**: 将验证失败的条目改为 `continue` + 日志警告,并增加对已知无害条目路径的跳过逻辑。
---
### P2-2 javaScriptStringLiteral 实现依赖隐式假设
**文件**: `EPUBCore/RDEPUBJavaScriptBridge.swift:169-174`
**现象**: `javaScriptStringLiteral` 将值包装进单元素数组做 JSON 编码,再暴力去掉 `[``]`
```swift
private static func javaScriptStringLiteral(_ value: String?) -> String {
guard let value else { return "null" }
return jsonString(from: [value], fallback: "[null]")
.replacingOccurrences(of: "[", with: "")
.replacingOccurrences(of: "]", with: "")
}
```
这不是 XSS/注入问题(`JSONSerialization` 已正确转义引号/反斜杠等危险字符),但该方法依赖"值不含 `[``]`"的隐式假设。当前调用场景传入的是 CSS 颜色值(如 `#FFFFFF`、`rgba(...)`),不含方括号,所以实际安全。但未来如果有人将包含方括号的值传入,输出会被静默破坏。
**建议**: 改为更直观的安全编码方式,如直接对单值做 `JSONSerialization` 后去掉两端引号,或添加文档注释明确标注此方法的约束前提。
---
### P3-1 后台线程同步 hop 主线程获取布局信息
**文件**: `EPUBUI/ReaderController/RDEPUBReaderContext.swift:160-186`
**现象**: `currentTextPageSize()` 在非主线程且缓存不可用时,通过 `DispatchQueue.main.sync` 回到主线程获取布局信息。调用方包括 `RDEPUBMetadataParseWorker`后台初始化第72行`RDEPUBChapterLoader``chapterLoadQueue.async`第298行
```swift
func currentTextPageSize() -> CGSize {
if Thread.isMainThread {
// 主线程路径 — 安全
...
} else if let lastTextPaginationPageSize, ... {
// 缓存路径 — 无需 hop
return lastTextPaginationPageSize
} else {
let mainThreadSize = DispatchQueue.main.sync { [weak self] in
self?.currentTextPageSize() ?? .zero
}
```
`Thread.isMainThread` 保护使得从主线程调用不会死锁。实际风险是**时序耦合**:后台任务阻塞等待主线程布局信息,如果主线程正在忙于 UI 操作(如翻页动画),后台线程会被卡住直到主线程空闲。这是一个线程模型/可维护性问题,而非可直接触发的死锁。
**建议**: 将布局参数作为初始化参数传入后台任务,而非在后台线程通过 `DispatchQueue.main.sync` 获取。这样后台线程完全自主,不依赖主线程时序。
---
## ✅ 已撤回的原始误判
以下条目经逐条代码验证后确认不成立或定级过高:
| 原始编号 | 原始结论 | 修正结论 |
|---------|---------|---------|
| ~~P0 引用循环~~ | Runtime/Coordinator 强引用 context 形成循环 | **不成立**。全量搜索确认所有 Coordinator/Runtime 均使用 `unowned let context``weak var context` |
| ~~P0 XSS 注入~~ | `javaScriptStringLiteral``]` 替换导致注入 | **不成立**。`JSONSerialization` 编码已处理引号/反斜杠等危险字符。实际是健壮性问题,非安全问题(已调整为 P2-2 |
| ~~P1 Publication 暴露 parser~~ | 外部可直接修改 parser 内部状态 | **不成立**。`RDEPUBParser` 关键属性均为 `public internal(set)`,外部模块无法修改 |
| ~~P2 NSRange 越界~~ | UTF-16 和 Swift String 混用导致偏移 | **不成立**。搜索代码全程在 `NSString` + `NSRange` 域内操作,未混合 Swift String 索引 |
| ~~P2 后台 deinit 崩溃~~ | teardownWebView 可能在后台线程执行 | **证据不足**。`RDEPUBWebView` 是 UIView 子类,正常在主线程释放,无证据表明会后台释放 |
| ~~P0 ZIP 路径遍历绕过~~ | `%2e%2e` 可绕过路径校验 | **论证过头**。当前实现有组件级 `..` 检查 + `standardizedFileURL` 前缀校验,双层防护有效 |
---
## 📊 问题汇总
| 严重度 | 编号 | 问题 | 类型 |
|--------|------|------|------|
| 🟠 P1 | P1-1 | WKURLSchemeHandler 同步文件 I/O 阻塞调用线程 | 性能 |
| 🟠 P1 | P1-2 | EPUB 解压缓存目录无清理策略 | 存储 |
| 🟠 P1 | P1-3 | 搜索在主线程同步执行,大书可致 ANR | 性能 |
| 🟡 P2 | P2-1 | ZIP 无效条目导致整本打开失败 | 鲁棒性 |
| 🟡 P2 | P2-2 | javaScriptStringLiteral 实现依赖隐式假设 | 可维护性 |
| 🔵 P3 | P3-1 | 后台线程同步 hop 主线程获取布局信息 | 线程模型 |
**整体评价**: SDK 架构设计良好模块分层清晰引用管理unowned/weak使用正确分页取消机制有 `paginationToken` + `cancellationController` 兜底。主要值得修复的是三个性能类 P1 问题(主线程阻塞搜索、资源加载阻塞、缓存无清理),两个 P2 鲁棒性/可维护性问题,以及一个 P3 线程模型优化建议。

View File

@ -251,8 +251,7 @@ RDEPUBReaderController
├─ RDEPUBPresentationRuntime // 分页状态与窗口替换
│ ├─ applyBookPageMap()
│ ├─ refreshBookPageMapInPlace()
│ ├─ applyPendingFullPageMapIfNeeded()
│ └─ RDEPUBNavigationStateMachine
│ └─ applyPendingFullPageMapIfNeeded()
├─ RDEPUBChapterWarmupOrchestrator // 按需章节预热与边界预取
│ ├─ prepareOnDemandChapter()
@ -495,7 +494,6 @@ Sources/RDReaderView/
| **Builder** | `RDEPUBBookPageMap.Builder` 增量构建页码映射 |
| **Strategy** | `RDEPUBTextRenderer` 协议,可替换渲染器实现 |
| **Pipeline** | `RDEPUBTextTypesetterPipeline` 排版管线8 个逻辑阶段,封装为 5-6 个顶层调用) |
| **State Machine** | `RDEPUBNavigatorState` 管理阅读器状态转换 |
| **Adapter** | `RDReaderLegacyDataSourceAdapter` 适配旧数据源协议 |
| **三级缓存** | 内存 → 磁盘摘要 → 全书分页,逐级降级 |
| **Token 取消** | `paginationToken` 确保过期异步任务不干扰新任务 |

411
Doc/AUDIT_FINAL.md Normal file
View File

@ -0,0 +1,411 @@
# ReadViewSDK 代码审查报告
> 审查范围:`Sources/RDReaderView/` 全部 184 个 Swift 文件(约 32,300 行)
> 审查维度并发安全、内存管理、API 正确性、Swift/iOS 平台特定、架构与依赖
> 审查日期2026-06-26
---
## 严重程度定义
| 标记 | 含义 |
|------|------|
| 🔴 高 | 有明确代码路径可导致崩溃、数据丢失或严重功能缺陷 |
| 🟡 中 | 方向正确但风险程度取决于运行时条件;或当前安全但架构上脆弱 |
| 🟢 低 | 代码异味、可维护性问题或未来风险 |
---
## 🔴 高优先级问题
### H-01 · 内存警告未连接章节缓存清理
`RDEPUBChapterRuntimeStore.handleMemoryWarning()` 已实现(清空除当前章节外的缓存 + 清除图片缓存),但从未被调用。
- `RDEPUBReaderRuntime.handleMemoryWarning()` 只清理了 `backgroundCoverageStore`,未调用 `chapterRuntimeStore.handleMemoryWarning()`
- `RDEPUBReaderController` 未重写 `didReceiveMemoryWarning`
**影响:** 系统内存压力下,`RDEPUBChapterDataCache`(无上限字典)和 `imageCache`(仅 countLimit=50无 totalCostLimit不会主动释放内存。
**位置:**
- `RDEPUBChapterRuntimeStore.swift:103``handleMemoryWarning()` 方法存在
- `RDEPUBReaderRuntime.swift:606-620` — 只清理 backgroundCoverageStore
---
### H-02 · 主线程信号量等待
`RDEPUBChapterLoader.loadChapterSynchronouslyForMigration()` 使用 `DispatchSemaphore.wait()` 阻塞调用线程。当从 `RDEPUBPresentationRuntime.rebindVisibleLocation()``allowSynchronousLoad: true` 调用时,会在主线程等待。
**影响:** 如果章节数据未缓存,主线程被阻塞直到章节构建完成,可能导致 ANR 或死锁(如果构建过程需要主线程资源)。
**位置:**
- `RDEPUBChapterLoader.swift:206-268``semaphore.wait()`
- `RDEPUBPresentationRuntime.swift:282-286``allowSynchronousLoad: true`
---
### H-03 · asyncAfter 闭包中的强制解包
`RDEPUBReaderController+DataSource.swift:425-436` 在 0.3 秒延迟闭包中强制解包 `lastTextPaginationPageSize!`。方法入口的 `guard let lastTextPaginationPageSize` 不保护延迟闭包——0.3 秒后该值可能已变为 `nil`
**位置:**
```swift
// line 432-433
let stillChanged = abs(currentSize.width - self.lastTextPaginationPageSize!.width) > 0.5
|| abs(currentSize.height - self.lastTextPaginationPageSize!.height) > 0.5
```
---
### H-04 · `unowned context` 在后台操作中的崩溃路径
以下两个类在后台操作中访问 `unowned let context: RDEPUBReaderContext`,若 controller 在操作期间释放则会崩溃:
**RDEPUBMetadataParseWorker**
- Line 117: `let context = self.context``[weak self]` guard 之前将 unowned 引用捕获为局部变量
- Lines 197-324: `BlockOperation` 闭包直接使用该局部变量unowned`[weak self]` 保护
- Lines 296-301, 372-376: `DispatchQueue.main.async` 闭包捕获 unowned 局部变量
**RDEPUBReaderLoadCoordinator**
- Line 28: 闭包只捕获 `[weak controller]`,未捕获 `[weak self]`
- Line 30: `self.context.makeParser()` 在后台队列访问 unowned 引用
- Lines 41-42, 53-54: `DispatchQueue.main.async` 闭包直接访问 `self.context`
**影响:** 若 `RDEPUBReaderController` 在后台操作期间释放(如用户快速退出阅读器),`unowned` 引用悬挂导致 EXC_BAD_ACCESS。
**位置:**
- `RDEPUBMetadataParseWorker.swift:117, 197-324, 296-301, 372-376`
- `RDEPUBReaderLoadCoordinator.swift:28-57`
---
### H-05 · 章节数据缓存无容量限制
`RDEPUBChapterDataCache` 是无上限的 `[Int: RDEPUBRuntimeChapter]` 字典,仅通过显式 `remove(spineIndex:)``removeAll()` 逐出。每章持有完整的 `typesetAttributedString`50-200KB+50 章可达 5-10MB+。
**位置:** `RDEPUBChapterDataCache.swift:5``private var storage: [Int: RDEPUBRuntimeChapter] = [:]`
---
### H-06 · 暗色图片静态 NSCache 无 totalCostLimit
`RDEPUBDarkImageAdjuster.imageCache` 是 static `NSCache<NSString, UIImage>()`,无 `totalCostLimit`,跨越整个 App 生命周期累积调整后的图片。
**位置:** `RDEPUBDarkImageAdjuster.swift:9`
---
### H-07 · 持久化操作静默丢弃错误
`RDEPUBReaderPersistence` 中所有编解码操作均使用 `try?`,失败时:
- **保存**静默返回数据丢失无任何反馈lines 84, 98, 112, 131
- **加载**:返回 `nil` 或空数组,损坏/版本不匹配的数据无法恢复lines 80, 94, 108, 127
**影响:** 用户书签和阅读位置可能在无声中丢失。
**位置:** `RDEPUBReaderPersistence.swift:76-131`
---
### H-08 · WKURLSchemeHandler 线程违规与取消回调缺失
两个确定 bug
**(a) 不同队列回调:** `webView(_:start:)` 在某个串行队列(由 WebKit 调度)上被调用,但 `respondWithInMemoryData``respondWithStreaming``didReceive`/`didFinish`/`didFailWithError` 显式切到了 `ioQueue`line 121, 151。这至少违反了"回调应与 `start` 使用同一串行队列"的要求。
**(b) 取消时不调用完成回调:** 任务被 `stop` 后,`isTaskActive` 返回 false 时:
- streaming 路径line 178`guard self.isTaskActive(taskID) else { return }` — 仅 return不调用 `didFinish``didFailWithError`
- in-memory 路径line 128, 141同理
这违反了"每个 started task 必须收到完成回调"的协议要求。
**位置:** `RDEPUBResourceURLSchemeHandler.swift:121-187`
---
### H-09 · 搜索在后台线程同步加载所有章节
`RDEPUBReaderSearchCoordinator``searchQueue` 上遍历所有 buildable spine 索引,逐个同步加载章节。每章持有完整 `typesetAttributedString`,大型书籍搜索时内存峰值显著。
**位置:** `RDEPUBReaderSearchCoordinator.swift:47, 173-236`
---
### H-10 · 依赖版本未锁定
`RDReaderView.podspec` 中 DTCoreText、SnapKit、SSAlertSwift 无版本约束:
```ruby
s.dependency 'ZIPFoundation', '~> 0.9' # ✅ 已锁定
s.dependency 'DTCoreText' # ❌ 无约束
s.dependency 'SnapKit' # ❌ 无约束
s.dependency 'SSAlertSwift' # ❌ 无约束
```
**位置:** `RDReaderView.podspec:17-20`
---
### H-11 · SnapKit 和 SSAlertSwift 疑似死依赖
全仓库搜索未找到任何 `snp.``SSAlert` 的使用点。这两个依赖可能增加了不必要的二进制体积和潜在冲突。
---
## 🟡 中优先级问题
### M-01 · `RDEPUBReaderState` 无同步保护(隐性风险)
所有属性(`parser`, `publication`, `activeBookmarks`, `pendingPageMapUpdates` 等)均为 `var`,无锁、无 `@MainActor`、无 `dispatchPrecondition`。当前所有访问确实在主线程(经追踪 16 个 `pendingPageMapUpdates` 访问点确认),但缺少形式化保障——未来开发者可能无意从后台线程访问而不自知。
**位置:** `RDEPUBReaderState.swift:1-71`
---
### M-02 · `currentSpineIndex` / `windowSpineIndices` 无锁但当前安全
这两个字段无锁保护但追踪全部读写路径后确认当前均在主线程。风险是隐性的store 对象被传入 `chapterLoadQueue` 上下文,未来开发者可能在该队列中访问这些属性而不自知。同文件的其他字段(`navigationLock`, `prefetchLock`, `buildingLock`, `cfiMapLock`)有锁保护,形成不一致的模式。
**位置:** `RDEPUBChapterRuntimeStore.swift:15-17`
---
### M-03 · `warmAnchors` 跨线程访问无同步
`RDEPUBBackgroundPriorityManager.warmAnchors` 在主线程写入(`addWarmAnchor`),在 `MetadataParseWorker` 的后台线程读取(`makeMetadataPriorityOrder`),无锁保护。
**位置:** `RDEPUBBackgroundPriorityPolicy.swift:65`
---
### M-04 · `assert(Thread.isMainThread)` 在 Release 中被移除
`RDEPUBReaderContext``makeLayoutSnapshot()`、`currentTextPageSize()` 等方法使用 `assert(Thread.isMainThread)` 检查。`assert` 在 `-O` 构建中被移除,生产环境中后台线程调用将静默访问 UIKit 属性,造成数据竞争且无诊断。
**应使用:** `dispatchPrecondition(condition: .onQueue(.main))``@MainActor` 标注。
**位置:** `RDEPUBReaderContext.swift:172, 188, 210, 215`
---
### M-05 · 磁盘缓存无自动淘汰策略
`RDEPUBChapterSummaryDiskCache``RDEPUBTextBookCache` 只有全量清除操作(`removeAll()`、`invalidateAll()`),无 LRU/大小/年龄驱逐策略。每次设置变更字体、行距、主题产生新缓存键旧文件无限累积。写操作使用原子替换tmp + replace交错不会损坏数据但可能读到旧值或看到不一致的目录状态。
**位置:**
- `RDEPUBChapterSummaryDiskCache.swift:89``removeAll()` 全清
- `RDEPUBTextBookCache.swift:191``invalidateAll()` 全清
---
### M-06 · CFI 范围分隔符可能与内容冲突
`RDEPUBCFICompatibility``".."``"-"` 分割 CFI 范围字符串,但这些字符可能出现在 CFI 文本断言内部(如 `[foo..bar]`)。代码先匹配 `".."` 再匹配 `"-"`,无歧义消解逻辑。
**位置:** `RDEPUBCFICompatibility.swift:18-21`
---
### M-07 · CFI 步索引对奇数输入容错不严谨
`RDEPUBCFIResolver``steps[1].index / 2 - 1` 对奇数索引做整除截断,可能映射到错误章节。`max(..., 0)` 仅防负索引,不防错误映射。属于 EPUB CFI 规范的输入验证问题。
**位置:** `RDEPUBCFIResolver.swift:38`
---
### M-08 · WKWebView 保留环需主动管理
`RDEPUBWebView``webView``configuration.userContentController``RDEPUBWebView`(作为 `WKScriptMessageHandler`)形成保留环。
主要解环路径是 `reset()``configureWebViewIfNeeded` 中主动调用 `teardownWebView()`。**注意:** `deinit` 虽也调用 `teardownWebView()`,但不能作为环会被打破的保证——保留环的存在意味着 `deinit` 可能永远不会触发。风险在于:若某条代码路径跳过了 `teardownWebView()`,环将持续且对象永远不会释放。
**位置:**
- `RDEPUBWebView.swift:129``deinit` 调用 `teardownWebView()`(不可靠兜底)
- `RDEPUBWebView.swift:138-160``reset()` 调用 `teardownWebView()`(主要解环路径)
---
### M-09 · `configuration` didSet 在 `isViewLoaded` 之前持久化
`RDEPUBReaderController.configuration``didSet``guard isViewLoaded` 之前执行了 `readerContext.configuration = configuration`、`applyWebViewDebugPolicy()` 和 `persistReaderSettingsIfNeeded()`。若在 view 加载前设置 configuration这些副作用会在 UI 未就绪时触发。
**位置:** `RDEPUBReaderController.swift:8-20`
---
### M-10 · empty catch 吞掉章节加载错误
`RDEPUBReaderPaginationCoordinator``loadInitialInteractiveRuntimeChapters``catch {}` 为空,章节加载失败被静默忽略,无日志、无恢复。
**位置:** `RDEPUBReaderPaginationCoordinator.swift:283`
---
### M-11 · `RDEPUBReaderFontChoice.displayName` 始终返回中文
枚举的 `displayName` 计算属性硬编码中文字符串("系统"、"宋体"、"圆体"、"等宽"),无本地化基础设施。
**位置:** `RDEPUBReaderConfiguration.swift:18-28`
---
### M-12 · 大量硬编码中文字符串,本地化覆盖不完整
覆盖文件:`RDEPUBReaderSettingsViewController`、`RDEPUBReaderSearchBarView`、`RDEPUBReaderHighlightsViewController`、`RDEPUBReaderBookmarksViewController`、`RDEPUBReaderTopToolView`、`RDEPUBReaderBottomToolView`、`RDEPUBTextContentView`、`RDEPUBWebView+Configuration`、`RDEPUBChapterLoader`(错误信息)等。
项目中存在约 88 个硬编码中文字符串(如"拷贝"、"高亮"、"批注"、"目录"、"搜索"等),但同时也有使用 `NSLocalizedString` 的地方(如 `RDEPUBImageViewController.swift:126`)。本地化基础设施存在但覆盖不完整——新添加的 UI 文本大多直接硬编码中文而未走本地化流程。
---
### M-13 · DTCoreText 类型泄漏到 EPUBUI 层
5 个 EPUBUI 文件直接使用 `DTCoreTextLayoutFrame`、`DTCoreTextLayoutLine`、`DTCoreTextGlyphRun` 等 DTCoreText 类型(虽有 `#if canImport(DTCoreText)` 保护),意味着替换渲染引擎需修改 UI 层文件。
**位置:**
- `RDEPUBPageInteractionController.swift`
- `RDEPUBPageLayoutSnapshot.swift`
- `RDEPUBTextPageRenderView.swift`
- `RDEPUBTextContentView.swift`
- `RDEPUBDarkImageAdjuster.swift`
---
### M-14 · `imageCache` 有 countLimit 但无 totalCostLimit
`RDEPUBChapterRuntimeStore.imageCache` 设有 `countLimit = 50`,但无 `totalCostLimit`。50 张大尺寸书籍插图可达 150-250MB。
**位置:** `RDEPUBChapterRuntimeStore.swift:41`
---
### M-15 · `UIMenuController` 已废弃
`RDEPUBWebView+Configuration.swift:39``RDEPUBTextContentView.swift:747-758` 使用 `UIMenuController`,该 API 从 iOS 16 起废弃。应迁移至 `UIEditMenuInteraction`iOS 16+),并为 iOS 15 保留 fallback。当前代码在 iOS 17+ 上仍可工作但随时可能失效。
---
### M-16 · Dynamic Type 未支持
UI 中所有字体使用硬编码大小(`UIFont.systemFont(ofSize: 13/14/16/18/19)`),未使用 `UIFontMetrics``preferredFont(forTextStyle:)`。启用 Dynamic Type 的用户看不到文字大小变化。
---
### M-17 · URL Scheme Handler 取消时 `clearTask` 可能被跳过
`respondWithInMemoryData` line 128 的 `guard self.isTaskActive(taskID) else { return }` 在取消时直接返回,跳过了 line 146 的 `self.clearTask(taskID)`。当前因 `stop` 已调用 `clearTask` 而无害,但结构上脆弱。
**位置:** `RDEPUBResourceURLSchemeHandler.swift:128`
---
### M-18 · 硬编码颜色值未集成到主题系统
`RDEPUBReaderSearchBarView` 含 18 个硬编码颜色字面量,默认高亮颜色 `"#F8E16C"` 硬编码在 `RDEPUBReaderController+PublicAPI.swift:65,75`。这些应属于 `RDEPUBReaderTheme`
---
### M-19 · 硬编码时间常量
多个 `DispatchQueue.main.asyncAfter(deadline:)``Thread.sleep` 使用硬编码延迟值0.08s、0.1s、0.15s、0.2s、0.25s、0.3s、1.0s、3.5s),无命名常量或可配置选项。
---
## 🟢 低优先级问题
### L-01 · `RDEPUBChapterLoader``[self]` 强捕获
Line 103 的 `store.chapterLoadQueue.async { [self] in` 强捕获 self阻止 loader 在队列任务完成前释放。应改为 `[weak self]`
**位置:** `RDEPUBChapterLoader.swift:103`
---
### L-02 · MetadataParseWorker Thread.sleep 忙等待
`waitForReadingInteractionToSettle()` 使用 `Thread.sleep(forTimeInterval: 0.08)` 轮询,无上限等待。应考虑 `DispatchSource` 或条件变量。
**位置:** `RDEPUBMetadataParseWorker.swift:385-390`
---
### L-03 · `RDEPUBTextBookCache.queue.sync` 潜在死锁
`load()``save()` 均使用 `queue.sync`。若从 queue 内部调用则会死锁。当前未发现此调用路径,但模式脆弱。
**位置:** `RDEPUBTextBookCache.swift:136-189`
---
### L-04 · `pendingLoads` 字典无超时清理
`RDEPUBChapterLoader.pendingLoads` 的完成闭包数组在章节加载失败且 `resolvePendingLoad` 未被调用时可能累积。当前所有路径均调用 `resolvePendingLoad`,但无防御性超时。
**位置:** `RDEPUBChapterLoader.swift:7-10`
---
### L-05 · `RDEPUBChapterWindowCoordinator` 是死代码
该类定义了但未被任何代码实例化或引用。其中对 `currentSpineIndex`/`windowSpineIndices` 的7个访问点均为死代码。
---
### L-06 · 平台版本不一致
- `podspec`: `s.platform = :ios, "15.0"`
- `Podfile`(根和 demo: `platform :ios, '15.6'`
---
### L-07 · `RDEPUBMetadataCancellationController` 微小竞争
`attach()` 方法在 `lock.unlock()``queue.cancelAllOperations()` 之间存在窄窗口,新操作可能被添加到队列但不会被取消。实际影响极小。
**位置:** `RDEPUBMetadataParseCancellationController.swift:attach()`
---
### L-08 · 页码标签硬编码偏移
`RDEPUBWebContentView``RDEPUBTextContentView` 的页码标签使用硬编码偏移24pt, 20pt在刘海屏设备上可能与安全区域重叠。
---
## 已排除项
以下原报告条目经核实后排除或合并:
| 原编号 | 原描述 | 排除原因 |
|--------|--------|----------|
| #6 | compactMap 结果越界 | 入口有 `childViewControllers.count == expectedCount` 检查compactMap 后不会少于预期数量 |
| #7 | `result!` 强解包崩溃 | 信号量等待的真实风险是无限等待/卡住,不是 result 为 nilsignal 前必定赋值 |
| #21 | 搜索协调器 fallback 空 store | `?? RDEPUBChapterRuntimeStore()` 前的可选链若为 nil 则整个调用返回 nilfallback 不会生效 |
| #23 | init 时加载所有 HTML 到内存 | 逐章取 HTML 算哈希后只保存 digestHTML 字符串不长期保留 |
| #35 | `.nonPersistent()` 阻止 cookie/localStorage | 不持久化是有意设计,不是 bug原始报告将"不持久化"误述为"不能用" |
| #48 | `didMoveToSuperview` 每次重复 makeUI | 有 `didBuildUI` 防重入保护 |
| 原L-04 | `activeTasks` 字典 `clearTask` 设 nil 不删键 | Swift 中 `dict[key] = nil` 等同于 `removeValue(forKey:)`,不保留空洞 |
| 原M-18 | MetadataParseWorker OperationQueue 未在 deinit 取消 | 外层 `[weak self]` guard 确保 self 存活才执行 queue 操作;`cancellationController` 提供显式取消路径 |
| 原L-08 | `ss_superViewController` 前缀暗示外部来源 | 命名风格猜测,非可验证缺陷 |
---
## 建议优先级排序
| 优先级 | 编号 | 修复复杂度 | 说明 |
|--------|------|-----------|------|
| P0 | H-01 | 低 | 在 `handleMemoryWarning` 中加一行 `chapterRuntimeStore.handleMemoryWarning()`,并在 controller 中转发 `didReceiveMemoryWarning` |
| P0 | H-04 | 中 | MetadataParseWorker 和 LoadCoordinator 中 `unowned context` 改为 `weak`,闭包加 `guard` |
| P0 | H-03 | 低 | `lastTextPaginationPageSize!` 改为 `guard let` 安全解包 |
| P1 | H-08 | 中 | URLSchemeHandler 回调改到与 start 相同队列,取消路径加 `didFailWithError` |
| P1 | H-02 | 中 | 主线程同步加载改为异步回调或确保不从主线程调用 |
| P1 | H-07 | 低 | 关键持久化操作加日志,至少记录失败原因 |
| P1 | H-10 | 低 | 给三个依赖加版本约束;移除 SnapKit 和 SSAlertSwift |
| P2 | H-05 | 中 | 为 ChapterDataCache 加 LRU 逐出策略或最大条目数限制 |
| P2 | H-06 | 低 | 为 DarkImageAdjuster.imageCache 设置 `totalCostLimit` |
| P2 | H-09 | 中 | 搜索改为流式/分批加载,避免一次性持有所有章节 |
| P2 | M-01/02 | 中 | 为 State 和 Store 关键属性加 `@MainActor``dispatchPrecondition` |
| P2 | M-04 | 低 | `assert` 改为 `dispatchPrecondition``precondition` |
| P2 | M-08 | 低 | 保留环加注释说明必须通过 `reset()` 打破 |
| P2 | M-15 | 中 | 迁移至 `UIEditMenuInteraction` |

View File

@ -4,7 +4,7 @@ Pod::Spec.new do |s|
s.name = "RDReaderView"
s.version = "0.0.1"
s.summary = "A reader view for novel"
s.platform = :ios, "15.0"
s.platform = :ios, "15.6"
s.swift_versions = ["5.10"]
s.homepage = "https://github.com/namesubai/RDReaderView.git"
s.author = { "subai" => "804663401@qq.com" }
@ -15,9 +15,7 @@ Pod::Spec.new do |s|
'RDReaderViewAssets' => ['Sources/RDReaderView/EPUBCore/Resources/**/*']
}
s.dependency 'ZIPFoundation', '~> 0.9'
s.dependency 'DTCoreText'
s.dependency 'SnapKit'
s.dependency 'SSAlertSwift'
s.dependency 'DTCoreText', '~> 1.6'
s.requires_arc = true
end

View File

@ -17,12 +17,8 @@ PODS:
- DTFoundation/UIKit (1.7.19):
- DTFoundation/Core
- RDReaderView (0.0.1):
- DTCoreText
- SnapKit
- SSAlertSwift
- DTCoreText (~> 1.6)
- ZIPFoundation (~> 0.9)
- SnapKit (5.7.1)
- SSAlertSwift (0.0.15)
- ZIPFoundation (0.9.20)
DEPENDENCIES:
@ -32,8 +28,6 @@ SPEC REPOS:
trunk:
- DTCoreText
- DTFoundation
- SnapKit
- SSAlertSwift
- ZIPFoundation
EXTERNAL SOURCES:
@ -43,9 +37,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb
DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0
RDReaderView: 617ec758a5db3c10024acf83b110465ea8343b5b
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SSAlertSwift: aad8dc0c20b36fcffe700b81d7be89d60c7ba4f1
RDReaderView: 2e0eeeff4bcfe8bbc09642344d326440af280ed5
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
PODFILE CHECKSUM: 775f5c8c488024e24d494aad6331e9fef8f9e2e5

View File

@ -3,7 +3,7 @@
"version": "0.0.1",
"summary": "A reader view for novel",
"platforms": {
"ios": "15.0"
"ios": "15.6"
},
"swift_versions": [
"5.10"
@ -28,13 +28,7 @@
"~> 0.9"
],
"DTCoreText": [
],
"SnapKit": [
],
"SSAlertSwift": [
"~> 1.6"
]
},
"requires_arc": true,

View File

@ -17,12 +17,8 @@ PODS:
- DTFoundation/UIKit (1.7.19):
- DTFoundation/Core
- RDReaderView (0.0.1):
- DTCoreText
- SnapKit
- SSAlertSwift
- DTCoreText (~> 1.6)
- ZIPFoundation (~> 0.9)
- SnapKit (5.7.1)
- SSAlertSwift (0.0.15)
- ZIPFoundation (0.9.20)
DEPENDENCIES:
@ -32,8 +28,6 @@ SPEC REPOS:
trunk:
- DTCoreText
- DTFoundation
- SnapKit
- SSAlertSwift
- ZIPFoundation
EXTERNAL SOURCES:
@ -43,9 +37,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb
DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0
RDReaderView: 617ec758a5db3c10024acf83b110465ea8343b5b
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SSAlertSwift: aad8dc0c20b36fcffe700b81d7be89d60c7ba4f1
RDReaderView: 2e0eeeff4bcfe8bbc09642344d326440af280ed5
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
PODFILE CHECKSUM: 775f5c8c488024e24d494aad6331e9fef8f9e2e5

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +0,0 @@
# SSAlert
实际开发中经常遇到各种自定义的弹窗的需求,其中编写弹窗动画和遮罩就很繁琐,而且一个一个去写重复的代码,代码会变得更加冗余。
SSAlert封装了动画部分而且支持自定义动画。可以快速构建弹窗简单易用基本可以满足大部分弹窗需求。自带常用自定义弹窗类型系统的UIAlertView,UIActionSheet。
# 特性
- 支持OC、Swift版本
- 支持自定义弹窗内容可以根据弹窗内容frame布局和自动布局确定弹窗大小
- 支持动画展示后刷新弹窗的大小
- 支带透明、黑色、无三种背景遮罩
- 自带从上、下、左、右、中弹出动画,动画支持弹簧效果,而且可以设置位置偏移量
- 支持自定义动画,动画隐藏回调
- 支持模态视图弹窗,支持拖拽隐藏
- 自带类似系统的弹窗UIAlertView,UIActionSheet
# 效果图
<img src="https://github.com/namesubai/SSAlert/blob/main/demo.gif" width = 20% height = 20% />
# 使用
## 1.导入代码
### Objective-C项目
1. pod 'SSAlert'
2. #import <SSAlert/SSAlert.h>
### Swift项目
1. pod 'SSAlertSwift'
2. import SSAlertSwift
## 2.SSAlertView用法
### SSAlertView的初始化
SSAlertView的初始化有两种一种是普通弹窗一种是模态视图弹窗两种的区别在于前者是弹出一个View后者是弹窗一个UIViewController。当需要点击弹窗上按钮不关闭弹窗跳转界面时候可以使用模态视图弹窗。
普通弹窗初始化:
```
let customView = UIView()
customView.frame = CGRect(x:0,y:0,width:200,height:200)
let alertView = SSAlertView(customView: customView, onView: navigationController!.view)
```
模态视图弹窗初始化:
```
let customView = UIView()
customView.frame = CGRect(x:0,y:0,width:200,height:200)
let alertView = SSAlertView(customView: customView, fromViewController: self)
```
### SSAlertView的动画设置
遵循 **SSAlertAnimation** 协议的自定义动画都可以设置 **SSAlertView****animation** 属性
**SSAlertView**自带的动画效果:**SSAlertDefaultAnmation**,提供上、下、左、右、中弹出动画等
```
let animation = SSAlertDefaultAnmation(state: .fromCenter)
alertView.animation = animation
```
### SSAlertView的展示和隐藏
```
alertView.show()
alertView.hide()
```
### SSAlertView的大小刷新
```
customView.frame = CGRect(x:0,y:0,width:400,height:400)
alertView.refreshFrame()
```
## 3.自带类型系统UIAlert,UIActionSheet的用法
```
///
let alertView = SSAlertView.alertView(style: .alert, title: "自定义Alert弹窗", message: "自带自定义Alert弹窗类似系统的UIAlertView", cancelButton: "Cancel", otherButtons: ["OK"], onView: navigationController!.view){ index in
print(index)
}
alertView.show()
///
let alertView = SSAlertView.modalAlertView(style: .actionSheet, title: "自定义ActionSheet弹窗", message: "自带自定义ActionSheet弹窗类似系统的ActionSheet", cancelButton: "Cancel", otherButtons: ["action1", "action2", "action3", "action4"], fromViewController: self) { index in
print(index)
}
alertView.show()
```
## 4.自定义SSAlertCommonView
```
let action1 = SSAlertAction(style: .actionSheet, title: "自定义Action1") {
print("自定义Action1")
}
action1.titleColor = .red
action1.backgroundColor = .yellow
action1.titleFont = UIFont.systemFont(ofSize: 18)
action1.height = 80
let action2 = SSAlertAction(style: .actionSheet, title: "自定义Action2") {
print("自定义Action2")
}
action2.titleColor = .yellow
action2.backgroundColor = .red
action2.titleFont = UIFont.systemFont(ofSize: 22)
action2.height = 55
let commonView = SSAlertCommonView(title: "自定义SSAlertCommonView", message: "文本文本文本文本文本文本", style: .actionSheet, actions: [action1, action2])
commonView.backgroundColor = .white
let alertView = SSAlertView(customView: commonView, onView: navigationController!.view, animation: SSAlertDefaultAnmation(state: .fromBottom))
alertView.show()
```
更多用法请下载代码,观看[demo](https://github.com/namesubai/SSAlert),
觉得好用手动点个Star❤

View File

@ -1,24 +0,0 @@
//
// SSAlertAnimation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
public typealias PanProgress = CGFloat
public typealias PanCancelProgress = CGFloat
public typealias DimissIsCancel = Bool
public protocol SSAlertAnimation {
///
func animationDuration() -> TimeInterval
///
func showAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Void))?)
///
func hideAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Bool))?)
///
func refreshAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Void))?)
///
func panToDimissTransilatePoint(point: CGPoint, panViewFrame: CGRect) -> (PanProgress, PanCancelProgress)
}

View File

@ -1,46 +0,0 @@
//
// SSAlertAnimationController.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
class SSAlertAnimationController: UIViewController {
var isDimiss: Bool = false
var isHideStatusBar = false {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
var isShowNavWhenViewWillDisappear = true
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
}
override var prefersStatusBarHidden: Bool {
return isHideStatusBar
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if !isDimiss, isShowNavWhenViewWillDisappear {
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag, completion: completion)
isDimiss = true
}
}

View File

@ -1,335 +0,0 @@
//
// SSAlertCommonView.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
public extension SSAlertAction {
enum Style {
case alert
case actionSheet
}
}
public class SSAlertAction: NSObject {
public var title: String
public var titleFont = UIFont.systemFont(ofSize: 16)
public var titleColor: UIColor? = nil
public var height: CGFloat = 55
public var backgroundColor: UIColor? = nil
public private(set) var style: Style
private var addAction:(() -> Void)? = nil
public init(style: Style, title: String, addAction: (() -> Void)? = nil) {
self.style = style
self.title = title
self.addAction = addAction
if style == .alert {
height = 50
}
super.init()
}
func triggerAction() {
if let addAction = addAction {
addAction()
}
}
}
extension SSAlertCommonView {
public static var alertMaxWidth = UIScreen.main.bounds.width * 0.7
public static var actionSheetMaxWidth = UIScreen.main.bounds.width
public static var buttonSpace: CGFloat = 0.5
public static var safeAreaBottomMargin: CGFloat {
if #available(iOS 11.0, *) {
return UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
return 0
}
}
public class SSAlertCommonView: UIView {
public lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
public lazy var messageLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
public lazy var containerView: UIView = {
let view = UIView()
return view
}()
public lazy var buttonView: UIView = {
let view = UIView()
return view
}()
public var lineColor: UIColor? = nil {
didSet {
lineArray.forEach { lineView in
lineView.backgroundColor = lineColor
}
}
}
public var lineEdgeInsets: UIEdgeInsets = .zero {
didSet {
refreshFrame()
}
}
public private(set) var style: SSAlertAction.Style
public private(set) var actions: [SSAlertAction]
public private(set) var title: String?
public private(set) var message: String?
private var buttonArray: [UIButton] = []
private var lineArray: [UIView] = []
private var hasText = false
public init(title: String? = nil, message: String? = nil, style: SSAlertAction.Style, actions:[SSAlertAction] = []) {
self.title = title
self.message = message
self.style = style
self.actions = actions
super.init(frame: .zero)
makeUI()
}
private func makeUI() {
addSubview(containerView)
if let title = title, !title.isEmpty {
titleLabel.text = title
containerView.addSubview(titleLabel)
hasText = true
}
if let message = message, !message.isEmpty {
messageLabel.text = message
containerView.addSubview(messageLabel)
hasText = true
}
if actions.count > 0 {
addSubview(buttonView)
actions.forEach { action in
let button = UIButton(type: .system)
button.setTitle(action.title, for: .normal)
button.backgroundColor = backgroundColor
if let backgroundColor = action.backgroundColor {
button.backgroundColor = backgroundColor
}
button.titleLabel?.font = action.titleFont
button.setTitleColor(action.titleColor, for: .normal)
button.addTarget(self, action: #selector(buttonAction(button:)), for: .touchUpInside)
buttonView.addSubview(button)
buttonArray.append(button)
var isAddLineView = false
if style == .actionSheet {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
} else {
if actions.count > 2 {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
} else {
if actions.count == 1 {
if hasText {
isAddLineView = true
}
} else {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
}
}
}
if isAddLineView {
let linView = UIView()
linView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
buttonView.addSubview(linView)
lineArray.append(linView)
}
}
}
refreshFrame()
}
private func refreshFrame() {
let space: CGFloat = 15
let viewMaxWidth = style == .alert ? SSAlertCommonView.alertMaxWidth : SSAlertCommonView.actionSheetMaxWidth
var height: CGFloat = 0
let width = viewMaxWidth
let containerWidth = viewMaxWidth - space * 2
var originY: CGFloat = 0
var cotainerOriginY: CGFloat = 0
if titleLabel.superview != nil {
let titleSize = titleLabel.sizeThatFits(CGSize(width: containerWidth, height: CGFloat(MAXFLOAT)))
titleLabel.ss_origin = CGPoint(x: 0, y: cotainerOriginY)
titleLabel.ss_size = CGSize(width: containerWidth, height: titleSize.height)
height += (titleSize.height + space)
cotainerOriginY += (space + titleSize.height)
originY = space
}
if messageLabel.superview != nil {
let messageSize = messageLabel.sizeThatFits(CGSize(width: containerWidth, height: CGFloat(MAXFLOAT)))
messageLabel.ss_origin = CGPoint(x: 0, y: cotainerOriginY)
messageLabel.ss_size = CGSize(width: containerWidth, height: messageSize.height)
height += (messageSize.height + space)
cotainerOriginY += (space + messageSize.height)
originY = space
}
containerView.ss_origin = CGPoint(x: space, y: originY)
containerView.ss_size = CGSize(width: containerWidth, height: height)
originY += height
if !buttonArray.isEmpty {
let buttonSpace: CGFloat = SSAlertCommonView.buttonSpace
var buttonTotalHeight: CGFloat = 0
var buttonTopY: CGFloat = 0
if style == .actionSheet {
for index in 0..<buttonArray.count {
let action = actions[index]
let button = buttonArray[index]
if lineArray.count == buttonArray.count {
let lineView = lineArray[index]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
if lineArray.count == buttonArray.count - 1 {
if index < lineArray.count {
let lineView = lineArray[index]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY + action.height, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
}
if index == buttonArray.count - 1 {
button.ss_size = CGSize(width: width, height: action.height + SSAlertCommonView.safeAreaBottomMargin)
button.titleEdgeInsets = UIEdgeInsets(top: -SSAlertCommonView.safeAreaBottomMargin, left: 0, bottom: 0, right: 0)
} else {
button.ss_size = CGSize(width: width, height: action.height)
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
buttonTopY += button.ss_h
}
buttonTotalHeight = buttonTopY
}
if style == .alert {
if buttonArray.count > 2 {
buttonArray.forEach { button in
let index = buttonArray.firstIndex(of: button)
let action = actions[index!]
if lineArray.count == buttonArray.count {
let lineView = lineArray[index!]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
if lineArray.count == buttonArray.count - 1 {
if index! < lineArray.count {
let lineView = lineArray[index!]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY + action.height, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
button.ss_size = CGSize(width: width, height: action.height)
buttonTopY += action.height
}
buttonTotalHeight = buttonTopY
} else {
if buttonArray.count == 1 {
let button = buttonArray.first!
let action = actions.first!
if lineArray.count > 0 {
let lineView = lineArray.first!
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
button.ss_size = CGSize(width: width, height: action.height)
buttonTotalHeight = buttonTopY
} else {
let button1 = buttonArray.first!
let button2 = buttonArray.last!
let action1 = actions.first!
let action2 = actions.last!
let maxButtonHeight = max(action1.height, action2.height)
if hasText {
let lineView1 = lineArray.first!
lineView1.frame = CGRect(x: 0, y: buttonTopY, width: width, height: buttonSpace)
buttonTopY += buttonSpace
}
button1.ss_origin = CGPoint(x: 0, y: buttonTopY)
button1.ss_size = CGSize(width: width / 2 - buttonSpace / 2, height: maxButtonHeight)
button2.ss_origin = CGPoint(x: button1.frame.maxX + buttonSpace, y: buttonTopY)
button2.ss_size = CGSize(width: width / 2 - buttonSpace / 2, height: maxButtonHeight)
let lineView2 = lineArray.last!
lineView2.frame = CGRect(x: button1.frame.maxX, y: buttonSpace, width: buttonSpace, height: button1.ss_h)
buttonTopY += maxButtonHeight
}
buttonTotalHeight = buttonTopY
}
}
buttonView.ss_origin = CGPoint(x: 0, y: originY)
buttonView.ss_size = CGSize(width: width, height: buttonTotalHeight)
height += buttonTotalHeight + (originY > 0 ? space : 0)
}
self.ss_size = CGSize(width: width, height: height)
}
@objc private func buttonAction(button: UIButton) {
let index = buttonArray.firstIndex(of: button)
let action = actions[index!]
action.triggerAction()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -1,223 +0,0 @@
//
// SSAlertDefaultAnmation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
extension SSAlertDefaultAnmation {
public enum State {
case fromTop, fromLeft, fromBottom, fromRight, fromCenter
}
}
open class SSAlertDefaultAnmation: NSObject {
public var state: State
///
public var centerOffset: CGPoint = .zero
///
public var duration: TimeInterval = 0.3
///
public var isSpringShowAnimation: Bool = true
///
public var panDimissProgress: CGFloat = 0.4
public var usingSpringWithDamping: CGFloat = 0.8
public var initialSpringVelocity: CGFloat = 0.5
private weak var animationView: SSAlertView? = nil
public init(state: State) {
self.state = state
super.init()
}
private func animation(animated: Bool, isSpringAnimation: Bool = true, animations: @escaping (() -> Void), completion: ((Bool) -> Void)? = nil) {
if animated {
if isSpringAnimation {
UIView.animate(withDuration: animationDuration(),
delay: 0,
usingSpringWithDamping: usingSpringWithDamping,
initialSpringVelocity: initialSpringVelocity,
options: [],
animations: animations,
completion: completion)
} else {
UIView.animate(withDuration: animationDuration(),
animations: animations,
completion: completion)
}
} else {
animations()
if let completion = completion {
completion(true)
}
}
}
public func setCenterOffset(center offset: CGPoint, duration: TimeInterval = 0.3, animated: Bool = true, completion:((Bool) -> Void)?) {
self.duration = duration
self.animation(animated: animated, animations: {
self.animationView?.transform = CGAffineTransform.init(translationX: offset.x, y: offset.y)
}, completion: completion)
}
}
extension SSAlertDefaultAnmation: SSAlertAnimation {
public func animationDuration() -> TimeInterval {
return duration
}
public func showAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Void))?) {
guard let animationSuperView = animationView.superview else {
return
}
self.animationView = animationView
animationView.alpha = 1
animationView.ss_size = viewSize
switch state {
case .fromBottom:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_y = animationSuperView.ss_h
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: { [weak self] in guard let self = self else { return }
animationView.transform = CGAffineTransform.init(translationX: 0, y: -animationView.ss_h + self.centerOffset.y)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromTop:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_y = -animationView.ss_h
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: { [weak self] in guard let self = self else { return }
animationView.transform = CGAffineTransform.init(translationX: 0, y: animationView.ss_h + self.centerOffset.y)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromLeft:
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.ss_x = -animationView.ss_w
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.transform = CGAffineTransform.init(translationX: animationView.ss_w + self.centerOffset.x, y: 0)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromRight:
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.ss_x = animationSuperView.ss_w
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.transform = CGAffineTransform.init(translationX: -animationView.ss_w + self.centerOffset.x, y: 0)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromCenter:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.transform = CGAffineTransform.init(scaleX: 1.5, y: 1.5)
animationView.alpha = 0
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.alpha = 1
animationView.transform = CGAffineTransform.identity
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
}
}
public func hideAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Bool))?) {
self.animationView = animationView
animation(animated: animated, isSpringAnimation: false, animations: {
if self.state == .fromCenter {
animationView.alpha = 0
} else {
animationView.transform = CGAffineTransform.identity
}
animationView.backgroundMask?.alpha = 0
}, completion: {
finished in
var isCancel = false
if let completion = completion {
isCancel = completion(finished)
}
if !isCancel {
animationView.removeFromSuperview()
animationView.backgroundMask?.removeFromSuperview()
self.animationView = nil
}
})
}
public func refreshAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Void))?) {
guard let animationSuperView = animationView.superview else {
return
}
self.animationView = animationView
animationView.alpha = 1
animationView.ss_size = viewSize
switch state {
case .fromBottom:
animationView.ss_centerX = animationSuperView.ss_w / 2.0 + centerOffset.x
animation(animated: animated, animations: {
animationView.ss_y = animationSuperView.ss_h - viewSize.height + self.centerOffset.y
}, completion: completion)
case .fromTop:
animationView.ss_centerX = animationSuperView.ss_w / 2.0 + centerOffset.x
animation(animated: animated, animations: {
animationView.ss_y = self.centerOffset.y
}, completion: completion)
case .fromLeft:
animationView.ss_centerY = animationSuperView.ss_h / 2.0 + centerOffset.y
animation(animated: animated, animations: {
animationView.ss_x = self.centerOffset.x
}, completion: completion)
case .fromRight:
animationView.ss_centerY = animationSuperView.ss_h / 2.0 + centerOffset.y
animation(animated: animated, animations: {
animationView.ss_x = animationSuperView.ss_w - viewSize.width + self.centerOffset.x
}, completion: completion)
case .fromCenter:
animationView.alpha = 0
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.transform = CGAffineTransform.init(scaleX: 1.5, y: 1.5)
animation(animated: animated, animations: {
animationView.alpha = 1
animationView.transform = CGAffineTransform.identity
}, completion: completion)
}
}
public func panToDimissTransilatePoint(point: CGPoint, panViewFrame: CGRect) -> (PanProgress, PanCancelProgress) {
var progress: CGFloat = 0
switch state {
case .fromTop:
if point.y <= 0 {
progress = abs(point.y / panViewFrame.height)
}
case .fromBottom:
if point.y >= 0 {
progress = abs(point.y / panViewFrame.height)
}
case .fromLeft:
if point.x <= 0 {
progress = abs(point.x / panViewFrame.width)
}
case .fromRight:
if point.x >= 0 {
progress = abs(point.x / panViewFrame.width)
}
case .fromCenter:
progress = abs(point.y / panViewFrame.height)
}
return (progress, panDimissProgress)
}
}

View File

@ -1,222 +0,0 @@
//
// SSAlertPresentAnimation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
typealias PanDimissAnimation = (CGPoint, CGRect) -> (PanProgress, PanCancelProgress)
typealias DimissAnimation = (DimissIsCancel) -> Void
class SSCustomInteractiveAnimation: UIPercentDrivenInteractiveTransition {
private(set) var panGestureRecognizer: UIPanGestureRecognizer
private var panDimissAnimation: PanDimissAnimation?
private var dimissAnimation: DimissAnimation?
init(panGestureRecognizer: UIPanGestureRecognizer, panDimissAnimation: PanDimissAnimation?, dimissAnimation: DimissAnimation?) {
self.panGestureRecognizer = panGestureRecognizer
self.panDimissAnimation = panDimissAnimation
self.dimissAnimation = dimissAnimation
super.init()
self.completionSpeed = 0.2
panGestureRecognizer.addTarget(self, action: #selector(panAction(pan:)))
}
@objc private func panAction(pan: UIPanGestureRecognizer) {
guard let view = pan.view else {
return
}
let poin = pan.translation(in: view)
var progress: CGFloat = 0
var cancelProgress: CGFloat = 0.4
if let panDimissAnimation = self.panDimissAnimation {
let (tProgress, tCancelProgress) = panDimissAnimation(poin, pan.view!.frame)
progress = tProgress
cancelProgress = tCancelProgress
}
switch pan.state {
case .began:
pan.setTranslation(CGPoint(x: 0, y: 0), in: view)
case .changed:
update(progress)
case .cancelled:
cancel()
case .ended:
if progress > cancelProgress {
finish()
dimissAnimation?(false)
} else {
cancel()
dimissAnimation?(true)
}
default:
break
}
}
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
}
deinit {
panGestureRecognizer.removeTarget(self, action: #selector(panAction(pan:)))
}
}
class SSAlertPresentAnimation: NSObject {
typealias AnimationCompletion = () -> Void
var duration: TimeInterval = 0.35
private var showAnimation: AnimationCompletion? = nil
private var hideAnimation: AnimationCompletion? = nil
private var endCompletion: AnimationCompletion? = nil
private var panDimissAnimation: PanDimissAnimation? = nil
private var dimissAnimation: DimissAnimation? = nil
private var transitionContext: UIViewControllerContextTransitioning?
private weak var nav: UINavigationController?
private weak var animationView: UIView?
private var panGestureRecognizer: UIPanGestureRecognizer?
init(navigationViewController: UINavigationController, animationView: UIView, canPanDimiss: Bool = true) {
self.nav = navigationViewController
self.animationView = animationView
super.init()
if canPanDimiss {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(pan:)))
pan.maximumNumberOfTouches = 1
animationView.addGestureRecognizer(pan)
}
}
@objc func panAction(pan: UIPanGestureRecognizer) {
if pan.state == .began {
panGestureRecognizer = pan
nav?.viewControllers[0].dismiss(animated: true, completion: {
[weak self] in guard let self = self else { return }
self.panGestureRecognizer = nil
});
}
}
func animationCompletion(showAnimation: AnimationCompletion? = nil,
hideAnimation: AnimationCompletion? = nil,
endCompletion: AnimationCompletion? = nil,
panDimissAnimation: PanDimissAnimation? = nil,
dimissAnimation: DimissAnimation? = nil) {
if showAnimation != nil {
self.showAnimation = showAnimation
}
if hideAnimation != nil {
self.hideAnimation = hideAnimation
}
if endCompletion != nil {
self.endCompletion = endCompletion
}
if panDimissAnimation != nil {
self.panDimissAnimation = panDimissAnimation
}
if panDimissAnimation != nil {
self.panDimissAnimation = panDimissAnimation
}
if dimissAnimation != nil {
self.dimissAnimation = dimissAnimation
}
}
@discardableResult
func setCompleteTransitionIsHide(isHide: Bool) -> Bool {
if let transitionContext = transitionContext {
let isCancelled = transitionContext.transitionWasCancelled
if !isCancelled {
if isHide {
transitionContext.containerView.subviews.forEach { subView in
subView.removeFromSuperview()
}
}
}
transitionContext.completeTransition(!isCancelled)
return isCancelled
}
return false
}
}
extension SSAlertPresentAnimation: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.panGestureRecognizer != nil {
return SSCustomInteractiveAnimation(panGestureRecognizer: self.panGestureRecognizer!, panDimissAnimation: self.panDimissAnimation, dimissAnimation: dimissAnimation)
}
return nil
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.panGestureRecognizer != nil {
return SSCustomInteractiveAnimation(panGestureRecognizer: self.panGestureRecognizer!, panDimissAnimation: self.panDimissAnimation, dimissAnimation: dimissAnimation)
}
return nil
}
}
extension SSAlertPresentAnimation: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let containerView = transitionContext.containerView
if let fromVC = fromVC, let toVC = toVC {
var fromView = fromVC.view
var toView = toVC.view
if transitionContext.responds(to: #selector(UIViewControllerContextTransitioning.view(forKey:))) {
fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
}
let isPresenting = toVC.presentingViewController == fromVC
if isPresenting {
if let toView = toView {
containerView.addSubview(toView)
}
if let showAnimation = self.showAnimation {
showAnimation()
}
} else {
if let toView = toView, let fromView = fromView {
containerView.insertSubview(toView, belowSubview: fromView)
}
if let hideAnimation = self.hideAnimation {
hideAnimation()
}
}
}
}
func animationEnded(_ transitionCompleted: Bool) {
if let endCompletion = self.endCompletion, transitionCompleted, let transitionContext = transitionContext, transitionContext.isInteractive {
endCompletion()
}
}
}

View File

@ -1,312 +0,0 @@
//
// SSAlertView.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import UIKit
extension SSAlertView {
public enum BackgroundMaskType {
///
case none
///
case clear
///
case black
}
}
fileprivate extension SSAlertView {
typealias HideCompletion = () -> Void
class HideCompletionData: NSObject {
var completion: HideCompletion?
}
}
open class SSAlertView: UIView {
/// customView
public var customEdgeInsets: UIEdgeInsets = .zero {
didSet {
setNeedsDisplay()
layoutIfNeeded()
}
}
///
public var animation: SSAlertAnimation
///
public private(set) var backgroundMask: UIView?
///
public var canTouchMaskHide: Bool = true
/// nil,
public var isTouchMaskHideAnimated: Bool? = nil
///
public private(set) var navigationController: UINavigationController?
public var isHideStatusBar = false {
didSet {
self.toViewContrller?.isHideStatusBar = isHideStatusBar
}
}
public var isShowNavWhenViewWillDisappear = true {
didSet {
self.toViewContrller?.isShowNavWhenViewWillDisappear = isShowNavWhenViewWillDisappear
}
}
private var onView: UIView
public private(set) var maskType: BackgroundMaskType
public private(set) var customView: UIView
private weak var fromViewController: UIViewController?
private weak var toViewContrller: SSAlertAnimationController?
private var presentAnimation: SSAlertPresentAnimation?
private var canPanDimiss: Bool = false
private var hideCompletions = [HideCompletionData]()
///
public init(customView: UIView,
onView: UIView,
animation: SSAlertAnimation = SSAlertDefaultAnmation(state: .fromCenter),
maskType: BackgroundMaskType = .black) {
self.animation = animation
self.onView = onView
self.maskType = maskType
self.customView = customView
super.init(frame: .zero)
makeUI()
}
/// ),canPanDimiss:
public convenience init(customView: UIView,
fromViewController: UIViewController,
animation: SSAlertAnimation = SSAlertDefaultAnmation(state: .fromCenter),
navigationControllerClass: UINavigationController.Type = UINavigationController.self,
maskType: BackgroundMaskType = .black,
canPanDimiss: Bool = true) {
let animaionVC = SSAlertAnimationController()
self.init(customView: customView, onView: animaionVC.view, animation: animation, maskType: maskType)
animaionVC.isHideStatusBar = self.isHideStatusBar
if let superViewController = superViewController(view: customView), superViewController != animaionVC {
animaionVC.addChild(superViewController)
superViewController.didMove(toParent: animaionVC)
}
self.canPanDimiss = canPanDimiss
self.fromViewController = fromViewController
let nav = navigationControllerClass.init(rootViewController: animaionVC)
nav.modalPresentationStyle = .custom
self.navigationController = nav
self.canPanDimiss = canPanDimiss
let animation = SSAlertPresentAnimation(navigationViewController: nav, animationView: customView, canPanDimiss: canPanDimiss)
self.navigationController!.transitioningDelegate = animation
self.navigationController!.modalPresentationCapturesStatusBarAppearance = true
self.presentAnimation = animation
self.toViewContrller = animaionVC
}
open override func layoutSubviews() {
super.layoutSubviews()
backgroundMask?.frame = CGRect(x: 0, y: 0, width: onView.ss_w, height: onView.ss_h)
customView.ss_x = customEdgeInsets.left
customView.ss_y = customEdgeInsets.top
customView.ss_w = frame.width - customEdgeInsets.left - customEdgeInsets.right
customView.ss_h = frame.height - customEdgeInsets.top - customEdgeInsets.bottom
}
private func makeUI() {
if maskType != .none {
let backgroundMask = UIButton()
backgroundMask.alpha = 0
onView.addSubview(backgroundMask)
backgroundMask.addTarget(self, action: #selector(backgroundMaskAction), for: .touchUpInside)
self.backgroundMask = backgroundMask
if maskType == .black {
backgroundMask.backgroundColor = UIColor.black.withAlphaComponent(0.3)
}
}
addSubview(customView)
onView.addSubview(self)
}
///
public func show(animated: Bool = true, completion: (() -> Void)? = nil) {
if navigationController != nil {
presentView(animated: animated, completion: completion)
} else {
showView(animated: animated, completion: completion)
}
}
///
public func hide(animated: Bool = true, completion: (() -> Void)? = nil) {
if navigationController != nil {
dimissView(animated: animated, completion: completion)
} else {
hideView(animated: animated, completion: completion)
}
}
///
/// - Parameter animated:
private func showView(animated: Bool = true, completion: (() -> Void)? = nil) {
isTouchMaskHideAnimated = animated
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.showAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated, completion: { _ in completion?() })
}
///
/// - Parameter animated:
private func hideView(animated: Bool = true, completion: (() -> Void)? = nil) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { [weak self] _ in guard let self = self else { return false }
completion?()
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
return false
}
}
///
/// - Parameter animated:
private func presentView(animated: Bool = true, completion: (() -> Void)? = nil) {
isTouchMaskHideAnimated = animated
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(showAnimation: {
[weak self] in guard let self = self else { return }
self.animation.showAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
self.presentAnimation?.setCompleteTransitionIsHide(isHide: false)
completion?()
}
})
self.presentAnimation?.duration = animation.animationDuration()
/// apppresnentmodalPresentationStyleCurrentContextpresentingVCtabbarVCtabbarVCdimisspresentedVCdimissdimisspresentigngVCdimiss,
/// definesPresentationContext = true,modalPresentationStyle = UIModalPresentationOverCurrentContextpresentigngVC self.navigationController,
/// UIModalPresentationOverCurrentContext UIModalPresentationCurrentContextstact
self.navigationController?.definesPresentationContext = true
self.navigationController?.modalPresentationStyle = .overCurrentContext
self.fromViewController?.present(self.navigationController!, animated: true, completion: nil)
if canPanDimiss {
dimissViewDragToHideAnimation(animated: animated)
}
}
///
/// - Parameter animated:
private func dimissView(animated: Bool = true, completion: (() -> Void)? = nil) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(hideAnimation: {
[weak self] in guard let self = self else { return }
self.animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
return self.presentAnimation!.setCompleteTransitionIsHide(isHide: true)
}
})
self.presentAnimation?.duration = animation.animationDuration()
self.toViewContrller?.dismiss(animated: animated, completion: {
[weak self] in guard let self = self else { return }
completion?()
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
})
}
private func dimissViewDragToHideAnimation(animated: Bool) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(hideAnimation: {
[weak self] in guard let self = self else { return }
self.animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
return self.presentAnimation!.setCompleteTransitionIsHide(isHide: true)
}
},endCompletion: {
[weak self] in guard let self = self else { return }
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
}, panDimissAnimation: { [weak self] point, frame in
guard let self = self else { return (0, 0.4) }
return self.animation.panToDimissTransilatePoint(point: point, panViewFrame: frame)
}, dimissAnimation: { [weak self] isCancel in guard let self = self else { return }
self.toViewContrller?.isDimiss = !isCancel
})
self.presentAnimation?.duration = animation.animationDuration()
}
public func refreshFrame(animated: Bool = true) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.refreshAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated, completion: nil)
}
@objc private func backgroundMaskAction() {
if canTouchMaskHide {
if fromViewController != nil {
dimissView(animated: isTouchMaskHideAnimated ?? false)
} else {
hideView(animated: isTouchMaskHideAnimated ?? false)
}
}
}
private func superViewController(view: UIView) -> UIViewController? {
var next = view.next
while next != nil {
if next is UIViewController {
return next as? UIViewController
} else {
next = next!.next
}
}
return nil
}
public func observeHideCompletion(completion: (() -> Void)?) {
let completionData = HideCompletionData()
completionData.completion = completion
self.hideCompletions.append(completionData)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -1,127 +0,0 @@
//
// SSAlertViewExtention.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
extension SSAlertView {
@discardableResult
public class func alertView(style: SSAlertAction.Style, title: String? = nil, message: String? = nil, cancelButton: String? = nil, otherButtons: [String] = [], onView: UIView, onTrigger:((Int) -> Void)? = nil) -> SSAlertView {
var actionArray = [SSAlertAction]()
var cancelAction: SSAlertAction? = nil
var alertView: SSAlertView?
if style == .alert {
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
}
if style == .actionSheet {
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
}
let customView = SSAlertCommonView(title: title, message: message, style: style, actions: actionArray)
customView.backgroundColor = .white
alertView = SSAlertView(customView: customView, onView: onView, animation: SSAlertDefaultAnmation(state: style == .actionSheet ? .fromBottom : .fromCenter), maskType: .black)
alertView!.canTouchMaskHide = false
if style == .alert {
alertView!.layer.cornerRadius = 10
alertView!.layer.masksToBounds = true
}
return alertView!
}
@discardableResult
public class func modalAlertView(style: SSAlertAction.Style, title: String? = nil, message: String? = nil, cancelButton: String? = nil, otherButtons: [String] = [], fromViewController: UIViewController, navigationViewControllerClass: UINavigationController.Type = UINavigationController.self, onTrigger:((Int) -> Void)? = nil) -> SSAlertView {
var actionArray = [SSAlertAction]()
var cancelAction: SSAlertAction? = nil
var alertView: SSAlertView?
if style == .alert {
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
}
if style == .actionSheet {
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
}
let customView = SSAlertCommonView(title: title, message: message, style: style, actions: actionArray)
customView.backgroundColor = .white
alertView = SSAlertView(customView: customView,fromViewController: fromViewController, animation: SSAlertDefaultAnmation(state: style == .actionSheet ? .fromBottom : .fromCenter), navigationControllerClass: navigationViewControllerClass, maskType: .black)
alertView!.canTouchMaskHide = false
if style == .alert {
alertView!.layer.cornerRadius = 10
alertView!.layer.masksToBounds = true
}
return alertView!
}
}

View File

@ -1,132 +0,0 @@
//
// UIViewFrameExtension.swift
// Pods-SSAlertSwiftDemo
//
// Created by yangsq on 2021/8/13.
//
import UIKit
public extension UIView {
var ss_size: CGSize {
get {
frame.size
}
set {
var frame = frame
frame.size = newValue
self.frame = frame
}
}
var ss_origin: CGPoint {
get {
frame.origin
}
set {
var frame = frame
frame.origin = newValue
self.frame = frame
}
}
var ss_center: CGPoint {
get {
center
}
set {
center = newValue
}
}
var ss_x: CGFloat {
get {
frame.origin.x
}
set {
var frame = frame
frame.origin.x = newValue
self.frame = frame
}
}
var ss_maxX: CGFloat {
get {
frame.maxX
}
}
var ss_y: CGFloat {
get {
frame.origin.y
}
set {
var frame = frame
frame.origin.y = newValue
self.frame = frame
}
}
var ss_maxY: CGFloat {
get {
frame.maxY
}
set {
var frame = frame
frame.origin.y = newValue
self.frame = frame
}
}
var ss_w: CGFloat {
get {
frame.width
}
set {
var frame = frame
frame.size.width = newValue
self.frame = frame
}
}
var ss_h: CGFloat {
get {
frame.height
}
set {
var frame = frame
frame.size.height = newValue
self.frame = frame
}
}
var ss_centerX: CGFloat {
get {
center.x
}
set {
var center = center
center.x = newValue
self.center = center
}
}
var ss_centerY: CGFloat {
get {
center.y
}
set {
var center = center
center.y = newValue
self.center = center
}
}
func ss_moveToCenter() {
if let superV = superview {
ss_center = CGPoint(x: superV.frame.width / 2, y: superV.frame.height / 2)
}
}
}

View File

@ -1,19 +0,0 @@
Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,155 +0,0 @@
<img src="https://snapkit.github.io/SnapKit/images/banner.jpg" alt="" />
SnapKit is a DSL to make Auto Layout easy on both iOS and OS X.
[![Build Status](https://travis-ci.org/SnapKit/SnapKit.svg)](https://travis-ci.org/SnapKit/SnapKit)
[![Platform](https://img.shields.io/cocoapods/p/SnapKit.svg?style=flat)](https://github.com/SnapKit/SnapKit)
[![Cocoapods Compatible](https://img.shields.io/cocoapods/v/SnapKit.svg)](https://cocoapods.org/pods/SnapKit)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
#### ⚠️ **To use with Swift 4.x please ensure you are using >= 4.0.0** ⚠️
#### ⚠️ **To use with Swift 5.x please ensure you are using >= 5.0.0** ⚠️
## Contents
- [Requirements](#requirements)
- [Migration Guides](#migration-guides)
- [Communication](#communication)
- [Installation](#installation)
- [Usage](#usage)
- [Credits](#credits)
- [License](#license)
## Requirements
- iOS 12.0+ / Mac OS X 10.13+ / tvOS 10.0+
- Xcode 10.0+
- Swift 4.0+
## Migration Guides
- [SnapKit 3.0 Migration Guide](Documentation/SnapKit%203.0%20Migration%20Guide.md)
## Communication
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/snapkit). (Tag 'snapkit')
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/snapkit).
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## Installation
### CocoaPods
[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
```bash
$ gem install cocoapods
```
> CocoaPods 1.1.0+ is required to build SnapKit 4.0.0+.
To integrate SnapKit into your Xcode project using CocoaPods, specify it in your `Podfile`:
```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
pod 'SnapKit', '~> 5.7.0'
end
```
Then, run the following command:
```bash
$ pod install
```
### Carthage
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
```bash
$ brew update
$ brew install carthage
```
To integrate SnapKit into your Xcode project using Carthage, specify it in your `Cartfile`:
```ogdl
github "SnapKit/SnapKit" ~> 5.0.0
```
Run `carthage update` to build the framework and drag the built `SnapKit.framework` into your Xcode project.
### Swift Package Manager
[Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. Its integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
> Xcode 11+ is required to build SnapKit using Swift Package Manager.
To integrate SnapKit into your Xcode project using Swift Package Manager, add it to the dependencies value of your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/SnapKit/SnapKit.git", .upToNextMajor(from: "5.0.1"))
]
```
### Manually
If you prefer not to use either of the aforementioned dependency managers, you can integrate SnapKit into your project manually.
---
## Usage
### Quick Start
```swift
import SnapKit
class MyViewController: UIViewController {
lazy var box = UIView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(box)
box.backgroundColor = .green
box.snp.makeConstraints { (make) -> Void in
make.width.height.equalTo(50)
make.center.equalTo(self.view)
}
}
}
```
### Playground
You can try SnapKit in Playground.
**Note:**
> To try SnapKit in playground, open `SnapKit.xcworkspace` and build SnapKit.framework for any simulator first.
### Resources
- [Documentation](https://snapkit.github.io/SnapKit/docs/)
- [F.A.Q.](https://snapkit.github.io/SnapKit/faq/)
## Credits
- Robert Payne ([@robertjpayne](https://twitter.com/robertjpayne))
- Many other contributors
## License
SnapKit is released under the MIT license. See LICENSE for details.

View File

@ -1,341 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public final class Constraint {
internal let sourceLocation: (String, UInt)
internal let label: String?
private let from: ConstraintItem
private let to: ConstraintItem
private let relation: ConstraintRelation
private let multiplier: ConstraintMultiplierTarget
private var constant: ConstraintConstantTarget {
didSet {
self.updateConstantAndPriorityIfNeeded()
}
}
private var priority: ConstraintPriorityTarget {
didSet {
self.updateConstantAndPriorityIfNeeded()
}
}
public var layoutConstraints: [LayoutConstraint]
public var isActive: Bool {
set {
if newValue {
activate()
}
else {
deactivate()
}
}
get {
for layoutConstraint in self.layoutConstraints {
if layoutConstraint.isActive {
return true
}
}
return false
}
}
// MARK: Initialization
internal init(from: ConstraintItem,
to: ConstraintItem,
relation: ConstraintRelation,
sourceLocation: (String, UInt),
label: String?,
multiplier: ConstraintMultiplierTarget,
constant: ConstraintConstantTarget,
priority: ConstraintPriorityTarget) {
self.from = from
self.to = to
self.relation = relation
self.sourceLocation = sourceLocation
self.label = label
self.multiplier = multiplier
self.constant = constant
self.priority = priority
self.layoutConstraints = []
// get attributes
let layoutFromAttributes = self.from.attributes.layoutAttributes
let layoutToAttributes = self.to.attributes.layoutAttributes
// get layout from
let layoutFrom = self.from.layoutConstraintItem!
// get relation
let layoutRelation = self.relation.layoutRelation
for layoutFromAttribute in layoutFromAttributes {
// get layout to attribute
let layoutToAttribute: LayoutAttribute
#if canImport(UIKit)
if layoutToAttributes.count > 0 {
if self.from.attributes == .edges && self.to.attributes == .margins {
switch layoutFromAttribute {
case .left:
layoutToAttribute = .leftMargin
case .right:
layoutToAttribute = .rightMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
} else if self.from.attributes == .margins && self.to.attributes == .edges {
switch layoutFromAttribute {
case .leftMargin:
layoutToAttribute = .left
case .rightMargin:
layoutToAttribute = .right
case .topMargin:
layoutToAttribute = .top
case .bottomMargin:
layoutToAttribute = .bottom
default:
fatalError()
}
} else if self.from.attributes == .directionalEdges && self.to.attributes == .directionalMargins {
switch layoutFromAttribute {
case .leading:
layoutToAttribute = .leadingMargin
case .trailing:
layoutToAttribute = .trailingMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
} else if self.from.attributes == .directionalMargins && self.to.attributes == .directionalEdges {
switch layoutFromAttribute {
case .leadingMargin:
layoutToAttribute = .leading
case .trailingMargin:
layoutToAttribute = .trailing
case .topMargin:
layoutToAttribute = .top
case .bottomMargin:
layoutToAttribute = .bottom
default:
fatalError()
}
} else if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else {
layoutToAttribute = layoutToAttributes[0]
}
} else {
if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
} else {
layoutToAttribute = layoutFromAttribute
}
}
#else
if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else if layoutToAttributes.count > 0 {
layoutToAttribute = layoutToAttributes[0]
} else {
layoutToAttribute = layoutFromAttribute
}
#endif
// get layout constant
let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
// get layout to
var layoutTo: AnyObject? = self.to.target
// use superview if possible
if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
layoutTo = layoutFrom.superview
}
// create layout constraint
let layoutConstraint = LayoutConstraint(
item: layoutFrom,
attribute: layoutFromAttribute,
relatedBy: layoutRelation,
toItem: layoutTo,
attribute: layoutToAttribute,
multiplier: self.multiplier.constraintMultiplierTargetValue,
constant: layoutConstant
)
// set label
layoutConstraint.label = self.label
// set priority
layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
// set constraint
layoutConstraint.constraint = self
// append
self.layoutConstraints.append(layoutConstraint)
}
}
// MARK: Public
@available(*, deprecated, renamed:"activate()")
public func install() {
self.activate()
}
@available(*, deprecated, renamed:"deactivate()")
public func uninstall() {
self.deactivate()
}
public func activate() {
self.activateIfNeeded()
}
public func deactivate() {
self.deactivateIfNeeded()
}
@discardableResult
public func update(offset: ConstraintOffsetTarget) -> Constraint {
self.constant = offset.constraintOffsetTargetValue
return self
}
@discardableResult
public func update(inset: ConstraintInsetTarget) -> Constraint {
self.constant = inset.constraintInsetTargetValue
return self
}
#if canImport(UIKit)
@discardableResult
@available(iOS 11.0, tvOS 11.0, *)
public func update(inset: ConstraintDirectionalInsetTarget) -> Constraint {
self.constant = inset.constraintDirectionalInsetTargetValue
return self
}
#endif
@discardableResult
public func update(priority: ConstraintPriorityTarget) -> Constraint {
self.priority = priority.constraintPriorityTargetValue
return self
}
@discardableResult
public func update(priority: ConstraintPriority) -> Constraint {
self.priority = priority.value
return self
}
@available(*, deprecated, renamed:"update(offset:)")
public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
@available(*, deprecated, renamed:"update(inset:)")
public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) }
@available(*, deprecated, renamed:"update(priority:)")
public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityRequired() -> Void {}
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") }
// MARK: Internal
internal func updateConstantAndPriorityIfNeeded() {
for layoutConstraint in self.layoutConstraints {
let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
let requiredPriority = ConstraintPriority.required.value
if (layoutConstraint.priority.rawValue < requiredPriority), (self.priority.constraintPriorityTargetValue != requiredPriority) {
layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
}
}
}
internal func activateIfNeeded(updatingExisting: Bool = false) {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
if updatingExisting {
var existingLayoutConstraints: [LayoutConstraint] = []
for constraint in item.constraints {
existingLayoutConstraints += constraint.layoutConstraints
}
for layoutConstraint in layoutConstraints {
let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
guard let updateLayoutConstraint = existingLayoutConstraint else {
fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
}
let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
}
} else {
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
}
}
internal func deactivateIfNeeded() {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Deactivate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
NSLayoutConstraint.deactivate(layoutConstraints)
item.remove(constraints: [self])
}
}

View File

@ -1,203 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral {
typealias IntegerLiteralType = UInt
internal init(rawValue: UInt) {
self.rawValue = rawValue
}
internal init(_ rawValue: UInt) {
self.init(rawValue: rawValue)
}
internal init(nilLiteral: ()) {
self.rawValue = 0
}
internal init(integerLiteral rawValue: IntegerLiteralType) {
self.init(rawValue: rawValue)
}
internal private(set) var rawValue: UInt
internal static var allZeros: ConstraintAttributes { return 0 }
internal static func convertFromNilLiteral() -> ConstraintAttributes { return 0 }
internal var boolValue: Bool { return self.rawValue != 0 }
internal func toRaw() -> UInt { return self.rawValue }
internal static func fromRaw(_ raw: UInt) -> ConstraintAttributes? { return self.init(raw) }
internal static func fromMask(_ raw: UInt) -> ConstraintAttributes { return self.init(raw) }
// normal
internal static let none: ConstraintAttributes = 0
internal static let left: ConstraintAttributes = ConstraintAttributes(UInt(1) << 0)
internal static let top: ConstraintAttributes = ConstraintAttributes(UInt(1) << 1)
internal static let right: ConstraintAttributes = ConstraintAttributes(UInt(1) << 2)
internal static let bottom: ConstraintAttributes = ConstraintAttributes(UInt(1) << 3)
internal static let leading: ConstraintAttributes = ConstraintAttributes(UInt(1) << 4)
internal static let trailing: ConstraintAttributes = ConstraintAttributes(UInt(1) << 5)
internal static let width: ConstraintAttributes = ConstraintAttributes(UInt(1) << 6)
internal static let height: ConstraintAttributes = ConstraintAttributes(UInt(1) << 7)
internal static let centerX: ConstraintAttributes = ConstraintAttributes(UInt(1) << 8)
internal static let centerY: ConstraintAttributes = ConstraintAttributes(UInt(1) << 9)
internal static let lastBaseline: ConstraintAttributes = ConstraintAttributes(UInt(1) << 10)
@available(iOS 8.0, OSX 10.11, *)
internal static let firstBaseline: ConstraintAttributes = ConstraintAttributes(UInt(1) << 11)
@available(iOS 8.0, *)
internal static let leftMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 12)
@available(iOS 8.0, *)
internal static let rightMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 13)
@available(iOS 8.0, *)
internal static let topMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 14)
@available(iOS 8.0, *)
internal static let bottomMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 15)
@available(iOS 8.0, *)
internal static let leadingMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 16)
@available(iOS 8.0, *)
internal static let trailingMargin: ConstraintAttributes = ConstraintAttributes(UInt(1) << 17)
@available(iOS 8.0, *)
internal static let centerXWithinMargins: ConstraintAttributes = ConstraintAttributes(UInt(1) << 18)
@available(iOS 8.0, *)
internal static let centerYWithinMargins: ConstraintAttributes = ConstraintAttributes(UInt(1) << 19)
// aggregates
internal static let edges: ConstraintAttributes = [.horizontalEdges, .verticalEdges]
internal static let horizontalEdges: ConstraintAttributes = [.left, .right]
internal static let verticalEdges: ConstraintAttributes = [.top, .bottom]
internal static let directionalEdges: ConstraintAttributes = [.directionalHorizontalEdges, .directionalVerticalEdges]
internal static let directionalHorizontalEdges: ConstraintAttributes = [.leading, .trailing]
internal static let directionalVerticalEdges: ConstraintAttributes = [.top, .bottom]
internal static let size: ConstraintAttributes = [.width, .height]
internal static let center: ConstraintAttributes = [.centerX, .centerY]
@available(iOS 8.0, *)
internal static let margins: ConstraintAttributes = [.leftMargin, .topMargin, .rightMargin, .bottomMargin]
@available(iOS 8.0, *)
internal static let directionalMargins: ConstraintAttributes = [.leadingMargin, .topMargin, .trailingMargin, .bottomMargin]
@available(iOS 8.0, *)
internal static let centerWithinMargins: ConstraintAttributes = [.centerXWithinMargins, .centerYWithinMargins]
internal var layoutAttributes:[LayoutAttribute] {
var attrs = [LayoutAttribute]()
if (self.contains(ConstraintAttributes.left)) {
attrs.append(.left)
}
if (self.contains(ConstraintAttributes.top)) {
attrs.append(.top)
}
if (self.contains(ConstraintAttributes.right)) {
attrs.append(.right)
}
if (self.contains(ConstraintAttributes.bottom)) {
attrs.append(.bottom)
}
if (self.contains(ConstraintAttributes.leading)) {
attrs.append(.leading)
}
if (self.contains(ConstraintAttributes.trailing)) {
attrs.append(.trailing)
}
if (self.contains(ConstraintAttributes.width)) {
attrs.append(.width)
}
if (self.contains(ConstraintAttributes.height)) {
attrs.append(.height)
}
if (self.contains(ConstraintAttributes.centerX)) {
attrs.append(.centerX)
}
if (self.contains(ConstraintAttributes.centerY)) {
attrs.append(.centerY)
}
if (self.contains(ConstraintAttributes.lastBaseline)) {
attrs.append(.lastBaseline)
}
#if canImport(UIKit)
if (self.contains(ConstraintAttributes.firstBaseline)) {
attrs.append(.firstBaseline)
}
if (self.contains(ConstraintAttributes.leftMargin)) {
attrs.append(.leftMargin)
}
if (self.contains(ConstraintAttributes.rightMargin)) {
attrs.append(.rightMargin)
}
if (self.contains(ConstraintAttributes.topMargin)) {
attrs.append(.topMargin)
}
if (self.contains(ConstraintAttributes.bottomMargin)) {
attrs.append(.bottomMargin)
}
if (self.contains(ConstraintAttributes.leadingMargin)) {
attrs.append(.leadingMargin)
}
if (self.contains(ConstraintAttributes.trailingMargin)) {
attrs.append(.trailingMargin)
}
if (self.contains(ConstraintAttributes.centerXWithinMargins)) {
attrs.append(.centerXWithinMargins)
}
if (self.contains(ConstraintAttributes.centerYWithinMargins)) {
attrs.append(.centerYWithinMargins)
}
#endif
return attrs
}
}
internal func + (left: ConstraintAttributes, right: ConstraintAttributes) -> ConstraintAttributes {
return left.union(right)
}
internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.formUnion(right)
}
internal func -=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.subtract(right)
}
internal func ==(left: ConstraintAttributes, right: ConstraintAttributes) -> Bool {
return left.rawValue == right.rawValue
}

View File

@ -1,37 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
public typealias ConstraintInterfaceLayoutDirection = UIUserInterfaceLayoutDirection
#else
import AppKit
public typealias ConstraintInterfaceLayoutDirection = NSUserInterfaceLayoutDirection
#endif
public struct ConstraintConfig {
public static var interfaceLayoutDirection: ConstraintInterfaceLayoutDirection = .leftToRight
}

View File

@ -1,213 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintConstantTarget {
}
extension CGPoint: ConstraintConstantTarget {
}
extension CGSize: ConstraintConstantTarget {
}
extension ConstraintInsets: ConstraintConstantTarget {
}
#if canImport(UIKit)
@available(iOS 11.0, tvOS 11.0, *)
extension ConstraintDirectionalInsets: ConstraintConstantTarget {
}
#endif
extension ConstraintConstantTarget {
internal func constraintConstantTargetValueFor(layoutAttribute: LayoutAttribute) -> CGFloat {
if let value = self as? CGFloat {
return value
}
if let value = self as? Float {
return CGFloat(value)
}
if let value = self as? Double {
return CGFloat(value)
}
if let value = self as? Int {
return CGFloat(value)
}
if let value = self as? UInt {
return CGFloat(value)
}
if let value = self as? CGSize {
if layoutAttribute == .width {
return value.width
} else if layoutAttribute == .height {
return value.height
} else {
return 0.0
}
}
if let value = self as? CGPoint {
#if canImport(UIKit)
switch layoutAttribute {
case .left, .right, .leading, .trailing, .centerX, .leftMargin, .rightMargin, .leadingMargin, .trailingMargin, .centerXWithinMargins:
return value.x
case .top, .bottom, .centerY, .topMargin, .bottomMargin, .centerYWithinMargins, .lastBaseline, .firstBaseline:
return value.y
case .width, .height, .notAnAttribute:
return 0.0
#if swift(>=5.0)
@unknown default:
return 0.0
#endif
}
#else
switch layoutAttribute {
case .left, .right, .leading, .trailing, .centerX:
return value.x
case .top, .bottom, .centerY, .lastBaseline, .firstBaseline:
return value.y
case .width, .height, .notAnAttribute:
return 0.0
#if swift(>=5.0)
@unknown default:
return 0.0
#endif
}
#endif
}
if let value = self as? ConstraintInsets {
#if canImport(UIKit)
switch layoutAttribute {
case .left, .leftMargin:
return value.left
case .top, .topMargin, .firstBaseline:
return value.top
case .right, .rightMargin:
return -value.right
case .bottom, .bottomMargin, .lastBaseline:
return -value.bottom
case .leading, .leadingMargin:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? value.left : value.right
case .trailing, .trailingMargin:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? -value.right : -value.left
case .centerX, .centerXWithinMargins:
return (value.left - value.right) / 2
case .centerY, .centerYWithinMargins:
return (value.top - value.bottom) / 2
case .width:
return -(value.left + value.right)
case .height:
return -(value.top + value.bottom)
case .notAnAttribute:
return 0.0
#if swift(>=5.0)
@unknown default:
return 0.0
#endif
}
#else
switch layoutAttribute {
case .left:
return value.left
case .top, .firstBaseline:
return value.top
case .right:
return -value.right
case .bottom, .lastBaseline:
return -value.bottom
case .leading:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? value.left : value.right
case .trailing:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? -value.right : -value.left
case .centerX:
return (value.left - value.right) / 2
case .centerY:
return (value.top - value.bottom) / 2
case .width:
return -(value.left + value.right)
case .height:
return -(value.top + value.bottom)
case .notAnAttribute:
return 0.0
#if swift(>=5.0)
@unknown default:
return 0.0
#endif
}
#endif
}
#if canImport(UIKit)
if #available(iOS 11.0, tvOS 11.0, *), let value = self as? ConstraintDirectionalInsets {
switch layoutAttribute {
case .left, .leftMargin:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? value.leading : value.trailing
case .top, .topMargin, .firstBaseline:
return value.top
case .right, .rightMargin:
return (ConstraintConfig.interfaceLayoutDirection == .leftToRight) ? -value.trailing : -value.leading
case .bottom, .bottomMargin, .lastBaseline:
return -value.bottom
case .leading, .leadingMargin:
return value.leading
case .trailing, .trailingMargin:
return -value.trailing
case .centerX, .centerXWithinMargins:
return (value.leading - value.trailing) / 2
case .centerY, .centerYWithinMargins:
return (value.top - value.bottom) / 2
case .width:
return -(value.leading + value.trailing)
case .height:
return -(value.top + value.bottom)
case .notAnAttribute:
return 0.0
#if swift(>=5.0)
@unknown default:
return 0.0
#else
default:
return 0.0
#endif
}
}
#endif
return 0.0
}
}

View File

@ -1,209 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintDSL {
var target: AnyObject? { get }
func setLabel(_ value: String?)
func label() -> String?
}
extension ConstraintDSL {
public func setLabel(_ value: String?) {
objc_setAssociatedObject(self.target as Any, &labelKey, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
public func label() -> String? {
return objc_getAssociatedObject(self.target as Any, &labelKey) as? String
}
}
private var labelKey: UInt8 = 0
public protocol ConstraintBasicAttributesDSL : ConstraintDSL {
}
extension ConstraintBasicAttributesDSL {
// MARK: Basics
public var left: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.left)
}
public var top: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.top)
}
public var right: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.right)
}
public var bottom: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.bottom)
}
public var leading: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.leading)
}
public var trailing: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.trailing)
}
public var width: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.width)
}
public var height: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.height)
}
public var centerX: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.centerX)
}
public var centerY: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.centerY)
}
public var edges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.edges)
}
public var directionalEdges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.directionalEdges)
}
public var horizontalEdges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.horizontalEdges)
}
public var verticalEdges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.verticalEdges)
}
public var directionalHorizontalEdges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.directionalHorizontalEdges)
}
public var directionalVerticalEdges: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.directionalVerticalEdges)
}
public var size: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.size)
}
public var center: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.center)
}
}
public protocol ConstraintAttributesDSL : ConstraintBasicAttributesDSL {
}
extension ConstraintAttributesDSL {
// MARK: Baselines
@available(*, deprecated, renamed:"lastBaseline")
public var baseline: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.lastBaseline)
}
@available(iOS 8.0, OSX 10.11, *)
public var lastBaseline: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.lastBaseline)
}
@available(iOS 8.0, OSX 10.11, *)
public var firstBaseline: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.firstBaseline)
}
// MARK: Margins
@available(iOS 8.0, *)
public var leftMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.leftMargin)
}
@available(iOS 8.0, *)
public var topMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.topMargin)
}
@available(iOS 8.0, *)
public var rightMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.rightMargin)
}
@available(iOS 8.0, *)
public var bottomMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.bottomMargin)
}
@available(iOS 8.0, *)
public var leadingMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.leadingMargin)
}
@available(iOS 8.0, *)
public var trailingMargin: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.trailingMargin)
}
@available(iOS 8.0, *)
public var centerXWithinMargins: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.centerXWithinMargins)
}
@available(iOS 8.0, *)
public var centerYWithinMargins: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.centerYWithinMargins)
}
@available(iOS 8.0, *)
public var margins: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.margins)
}
@available(iOS 8.0, *)
public var directionalMargins: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.directionalMargins)
}
@available(iOS 8.0, *)
public var centerWithinMargins: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.centerWithinMargins)
}
}

View File

@ -1,69 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintDescription {
internal let item: LayoutConstraintItem
internal var attributes: ConstraintAttributes
internal var relation: ConstraintRelation? = nil
internal var sourceLocation: (String, UInt)? = nil
internal var label: String? = nil
internal var related: ConstraintItem? = nil
internal var multiplier: ConstraintMultiplierTarget = 1.0
internal var constant: ConstraintConstantTarget = 0.0
internal var priority: ConstraintPriorityTarget = 1000.0
internal lazy var constraint: Constraint? = {
guard let relation = self.relation,
let related = self.related,
let sourceLocation = self.sourceLocation else {
return nil
}
let from = ConstraintItem(target: self.item, attributes: self.attributes)
return Constraint(
from: from,
to: related,
relation: relation,
sourceLocation: sourceLocation,
label: self.label,
multiplier: self.multiplier,
constant: self.constant,
priority: self.priority
)
}()
// MARK: Initialization
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
self.item = item
self.attributes = attributes
}
}

View File

@ -1,49 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
public protocol ConstraintDirectionalInsetTarget: ConstraintConstantTarget {
}
@available(iOS 11.0, tvOS 11.0, *)
extension ConstraintDirectionalInsets: ConstraintDirectionalInsetTarget {
}
extension ConstraintDirectionalInsetTarget {
@available(iOS 11.0, tvOS 11.0, *)
internal var constraintDirectionalInsetTargetValue: ConstraintDirectionalInsets {
if let amount = self as? ConstraintDirectionalInsets {
return amount
} else {
return ConstraintDirectionalInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
}
}
}
#endif

View File

@ -1,34 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
@available(iOS 11.0, tvOS 11.0, *)
public typealias ConstraintDirectionalInsets = NSDirectionalEdgeInsets
#endif

View File

@ -1,72 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintInsetTarget: ConstraintConstantTarget {
}
extension Int: ConstraintInsetTarget {
}
extension UInt: ConstraintInsetTarget {
}
extension Float: ConstraintInsetTarget {
}
extension Double: ConstraintInsetTarget {
}
extension CGFloat: ConstraintInsetTarget {
}
extension ConstraintInsets: ConstraintInsetTarget {
}
extension ConstraintInsetTarget {
internal var constraintInsetTargetValue: ConstraintInsets {
if let amount = self as? ConstraintInsets {
return amount
} else if let amount = self as? Float {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? Double {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? CGFloat {
return ConstraintInsets(top: amount, left: amount, bottom: amount, right: amount)
} else if let amount = self as? Int {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? UInt {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else {
return ConstraintInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
}

View File

@ -1,35 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
public typealias ConstraintInsets = UIEdgeInsets
#else
public typealias ConstraintInsets = NSEdgeInsets
#endif

View File

@ -1,61 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public final class ConstraintItem {
internal weak var target: AnyObject?
internal let attributes: ConstraintAttributes
internal init(target: AnyObject?, attributes: ConstraintAttributes) {
self.target = target
self.attributes = attributes
}
internal var layoutConstraintItem: LayoutConstraintItem? {
return self.target as? LayoutConstraintItem
}
}
public func ==(lhs: ConstraintItem, rhs: ConstraintItem) -> Bool {
// pointer equality
guard lhs !== rhs else {
return true
}
// must both have valid targets and identical attributes
guard let target1 = lhs.target,
let target2 = rhs.target,
target1 === target2 && lhs.attributes == rhs.attributes else {
return false
}
return true
}

View File

@ -1,36 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#endif
@available(iOS 9.0, OSX 10.11, *)
public extension ConstraintLayoutGuide {
var snp: ConstraintLayoutGuideDSL {
return ConstraintLayoutGuideDSL(guide: self)
}
}

View File

@ -1,37 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
@available(iOS 9.0, *)
public typealias ConstraintLayoutGuide = UILayoutGuide
#else
@available(OSX 10.11, *)
public typealias ConstraintLayoutGuide = NSLayoutGuide
#endif

View File

@ -1,66 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
@available(iOS 9.0, OSX 10.11, *)
public struct ConstraintLayoutGuideDSL: ConstraintAttributesDSL {
@discardableResult
public func prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
return ConstraintMaker.prepareConstraints(item: self.guide, closure: closure)
}
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.guide, closure: closure)
}
public func remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.remakeConstraints(item: self.guide, closure: closure)
}
public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.updateConstraints(item: self.guide, closure: closure)
}
public func removeConstraints() {
ConstraintMaker.removeConstraints(item: self.guide)
}
public var target: AnyObject? {
return self.guide
}
internal let guide: ConstraintLayoutGuide
internal init(guide: ConstraintLayoutGuide) {
self.guide = guide
}
}

View File

@ -1,36 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
@available(iOS 8.0, *)
public typealias ConstraintLayoutSupport = UILayoutSupport
#else
public class ConstraintLayoutSupport {}
#endif

View File

@ -1,56 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
@available(iOS 8.0, *)
public struct ConstraintLayoutSupportDSL: ConstraintDSL {
public var target: AnyObject? {
return self.support
}
internal let support: ConstraintLayoutSupport
internal init(support: ConstraintLayoutSupport) {
self.support = support
}
public var top: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.top)
}
public var bottom: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.bottom)
}
public var height: ConstraintItem {
return ConstraintItem(target: self.target, attributes: ConstraintAttributes.height)
}
}

View File

@ -1,224 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintMaker {
public var left: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.left)
}
public var top: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.top)
}
public var bottom: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.bottom)
}
public var right: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.right)
}
public var leading: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.leading)
}
public var trailing: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.trailing)
}
public var width: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.width)
}
public var height: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.height)
}
public var centerX: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.centerX)
}
public var centerY: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.centerY)
}
@available(*, deprecated, renamed:"lastBaseline")
public var baseline: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.lastBaseline)
}
public var lastBaseline: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.lastBaseline)
}
@available(iOS 8.0, OSX 10.11, *)
public var firstBaseline: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.firstBaseline)
}
@available(iOS 8.0, *)
public var leftMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.leftMargin)
}
@available(iOS 8.0, *)
public var rightMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.rightMargin)
}
@available(iOS 8.0, *)
public var topMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.topMargin)
}
@available(iOS 8.0, *)
public var bottomMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.bottomMargin)
}
@available(iOS 8.0, *)
public var leadingMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.leadingMargin)
}
@available(iOS 8.0, *)
public var trailingMargin: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.trailingMargin)
}
@available(iOS 8.0, *)
public var centerXWithinMargins: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.centerXWithinMargins)
}
@available(iOS 8.0, *)
public var centerYWithinMargins: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.centerYWithinMargins)
}
public var edges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.edges)
}
public var horizontalEdges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.horizontalEdges)
}
public var verticalEdges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.verticalEdges)
}
public var directionalEdges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.directionalEdges)
}
public var directionalHorizontalEdges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.directionalHorizontalEdges)
}
public var directionalVerticalEdges: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.directionalVerticalEdges)
}
public var size: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.size)
}
public var center: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.center)
}
@available(iOS 8.0, *)
public var margins: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.margins)
}
@available(iOS 8.0, *)
public var directionalMargins: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.directionalMargins)
}
@available(iOS 8.0, *)
public var centerWithinMargins: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.centerWithinMargins)
}
public let item: LayoutConstraintItem
private var descriptions = [ConstraintDescription]()
internal init(item: LayoutConstraintItem) {
self.item = item
self.item.prepare()
}
internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
let description = ConstraintDescription(item: self.item, attributes: attributes)
self.descriptions.append(description)
return ConstraintMakerExtendable(description)
}
internal static func prepareConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
let maker = ConstraintMaker(item: item)
closure(maker)
var constraints: [Constraint] = []
for description in maker.descriptions {
guard let constraint = description.constraint else {
continue
}
constraints.append(constraint)
}
return constraints
}
internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
let constraints = prepareConstraints(item: item, closure: closure)
for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: false)
}
}
internal static func remakeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
self.removeConstraints(item: item)
self.makeConstraints(item: item, closure: closure)
}
internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
guard item.constraints.count > 0 else {
self.makeConstraints(item: item, closure: closure)
return
}
let constraints = prepareConstraints(item: item, closure: closure)
for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: true)
}
}
internal static func removeConstraints(item: LayoutConstraintItem) {
let constraints = item.constraints
for constraint in constraints {
constraint.deactivateIfNeeded()
}
}
}

View File

@ -1,64 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintMakerEditable: ConstraintMakerPrioritizable {
@discardableResult
public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
self.description.multiplier = amount
return self
}
@discardableResult
public func dividedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
return self.multipliedBy(1.0 / amount.constraintMultiplierTargetValue)
}
@discardableResult
public func offset(_ amount: ConstraintOffsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintOffsetTargetValue
return self
}
@discardableResult
public func inset(_ amount: ConstraintInsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintInsetTargetValue
return self
}
#if canImport(UIKit)
@discardableResult
@available(iOS 11.0, tvOS 11.0, *)
public func inset(_ amount: ConstraintDirectionalInsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintDirectionalInsetTargetValue
return self
}
#endif
}

View File

@ -1,195 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintMakerExtendable: ConstraintMakerRelatable {
public var left: ConstraintMakerExtendable {
self.description.attributes += .left
return self
}
public var top: ConstraintMakerExtendable {
self.description.attributes += .top
return self
}
public var bottom: ConstraintMakerExtendable {
self.description.attributes += .bottom
return self
}
public var right: ConstraintMakerExtendable {
self.description.attributes += .right
return self
}
public var leading: ConstraintMakerExtendable {
self.description.attributes += .leading
return self
}
public var trailing: ConstraintMakerExtendable {
self.description.attributes += .trailing
return self
}
public var width: ConstraintMakerExtendable {
self.description.attributes += .width
return self
}
public var height: ConstraintMakerExtendable {
self.description.attributes += .height
return self
}
public var centerX: ConstraintMakerExtendable {
self.description.attributes += .centerX
return self
}
public var centerY: ConstraintMakerExtendable {
self.description.attributes += .centerY
return self
}
@available(*, deprecated, renamed:"lastBaseline")
public var baseline: ConstraintMakerExtendable {
self.description.attributes += .lastBaseline
return self
}
public var lastBaseline: ConstraintMakerExtendable {
self.description.attributes += .lastBaseline
return self
}
@available(iOS 8.0, OSX 10.11, *)
public var firstBaseline: ConstraintMakerExtendable {
self.description.attributes += .firstBaseline
return self
}
@available(iOS 8.0, *)
public var leftMargin: ConstraintMakerExtendable {
self.description.attributes += .leftMargin
return self
}
@available(iOS 8.0, *)
public var rightMargin: ConstraintMakerExtendable {
self.description.attributes += .rightMargin
return self
}
@available(iOS 8.0, *)
public var topMargin: ConstraintMakerExtendable {
self.description.attributes += .topMargin
return self
}
@available(iOS 8.0, *)
public var bottomMargin: ConstraintMakerExtendable {
self.description.attributes += .bottomMargin
return self
}
@available(iOS 8.0, *)
public var leadingMargin: ConstraintMakerExtendable {
self.description.attributes += .leadingMargin
return self
}
@available(iOS 8.0, *)
public var trailingMargin: ConstraintMakerExtendable {
self.description.attributes += .trailingMargin
return self
}
@available(iOS 8.0, *)
public var centerXWithinMargins: ConstraintMakerExtendable {
self.description.attributes += .centerXWithinMargins
return self
}
@available(iOS 8.0, *)
public var centerYWithinMargins: ConstraintMakerExtendable {
self.description.attributes += .centerYWithinMargins
return self
}
public var edges: ConstraintMakerExtendable {
self.description.attributes += .edges
return self
}
public var horizontalEdges: ConstraintMakerExtendable {
self.description.attributes += .horizontalEdges
return self
}
public var verticalEdges: ConstraintMakerExtendable {
self.description.attributes += .verticalEdges
return self
}
public var directionalEdges: ConstraintMakerExtendable {
self.description.attributes += .directionalEdges
return self
}
public var directionalHorizontalEdges: ConstraintMakerExtendable {
self.description.attributes += .directionalHorizontalEdges
return self
}
public var directionalVerticalEdges: ConstraintMakerExtendable {
self.description.attributes += .directionalVerticalEdges
return self
}
public var size: ConstraintMakerExtendable {
self.description.attributes += .size
return self
}
@available(iOS 8.0, *)
public var margins: ConstraintMakerExtendable {
self.description.attributes += .margins
return self
}
@available(iOS 8.0, *)
public var directionalMargins: ConstraintMakerExtendable {
self.description.attributes += .directionalMargins
return self
}
@available(iOS 8.0, *)
public var centerWithinMargins: ConstraintMakerExtendable {
self.description.attributes += .centerWithinMargins
return self
}
}

View File

@ -1,49 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintMakerFinalizable {
internal let description: ConstraintDescription
internal init(_ description: ConstraintDescription) {
self.description = description
}
@discardableResult
public func labeled(_ label: String) -> ConstraintMakerFinalizable {
self.description.label = label
return self
}
public var constraint: Constraint {
return self.description.constraint!
}
}

View File

@ -1,70 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
@available(*, deprecated, message:"Use ConstraintMakerPrioritizable instead.")
public typealias ConstraintMakerPriortizable = ConstraintMakerPrioritizable
public class ConstraintMakerPrioritizable: ConstraintMakerFinalizable {
@discardableResult
public func priority(_ amount: ConstraintPriority) -> ConstraintMakerFinalizable {
self.description.priority = amount.value
return self
}
@discardableResult
public func priority(_ amount: ConstraintPriorityTarget) -> ConstraintMakerFinalizable {
self.description.priority = amount
return self
}
@available(*, deprecated, message:"Use priority(.required) instead.")
@discardableResult
public func priorityRequired() -> ConstraintMakerFinalizable {
return self.priority(.required)
}
@available(*, deprecated, message:"Use priority(.high) instead.")
@discardableResult
public func priorityHigh() -> ConstraintMakerFinalizable {
return self.priority(.high)
}
@available(*, deprecated, message:"Use priority(.medium) instead.")
@discardableResult
public func priorityMedium() -> ConstraintMakerFinalizable {
return self.priority(.medium)
}
@available(*, deprecated, message:"Use priority(.low) instead.")
@discardableResult
public func priorityLow() -> ConstraintMakerFinalizable {
return self.priority(.low)
}
}

View File

@ -1,57 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
extension ConstraintMakerRelatable {
@discardableResult
public func equalToSuperview<T: ConstraintRelatableTarget>(_ closure: (ConstraintView) -> T, _ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `equalToSuperview`.")
}
return self.relatedTo(closure(other), relation: .equal, file: file, line: line)
}
@discardableResult
public func lessThanOrEqualToSuperview<T: ConstraintRelatableTarget>(_ closure: (ConstraintView) -> T, _ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `lessThanOrEqualToSuperview`.")
}
return self.relatedTo(closure(other), relation: .lessThanOrEqual, file: file, line: line)
}
@discardableResult
public func greaterThanOrEqualToSuperview<T: ConstraintRelatableTarget>(_ closure: (ConstraintView) -> T, _ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `greaterThanOrEqualToSuperview`.")
}
return self.relatedTo(closure(other), relation: .greaterThanOrEqual, file: file, line: line)
}
}

View File

@ -1,115 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class ConstraintMakerRelatable {
internal let description: ConstraintDescription
internal init(_ description: ConstraintDescription) {
self.description = description
}
internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
let related: ConstraintItem
let constant: ConstraintConstantTarget
if let other = other as? ConstraintItem {
guard other.attributes == ConstraintAttributes.none ||
other.attributes.layoutAttributes.count <= 1 ||
other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
other.attributes == .edges && self.description.attributes == .margins ||
other.attributes == .margins && self.description.attributes == .edges ||
other.attributes == .directionalEdges && self.description.attributes == .directionalMargins ||
other.attributes == .directionalMargins && self.description.attributes == .directionalEdges else {
fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
}
related = other
constant = 0.0
} else if let other = other as? ConstraintView {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else if let other = other as? ConstraintConstantTarget {
related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
constant = other
} else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else {
fatalError("Invalid constraint. (\(file), \(line))")
}
let editable = ConstraintMakerEditable(self.description)
editable.description.sourceLocation = (file, line)
editable.description.relation = relation
editable.description.related = related
editable.description.constant = constant
return editable
}
@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
@discardableResult
public func equalToSuperview(_ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `equalToSuperview`.")
}
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
@discardableResult
public func lessThanOrEqualTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .lessThanOrEqual, file: file, line: line)
}
@discardableResult
public func lessThanOrEqualToSuperview(_ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `lessThanOrEqualToSuperview`.")
}
return self.relatedTo(other, relation: .lessThanOrEqual, file: file, line: line)
}
@discardableResult
public func greaterThanOrEqualTo(_ other: ConstraintRelatableTarget, _ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .greaterThanOrEqual, file: file, line: line)
}
@discardableResult
public func greaterThanOrEqualToSuperview(_ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
guard let other = self.description.item.superview else {
fatalError("Expected superview but found nil when attempting make constraint `greaterThanOrEqualToSuperview`.")
}
return self.relatedTo(other, relation: .greaterThanOrEqual, file: file, line: line)
}
}

View File

@ -1,75 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintMultiplierTarget {
var constraintMultiplierTargetValue: CGFloat { get }
}
extension Int: ConstraintMultiplierTarget {
public var constraintMultiplierTargetValue: CGFloat {
return CGFloat(self)
}
}
extension UInt: ConstraintMultiplierTarget {
public var constraintMultiplierTargetValue: CGFloat {
return CGFloat(self)
}
}
extension Float: ConstraintMultiplierTarget {
public var constraintMultiplierTargetValue: CGFloat {
return CGFloat(self)
}
}
extension Double: ConstraintMultiplierTarget {
public var constraintMultiplierTargetValue: CGFloat {
return CGFloat(self)
}
}
extension CGFloat: ConstraintMultiplierTarget {
public var constraintMultiplierTargetValue: CGFloat {
return self
}
}

View File

@ -1,69 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintOffsetTarget: ConstraintConstantTarget {
}
extension Int: ConstraintOffsetTarget {
}
extension UInt: ConstraintOffsetTarget {
}
extension Float: ConstraintOffsetTarget {
}
extension Double: ConstraintOffsetTarget {
}
extension CGFloat: ConstraintOffsetTarget {
}
extension ConstraintOffsetTarget {
internal var constraintOffsetTargetValue: CGFloat {
let offset: CGFloat
if let amount = self as? Float {
offset = CGFloat(amount)
} else if let amount = self as? Double {
offset = CGFloat(amount)
} else if let amount = self as? CGFloat {
offset = CGFloat(amount)
} else if let amount = self as? Int {
offset = CGFloat(amount)
} else if let amount = self as? UInt {
offset = CGFloat(amount)
} else {
offset = 0.0
}
return offset
}
}

View File

@ -1,77 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public struct ConstraintPriority : ExpressibleByFloatLiteral, Equatable, Strideable {
public typealias FloatLiteralType = Float
public let value: Float
public init(floatLiteral value: Float) {
self.value = value
}
public init(_ value: Float) {
self.value = value
}
public static var required: ConstraintPriority {
return 1000.0
}
public static var high: ConstraintPriority {
return 750.0
}
public static var medium: ConstraintPriority {
#if os(OSX)
return 501.0
#else
return 500.0
#endif
}
public static var low: ConstraintPriority {
return 250.0
}
public static func ==(lhs: ConstraintPriority, rhs: ConstraintPriority) -> Bool {
return lhs.value == rhs.value
}
// MARK: Strideable
public func advanced(by n: FloatLiteralType) -> ConstraintPriority {
return ConstraintPriority(floatLiteral: value + n)
}
public func distance(to other: ConstraintPriority) -> FloatLiteralType {
return other.value - value
}
}

View File

@ -1,85 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintPriorityTarget {
var constraintPriorityTargetValue: Float { get }
}
extension Int: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return Float(self)
}
}
extension UInt: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return Float(self)
}
}
extension Float: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return self
}
}
extension Double: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return Float(self)
}
}
extension CGFloat: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return Float(self)
}
}
#if canImport(UIKit)
extension UILayoutPriority: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return self.rawValue
}
}
#endif

View File

@ -1,72 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol ConstraintRelatableTarget {
}
extension Int: ConstraintRelatableTarget {
}
extension UInt: ConstraintRelatableTarget {
}
extension Float: ConstraintRelatableTarget {
}
extension Double: ConstraintRelatableTarget {
}
extension CGFloat: ConstraintRelatableTarget {
}
extension CGSize: ConstraintRelatableTarget {
}
extension CGPoint: ConstraintRelatableTarget {
}
extension ConstraintInsets: ConstraintRelatableTarget {
}
#if canImport(UIKit)
@available(iOS 11.0, tvOS 11.0, *)
extension ConstraintDirectionalInsets: ConstraintRelatableTarget {
}
#endif
extension ConstraintItem: ConstraintRelatableTarget {
}
extension ConstraintView: ConstraintRelatableTarget {
}
@available(iOS 9.0, OSX 10.11, *)
extension ConstraintLayoutGuide: ConstraintRelatableTarget {
}

View File

@ -1,48 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
internal enum ConstraintRelation : Int {
case equal = 1
case lessThanOrEqual
case greaterThanOrEqual
internal var layoutRelation: LayoutRelation {
get {
switch(self) {
case .equal:
return .equal
case .lessThanOrEqual:
return .lessThanOrEqual
case .greaterThanOrEqual:
return .greaterThanOrEqual
}
}
}
}

View File

@ -1,152 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public extension ConstraintView {
@available(*, deprecated, renamed:"snp.left")
var snp_left: ConstraintItem { return self.snp.left }
@available(*, deprecated, renamed:"snp.top")
var snp_top: ConstraintItem { return self.snp.top }
@available(*, deprecated, renamed:"snp.right")
var snp_right: ConstraintItem { return self.snp.right }
@available(*, deprecated, renamed:"snp.bottom")
var snp_bottom: ConstraintItem { return self.snp.bottom }
@available(*, deprecated, renamed:"snp.leading")
var snp_leading: ConstraintItem { return self.snp.leading }
@available(*, deprecated, renamed:"snp.trailing")
var snp_trailing: ConstraintItem { return self.snp.trailing }
@available(*, deprecated, renamed:"snp.width")
var snp_width: ConstraintItem { return self.snp.width }
@available(*, deprecated, renamed:"snp.height")
var snp_height: ConstraintItem { return self.snp.height }
@available(*, deprecated, renamed:"snp.centerX")
var snp_centerX: ConstraintItem { return self.snp.centerX }
@available(*, deprecated, renamed:"snp.centerY")
var snp_centerY: ConstraintItem { return self.snp.centerY }
@available(*, deprecated, renamed:"snp.baseline")
var snp_baseline: ConstraintItem { return self.snp.baseline }
@available(*, deprecated, renamed:"snp.lastBaseline")
@available(iOS 8.0, OSX 10.11, *)
var snp_lastBaseline: ConstraintItem { return self.snp.lastBaseline }
@available(iOS, deprecated, renamed:"snp.firstBaseline")
@available(iOS 8.0, OSX 10.11, *)
var snp_firstBaseline: ConstraintItem { return self.snp.firstBaseline }
@available(iOS, deprecated, renamed:"snp.leftMargin")
@available(iOS 8.0, *)
var snp_leftMargin: ConstraintItem { return self.snp.leftMargin }
@available(iOS, deprecated, renamed:"snp.topMargin")
@available(iOS 8.0, *)
var snp_topMargin: ConstraintItem { return self.snp.topMargin }
@available(iOS, deprecated, renamed:"snp.rightMargin")
@available(iOS 8.0, *)
var snp_rightMargin: ConstraintItem { return self.snp.rightMargin }
@available(iOS, deprecated, renamed:"snp.bottomMargin")
@available(iOS 8.0, *)
var snp_bottomMargin: ConstraintItem { return self.snp.bottomMargin }
@available(iOS, deprecated, renamed:"snp.leadingMargin")
@available(iOS 8.0, *)
var snp_leadingMargin: ConstraintItem { return self.snp.leadingMargin }
@available(iOS, deprecated, renamed:"snp.trailingMargin")
@available(iOS 8.0, *)
var snp_trailingMargin: ConstraintItem { return self.snp.trailingMargin }
@available(iOS, deprecated, renamed:"snp.centerXWithinMargins")
@available(iOS 8.0, *)
var snp_centerXWithinMargins: ConstraintItem { return self.snp.centerXWithinMargins }
@available(iOS, deprecated, renamed:"snp.centerYWithinMargins")
@available(iOS 8.0, *)
var snp_centerYWithinMargins: ConstraintItem { return self.snp.centerYWithinMargins }
@available(*, deprecated, renamed:"snp.edges")
var snp_edges: ConstraintItem { return self.snp.edges }
@available(*, deprecated, renamed:"snp.size")
var snp_size: ConstraintItem { return self.snp.size }
@available(*, deprecated, renamed:"snp.center")
var snp_center: ConstraintItem { return self.snp.center }
@available(iOS, deprecated, renamed:"snp.margins")
@available(iOS 8.0, *)
var snp_margins: ConstraintItem { return self.snp.margins }
@available(iOS, deprecated, renamed:"snp.centerWithinMargins")
@available(iOS 8.0, *)
var snp_centerWithinMargins: ConstraintItem { return self.snp.centerWithinMargins }
@available(*, deprecated, renamed:"snp.prepareConstraints(_:)")
func snp_prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
return self.snp.prepareConstraints(closure)
}
@available(*, deprecated, renamed:"snp.makeConstraints(_:)")
func snp_makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
self.snp.makeConstraints(closure)
}
@available(*, deprecated, renamed:"snp.remakeConstraints(_:)")
func snp_remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
self.snp.remakeConstraints(closure)
}
@available(*, deprecated, renamed:"snp.updateConstraints(_:)")
func snp_updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
self.snp.updateConstraints(closure)
}
@available(*, deprecated, renamed:"snp.removeConstraints()")
func snp_removeConstraints() {
self.snp.removeConstraints()
}
var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
}

View File

@ -1,35 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
#if canImport(UIKit)
public typealias ConstraintView = UIView
#else
public typealias ConstraintView = NSView
#endif

View File

@ -1,101 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public struct ConstraintViewDSL: ConstraintAttributesDSL {
@discardableResult
public func prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
return ConstraintMaker.prepareConstraints(item: self.view, closure: closure)
}
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
public func remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.remakeConstraints(item: self.view, closure: closure)
}
public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.updateConstraints(item: self.view, closure: closure)
}
public func removeConstraints() {
ConstraintMaker.removeConstraints(item: self.view)
}
public var contentHuggingHorizontalPriority: Float {
get {
return self.view.contentHuggingPriority(for: .horizontal).rawValue
}
nonmutating set {
self.view.setContentHuggingPriority(LayoutPriority(rawValue: newValue), for: .horizontal)
}
}
public var contentHuggingVerticalPriority: Float {
get {
return self.view.contentHuggingPriority(for: .vertical).rawValue
}
nonmutating set {
self.view.setContentHuggingPriority(LayoutPriority(rawValue: newValue), for: .vertical)
}
}
public var contentCompressionResistanceHorizontalPriority: Float {
get {
return self.view.contentCompressionResistancePriority(for: .horizontal).rawValue
}
nonmutating set {
self.view.setContentCompressionResistancePriority(LayoutPriority(rawValue: newValue), for: .horizontal)
}
}
public var contentCompressionResistanceVerticalPriority: Float {
get {
return self.view.contentCompressionResistancePriority(for: .vertical).rawValue
}
nonmutating set {
self.view.setContentCompressionResistancePriority(LayoutPriority(rawValue: newValue), for: .vertical)
}
}
public var target: AnyObject? {
return self.view
}
internal let view: ConstraintView
internal init(view: ConstraintView) {
self.view = view
}
}

View File

@ -1,169 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public extension LayoutConstraint {
override var description: String {
var description = "<"
description += descriptionForObject(self)
if let firstItem = conditionalOptional(from: self.firstItem) {
description += " \(descriptionForObject(firstItem))"
}
if self.firstAttribute != .notAnAttribute {
description += ".\(descriptionForAttribute(self.firstAttribute))"
}
description += " \(descriptionForRelation(self.relation))"
if let secondItem = self.secondItem {
description += " \(descriptionForObject(secondItem))"
}
if self.secondAttribute != .notAnAttribute {
description += ".\(descriptionForAttribute(self.secondAttribute))"
}
if self.multiplier != 1.0 {
description += " * \(self.multiplier)"
}
if self.secondAttribute == .notAnAttribute {
description += " \(self.constant)"
} else {
if self.constant > 0.0 {
description += " + \(self.constant)"
} else if self.constant < 0.0 {
description += " - \(abs(self.constant))"
}
}
if self.priority.rawValue != 1000.0 {
description += " ^\(self.priority)"
}
description += ">"
return description
}
}
private func descriptionForRelation(_ relation: LayoutRelation) -> String {
switch relation {
case .equal: return "=="
case .greaterThanOrEqual: return ">="
case .lessThanOrEqual: return "<="
#if swift(>=5.0)
@unknown default: return "unknown"
#endif
}
}
private func descriptionForAttribute(_ attribute: LayoutAttribute) -> String {
#if canImport(UIKit)
switch attribute {
case .notAnAttribute: return "notAnAttribute"
case .top: return "top"
case .left: return "left"
case .bottom: return "bottom"
case .right: return "right"
case .leading: return "leading"
case .trailing: return "trailing"
case .width: return "width"
case .height: return "height"
case .centerX: return "centerX"
case .centerY: return "centerY"
case .lastBaseline: return "lastBaseline"
case .firstBaseline: return "firstBaseline"
case .topMargin: return "topMargin"
case .leftMargin: return "leftMargin"
case .bottomMargin: return "bottomMargin"
case .rightMargin: return "rightMargin"
case .leadingMargin: return "leadingMargin"
case .trailingMargin: return "trailingMargin"
case .centerXWithinMargins: return "centerXWithinMargins"
case .centerYWithinMargins: return "centerYWithinMargins"
#if swift(>=5.0)
@unknown default: return "unknown"
#endif
}
#else
switch attribute {
case .notAnAttribute: return "notAnAttribute"
case .top: return "top"
case .left: return "left"
case .bottom: return "bottom"
case .right: return "right"
case .leading: return "leading"
case .trailing: return "trailing"
case .width: return "width"
case .height: return "height"
case .centerX: return "centerX"
case .centerY: return "centerY"
case .lastBaseline: return "lastBaseline"
case .firstBaseline: return "firstBaseline"
#if swift(>=5.0)
@unknown default: return "unknown"
#endif
}
#endif
}
private func conditionalOptional<T>(from object: Optional<T>) -> Optional<T> {
return object
}
private func conditionalOptional<T>(from object: T) -> Optional<T> {
return Optional.some(object)
}
private func descriptionForObject(_ object: AnyObject) -> String {
let pointerDescription = String(format: "%p", UInt(bitPattern: ObjectIdentifier(object)))
var desc = ""
desc += type(of: object).description()
if let object = object as? ConstraintView {
desc += ":\(object.snp.label() ?? pointerDescription)"
} else if let object = object as? LayoutConstraint {
desc += ":\(object.label ?? pointerDescription)"
} else {
desc += ":\(pointerDescription)"
}
if let object = object as? LayoutConstraint, let file = object.constraint?.sourceLocation.0, let line = object.constraint?.sourceLocation.1 {
desc += "@\((file as NSString).lastPathComponent)#\(line)"
}
desc += ""
return desc
}

View File

@ -1,61 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public class LayoutConstraint : NSLayoutConstraint {
public var label: String? {
get {
return self.identifier
}
set {
self.identifier = newValue
}
}
internal weak var constraint: Constraint? = nil
}
internal func ==(lhs: LayoutConstraint, rhs: LayoutConstraint) -> Bool {
// If firstItem or secondItem on either constraint has a dangling pointer
// this comparison can cause a crash. The solution for this is to ensure
// your layout code hold strong references to things like Views, LayoutGuides
// and LayoutAnchors as SnapKit will not keep strong references to any of these.
guard lhs.firstAttribute == rhs.firstAttribute &&
lhs.secondAttribute == rhs.secondAttribute &&
lhs.relation == rhs.relation &&
lhs.priority == rhs.priority &&
lhs.multiplier == rhs.multiplier &&
lhs.secondItem === rhs.secondItem &&
lhs.firstItem === rhs.firstItem else {
return false
}
return true
}

View File

@ -1,93 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public protocol LayoutConstraintItem: AnyObject {
}
@available(iOS 9.0, OSX 10.11, *)
extension ConstraintLayoutGuide : LayoutConstraintItem {
}
extension ConstraintView : LayoutConstraintItem {
}
extension LayoutConstraintItem {
internal func prepare() {
if let view = self as? ConstraintView {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
internal var superview: ConstraintView? {
if let view = self as? ConstraintView {
return view.superview
}
if #available(iOS 9.0, OSX 10.11, *), let guide = self as? ConstraintLayoutGuide {
return guide.owningView
}
return nil
}
internal var constraints: [Constraint] {
return self.constraintsSet.allObjects as! [Constraint]
}
internal func add(constraints: [Constraint]) {
let constraintsSet = self.constraintsSet
for constraint in constraints {
constraintsSet.add(constraint)
}
}
internal func remove(constraints: [Constraint]) {
let constraintsSet = self.constraintsSet
for constraint in constraints {
constraintsSet.remove(constraint)
}
}
private var constraintsSet: NSMutableSet {
let constraintsSet: NSMutableSet
if let existing = objc_getAssociatedObject(self, &constraintsKey) as? NSMutableSet {
constraintsSet = existing
} else {
constraintsSet = NSMutableSet()
objc_setAssociatedObject(self, &constraintsKey, constraintsSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return constraintsSet
}
}
private var constraintsKey: UInt8 = 0

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>

View File

@ -1,42 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
#if canImport(UIKit)
import UIKit
#if swift(>=4.2)
typealias LayoutRelation = NSLayoutConstraint.Relation
typealias LayoutAttribute = NSLayoutConstraint.Attribute
#else
typealias LayoutRelation = NSLayoutRelation
typealias LayoutAttribute = NSLayoutAttribute
#endif
typealias LayoutPriority = UILayoutPriority
#else
import AppKit
typealias LayoutRelation = NSLayoutConstraint.Relation
typealias LayoutAttribute = NSLayoutConstraint.Attribute
typealias LayoutPriority = NSLayoutConstraint.Priority
#endif

View File

@ -1,36 +0,0 @@
//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if canImport(UIKit)
import UIKit
#endif
@available(iOS 8.0, *)
public extension ConstraintLayoutSupport {
var snp: ConstraintLayoutSupportDSL {
return ConstraintLayoutSupportDSL(support: self)
}
}

View File

@ -53,29 +53,6 @@ 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.
## SnapKit
Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## ZIPFoundation
MIT License

View File

@ -76,35 +76,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>SnapKit</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>MIT License

View File

@ -2,6 +2,4 @@ ${PODS_ROOT}/Target Support Files/Pods-ReadViewDemo/Pods-ReadViewDemo-frameworks
${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework
${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework
${BUILT_PRODUCTS_DIR}/RDReaderView/RDReaderView.framework
${BUILT_PRODUCTS_DIR}/SSAlertSwift/SSAlertSwift.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework

View File

@ -1,6 +1,4 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTCoreText.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTFoundation.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RDReaderView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSAlertSwift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZIPFoundation.framework

View File

@ -2,6 +2,4 @@ ${PODS_ROOT}/Target Support Files/Pods-ReadViewDemo/Pods-ReadViewDemo-frameworks
${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework
${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework
${BUILT_PRODUCTS_DIR}/RDReaderView/RDReaderView.framework
${BUILT_PRODUCTS_DIR}/SSAlertSwift/SSAlertSwift.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework

View File

@ -1,6 +1,4 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTCoreText.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTFoundation.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RDReaderView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSAlertSwift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZIPFoundation.framework

View File

@ -179,16 +179,12 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework"
install_framework "${BUILT_PRODUCTS_DIR}/RDReaderView/RDReaderView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SSAlertSwift/SSAlertSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework"
install_framework "${BUILT_PRODUCTS_DIR}/RDReaderView/RDReaderView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SSAlertSwift/SSAlertSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then

View File

@ -1,12 +1,12 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView/RDReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift/SSAlertSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView/RDReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDReaderView" -framework "SSAlertSwift" -framework "SnapKit" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDReaderView" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,12 +1,12 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView/RDReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift/SSAlertSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView/RDReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDReaderView" -framework "SSAlertSwift" -framework "SnapKit" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"xml2" -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDReaderView" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,9 +1,9 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "SSAlertSwift" -framework "SnapKit" -framework "ZIPFoundation"
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,9 +1,9 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RDReaderView
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "SSAlertSwift" -framework "SnapKit" -framework "ZIPFoundation"
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "DTCoreText" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.15</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_SSAlertSwift : NSObject
@end
@implementation PodsDummy_SSAlertSwift
@end

View File

@ -1,12 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -1,16 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double SSAlertSwiftVersionNumber;
FOUNDATION_EXPORT const unsigned char SSAlertSwiftVersionString[];

View File

@ -1,14 +0,0 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SSAlertSwift
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -1,6 +0,0 @@
framework module SSAlertSwift {
umbrella header "SSAlertSwift-umbrella.h"
export *
module * { export * }
}

View File

@ -1,14 +0,0 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SSAlertSwift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SSAlertSwift
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>5.7.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.7.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_SnapKit : NSObject
@end
@implementation PodsDummy_SnapKit
@end

View File

@ -1,12 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -1,16 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double SnapKitVersionNumber;
FOUNDATION_EXPORT const unsigned char SnapKitVersionString[];

View File

@ -1,15 +0,0 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -1,6 +0,0 @@
framework module SnapKit {
umbrella header "SnapKit-umbrella.h"
export *
module * { export * }
}

View File

@ -1,15 +0,0 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -199,6 +199,8 @@ public struct RDEPUBBookmark: Codable, Equatable {
public var location: RDEPUBLocation
public var rangeInfo: String?
public var chapterTitle: String?
public var note: String?
@ -209,6 +211,7 @@ public struct RDEPUBBookmark: Codable, Equatable {
id: String = UUID().uuidString,
bookIdentifier: String? = nil,
location: RDEPUBLocation,
rangeInfo: String? = nil,
chapterTitle: String? = nil,
note: String? = nil,
createdAt: Date = Date()
@ -216,6 +219,7 @@ public struct RDEPUBBookmark: Codable, Equatable {
self.id = id
self.bookIdentifier = bookIdentifier
self.location = location
self.rangeInfo = rangeInfo?.nilIfEmpty
self.chapterTitle = chapterTitle?.nilIfEmpty
self.note = note?.nilIfEmpty
self.createdAt = createdAt
@ -288,7 +292,7 @@ public struct RDEPUBAnnotation: Codable, Equatable {
kind: .bookmark,
location: bookmark.location,
text: nil,
rangeInfo: nil,
rangeInfo: bookmark.rangeInfo,
color: nil,
chapterTitle: bookmark.chapterTitle,
note: bookmark.note,
@ -302,6 +306,7 @@ public struct RDEPUBAnnotation: Codable, Equatable {
id: id,
bookIdentifier: bookIdentifier,
location: location,
rangeInfo: rangeInfo,
chapterTitle: chapterTitle,
note: note,
createdAt: createdAt

View File

@ -18,6 +18,8 @@ public enum RDEPUBTextAttachmentKind: String, Codable, Equatable {
case image
case generic
case footnote
}
public struct RDEPUBTextPageMetadata: Codable, Equatable {

View File

@ -14,6 +14,10 @@ enum RDEPUBJavaScriptBridgeMessage: String, CaseIterable {
case javaScriptError = "ssReaderJSError"
case fixedLayoutReady = "ssReaderFixedLayoutReady"
case imageDidTap = "ssReaderImageDidTap"
case footnoteDidTap = "ssReaderFootnoteDidTap"
}
enum RDEPUBJavaScriptBridge {
@ -36,7 +40,9 @@ enum RDEPUBJavaScriptBridge {
"INTERNAL_LINK_MESSAGE": RDEPUBJavaScriptBridgeMessage.internalLink.rawValue,
"EXTERNAL_LINK_MESSAGE": RDEPUBJavaScriptBridgeMessage.externalLink.rawValue,
"JAVASCRIPT_ERROR_MESSAGE": RDEPUBJavaScriptBridgeMessage.javaScriptError.rawValue,
"FIXED_LAYOUT_READY_MESSAGE": RDEPUBJavaScriptBridgeMessage.fixedLayoutReady.rawValue
"FIXED_LAYOUT_READY_MESSAGE": RDEPUBJavaScriptBridgeMessage.fixedLayoutReady.rawValue,
"IMAGE_DID_TAP_MESSAGE": RDEPUBJavaScriptBridgeMessage.imageDidTap.rawValue,
"FOOTNOTE_DID_TAP_MESSAGE": RDEPUBJavaScriptBridgeMessage.footnoteDidTap.rawValue
]
)
].joined(separator: "\n\n")
@ -160,10 +166,23 @@ enum RDEPUBJavaScriptBridge {
return string
}
/// Encodes a string value as a safe JavaScript string literal.
///
/// Uses JSON serialization to handle escaping of quotes, backslashes, control
/// characters, and Unicode, then strips the array wrapper to produce a standalone
/// JS string literal (including surrounding quotes).
///
/// - Parameter value: The string to encode, or `nil` which produces the JS keyword `null`.
/// - Returns: A JavaScript string literal safe for inline embedding in JS source.
private static func javaScriptStringLiteral(_ value: String?) -> String {
guard let value else { return "null" }
return jsonString(from: [value], fallback: "[null]")
.replacingOccurrences(of: "[", with: "")
.replacingOccurrences(of: "]", with: "")
guard JSONSerialization.isValidJSONObject([value]),
let data = try? JSONSerialization.data(withJSONObject: [value], options: []),
let arrayString = String(data: data, encoding: .utf8) else {
return "null"
}
// JSON encodes ["value"] as `["value"]`. Drop the leading `[` and trailing `]`
// to yield `"value"` a valid JS string literal with all special characters escaped.
return String(arrayString.dropFirst().dropLast())
}
}

View File

@ -37,12 +37,20 @@ public struct RDEPUBNavigatorLayoutContext: Equatable {
return CGSize(width: width, height: containerSize.height)
}
/// Content insets that account for safe area (Dynamic Island / notch) on iPhone.
/// Uses the larger of safeAreaInsets and reflowableContentInsets for top/bottom
/// to ensure content is never hidden under the Dynamic Island or home indicator.
public var safeReflowableContentInsets: UIEdgeInsets {
let top = max(safeAreaInsets.top, reflowableContentInsets.top)
let bottom = max(safeAreaInsets.bottom, reflowableContentInsets.bottom)
let left = max(safeAreaInsets.left, reflowableContentInsets.left)
let right = max(safeAreaInsets.right, reflowableContentInsets.right)
return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
}
/// Fixed layout content insets that respect safe areas on all devices including iPhone.
public var fixedContentInset: UIEdgeInsets {
var insets = safeAreaInsets
if userInterfaceIdiom != .phone {
insets = .zero
}
let horizontalInsets = max(insets.left, insets.right)
insets.left = horizontalInsets
insets.right = horizontalInsets

View File

@ -1,21 +0,0 @@
import Foundation
public enum RDEPUBNavigatorState: String, Codable {
case initializing
case loading
case idle
case jumping
case moving
case repaginating
public var isStableForSnapshotApplication: Bool {
self == .idle
}
}

View File

@ -29,7 +29,11 @@ extension RDEPUBParser {
let extractionURL = temporaryExtractionDirectory(for: epubURL)
if fileManager.fileExists(atPath: extractionURL.path) {
return extractionURL
let containerURL = extractionURL.appendingPathComponent("META-INF/container.xml")
if fileManager.fileExists(atPath: containerURL.path) {
return extractionURL
}
try? fileManager.removeItem(at: extractionURL)
}
guard let archive = Archive(url: epubURL, accessMode: .read) else {
@ -38,24 +42,50 @@ extension RDEPUBParser {
try fileManager.createDirectory(at: extractionURL, withIntermediateDirectories: true)
for entry in archive {
guard let destinationURL = validatedExtractionDestination(for: entry.path, extractionRoot: extractionURL) else {
throw RDEPUBParserError.invalidArchiveEntryPath(entry.path)
}
switch entry.type {
case .directory:
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
case .file:
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
_ = try archive.extract(entry, to: destinationURL)
case .symlink:
continue
do {
for entry in archive {
if shouldSkipEntry(entry.path) { continue }
guard let destinationURL = validatedExtractionDestination(for: entry.path, extractionRoot: extractionURL) else {
if isCriticalEntry(entry.path) {
throw RDEPUBParserError.invalidArchiveEntryPath(entry.path)
}
continue
}
switch entry.type {
case .directory:
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
case .file:
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
_ = try archive.extract(entry, to: destinationURL)
case .symlink:
continue
}
}
} catch {
try? fileManager.removeItem(at: extractionURL)
throw error
}
return extractionURL
}
private func isCriticalEntry(_ entryPath: String) -> Bool {
let lowercased = entryPath.lowercased()
return lowercased == "mimetype"
|| lowercased.hasPrefix("meta-inf/")
|| lowercased.hasSuffix(".opf")
}
private func shouldSkipEntry(_ entryPath: String) -> Bool {
let lowercased = entryPath.lowercased()
if lowercased.hasPrefix("__macosx/") { return true }
if lowercased.hasPrefix(".ds_store") { return true }
if lowercased.contains("/.ds_store") { return true }
if lowercased.hasSuffix("/thumbs.db") { return true }
return false
}
private func validatedExtractionDestination(for entryPath: String, extractionRoot: URL) -> URL? {
if entryPath.hasPrefix("/") {

View File

@ -21,6 +21,64 @@ public final class RDEPUBParser {
public init() {}
// MARK: - Cache Management
/// The base directory used for EPUB extraction caches.
public static var extractionCacheBaseDirectory: URL {
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?
.appendingPathComponent("ssreaderview-epub", isDirectory: true)
?? FileManager.default.temporaryDirectory.appendingPathComponent("ssreaderview-epub", isDirectory: true)
}
/// Removes all EPUB extraction caches from disk.
/// Call this when the host app wants to free disk space, e.g. on didReceiveMemoryWarning
/// or during cleanup when the reader is no longer needed.
public static func cleanExtractionCache() throws {
let fileManager = FileManager.default
let baseURL = extractionCacheBaseDirectory
guard fileManager.fileExists(atPath: baseURL.path) else { return }
try fileManager.removeItem(at: baseURL)
}
/// Evicts extraction caches until total disk usage is below the given threshold,
/// removing least-recently-accessed directories first.
/// - Parameter maxBytes: Maximum total bytes for all extraction caches. Defaults to 500 MB.
public static func evictExtractionCache(maxBytes: UInt64 = 500 * 1024 * 1024) throws {
let fileManager = FileManager.default
let baseURL = extractionCacheBaseDirectory
guard fileManager.fileExists(atPath: baseURL.path) else { return }
let contents = try fileManager.contentsOfDirectory(
at: baseURL,
includingPropertiesForKeys: [.contentAccessDateKey, .isDirectoryKey],
options: .skipsHiddenFiles
)
var entries: [(url: URL, accessDate: Date, size: UInt64)] = []
var totalSize: UInt64 = 0
for directoryURL in contents {
guard let resourceValues = try? directoryURL.resourceValues(forKeys: [.isDirectoryKey, .contentAccessDateKey]),
resourceValues.isDirectory == true else {
continue
}
let directorySize = directoryURL.directorySize
let accessDate = resourceValues.contentAccessDate ?? Date.distantPast
entries.append((url: directoryURL, accessDate: accessDate, size: directorySize))
totalSize += directorySize
}
guard totalSize > maxBytes else { return }
entries.sort { $0.accessDate < $1.accessDate }
for entry in entries {
guard totalSize > maxBytes else { break }
try? fileManager.removeItem(at: entry.url)
totalSize -= entry.size
}
}
public func makePublication() -> RDEPUBPublication {
RDEPUBPublication(parser: self)
}
@ -75,4 +133,24 @@ public final class RDEPUBParser {
public func parseNavDocument(_ navURL: URL, baseURL: URL? = nil) -> [EPUBTableOfContentsItem] {
parseNavDocumentItems(at: navURL, baseURL: baseURL ?? navURL.deletingLastPathComponent())
}
}
// MARK: - URL Directory Size Extension
private extension URL {
var directorySize: UInt64 {
let fileManager = FileManager.default
guard let enumerator = fileManager.enumerator(at: self, includingPropertiesForKeys: [.fileSizeKey], options: [.skipsHiddenFiles], errorHandler: nil) else {
return 0
}
var totalSize: UInt64 = 0
for case let fileURL as URL in enumerator {
guard let resourceValues = try? fileURL.resourceValues(forKeys: [.fileSizeKey]),
let fileSize = resourceValues.fileSize else {
continue
}
totalSize += UInt64(fileSize)
}
return totalSize
}
}

View File

@ -7,18 +7,10 @@ public final class RDEPUBReadingSession {
public let publication: RDEPUBPublication
public private(set) var navigatorState: RDEPUBNavigatorState = .initializing
public private(set) var activePages: [EPUBPage] = []
public private(set) var activeChapters: [EPUBChapterInfo] = []
public private(set) var stagedPages: [EPUBPage]?
public private(set) var stagedChapters: [EPUBChapterInfo]?
public private(set) var stagedRestoreLocation: RDEPUBLocation?
public private(set) var pendingNavigationLocation: RDEPUBLocation?
public private(set) var pendingNavigationPageNum: Int?
@ -37,16 +29,10 @@ public final class RDEPUBReadingSession {
publication.resourceResolver
}
public func transition(to state: RDEPUBNavigatorState) {
navigatorState = state
}
public func resetRuntimeState() {
navigatorState = .initializing
activePages = []
activeChapters = []
clearPendingNavigation()
clearStagedSnapshot()
currentViewport = nil
currentReadingContext = nil
}
@ -56,36 +42,6 @@ public final class RDEPUBReadingSession {
activeChapters = snapshot.chapters
}
public func stageSnapshot(_ snapshot: PaginationSnapshot, restoreLocation: RDEPUBLocation?) {
stagedPages = snapshot.pages
stagedChapters = snapshot.chapters
stagedRestoreLocation = restoreLocation
}
public func stagedSnapshot() -> PaginationSnapshot? {
guard let stagedPages, let stagedChapters else {
return nil
}
return (stagedPages, stagedChapters)
}
public func consumeStagedSnapshotIfAllowed() -> (snapshot: PaginationSnapshot, restoreLocation: RDEPUBLocation?)? {
guard navigatorState.isStableForSnapshotApplication,
let stagedPages,
let stagedChapters else {
return nil
}
let restoreLocation = stagedRestoreLocation
clearStagedSnapshot()
return ((stagedPages, stagedChapters), restoreLocation)
}
public func clearStagedSnapshot() {
stagedPages = nil
stagedChapters = nil
stagedRestoreLocation = nil
}
public func clearPendingNavigation() {
pendingNavigationLocation = nil
pendingNavigationPageNum = nil
@ -241,7 +197,6 @@ public final class RDEPUBReadingSession {
pendingNavigationLocation = shouldKeepPendingNavigation ? normalizedLocation : nil
pendingNavigationPageNum = shouldKeepPendingNavigation ? pageIndex + 1 : nil
pendingNavigationHighlightRangeInfo = hasTargetHighlightRangeInfo ? targetHighlightRangeInfo : nil
transition(to: .jumping)
return pageIndex + 1
}
@ -292,9 +247,6 @@ public final class RDEPUBReadingSession {
if pendingNavigationPageNum == pageNumber {
clearPendingNavigation()
}
if navigatorState == .jumping || navigatorState == .moving {
transition(to: .idle)
}
}
public func currentReadingLocation(bookIdentifier: String?) -> RDEPUBLocation? {

View File

@ -1,4 +1,3 @@
import Foundation
import WebKit
@ -19,6 +18,8 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
private let syncQueue = DispatchQueue(label: "com.ssreaderview.epub.scheme-handler")
private let ioQueue = DispatchQueue(label: "com.ssreaderview.epub.scheme-handler.io", qos: .utility)
private var activeTasks: [ObjectIdentifier: Bool] = [:]
private static let debugMetricsQueue = DispatchQueue(label: "com.ssreaderview.epub.scheme-handler.metrics")
private static var streamedResponseCount = 0
@ -69,6 +70,8 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
expectedContentLength: 0,
textEncodingName: Self.textEncodingName(for: requestURL.pathExtension)
)
// H-08 fix: These calls happen synchronously in webView(_:start:) which
// runs on the same queue WebKit calls start on, so no thread violation here.
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(Data())
urlSchemeTask.didFinish()
@ -116,61 +119,153 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
return size
}
/// H-08 fix: Dispatch all WKURLSchemeTask completion callbacks to the main queue,
/// ensuring they run on the same serial queue that WebKit calls start() on.
/// Also: cancelled tasks now call didFailWithError(NSURLErrorCancelled) instead
/// of silently returning, satisfying the protocol requirement that every started
/// task must receive a completion callback.
private func respondWithInMemoryData(fileURL: URL, requestURL: URL, taskID: ObjectIdentifier, urlSchemeTask: any WKURLSchemeTask) {
do {
let data = try Data(contentsOf: fileURL)
guard isTaskActive(taskID) else { return }
let response = URLResponse(
url: requestURL,
mimeType: Self.mimeType(for: fileURL.pathExtension),
expectedContentLength: data.count,
textEncodingName: Self.textEncodingName(for: fileURL.pathExtension)
)
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
Self.recordInMemoryResponse()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished")
} catch {
guard isTaskActive(taskID) else { return }
Self.recordFailure()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "failed", error: error)
urlSchemeTask.didFailWithError(error)
ioQueue.async { [weak self] in
guard let self else {
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
do {
let data = try Data(contentsOf: fileURL)
guard self.isTaskActive(taskID) else {
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
let response = URLResponse(
url: requestURL,
mimeType: Self.mimeType(for: fileURL.pathExtension),
expectedContentLength: data.count,
textEncodingName: Self.textEncodingName(for: fileURL.pathExtension)
)
self.dispatchTaskSuccessCallback(
taskID: taskID,
urlSchemeTask: urlSchemeTask
) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
Self.recordInMemoryResponse()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished")
} catch {
guard self.isTaskActive(taskID) else {
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
Self.recordFailure()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "failed", error: error)
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(error)
}
}
self.clearTask(taskID)
}
clearTask(taskID)
}
private func respondWithStreaming(fileURL: URL, requestURL: URL, taskID: ObjectIdentifier, urlSchemeTask: any WKURLSchemeTask, fileSize: UInt64) {
let response = URLResponse(
url: requestURL,
mimeType: Self.mimeType(for: fileURL.pathExtension),
expectedContentLength: Int(fileSize),
textEncodingName: Self.textEncodingName(for: fileURL.pathExtension)
)
urlSchemeTask.didReceive(response)
ioQueue.async { [weak self] in
guard let self else {
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
let response = URLResponse(
url: requestURL,
mimeType: Self.mimeType(for: fileURL.pathExtension),
expectedContentLength: Int(fileSize),
textEncodingName: Self.textEncodingName(for: fileURL.pathExtension)
)
self.dispatchTaskCallbackIfActive(taskID: taskID) {
urlSchemeTask.didReceive(response)
}
guard let fileHandle = try? FileHandle(forReadingFrom: fileURL) else {
guard isTaskActive(taskID) else { return }
Self.recordFailure()
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorFileDoesNotExist))
clearTask(taskID)
return
}
guard let fileHandle = try? FileHandle(forReadingFrom: fileURL) else {
guard self.isTaskActive(taskID) else {
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
Self.recordFailure()
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorFileDoesNotExist))
}
self.clearTask(taskID)
return
}
let chunkSize = 65_536
defer {
fileHandle.closeFile()
clearTask(taskID)
let chunkSize = 65_536
var streamingCompleted = false
defer {
fileHandle.closeFile()
if !streamingCompleted {
// Task was cancelled during streaming; didFailWithError already sent above
// or will be sent by the guard check below. No need to send again.
}
self.clearTask(taskID)
}
while true {
guard self.isTaskActive(taskID) else {
// H-08 fix: Task was cancelled. Send didFailWithError on main queue.
DispatchQueue.main.async {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
}
return
}
let data = fileHandle.readData(ofLength: chunkSize)
if data.isEmpty { break }
self.dispatchTaskCallbackIfActive(taskID: taskID) {
urlSchemeTask.didReceive(data)
}
}
streamingCompleted = true
self.dispatchTaskSuccessCallback(
taskID: taskID,
urlSchemeTask: urlSchemeTask
) {
urlSchemeTask.didFinish()
}
Self.recordStreamedResponse()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished-streaming")
}
while true {
guard isTaskActive(taskID) else { return }
let data = fileHandle.readData(ofLength: chunkSize)
if data.isEmpty { break }
urlSchemeTask.didReceive(data)
}
private func dispatchTaskCallbackIfActive(taskID: ObjectIdentifier, _ callback: @escaping () -> Void) {
DispatchQueue.main.async { [weak self] in
guard let self, self.isTaskActive(taskID) else { return }
callback()
}
}
private func dispatchTaskSuccessCallback(
taskID: ObjectIdentifier,
urlSchemeTask: any WKURLSchemeTask,
_ callback: @escaping () -> Void
) {
DispatchQueue.main.async { [weak self] in
guard let self else {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
return
}
guard self.isTaskActive(taskID) else {
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
self.clearTask(taskID)
return
}
callback()
}
urlSchemeTask.didFinish()
Self.recordStreamedResponse()
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished-streaming")
}
private static func recordStreamedResponse() {

View File

@ -36,11 +36,18 @@ extension RDEPUBWebView {
guard let self else { return }
self.handleSelectionMenuAction(action)
}
UIMenuController.shared.menuItems = [
UIMenuItem(title: "拷贝", action: #selector(RDEPUBAnnotationWebView.rd_copy(_:))),
UIMenuItem(title: "高亮", action: #selector(RDEPUBAnnotationWebView.rd_highlight(_:))),
UIMenuItem(title: "批注", action: #selector(RDEPUBAnnotationWebView.rd_annotate(_:)))
]
// M-15: Use UIEditMenuInteraction on iOS 16+ to replace deprecated UIMenuController
if #available(iOS 16.0, *) {
let interaction = UIEditMenuInteraction(delegate: webView)
webView.addInteraction(interaction)
webView._editMenuInteraction = interaction
} else {
UIMenuController.shared.menuItems = [
UIMenuItem(title: "拷贝", action: #selector(RDEPUBAnnotationWebView.rd_copy(_:))),
UIMenuItem(title: "高亮", action: #selector(RDEPUBAnnotationWebView.rd_highlight(_:))),
UIMenuItem(title: "批注", action: #selector(RDEPUBAnnotationWebView.rd_annotate(_:)))
]
}
webView.navigationDelegate = self
webView.scrollView.isScrollEnabled = false
webView.scrollView.showsHorizontalScrollIndicator = false
@ -82,6 +89,10 @@ extension RDEPUBWebView {
if let webView {
RDEPUBWebViewDebug.log(debugScope, message: "teardown webView=\(RDEPUBWebViewDebug.webViewID(webView))")
}
// M-15: Clean up UIEditMenuInteraction on iOS 16+
if #available(iOS 16.0, *), let interaction = (webView as? RDEPUBAnnotationWebView)?._editMenuInteraction as? UIEditMenuInteraction {
webView?.removeInteraction(interaction)
}
webView?.navigationDelegate = nil
if let userContentController = webView?.configuration.userContentController {
RDEPUBJavaScriptBridge.messageNames.forEach {

View File

@ -107,8 +107,43 @@ extension RDEPUBWebView: WKScriptMessageHandler {
self.rendered()
}
}
case RDEPUBJavaScriptBridgeMessage.imageDidTap.rawValue:
guard let body = message.body as? [String: Any],
let src = body["src"] as? String else { return }
let sourceRect = sourceRect(from: body["rect"])
delegate?.epubWebView(self, didTapImageWithSource: src, sourceRect: sourceRect)
case RDEPUBJavaScriptBridgeMessage.footnoteDidTap.rawValue:
guard let body = message.body as? [String: Any],
let altText = body["alt"] as? String else { return }
let sourceRect = sourceRect(from: body["rect"])
delegate?.epubWebView(self, didTapFootnoteWithAltText: altText, sourceRect: sourceRect)
default:
break
}
}
private func sourceRect(from value: Any?) -> CGRect? {
guard let rectDict = value as? [String: Any] else { return nil }
return CGRect(
x: cgFloatValue(rectDict["x"]),
y: cgFloatValue(rectDict["y"]),
width: cgFloatValue(rectDict["width"]),
height: cgFloatValue(rectDict["height"])
)
}
private func cgFloatValue(_ value: Any?) -> CGFloat {
switch value {
case let number as NSNumber:
return CGFloat(truncating: number)
case let double as Double:
return CGFloat(double)
case let int as Int:
return CGFloat(int)
case let string as String:
return CGFloat(Double(string) ?? 0)
default:
return 0
}
}
}

View File

@ -17,16 +17,26 @@ public protocol RDEPUBWebViewDelegate: AnyObject {
func epubWebView(_ webView: RDEPUBWebView, didLogJavaScriptError message: String)
func epubWebViewDidFinishRendering(_ webView: RDEPUBWebView)
func epubWebView(_ webView: RDEPUBWebView, didTapImageWithSource src: String, sourceRect: CGRect?)
func epubWebView(_ webView: RDEPUBWebView, didTapFootnoteWithAltText altText: String, sourceRect: CGRect?)
}
public extension RDEPUBWebViewDelegate {
func epubWebView(_ webView: RDEPUBWebView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {}
func epubWebView(_ webView: RDEPUBWebView, didTapImageWithSource src: String, sourceRect: CGRect?) {}
func epubWebView(_ webView: RDEPUBWebView, didTapFootnoteWithAltText altText: String, sourceRect: CGRect?) {}
}
final class RDEPUBAnnotationWebView: WKWebView {
var onSelectionAction: ((RDEPUBAnnotationMenuAction) -> Void)?
// M-15: Backing store for UIEditMenuInteraction (iOS 16+).
// Stored as Any? to avoid @available on stored property restriction.
var _editMenuInteraction: Any?
override var canBecomeFirstResponder: Bool {
true
}
@ -55,6 +65,29 @@ final class RDEPUBAnnotationWebView: WKWebView {
}
}
// MARK: - UIEditMenuInteractionDelegate (iOS 16+)
@available(iOS 16.0, *)
extension RDEPUBAnnotationWebView: UIEditMenuInteractionDelegate {
func editMenuInteraction(
_ interaction: UIEditMenuInteraction,
menuFor configuration: UIEditMenuConfiguration
) -> UIMenu {
UIMenu(children: [
UICommand(title: "拷贝", action: #selector(rd_copy(_:))),
UICommand(title: "高亮", action: #selector(rd_highlight(_:))),
UICommand(title: "批注", action: #selector(rd_annotate(_:)))
])
}
func editMenuInteraction(
_ interaction: UIEditMenuInteraction,
targetRectFor configuration: UIEditMenuConfiguration
) -> CGRect {
bounds
}
}
public final class RDEPUBWebView: UIView {
public weak var delegate: RDEPUBWebViewDelegate?
@ -120,6 +153,11 @@ public final class RDEPUBWebView: UIView {
fatalError("init(coder:) has not been implemented")
}
// M-08: Retain cycle note webView configuration.userContentController self (as WKScriptMessageHandler)
// forms a retain cycle. The cycle is broken by calling teardownWebView(), which removes
// the message handlers. teardownWebView() is called in reset() and deinit. IMPORTANT:
// deinit may never trigger if the cycle is not broken first. Every code path that disposes
// of this view MUST call reset() before releasing the last reference.
deinit {
teardownWebView()
}

View File

@ -386,6 +386,43 @@
}, 120);
}
function handleDocumentClick(event) {
// Check for image tap. Footnote images always show the note popup; regular
// images inside links are left to the link handler below.
var img = event.target;
while (img && img.tagName !== 'IMG') {
img = img.parentElement;
}
if (img && img.tagName === 'IMG') {
var src = img.getAttribute('src');
var classes = (img.getAttribute('class') || '').toLowerCase();
var alt = (img.getAttribute('alt') || '').trim();
var isFootnote = classes.indexOf('qqreader-footnote') >= 0 || alt.length > 0;
if (isFootnote) {
event.preventDefault();
event.stopPropagation();
var footnoteRect = img.getBoundingClientRect();
window.webkit.messageHandlers.{{FOOTNOTE_DID_TAP_MESSAGE}}.postMessage({
alt: alt,
rect: { x: footnoteRect.x, y: footnoteRect.y, width: footnoteRect.width, height: footnoteRect.height }
});
return;
}
// If a regular image is inside a link, let the link handler deal with it.
var linkParent = img.closest ? img.closest('a[href]') : null;
if (!linkParent) {
if (src) {
event.preventDefault();
event.stopPropagation();
var rect = img.getBoundingClientRect();
window.webkit.messageHandlers.{{IMAGE_DID_TAP_MESSAGE}}.postMessage({
src: src,
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
});
return;
}
}
}
var anchor = event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) { return; }
var href = anchor.getAttribute('href');

View File

@ -22,10 +22,6 @@ struct RDEPUBChapterTailNormalizer {
previous.diagnostics.append(note)
compacted.append(previous)
} else {
#if DEBUG
print("[EPUB][Pagination] href=\(href) dropped leading/intermediate whitespace frame \(NSStringFromRange(frame.contentRange))")
#endif
}
continue
}
@ -41,9 +37,6 @@ struct RDEPUBChapterTailNormalizer {
previousFrame.diagnostics.append(note)
normalized.append(previousFrame)
} else {
#if DEBUG
print("[EPUB][Pagination] href=\(href) dropped trailing frame \(NSStringFromRange(lastFrame.contentRange))")
#endif
}
}
@ -91,10 +84,42 @@ struct RDEPUBChapterTailNormalizer {
return false
}
guard !frameHasAvoidPageBreakInside(trailingFrame, in: content) else {
return false
}
let previousVisibleCount = visibleCharacterCount(in: content, range: previousFrame.contentRange)
return previousVisibleCount >= max(visibleCount * 8, 12)
}
private func frameHasAvoidPageBreakInside(
_ frame: RDEPUBTextLayoutFrame,
in content: NSAttributedString
) -> Bool {
if frame.semanticHints.contains(.avoidPageBreakInside) {
return true
}
guard content.length > 0,
let safeRange = clampedRange(frame.contentRange, in: content),
safeRange.length > 0 else {
return false
}
var found = false
content.enumerateAttribute(.rdPageSemanticHints, in: safeRange) { value, _, stop in
guard let rawValue = value as? String else { return }
let hints = rawValue
.split(separator: ",")
.compactMap { RDEPUBTextSemanticHint(rawValue: String($0)) }
if hints.contains(.avoidPageBreakInside) {
found = true
stop.pointee = true
}
}
return found
}
private func mergeTrailingFrame(
_ previousFrame: RDEPUBTextLayoutFrame,
with trailingFrame: RDEPUBTextLayoutFrame
@ -147,6 +172,13 @@ struct RDEPUBChapterTailNormalizer {
return count
}
private func clampedRange(_ range: NSRange, in content: NSAttributedString) -> NSRange? {
guard range.location >= 0, range.length >= 0, range.location < content.length else {
return nil
}
return NSRange(location: range.location, length: min(range.length, content.length - range.location))
}
private func uniqueValues<T: Equatable>(from values: [T]) -> [T] {
values.reduce(into: [T]()) { result, value in
if !result.contains(value) {

View File

@ -187,16 +187,8 @@ public final class RDEPUBTextBookBuilder {
let renderDuration = CFAbsoluteTimeGetCurrent() - renderStart
let plainText = rendered.attributedString.string.trimmingCharacters(in: .whitespacesAndNewlines)
if item.href.lowercased().contains("cover") {
#if DEBUG
print("[EPUB][Cover] rendered href=\(item.href) textLength=\(plainText.count) attrLength=\(rendered.attributedString.length) attachments=\(attachmentCount(in: rendered.attributedString))")
#endif
}
if shouldSkipChapter(item: item, content: rendered.attributedString, text: plainText) {
if item.href.lowercased().contains("cover") {
#if DEBUG
print("[EPUB][Cover] skipped href=\(item.href)")
#endif
}
return nil
}
@ -287,12 +279,6 @@ public final class RDEPUBTextBookBuilder {
]
: normalizedFrames
if item.href.lowercased().contains("cover") {
#if DEBUG
print("[EPUB][Cover] paginated href=\(item.href) pages=\(effectiveFrames.count) firstRange=\(effectiveFrames.first.map { NSStringFromRange($0.contentRange) } ?? "none")")
#endif
}
let chapterAttributedContent = content.copy() as! NSAttributedString
let pages = effectiveFrames.enumerated().map { localPageIndex, frame in
let range = frame.contentRange

View File

@ -104,7 +104,7 @@ final class ChapterPaginationArchive: NSObject, NSSecureCoding {
public final class RDEPUBTextBookCache {
public var schemaVersion: Int = 6
public var schemaVersion: Int = 14
private let queue = DispatchQueue(label: "com.rdreader.textbookcache", qos: .utility)

View File

@ -30,6 +30,10 @@ struct RDEPUBCSSCompatibilityLayer {
normalized += result.normalizedRules
}
let alignmentIndentResult = addTextIndentResetForAlignedBlocks(in: rewritten)
rewritten = alignmentIndentResult.css
normalized += alignmentIndentResult.normalizedRules
if !policy.allowPublisherMargins {
let result = clampLengthProperty(
in: rewritten,
@ -133,4 +137,57 @@ struct RDEPUBCSSCompatibilityLayer {
}
return (rewritten, normalized)
}
private func addTextIndentResetForAlignedBlocks(
in css: String
) -> (css: String, normalizedRules: [String]) {
guard let regex = try? NSRegularExpression(
pattern: #"(?is)([^{}]+)\{([^{}]*)\}"#
) else {
return (css, [])
}
let nsCSS = css as NSString
var rewritten = css
var normalized: [String] = []
for match in regex.matches(in: css, range: NSRange(location: 0, length: nsCSS.length)).reversed() {
guard match.numberOfRanges > 2 else { continue }
let selector = nsCSS.substring(with: match.range(at: 1))
let declarations = nsCSS.substring(with: match.range(at: 2))
guard declaresRightOrCenterAlignment(declarations),
!declaresProperty("text-indent", in: declarations) else {
continue
}
let replacement = "\(selector){\(declarations)\n text-indent: 0 !important;\n}"
if let range = Range(match.range, in: rewritten) {
rewritten.replaceSubrange(range, with: replacement)
normalized.append("aligned-text-indent-reset")
}
}
return (rewritten, normalized)
}
private func declaresRightOrCenterAlignment(_ declarations: String) -> Bool {
guard let regex = try? NSRegularExpression(
pattern: #"(?i)\btext-align\s*:\s*(right|center)\b"#
) else {
return false
}
let range = NSRange(location: 0, length: (declarations as NSString).length)
return regex.firstMatch(in: declarations, range: range) != nil
}
private func declaresProperty(_ property: String, in declarations: String) -> Bool {
guard let regex = try? NSRegularExpression(
pattern: #"(?i)\b"# + NSRegularExpression.escapedPattern(for: property) + #"\s*:"#
) else {
return false
}
let range = NSRange(location: 0, length: (declarations as NSString).length)
return regex.firstMatch(in: declarations, range: range) != nil
}
}

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