{"id":32525590,"url":"https://github.com/jtcodes/epubreaderswiftui","last_synced_at":"2026-05-15T08:10:46.645Z","repository":{"id":320752149,"uuid":"1083237302","full_name":"jtCodes/EPUBReaderSwiftUI","owner":"jtCodes","description":"Simple plug and use EPUB reader for SwiftUI!","archived":false,"fork":false,"pushed_at":"2025-10-25T16:27:31.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-25T18:09:52.808Z","etag":null,"topics":["epub","readium","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jtCodes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-25T16:05:47.000Z","updated_at":"2025-10-25T17:06:16.000Z","dependencies_parsed_at":"2025-10-25T18:10:02.372Z","dependency_job_id":"c5018195-8cd9-4448-a619-db4cf53de3a5","html_url":"https://github.com/jtCodes/EPUBReaderSwiftUI","commit_stats":null,"previous_names":["jtcodes/epubreaderswiftui"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/jtCodes/EPUBReaderSwiftUI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jtCodes%2FEPUBReaderSwiftUI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jtCodes%2FEPUBReaderSwiftUI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jtCodes%2FEPUBReaderSwiftUI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jtCodes%2FEPUBReaderSwiftUI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jtCodes","download_url":"https://codeload.github.com/jtCodes/EPUBReaderSwiftUI/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jtCodes%2FEPUBReaderSwiftUI/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281418064,"owners_count":26497723,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-28T02:00:06.022Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["epub","readium","swiftui"],"created_at":"2025-10-28T09:51:13.600Z","updated_at":"2026-05-15T08:10:46.618Z","avatar_url":"https://github.com/jtCodes.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EPUBReaderSwiftUI\n\nA SwiftUI‑first component for rendering EPUB books.\n\n\u003cp\u003e\n\n  \u003cimg src=\"https://github.com/user-attachments/assets/9575dbce-450d-402d-96da-07b0f4c0dc21\" width=\"300\" style=\"margin-right:10px;\" /\u003e\n\n  \u003cimg src=\"https://github.com/user-attachments/assets/f2d528c6-755e-43b1-83d7-659439e3cb6d\" width=\"300\" /\u003e\n\n\u003c/p\u003e\n\n\n## 📦 Overview\n\nEPUBReaderSwiftUI enables you to display `.epub` files within a SwiftUI view or container.  \nIt supports the essential EPUB workflow: file selection, unpacking, and rendering of chapters/HTML content, while using a SwiftUI‑friendly interface.\n\n## ✅ Features\n\n- Drop-in SwiftUI view (`EPUBReaderView`) — works with `.sheet`, `.fullScreenCover`, or inline.\n- **Local \u0026 remote** EPUB loading — pass a file URL, a remote URL string, or an `EPUBSource` enum. Remote files are downloaded and cached automatically.\n- **Customizable overlay** — replace the default reader chrome (toolbar, progress bar, font controls) with your own SwiftUI view via an `overlay:` closure.\n- **Customizable table of contents** — supply a `tocView:` closure to build your own TOC UI, or use the built-in `DefaultEPUBReaderTOCView`.\n- **Highlighting \u0026 notes** — select text to highlight, tap highlights to edit color/style, add notes, or delete. Built-in default UI or fully custom via callbacks.\n- **Bookmarks** — toggle bookmarks from the overlay; the default overlay includes a bookmark button out of the box. Your app owns the array and persists it.\n- Reading position persistence — save and restore the locator across sessions.\n- Font-size control via `EPUBReaderSwiftUIPreferences`.\n- Navigation across chapters, progress tracking.\n- Renders content via WKWebView (Readium) inside SwiftUI.\n- No need to `import ReadiumShared` — common types (`Locator`, `Link`) are re-exported.\n\n## 🧭 Getting Started\n\n### Installation\n\nAdd `EPUBReaderSwiftUI` as a Swift Package in Xcode:  \n```\nFile → Add Packages… → `https://github.com/jtCodes/EPUBReaderSwiftUI.git`\n```\n\nAlternatively, integrate manually by copying the `Sources/EPUBReaderSwiftUI` folder.\n\n### Usage\n\n\u003e **Note:** `ReadiumShared` is re-exported automatically — you only need `import EPUBReaderSwiftUI`.\n\n#### ① Remote URL (simplest)\n\nPass a URL string directly. The package downloads, caches, and opens the file for you.\n\n```swift\nimport SwiftUI\nimport EPUBReaderSwiftUI\n\nstruct RemoteURLExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n\n    var body: some View {\n        Button(\"Open Remote EPUB\") {\n            showReader = true\n        }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                remoteURL: \"https://www.gutenberg.org/ebooks/9662.epub3.images\",\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences\n            ) { locator, preferences in\n                savedLocator = locator\n                savedPreferences = preferences\n                showReader = false\n            }\n        }\n    }\n}\n```\n\n#### ② Local file URL\n\nLoad an `.epub` that's already on disk or in your app bundle.\n\n```swift\nstruct LocalFileExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n\n    var body: some View {\n        Button(\"Open Local EPUB\") {\n            showReader = true\n        }\n        .fullScreenCover(isPresented: $showReader) {\n            if let url = Bundle.main.url(forResource: \"republic\", withExtension: \"epub\") {\n                EPUBReaderView(\n                    url: url,\n                    initialLocator: savedLocator,\n                    initialPreferences: savedPreferences\n                ) { locator, preferences in\n                    savedLocator = locator\n                    savedPreferences = preferences\n                    showReader = false\n                }\n            }\n        }\n    }\n}\n```\n\n#### ③ EPUBSource enum (choose at runtime)\n\nUse the `EPUBSource` enum when you need to decide between local and remote at runtime.\n\n```swift\nstruct SourceEnumExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n\n    let source: EPUBSource = .remoteURL(\n        \"https://www.gutenberg.org/ebooks/9662.epub3.images\",\n        useCache: true\n    )\n    // Or: let source: EPUBSource = .fileURL(Bundle.main.url(forResource: \"republic\", withExtension: \"epub\")!)\n\n    var body: some View {\n        Button(\"Open EPUB\") {\n            showReader = true\n        }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                source: source,\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences\n            ) { locator, preferences in\n                savedLocator = locator\n                savedPreferences = preferences\n                showReader = false\n            }\n        }\n    }\n}\n```\n\n### Customization\n\n#### Custom Overlay\n\nSupply an `overlay` closure to replace the default reader chrome. You receive an `EPUBReaderOverlayContext` with all the state and actions you need (title, author, current locator, font-size binding, close/navigate callbacks, etc.).\n\n```swift\nstruct CustomOverlayExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n\n    var body: some View {\n        Button(\"Open with Custom Overlay\") {\n            showReader = true\n        }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                remoteURL: \"https://www.gutenberg.org/ebooks/9662.epub3.images\",\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences\n            ) { locator, preferences in\n                savedLocator = locator\n                savedPreferences = preferences\n                showReader = false\n            } overlay: { context in\n                // Build any SwiftUI overlay you want.\n                // 'context' exposes: .title, .author, .chapterTitle,\n                // .totalProgression, .showControls, .fontSize,\n                // .close(), .showTableOfContents(), .goToProgression(_:)\n                VStack {\n                    if context.showControls {\n                        HStack {\n                            Button(action: context.close) {\n                                Image(systemName: \"chevron.left\")\n                                Text(\"Library\")\n                            }\n                            Spacer()\n                            Button(action: context.showTableOfContents) {\n                                Image(systemName: \"list.bullet\")\n                            }\n                        }\n                        .padding()\n                        .background(.ultraThinMaterial)\n                    }\n                    Spacer()\n                }\n            }\n        }\n    }\n}\n```\n\n#### Custom Table of Contents\n\nSupply a `tocView` closure to replace the default TOC sheet. You receive an `EPUBReaderTOCContext` with the chapter list, current locator, and navigation/dismiss actions.\n\n```swift\nstruct CustomTOCExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n\n    var body: some View {\n        Button(\"Open with Custom TOC\") {\n            showReader = true\n        }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                remoteURL: \"https://www.gutenberg.org/ebooks/9662.epub3.images\",\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences\n            ) { locator, preferences in\n                savedLocator = locator\n                savedPreferences = preferences\n                showReader = false\n            } overlay: { context in\n                // Keep the default overlay, only customize the TOC\n                DefaultEPUBReaderOverlay(context: context)\n            } tocView: { context in\n                // 'context' exposes: .tableOfContents, .currentLocator,\n                // .navigateToLink(_:), .dismiss()\n                NavigationView {\n                    List(context.tableOfContents, id: \\.href) { link in\n                        Button {\n                            context.navigateToLink(link)\n                        } label: {\n                            Text(link.title ?? \"Untitled\")\n                        }\n                    }\n                    .navigationTitle(\"Chapters\")\n                    .toolbar {\n                        ToolbarItem(placement: .navigationBarTrailing) {\n                            Button(\"Close\") { context.dismiss() }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n\u003e **Tip:** You can customize just the overlay, just the TOC, or both. Omit either closure to keep the default.\n\n#### Highlighting \u0026 Notes\n\nEPUBReaderSwiftUI has built-in highlighting support powered by Readium's Decoration API. Users can select text, tap **Highlight** in the context menu, and then tap existing highlights to change their color, style, add a note, or delete them.\n\nHighlighting is **opt-in** — the \"Highlight\" menu item only appears when you provide an `onHighlightCreated` callback. Your app owns the `[EPUBHighlight]` array and is responsible for persisting it (UserDefaults, Core Data, CloudKit, etc.).\n\n##### Minimal example (built-in edit UI)\n\nPass `highlights` and `onHighlightCreated` — the library provides a default popover for editing color, style, notes, and deleting:\n\n```swift\nstruct HighlightExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n    @State private var highlights: [EPUBHighlight] = []\n\n    var body: some View {\n        Button(\"Open Reader\") { showReader = true }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                remoteURL: \"https://www.gutenberg.org/ebooks/1497.epub3.images\",\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences,\n                highlights: $highlights,\n                onHighlightCreated: { highlights.append($0) },\n                onClose: { locator, preferences in\n                    savedLocator = locator\n                    savedPreferences = preferences\n                    showReader = false\n                }\n            )\n        }\n    }\n}\n```\n\n##### Custom highlight tap handler\n\nTo replace the built-in edit UI with your own, provide an `onHighlightTapped` callback:\n\n```swift\nEPUBReaderView(\n    remoteURL: \"https://example.com/book.epub\",\n    highlights: $highlights,\n    onHighlightCreated: { highlights.append($0) },\n    onHighlightTapped: { event in\n        // event.highlight — the tapped EPUBHighlight\n        // event.rect      — bounding rect for anchoring a popover\n        selectedHighlight = event.highlight\n        showMyCustomEditor = true\n    },\n    onClose: { locator, preferences in\n        showReader = false\n    }\n)\n```\n\n##### EPUBHighlight\n\n| Property | Type | Description |\n|---|---|---|\n| `id` | `String` | Unique identifier (UUID by default). |\n| `locator` | `Locator` | Readium locator — position in the book. |\n| `color` | `EPUBHighlightColor` | `.yellow`, `.green`, `.blue`, `.red`, `.purple`. |\n| `style` | `EPUBHighlightStyle` | `.highlight` (background) or `.underline`. |\n| `note` | `String?` | Optional user note attached to the highlight. |\n| `highlightText` | `String?` | The selected text (read-only, from the locator). |\n\n`EPUBHighlight` conforms to `Codable`, `Identifiable`, `Equatable`, and `Hashable`, so you can serialize it directly with `JSONEncoder`/`JSONDecoder`.\n\n#### Bookmarks\n\nPass a `bookmarks` binding and the default overlay will show a bookmark toggle button (filled when the current chapter is bookmarked). No extra callbacks needed — the library handles add/remove automatically via `EPUBReaderOverlayContext.toggleBookmark`.\n\n##### Minimal example\n\n```swift\nstruct BookmarkExample: View {\n    @State private var showReader = false\n    @State private var savedLocator: EPUBReaderSwiftUILocator?\n    @State private var savedPreferences = EPUBReaderSwiftUIPreferences()\n    @State private var bookmarks: [EPUBBookmark] = []\n\n    var body: some View {\n        Button(\"Open Reader\") { showReader = true }\n        .fullScreenCover(isPresented: $showReader) {\n            EPUBReaderView(\n                remoteURL: \"https://www.gutenberg.org/ebooks/1497.epub3.images\",\n                initialLocator: savedLocator,\n                initialPreferences: savedPreferences,\n                bookmarks: $bookmarks,\n                onClose: { locator, preferences in\n                    savedLocator = locator\n                    savedPreferences = preferences\n                    showReader = false\n                }\n            )\n        }\n    }\n}\n```\n\n##### Custom overlay with bookmarks\n\nIn custom overlays, use the bookmark properties on `EPUBReaderOverlayContext`:\n\n```swift\n// Inside your custom overlay:\nButton(action: context.toggleBookmark) {\n    Image(systemName: context.isCurrentPageBookmarked ? \"bookmark.fill\" : \"bookmark\")\n}\n\n// List all bookmarks:\nForEach(context.bookmarks) { bookmark in\n    Text(bookmark.chapterTitle ?? \"Unknown\")\n}\n```\n\n##### EPUBBookmark\n\n| Property | Type | Description |\n|---|---|---|\n| `id` | `String` | Unique identifier (UUID by default). |\n| `locator` | `Locator` | Readium locator — position in the book. |\n| `createdAt` | `Date` | When the bookmark was created. |\n| `chapterTitle` | `String?` | Chapter title (read-only, from the locator). |\n| `progression` | `Double?` | Overall reading progression 0.0–1.0 (read-only). |\n\n`EPUBBookmark` conforms to `Codable`, `Identifiable`, `Equatable`, and `Hashable`.\n\n## 🔧 How it works (at a high level)\n\n1. The EPUB file is unzipped into a temporary directory.  \n2. The OPF file (manifest/spine) is parsed to discover chapters, metadata and Table of Contents.  \n3. A `WKWebView` inside SwiftUI is used to display the HTML content of each chapter.  \n4. Navigation controls allow moving between chapters or pages.  \n5. State management handles current chapter, progress, bookmarks.\n\n## 📚 Supported EPUB Features\n\n- Standard EPUB 2/EPUB 3 packages (ZIP‑based).  \n- Basic HTML + CSS rendering via WebKit.  \n- Table of Contents.  \n- Local images/fonts inside the EPUB.\n\n## ⚠️ Limitations \u0026 Known Issues\n\n- Does **not** support advanced EPUB features such as media overlays (audio syncing), fixed‑layout books, or interactive content (unless explicitly added).  \n- Performance may degrade for very large books or very high font sizes — advisable to test on real devices.  \n- Annotations beyond highlights and bookmarks are not included out-of-the-box (you may need to extend).  \n- SwiftUI integration means there may be some bridging to UIKit/WebKit under the hood.\n\n## 📖 License\n\nMIT (or whichever license specified in the repository).  \nSee `LICENSE` for details.\n\n## 🤝 Contributing\n\nContributions, bug reports and feature requests are welcome!  \nPlease open an issue or PR describing the improvement, and follow the code style (SwiftLint, SwiftFormat) as used in the project.\n\n## 👤 Author\n\nCreated by [jtCodes](https://github.com/jtCodes) — thank you for sharing your work.\n\n---\n\n*Enjoy reading!*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjtcodes%2Fepubreaderswiftui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjtcodes%2Fepubreaderswiftui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjtcodes%2Fepubreaderswiftui/lists"}