Skip to content

Commit 0df9329

Browse files
shsw228claude
andcommitted
[docs] 公開 API に DocC コメントとモジュール概要カタログを追加
- 全 public 型/プロトコル/メンバへ DocC コメント(///)を整備 - PhotoScrubberKit.docc に Overview と Topics グルーピングを追加 - docbuild 警告ゼロを確認 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3add422 commit 0df9329

8 files changed

Lines changed: 189 additions & 2 deletions

Sources/PhotoScrubberKit/CustomScrubView.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import UIKit
22

3+
/// スクラブのスクロール方向。
34
public enum ScrubAxis: Sendable {
5+
/// 横方向にページング/スクロールする。
46
case horizontal
7+
/// 縦方向にページング/スクロールする。
58
case vertical
69
}
710

11+
/// メイン画像用の paging スクラブビュー。
12+
///
13+
/// `UICollectionView` ベースで 1 ページ = 1 item を全面表示する。スクロールに応じて
14+
/// ``progress`` を更新し、``CustomScrubViewDelegate`` へ進行度・ページ変化を通知する。
15+
/// 回転で bounds が変わっても表示ページを保持する。
16+
///
17+
/// 単体でも使えるが、サムネイル帯と連動させるなら ``PhotoScrubberCoupling`` を使う。
818
@MainActor
919
public final class CustomScrubView: UICollectionView {
1020

21+
/// スクロール方向。変更すると先頭ページへリセットされる。既定は ``ScrubAxis/horizontal``。
1122
public var axis: ScrubAxis = .horizontal {
1223
didSet {
1324
guard oldValue != axis else { return }
@@ -20,12 +31,17 @@ public final class CustomScrubView: UICollectionView {
2031
}
2132
}
2233

34+
/// ページ数と各ページの view を供給するデータソース。
2335
public weak var pageDataSource: (any CustomScrubViewDataSource)?
36+
/// 進行度・表示ページ変化の通知先。
2437
public weak var pageDelegate: (any CustomScrubViewDelegate)?
2538

39+
/// 現在の進行度。`0...(pageCount - 1)` の連続値。KVO 可能 (`@objc dynamic`)。
2640
@objc dynamic public private(set) var progress: CGFloat = 0
41+
/// 現在表示中のページ index。
2742
public private(set) var currentPageIndex: Int = 0
2843

44+
/// 現在表示中ページにホストされている view。該当が無ければ `nil`。
2945
public var visibleView: UIView? {
3046
guard itemCount > 0 else { return nil }
3147
let indexPath = IndexPath(item: currentPageIndex, section: 0)
@@ -61,6 +77,7 @@ public final class CustomScrubView: UICollectionView {
6177
register(PageCell.self, forCellWithReuseIdentifier: PageCell.reuseID)
6278
}
6379

80+
/// データソースを読み直し、先頭ページへリセットする。
6481
public override func reloadData() {
6582
itemCount = pageDataSource?.numberOfPages(in: self) ?? 0
6683
currentPageIndex = 0
@@ -69,6 +86,10 @@ public final class CustomScrubView: UICollectionView {
6986
super.reloadData()
7087
}
7188

89+
/// 指定ページへ移動する。
90+
/// - Parameters:
91+
/// - index: 移動先ページ index。範囲外なら無視される。
92+
/// - animated: アニメーション付きで移動するか。
7293
public func setCurrentPage(_ index: Int, animated: Bool) {
7394
guard index >= 0, index < itemCount else { return }
7495
let extent = pageExtent
@@ -80,6 +101,10 @@ public final class CustomScrubView: UICollectionView {
80101
updateCurrentPage(to: index)
81102
}
82103

104+
/// 進行度を直接指定して位置を合わせる。主に連動相手からの同期に使う。
105+
/// - Parameters:
106+
/// - progress: `0...(pageCount - 1)` の連続値。範囲外は丸められる。
107+
/// - animated: アニメーション付きで移動するか。既定 `false`。
83108
public func setProgress(_ progress: CGFloat, animated: Bool = false) {
84109
guard itemCount > 0 else { return }
85110
let extent = pageExtent
@@ -100,12 +125,17 @@ public final class CustomScrubView: UICollectionView {
100125
pageDelegate?.scrubView(self, didChangeVisiblePage: index)
101126
}
102127

128+
/// 末尾にページを 1 つ追加する。追加分の view は ``pageDataSource`` から取得される。
103129
public func appendPage() {
104130
itemCount += 1
105131
let indexPath = IndexPath(item: itemCount - 1, section: 0)
106132
insertItems(at: [indexPath])
107133
}
108134

135+
/// 指定ページを削除し、必要なら表示ページを補正する。
136+
/// - Parameters:
137+
/// - index: 削除するページ index。範囲外なら無視される。
138+
/// - animated: アニメーション付きで削除するか。`true` の場合は完了まで `await` で待つ。
109139
public func deletePage(at index: Int, animated: Bool) async {
110140
guard index >= 0, index < itemCount else { return }
111141
let oldCurrent = currentPageIndex

Sources/PhotoScrubberKit/CustomScrubViewController.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import UIKit
22

3+
/// ``CustomScrubView`` を `view` 全体に敷いた、メイン単体用のコンテナ ViewController。
4+
///
5+
/// サムネイル帯との連動が不要で、メインのスクラブビューだけを画面に置きたいときに使う。
6+
/// サムネイル連動が必要なら ``PhotoScrubberCoupling`` を使う。
37
open class CustomScrubViewController: UIViewController {
48

9+
/// 管理対象のスクラブビュー。`viewDidLoad` で `view` に敷き込まれる。
510
public let scrubView: CustomScrubView
611

12+
/// 指定したスクロール軸でコンテナを生成する。
13+
/// - Parameter axis: スクラブの方向。既定は ``ScrubAxis/horizontal``。
714
public init(axis: ScrubAxis = .horizontal) {
815
self.scrubView = CustomScrubView()
916
super.init(nibName: nil, bundle: nil)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import UIKit
22

3+
/// ``CustomScrubView`` にページ数と各ページの view を供給するデータソース。
4+
///
5+
/// ``PhotoScrubberCoupling`` 経由で使う場合は、代わりに
6+
/// ``PhotoScrubberDataSource`` を実装する(本 protocol は内部で橋渡しされる)。
37
@MainActor
48
public protocol CustomScrubViewDataSource: AnyObject {
9+
/// ページ総数。
510
func numberOfPages(in scrubView: CustomScrubView) -> Int
11+
/// `index` 番目のページに表示する view を返す。
612
func scrubView(_ scrubView: CustomScrubView, viewForPageAt index: Int) -> UIView
713
}

Sources/PhotoScrubberKit/CustomScrubViewDelegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import UIKit
22

3+
/// ``CustomScrubView`` の進行度・表示ページ変化を受け取るデリゲート。全メソッドが任意実装。
34
@MainActor
45
public protocol CustomScrubViewDelegate: AnyObject {
6+
/// スクロールで進行度が変化したとき呼ばれる。`progress` は `0...(pageCount - 1)` の連続値。
57
func scrubView(_ scrubView: CustomScrubView, didUpdateProgress progress: CGFloat)
8+
/// 表示中のページが別の index に切り替わったとき呼ばれる。
69
func scrubView(_ scrubView: CustomScrubView, didChangeVisiblePage index: Int)
710
}
811

Sources/PhotoScrubberKit/PhotoScrubberCoupling.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
import UIKit
22

3+
/// ``PhotoScrubberCoupling`` にメイン画像/サムネイル両方のコンテンツを供給するデータソース。
4+
///
5+
/// メイン (``PhotoScrubberCoupling/scrubView``) とストリップ
6+
/// (``PhotoScrubberCoupling/stripView``) は同じ item 数を共有する。両ビューの
7+
/// `index` は常に同じ item を指す。
38
@MainActor
49
public protocol PhotoScrubberDataSource: AnyObject {
10+
/// スクラバーが表示する item 数。メイン・サムネイルで共通。
511
func numberOfItems(in coupling: PhotoScrubberCoupling) -> Int
12+
/// メイン (paging) 側の `index` 番目に表示する view を返す。
613
func photoScrubber(_ coupling: PhotoScrubberCoupling, mainViewAt index: Int) -> UIView
14+
/// サムネイル帯の `index` 番目に表示する view を返す。
715
func photoScrubber(_ coupling: PhotoScrubberCoupling, thumbnailViewAt index: Int) -> UIView
816
}
917

18+
/// スクラブ操作の進行・表示 item 変化を受け取るデリゲート。全メソッドが任意実装。
1019
@MainActor
1120
public protocol PhotoScrubberDelegate: AnyObject {
21+
/// メイン/ストリップいずれかのスクロールで進行度が変化したとき呼ばれる。
22+
///
23+
/// `progress` は `0...(itemCount - 1)` の連続値(item index の小数表現)。
1224
func photoScrubber(_ coupling: PhotoScrubberCoupling, didUpdateProgress progress: CGFloat)
25+
/// 表示中の item が別の index に切り替わったとき呼ばれる。
1326
func photoScrubber(_ coupling: PhotoScrubberCoupling, didChangeVisibleItem index: Int)
1427
}
1528

@@ -18,25 +31,46 @@ public extension PhotoScrubberDelegate {
1831
func photoScrubber(_ coupling: PhotoScrubberCoupling, didChangeVisibleItem index: Int) {}
1932
}
2033

34+
/// Apple Photos.app 風スクラバーの中核。メイン画像ビューとサムネイル帯を双方向に連動させる。
35+
///
36+
/// 一方をスクラブするともう一方が追従する。レイアウト(2 ビューの配置)は呼び出し側の責務で、
37+
/// 本クラスは ``scrubView`` / ``stripView`` の 2 つの `UIView` と結合ロジックだけを提供する。
38+
///
39+
/// ## 使い方
40+
/// 1. ``dataSource`` を設定し、必要なら ``delegate`` / ``prefetcher`` も設定する。
41+
/// 2. ``scrubView`` と ``stripView`` を任意のレイアウトに配置する。
42+
/// 3. ``reloadData()`` を呼ぶ。
43+
///
44+
/// データ変更は ``appendItem()`` / ``deleteItem(at:animated:)`` で両ビューへ一括反映される。
2145
@MainActor
2246
public final class PhotoScrubberCoupling {
2347

48+
/// メイン (paging) のスクラブビュー。
2449
public let scrubView: CustomScrubView
50+
/// サムネイル帯のストリップビュー。
2551
public let stripView: ScrubberStripView
2652

53+
/// item 数とメイン/サムネイル view を供給するデータソース。
2754
public weak var dataSource: (any PhotoScrubberDataSource)?
55+
/// 進行度・表示 item 変化の通知先(任意)。
2856
public weak var delegate: (any PhotoScrubberDelegate)?
57+
/// 周辺 item の事前ロード依頼先(任意)。
2958
public weak var prefetcher: (any PhotoScrubberPrefetching)?
3059

31-
/// `didChangeVisibleItem` 発火時に現在 index ± `mainPrefetchRadius` を main 用 prefetch として通知。
60+
/// `didChangeVisibleItem` 発火時に現在 index ± `mainPrefetchRadius` を main 用 prefetch として通知。既定 `2`。
3261
public var mainPrefetchRadius: Int = 2
3362

34-
/// 同上、thumbnail 用。
63+
/// 同上、thumbnail 用。既定 `5`。
3564
public var thumbnailPrefetchRadius: Int = 5
3665

3766
private let forwardingProxy = ForwardingProxy()
3867
private var isProgrammaticUpdate = false
3968

69+
/// スクラバーを生成する。
70+
///
71+
/// - Parameters:
72+
/// - scrubView: 既存のメインビューを使う場合に指定。`nil` なら内部で生成する。
73+
/// - stripView: 既存のストリップビューを使う場合に指定。`nil` なら内部で生成する。
4074
public init(scrubView: CustomScrubView? = nil,
4175
stripView: ScrubberStripView? = nil) {
4276
self.scrubView = scrubView ?? CustomScrubView()
@@ -48,11 +82,18 @@ public final class PhotoScrubberCoupling {
4882
self.stripView.stripDelegate = forwardingProxy
4983
}
5084

85+
/// データソースを読み直し、メイン・サムネイル両方を再構築する。
5186
public func reloadData() {
5287
scrubView.reloadData()
5388
stripView.reloadData()
5489
}
5590

91+
/// 指定 index の item をメイン・サムネイル両方から削除する。
92+
///
93+
/// 両ビューの削除を並行実行し、完了まで待つ。`async` なので呼び出し側で `await` する。
94+
/// - Parameters:
95+
/// - index: 削除する item の index。
96+
/// - animated: アニメーション付きで削除するか。
5697
public func deleteItem(at index: Int, animated: Bool) async {
5798
isProgrammaticUpdate = true
5899
async let mainDone: Void = scrubView.deletePage(at: index, animated: animated)
@@ -61,6 +102,10 @@ public final class PhotoScrubberCoupling {
61102
isProgrammaticUpdate = false
62103
}
63104

105+
/// 末尾に item を 1 つ追加し、メイン・サムネイル両方へ反映する。
106+
///
107+
/// 追加後の item は ``dataSource`` から取得されるため、本メソッド呼び出し前に
108+
/// データソース側の件数を増やしておくこと。
64109
public func appendItem() {
65110
scrubView.appendPage()
66111
stripView.appendThumbnail()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ``PhotoScrubberKit``
2+
3+
UIKit 製の Apple Photos.app 風スクラバー UI(メイン画像 + サムネイル帯の双方向連動)を提供する Swift Package。
4+
5+
## Overview
6+
7+
レイアウト(2 ビューの配置)は呼び出し側の責務で、ライブラリは 2 つの `UIView`
8+
結合ロジックだけを提供する。内部実装は `UICollectionView` ベースで、メインは paging、
9+
ストリップは自作レイアウトで進行度に応じた stride 補間を行う。
10+
11+
典型的な使い方は ``PhotoScrubberCoupling`` にデータソースを設定し、
12+
``PhotoScrubberCoupling/scrubView````PhotoScrubberCoupling/stripView``
13+
任意のレイアウトに配置して ``PhotoScrubberCoupling/reloadData()`` を呼ぶだけ。
14+
15+
```swift
16+
let coupling = PhotoScrubberCoupling()
17+
coupling.dataSource = self
18+
coupling.delegate = self // optional
19+
coupling.prefetcher = self // optional
20+
coupling.reloadData()
21+
```
22+
23+
メイン単体でよい場合は ``CustomScrubView`` / ``CustomScrubViewController`` を、
24+
サムネイル帯だけが必要なら ``ScrubberStripView`` を直接使える。
25+
26+
## Topics
27+
28+
### 連動コンテナ
29+
30+
- ``PhotoScrubberCoupling``
31+
- ``PhotoScrubberDataSource``
32+
- ``PhotoScrubberDelegate``
33+
- ``PhotoScrubberPrefetching``
34+
- ``PhotoScrubberItemKind``
35+
36+
### メインビュー
37+
38+
- ``CustomScrubView``
39+
- ``CustomScrubViewController``
40+
- ``CustomScrubViewDataSource``
41+
- ``CustomScrubViewDelegate``
42+
43+
### サムネイル帯
44+
45+
- ``ScrubberStripView``
46+
- ``ScrubberThumbnailDataSource``
47+
- ``ScrubberStripViewDelegate``
48+
49+
### 共通
50+
51+
- ``ScrubAxis``

Sources/PhotoScrubberKit/PhotoScrubberPrefetching.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import UIKit
22

3+
/// prefetch 依頼がメイン・サムネイルどちらの view に対するものかを表す。
34
public enum PhotoScrubberItemKind: Sendable {
5+
/// メイン (paging) 側の view。
46
case main
7+
/// サムネイル帯の view。
58
case thumbnail
69
}
710

@@ -13,6 +16,14 @@ public enum PhotoScrubberItemKind: Sendable {
1316
/// 通知する。caller 側で expansion や cancel を制御してよい。
1417
@MainActor
1518
public protocol PhotoScrubberPrefetching: AnyObject {
19+
/// 周辺 item の事前ロードを依頼する。
20+
///
21+
/// - Parameters:
22+
/// - coupling: 依頼元のスクラバー。
23+
/// - indices: 事前ロード対象の item index 群(現在 index ± radius、現在 index は除く)。
24+
/// - kind: 依頼対象がメインかサムネイルか。半径は
25+
/// ``PhotoScrubberCoupling/mainPrefetchRadius`` /
26+
/// ``PhotoScrubberCoupling/thumbnailPrefetchRadius`` で調整する。
1627
func photoScrubber(_ coupling: PhotoScrubberCoupling,
1728
prefetchItemsFor indices: [Int],
1829
kind: PhotoScrubberItemKind)

0 commit comments

Comments
 (0)