Fix crash on loading App Store search results
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user