Implementing Swift Selection Search: A Step-by-Step GuideImplementing a selection-based search feature in Swift—often called “Selection Search”—lets users search for selected text or content directly from your app. This pattern appears across many platforms: a user highlights text, a contextual menu appears, and they can choose “Search” (or a custom action) to query a search engine, your app’s internal index, or an external API. This guide walks through designing, implementing, and polishing a robust Selection Search experience for iOS using Swift and modern Apple APIs.
Why implement Selection Search?
- Improves discoverability: Users can investigate unfamiliar terms without leaving the current context.
- Boosts engagement: Quick, in-context actions reduce friction and increase usage of app features.
- Supports accessibility: Selection-based actions are familiar and work well with assistive technologies.
Overview of the approach
At a high level, implement Selection Search by:
- Detecting text selection in a view (UITextView, WKWebView, UILabel-like controls).
- Adding a custom action to the UIMenuController or UIEditMenuInteraction (iOS 13+).
- Handling the action: extracting selected text and performing a search (local or network).
- Presenting results with a UI that preserves context (sheet, modal, push, or inline panel).
- Handling edge cases: permissions, empty selections, different content types, and accessibility.
What APIs to use
- UITextView / UITextField selection APIs (selectedTextRange, text(in:), selectedRange)
- UIMenuController (older iOS) and UIEditMenuInteraction / UIMenu / UIAction (iOS 13+)
- UIPasteConfigurationSupporting for drag/drop contexts if needed
- WKWebView: evaluateJavaScript to get window.getSelection().toString()
- UISheetPresentationController, UISearchController, or custom view controller for displaying results
- Combine or async/await for network requests (iOS 13+ / iOS 15+ respectively)
- Speech, Vision, or NaturalLanguage frameworks if extending to non-text inputs
Step 1 — Detecting selection and adding a menu action
Core idea: when the selection menu appears, add a “Search” action that triggers your handler.
Example for a UITextView using UIMenuController (compatibility-friendly):
class SearchableTextView: UITextView { override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if action == #selector(searchSelectedText(_:)) { return hasText && selectedRange.length > 0 } return super.canPerformAction(action, withSender: sender) } override var canBecomeFirstResponder: Bool { true } @objc func searchSelectedText(_ sender: Any?) { guard let range = selectedTextRange, let selected = text(in: range)?.trimmingCharacters(in: .whitespacesAndNewlines), !selected.isEmpty else { return } NotificationCenter.default.post(name: .didRequestSelectionSearch, object: self, userInfo: ["text": selected]) } }
For modern APIs (iOS 13+), use UIEditMenuInteraction or UIMenu in view controllers:
// Example: registering a UIEditMenuInteraction on a custom view let interaction = UIEditMenuInteraction() view.addInteraction(interaction) // Implement the delegate to supply a UIAction for "Search"
For WKWebView:
webView.evaluateJavaScript("window.getSelection().toString()") { result, error in if let selected = result as? String, !selected.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { // proceed with search } }
Step 2 — Extracting and normalizing the selected text
Normalize the selection: trim whitespace, limit length, handle punctuation and special characters, and optionally determine content type (URL, email, code snippet).
Example normalization:
func normalizeSelected(_ raw: String) -> String? { let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } // Limit length to, say, 250 characters let maxLen = 250 return String(trimmed.prefix(maxLen)) }
Detect simple types:
enum SelectionType { case plainText, url, email } func detectType(_ text: String) -> SelectionType { if let _ = URL(string: text), text.contains(".") { return .url } if text.contains("@") { return .email } return .plainText }
Step 3 — Performing the search
Decide whether search is:
- Internal (local database, Core Data, Spotlight/CoreSpotlight)
- External (web search, REST API)
Use async/await or Combine for network calls. Example using async/await:
struct SearchResult: Decodable { let title: String let snippet: String let url: URL? } func performSearch(query: String) async throws -> [SearchResult] { let urlEncoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? query let url = URL(string: "https://api.example.com/search?q=(urlEncoded)")! var request = URLRequest(url: url) request.httpMethod = "GET" let (data, _) = try await URLSession.shared.data(for: request) return try JSONDecoder().decode([SearchResult].self, from: data) }
For local searches, use NSPredicate or Core Spotlight:
// Example: simple predicate search in an array of models let filtered = items.filter { $0.text.localizedCaseInsensitiveContains(query) }
Step 4 — Presenting results
Choose a UI that keeps context and is easy to dismiss:
- Inline panel: small dismissible view anchored to selection (use popover on iPad)
- Bottom sheet: UISheetPresentationController for rich results
- Modal / push: full-screen or pushed view for deep exploration
- Quick preview: SFSafariViewController for web results
Example: present a bottom sheet with a UISearchController-like results list.
let resultsVC = ResultsViewController() resultsVC.query = selectedText resultsVC.modalPresentationStyle = .pageSheet if let sheet = resultsVC.sheetPresentationController { sheet.detents = [.medium(), .large()] } present(resultsVC, animated: true)
UX tips:
- Show a loading state immediately.
- Highlight query terms in results.
- Allow opening results in external browser or sharing.
- Provide “Search again” or refine controls.
Step 5 — Accessibility & internationalization
- Ensure the custom menu action is reachable via VoiceOver (set accessibilityLabel and traits).
- Localize the action name and any UI strings.
- For complex scripts/languages, handle selection boundaries carefully (use NSString range APIs or TextKit).
- Support Dynamic Type in result views.
Example accessibility tweak:
searchButton.accessibilityLabel = NSLocalizedString("Search selection", comment: "") searchButton.accessibilityTraits = .button
Step 6 — Handling edge cases
- Empty selection: disable the action.
- Very long selections: truncate and indicate via UI.
- Selection in attributed text: preserve or strip attributes depending on need.
- Non-text content (images, PDFs): provide a fallback (e.g., OCR via Vision).
- Privacy: avoid sending sensitive selections; provide user confirmation if selection looks like personal data (emails, SSNs).
Step 7 — Testing and performance
- Test with VoiceOver and Switch Control.
- Test across devices and orientations, including iPad popover variations.
- Load-test searches if using network APIs, and debounce rapid repeated requests.
- Measure latency and show progress indicators when needed.
Example: Minimal end-to-end sample
High-level flow:
- User selects text in a UITextView subclass that exposes a “Search” action.
- The action posts a notification with the selection.
- A coordinator listens, normalizes text, calls an async search API, and presents results in a sheet.
Key code pieces have been shown above; combine them into your app architecture (MVC, MVVM, or Coordinator pattern).
Privacy considerations
- Only send selections to external services after ensuring user consent if content may be sensitive.
- Provide an option to disable selection-based searches in app settings.
- If logging queries, anonymize or avoid storing personally identifiable content.
Advanced extensions
- Add quick actions for other context-aware operations (define, translate, share).
- Integrate Core ML / NaturalLanguage to provide smart suggestions (e.g., detect entity types).
- Use Spotlight indexing to allow searching internal content from system search.
- Implement offline capabilities using an embedded index like SQLite + FTS.
Conclusion
Selection Search is a high-impact feature that improves user flow by letting users investigate content without leaving context. Implement it by adding a contextual menu action, extracting and normalizing selected text, performing a search (local or remote), and presenting results in a contextual UI. Prioritize accessibility, privacy, and responsiveness to create a polished experience.
Leave a Reply