An iOS/macOS client built with SwiftUI that provides a unified interface for browsing feeds, organizing media collections from multiple illustration social platforms. Serves as the frontend of the Bottle project. The backend is at (bottle-rs).
- Feeds: Browse multiple illustration platforms in a unified feed interface. User can subscribe to different types of feeds (timeline, bookmarks, etc.) from different platforms. Bottle tracks them and updates new contents.
- Library: Manage favorite illustration by adding them from feeds to the library, and organizes them in a tree hierarchy of albums and folders, with drag-and-drop support.
- Artist View: Browse works grouped by artists across different platforms, and in both community source and personal library contexts, to appreciate unique styles of different artists. The artist view is supported by the native 3-column layout in SwiftUI. Also, browse the community posts and the saved works of any artist with a context menu.
- Media Viewing:
- Photos.app-like grid layout with adjustable density for immersive experience.
- View media in the optimized gallery viewer for both single and multi-image posts such as manga.
- Browse illustration's metadata in the information pane.
- Progressive JPEG support and image caching for more responsive browsing.
- Background Jobs: Monitor and trigger background jobs of feed updates and media downloads.
- Native Experience: Built with SwiftUI, the app enjoys a smooth, responsive, and visually consistent native experience.
One of the technical motivations of this app is to learn to build with modern Apple technologies and follow contemporary iOS/macOS development practices.
The app's core architecture is based on MVVM (Model-View-ViewModel), with ViewModels being bridges connecting data models and views. All ViewModels conforms to ObservableObject so that views can subscribe to data changes.
ViewModels play key roles in both two directions: (1) accept user's requests and load the needed data, and (2) parse and aggregate the response from backend and provide necessary data to update the view. For clearer code organization, these two roles are split and abstracted to two separate sets of protocols: ContentLoader and EntityProvider:
ContentLoaders are responsible for loading contents that are either paginated (with known count) or indefinite. The behaviors are provided by the default implementations ofPaginatedLoaderandIndefiniteLoader, respectively.EntityProviders are responsible for aggregate backend responses, which may contain flattened entities (posts, media, works, images, users) with one-to-many/many-to-many relationships. And for different kind of views, the relationships that the aggregation relies on are different. There are 3 kinds of entity providers:PostProvider,MediaProviderandUserProvider.
By conforming to any combination of the two set of protocols (with default implementation), concrete ViewModel classes can be easily constructed for different view purposes.
Besides, there are other dedicated ViewModels such as AppModel for the global app state and GalleryViewModel for the multi-image viewer.
Core technologies used in the app:
- SwiftUI: The declarative UI framework.
- Async/Await: Utilize Swift's structured concurrency for network operations.
- Observation Framework: Use the new
@Observablemacro for simple state management without the need ofCombine. Only used inAppModelfor experiment for now. - Core Transferable Framework: Support drag and drop in a modern Swift approach.
- Nuke: Efficient image loading and caching.
App/BottleApp.swift: App entry point.ContentView.swift: Core navigation logic for 2-column/3-column layout with sidebar.AppModel.swift: Global state management for the app.ViewModel.swift: Core protocols and implementations for ViewModels.
Component/: UI components for feed/library browsing and management. Important ones are:PostView-PostGridfor multi-image works like manga,MediaView-MediaGridfor illustrations,- and
UserListfor artist view.
Data/Client.swift: Network communication layer with the backend server.Response.swift: Data models and entity definitions, with extensions.Request.swift: API request structures and parameters.
Util/: Helper extensions and utility functions, including image/URL handling and some fallback codes (UIKit/AppKit).
Although SwiftUI has been growing these years, some critical functionalities are still missing or difficult to implement right. (And the infamous bad Apple documentation problem is true.) So I have to use some workarounds for now, which includes:
- Fallback to UIKit/AppKit for smooth zooming and panning gesture in the image viewer.
- Fix on nested ScrollView problem on macOS: source.
- Navigation cababilities provided by SwiftUI are somewhat limited. Switching between 2-column and 3-column layouts of
NavigationSplitViewwill loss the states. Also,NavigationStackmust be nested in theNavigationSplitViewto support inner navigation, which is not elegant and may cause problems. - Some behaviors are not consistent among platforms (iOS and macOS), such as the zooming gesture mentioned before.

