SwiftUI series 10: Building a Better Fruit Search UI in SwiftUI: Custom Components and Search Implementation

In this tutorial, we'll explore how to create a polished search interface with custom components and smooth interactions. We'll break down the implementation into three main parts: FruitRow, CategoryButton, and the main BetterSearchView.

1. FruitRow Component

First, let's look at our custom row component for displaying fruit information:

struct FruitRow: View {
    let fruit: Fruit

    var body: some View {
        HStack(spacing: 16) {
            Text(fruit.emoji)
                .font(.title)
                .frame(width: 50, height: 50)
                .background(Color.gray.opacity(0.1))
                .clipShape(Circle())
            VStack(alignment: .leading, spacing: 4) {
                Text(fruit.name)
                    .font(.headline)
                HStack {
                    Text(fruit.category)
                        .font(.caption)
                        .foregroundColor(.secondary)
                    Text("•")
                        .foregroundStyle(.secondary)
                    Text("Vitamins: \(fruit.vitamins)")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }
        }
        .padding(.vertical, 4)
    }
}

Key Features:

  • Circular emoji container with gray background
  • Clean layout with fruit name as headline
  • Secondary information displayed with proper spacing
  • Consistent padding for optimal appearance

2. CategoryButton Component

Next, let's examine our custom category filter button:

struct CategoryButton: View {
    let title: String
    let isSelected: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.subheadline)
                .fontWeight(.medium)
                .padding(.horizontal, 16)
                .padding(.vertical, 8)
                .background(isSelected ? Color.accentColor : Color.gray.opacity(0.1))
                .foregroundStyle(isSelected ? .white : .primary)
                .clipShape(Capsule())
        }
    }
}

Key Features:

  • Capsule-shaped button design
  • Visual feedback for selected state
  • Consistent padding and spacing
  • Reusable component design

3. BetterSearchView Implementation

Finally, let's look at how we bring it all together:

struct BetterSearchView: View {
    @StateObject private var viewModel = FruitViewModel()

    var body: some View {
        NavigationStack {
            VStack(spacing: 1) {
                // Category Filter ScrollView
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing: 12) {
                        CategoryButton(title: "All", isSelected: viewModel.selectedCategory == nil) {
                            viewModel.selectedCategory = nil
                        }

                        ForEach(viewModel.categories, id: \.self) { category in
                            CategoryButton(title: category, 
                                         isSelected: viewModel.selectedCategory == category) {
                                viewModel.selectedCategory = category
                            }
                        }
                    }
                    .padding(.horizontal)
                }
                .padding(.vertical, 8)

                // Fruit List
                List {
                    ForEach(viewModel.filteredFruits) { fruit in
                        FruitRow(fruit: fruit)
                    }
                }
                .listStyle(.inset)
            }
            .searchable(text: $viewModel.searchText, 
                       placement: .navigationBarDrawer(displayMode: .always),
                       prompt: "Search The Name Or Select Category...")
            .navigationTitle("A Better Fruit Search")
            .navigationBarTitleDisplayMode(.large)
        }
    }
}

Key Features:

  1. Navigation and Search

    • Large navigation title
    • Persistent search bar
    • Custom search prompt
  2. Category Filtering

    • Horizontal scrolling category buttons
    • "All" option to clear filters
    • Visual feedback for selected category
  3. List Implementation

    • Clean, inset list style
    • Custom row components
    • Smooth scrolling experience

Putting It All Together

The view hierarchy creates a seamless user experience:

  1. Search bar at the top
  2. Horizontal scrolling categories
  3. Filtered list of fruits
  4. Custom-styled rows for each fruit

Best Practices Implemented

  1. Component Separation

    • Reusable components (FruitRow, CategoryButton)
    • Clean, maintainable code structure
  2. User Experience

    • Smooth animations
    • Clear visual hierarchy
    • Intuitive filtering system
  3. Performance

    • Efficient list rendering
    • Minimal view updates
    • Smooth scrolling