├── .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
--------------------------------------------------------------------------------