Archived
1
This repository has been archived on 2020-06-04. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tagtunes/TagTunes/SearchController.swift
Kim Wittenburg 0a485ff42a Stuff…
2019-02-01 22:59:01 +01:00

183 lines
6.5 KiB
Swift
Executable File

//
// SearchController.swift
// TagTunes
//
// Created by Kim Wittenburg on 06.03.16.
// Copyright © 2016 Kim Wittenburg. All rights reserved.
//
import SearchAPI
import AppKitPlus
/// A search delegate is notified when a `SearchController` selects an item.
protocol SearchDelegate: class {
/// The view controller displaying the content.
var contentViewController: ContentViewController! { get }
/// Invoked when the `searchController` selected a item. In the
/// implementation the selected `searchResult` should be added to the
/// `contentViewController`.
func searchController(searchController: SearchController, didSelectSearchResult searchResult: TagTunesItem)
}
/// Manages the user interface for search results.
public class SearchController: NSObject, PopUpSearchFieldDelegate {
/// This struct contains *magic numbers* like references to storyboard
/// identifiers.
private struct Constants {
/// The identifier used for the rows in the search results pop up.
static let AlbumTableCellViewIdentifier = "AlbumTableCellViewIdentifier"
/// The identifier used for the `No Results` row in the search results
/// pop up.
static let NoResultsTableCellViewIdentifier = "NoResultsTableCellViewIdentifier"
}
weak var delegate: SearchDelegate?
/// The search field. This should be set by the view controller containing
/// the user interface for searching.
public weak var searchField: PopUpSearchField! {
willSet {
searchField?.popUpDelegate = nil
}
didSet {
searchField?.popUpDelegate = self
}
}
/// Used for searching and loading search results
private let urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue())
/// The URL task currently loading the search results.
private var searchTask: NSURLSessionTask? {
didSet {
searching = searchTask != nil
}
}
public private(set) dynamic var searching: Bool = false
/// The search results for the current search term.
private var searchResults = [Album]() {
didSet {
searchField.updatePopUp()
}
}
/// The error that occured during searching, if any.
private var searchError: NSError?
/// Begins searching for the `searchField`'s `stringValue`.
public func beginSearch() {
let searchString = searchField.stringValue
cancelSearch()
if searchString == "" {
searchResults = []
} else {
var request = SearchAPIRequest(searchRequestWithTerm: searchString)
request.mediaType = .Music
request.entity = .Album
request.maximumNumberOfResults = UInt(Preferences.sharedPreferences.numberOfSearchResults)
if Preferences.sharedPreferences.useEnglishTags {
request.language = .English
}
request.country = Preferences.sharedPreferences.iTunesStore
searchTask = urlSession.dataTaskWithURL(request.URL, completionHandler: processSearchResults)
searchTask!.resume()
}
}
/// Cancels the current search (if there is one) and removes the search
/// results.
private func cancelSearch() {
searchTask?.cancel()
}
/// Processes the data returned from a network request into the
/// `searchResults` array.
private func processSearchResults(data: NSData?, response: NSURLResponse?, error: NSError?) {
searchTask = nil
var newResults = [Album]()
if let theData = data where error == nil {
do {
let result = try SearchAPIResult(data: theData)
newResults = result.resultEntities.flatMap{ $0 as? Album }
} catch let error as NSError {
searchError = error
}
} else if let theError = error {
searchError = theError
}
searchResults = newResults
}
public func popUpSearchFieldWillHidePopUp(searchField: PopUpSearchField) {
cancelSearch()
}
public func popUpSearchFieldShouldShowPopUp(searchField: PopUpSearchField) -> Bool {
return !searchField.stringValue.isEmpty
}
public func popUpSearchField(searchField: PopUpSearchField, didSelectPopUpEntryAtRow row: Int) {
let searchResult = searchResults[row]
let albumItem = AlbumItem(entity: searchResult)
albumItem.beginLoadingChildren()
delegate?.searchController(self, didSelectSearchResult: albumItem)
searchField.reloadPopUpRow(row)
}
private var shouldDisplayNoResults: Bool {
return searchResults.isEmpty && !searchField.stringValue.isEmpty && searchTask == nil
}
public func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if shouldDisplayNoResults {
return 1
}
return searchResults.count
}
public func tableView(tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
return 39
}
public func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
if shouldDisplayNoResults {
var view = tableView.makeViewWithIdentifier(Constants.NoResultsTableCellViewIdentifier, owner: nil) as? CenteredTableCellView
if view == nil {
view = CenteredTableCellView()
view?.identifier = Constants.NoResultsTableCellViewIdentifier
}
view?.setupForNoResults()
return view
} else {
var view = tableView.makeViewWithIdentifier(Constants.AlbumTableCellViewIdentifier, owner: nil) as? AlbumTableCellView
if view == nil {
view = AlbumTableCellView()
view?.identifier = Constants.AlbumTableCellViewIdentifier
}
let searchResult = searchResults[row]
let height = self.tableView(tableView, heightOfRow: row)
let albumEnabled = delegate?.contentViewController.itemForEntity(searchResult) == nil
view?.setupForAlbum(searchResult, enabled: albumEnabled, height: height)
return view
}
}
public func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
if shouldDisplayNoResults {
return false
} else {
return delegate?.contentViewController?.itemForEntity(searchResults[row]) == nil
}
}
}