EhPanda: The SwiftUI Client That Makes a Web-Only World Feel Native

A deep look at how EhPanda uses TCA, HTML scraping, Core Data, and cookie orchestration to turn a fragile service into a fluid iOS app.

9 min read • View on GitHub • More from EhPanda-Team

An iPhone sits on a desk while raw HTML fragments, cookie shards, and route arrows are pulled through a set of gears into a clean gallery grid. The scene explains how EhPanda converts brittle web data into a disciplined native interface.
EhPanda’s core move is transformation: messy web output goes in, a coherent iPhone experience comes out.
Key Takeaways

Why this app exists

EhPanda is not trying to be a generic manga reader. It is a native answer to a source that was never designed to feel good on mobile, especially once you factor in login state, ExHentai access, and brittle markup.

That mismatch is the whole story. The interesting part is not that the app wraps a website. It is that it turns a messy, cookie-gated, HTML-only service into something that behaves like a careful iPhone product.

The repo makes that possible with a very specific stack: SwiftUI for presentation, TCA for orchestration, Kanna for scraping, and Core Data for local persistence. The UI is the visible layer. The real work happens underneath.

The trick is not the UI, it’s the state machine

EhPanda’s most important design choice is that navigation and side effects live in reducers, not scattered through the view tree. That matters because the app has many ways to open the same place: a tap, a deep link, a clipboard URL, or a settings change.

EhPanda’s routing logic is centralized, so the same state machine can handle taps, links, and clipboard events without fragmenting behavior.

That centralization is the app’s insurance policy. If a route change also needs a side effect, such as syncing a setting or checking whether the clipboard has changed, the reducer can do it without creating hidden UI behavior.

switch action {
case .detectClipboardURL:
  guard clipboardClient.changeCount() != state.clipboardChangeCount else { return .none }
  state.clipboardChangeCount = clipboardClient.changeCount()
  return .run { send in
    if let url = await clipboardClient.galleryURL() {
      await send(.route(.openGallery(url)))
    }
  }

default:
  return .none
}

That kind of logic reads like plumbing until you notice what it buys: predictability. The app can afford to be opinionated because the rule for changing state lives in one place.

How raw HTML becomes usable content

A close-up of a markup sieve turns a stream of tangled HTML tags into orderly gallery cards. The scene explains how the parser extracts structured data from brittle page fragments and normalizes them for display.
The parser is a translation layer. It turns unstable page markup into typed app content.

This is where EhPanda stops being an app shell and starts behaving like a real client. `Parser.swift` pulls structure out of brittle HTML with selectors and XPath, then normalizes it into data the UI can trust.

The subtle part is not just extraction. The parser also interprets presentation clues such as tag color and text contrast, so the app can render a readable interface even when the source markup was never built for one.

The parser’s job is translation, not decoration

A scraper that merely collects fields is fragile. EhPanda’s parser goes further by converting web-native conventions into app-native decisions. That is why the output feels consistent even when the input is not.

The payoff is simple: once parsing is done well, the rest of the app can think in stable Swift types instead of raw page soup.

Cookies, persistence, and why this app feels reliable

For a service like this, session handling is not a background detail. It is the product. If cookies drift, expire, or desynchronize, the whole experience falls apart.

LayerWhat it ownsWhy it mattersUser-visible effect
CookieClientSession cookies and host syncKeeps access state coherent across requestsLogin and ExHentai access feel persistent
DatabaseClientCore Data operationsHides persistence complexity behind a client interfaceLibrary state survives relaunches and migrations
ParserHTML extraction and normalizationConverts brittle markup into typed contentPages and galleries render consistently
AppReducerNavigation and side effectsKeeps behavior unidirectional and testableThe app responds predictably to links and taps

The interesting detail is that both cookies and persistence are wrapped as clients. That makes them easier to reason about, easier to test, and less likely to leak implementation chaos into the UI.

It also makes the app sturdier under failure. Core Data work is pushed through a controlled layer, and cookie state is synchronized instead of assumed. Reliability becomes part of the design, not an accident.

The nicest UX is the one you barely notice

EhPanda’s best polish lives in the small transitions. It watches the clipboard for gallery URLs, avoids duplicate handling with a change counter, and blocks launch when the database is not ready instead of pretending everything is fine.

That last detail matters. A migration gate is not glamorous, but it prevents the app from crashing into a half-upgraded store and gives users a clear recovery path.

This is the kind of UX that comes from treating state as a first-class system. The app feels calm because its edge cases were designed as state transitions, not left to chance.

Why native iOS still matters here

Cross-platform clients can cover more ground. Native clients can usually feel more exact. EhPanda chooses the second path, and that choice shows up in the architecture as much as the interface.

ProjectPlatformArchitectureOffline supportNative feelBest fit
EhPandaiOS, iPadOSSwiftUI + TCAYesHighUsers who want a focused native iPhone client
JHenTaiAndroid, iOS, WindowsFlutterYesMediumTeams that want one codebase across platforms
EhViewerAndroidNative AndroidYesHighAndroid users who want a mature benchmark app
Mihon / TachiyomiAndroidPlugin ecosystemYesMediumReaders who value extensibility over single-app polish
Web-based viewersiOS browserWeb stackLimitedLowPeople who want the lightest possible setup

The comparison is not about feature checklists. EhPanda’s advantage is narrower and more interesting. It combines native polish with disciplined state management, which is a rare pairing in a niche client built on top of hostile web data.

That is why it stands out. It is not trying to win by being the broadest tool. It wins by being the most coherent one.

The larger lesson

EhPanda is a useful pattern for any team dealing with legacy HTML, awkward session rules, or a source system that will never become clean on its own. The trick is not to fight the mess with more UI. It is to contain it with architecture.

If the source is chaotic, make the app disciplined. That is the lesson here. The more fragile the input, the more valuable the state machine becomes.