Home Screen
The primary content feed users see after login. Three implementations exist, from the original UIKit-based Home to the newest SwiftUI Slices with REST backend. The most complex content module in the Youll platform.
The Home Screen module is the primary content feed that users see after login. It displays a dynamic, vertically scrolling feed of content sections personalized to each user based on their cohort membership, subscription status, and feature flags.
The platform provides three home screen implementations, each fully functional and actively used across different customer apps:
| Version | Name | UI Framework | Backend | Tab Item | Status |
|---|---|---|---|---|---|
| v1 | Home | UIKit | Hasura / GraphQL | .home | Deprecated |
| v2 | Showcase | SwiftUI | Hasura / GraphQL | .showcase / .exploreShowcase | Active |
| v3 | Slices | SwiftUI | REST | .slices | Active |
New customer apps should use Showcase (v2) or Slices (v3). Home v1 remains functional for existing customers but is deprecated and should not be used for new implementations. Which active version a customer app uses depends on the tab item returned by makeTabItems().
Like all core content modules, the home screen implementations live in the Core package (screens) and the Client package (coordinators). For the underlying patterns, see:
- Coordinator Pattern for the coordinator hierarchy, HomeNavigable protocol, and deep link routing
- Communication Patterns for Combine publishers and delegate patterns
- Design Token System for how screen config values flow from Figma exports to runtime theming
Activation
The home screen is activated by including the appropriate tab item in the customer app's tab selection:
// v1: Home (UIKit + GraphQL) — DEPRECATED
func makeTabItems() -> [TabItem] {
[.home, .explore, .library, .journeys, .profile]
}
// v2: Showcase (SwiftUI + GraphQL)
func makeTabItems() -> [TabItem] {
[.showcase, .explore, .library, .journeys, .profile]
}
// v3: Slices (SwiftUI + REST)
func makeTabItems() -> [TabItem] {
[.slices, .explore, .library, .journeys, .profile]
}No feature flags are required to enable the home screen itself. Individual sections within the feed are controlled by their own feature flags (see Feature Flags below).
Version Overview
Home (v1) — Deprecated
Home v1 is deprecated. New customer apps should use Showcase (v2) or Slices (v3) instead.
The original home screen implementation, built with UIKit. Uses a UICollectionView with compositional layout and diffable data source. The backend is powered by Hasura/GraphQL, with the GetHomeScreenSectionsQuery returning ordered feed sections ("slices"). Supports 27 distinct section types.
Home v1 remains functional for existing customer apps that have not yet migrated, but no new implementations should use it.
Showcase (v2)
A SwiftUI rewrite of the home screen, built to replace Home v1. Also uses the Hasura/GraphQL backend. Showcase was designed with a more device-centric layout, making it suitable for hardware-focused apps (e.g., apps using Tuya IoT devices), though it works for any content-on-demand app.
Showcase uses ShowcaseCoordinator in Client and its screens live under Core/Screens/Showcase/. It also supports the .exploreShowcase tab variant (differentiated by an isUsedForExplore flag).
Slices (v3)
The newest home screen implementation, also built with SwiftUI. The key difference from Showcase is the backend: Slices uses REST APIs instead of Hasura/GraphQL. This represents the platform's direction toward REST-based data fetching for the home feed.
The sections below document the Home (v1) implementation in detail for reference and for existing customers still using it. Showcase (v2) and Slices (v3) share many of the same concepts (feed sections, personalization, configuration) but with different UI frameworks and data transport layers.
Home (v1) Detail — Deprecated
Architecture
Home (v1) spans three packages in the Bricks dependency graph:
YoullNetwork
└── FeedAPI.getHomeFeedSections() ← GraphQL query
│
Core │
├── HomeFeedService ← API + caching layer
├── HomeFeedDataProvider ← State machine + snapshot generation
├── HomeViewModel ← UI state + delegate callbacks
├── HomeViewController / HomeView ← UICollectionView-based feed
├── FeedSection (27 cases) ← Section type definitions
├── HomeFeedDataSource ← Diffable data source
├── HomeFeedLayoutProvider ← Compositional layout
├── FeedCollectionItemProvider ← Cell/header registration + dequeue
└── 11 cell types ← One per visual section style
│
Client │
└── HomeCoordinator ← Navigation + integration (~1,146 lines)HomeCoordinator
HomeCoordinator inherits from BaseCoordinator and conforms to the HomeNavigable protocol (shared with ShowcaseCoordinator for deep link routing). It is created by MainCoordinator.createHomeCoordinator() during tab setup:
private func createHomeCoordinator() -> Coordinator {
let homeCoordinator = HomeCoordinator(
config: config,
delegate: self,
timerModuleInterface: timerModuleInterface,
tuyaModuleInterface: tuyaModuleInterface)
homeCoordinator.childCoordinatorDelegate = self
homeCoordinator.start()
addChildCoordinator(homeCoordinator, flow: .home)
return homeCoordinator
}HomeCoordinator receives two optional module interfaces:
TimerModuleInterface: if injected, the Home feed can display a timer widget and navigate to timer screensTuyaModuleInterface: if injected, the Home feed can display IoT device widgets and navigate to device control screens
The coordinator creates these services on init:
HomeFeedServicefor API calls and cachingNavigationContentServicefor navigation bar contentEventsContentServicefor events dataJourneyServicefor journey dataLinearJourneyManagerfor linear journey state tracking
HomeViewController
HomeViewController is a UICollectionView-based screen with two header modes:
| Header Type | Behavior |
|---|---|
.floating | Header content scrolls with the feed, floating above the background |
.fixedWithLogo | Header is pinned to the top with a logo, search button, and optional live users count |
The header type is configured by the customer app via HomeScreenConfigFactoryType.headerType.
The view controller uses:
HomeFeedDataSource(wrapsUICollectionViewDiffableDataSource) for diff-based updatesHomeFeedLayoutProviderfor compositional layout per sectionFeedCollectionItemProviderfor cell registration and dequeuing
HomeViewModel
HomeViewModel owns the data pipeline and exposes state to the view controller. It holds:
HomeFeedDataProviderfor loading and caching feed dataHomeFeedLayoutProviderfor section layout computationFeedCollectionItemProviderfor cell/header configurationscreenConfig: HomeScreenConfigfor all visual configuration
The ViewModel communicates user actions back to the coordinator through the HomeFeedDelegate protocol, which has 25+ methods covering every possible interaction in the feed.
Feed Sections
The FeedSection enum defines 27 section types that can appear in the Home (v1) feed. The server returns ordered feed sections via GraphQL, and HomeFeedService maps them to FeedSection cases. Feature flags filter out sections for disabled modules before the data reaches the UI.
Content Sections
| Section | Purpose | Visual Style |
|---|---|---|
.featured | Featured content cards, typically editorial picks | Horizontal scroll, large cards |
.suggested | Recommended content (often quiz-driven) | Horizontal scroll, standard cards |
.tag | Content grouped by a tag | Horizontal scroll, standard cards |
.bordered | Content grouped by tag, bordered card style | Horizontal scroll, bordered cards |
.banner | Single full-width content banner | Full-width card |
.history | Recently played content | Horizontal scroll |
.favorites | User's favorited content | Horizontal scroll |
.dailyQuote | Inspirational daily quote | Full-width quote card |
Journey Sections
| Section | Purpose |
|---|---|
.journey | Timeline journey with day-by-day progress |
.linearJourney | Linear journey with sequential sessions |
.currentJourney | Active journey showing current session and progress |
.linearJourneys | All available linear journeys (discovery) |
Module Integration Sections
| Section | Purpose | Requires |
|---|---|---|
.events | Upcoming events list | eventsEnabled flag |
.todayMealsPlan | Today's meal plan | mealsEnabled flag |
.todayWorkout | Today's highlighted workout | activityEnabled flag |
.workouts | Workout collection | activityEnabled flag |
.timer | Quick-access timer widget | TimerModuleInterface injected |
.tuyaHardware | IoT device status and controls | TuyaModuleInterface injected |
.timerSessions | Timer session history | TimerModuleInterface injected |
UI & Layout Sections
| Section | Purpose |
|---|---|
.navigation | Top navigation bar with logo, welcome message, live users count, search button |
.greetings | Personalized greeting with the user's first name in gradient text |
.titling | Text titles and subtitles for visual hierarchy |
.spacer | Vertical spacing between sections |
.share | Share section with URL and CTA |
Commercial Sections
| Section | Purpose |
|---|---|
.promoOffer | Promotional offer with countdown timer and CTA |
.lifetimeAccessProduct | Lifetime subscription offer |
.facilitator | Facilitator/instructor profiles |
Section Ordering
The feed order is server-driven. The GraphQL query GetHomeScreenSectionsQuery returns slices in a specific order, and HomeFeedService preserves that order. The only client-side modifications are:
- Prefix sections added before server content:
.navigationand optionally.greetings - Promo offers inserted after the navigation section (fetched separately)
- Feature flag filtering: sections for disabled modules are removed (e.g.,
.eventsremoved wheneventsEnabledis false)
Section Dismissal
Users can dismiss certain sections from their feed. When a user dismisses a section, HomeFeedService calls the dismissHomeSlice GraphQL mutation with the slice ID. The section is removed from the local snapshot immediately and will not reappear on the next server fetch.
Data Flow
Query and Caching
User opens Home tab
│
▼
HomeFeedDataProvider.loadData()
│
├──▸ Check Core Data cache
│ └── If cached: emit .loaded(cache: true) with cached sections
│
├──▸ HomeFeedService.getFeedData()
│ ├── Get user's cohort IDs from profile
│ ├── Call FeedAPI.getHomeFeedSections() (GraphQL)
│ ├── Fetch timer sessions (REST, if enabled)
│ ├── Store result in Core Data cache
│ └── Return HomeScreenFeed
│
└──▸ Emit .loaded(cache: false) with fresh sectionsThe data provider follows a state machine pattern:
| State | Meaning |
|---|---|
.loading | Initial load in progress |
.loaded(cache: true) | Displaying cached data while fresh data loads |
.loaded(cache: false) | Displaying fresh server data |
.failure(Error) | Load failed, showing error state |
.reloading | Pull-to-refresh in progress |
Personalization
Content personalization happens at multiple levels:
Cohort-based filtering. The GraphQL query includes the user's cohort IDs (from their profile). The backend returns only slices that match the user's cohort membership. This is the primary personalization mechanism.
Subscription status filtering. Each slice has a visibility scope: all_users, subscribed_only, or unsubscribed_only. The query sends the user's subscription status, and the backend filters accordingly.
Suggested content. The .suggested section can be driven by quiz results. When a user completes a quiz, the backend uses their answers to populate a personalized content recommendation section with a specific quizConfigID.
Promo offers. Promotional offers are fetched separately and filtered client-side against Settings.offerIdsToHide (offers the user has previously dismissed).
Refresh Behavior
| Trigger | Behavior |
|---|---|
| Pull-to-refresh | Full reload from server |
| Network restoration | Full reload (observed via NetworkReachability.reachablePublisher) |
viewWillAppear | Reload if more than 5 minutes since last load |
| Registration completed | Reload (via Config.registrationCompletionPublisher) |
| Subscription completed | Reload (via Config.subscriptionCompletionPublisher) |
| Player closed | Reload (via ContentDetailsCoordinator.reloadHomeContentPublisher) |
| Content liked/unliked | Update favorites (via ContentDetailsCoordinator.didLikeContentPublisher) |
Greetings and Welcome
Greetings Section
When Settings.displayHomeScreenGreetings is true, HomeFeedService prepends a .greetings section to the feed. It extracts the user's first name from their profile (defaulting to "Stranger" if empty) and renders it in HomeFeedGreetingCell with a gradient-styled username label.
The greeting configuration (text, colors, gradient) is part of HomeScreenConfig.content.
Welcome Message
A one-shot welcome popup displayed on first launch after onboarding:
- Condition:
Settings.displayedWelcomeMessage == falseANDSettings.onboardingType == .regular - Configuration:
ScreenConfig.generic.makeWelcomeMessageViewConfig() - Flow:
HomeCoordinator.displayWelcomeMessageIfNeeded()creates aWelcomeMessageViewControllerwith aWelcomeMessageViewModel - After display: Sets
Settings.displayedWelcomeMessage = trueso it never shows again - Optional follow-up: A welcome session can be configured via
makeWelcomeSessionViewConfig()
Configuration
HomeScreenConfigFactoryType
Customer apps implement this protocol to configure the Home screen's visual appearance:
protocol HomeScreenConfigFactoryType {
var headerType: HeaderType { get }
func makeHomeScreenConfig(navBarTitle: String, bgUrl: URL?) -> HomeScreenConfig
func makeTagFeedScreenConfig(title: String, bgUrl: URL?) -> TagFeedScreenConfig
func makeEventsListScreenConfig(showTopSearchButton: Bool) -> EventsListScreenConfig?
func makeDailyQuoteModalConfig() -> DailyQuoteModalViewConfig?
func makeTimerSessionsHistoryConfig() -> TimerSessionsHistoryViewConfig?
}| Method | Purpose | Required |
|---|---|---|
headerType | .floating or .fixedWithLogo | Yes |
makeHomeScreenConfig | Full Home screen visual config | Yes |
makeTagFeedScreenConfig | Tag feed drill-down screen config | Yes |
makeEventsListScreenConfig | Events list screen (returns nil if events not used) | Optional |
makeDailyQuoteModalConfig | Daily quote modal (returns nil if not used) | Optional |
makeTimerSessionsHistoryConfig | Timer sessions history (returns nil if not used) | Optional |
HomeScreenConfig
The HomeScreenConfig struct controls every visual aspect of the Home (v1) screen:
| Area | What It Controls |
|---|---|
| Status bar | Light/dark style, background handling |
| Background | Background image URL, placeholder, background color |
| Navigation bar | Logo image, welcome message text, search button visibility, live users display config |
| Content | Pull-to-refresh icon and label, greeting label with gradient, header text and subtitle |
| Cell themes | Per-section-type theme configs for colors, fonts, sizes, corner radii (featured, suggested, tag, facilitator, events, workouts, timer, promo, bordered, banner, history, meals, journey, and more) |
| Promo cell | Countdown timer styling, CTA button config |
| Skeleton | Loading state placeholder appearance |
| Error state | Error screen appearance and retry button |
Cell themes are the most granular part of configuration. Each section type has its own theme config object, allowing customer apps to give each section a distinct visual identity while reusing the same underlying cell implementations.
Navigation and Integration
HomeFeedDelegate
The HomeFeedDelegate protocol is the bridge between HomeViewModel (in Core) and HomeCoordinator (in Client). When a user taps anything in the feed, the ViewModel calls the appropriate delegate method, and the coordinator handles navigation.
Content navigation:
| Delegate Method | Navigation Target |
|---|---|
showTaggedItemDetails(for:autoPlayItemIDs:) | Content playback via ContentDetailsCoordinator.shared |
showAllContentForTag(id:itemType:title:sortBy:sortDirection:) | Tag feed drill-down screen |
showAllSuggestedItems(quizConfigID:) | Suggested content list |
didSelectFacilitator(item:) | Facilitator profile via ContentDetailsCoordinator.shared |
didSelectDailyQuote(quote:) | Daily quote modal |
shareAction(url:) | System share sheet |
Cross-module navigation:
| Delegate Method | Target Module | Route |
|---|---|---|
didSelectLinearJourney(_:) | Journeys | Journey details screen |
didSelectTimelineJourney(_:) | Journeys | Journey details screen |
didSelectCurrentJourneySession(_:allSessions:) | Journeys | Content playback |
showAllJourneys() | Journeys | Journeys list |
didSelectEvent(item:) | Events | Event details screen |
showAllEvents() | Events | Events list screen |
didSelectMeal(item:) | Meals | Meal details screen |
showMeals() | Meals | Meals list screen |
didSelectWorkout(workoutModel:isTodaysWorkout:) | Activity | Workout details |
showAllWorkouts(for:sectionTitle:) | Activity | Workouts list |
showTimer(tuyaController:) | Timer | Timer screen (via TimerModuleInterface) |
showTimerSessionsHistory(selectedSession:) | Timer | Timer sessions history |
didSelectTuyaDevice(item:) | Tuya | Device control screen (via TuyaModuleInterface) |
didSelectPairTuyaDevice() | Tuya | Device pairing flow (via TuyaModuleInterface) |
searchAction() | Search | Search screen (via SearchCoordinator.shared) |
presentQuiz(skipIntroScreen:) | Quiz | Quiz flow (via QuizCoordinator.shared) |
showAllFavorites() | Library | Favorites screen |
Commercial navigation:
| Delegate Method | Navigation Target |
|---|---|
didSelectPromoOffer(_:) | Promo offer details or subscription flow |
didSelectLifetimeAccessProduct(id:) | Lifetime access purchase flow |
Combine Subscriptions
HomeCoordinator subscribes to several cross-cutting publishers set up in its start() method:
| Publisher | Source | Purpose |
|---|---|---|
didClosePlayerPublisher | ContentDetailsCoordinator.shared | Refresh feed when player closes |
reloadHomeContentPublisher | ContentDetailsCoordinator.shared | Full reload triggered externally |
didLikeContentPublisher | ContentDetailsCoordinator.shared | Update favorites section when content is liked/unliked |
quizDismissPublisher | QuizCoordinator.shared | Handle quiz completion, refresh suggested content |
registrationCompletionPublisher | Config | Refresh feed after user registers |
subscriptionCompletionPublisher | Config | Refresh feed after subscription purchase |
Cross-Sibling Communication
MainCoordinator wires up cross-sibling delegates after creating tab coordinators:
if let homeCoordinator = childCoordinators[.home] as? HomeCoordinator,
let journeyCoordinator = childCoordinators[.journeys] as? JourneysCoordinator {
journeyCoordinator.journeyCompletionDelegate = homeCoordinator
}HomeCoordinator conforms to JourneyCompletionDelegate, receiving callbacks when the user views journey content in the Journeys tab. This allows the Home feed to refresh journey progress sections.
Deep Link Support
HomeCoordinator conforms to the HomeNavigable protocol, enabling deep link routing from MainCoordinator:
protocol HomeNavigable {
func navigateToCategory(id: String?)
func navigateToSubcategory(id: String?)
func navigateToTag(id: String?, sortInfo: (sortBy: SliceSortBy, sortDirection: SliceSortDirection)?)
func navigateToContent(id: String?)
func navigateToEvent(id: String?)
}When a deep link arrives for content, categories, or events, MainCoordinator switches to the Home tab and calls the appropriate HomeNavigable method.
Additional Flows
HomeCoordinator manages several secondary flows triggered from the Home screen:
| Flow | Trigger | Description |
|---|---|---|
| Push notification prompt | First visit after conditions met | displayNotificationAlertControllerIfNeeded() presents the push notification permission dialog |
| Quiz trigger | On visit if quiz is configured | displayQuizIfNeeded() checks flags and presents quiz flow |
| What's New popup | After app update | WhatsNewControllerManager shows a popup when content has changed (hash comparison) |
| Reminders popup | After player close (if reminders flag active) | Presents reminder configuration |
Feature Flags
These feature flags control which sections appear in the Home (v1) feed:
| Flag | Effect on Home |
|---|---|
journeysEnabled | Journey sections (.journey, .linearJourney, .currentJourney, .linearJourneys) visible |
eventsEnabled | .events section visible (filtered in HomeFeedService) |
mealsEnabled | .todayMealsPlan section visible |
activityEnabled | .todayWorkout and .workouts sections visible |
quizEnabled | Quiz CTA visible, .suggested sections with quiz config active, quiz trigger on visit |
reminders | Reminders popup after player close |
Flags are checked by HomeFeedService when processing the server response. Sections for disabled modules are removed before the data reaches the UI layer.
For Agents
Reading Order (Home v1)
When working with the Home (v1) module, read files in this order:
Client/Coordinators/Tabs/HomeCoordinator.swift(~1,146 lines) for the full navigation flow and integration pointsCore/Screens/Home/Feed/HomeViewModel.swiftfor the ViewModel,HomeFeedDelegateprotocol, and data provider usageCore/Screens/Home/DataProvider/FeedSection.swiftfor all 27 section typesCore/Screens/Home/DataProvider/HomeFeedDataProvider.swiftfor the state machine and data loadingCore/Data/API Service/HomeFeedService.swiftfor the API layer, caching, and feature flag filteringCore/Screens/Home/Feed/HomeScreenConfig.swiftfor the configuration structureCore/Screens/Home/Feed/HomeViewController.swiftfor the view controller and collection view setupClient/App/Screen Config/FactoryTypes.swift(search forHomeScreenConfigFactoryType) for the configuration protocol
Key Files (Home v1)
| Component | File Path |
|---|---|
| HomeCoordinator | Bricks/modules/Client/Client/Coordinators/Tabs/HomeCoordinator.swift |
| HomeViewController | Bricks/modules/Core/Core/Screens/Home/Feed/HomeViewController.swift |
| HomeView | Bricks/modules/Core/Core/Screens/Home/Feed/HomeView.swift |
| HomeViewModel | Bricks/modules/Core/Core/Screens/Home/Feed/HomeViewModel.swift |
| HomeScreenConfig | Bricks/modules/Core/Core/Screens/Home/Feed/HomeScreenConfig.swift |
| HomeFeedService | Bricks/modules/Core/Core/Data/API Service/HomeFeedService.swift |
| HomeFeedDataProvider | Bricks/modules/Core/Core/Screens/Home/DataProvider/HomeFeedDataProvider.swift |
| FeedSection enum | Bricks/modules/Core/Core/Screens/Home/DataProvider/FeedSection.swift |
| FeedItem | Bricks/modules/Core/Core/Screens/Home/DataProvider/FeedItem.swift |
| FeedSectionHeader | Bricks/modules/Core/Core/Screens/Home/DataProvider/FeedSectionHeader.swift |
| HomeFeedDataSource | Bricks/modules/Core/Core/Screens/Home/DataSource.swift |
| HomeFeedLayoutProvider | Bricks/modules/Core/Core/Screens/Home/Feed/HomeFeedLayoutProvider.swift |
| FeedCollectionItemProvider | Bricks/modules/Core/Core/Screens/Home/Feed/FeedCollectionItemProvider.swift |
| FeedAPI (GraphQL) | Bricks/modules/YoullNetwork/Network/Sources/Public/Data Task/FeedAPI.swift |
| HomeScreenConfigFactoryType | Bricks/modules/Client/Client/App/Screen Config/FactoryTypes.swift |
| Tab setup | Bricks/modules/Client/Client/Coordinators/Main/MainCoordinators+TabSetup.swift |
| Home cells | Bricks/modules/Core/Core/Screens/Home/Cells/ (11 cell files) |
| Tag feed screen | Bricks/modules/Core/Core/Screens/Home/TagFeed/TagContentViewController.swift |
| Feature flags | Bricks/modules/Core/Core/Content/Flagging/Flag.swift |
Cell Types (Home v1)
| Cell | Purpose |
|---|---|
HomeNavigationBarCell | Top nav bar with logo, welcome message, live users, search |
HomeFeedGreetingCell | Personalized greeting with gradient username |
HomeFeaturedCell | Featured content cards |
HomeJourneyCell | Timeline journey display |
HomeLinearJourneyCell | Linear journey progress |
HomeTimerCell | Timer quick-access widget |
HomeTuyaHardwareCell | IoT device controls |
HomeShareCell | Share section |
HomeTitleCell | Section titles |
HomeFeedCTACell | Call-to-action buttons |
HomeSpaceCell | Vertical spacing |
Tips (Home v1)
- To add a new feed section type: Add a case to
FeedSection, add a cell inCore/Screens/Home/Cells/, register it inFeedCollectionItemProvider, add layout inHomeFeedLayoutProvider, and add a theme config toHomeScreenConfig. - To modify the greeting: Look at
HomeFeedService.prefixSections()for the data,HomeFeedGreetingCellfor the UI, andHomeScreenConfig.contentfor the theme. - To add a new navigation action from the feed: Add a method to
HomeFeedDelegate(inHomeViewModel.swift), call it fromHomeViewModel.didSelectItem(at:), and implement it inHomeCoordinator. - To change feed refresh behavior: Look at
HomeFeedDataProviderfor the state machine andHomeViewModel.reloadIfNeededAfterViewWillAppear()for the 5-minute throttle logic. - To trace a content tap: The ViewModel calls
HomeFeedDelegate.showTaggedItemDetails(for:autoPlayItemIDs:), HomeCoordinator callsContentDetailsCoordinator.shared.didSelectSession(...), which presents the player. - HomeCoordinator is ~1,146 lines. It handles navigation for all 27 section types. When debugging a specific interaction, search for the corresponding
HomeFeedDelegatemethod name in the coordinator.
Key Files (Showcase v2)
| Component | File Path |
|---|---|
| ShowcaseCoordinator | Bricks/modules/Client/Client/Coordinators/Tabs/ShowcaseCoordinator.swift |
| Showcase screens | Bricks/modules/Core/Core/Screens/Showcase/ |
What's Next
Communication Patterns
How components communicate across the Youll platform using Combine publishers, delegates, closures, singletons, and NotificationCenter.
Explore
Content discovery screen where users browse by categories, subcategories, facilitators, and tags. Supports two operational modes, generic tag-based browsing and a custom home-like feed.