Search for webmap by keyword

View on GitHub
Sample viewer app

Find webmap portal items by using a search term.

Search for Webmap by Keyword

Use case

Portals can contain many portal items and, at times, you may wish to query the portal to find what you're looking for. In this example, we search for webmap portal items using a text search.

How to use the sample

Enter search terms into the search bar. Once the search is complete, a list is populated with the resultant webmaps. Tap on a webmap to set it to the map view.

How it works

  1. Create an AGSPortal and load it.
  2. Create an AGSPortalQueryParameters object using init(forItemsOf:withSearch:). Pass .webMap as the type and use the search field text to build a search query. Note that webmaps authored prior to July 2nd, 2014, are not supported, so you can also limit the query to only return maps published after that date.
  3. Use findItems(with:completion:) to get the first set of matching items.

Relevant API

  • AGSPortal
  • AGSPortalItem
  • AGSPortalQueryParameters
  • AGSPortalQueryResultSet

Tags

keyword, query, search, webmap

Sample Code

SearchForWebmapByKeywordViewController.swiftWebMapCell.swiftWebMapViewController.swift
                                                                                                                                                                                          
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//
// Copyright 2017 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit
import ArcGIS

class SearchForWebmapByKeywordViewController: UICollectionViewController {
    private var portal: AGSPortal?
    private var resultPortalItems: [AGSPortalItem] = [] {
        didSet {
            collectionView.reloadData()
        }
    }

    private var lastQueryCancelable: AGSCancelable?

    override func viewDidLoad() {
        super.viewDidLoad()

        // register to be notified about authentication challenges
        AGSAuthenticationManager.shared().delegate = self

        // create the portal
        portal = AGSPortal(url: URL(string: "https://arcgis.com")!, loginRequired: false)

        (navigationItem.rightBarButtonItem as! SourceCodeBarButtonItem).filenames = ["SearchForWebmapByKeywordViewController", "WebMapCell", "WebMapViewController"]

        addSearchController()
    }

    private func addSearchController() {
        // create the search controller
        let searchController = UISearchController(searchResultsController: nil)
        searchController.obscuresBackgroundDuringPresentation = false
        // Setting hidesNavigationBarDuringPresentation to false causes issues on iOS 13 - 13.1.2,
        // so we hide the navigation bar during search.
        searchController.hidesNavigationBarDuringPresentation = true
        // send search query updates to the results controller
        searchController.searchResultsUpdater = self

        let searchBar = searchController.searchBar
        searchBar.autocapitalizationType = .none

        // Change the backgroundColor to differentiate from nav bar color.
        searchBar.searchTextField.backgroundColor = .tertiarySystemBackground
        // Set the color of the insertion cursor as well as the text.
        // The text is default to black color whereas the cursor is white.
        searchBar.searchTextField.tintColor = .label

        // embed the search bar under the title in the navigation bar
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = false
    }

    let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    private func startWebMapSearch(query: String) {
        // if the last search hasn't returned yet, cancel it
        lastQueryCancelable?.cancel()

        // webmaps authored prior to July 2nd, 2014 are not supported - so
        // search only from that date to the current time
        let dateRange = dateFormatter.date(from: "July 2, 2014")!...Date()
        let uploadedRange = dateRange.lowerBound.millisecondsSince1970...dateRange.upperBound.millisecondsSince1970
        let dateString = String(format: "uploaded:[%ld TO %ld]", uploadedRange.lowerBound, uploadedRange.upperBound)

        // create query parameters looking for items of type .webMap and
        // with our keyword and date string
        let queryParams = AGSPortalQueryParameters(forItemsOf: .webMap, withSearch: "\(query) AND \(dateString)")
        lastQueryCancelable = portal?.findItems(with: queryParams) { [weak self] (resultSet: AGSPortalQueryResultSet?, error: Error?) in
            if let error = error {
                if (error as NSError).code != NSUserCancelledError {
                    print(error.localizedDescription)
                }
            } else {
                // if our results are an array of portal items, set it on our web maps collection ViewController
                if let portalItems = resultSet?.results as? [AGSPortalItem] {
                    self?.resultPortalItems = portalItems
                } else {
                    self?.resultPortalItems = []
                }
            }
        }
    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let controller = segue.destination as? WebMapViewController,
            let selectedIndex = collectionView.indexPathsForSelectedItems?.first?.row {
            controller.portalItem = resultPortalItems[selectedIndex]
        }
    }

    // MARK: - UICollectionViewDataSource

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return resultPortalItems.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! WebMapCell
        let portalItem = resultPortalItems[indexPath.row]

        cell.titleLabel.text = portalItem.title
        cell.ownerLabel.text = portalItem.owner

        // imageview border
        cell.thumbnail.layer.borderColor = UIColor.darkGray.cgColor
        cell.thumbnail.layer.borderWidth = 1

        // date label
        cell.timerLabel.text = dateFormatter.string(from: portalItem.modified!)

        if let image = portalItem.thumbnail?.image {
            cell.thumbnail.image = image
        } else {
            cell.thumbnail.image = UIImage(named: "Placeholder")
            portalItem.thumbnail?.load { [weak self] (error: Error?) in
                if let error = error {
                    print("Error downloading thumbnail :: \(error.localizedDescription)")
                } else {
                    self?.collectionView.reloadItems(at: [indexPath])
                }
            }
        }

        return cell
    }
}

extension SearchForWebmapByKeywordViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        if let query = searchController.searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines),
            !query.isEmpty {
            startWebMapSearch(query: query)
        } else {
            lastQueryCancelable?.cancel()
            resultPortalItems = []
        }
    }
}

extension SearchForWebmapByKeywordViewController: AGSAuthenticationManagerDelegate {
    func authenticationManager(_ authenticationManager: AGSAuthenticationManager, didReceive challenge: AGSAuthenticationChallenge) {
        // if a challenge is received, then the portal item is not fully public and cannot be displayed

        // don't present this challenge
        challenge.cancel()

        if let mapController = navigationController?.topViewController as? WebMapViewController {
            // stop attempts at map loading
            mapController.mapView.map = nil

            // close the view controller
            navigationController?.popViewController(animated: true)
        }

        // notify the user
        presentAlert(message: "Web map access denied")
    }
}

private extension Date {
    /// The milliseconds between the date value and 00:00:00 UTC on 1 January 1970.
    var millisecondsSince1970: Int64 {
        return Int64(timeIntervalSince1970 * 1_000)
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.