├── .gitignore ├── DemoDirectory ├── DemoDirectory.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── DemoDirectory.xcscheme ├── DemoDirectory │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── check-square.imageset │ │ │ ├── Contents.json │ │ │ └── check-square.pdf │ │ ├── imagePlaceholder.imageset │ │ │ ├── Contents.json │ │ │ ├── placeholder-1.png │ │ │ ├── placeholder-2.png │ │ │ └── placeholder.png │ │ └── square.imageset │ │ │ ├── Contents.json │ │ │ └── square.pdf │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── DemoDirectory.entitlements │ ├── DemoListViewController.swift │ ├── Demos │ │ ├── Clear │ │ │ ├── ClearFiltersDemoController.swift │ │ │ ├── ClearFiltersDemoSwiftUI.swift │ │ │ └── ClearFiltersDemoViewController.swift │ │ ├── CurrentFilters │ │ │ ├── CurrentFiltersDemoSwiftUI.swift │ │ │ └── CurrentFiltersDemoViewController.swift │ │ ├── DynamicFacets │ │ │ ├── DynamicFacetsDemoController.swift │ │ │ ├── DynamicFacetsDemoViewController.swift │ │ │ └── DynamicFacetsSwiftUIDemoViewController.swift │ │ ├── FacetSearchDemoViewController.swift │ │ ├── Filters │ │ │ ├── FilterList │ │ │ │ ├── FilterListDemo.swift │ │ │ │ ├── FilterListDemoController.swift │ │ │ │ ├── FilterListDemoSwiftUI.swift │ │ │ │ └── FilterListDemoViewController.swift │ │ │ ├── FilterNumericComparisonDemoViewController.swift │ │ │ ├── FilterNumericRange │ │ │ │ ├── FilterNumberRangeDemoSwiftUI.swift │ │ │ │ ├── FilterNumericRangeDemoController.swift │ │ │ │ └── FilterNumericRangeDemoViewController.swift │ │ │ ├── Hierarchical │ │ │ │ ├── HierarchicalDemoController.swift │ │ │ │ ├── HierarchicalDemoSwiftUI.swift │ │ │ │ └── HierarchicalDemoViewController.swift │ │ │ ├── RefinementList │ │ │ │ ├── RefinementListDemoController.swift │ │ │ │ ├── RefinementListDemoSwiftUI.swift │ │ │ │ └── RefinementListDemoViewController.swift │ │ │ ├── RefinementPersistentListDemoViewController.swift │ │ │ ├── SegmentedDemoViewController.swift │ │ │ └── Toggle │ │ │ │ ├── ToggleDefaultDemoViewController.swift │ │ │ │ ├── ToggleDemoController.swift │ │ │ │ ├── ToggleDemoSwiftUI.swift │ │ │ │ └── ToggleDemoViewController.swift │ │ ├── Loading │ │ │ ├── LoadingDemoController.swift │ │ │ └── LoadingDemoSwiftUI.swift │ │ ├── MultiIndex │ │ │ ├── MultiIndexDemoViewController.swift │ │ │ └── MultiIndexMergedListDemoViewController.swift │ │ ├── MultiIndexCommerceDemoViewController.swift │ │ ├── MultiIndexSnippetViewController.swift │ │ ├── QueryRuleCustomData │ │ │ ├── QueryRuleCustomDataDemoSwiftUI.swift │ │ │ └── QueryRuleCustomDataDemoViewController.swift │ │ ├── QuerySuggestionsDemoViewController.swift │ │ ├── RatingViewController.swift │ │ ├── RecommendController.swift │ │ ├── RelatedItemsDemoViewController.swift │ │ ├── ResultsViewController.swift │ │ ├── SearchControllerDemo.swift │ │ ├── SearchInputDemoViewController.swift │ │ ├── SingleIndexSnippetViewController.swift │ │ ├── Sort │ │ │ ├── RelevantSort │ │ │ │ ├── RelevantSortDemoController.swift │ │ │ │ ├── RelevantSortDemoSwiftUI.swift │ │ │ │ ├── RelevantSortDemoViewController.swift │ │ │ │ ├── RelevantSortToggleController.swift │ │ │ │ └── SortByController.swift │ │ │ └── SortBy │ │ │ │ ├── SortByDemoController.swift │ │ │ │ ├── SortByDemoSwiftUI.swift │ │ │ │ └── SortByDemoViewController.swift │ │ ├── Stats │ │ │ ├── StatsDemoController.swift │ │ │ ├── StatsDemoSwiftUI.swift │ │ │ └── StatsDemoViewController.swift │ │ ├── StoreItemCollectionViewCell.swift │ │ ├── StoreItemView.swift │ │ ├── SuggestionsTableViewController.swift │ │ ├── SwiftUI │ │ │ ├── AlgoliaController.swift │ │ │ ├── ContentView.swift │ │ │ ├── FacetsView.swift │ │ │ ├── GettingStartedSwiftUI.swift │ │ │ ├── GettingStartedSwiftUI_iOS15.swift │ │ │ ├── InstantSearchItem.swift │ │ │ ├── QueryInputObservableController.swift │ │ │ ├── QueryRuleCustomDataSwiftUIDemo.swift │ │ │ ├── RelevantSortSwiftUIDemo.swift │ │ │ ├── ShopItemRow.swift │ │ │ ├── SuggestionsView.swift │ │ │ └── SwiftUIDemoViewController.swift │ │ ├── VoiceInputDemoViewController.swift │ │ └── Widgets │ │ │ ├── CellConfigurator.swift │ │ │ ├── HitsCollectionViewController.swift │ │ │ ├── HitsTableViewController.swift │ │ │ ├── MultiIndex │ │ │ └── MultiIndexSearchConnector.swift │ │ │ └── SingleIndex │ │ │ ├── GettingStartedSearchViewController.swift │ │ │ ├── HitsConnectorSearchViewController.swift │ │ │ └── SingleIndexSearchConnector.swift │ ├── Helpers │ │ ├── DemoHelper.swift │ │ ├── FacetListTableController.swift │ │ ├── FilterStateViewController.swift │ │ ├── Helpers.swift │ │ ├── NumericRangeController.swift │ │ ├── RangeSlider.swift │ │ ├── SearchStateViewController.swift │ │ ├── StoreItemsTableViewController.swift │ │ └── UITableView+EmptyMessage.swift │ ├── Info.plist │ ├── MainSplitViewController.swift │ ├── Models │ │ ├── Actor.swift │ │ ├── Banner.swift │ │ ├── Demo.swift │ │ ├── DemoFactory.swift │ │ ├── Movie.swift │ │ └── StoreItem.swift │ ├── Snippets.swift │ └── Views │ │ ├── BannerViewController.swift │ │ ├── SuggestionCollectionViewCell.swift │ │ ├── TagListViewController.swift │ │ ├── TemplateViewController.swift │ │ └── UIView+Layout.swift └── DemoDirectoryTests │ ├── DemoDirectoryTests.swift │ └── Info.plist ├── DemoEcommerce ├── DemoEcommerce.entitlements ├── DemoEcommerce.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── Shared │ ├── AlgoliaController.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── DemoEcommerceApp.swift │ ├── Extras │ │ ├── NumberRangeObservableController.swift │ │ └── RatingController.swift │ ├── FilterToggleObservableController.swift │ ├── HierarchicalList.swift │ ├── HierarchicalObservableController.swift │ ├── InstantSearchItem.swift │ └── View │ │ ├── ExpandableView.swift │ │ ├── FiltersView.swift │ │ ├── MainView.swift │ │ ├── RatingPicker.swift │ │ ├── SearchView.swift │ │ ├── SearchableFacetList.swift │ │ ├── ShopItemRow.swift │ │ └── SuggestionsList.swift ├── Tests iOS │ ├── Info.plist │ └── Tests_iOS.swift ├── Tests macOS │ ├── Info.plist │ └── Tests_macOS.swift ├── iOS │ └── Info.plist └── macOS │ ├── Info.plist │ └── macOS.entitlements ├── Examples ├── CategoriesHits │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ └── demo.gif ├── Examples.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── CategoriesHits.xcscheme │ │ ├── Examples.xcscheme │ │ ├── MultiIndex.xcscheme │ │ ├── QuerySuggestions.xcscheme │ │ ├── QuerySuggestionsCategories.xcscheme │ │ └── QuerySuggestionsHits.xcscheme ├── Examples │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── CategoryTableViewCell+Facet.swift │ ├── CategoryTableViewCell.swift │ ├── Demo.swift │ ├── Info.plist │ ├── Product.swift │ ├── QuerySuggestions │ │ ├── QuerySuggestions.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── QuerySuggestions │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── SceneDelegate.swift │ │ │ └── ViewController.swift │ ├── SceneDelegate.swift │ ├── SearchSuggestionTableViewCell+QuerySuggestion.swift │ ├── SearchSuggestionTableViewCell.swift │ ├── StoreItemTableViewCell+StoreItem.swift │ ├── StoreItemTableViewCell.swift │ └── ViewController.swift ├── MultiIndex │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ └── demo.gif ├── QuerySuggestions │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ ├── ViewController.swift │ └── demo.gif ├── QuerySuggestionsCategories │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ └── demo.gif ├── QuerySuggestionsHits │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ └── demo.gif ├── QuerySuggestionsRecentSearches │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultController.swift │ ├── SearchViewController.swift │ └── demo.gif └── VoiceSearch │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── SceneDelegate.swift │ ├── SearchResultsController.swift │ ├── SearchViewController.swift │ └── demo.gif ├── InsightsIntegration.playground ├── Contents.swift └── contents.xcplayground ├── LICENSE ├── README.md └── docs ├── Movies.gif ├── _icebnb.gif ├── ecommerce.png ├── facets.png ├── icebnb.gif ├── infinite-scrolling.gif ├── instant-results.gif ├── multi-index.gif ├── query-suggestions.gif ├── single-index.png ├── sort-by.gif └── suggestion.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | *.DS_Store 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | Package.resolved 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | ecommerce Demo/Pods/ 49 | Getting Started Programatically/Pods/ 50 | Getting Started Storyboard/Pods/ 51 | Getting Started Storyboard Objective-C/Pods/ 52 | Getting Started Part 2 Start/Pods/ 53 | IS-CustomSource/Pods/ 54 | IS-CustomSource/Podfile.lock 55 | Icebnb/Pods 56 | Query Suggestions/Pods/ 57 | Movies/Pods/ 58 | DemoDirectory/Pods/ 59 | DemoDirectory/Podfile.lock 60 | 61 | # Unfinished Projects 62 | 63 | 64 | # Carthage 65 | # 66 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 67 | # Carthage/Checkouts 68 | 69 | Carthage/Build 70 | 71 | # fastlane 72 | # 73 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 74 | # screenshots whenever they are needed. 75 | # For more information about the recommended setup visit: 76 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 77 | 78 | fastlane/report.xml 79 | fastlane/Preview.html 80 | fastlane/screenshots 81 | fastlane/test_output 82 | 83 | #local dependencies 84 | algoliasearch-client-swift/ 85 | instantsearch-ios/ 86 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AlgoliaSearchClient", 6 | "repositoryURL": "https://github.com/algolia/algoliasearch-client-swift", 7 | "state": { 8 | "branch": null, 9 | "revision": "226f43968f64e69dbc0499d7ae5790967275ff61", 10 | "version": "8.12.0" 11 | } 12 | }, 13 | { 14 | "package": "InstantSearch", 15 | "repositoryURL": "https://github.com/algolia/instantsearch-ios", 16 | "state": { 17 | "branch": null, 18 | "revision": "a1e124166ac763d08e92c870c896e5e65f8b6c87", 19 | "version": "7.15.0" 20 | } 21 | }, 22 | { 23 | "package": "SDWebImage", 24 | "repositoryURL": "https://github.com/SDWebImage/SDWebImage", 25 | "state": { 26 | "branch": null, 27 | "revision": "88b3ba211c0d604e60eab204c1baf8229c513399", 28 | "version": "5.9.4" 29 | } 30 | }, 31 | { 32 | "package": "SDWebImageSwiftUI", 33 | "repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI", 34 | "state": { 35 | "branch": null, 36 | "revision": "4c7f169e39bc35d6b80d42b8eb8301bee9cd0907", 37 | "version": "1.5.0" 38 | } 39 | }, 40 | { 41 | "package": "swift-log", 42 | "repositoryURL": "https://github.com/apple/swift-log.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7", 46 | "version": "1.4.2" 47 | } 48 | }, 49 | { 50 | "package": "InstantSearchVoiceOverlay", 51 | "repositoryURL": "https://github.com/algolia/voice-overlay-ios", 52 | "state": { 53 | "branch": null, 54 | "revision": "55a1047be9e0d9e4d4295ef127cc31b3ef7b8b54", 55 | "version": "1.2.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DemoDirectory 4 | // 5 | // Created by Guy Daher on 05/03/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | self.window?.rootViewController = MainSplitViewController(nibName: nil, bundle: nil) 19 | self.window?.makeKeyAndVisible() 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/check-square.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "check-square.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "preserves-vector-representation" : true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/check-square.imageset/check-square.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/DemoDirectory/DemoDirectory/Assets.xcassets/check-square.imageset/check-square.pdf -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "placeholder.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "placeholder-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "placeholder-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder-1.png -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder-2.png -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/DemoDirectory/DemoDirectory/Assets.xcassets/imagePlaceholder.imageset/placeholder.png -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/square.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "square.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "preserves-vector-representation" : true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Assets.xcassets/square.imageset/square.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/DemoDirectory/DemoDirectory/Assets.xcassets/square.imageset/square.pdf -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/DemoDirectory.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.device.audio-input 6 | 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Clear/ClearFiltersDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClearFiltersDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class ClearFiltersDemoController { 13 | 14 | let filterState: FilterState 15 | 16 | let clearColorsConnector: FilterClearConnector 17 | let clearExceptColorsConnector: FilterClearConnector 18 | 19 | init(clearController: FCC, 20 | clearExceptController: FCC) { 21 | filterState = .init() 22 | let groupColor = FilterGroup.ID.or(name: "color", filterType: .facet) 23 | clearColorsConnector = .init(filterState: filterState, 24 | clearMode: .specified, 25 | filterGroupIDs: [groupColor], 26 | controller: clearController) 27 | clearExceptColorsConnector = .init(filterState: filterState, 28 | clearMode: .except, 29 | filterGroupIDs: [groupColor], 30 | controller: clearExceptController) 31 | 32 | let categoryFacet = Filter.Facet(attribute: "category", value: "shoe") 33 | let redFacet = Filter.Facet(attribute: "color", value: "red") 34 | let greenFacet = Filter.Facet(attribute: "color", value: "green") 35 | 36 | filterState[and: "category"].add(categoryFacet) 37 | filterState[or: "color"].add(redFacet, greenFacet) 38 | filterState.notifyChange() 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Clear/ClearFiltersDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClearFiltersDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/07/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct ClearFiltersDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | @ObservedObject var filterClearController: FilterClearObservableController 19 | 20 | var body: some View { 21 | Button("Clear filters") { 22 | filterClearController.clear() 23 | } 24 | } 25 | 26 | } 27 | 28 | static var previews: some View { 29 | ContentView(filterClearController: .init()) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/CurrentFilters/CurrentFiltersDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentFiltersDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/07/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct CurrentFiltersDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | @ObservedObject var currentFiltersController: CurrentFiltersObservableController 19 | 20 | var body: some View { 21 | NavigationView { 22 | VStack { 23 | let filtersPerGroup = Dictionary(grouping: currentFiltersController.filters) { $0.id } 24 | .mapValues { $0.map(\.filter) } 25 | .map { $0 } 26 | ForEach(filtersPerGroup, id: \.key) { (group, filters) in 27 | HStack { 28 | Text(group.description) 29 | .bold() 30 | .padding(.leading) 31 | Spacer() 32 | } 33 | .padding(.vertical, 5) 34 | .background(Color(.systemGray5)) 35 | ForEach(filters, id: \.self) { filter in 36 | HStack { 37 | Text(filter.description) 38 | .padding(.leading) 39 | Spacer() 40 | } 41 | } 42 | } 43 | Spacer() 44 | }.navigationBarTitle("Filters") 45 | } 46 | } 47 | 48 | } 49 | 50 | static var previews: some View { 51 | ContentView(currentFiltersController: .init(filters: [ 52 | .init(filter: .facet(.init(attribute: "brand", stringValue: "sony")), id: .and(name: "groupA")), 53 | .init(filter: .numeric(.init(attribute: "price", range: 50...100)), id: .and(name: "groupA")), 54 | .init(filter: .tag("Free delivery"), id: .and(name: "groupA")), 55 | .init(filter: .numeric(.init(attribute: "salesRank", operator: .lessThan, value: 100)), id: .and(name: "groupB")), 56 | .init(filter: .tag("On Sale"), id: .and(name: "groupB")) 57 | ])) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/DynamicFacets/DynamicFacetsDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicFacetListDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 18/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import InstantSearch 12 | 13 | class DynamicFacetListDemoController { 14 | 15 | let searcher: HitsSearcher 16 | let queryInputConnector: QueryInputConnector 17 | let DynamicFacetListConnector: DynamicFacetListConnector 18 | let filterState: FilterState 19 | 20 | init(queryInputController: QIC, 21 | DynamicFacetListController: DFC) { 22 | searcher = .init(client: .init(appID: "RVURKQXRHU", 23 | apiKey: "937e4e6ec422ff69fe89b569dba30180"), 24 | indexName: "test_facet_ordering") 25 | filterState = .init() 26 | queryInputConnector = .init(searcher: searcher, controller: queryInputController) 27 | DynamicFacetListConnector = .init(searcher: searcher, 28 | filterState: filterState, 29 | selectionModeForAttribute: [ 30 | "color": .multiple, 31 | "country": .multiple 32 | ], 33 | filterGroupForAttribute: [ 34 | "brand": ("brand", .or), 35 | "color" : ("color", .or), 36 | "size": ("size", .or), 37 | "country": ("country", .or) 38 | ], 39 | controller: DynamicFacetListController) 40 | searcher.request.query.facets = ["brand", "color", "size", "country"] 41 | searcher.connectFilterState(filterState) 42 | searcher.search() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/FilterList/FilterListDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterListDemo.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | struct FilterListDemo { 13 | 14 | static func facet() -> FilterListDemoViewController { 15 | 16 | let facetFilters: [Filter.Facet] = ["red", "blue", "green", "yellow", "black"].map { 17 | .init(attribute: "color", stringValue: $0) 18 | } 19 | 20 | return FilterListDemoViewController(items: facetFilters, selectionMode: .multiple) 21 | 22 | } 23 | 24 | static func numeric() -> FilterListDemoViewController { 25 | 26 | let numericFilters: [Filter.Numeric] = [ 27 | .init(attribute: "price", operator: .lessThan, value: 5), 28 | .init(attribute: "price", range: 5...10), 29 | .init(attribute: "price", range: 10...25), 30 | .init(attribute: "price", range: 25...100), 31 | .init(attribute: "price", operator: .greaterThan, value: 100) 32 | ] 33 | 34 | return FilterListDemoViewController(items: numericFilters, selectionMode: .single) 35 | 36 | } 37 | 38 | static func tag() -> FilterListDemoViewController { 39 | 40 | let tagFilters: [Filter.Tag] = [ 41 | "coupon", "free shipping", "free return", "on sale", "no exchange"] 42 | 43 | return FilterListDemoViewController(items: tagFilters, selectionMode: .multiple) 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/FilterList/FilterListDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterListDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class FilterListDemoController { 13 | 14 | let searcher: HitsSearcher 15 | let filterState: FilterState 16 | let filterListConnector: FilterListConnector 17 | 18 | init(filters: [Filter], 19 | controller: Controller, 20 | selectionMode: SelectionMode) where Controller.Item == Filter { 21 | searcher = HitsSearcher(client: .demo, 22 | indexName: "mobile_demo_filter_list") 23 | filterState = .init() 24 | filterListConnector = .init(filterState: filterState, 25 | filters: filters, 26 | selectionMode: selectionMode, 27 | operator: .or, 28 | groupName: "filters", 29 | controller: controller) 30 | 31 | searcher.isDisjunctiveFacetingEnabled = false 32 | searcher.search() 33 | searcher.connectFilterState(filterState) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/FilterList/FilterListDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterListDemoViewController.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 26/05/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | import UIKit 12 | 13 | class FilterListDemoViewController: UIViewController { 14 | 15 | let controller: FilterListDemoController 16 | 17 | let filterListController: FilterListTableController 18 | let searchStateViewController: SearchStateViewController 19 | 20 | init(items: [F], selectionMode: SelectionMode) { 21 | filterListController = FilterListTableController(tableView: .init()) 22 | searchStateViewController = SearchStateViewController() 23 | controller = .init(filters: items, 24 | controller: filterListController, 25 | selectionMode: selectionMode) 26 | super.init(nibName: nil, bundle: nil) 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | setup() 36 | setupUI() 37 | } 38 | 39 | } 40 | 41 | private extension FilterListDemoViewController { 42 | 43 | func setup() { 44 | searchStateViewController.connectFilterState(controller.filterState) 45 | searchStateViewController.connectSearcher(controller.searcher) 46 | } 47 | 48 | func setupUI() { 49 | 50 | view.backgroundColor = .white 51 | 52 | let mainStackView = UIStackView() 53 | mainStackView.translatesAutoresizingMaskIntoConstraints = false 54 | mainStackView.axis = .vertical 55 | mainStackView.spacing = .px16 56 | 57 | view.addSubview(mainStackView) 58 | 59 | mainStackView.pin(to: view.safeAreaLayoutGuide) 60 | 61 | addChild(searchStateViewController) 62 | searchStateViewController.didMove(toParent: self) 63 | searchStateViewController.view.heightAnchor.constraint(equalToConstant: 150).isActive = true 64 | 65 | mainStackView.addArrangedSubview(searchStateViewController.view) 66 | mainStackView.addArrangedSubview(filterListController.tableView) 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/FilterNumericRange/FilterNumericRangeDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterNumericRangeDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | 12 | class FilterNumericRangeDemoController { 13 | 14 | let searcher: HitsSearcher 15 | let filterState: FilterState 16 | 17 | let primarySliderConnector: NumberRangeConnector 18 | let secondarySliderConnector: NumberRangeConnector 19 | 20 | init(primaryController: NumericRangeController, 21 | secondaryController: NumericRangeController) { 22 | self.searcher = HitsSearcher(client: .demo, indexName: "mobile_demo_filter_numeric_comparison") 23 | self.filterState = .init() 24 | 25 | primarySliderConnector = .init(searcher: searcher, 26 | filterState: filterState, 27 | attribute: "price", 28 | controller: primaryController) 29 | 30 | secondarySliderConnector = .init(searcher: searcher, 31 | filterState: filterState, 32 | attribute: "price", 33 | controller: secondaryController) 34 | 35 | searcher.connectFilterState(filterState) 36 | searcher.search() 37 | 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/Hierarchical/HierarchicalDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchicalDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class HierarchicalDemoController { 13 | 14 | let searcher: HitsSearcher 15 | let filterState: FilterState 16 | let hierarchicalConnector: HierarchicalConnector 17 | 18 | struct HierarchicalCategory { 19 | static var base: Attribute = "hierarchicalCategories" 20 | static var lvl0: Attribute { "\(base).lvl0" } 21 | static var lvl1: Attribute { "\(base).lvl1" } 22 | static var lvl2: Attribute { "\(base).lvl2" } 23 | } 24 | 25 | let order = [ 26 | HierarchicalCategory.lvl0, 27 | HierarchicalCategory.lvl1, 28 | HierarchicalCategory.lvl2, 29 | ] 30 | 31 | init(controller: Controller) where Controller.Item == [HierarchicalFacet] { 32 | searcher = HitsSearcher(client: .demo, indexName: "mobile_demo_hierarchical") 33 | filterState = .init() 34 | hierarchicalConnector = .init(searcher: searcher, 35 | filterState: filterState, 36 | hierarchicalAttributes: order, 37 | separator: " > ", 38 | controller: controller, 39 | presenter: DefaultPresenter.Hierarchical.present) 40 | searcher.connectFilterState(filterState) 41 | searcher.search() 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/Hierarchical/HierarchicalDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchicalDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | class HierarchicalDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | let controller: HierarchicalObservableController 19 | 20 | var body: some View { 21 | NavigationView { 22 | VStack { 23 | HierarchicalList(controller) { facet, nestingLevel, isSelected in 24 | HierarchicalFacetRow(facet: facet, 25 | nestingLevel: nestingLevel, 26 | isSelected: isSelected) 27 | .frame(height: 30) 28 | Divider() 29 | } 30 | Spacer() 31 | } 32 | .padding() 33 | .navigationBarTitle("Hierarchical Filters") 34 | } 35 | } 36 | 37 | } 38 | 39 | static let controller: HierarchicalObservableController = .init() 40 | static let demoController = HierarchicalDemoController(controller: controller) 41 | 42 | static var previews: some View { 43 | ContentView(controller: controller) 44 | .onAppear { 45 | _ = demoController 46 | } 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/Toggle/ToggleDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class ToggleDemoController { 13 | 14 | let searcher: HitsSearcher 15 | let filterState: FilterState 16 | 17 | let sizeConstraintConnector: FilterToggleConnector 18 | let vintageConnector: FilterToggleConnector 19 | let couponConnector: FilterToggleConnector 20 | 21 | init(sizeConstraintButtonController: SelectableFilterButtonController, 22 | vintageButtonController: SelectableFilterButtonController, 23 | couponSwitchController: FilterSwitchController) { 24 | searcher = HitsSearcher(client: .demo, indexName: "mobile_demo_filter_toggle") 25 | filterState = .init() 26 | 27 | // Size constraint button 28 | let sizeConstraintFilter = Filter.Numeric(attribute: "size", operator: .greaterThan, value: 40) 29 | sizeConstraintConnector = .init(filterState: filterState, 30 | filter: sizeConstraintFilter, 31 | controller: sizeConstraintButtonController) 32 | 33 | // Vintage tag button 34 | let vintageFilter = Filter.Tag(value: "vintage") 35 | vintageConnector = .init(filterState: filterState, 36 | filter: vintageFilter, 37 | controller: vintageButtonController) 38 | 39 | // Coupon switch 40 | let couponFacet = Filter.Facet(attribute: "promotions", stringValue: "coupon") 41 | couponConnector = .init(filterState: filterState, 42 | filter: couponFacet, 43 | controller: couponSwitchController) 44 | 45 | 46 | searcher.connectFilterState(filterState) 47 | searcher.search() 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Filters/Toggle/ToggleDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 01/07/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct ToggleDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | @ObservedObject var toggleController: FilterToggleObservableController 19 | 20 | var body: some View { 21 | Toggle(toggleController.filter?.description ?? "No filter", 22 | isOn: $toggleController.isSelected).padding() 23 | } 24 | 25 | } 26 | 27 | static var previews: some View { 28 | ContentView(toggleController: .init(filter: "On Sale", isSelected: true)) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Loading/LoadingDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class LoadingDemoController { 13 | 14 | typealias HitType = Movie 15 | 16 | let searcher: HitsSearcher 17 | let hitsConnector: HitsConnector 18 | let queryInputConnector: QueryInputConnector 19 | let loadingConnector: LoadingConnector 20 | let statsConnector: StatsConnector 21 | 22 | init(queryInputController: QI, 26 | loadingController: LC, 27 | statsController: SC, 28 | hitsController: HC) where HC.DataSource == HitsInteractor { 29 | searcher = HitsSearcher(client: .demo, indexName: "mobile_demo_movies") 30 | queryInputConnector = QueryInputConnector(searcher: searcher, 31 | controller: queryInputController) 32 | loadingConnector = .init(searcher: searcher, 33 | controller: loadingController) 34 | statsConnector = .init(searcher: searcher, 35 | controller: statsController, 36 | presenter: DefaultPresenter.Stats.present) 37 | hitsConnector = .init(searcher: searcher, 38 | interactor: .init(), 39 | controller: hitsController) 40 | searcher.search() 41 | } 42 | 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Loading/LoadingDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/07/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct LoadingDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | @ObservedObject var loadingController: LoadingObservableController 19 | 20 | var body: some View { 21 | VStack { 22 | Text("Loading") 23 | if #available(iOS 14.0, *) { 24 | if loadingController.isLoading { 25 | ProgressView() 26 | } 27 | } 28 | } 29 | } 30 | 31 | } 32 | 33 | static var previews: some View { 34 | ContentView(loadingController: .init(isLoading: true)) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/RecommendController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecommendController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 24/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import AlgoliaSearchClient 12 | 13 | class RecommendController { 14 | 15 | let recommendClient: RecommendClient 16 | 17 | init(recommendClient: RecommendClient) { 18 | self.recommendClient = recommendClient 19 | } 20 | 21 | func presentRelatedItems(for objectID: ObjectID, from sourceViewController: UIViewController, animated: Bool = true) { 22 | recommendClient.getRelatedProducts(options: [.init(indexName: .recommend, objectID: objectID)]) { result in 23 | DispatchQueue.main.async { 24 | switch result { 25 | case .failure(let error): 26 | let alertController = UIAlertController(title: "Error", message: "\(error)", preferredStyle: .alert) 27 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel)) 28 | sourceViewController.present(alertController, animated: animated) 29 | case .success(let response): 30 | let viewController = StoreItemsTableViewController.with(response.results.first!) 31 | viewController.title = "Related items" 32 | let navigationController = UINavigationController(rootViewController: viewController) 33 | sourceViewController.present(navigationController, animated: animated) 34 | } 35 | } 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SearchInputDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchInputDemoViewController.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 13/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | class SearchInputDemoViewController: UIViewController { 14 | 15 | let searcher: HitsSearcher 16 | 17 | let hitsInteractor: HitsInteractor> 18 | 19 | let searchController: UISearchController 20 | let textFieldController: TextFieldController 21 | let queryInputConnector: QueryInputConnector 22 | let resultsViewController: ResultsViewController 23 | 24 | init(searchTriggeringMode: SearchTriggeringMode) { 25 | searcher = .init(client: .newDemo, 26 | indexName: Index.Ecommerce.products) 27 | resultsViewController = .init(searcher: searcher) 28 | searchController = .init(searchResultsController: resultsViewController) 29 | textFieldController = .init(searchBar: searchController.searchBar) 30 | queryInputConnector = .init(searcher: searcher, 31 | searchTriggeringMode: searchTriggeringMode, 32 | controller: textFieldController) 33 | hitsInteractor = .init() 34 | super.init(nibName: .none, bundle: .none) 35 | setup() 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | setupUI() 45 | } 46 | 47 | override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | searchController.isActive = true 50 | } 51 | 52 | private func setupUI() { 53 | title = "Search" 54 | view.backgroundColor = .white 55 | definesPresentationContext = true 56 | navigationItem.searchController = searchController 57 | navigationItem.hidesSearchBarWhenScrolling = false 58 | searchController.hidesNavigationBarDuringPresentation = false 59 | searchController.showsSearchResultsController = true 60 | searchController.automaticallyShowsCancelButton = false 61 | } 62 | 63 | private func setup() { 64 | hitsInteractor.connectSearcher(searcher) 65 | hitsInteractor.connectController(resultsViewController.hitsViewController) 66 | searcher.search() 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Sort/RelevantSort/RelevantSortToggleController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelevantSortToggleController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 25.03.2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | class RelevantSortToggleController: UIViewController, RelevantSortController { 14 | 15 | var didToggle: (() -> Void)? 16 | 17 | let label: UILabel 18 | let button: UIButton 19 | 20 | init() { 21 | self.label = .init() 22 | self.button = .init() 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | layout() 33 | } 34 | 35 | func layout() { 36 | label.numberOfLines = 2 37 | label.translatesAutoresizingMaskIntoConstraints = false 38 | label.font = .systemFont(ofSize: 12) 39 | button.translatesAutoresizingMaskIntoConstraints = false 40 | button.titleLabel?.font = .systemFont(ofSize: 12) 41 | button.titleLabel?.numberOfLines = 0 42 | button.titleLabel?.textAlignment = .center 43 | button.setTitleColor(.blue, for: .normal) 44 | button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) 45 | let stackView = UIStackView() 46 | stackView.axis = .horizontal 47 | stackView.distribution = .fillEqually 48 | stackView.translatesAutoresizingMaskIntoConstraints = false 49 | stackView.addArrangedSubview(label) 50 | stackView.addArrangedSubview(button) 51 | view.addSubview(stackView) 52 | stackView.pin(to: view, insets: .init(top: 0, left: 0, bottom: 0, right: 0)) 53 | } 54 | 55 | func setItem(_ item: RelevantSortTextualRepresentation?) { 56 | guard let item = item else { 57 | label.text = nil 58 | button.setTitle(nil, for: .normal) 59 | view.isHidden = true 60 | return 61 | } 62 | view.isHidden = false 63 | label.text = item.hintText 64 | button.setTitle(item.toggleTitle, for: .normal) 65 | } 66 | 67 | @objc func didTapButton() { 68 | didToggle?() 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Sort/RelevantSort/SortByController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortByController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 25.03.2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | class SortByController: NSObject, SelectableSegmentController { 14 | 15 | let searchBar: UISearchBar 16 | 17 | var onClick: ((Int) -> Void)? 18 | 19 | init(searchBar: UISearchBar) { 20 | self.searchBar = searchBar 21 | super.init() 22 | self.searchBar.delegate = self 23 | } 24 | 25 | func setSelected(_ selected: Int?) { 26 | searchBar.selectedScopeButtonIndex = selected ?? 0 27 | } 28 | 29 | func setItems(items: [Int: String]) { 30 | searchBar.scopeButtonTitles = items.sorted(by: \.key).map(\.value) 31 | } 32 | 33 | } 34 | 35 | extension SortByController: UISearchBarDelegate { 36 | 37 | func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { 38 | onClick?(selectedScope) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Sort/SortBy/SortByDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortByDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | 12 | class SortByDemoController { 13 | 14 | let searcher: HitsSearcher 15 | let queryInputConnector: QueryInputConnector 16 | let statsConnector: StatsConnector 17 | let hitsConnector: HitsConnector> 18 | let sortByConnector: SortByConnector 19 | 20 | init() { 21 | self.searcher = HitsSearcher(client: .newDemo, 22 | indexName: Index.Ecommerce.products) 23 | self.queryInputConnector = .init(searcher: searcher) 24 | self.hitsConnector = .init(searcher: searcher) 25 | self.statsConnector = .init(searcher: searcher) 26 | sortByConnector = .init(searcher: searcher, 27 | indicesNames: [Index.Ecommerce.products, 28 | Index.Ecommerce.productsAsc, 29 | Index.Ecommerce.productsDesc], 30 | selected: 0) 31 | searcher.search() 32 | searcher.isDisjunctiveFacetingEnabled = false 33 | } 34 | 35 | func title(for indexName: IndexName) -> String { 36 | switch indexName { 37 | case Index.Ecommerce.products: 38 | return "Default" 39 | case Index.Ecommerce.productsAsc: 40 | return "Price Asc" 41 | case Index.Ecommerce.productsDesc: 42 | return "Price Desc" 43 | default: 44 | return indexName.rawValue 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Stats/StatsDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatsDemoController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 30/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | import UIKit 12 | 13 | class StatsDemoController { 14 | 15 | let searcher: HitsSearcher 16 | let statsConnector: StatsConnector 17 | let queryInputConnector: QueryInputConnector 18 | 19 | init(queryInputController: QI, 20 | statsController: SC, 21 | attributedStatsController: ASC) { 22 | self.searcher = HitsSearcher(client: .demo, indexName: "mobile_demo_movies") 23 | self.queryInputConnector = .init(searcher: searcher, controller: queryInputController) 24 | self.statsConnector = .init(searcher: searcher, controller: statsController) { stats -> String? in 25 | guard let stats = stats else { 26 | return nil 27 | } 28 | return "\(stats.totalHitsCount) hits in \(stats.processingTimeMS) ms" 29 | } 30 | 31 | statsConnector.interactor.connectController(attributedStatsController) { stats -> NSAttributedString? in 32 | guard let stats = stats else { 33 | return nil 34 | } 35 | let string = NSMutableAttributedString() 36 | string.append(NSAttributedString(string: "\(stats.totalHitsCount)", attributes: [NSAttributedString.Key.font: UIFont(name: "Chalkduster", size: 15)!])) 37 | string.append(NSAttributedString(string: " hits")) 38 | return string 39 | } 40 | 41 | searcher.search() 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Stats/StatsDemoSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatsDemoSwiftUI.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/07/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct StatsDemoSwiftUI: PreviewProvider { 15 | 16 | struct ContentView: View { 17 | 18 | @ObservedObject var statsController: StatsTextObservableController 19 | 20 | var body: some View { 21 | Text(statsController.stats) 22 | } 23 | 24 | } 25 | 26 | static var previews: some View { 27 | ContentView(statsController: .init(stats: "100 results (5ms)")) 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/StoreItemCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreItemCollection ViewCell.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 23/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | class StoreItemCollectionViewCell: UICollectionViewCell { 14 | 15 | let storeItemView: StoreItemView 16 | 17 | override init(frame: CGRect) { 18 | storeItemView = .init(frame: .zero) 19 | storeItemView.mainStackView.axis = .vertical 20 | super.init(frame: frame) 21 | storeItemView.translatesAutoresizingMaskIntoConstraints = false 22 | contentView.addSubview(storeItemView) 23 | storeItemView.pin(to: contentView) 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SuggestionsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuggestionsTableViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 24/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | class SuggestionsTableViewController: UITableViewController, HitsController, QueryInputController { 14 | 15 | var onQueryChanged: ((String?) -> Void)? 16 | var onQuerySubmitted: ((String?) -> Void)? 17 | 18 | public var hitsSource: HitsInteractor? 19 | 20 | let cellID = "suggestionCellID" 21 | 22 | public override init(style: UITableView.Style) { 23 | super.init(style: style) 24 | tableView.register(SearchSuggestionTableViewCell.self, forCellReuseIdentifier: cellID) 25 | } 26 | 27 | required init?(coder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | func setQuery(_ query: String?) { 32 | // not applicable 33 | } 34 | 35 | public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 36 | return hitsSource?.numberOfHits() ?? 0 37 | } 38 | 39 | public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 40 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? SearchSuggestionTableViewCell else { return .init() } 41 | 42 | if let suggestion = hitsSource?.hit(atIndex: indexPath.row) { 43 | cell.setup(with: suggestion) 44 | cell.didTapTypeAheadButton = { 45 | self.onQueryChanged?(suggestion.query) 46 | } 47 | } 48 | 49 | return cell 50 | } 51 | 52 | public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 53 | guard let suggestion = hitsSource?.hit(atIndex: indexPath.row) else { return } 54 | onQuerySubmitted?(suggestion.query) 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/FacetsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FacetsView.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/04/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchSwiftUI 11 | import SwiftUI 12 | 13 | struct FacetsView: View { 14 | 15 | let isSearchable: Bool 16 | 17 | @ObservedObject var facetSearchQueryInputController: QueryInputObservableController 18 | @ObservedObject var facetListController: FacetListObservableController 19 | @ObservedObject var statsController: StatsTextObservableController 20 | @ObservedObject var currentFiltersController: CurrentFiltersObservableController 21 | @ObservedObject var filterClearController: FilterClearObservableController 22 | 23 | @State private var isEditingFacetSearch = false 24 | 25 | var body: some View { 26 | let facetList = 27 | VStack { 28 | if isSearchable { 29 | SearchBar(text: $facetSearchQueryInputController.query, 30 | isEditing: $isEditingFacetSearch, 31 | placeholder: "Search for facet") 32 | } 33 | FacetList(facetListController) { facet, isSelected in 34 | VStack { 35 | FacetRow(facet: facet, isSelected: isSelected) 36 | Divider() 37 | } 38 | } noResults: { 39 | Text("No facets found") 40 | .frame(maxWidth: .infinity, maxHeight: .infinity) 41 | } 42 | } 43 | .padding() 44 | .navigationBarTitle("Brand") 45 | 46 | if #available(iOS 14.0, *) { 47 | facetList.toolbar { 48 | ToolbarItem(placement: .bottomBar) { 49 | Spacer() 50 | } 51 | ToolbarItem(placement: .bottomBar) { 52 | Text(statsController.stats) 53 | } 54 | ToolbarItem(placement: .bottomBar) { 55 | Spacer() 56 | } 57 | ToolbarItem(placement: .bottomBar) { 58 | Button(action: filterClearController.clear, 59 | label: { Image(systemName: "trash") } 60 | ).disabled(currentFiltersController.filters.isEmpty) 61 | } 62 | } 63 | } else { 64 | facetList 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/InstantSearchItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstantSearchItem.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/04/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct InstantSearchItem: Codable, Hashable { 12 | 13 | let objectID: String 14 | let name: String 15 | let brand: String? 16 | let description: String? 17 | let image: URL? 18 | let price: Double? 19 | 20 | } 21 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/QueryInputObservableController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryInputObservableController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 25/03/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/QueryRuleCustomDataSwiftUIDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryRuleCustomDataSwiftUIDemo.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 10/06/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/SuggestionsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuggestionsView.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 19/04/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | struct SuggestionsView: View { 15 | 16 | @Binding var query: String 17 | @Binding var isEditing: Bool 18 | @ObservedObject var suggestionsController: HitsObservableController 19 | 20 | var body: some View { 21 | HitsList(suggestionsController) { (hit, _) in 22 | if let querySuggestion = hit { 23 | SuggestionRow(suggestion: querySuggestion) { suggestion in 24 | query = suggestion 25 | isEditing = false 26 | } onTypeAhead: { suggestion in 27 | query = suggestion 28 | } 29 | Divider() 30 | } else { 31 | EmptyView() 32 | } 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/SwiftUI/SwiftUIDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIDemoViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 25/03/2021. 6 | // Copyright © 2021 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearch 11 | import InstantSearchSwiftUI 12 | import SwiftUI 13 | 14 | class SwiftUIDemoViewController: UIHostingController { 15 | 16 | let viewModel = AlgoliaController.test(areFacetsSearchable: true) 17 | 18 | init() { 19 | let contentView = ContentView(areFacetsSearchable: true) 20 | super.init(rootView: contentView) 21 | viewModel.setup(contentView) 22 | UIScrollView.appearance().keyboardDismissMode = .interactive 23 | } 24 | 25 | @objc required dynamic init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Widgets/CellConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellConfigurator.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 29/07/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public protocol CellConfigurator { 13 | associatedtype Model: Codable 14 | init(model: Model, indexPath: IndexPath) 15 | static var cellIdentifier: String { get } 16 | } 17 | 18 | extension CellConfigurator { 19 | static var cellIdentifier: String { return "\(Self.self)" } 20 | } 21 | 22 | public protocol TableViewCellConfigurator: CellConfigurator { 23 | associatedtype Cell: UITableViewCell 24 | var cellHeight: CGFloat { get } 25 | func configure(_ cell: Cell) 26 | } 27 | 28 | extension TableViewCellConfigurator { 29 | var cellHeight: CGFloat { return 44 } 30 | } 31 | 32 | public protocol CollectionViewCellConfigurator: CellConfigurator { 33 | associatedtype Cell: UICollectionViewCell 34 | var cellSize: CGSize { get } 35 | func configure(_ cell: Cell) 36 | } 37 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Widgets/HitsCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HitsCollectionViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 29/07/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | open class HitsCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, HitsController { 14 | 15 | public var hitsSource: HitsInteractor? 16 | 17 | open override func viewDidLoad() { 18 | super.viewDidLoad() 19 | collectionView.register(CellConfigurator.Cell.self, forCellWithReuseIdentifier: CellConfigurator.cellIdentifier) 20 | } 21 | 22 | open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 23 | return hitsSource?.numberOfHits() ?? 0 24 | } 25 | 26 | open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 27 | return collectionView.dequeueReusableCell(withReuseIdentifier: CellConfigurator.cellIdentifier, for: indexPath) 28 | } 29 | 30 | open override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 31 | guard let cell = cell as? CellConfigurator.Cell else { return } 32 | guard let model = hitsSource?.hit(atIndex: indexPath.row) else { return } 33 | CellConfigurator(model: model, indexPath: indexPath).configure(cell) 34 | } 35 | 36 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 37 | guard let model = hitsSource?.hit(atIndex: indexPath.row) else { return .zero } 38 | return CellConfigurator(model: model, indexPath: indexPath).cellSize 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Demos/Widgets/HitsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HitsTableViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 29/07/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | open class HitsTableViewController: UITableViewController, HitsController { 14 | 15 | public var hitsSource: HitsInteractor? 16 | 17 | open override func viewDidLoad() { 18 | super.viewDidLoad() 19 | tableView.register(CellConfigurator.Cell.self, forCellReuseIdentifier: CellConfigurator.cellIdentifier) 20 | } 21 | 22 | open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 23 | return hitsSource?.numberOfHits() ?? 0 24 | } 25 | 26 | open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 27 | return tableView.dequeueReusableCell(withIdentifier: CellConfigurator.cellIdentifier, for: indexPath) 28 | } 29 | 30 | open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 31 | guard let cell = cell as? CellConfigurator.Cell else { return } 32 | guard let model = hitsSource?.hit(atIndex: indexPath.row) else { return } 33 | CellConfigurator(model: model, indexPath: indexPath).configure(cell) 34 | } 35 | 36 | open override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 37 | guard let model = hitsSource?.hit(atIndex: indexPath.row) else { return 0 } 38 | return CellConfigurator(model: model, indexPath: indexPath).cellHeight 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Helpers/DemoHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoHelper.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 06/05/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearchCore 12 | 13 | extension SearchClient { 14 | static let demo = Self(appID: "latency", apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db") 15 | static let newDemo = Self(appID: "latency", apiKey: "927c3fe76d4b52c5a2912973f35a3077") 16 | static let recommend = Self(appID: "XX85YRZZMV", apiKey: "d17ff64e913b3293cfba3d3665480217") 17 | } 18 | 19 | extension Index { 20 | 21 | static func demo(withName demoIndexName: IndexName) -> Index { 22 | return SearchClient.demo.index(withName: demoIndexName) 23 | } 24 | 25 | } 26 | 27 | extension IndexName { 28 | static let recommend: IndexName = "test_FLAGSHIP_ECOM_recommend" 29 | } 30 | 31 | extension Index { 32 | 33 | struct Ecommerce { 34 | static let products: IndexName = "STAGING_native_ecom_demo_products" 35 | static let productsAsc: IndexName = "STAGING_native_ecom_demo_products_products_price_asc" 36 | static let productsDesc: IndexName = "STAGING_native_ecom_demo_products_products_price_desc" 37 | static let suggestions: IndexName = "STAGING_native_ecom_demo_products_query_suggestions" 38 | } 39 | 40 | } 41 | 42 | struct DemoHelper { 43 | public static let appID = "latency" 44 | public static let apiKey = "1f6fd3a6fb973cb08419fe7d288fa4db" 45 | } 46 | 47 | extension IndexPath { 48 | static let zero = IndexPath(row: 0, section: 0) 49 | } 50 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Helpers/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Guy Daher on 08/03/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | convenience init(hexString: String) { 14 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 15 | var int = UInt32() 16 | Scanner(string: hex).scanHexInt32(&int) 17 | let a, r, g, b: UInt32 18 | switch hex.count { 19 | case 3: // RGB (12-bit) 20 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 21 | case 6: // RGB (24-bit) 22 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 23 | case 8: // ARGB (32-bit) 24 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 25 | default: 26 | (a, r, g, b) = (255, 0, 0, 0) 27 | } 28 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 29 | } 30 | 31 | static let algoliaCyan = UIColor(hexString: "5468FF") 32 | 33 | } 34 | 35 | extension UIStackView { 36 | 37 | func addBackground(color: UIColor) { 38 | let subview = UIView(frame: bounds) 39 | subview.backgroundColor = color 40 | subview.autoresizingMask = [.flexibleWidth, .flexibleHeight] 41 | insertSubview(subview, at: 0) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Helpers/StoreItemsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EcomHitsTableViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 21/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearch 12 | 13 | class StoreItemsTableViewController: UITableViewController, HitsController { 14 | 15 | let cellIdentifier = "cellID" 16 | 17 | var hitsSource: HitsInteractor>? 18 | 19 | var didSelect: ((Hit) -> Void)? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | tableView.register(StoreItemTableViewCell.self, forCellReuseIdentifier: cellIdentifier) 24 | } 25 | 26 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 27 | let hitsCount = hitsSource?.numberOfHits() ?? 0 28 | if hitsCount == 0 { 29 | tableView.setEmptyMessage("No results") 30 | } else { 31 | tableView.restore() 32 | } 33 | return hitsCount 34 | } 35 | 36 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 37 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? StoreItemTableViewCell else { 38 | return UITableViewCell() 39 | } 40 | guard let hit = hitsSource?.hit(atIndex: indexPath.row) else { 41 | return cell 42 | } 43 | cell.setup(with: hit) 44 | return cell 45 | } 46 | 47 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 48 | return 80 49 | } 50 | 51 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 52 | if let hit = hitsSource?.hit(atIndex: indexPath.row) { 53 | didSelect?(hit) 54 | } 55 | } 56 | 57 | } 58 | 59 | extension StoreItemsTableViewController { 60 | 61 | static func with(_ response: SearchResponse) -> Self { 62 | let hitsInteractor = HitsInteractor>(infiniteScrolling: .off) 63 | hitsInteractor.update(response) 64 | let viewController = Self() 65 | hitsInteractor.connectController(viewController) 66 | return viewController 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Helpers/UITableView+EmptyMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+EmptyMessage.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 22/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UITableView { 13 | 14 | func setEmptyMessage(_ message: String) { 15 | let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)) 16 | messageLabel.text = message 17 | messageLabel.textColor = .black 18 | messageLabel.numberOfLines = 0 19 | messageLabel.textAlignment = .center 20 | messageLabel.sizeToFit() 21 | 22 | self.backgroundView = messageLabel 23 | self.separatorStyle = .none 24 | } 25 | 26 | func restore() { 27 | self.backgroundView = nil 28 | self.separatorStyle = .singleLine 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | NSMicrophoneUsageDescription 11 | Test Voiceoverlay 12 | NSSpeechRecognitionUsageDescription 13 | Test Voiceoverlay 14 | CFBundleDevelopmentRegion 15 | $(DEVELOPMENT_LANGUAGE) 16 | CFBundleExecutable 17 | $(EXECUTABLE_NAME) 18 | CFBundleIdentifier 19 | $(PRODUCT_BUNDLE_IDENTIFIER) 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | CFBundleName 23 | $(PRODUCT_NAME) 24 | CFBundlePackageType 25 | APPL 26 | CFBundleShortVersionString 27 | 1.0 28 | CFBundleURLTypes 29 | 30 | 31 | CFBundleTypeRole 32 | Editor 33 | CFBundleURLSchemes 34 | 35 | algoliademo 36 | 37 | 38 | 39 | CFBundleVersion 40 | 1 41 | LSRequiresIPhoneOS 42 | 43 | UILaunchStoryboardName 44 | LaunchScreen 45 | UIMainStoryboardFile 46 | Main 47 | UIRequiredDeviceCapabilities 48 | 49 | armv7 50 | 51 | UISupportedInterfaceOrientations 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | UISupportedInterfaceOrientations~ipad 58 | 59 | UIInterfaceOrientationPortrait 60 | UIInterfaceOrientationPortraitUpsideDown 61 | UIInterfaceOrientationLandscapeLeft 62 | UIInterfaceOrientationLandscapeRight 63 | 64 | UIUserInterfaceStyle 65 | Light 66 | 67 | 68 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/MainSplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainSplitViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 09/06/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class MainSplitViewController: UISplitViewController { 13 | 14 | let demoListNavigationController: UINavigationController 15 | let demoListViewController: DemoListViewController 16 | let demoFactory: DemoFactory 17 | 18 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 19 | demoListViewController = .init(nibName: nil, bundle: nil) 20 | demoFactory = .init() 21 | demoListNavigationController = UINavigationController(rootViewController: demoListViewController) 22 | demoListNavigationController.navigationBar.prefersLargeTitles = true 23 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | demoListViewController.title = "Demo Directory" 33 | demoListViewController.delegate = self 34 | viewControllers = [demoListNavigationController] 35 | } 36 | 37 | } 38 | 39 | extension MainSplitViewController: DemoListViewControllerDelegate { 40 | 41 | func demoListViewController(_ demoListViewController: DemoListViewController, didSelect demo: Demo) { 42 | do { 43 | let viewController = try demoFactory.viewController(for: demo, using: .SwiftUI) 44 | let navigationController = UINavigationController(rootViewController: viewController) 45 | showDetailViewController(navigationController, sender: self) 46 | } catch let error as DemoFactory.Error { 47 | switch error { 48 | case .demoNotImplemented: 49 | let notImplementedAlertController = UIAlertController(title: nil, message: "This demo is not implemented yet", preferredStyle: .alert) 50 | let okAction = UIAlertAction(title: "OK", style: .cancel, handler: .none) 51 | notImplementedAlertController.addAction(okAction) 52 | present(notImplementedAlertController, animated: true, completion: .none) 53 | } 54 | } catch let error { 55 | print("\(error)") 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Models/Actor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Actor.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 12/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Actor: Codable { 12 | let name: String 13 | let rating: Int 14 | let image_path: String 15 | } 16 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Models/Banner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Banner.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 12/10/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Banner: Codable { 12 | let title: String? 13 | let banner: URL? 14 | let link: URL 15 | } 16 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Models/Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Demo.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 09/06/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Demo: Codable { 12 | 13 | let objectID: String 14 | let name: String 15 | let type: String 16 | let index: String 17 | 18 | enum ID: String { 19 | case singleIndex = "paging_single_index" 20 | case multiIndex = "paging_multiple_index" 21 | case sffv = "facet_list_search" 22 | case toggle = "filter_toggle" 23 | case toggleDefault = "filter_toggle_default" 24 | case facetList = "facet_list" 25 | case dynamicFacetList = "dynamic_facets" 26 | case facetListPersistentSelection = "facet_list_persistent" 27 | case segmented = "filter_segment" 28 | // case allFilterList = "filter_list_all" 29 | case mergedList = "merged_list" 30 | case facetFilterList = "filter_list_facet" 31 | case numericFilterList = "filter_list_numeric" 32 | case tagFilterList = "filter_list_tag" 33 | case filterNumericComparison = "filter_numeric_comparison" 34 | case sortBy = "sort_by" 35 | case currentFilters = "filter_current" 36 | case searchAsYouType = "search_as_you_type" 37 | case searchOnSubmit = "search_on_submit" 38 | case clearFilters = "filter_clear" 39 | case filterNumericRange = "filter_numeric_range" 40 | case filterRating = "filter_rating" 41 | case stats 42 | case highlighting 43 | case loading 44 | case hierarchical = "filter_hierarchical" 45 | case querySuggestions = "query_suggestions" 46 | case relatedItems = "personalisation_related_items" 47 | case queryRuleCustomData = "query_rule_custom_data" 48 | case relevantSort = "dynamic_sort" 49 | case voiceSearch = "voice_search" 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Models/Movie.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Movie.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 12/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Movie: Codable { 12 | let title: String 13 | let year: Int 14 | let image: URL 15 | let genre: [String] 16 | } 17 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Models/StoreItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreItem.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 21/03/2022. 6 | // Copyright © 2022 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct StoreItem: Codable { 12 | 13 | let name: String 14 | let brand: String? 15 | let productType: String? 16 | let images: [URL] 17 | let price: Price? 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case name 21 | case brand 22 | case productType = "product_type" 23 | case images = "image_urls" 24 | case image 25 | case price 26 | } 27 | 28 | init(from decoder: Decoder) throws { 29 | let container = try decoder.container(keyedBy: CodingKeys.self) 30 | self.name = try container.decode(String.self, forKey: .name) 31 | self.brand = try? container.decode(String.self, forKey: .brand) 32 | self.productType = try? container.decode(String.self, forKey: .productType) 33 | if let rawImages = try? container.decode([String].self, forKey: .images) { 34 | self.images = rawImages.compactMap(URL.init) 35 | } else { 36 | self.images = (try? container.decode(URL.self, forKey: .image)).flatMap({ [$0] }) ?? [] 37 | } 38 | if let price = try? container.decode(Price.self, forKey: .price) { 39 | self.price = price 40 | } else { 41 | self.price = nil 42 | } 43 | } 44 | 45 | func encode(to encoder: Encoder) throws { 46 | var container = encoder.container(keyedBy: CodingKeys.self) 47 | try container.encode(name, forKey: .name) 48 | try container.encode(brand, forKey: .brand) 49 | try container.encode(productType, forKey: .productType) 50 | try container.encode(images, forKey: .images) 51 | try container.encode(price, forKey: .price) 52 | } 53 | 54 | } 55 | 56 | struct Price: Codable { 57 | 58 | let currency: String 59 | let value: Double 60 | let discountedValue: Double? 61 | let discountLevel: Double? 62 | let onSales: Bool? 63 | 64 | var isDiscounted: Bool { 65 | discountedValue.flatMap { $0 > 0 } ?? false 66 | } 67 | 68 | enum CodingKeys: String, CodingKey { 69 | case currency 70 | case value 71 | case discountedValue = "discounted_value" 72 | case discountLevel = "discount_level" 73 | case onSales = "on_sales" 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Views/BannerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BannerViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 12/10/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearchCore 12 | 13 | class BannerViewController: UIViewController, ItemController { 14 | 15 | var banner: Banner? { 16 | didSet { 17 | guard let banner = banner else { return } 18 | if let imageURL = banner.banner { 19 | imageView.sd_setImage(with: imageURL) 20 | } else if let text = banner.title { 21 | label.text = text 22 | view.backgroundColor = UIColor(red: 84/255, green: 104/255, blue: 1, alpha: 1) 23 | } 24 | } 25 | } 26 | 27 | let imageView: UIImageView 28 | let label: UILabel 29 | var didTapBanner: (() -> Void)? 30 | 31 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 32 | imageView = .init() 33 | label = .init() 34 | super.init(nibName: nil, bundle: nil) 35 | label.textColor = .white 36 | imageView.contentMode = .scaleAspectFit 37 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapBanner(_:))) 38 | view.isUserInteractionEnabled = true 39 | view.addGestureRecognizer(tapGestureRecognizer) 40 | } 41 | 42 | required init?(coder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | imageView.translatesAutoresizingMaskIntoConstraints = false 49 | view.addSubview(imageView) 50 | imageView.pin(to: view) 51 | label.translatesAutoresizingMaskIntoConstraints = false 52 | label.font = .systemFont(ofSize: 20, weight: .semibold) 53 | view.addSubview(label) 54 | label.pin(to: view, insets: .init(top: 10, left: 10, bottom: -10, right: -10)) 55 | } 56 | 57 | @objc func didTapBanner(_ tapGestureRecognizer: UITapGestureRecognizer) { 58 | didTapBanner?() 59 | } 60 | 61 | func setItem(_ item: Banner?) { 62 | banner = item 63 | if banner == nil { 64 | view.isHidden = true 65 | view.backgroundColor = .white 66 | imageView.sd_setImage(with: nil) 67 | label.text = nil 68 | } else { 69 | view.isHidden = false 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Views/SuggestionCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuggestionCollectionViewCell.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 18/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import InstantSearchCore 12 | 13 | class SuggestionCollectionViewCell: UICollectionViewCell { 14 | 15 | let label: UILabel 16 | 17 | override init(frame: CGRect) { 18 | self.label = UILabel(frame: .zero) 19 | super.init(frame: frame) 20 | layout() 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | private func layout() { 28 | label.translatesAutoresizingMaskIntoConstraints = false 29 | label.textAlignment = .center 30 | label.numberOfLines = 0 31 | contentView.backgroundColor = .white 32 | contentView.addSubview(label) 33 | label.pin(to: contentView.layoutMarginsGuide) 34 | } 35 | 36 | } 37 | 38 | extension SuggestionCollectionViewCell { 39 | 40 | func setup(with querySuggestion: QuerySuggestion) { 41 | label.attributedText = querySuggestion 42 | .highlighted 43 | .flatMap(HighlightedString.init) 44 | .flatMap { NSAttributedString(highlightedString: $0, 45 | inverted: true, 46 | attributes: [.font: UIFont.boldSystemFont(ofSize: label.font.pointSize)]) 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Views/TagListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagListViewController.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Guy Daher on 13/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InstantSearchCore 11 | import UIKit 12 | import TagListView 13 | 14 | open class TagListController: NSObject, ItemListController, TagListViewDelegate { 15 | 16 | public typealias Item = FilterAndID 17 | 18 | open var onRemoveItem: ((FilterAndID) -> Void)? 19 | 20 | public let tagListView: TagListView 21 | 22 | public var items: [FilterAndID] = [] 23 | 24 | public init(tagListView: TagListView) { 25 | self.tagListView = tagListView 26 | super.init() 27 | tagListView.delegate = self 28 | setupUI() 29 | } 30 | 31 | open func setItems(_ item: [FilterAndID]) { 32 | items = Array(item) 33 | } 34 | 35 | open func reload() { 36 | tagListView.removeAllTags() 37 | let tagViews = tagListView.addTags(items.map { $0.text }) 38 | for (index, tagView) in tagViews.enumerated() { 39 | tagView.tag = index 40 | } 41 | } 42 | 43 | public func tagRemoveButtonPressed(_ title: String, tagView: TagView, sender: TagListView) { 44 | let tag = tagView.tag 45 | let item = items[tag] 46 | 47 | onRemoveItem?(item) 48 | } 49 | 50 | } 51 | 52 | extension TagListController { 53 | func setupUI() { 54 | tagListView.textFont = UIFont.boldSystemFont(ofSize: 20) 55 | tagListView.tagBackgroundColor = UIColor(red: 98/255, green: 146/255, blue: 226/255, alpha: 1) 56 | tagListView.cornerRadius = 5 57 | tagListView.marginX = 20 58 | tagListView.paddingX = 5 59 | tagListView.enableRemoveButton = true 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Views/TemplateViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestViewController.swift 3 | // DemoDirectory 4 | // 5 | // Created by Vladislav Fitc on 12/10/2020. 6 | // Copyright © 2020 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class TemplateViewController: UIViewController { 13 | 14 | let label = UILabel() 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | view.backgroundColor = .white 19 | label.translatesAutoresizingMaskIntoConstraints = false 20 | view.addSubview(label) 21 | label.pin(to: view.safeAreaLayoutGuide) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectory/Views/UIView+Layout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Layout.swift 3 | // development-pods-instantsearch 4 | // 5 | // Created by Vladislav Fitc on 18/06/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIView { 13 | 14 | func pin(to view: UIView, insets: UIEdgeInsets = .init()) { 15 | NSLayoutConstraint.activate([ 16 | topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), 17 | bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: insets.bottom), 18 | leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), 19 | trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: insets.right), 20 | ]) 21 | } 22 | 23 | func pin(to layoutGuide: UILayoutGuide, insets: UIEdgeInsets = .init()) { 24 | NSLayoutConstraint.activate([ 25 | topAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: insets.top), 26 | bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor, constant: insets.bottom), 27 | leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor, constant: insets.left), 28 | trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor, constant: insets.right), 29 | ]) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectoryTests/DemoDirectoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoDirectoryTests.swift 3 | // DemoDirectoryTests 4 | // 5 | // Created by Vladislav Fitc on 07/08/2019. 6 | // Copyright © 2019 Algolia. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import DemoDirectory 11 | 12 | class DemoDirectoryTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /DemoDirectory/DemoDirectoryTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DemoEcommerce/DemoEcommerce.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DemoEcommerce/DemoEcommerce.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DemoEcommerce/DemoEcommerce.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DemoEcommerce/DemoEcommerce.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AlgoliaSearchClient", 6 | "repositoryURL": "https://github.com/algolia/algoliasearch-client-swift", 7 | "state": { 8 | "branch": null, 9 | "revision": "3bc2d64ea9fbf517c5711992f562770743e64ca8", 10 | "version": "8.11.0" 11 | } 12 | }, 13 | { 14 | "package": "InstantSearch", 15 | "repositoryURL": "https://github.com/algolia/instantsearch-ios", 16 | "state": { 17 | "branch": null, 18 | "revision": "28f09dcfc326d7a8846b83e3cd80d795bfc6c686", 19 | "version": "7.14.0" 20 | } 21 | }, 22 | { 23 | "package": "SDWebImage", 24 | "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "d1c75d839709b3f91c82ecf7d3c7665272090456", 28 | "version": "5.11.0" 29 | } 30 | }, 31 | { 32 | "package": "SDWebImageSwiftUI", 33 | "repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI", 34 | "state": { 35 | "branch": null, 36 | "revision": "cd8625b7cf11a97698e180d28bb7d5d357196678", 37 | "version": "2.0.2" 38 | } 39 | }, 40 | { 41 | "package": "swift-log", 42 | "repositoryURL": "https://github.com/apple/swift-log.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7", 46 | "version": "1.4.2" 47 | } 48 | }, 49 | { 50 | "package": "Sliders", 51 | "repositoryURL": "https://github.com/spacenation/swiftui-sliders", 52 | "state": { 53 | "branch": null, 54 | "revision": "5109792d45a53c3c41680b8bdb0602b5dde39c9a", 55 | "version": "1.0.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.578", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/DemoEcommerceApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoEcommerceApp.swift 3 | // Shared 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DemoEcommerceApp: App { 12 | 13 | let algoliaController = AlgoliaController.test() 14 | 15 | var body: some Scene { 16 | WindowGroup { 17 | MainView(viewModel: algoliaController.viewModel) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/Extras/NumberRangeObservableController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PriceController.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 21/04/2021. 6 | // 7 | 8 | import Foundation 9 | import InstantSearch 10 | 11 | public class NumberRangeObservableController: ObservableObject, NumberRangeController { 12 | 13 | @Published public var range: ClosedRange = Number(0)...Number(1) { 14 | didSet { 15 | if oldValue != range { 16 | onRangeChanged?(range) 17 | } 18 | } 19 | } 20 | 21 | @Published public var bounds: ClosedRange = Number(0)...Number(1) 22 | 23 | public var onRangeChanged: ((ClosedRange) -> Void)? 24 | 25 | private var isInitialBoundsSet: Bool = true 26 | 27 | public func setItem(_ range: ClosedRange) { 28 | self.range = range 29 | } 30 | 31 | public func setBounds(_ bounds: ClosedRange) { 32 | self.bounds = bounds 33 | if isInitialBoundsSet { 34 | isInitialBoundsSet = false 35 | self.range = bounds 36 | } 37 | } 38 | 39 | public init(range: ClosedRange, 40 | bounds: ClosedRange) { 41 | self.range = range.clamped(to: bounds) 42 | self.bounds = bounds 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/Extras/RatingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RatingController.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 21/04/2021. 6 | // 7 | 8 | import Foundation 9 | import InstantSearch 10 | 11 | class RatingController: ObservableObject, FacetListController { 12 | 13 | @Published var maxRating: Int = 5 14 | @Published var selectedRating: Int? 15 | @Published var itemsCountPerRating: [Int: Int] = [:] 16 | 17 | var onClick: ((Facet) -> Void)? 18 | var didChangeSelected: ((Int?) -> Void)? 19 | 20 | func select(minRating: Int) { 21 | if minRating == selectedRating { 22 | selectedRating = nil 23 | } else { 24 | selectedRating = minRating 25 | } 26 | didChangeSelected?(selectedRating) 27 | } 28 | 29 | func setSelectableItems(selectableItems: [SelectableItem]) { 30 | let itemsCountList = (1.. (Int, Int) in 32 | let count = selectableItems 33 | .map(\.item) 34 | .filter { Int($0.value).flatMap { $0 >= rating } ?? false } 35 | .map(\.count) 36 | .reduce(0, +) 37 | return (rating, count) 38 | } 39 | itemsCountPerRating = Dictionary(uniqueKeysWithValues: itemsCountList) 40 | selectedRating = selectableItems 41 | .filter { (_, isSelected) in isSelected } 42 | .compactMap { (item, _) in Int(item.value) } 43 | .min() 44 | } 45 | 46 | func reload() { 47 | objectWillChange.send() 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/FilterToggleObservableController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterToggleObservableController.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 11/04/2021. 6 | // 7 | 8 | import Foundation 9 | import InstantSearch 10 | 11 | public class FilterToggleObservableController: ObservableObject, SelectableController { 12 | 13 | @Published public var isSelected: Bool = false { 14 | didSet { 15 | if oldValue != isSelected { 16 | onClick?(isSelected) 17 | } 18 | } 19 | } 20 | 21 | public var onClick: ((Bool) -> Void)? 22 | 23 | public func setSelected(_ isSelected: Bool) { 24 | self.isSelected = isSelected 25 | } 26 | 27 | public func setItem(_ item: Filter) { 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/HierarchicalObservableController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchicalObservableController.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 21/04/2021. 6 | // 7 | 8 | import Foundation 9 | import InstantSearch 10 | 11 | public class HierarchicalObservableController: ObservableObject, HierarchicalController { 12 | 13 | @Published public var items: [HierarchicalFacet] = [] 14 | 15 | public var onClick: ((String) -> Void)? 16 | 17 | public func setItem(_ facets: [HierarchicalFacet]) { 18 | self.items = facets 19 | } 20 | 21 | public func select(_ facetValue: String) { 22 | onClick?(facetValue) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/InstantSearchItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstantSearchItem.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct InstantSearchItem: Codable, Hashable { 11 | let objectID: String 12 | let name: String 13 | let brand: String? 14 | let description: String? 15 | let image: URL? 16 | let price: Double? 17 | let free_shipping: Bool? 18 | let rating: Int? 19 | let categories: [String]? 20 | let hierarchicalCategories: [String: String]? 21 | } 22 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/View/ExpandableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableView.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 11/04/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct ExpandableView: View { 12 | 13 | let title: String 14 | let child: () -> Child 15 | @State private var isExpanded: Bool = true 16 | 17 | init(title: String, @ViewBuilder child: @escaping () -> Child) { 18 | self.title = title 19 | self.child = child 20 | } 21 | 22 | var body: some View { 23 | VStack(alignment: .leading, spacing: 10) { 24 | HStack { 25 | Button(action: { withAnimation { isExpanded.toggle() } }, 26 | label: { 27 | Text(title) 28 | .font(.headline) 29 | Spacer() 30 | Image(systemName: "chevron.right") 31 | .rotationEffect(.degrees(isExpanded ? 90 : 0)) 32 | .animation(.linear) 33 | }).padding(.trailing, 20) 34 | } 35 | if isExpanded { 36 | child().transition(.opacity) 37 | } 38 | } 39 | } 40 | 41 | } 42 | 43 | struct ExpandableView_Previews : PreviewProvider { 44 | 45 | static var previews: some View { 46 | VStack(spacing: 20) { 47 | ExpandableView(title: "Text1") { 48 | Text("Enjoy the exceptional display and all-day power of the Samsung Galaxy S7 smartphone. A 12MP rear-facing camera and 5MP front-facing camera capture memories as they happen, and the 5.1-inch display uses dual-pixel technology to display them with superior clarity. The Samsung Galaxy S7 smartphone features durable housing and a water-resistant design.") 49 | }.frame(alignment: .top).padding(.horizontal, 10) 50 | ExpandableView(title: "Text2") { 51 | Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") 52 | }.frame(alignment: .top).padding(.horizontal, 10) 53 | }.frame(maxHeight: .infinity, alignment: .top) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/View/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainView.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 21/04/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import InstantSearch 11 | import InstantSearchSwiftUI 12 | 13 | struct MainView: View { 14 | 15 | var algoliaViewModel: AlgoliaViewModel 16 | 17 | @ObservedObject var currentFiltersController: CurrentFiltersObservableController 18 | 19 | @State private var isFiltersViewPresented = false 20 | 21 | @Environment(\.presentationMode) var presentation 22 | 23 | var body: some View { 24 | let searchView = SearchView(viewModel: algoliaViewModel) 25 | let filtersView = FiltersView(viewModel: algoliaViewModel) 26 | NavigationView { 27 | if UIDevice.current.userInterfaceIdiom == .phone { 28 | searchView 29 | .navigationBarItems(trailing: filtersButton()) 30 | .sheet(isPresented: $isFiltersViewPresented) { 31 | NavigationView { 32 | filtersView 33 | } 34 | } 35 | } else { 36 | filtersView 37 | searchView 38 | } 39 | } 40 | } 41 | 42 | private func filtersButton() -> some View { 43 | Button(action: { 44 | withAnimation { 45 | isFiltersViewPresented.toggle() 46 | } 47 | }, 48 | label: { 49 | let imageName = currentFiltersController.filters.isEmpty ? "line.horizontal.3.decrease.circle" : "line.horizontal.3.decrease.circle.fill" 50 | Image(systemName: imageName) 51 | .font(.title) 52 | }) 53 | } 54 | 55 | init(viewModel: AlgoliaViewModel) { 56 | self.algoliaViewModel = viewModel 57 | self.currentFiltersController = algoliaViewModel.currentFiltersController 58 | } 59 | 60 | } 61 | 62 | 63 | struct MainView_Previews : PreviewProvider { 64 | 65 | static let algoliaController = AlgoliaController.test() 66 | 67 | static var previews: some View { 68 | MainView(viewModel: algoliaController.viewModel) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/View/SearchableFacetList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchableFacets.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import Foundation 9 | import InstantSearch 10 | import InstantSearchSwiftUI 11 | import SwiftUI 12 | 13 | 14 | struct SearchableFacetList: View { 15 | 16 | @ObservedObject var facetSearchQueryInputController: QueryInputObservableController 17 | @ObservedObject var facetListController: FacetListObservableController 18 | 19 | @State private var isEditingFacetSearch = false 20 | 21 | var body: some View { 22 | VStack { 23 | SearchBar(text: $facetSearchQueryInputController.query, 24 | isEditing: $isEditingFacetSearch, 25 | placeholder: "Brand name...") 26 | FacetList(facetListController) { facet, isSelected in 27 | VStack { 28 | FacetRow(facet: facet, isSelected: isSelected) 29 | Divider() 30 | } 31 | .padding(.vertical, 7) 32 | } noResults: { 33 | Text("No facets found") 34 | .frame(maxWidth: .infinity, maxHeight: .infinity) 35 | } 36 | } 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /DemoEcommerce/Shared/View/SuggestionsList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuggestionsList.swift 3 | // DemoEcommerce 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import InstantSearch 11 | import InstantSearchSwiftUI 12 | 13 | struct SuggestionsList: View { 14 | 15 | @Binding var isEditing: Bool 16 | @ObservedObject var queryInputObservable: QueryInputObservableController 17 | @ObservedObject var suggestionsObservable: HitsObservableController 18 | 19 | var body: some View { 20 | HitsList(suggestionsObservable) { (suggestion, _) in 21 | if let suggestion = suggestion { 22 | SuggestionRow(suggestion: suggestion, 23 | onSelection: { suggestion in 24 | queryInputObservable.setQuery(suggestion) 25 | isEditing = false 26 | }, 27 | onTypeAhead: { suggestion in 28 | queryInputObservable.setQuery(suggestion) 29 | }) 30 | Divider() 31 | } else { 32 | Color(.systemGreen) 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /DemoEcommerce/Tests iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DemoEcommerce/Tests iOS/Tests_iOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests_iOS.swift 3 | // Tests iOS 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class Tests_iOS: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DemoEcommerce/Tests macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DemoEcommerce/Tests macOS/Tests_macOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests_macOS.swift 3 | // Tests macOS 4 | // 5 | // Created by Vladislav Fitc on 10/04/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class Tests_macOS: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DemoEcommerce/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /DemoEcommerce/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | 26 | 27 | -------------------------------------------------------------------------------- /DemoEcommerce/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CategoriesHits 4 | // 5 | // Created by Vladislav Fitc on 19/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/README.md: -------------------------------------------------------------------------------- 1 | # Categories & Hits implementation example 2 | 3 | Search experience consisting of two results sections: 4 | - Products categories lists 5 | - Products list 6 | 7 | Demonstrates simultaneous search for hits and facet values. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // CategoriesHits 4 | // 5 | // Created by Vladislav Fitc on 19/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let window = UIWindow(windowScene: windowScene) 18 | let viewController = CategoriesHits.SearchViewController() 19 | let navigation = UINavigationController(rootViewController: viewController) 20 | window.rootViewController = navigation 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | } 24 | 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Examples/CategoriesHits/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/CategoriesHits/demo.gif -------------------------------------------------------------------------------- /Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Examples/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 30/10/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Examples/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/Examples/CategoryTableViewCell+Facet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryTableViewCell+Facet.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 13/12/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import AlgoliaSearchClient 11 | 12 | extension CategoryTableViewCell { 13 | 14 | func setup(with facet: Facet) { 15 | guard let textLabel = textLabel else { return } 16 | if let rawHighlighted = facet.highlighted { 17 | let highlightedValue = HighlightedString(string: rawHighlighted) 18 | textLabel.attributedText = NSAttributedString(highlightedString: highlightedValue, 19 | attributes: [ 20 | .font: UIFont.systemFont(ofSize: textLabel.font.pointSize, weight: .bold) 21 | ]) 22 | } else { 23 | textLabel.text = facet.value 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Examples/CategoryTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryTableViewCell.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import InstantSearch 11 | 12 | class CategoryTableViewCell: UITableViewCell { 13 | 14 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 15 | super.init(style: style, reuseIdentifier: reuseIdentifier) 16 | imageView?.image = UIImage(systemName: "square.grid.2x2") 17 | tintColor = .lightGray 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Examples/Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Demo.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 13/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Demo: Int, CaseIterable { 11 | 12 | case querySuggestions 13 | case voiceSearch 14 | case multiIndex 15 | case querySuggestionsAndCategories 16 | case querySuggestionsAndRecentSearches 17 | case querySuggestionsAndHits 18 | 19 | var title: String { 20 | switch self { 21 | case .querySuggestions: 22 | return "Query suggestions" 23 | case .voiceSearch: 24 | return "Voice search" 25 | case .multiIndex: 26 | return "Multi-index search" 27 | case .querySuggestionsAndCategories: 28 | return "Query suggestions and categories" 29 | case .querySuggestionsAndRecentSearches: 30 | return "Query suggestions and recent searches" 31 | case .querySuggestionsAndHits: 32 | return "Query suggestions and hits" 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Examples/Examples/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/Examples/Product.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Product.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Product: Codable { 11 | let name: String 12 | let description: String 13 | let brand: String? 14 | let image: URL 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QuerySuggestions 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/Examples/QuerySuggestions/QuerySuggestions/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // QuerySuggestions 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view. 15 | } 16 | 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Examples/Examples/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 30/10/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Examples/Examples/SearchSuggestionTableViewCell+QuerySuggestion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchSuggestionTableViewCell+QuerySuggestion.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 13/12/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import InstantSearchCore 11 | 12 | extension SearchSuggestionTableViewCell { 13 | 14 | func setup(with querySuggestion: QuerySuggestion) { 15 | guard let textLabel = textLabel else { return } 16 | textLabel.attributedText = querySuggestion 17 | .highlighted 18 | .flatMap(HighlightedString.init) 19 | .flatMap { NSAttributedString(highlightedString: $0, 20 | inverted: true, 21 | attributes: [.font: UIFont.boldSystemFont(ofSize: textLabel.font.pointSize)]) 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Examples/SearchSuggestionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchSuggestionTableViewCell.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class SearchSuggestionTableViewCell: UITableViewCell { 12 | 13 | var didTapTypeAheadButton: (() -> Void)? 14 | 15 | private func typeAheadButton() -> UIButton { 16 | let typeAheadButton = UIButton() 17 | typeAheadButton.setImage(UIImage(systemName: "arrow.up.left"), for: .normal) 18 | typeAheadButton.sizeToFit() 19 | typeAheadButton.addTarget(self, action: #selector(typeAheadButtonTap), for: .touchUpInside) 20 | return typeAheadButton 21 | } 22 | 23 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 24 | super.init(style: style, reuseIdentifier: reuseIdentifier) 25 | accessoryView = typeAheadButton() 26 | imageView?.image = UIImage(systemName: "magnifyingglass") 27 | tintColor = .lightGray 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | @objc func typeAheadButtonTap(_ sender: UIButton) { 35 | didTapTypeAheadButton?() 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Examples/Examples/StoreItemTableViewCell+StoreItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreItemTableViewCell+StoreItem.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 13/12/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import AlgoliaSearchClient 11 | import InstantSearchCore 12 | 13 | extension StoreItemView { 14 | 15 | func setup(with productHit: Hit) { 16 | let product = productHit.object 17 | itemImageView.sd_setImage(with: product.images.first) 18 | 19 | if let highlightedName = productHit.hightlightedString(forKey: "name") { 20 | titleLabel.attributedText = NSAttributedString(highlightedString: highlightedName, 21 | attributes: [ 22 | .foregroundColor: UIColor.tintColor]) 23 | } else { 24 | titleLabel.text = product.name 25 | } 26 | 27 | if let highlightedDescription = productHit.hightlightedString(forKey: "brand") { 28 | subtitleLabel.attributedText = NSAttributedString(highlightedString: highlightedDescription, 29 | attributes: [ 30 | .foregroundColor: UIColor.tintColor 31 | ]) 32 | } else { 33 | subtitleLabel.text = product.brand 34 | } 35 | 36 | if let price = product.price { 37 | priceLabel.text = "\(price.value) €" 38 | } 39 | 40 | } 41 | 42 | } 43 | 44 | extension StoreItemCollectionViewCell { 45 | 46 | func setup(with productHit: Hit) { 47 | storeItemView.setup(with: productHit) 48 | } 49 | 50 | } 51 | 52 | extension StoreItemTableViewCell { 53 | 54 | func setup(with productHit: Hit) { 55 | storeItemView.setup(with: productHit) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Examples/Examples/StoreItemTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreItemTableViewCell.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import Foundation 9 | import AlgoliaSearchClient 10 | import UIKit 11 | 12 | class StoreItemTableViewCell: UITableViewCell { 13 | 14 | let storeItemView: StoreItemView 15 | 16 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 17 | storeItemView = .init(frame: .zero) 18 | super.init(style: style, reuseIdentifier: reuseIdentifier) 19 | storeItemView.translatesAutoresizingMaskIntoConstraints = false 20 | contentView.addSubview(storeItemView) 21 | storeItemView.pin(to: contentView) 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Examples/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Examples 4 | // 5 | // Created by Vladislav Fitc on 30/10/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UITableViewController { 11 | 12 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 13 | return Demo.allCases.count 14 | } 15 | 16 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 17 | let cell = tableView.dequeueReusableCell(withIdentifier: "demoCell", for: indexPath) 18 | if let demo = Demo(rawValue: indexPath.row) { 19 | cell.textLabel?.text = demo.title 20 | } 21 | cell.accessoryType = .disclosureIndicator 22 | return cell 23 | } 24 | 25 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 26 | guard let demo = Demo(rawValue: indexPath.row) else { 27 | return 28 | } 29 | 30 | let viewController: UIViewController 31 | 32 | switch demo { 33 | case .querySuggestions: 34 | viewController = QuerySuggestions.SearchViewController() 35 | 36 | case .voiceSearch: 37 | viewController = VoiceSearch.SearchViewController() 38 | 39 | case .multiIndex: 40 | viewController = MultiIndex.SearchViewController() 41 | 42 | case .querySuggestionsAndCategories: 43 | viewController = QuerySuggestionsAndCategories.SearchViewController() 44 | 45 | case .querySuggestionsAndRecentSearches: 46 | viewController = QuerySuggestionsAndRecentSearches.SearchViewController() 47 | 48 | case .querySuggestionsAndHits: 49 | viewController = QuerySuggestionsAndHits.SearchViewController() 50 | } 51 | 52 | navigationController?.pushViewController(viewController, animated: true) 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Examples/MultiIndex/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MultiIndex 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/MultiIndex/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/MultiIndex/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/MultiIndex/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/MultiIndex/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/MultiIndex/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/MultiIndex/README.md: -------------------------------------------------------------------------------- 1 | # Multi-index search implementation example 2 | 3 | Search experience consisting of search in two indices: 4 | - Actors 5 | - Films 6 | 7 | Demonstrates simultaneous search in multiple indices 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/MultiIndex/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MultiIndex 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let window = UIWindow(windowScene: windowScene) 18 | let viewController = MultiIndex.SearchViewController() 19 | let navigation = UINavigationController(rootViewController: viewController) 20 | window.rootViewController = navigation 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | } 24 | 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Examples/MultiIndex/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/MultiIndex/demo.gif -------------------------------------------------------------------------------- /Examples/QuerySuggestions/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QuerySuggestions 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/README.md: -------------------------------------------------------------------------------- 1 | # Query Suggestions implementation example 2 | 3 | Search for query suggestions. 4 | Selection of a suggestions updates the search query. 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/SearchResultsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultsController.swift 3 | // QuerySuggestions 4 | // 5 | // Created by Vladislav Fitc on 05/11/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import InstantSearch 11 | 12 | extension QuerySuggestions { 13 | 14 | class SearchResultsController: UITableViewController, HitsController { 15 | 16 | var hitsSource: HitsInteractor? 17 | 18 | var didSelectSuggestion: ((String) -> Void)? 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | tableView.register(SearchSuggestionTableViewCell.self, forCellReuseIdentifier: "suggestion") 23 | } 24 | 25 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 26 | hitsSource?.numberOfHits() ?? 0 27 | } 28 | 29 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 | let cell = tableView.dequeueReusableCell(withIdentifier: "suggestion", for: indexPath) 31 | if 32 | let suggestionCell = cell as? SearchSuggestionTableViewCell, 33 | let suggestion = hitsSource?.hit(atIndex: indexPath.row) { 34 | suggestionCell.setup(with: suggestion) 35 | } 36 | return cell 37 | } 38 | 39 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 40 | hitsSource?.hit(atIndex: indexPath.row).flatMap { 41 | didSelectSuggestion?($0.query) 42 | } 43 | } 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // QuerySuggestions 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view. 15 | } 16 | 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Examples/QuerySuggestions/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/QuerySuggestions/demo.gif -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QuerySuggestionsCategories 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/README.md: -------------------------------------------------------------------------------- 1 | # Query suggestions & Categories implementation example 2 | 3 | Search experience consisting of two results sections: 4 | - Query suggestions list 5 | - Product categories list 6 | 7 | Demonstrates simultaneous search for hits (suggestions) and facet values. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsCategories/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/QuerySuggestionsCategories/demo.gif -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QuerySuggestionsHits 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/README.md: -------------------------------------------------------------------------------- 1 | # Query suggestions & Hits implementation example 2 | 3 | Search experience consisting of two results sections: 4 | - Query suggestions list 5 | - Products list 6 | 7 | Demonstrates simultaneous search in multiple indices 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // QuerySuggestionsHits 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let window = UIWindow(windowScene: windowScene) 18 | let viewController = QuerySuggestionsAndHits.SearchViewController() 19 | let navigation = UINavigationController(rootViewController: viewController) 20 | window.rootViewController = navigation 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | } 24 | 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsHits/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/QuerySuggestionsHits/demo.gif -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QuerySuggestionsRecentSearches 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/README.md: -------------------------------------------------------------------------------- 1 | # Query suggestions & Recent searches implementation example 2 | 3 | Search experience consisting of two sections: 4 | - Recent searches 5 | - Query suggestions list 6 | 7 | When a search query submitted, it's added to the recent searches list. 8 | Selection of a suggestion or a recent search input updates the search query. 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/QuerySuggestionsRecentSearches/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/QuerySuggestionsRecentSearches/demo.gif -------------------------------------------------------------------------------- /Examples/VoiceSearch/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VoiceSearch 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSSpeechRecognitionUsageDescription 6 | Voice search 7 | NSMicrophoneUsageDescription 8 | Voice search 9 | UIApplicationSceneManifest 10 | 11 | UIApplicationSupportsMultipleScenes 12 | 13 | UISceneConfigurations 14 | 15 | UIWindowSceneSessionRoleApplication 16 | 17 | 18 | UISceneConfigurationName 19 | Default Configuration 20 | UISceneDelegateClassName 21 | $(PRODUCT_MODULE_NAME).SceneDelegate 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/README.md: -------------------------------------------------------------------------------- 1 | # Voice Search implementation example 2 | 3 | Search experience with voice input button activating the [Voice Overlay](https://github.com/algolia/voice-overlay-ios) interface. 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // VoiceSearch 4 | // 5 | // Created by Vladislav Fitc on 04/11/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let window = UIWindow(windowScene: windowScene) 18 | let viewController = VoiceSearch.SearchViewController() 19 | let navigation = UINavigationController(rootViewController: viewController) 20 | window.rootViewController = navigation 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | } 24 | 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/SearchResultsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultsController.swift 3 | // VoiceSearch 4 | // 5 | // Created by Vladislav Fitc on 05/11/2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import InstantSearch 11 | 12 | extension VoiceSearch { 13 | 14 | class SearchResultsController: UITableViewController, HitsController { 15 | 16 | var hitsSource: HitsInteractor>? 17 | 18 | let productCellID = "productCellID" 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | tableView.register(ProductTableViewCell.self, forCellReuseIdentifier: productCellID) 23 | } 24 | 25 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 26 | hitsSource?.numberOfHits() ?? 0 27 | } 28 | 29 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 | let cell = tableView.dequeueReusableCell(withIdentifier: productCellID, for: indexPath) 31 | if 32 | let productTableViewCell = cell as? ProductTableViewCell, 33 | let product = hitsSource?.hit(atIndex: indexPath.row) { 34 | productTableViewCell.setup(with: product) 35 | } 36 | return cell 37 | } 38 | 39 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 40 | // Handle hit selection 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 44 | return 100 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Examples/VoiceSearch/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/Examples/VoiceSearch/demo.gif -------------------------------------------------------------------------------- /InsightsIntegration.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | var str = "Hello, playground" 4 | -------------------------------------------------------------------------------- /InsightsIntegration.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Algolia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Movies.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/Movies.gif -------------------------------------------------------------------------------- /docs/_icebnb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/_icebnb.gif -------------------------------------------------------------------------------- /docs/ecommerce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/ecommerce.png -------------------------------------------------------------------------------- /docs/facets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/facets.png -------------------------------------------------------------------------------- /docs/icebnb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/icebnb.gif -------------------------------------------------------------------------------- /docs/infinite-scrolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/infinite-scrolling.gif -------------------------------------------------------------------------------- /docs/instant-results.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/instant-results.gif -------------------------------------------------------------------------------- /docs/multi-index.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/multi-index.gif -------------------------------------------------------------------------------- /docs/query-suggestions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/query-suggestions.gif -------------------------------------------------------------------------------- /docs/single-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/single-index.png -------------------------------------------------------------------------------- /docs/sort-by.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/sort-by.gif -------------------------------------------------------------------------------- /docs/suggestion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algolia/instantsearch-ios-examples/ddaec097e6a0a1b13b5c0ad5902531c94282f63c/docs/suggestion.gif --------------------------------------------------------------------------------