An MVP-based iOS page decomposition framework. Break down complex pages into independent, reusable, pluggable Presenter components.
| Problem | DDComponent's Solution |
|---|---|
| Multiple developers modifying the same file causes conflicts | Each feature is split into independent Presenters, no interference |
| Feature boundaries are fuzzy, no one owns quality | Presenters serve as clear logic boundaries with well-defined responsibilities |
| Same feature needs different UIs (e.g. Resso vs TTM) | P-Tree and V-Tree are separated — logic is reused, UI is swappable |
| Features need dynamic pluggability | Use the install DSL to assemble Presenters and Services on demand |
P-Tree (Presenter Tree) V-Tree (View Tree)
┌─────────────────┐ ┌─────────────────┐
│ RootPresenter │ - - - - │ RootView │
│ ├ LikePresenter │ - - - - │ ├ LikeButton │
│ ├ SharePresenter│ - - - - │ ├ ShareButton │
│ └ CommentPresenter│ - - - │ └ CommentList │
└─────────────────┘ └─────────────────┘
- P-Tree (Presenter Tree): The functional logic structure of the page
- V-Tree (View Tree): The UI presentation structure
- The two are loosely bound: the same P-Tree can bind to entirely different V-Trees
DDComponent draws inspiration from React/SwiftUI — all UI updates must go through a single setState entry point, batched and executed by the UpdatePipeline:
Data change → setState { ... } → Pipeline scheduling → onUpdate(view:context:) → UI update
// 1. View protocol (not UIView)
protocol LikeViewProtocol: AnyObject {
var isLiked: Bool { get set }
var likeCount: Int { get set }
var onLikeTapped: (() -> Void)? { get set }
}
// 2. Presenter (business logic)
class LikePresenter: ViewPresenter<LikeViewProtocol> {
var isLiked = false { didSet { setState {} } }
func toggleLike() {
getService(LikeService.self)?.toggleLike { [weak self] result in
self?.isLiked = result
}
}
override func onBindView(_ view: LikeViewProtocol) {
view.onLikeTapped = { [weak self] in self?.toggleLike() }
}
override func onUpdate(view: LikeViewProtocol, context: ViewUpdateContext) {
view.isLiked = isLiked
}
}
// 3. Assembly
class MyViewController: PageViewController<MyRootView, MyRootPresenter> {
@PageInstallerBuilder override var pageInstallers: PageInstaller {
ServiceInstaller(LikeService.self, { LikeServiceImpl() })
}
}pod 'DDComponent'
# Optional: @PresenterState macro
pod 'DDComponent/Macros'This project ships with a Claude Code plugin that provides DDComponent-specific development skills and a code review Agent:
| Plugin Feature | Description |
|---|---|
ddcomponent skill |
Guides Claude Code to build pages following the Presenter tree architecture, lifecycle conventions, and Service/Notify communication patterns |
code-reviewer Agent |
Reviews DDComponent code for type correctness, state design, lifecycle usage, and logic consistency |
Run in Claude Code:
# Add GitHub marketplace
/plugin marketplace add djs66256/DDComponent
# Install the plugin
/plugin install ddcomponent@ddcomponent| You want to... | Read this |
|---|---|
| Understand the framework design philosophy | Architecture |
| Build pages / lists / features | Usage Guide |
| Develop list cells | Reusable Presenter |
| Use the Hooks-style API | use* API |
| Simplify state declarations | @PresenterState Macro |
| Adapt for Swift 6 | Swift 6 Concurrency Patterns |
| Browse example code | Demo Index |
Documentation index: docs/en/README.md
The framework includes 122 unit tests covering all core capabilities. Tests are located in Example/Tests/.
cd Example
xcodebuild test -project DDComponent.xcodeproj -scheme DDComponent-Example -destination 'platform=iOS Simulator,name=iPhone 17'- iOS 13.0+
- Swift 5.7+
- Xcode 14.0+
DDComponent is available under the MIT license. See the LICENSE file for more info.