Mastering Swift Selection Search for Efficient iOS Development

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.


  • 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:

  1. Detecting text selection in a view (UITextView, WKWebView, UILabel-like controls).
  2. Adding a custom action to the UIMenuController or UIEditMenuInteraction (iOS 13+).
  3. Handling the action: extracting selected text and performing a search (local or network).
  4. Presenting results with a UI that preserves context (sheet, modal, push, or inline panel).
  5. 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 } 

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:

  1. User selects text in a UITextView subclass that exposes a “Search” action.
  2. The action posts a notification with the selection.
  3. 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.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *