Using suggested searches with a search controller
At a glance
| Item | Summary |
|---|---|
| Purpose | Present suggested search tokens, filter products, navigate results, and restore search state with UISearchController. |
| App architecture | MainTableViewController owns the product list, UISearchController, and results controller; ResultsTableController switches between suggestions and filtered products, then reports selections through the local weak SuggestedSearch delegate. |
| Main patterns | Controller-owned search adapter, Weak local delegate, Token-aware filtering |
| Project style | Eight Swift files; a primary table controller with focused extensions for search updates and suggested-search callbacks. |
Project structure
Source bundle/
├── SuggestedSearch/
│ ├── ResultsTableController.swift
│ ├── MainTableViewController.swift
│ ├── AppDelegate.swift
│ ├── DetailViewController.swift
│ ├── SceneDelegate.swift
│ ├── Product.swift
│ ├── MainTableViewController+ResultsUpdate.swift
│ ├── MainTableViewController+SuggestedSearch.swift
│ └── Base.lproj/
│ └── LaunchScreen.storyboard
├── Configuration/
│ └── SampleCode.xcconfig
└── SuggestedSearch.xcodeproj/
├── .xcodesamplecode.plist
└── project.pbxproj
Structure observations
- Search construction, presentation, and restoration belong to the primary controller rather than app lifecycle delegates.
- Results are a child adapter with their own table state, but product/filter authority remains with the primary controller.
- The local delegate carries two user intents—insert a token or open a product—without retaining the main controller.
Overall architecture
flowchart LR
PRODUCTS["MainTableViewController products"] --> MAIN["MainTableViewController"]
MAIN --> SEARCH["UISearchController"]
SEARCH --> UPDATE["UISearchResultsUpdating callback"]
UPDATE --> FILTER["Text and UISearchToken filter"]
FILTER --> RESULTS["ResultsTableController"]
RESULTS --> SUGGEST["Suggested rows or filtered products"]
SUGGEST --> DELEGATE["SuggestedSearch weak delegate"]
DELEGATE --> MAIN
MAIN --> ACTIVITY["NSUserActivity search restoration"]
Reference code
SuggestedSearch/MainTableViewController.swift:101 — search-controller composition
resultsTableController = ResultsTableController()
resultsTableController.suggestedSearchDelegate = self
searchController = UISearchController(
searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.delegate = self
navigationItem.searchController = searchController
definesPresentationContext = trueInterpretation
The main controller is the search feature root. UIKit sends text/token changes through UISearchResultsUpdating; the controller filters its canonical products and assigns the projection to the results controller. Selection travels back as intent through a narrow local protocol.
Ownership and state
classDiagram
MainTableViewController *-- ProductArray : products
MainTableViewController *-- UISearchController : creates
MainTableViewController *-- ResultsTableController : creates
ResultsTableController *-- ProductArray : filteredProducts
ResultsTableController o-- SuggestedSearch : weak delegate
UISearchController o-- ResultsTableController : results content
MainTableViewController *-- NSUserActivity : scene restoration state
Ownership evidence
SuggestedSearch/ResultsTableController.swift:19 — results projection and weak callback
class ResultsTableController: UITableViewController {
var filteredProducts = [Product]()
weak var suggestedSearchDelegate: SuggestedSearch?
static let suggestedSearches = [
NSLocalizedString("Red Flowers", comment: ""),
NSLocalizedString("Green Flowers", comment: ""),
NSLocalizedString("Blue Flowers", comment: "")
]
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
MainTableViewController |
Canonical products, search controller, results controller | Creates and strongly stores all three feature collaborators | It updates filtering, tokens, presentation, navigation, and restoration state. |
UISearchController |
Search bar and results presentation | Owned by the main controller/navigation item | UIKit drives search/delegate callbacks. |
ResultsTableController |
Filtered-product projection and suggestion-mode flag | Stores result values and static suggestion labels | It chooses row representation and emits selection intent. |
ResultsTableController |
suggestedSearchDelegate |
Weak, class-bound reference | The main controller owns the child; the callback must not create a cycle. |
Product |
Title, date, price, color | Value stored in product arrays | Formatting helpers derive display values without owning UI. |
Composition denotes source-visible construction and retained value state. Aggregation denotes a stored or injected collaborator whose exclusive lifetime the source does not prove.
Class and protocol design
SuggestedSearch/ResultsTableController.swift:10 — local results-to-owner contract
protocol SuggestedSearch: AnyObject {
func didSelectSuggestedSearch(token: UISearchToken)
func didSelectProduct(product: Product)
}
class ResultsTableController: UITableViewController {
weak var suggestedSearchDelegate: SuggestedSearch?
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
MainTableViewController |
Owns search composition, filtering source, restoration, and navigation | UITableViewController, UISearchResultsUpdating, SuggestedSearch, search delegates |
ResultsTableController |
Displays suggestions or filtered products and emits selection | UITableViewController |
SuggestedSearch |
Reports token/product selection intent | Local class-bound protocol |
DetailViewController |
Presents one product and restores detail state | UIViewController |
Product |
Searchable/serializable product data and formatting | Codable value type |
SuggestedSearch is the sample’s protocol-oriented seam: the results controller depends on two capabilities, not on MainTableViewController. UIKit search protocols handle framework events separately.
Access control
| Symbol | Access | Verified effect | Design reason |
|---|---|---|---|
MainTableViewController.RestorationKeys |
private nested enum |
Usable only by the main controller’s lexical implementation | Keeps scene-payload keys coupled to the state they encode. |
setupDataModel |
private |
Only the main controller initializes the bundled product list | Demo-data construction is not a child-controller command. |
ResultsTableController.suggestedImage |
private |
Only result-cell rendering can create suggestion artwork | Prevents callers from depending on a presentation helper. |
Controllers, Product, and local protocol |
implicit internal |
Visible across the application target | The app needs cross-file conformances but exports no library API. |
Reference code
SuggestedSearch/MainTableViewController.swift:17 — controller-private restoration schema
private enum RestorationKeys: String {
case searchControllerIsActive
case searchBarText
case searchBarIsFirstResponder
case searchBarToken
}
var searchController: UISearchController!
var resultsTableController: ResultsTableController!
var products = [Product]()Private nested keys protect the controller’s restoration encoding, while the three collaborator properties remain internal so same-module extensions can use them. No public, open, or fileprivate feature API is present.
Logic ownership and placement
| Logic | Owning type or file | Why it lives there |
|---|---|---|
| Search object construction and scene restoration | MainTableViewController |
It owns the search UI and lifecycle state. |
| Text/token filtering | MainTableViewController+ResultsUpdate |
Filtering reads canonical products and writes the results projection. |
| Suggestion/result row presentation | ResultsTableController |
Only this controller knows which mode its table currently displays. |
| Token insertion and result navigation | MainTableViewController+SuggestedSearch |
These actions mutate main navigation/search state after a child intent. |
| Product display formatting | Product |
Price/year derivation is domain presentation data shared by tables/details. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Controller-owned search adapter | SuggestedSearch/MainTableViewController.swift:101 |
Composes UIKit search objects around one canonical product owner. |
| Weak local delegate | SuggestedSearch/ResultsTableController.swift:50 |
Returns semantic user intent without a parent-controller dependency or retain cycle. |
| Token-aware filtering | SuggestedSearch/MainTableViewController+ResultsUpdate.swift:13 |
Combines free text and represented token state into one results projection. |
Naming conventions
- Controller roles are explicit (
MainTableViewController,ResultsTableController,DetailViewController). - The local protocol names the interaction capability (
SuggestedSearch); methods name emitted intents (didSelect...). - Mode and projection names are descriptive (
showSuggestedSearches,filteredProducts). - Focused extensions are named for the conformance responsibility (
ResultsUpdate,SuggestedSearch).
Architecture takeaways
- Let the primary controller own canonical products and search composition; keep the child as a results projection.
- Return token/product selection through a weak capability protocol rather than a concrete parent reference.
- Keep restoration keys private and collaborators internal when same-module extensions need them.
Source map
| Source file | Architectural role |
|---|---|
SuggestedSearch/MainTableViewController.swift |
Search composition, product source, presentation, and restoration |
SuggestedSearch/MainTableViewController+ResultsUpdate.swift |
Text/token filtering |
SuggestedSearch/MainTableViewController+SuggestedSearch.swift |
Local delegate conformance, token insertion, and navigation |
SuggestedSearch/ResultsTableController.swift |
Suggestion/result projection and weak callback |
SuggestedSearch/Product.swift |
Product value and display formatting |
SuggestedSearch/DetailViewController.swift |
Selected-product presentation and restoration |