Fix crash on loading App Store search results

This commit is contained in:
relikd
2020-09-08 11:52:07 +02:00
parent 6409e5eaf3
commit fb680d669b
4 changed files with 51 additions and 27 deletions

View File

@@ -40,8 +40,8 @@ extension URL {
return components.url return components.url
} }
func download(to file: URL, onSuccess: @escaping () -> Void) { @discardableResult func download(to file: URL, onSuccess: @escaping () -> Void) -> URLSessionDownloadTask {
URLSession.shared.downloadTask(with: self) { location, response, error in let task = URLSession.shared.downloadTask(with: self) { location, response, error in
if let loc = location { if let loc = location {
try? FileManager.default.removeItem(at: file) try? FileManager.default.removeItem(at: file)
do { do {
@@ -51,6 +51,8 @@ extension URL {
NSLog("[VPN.ERROR] \(error)") NSLog("[VPN.ERROR] \(error)")
} }
} }
}.resume() }
task.resume()
return task
} }
} }

View File

@@ -20,15 +20,15 @@ struct AppStoreSearch {
let developer, imageURL: String? let developer, imageURL: String?
} }
static func search(_ term: String, _ closure: @escaping ([Result]?) -> Void) { static func search(_ term: String, _ closure: @escaping ([Result]?, Error?) -> Void) {
URLSession.shared.dataTask(with: .init(url: .appStoreSearch(query: term))) { data, response, error in URLSession.shared.dataTask(with: .init(url: .appStoreSearch(query: term))) { data, response, error in
guard let data = data, error == nil, guard let data = data, error == nil,
let response = response as? HTTPURLResponse, let response = response as? HTTPURLResponse,
(200 ..< 300) ~= response.statusCode else { (200 ..< 300) ~= response.statusCode else {
closure(nil) closure(nil, error ?? URLError(.badServerResponse))
return return
} }
closure(jsonSearchToList(data)) closure(jsonSearchToList(data), nil)
}.resume() }.resume()
} }

View File

@@ -98,9 +98,7 @@ struct BundleIcon {
return img return img
} }
static func download(_ bundleId: String, urlStr: String, whenDone: @escaping () -> Void) { static func download(_ bundleId: String, url: URL, whenDone: @escaping () -> Void) -> URLSessionDownloadTask {
if let url = URL(string: urlStr) { return url.download(to: local(bundleId), onSuccess: whenDone)
url.download(to: local(bundleId), onSuccess: whenDone)
}
} }
} }

View File

@@ -12,6 +12,10 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
private var searchActive: Bool = false private var searchActive: Bool = false
var delegate: TVCAppSearchDelegate? var delegate: TVCAppSearchDelegate?
private var searchNo = 0
private var searchError: Bool = false
private var downloadQueue: [URLSessionDownloadTask] = []
@IBOutlet private var searchBar: UISearchBar! @IBOutlet private var searchBar: UISearchBar!
override func viewDidLoad() { override func viewDidLoad() {
@@ -29,6 +33,18 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
dismiss(animated: true) dismiss(animated: true)
} }
private func showManualEntryAlert() {
let alert = AskAlert(title: "App Name",
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
buttonText: "Set") {
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
self.closeThis()
}
alert.addTextField { $0.placeholder = "com.apple.notes" }
alert.presentIn(self)
}
// MARK: - Table View Data Source // MARK: - Table View Data Source
override func numberOfSections(in _: UITableView) -> Int { override func numberOfSections(in _: UITableView) -> Int {
@@ -60,7 +76,13 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
case 0: case 0:
guard dataSource.count > 0, indexPath.row < dataSource.count else { guard dataSource.count > 0, indexPath.row < dataSource.count else {
if indexPath.row == 0 { if indexPath.row == 0 {
cell.textLabel?.text = isLoading ? "Loading …" : "no results" if searchError {
cell.textLabel?.text = "Error loading results"
} else if isLoading {
cell.textLabel?.text = "Loading …"
} else {
cell.textLabel?.text = "No results"
}
cell.isUserInteractionEnabled = false cell.isUserInteractionEnabled = false
} else { } else {
cell.textLabel?.text = "Create manually …" cell.textLabel?.text = "Create manually …"
@@ -84,13 +106,16 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
preconditionFailure() preconditionFailure()
} }
let sno = searchNo
cell.imageView?.image = BundleIcon.image(bundleId) { cell.imageView?.image = BundleIcon.image(bundleId) {
guard let url = altLoadUrl else { return } guard let u = altLoadUrl, let url = URL(string: u) else { return }
BundleIcon.download(bundleId, urlStr: url) { self.downloadQueue.append(BundleIcon.download(bundleId, url: url) {
DispatchQueue.main.async { DispatchQueue.main.async {
// make sure its the same request
guard sno == self.searchNo else { return }
tableView.reloadRows(at: [indexPath], with: .automatic) tableView.reloadRows(at: [indexPath], with: .automatic)
} }
} })
} }
cell.isUserInteractionEnabled = true cell.isUserInteractionEnabled = true
cell.imageView?.layer.cornerRadius = 6.75 cell.imageView?.layer.cornerRadius = 6.75
@@ -103,14 +128,7 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
switch indexPath.section { switch indexPath.section {
case 0: case 0:
guard indexPath.row < dataSource.count else { guard indexPath.row < dataSource.count else {
let alert = AskAlert(title: "App Name", showManualEntryAlert()
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
buttonText: "Set") {
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
self.closeThis()
}
alert.addTextField { $0.placeholder = "com.apple.notes" }
alert.presentIn(self)
return return
} }
let src = dataSource[indexPath.row] let src = dataSource[indexPath.row]
@@ -130,6 +148,8 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil) NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
isLoading = true isLoading = true
tableView.reloadData() tableView.reloadData()
for x in downloadQueue { x.cancel() }
downloadQueue = []
if searchText.count > 0 { if searchText.count > 0 {
perform(#selector(performSearch), with: nil, afterDelay: 0.4) perform(#selector(performSearch), with: nil, afterDelay: 0.4)
} else { } else {
@@ -140,18 +160,22 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
/// Internal callback function for delayed text evaluation. /// Internal callback function for delayed text evaluation.
/// This way we can avoid unnecessary searches while user is typing. /// This way we can avoid unnecessary searches while user is typing.
@objc private func performSearch() { @objc private func performSearch() {
func setSource(_ newSource: [AppStoreSearch.Result], _ err: Bool) {
searchNo += 1
searchError = err
dataSource = searchActive ? newSource : []
tableView.reloadData()
}
isLoading = false isLoading = false
let term = searchBar.text?.lowercased() ?? "" let term = searchBar.text?.lowercased() ?? ""
searchActive = term.count > 0 searchActive = term.count > 0
guard searchActive else { guard searchActive else {
dataSource = [] setSource([], false)
tableView.reloadData()
return return
} }
AppStoreSearch.search(term) { AppStoreSearch.search(term) { source, error in
self.dataSource = $0 ?? []
DispatchQueue.main.async { DispatchQueue.main.async {
self.tableView.reloadData() setSource(source ?? [], error != nil)
} }
} }
} }