Pattern · Lists and Feeds
NewSearch Panel SwiftUI Block
The body of a search screen: recent and suggested searches, live suggestions with the match highlighted, and results by scope.
Drop it under a `.searchable` field. Before typing it shows recent searches with remove and Clear all and suggested searches as chips; while typing it asks your closure for debounced suggestions with the typed text highlighted; after a search it shows scope chips with counts and a card of results per scope, with loading, failed and no-results states built in.
- SwiftUI
- iOS 17.0+
- search-panel
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Recent searches with remove and Clear all, kept for you
- Suggested searches as chips before typing
- Debounced live suggestions with the typed text in semibold
- Results by scope with counts and Show all
- Loading shapes, no results with a hint, and Try again on failure
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
Before anything is typed, the panel shows Recent searches on a card, each with a remove button and Clear all above them, and Suggested searches as outlined chips. As soon as the person types, it switches to Suggestions: your suggest closure runs 250 milliseconds after typing stops, a small spinner sits beside the title while it works, and each suggestion shows the typed text in semibold primary ink with the rest in the secondary ink, with a fill-in arrow to complete the field without searching and a last row that searches for exactly what was typed. When a search is submitted from the keyboard, a recent search, a chip or a suggestion, the panel runs your search closure behind grey shapes and then shows the results by scope: a row of chips with counts (All 9, Places 4, Guides 3, People 2), and under All a card per scope with its first two results and Show all, which selects that scope. Results have a symbol or initials, the title with the match in semibold, a line under it and a short trailing detail. A search with no results says so with a hint, and a failed one shows its message with Try again. Every submitted search moves to the top of Recent.
Notes
SearchPanel(query:submitted:recents:suggested:scopes:suggest:search:onSelect:)takes three bindings and three closures. It is a section: no navigation stack, background, padding or scroll view of its own. Put it in theScrollViewthat carries.searchable(text: $query)and.onSubmit(of: .search) { submitted = query }; SwiftUI only delivers the keyboard's Search to a submit handler on the host.queryis the field's text.submittedis the last submitted search: results show while it matches the trimmed query, and editing the field goes back to suggestions. Tapping a recent search, a chip or a suggestion sets both.recentsis newest first. The block inserts submitted searches (without duplicates, up to eight), removes one with its button and clears them all; persist the array however your app stores preferences.suggestedare the chips shown before typing. Leave it empty to hide the section; with neither recents nor suggestions the panel shows a short prompt built from the scope names.SearchPanel.Scopeis anidand atitle, in chip order;SearchPanel.sampleScopesis Places, Guides and People. Only scopes with results get a chip.SearchPanel.Resultholds anid, itsscopeid, thetitle, an optionalsubtitle, a shortdetailand an optional SF Symbol; without a symbol the row shows the title's initials, for people.Result.samplesis the catalog from the preview.suggest(text)is debounced and cancelled when the text changes, so a slow answer never replaces a newer one. A thrown error hides the suggestions quietly; the field still submits.search(text)runs once per submitted search and the results are kept while the text stays the same. A thrown error shows itserrorDescription(or "Check your connection and try again.") with Try again;CancellationErroris ignored.onSelectis called when a result is tapped.- Haptics mark a selection when the scope changes.
- VoiceOver hears headers for each section, labels for the remove and fill-in buttons, each result as its title, subtitle and detail, and an announcement with the result count.
- Reduce Motion turns every change into a short fade.
SearchPanelExampleat the end ofSearchPanel.swiftis the Explore screen in the preview: a travel guide with three recent searches, four suggestions, a small catalog for suggestions and a search that returns places, guides and people. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - Cards are
color.surfacewithradius.lg, rows at least 52 points,SPHairlinedividers inseparatorinset to the text, and sections 24 points apart (space.xl). Section titles aretype.headline. - Rows use
type.body, subtitles and detailstype.footnoteintextSecondaryandtextTertiary, symbolstextSecondary. Result symbols and initials sit in 40 point circles onfill. - Suggested chips are
surfacecapsules with aseparatorstroke. The selected scope chip istextPrimarywithbgtext; the rest match the suggested chips. Counts use monospaced digits. - Highlighted text is semibold
textPrimaryagainsttextSecondaryin suggestions and againsttextPrimaryin results. - Try again is the secondary button:
fillatradius.md, 52 points,type.headline. - Changes animate with
motion.smoothandmotion.snappy, and become short fades under Reduce Motion.
03
Keep building
Pieces that work well next to this one.