Skip to main content

RProductCard

A flexible and powerful SwiftUI component for displaying products in different layouts. The RProductCard automatically adapts to various use cases with built-in image loading, error handling, multiple visual variants, and sophisticated animations for enhanced user feedback.

Overview

The RProductCard is designed to be the primary component for displaying products throughout your app. It supports multiple layout variants, handles multiple images with swipe navigation, and includes comprehensive error states.

Basic Usage

Basic Example
import SwiftUI
import ReachuUI
import ReachuCore

struct ProductGridView: View {
let products: [Product]

var body: some View {
LazyVGrid(columns: [
GridItem(.flexible()),
GridItem(.flexible())
]) {
ForEach(products) { product in
RProductCard(
product: product,
variant: .grid,
onTap: {
// Navigate to product detail
},
onAddToCart: {
// Add to cart logic
}
)
}
}
}
}

Layout Variants

The RProductCard supports four distinct layout variants, each optimized for different use cases:

Grid Variant

Perfect for product grids and catalog views.

Grid Layout
RProductCard(
product: product,
variant: .grid,
showBrand: true,
onAddToCart: { addToCart(product) }
)

Features:

  • ✅ Square image area (160pt height)
  • ✅ Product title, brand, and price
  • ✅ Add to Cart button
  • ✅ Multiple image support with swipe navigation
  • ✅ Optimized for 2-column grids

List Variant

Ideal for search results and compact product lists.

List Layout
RProductCard(
product: product,
variant: .list,
showDescription: true,
onTap: { showProductDetail(product) }
)

Features:

  • ✅ Horizontal layout with small image (80pt)
  • ✅ Space-efficient design
  • ✅ Optional description text
  • ✅ Compact Add to Cart button

Hero Variant

Showcase products with large, prominent display.

Hero Layout
RProductCard(
product: product,
variant: .hero,
showBrand: true,
showDescription: true,
onAddToCart: { addToCart(product) }
)

Features:

  • ✅ Large image area (240pt height)
  • ✅ Full product information display
  • ✅ Prominent Add to Cart button
  • ✅ Perfect for featured products
  • ✅ Enhanced shadow and styling

Minimal Variant

Compact cards for carousels and space-constrained areas.

Minimal Layout
RProductCard(
product: product,
variant: .minimal,
onTap: { showProductDetail(product) }
)

Features:

  • ✅ Fixed width (120pt) for horizontal scrolling
  • ✅ Basic product info only
  • ✅ No Add to Cart button (tap-only interaction)
  • ✅ Perfect for product carousels

Advanced Features

Multiple Images with Swipe Navigation

The RProductCard automatically handles multiple product images with smooth swipe navigation:

Multiple Images
// Product with multiple images
let product = Product(
id: "123",
title: "Wireless Headphones",
images: [
ProductImage(id: "1", url: "https://example.com/image1.jpg", order: 0),
ProductImage(id: "2", url: "https://example.com/image2.jpg", order: 1),
ProductImage(id: "3", url: "https://example.com/image3.jpg", order: 2),
],
// ... other properties
)

// Grid and Hero variants automatically show image pagination
RProductCard(product: product, variant: .grid)

Image Features:

  • Smart Ordering: Prioritizes images with order 0 and 1
  • Swipe Navigation: TabView with page indicators (iOS/tvOS/watchOS)
  • Single Image Optimization: List and minimal variants show primary image only
  • Loading States: Shimmer effect while images load
  • Error Handling: Fallback icons for broken images
  • Button Animations: Scale effects and loading spinners
  • Success Feedback: Temporary "Added!" state with checkmarks
  • Haptic Feedback: Tactile confirmation on iOS devices

Comprehensive Options

Full Configuration
RProductCard(
product: product,
variant: .hero,
showBrand: true, // Show product brand
showDescription: true, // Show product description
onTap: { // Handle card tap
showProductDetail(product)
},
onAddToCart: { // Handle add to cart
cartManager.add(product)
}
)

Styling & Customization

Design System Integration

The RProductCard automatically uses your app's design system:

Design System Usage
// Colors from ReachuColors
.foregroundColor(ReachuColors.textPrimary)
.backgroundColor(ReachuColors.surface)

// Typography from ReachuTypography
.font(ReachuTypography.headline)

// Spacing from ReachuSpacing
.padding(ReachuSpacing.md)

// Border radius from ReachuBorderRadius
.cornerRadius(ReachuBorderRadius.large)

Stock Status Handling

Out-of-stock products are handled automatically:

Stock Management
// Product with zero quantity
let outOfStockProduct = Product(
id: "456",
title: "Sold Out Item",
quantity: 0, // highlight-line
// ... other properties
)

// Card automatically shows "Out of Stock" instead of Add to Cart
RProductCard(product: outOfStockProduct, variant: .grid)

Stock Features:

  • Automatic Detection: Based on product.quantity
  • Visual Feedback: "Out of Stock" badge with error styling
  • Disabled Interactions: Add to Cart button becomes non-interactive
  • Consistent Styling: Maintains layout while showing unavailability

Real-World Examples

Product Catalog Grid

CatalogView.swift
struct ProductCatalogView: View {
@StateObject private var viewModel = CatalogViewModel()
@State private var selectedVariant: LayoutVariant = .grid

var body: some View {
VStack {
// Layout variant picker
Picker("Layout", selection: $selectedVariant) {
Text("Grid").tag(LayoutVariant.grid)
Text("List").tag(LayoutVariant.list)
Text("Hero").tag(LayoutVariant.hero)
}
.pickerStyle(SegmentedPickerStyle())
.padding()

ScrollView {
LazyVGrid(columns: gridColumns, spacing: 16) {
ForEach(viewModel.products) { product in
RProductCard(
product: product,
variant: selectedVariant.cardVariant,
showDescription: selectedVariant == .hero,
onTap: {
viewModel.showProductDetail(product)
},
onAddToCart: {
viewModel.addToCart(product)
}
)
}
}
.padding()
}
}
}

private var gridColumns: [GridItem] {
switch selectedVariant {
case .grid:
return Array(repeating: GridItem(.flexible()), count: 2)
case .list, .hero:
return [GridItem(.flexible())]
}
}
}
FeaturedCarousel.swift
struct FeaturedProductsCarousel: View {
let featuredProducts: [Product]

var body: some View {
VStack(alignment: .leading) {
Text("Featured Products")
.font(ReachuTypography.headline)
.padding(.horizontal)

ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: ReachuSpacing.sm) {
ForEach(featuredProducts) { product in
RProductCard(
product: product,
variant: .minimal,
onTap: { showProductDetail(product) }
)
}
}
.padding(.horizontal)
}
}
}
}

Mixed Layout Product Feed

ProductFeed.swift
struct ProductFeedView: View {
let products: [Product]

var body: some View {
LazyVStack(spacing: ReachuSpacing.lg) {
ForEach(Array(products.enumerated()), id: \.element.id) { index, product in
Group {
if index == 0 {
// First product as hero
RProductCard(
product: product,
variant: .hero,
showDescription: true
)
} else if index % 5 == 0 {
// Every 5th product as hero
RProductCard(product: product, variant: .hero)
} else {
// Regular products as list
RProductCard(product: product, variant: .list)
}
}
}
}
.padding()
}
}

✨ Animation & Feedback System

RProductCard includes a sophisticated animation system that provides immediate visual and tactile feedback when users interact with the "Add to Cart" button.

Automatic Animation Sequence

When a user taps "Add to Cart", the following sequence occurs automatically:

Animation Sequence
1. Button Scale Animation (0.1s)    // Button scales to 0.9x
2. Loading State (variable) // Shows spinner while processing
3. Success State (1.5s) // Shows "Added!" with checkmark
4. Return to Normal (0.3s) // Fades back to original state

Visual Feedback States

Normal State

Default Button
RButton(
title: "Add to Cart",
style: .primary,
size: .medium
)

Loading State

During Cart Operation
RButton(
title: "Add to Cart",
style: .primary,
size: .medium,
isLoading: true // Shows spinner automatically
)

Success State

After Successful Add
RButton(
title: "Added!", // Changes to success text
style: .primary,
size: .medium,
icon: "checkmark" // Shows checkmark icon
)

Haptic Feedback Integration

On iOS devices, the card provides tactile feedback:

Haptic Patterns
// Light impact when button is pressed
UIImpactFeedbackGenerator(style: .light).impactOccurred()

// Success notification when item is added
UINotificationFeedbackGenerator().notificationOccurred(.success)

Haptic Timeline:

  1. Button Press → Light impact feedback
  2. Successful Add → Success notification pattern
  3. Error → Error notification pattern (if operation fails)

Cross-Platform Behavior

iOS

  • Full haptic feedback support
  • Smooth spring animations with proper timing
  • Respects accessibility settings for reduced motion

macOS

  • Visual animations without haptic feedback
  • Proper hover states and cursor interaction
  • Keyboard navigation support

tvOS

  • Adapted for remote control interaction
  • Focus management compatible
  • Appropriate timing for TV interface

watchOS

  • Simplified animations for performance
  • Watch-specific haptic patterns when available
  • Optimized for small screen interactions

Animation Customization

While animations are automatic, you can customize the experience:

Custom Animation Timing
// The animation system uses these built-in timings:
.animation(.easeInOut(duration: 0.1), value: buttonScale) // Press animation
.animation(.spring(response: 0.3, dampingFraction: 0.6), value: showCheckmark) // Success animation
.animation(.easeInOut(duration: 0.3), value: isAddingToCart) // Reset animation

Integration with Global Systems

The card animations work seamlessly with other Reachu UX components:

Coordinated UX System
// When user taps "Add to Cart":
1. RProductCard animates button // ✅ Local feedback
2. ToastManager shows notification // ✅ Global feedback
3. CartManager updates state // ✅ State management
4. RFloatingCartIndicator updates // ✅ Persistent indicator
5. Haptic feedback fires // ✅ Tactile confirmation

Animation Performance

The animation system is optimized for smooth performance:

  • Hardware Acceleration - Uses Core Animation for smooth rendering
  • State Management - Minimal re-renders with @State variables
  • Timer Management - Proper cleanup of animation timers
  • Memory Efficiency - No memory leaks from animation callbacks

Accessibility & Animations

The animation system respects accessibility preferences:

Accessibility Integration
// Respects reduced motion preferences
@Environment(\.accessibilityReduceMotion) var reduceMotion

// Shortened animations when reduce motion is enabled
.animation(reduceMotion ? .none : .spring(...), value: animationState)

// Alternative feedback for users who can't see animations
.accessibilityAnnouncement("Product added to cart")

Debug & Testing

To test animations during development:

Animation Testing
#if DEBUG
// Slow down animations for testing
.animation(.spring(response: 2.0, dampingFraction: 0.6), value: showCheckmark)

// Add visual debugging
.background(showCheckmark ? Color.green.opacity(0.3) : Color.clear)
#endif

Accessibility

The RProductCard includes comprehensive accessibility support:

Accessibility Features
// Automatic accessibility labels
.accessibilityLabel("\(product.title), \(product.price.displayAmount)")

// Add to cart button accessibility
.accessibilityHint("Add this product to your shopping cart")

// Stock status accessibility
.accessibilityValue(isInStock ? "In stock" : "Out of stock")

// Image accessibility
.accessibilityLabel("Product image for \(product.title)")

Performance Considerations

Image Loading Optimization

  • Lazy Loading: Images load only when visible
  • Automatic Caching: Built-in image caching via AsyncImage
  • Memory Management: Proper image disposal when cards scroll off-screen
  • Progressive Loading: Placeholder → Image → Error state progression

Best Practices

  1. Use appropriate variants for your layout needs
  2. Implement proper data loading patterns with ViewModels
  3. Handle loading states at the collection level
  4. Optimize image URLs (use appropriate sizes from your CDN)
  5. Test with various screen sizes and accessibility settings

Component Usage Guidelines

When to Use Each Variant

The RProductCard variants are designed for specific use cases. Choose the right variant based on your context:

VariantBest ForCard SizeInformation DensityPrimary Use Cases
GridProduct catalogsMedium (flexible)BalancedMain product grids, category pages, search results
ListCompact listingsSmall (80pt image)HighSearch results, cart items, compact product lists
HeroFeatured productsLarge (240pt image)FullHomepage features, product spotlights, promotions
MinimalQuick browsingSmall (120pt wide)MinimalCarousels, recommendations, related products

Design System Integration

Consistent Spacing and Styling
// All variants automatically use design system tokens
RProductCard(product: product, variant: .grid)
// Uses ReachuSpacing.md for internal spacing
// Uses ReachuColors.surface for background
// Uses ReachuTypography.headline for title
// Uses ReachuBorderRadius.large for corners

Responsive Behavior

Adaptive Layout Example
struct ResponsiveProductGrid: View {
let products: [Product]
@Environment(\.horizontalSizeClass) var sizeClass

var columns: [GridItem] {
switch sizeClass {
case .compact:
return Array(repeating: GridItem(.flexible()), count: 2)
default:
return Array(repeating: GridItem(.flexible()), count: 3)
}
}

var body: some View {
LazyVGrid(columns: columns) {
ForEach(products) { product in
RProductCard(
product: product,
variant: sizeClass == .compact ? .grid : .hero,
showDescription: sizeClass != .compact
)
}
}
}
}

Pro Tip

Use the Hero variant sparingly for featured products, Grid variant for main catalogs, List variant for search results, and Minimal variant for recommendations. Consider screen size and information hierarchy when choosing variants.