From b99c8cec21c7c8b97dc9a2f8d0b65d2013c11ecb Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 2 Sep 2019 19:23:11 -0700 Subject: [PATCH] Seperate Data source for Search Results --- Clocker/Clocker.xcodeproj/project.pbxproj | 4 + .../General/PreferencesViewController.swift | 218 +++--------------- .../General/SearchDataSource.swift | 185 +++++++++++++++ 3 files changed, 219 insertions(+), 188 deletions(-) create mode 100644 Clocker/Preferences/General/SearchDataSource.swift diff --git a/Clocker/Clocker.xcodeproj/project.pbxproj b/Clocker/Clocker.xcodeproj/project.pbxproj index eb47daa..eb08e5f 100755 --- a/Clocker/Clocker.xcodeproj/project.pbxproj +++ b/Clocker/Clocker.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ 9ACB31401EDA994200F3E1D3 /* ShortcutRecorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ACB313F1EDA994200F3E1D3 /* ShortcutRecorder.framework */; }; 9ACE03EF1CB0ADE00039FC01 /* Firebase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ACE03EE1CB0ADE00039FC01 /* Firebase.framework */; }; 9ACF469D1DCBD45200C49B51 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ACF469C1DCBD45200C49B51 /* Quartz.framework */; }; + 9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */; }; C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20839C921515C1E00C86589 /* ClockerUnitTests.swift */; }; C213713420B4FD920024D5A4 /* FloatingWindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C213713320B4FD920024D5A4 /* FloatingWindowTests.swift */; }; C22F3D802107778A0001D5E1 /* ShortcutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22F3D7F2107778A0001D5E1 /* ShortcutTests.swift */; }; @@ -346,6 +347,7 @@ 9ACB313F1EDA994200F3E1D3 /* ShortcutRecorder.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ShortcutRecorder.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Clocker-ewkrwqfbimlgoicxkolbqepjsbcy/Build/Products/Release/ShortcutRecorder.framework"; sourceTree = ""; }; 9ACE03EE1CB0ADE00039FC01 /* Firebase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Firebase.framework; path = Frameworks/Firebase.framework; sourceTree = ""; }; 9ACF469C1DCBD45200C49B51 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; + 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDataSource.swift; sourceTree = ""; }; 9AFCC7FC1FD668FF00509B9C /* ClockerHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClockerHelper.entitlements; sourceTree = ""; }; C20839C721515C1E00C86589 /* ClockerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClockerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C20839C921515C1E00C86589 /* ClockerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockerUnitTests.swift; sourceTree = ""; }; @@ -673,6 +675,7 @@ 9AB6F1682259D26400A44663 /* Models */, 9AB6F1602259D1B000A44663 /* PreferencesDataSource.swift */, 9AB6F15F2259D1B000A44663 /* PreferencesViewController.swift */, + 9ACF618C231DABAE00F5E51E /* SearchDataSource.swift */, ); path = General; sourceTree = ""; @@ -1182,6 +1185,7 @@ 35C36F1A225961DA002FA5C6 /* Date+Manipulations.swift in Sources */, 35C36F572259DD8A002FA5C6 /* TimezoneDataSource.swift in Sources */, 35C36F462259D892002FA5C6 /* DataStore.swift in Sources */, + 9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */, C2CCCD8220619C4C00F2DFC2 /* LocationController.swift in Sources */, 35C36F4B2259D971002FA5C6 /* UnderlinedButton.swift in Sources */, 9AB6F1562259CF3900A44663 /* CalendarViewController.swift in Sources */, diff --git a/Clocker/Preferences/General/PreferencesViewController.swift b/Clocker/Preferences/General/PreferencesViewController.swift index 7baa4a5..80fc597 100644 --- a/Clocker/Preferences/General/PreferencesViewController.swift +++ b/Clocker/Preferences/General/PreferencesViewController.swift @@ -12,19 +12,6 @@ struct PreferencesConstants { static let hotKeyPathIdentifier = "values.globalPing" } -enum RowType { - case timezoneHeader - case cityHeader - case city - case timezone -} - -struct TimezoneMetadata { - let timezone: NSTimeZone - let tags: Set - let formattedName: String -} - class PreferencesViewController: ParentViewController { private var isActivityInProgress = false { didSet { @@ -40,11 +27,7 @@ class PreferencesViewController: ParentViewController { return DataStore.shared().timezones() } - private var timezoneMetadataDictionary: [String: [String]] = [:] private lazy var startupManager = StartupManager() - private var filteredArray: [Any] = [] - private var timezoneArray: [TimezoneMetadata] = [] - private var timezoneFilteredArray: [TimezoneMetadata] = [] private var dataTask: URLSessionDataTask? = .none private lazy var notimezoneView: NoTimezoneView? = { @@ -87,7 +70,9 @@ class PreferencesViewController: ParentViewController { @IBOutlet var sortToggle: NSButton! private var themeDidChangeNotification: NSObjectProtocol? private var selectionsDataSource: PreferencesDataSource! - private var finalArray: [RowType] = [] + + // Search Results Data Source Handler + private var searchResultsDataSource: SearchDataSource! override func viewDidLoad() { super.viewDidLoad() @@ -110,38 +95,14 @@ class PreferencesViewController: ParentViewController { } searchField.placeholderString = "Enter city, state, country or timezone name" - setupTimezoneDatasource() selectionsDataSource = PreferencesDataSource(callbackDelegate: self) timezoneTableView.dataSource = selectionsDataSource timezoneTableView.delegate = selectionsDataSource - reloadSearchResults() - } - - private func calculateArray() { - finalArray = [] - - func addTimezonesIfNeeded(_ data: [TimezoneMetadata]) { - if !data.isEmpty { - finalArray.append(.timezoneHeader) - } - data.forEach { _ in - finalArray.append(.timezone) - } - } - - if searchField.stringValue.isEmpty { - addTimezonesIfNeeded(timezoneArray) - } else { - if !filteredArray.isEmpty { - finalArray.append(.cityHeader) - } - filteredArray.forEach { _ in - finalArray.append(.city) - } - addTimezonesIfNeeded(timezoneFilteredArray) - } + searchResultsDataSource = SearchDataSource(with: searchField) + availableTimezoneTableView.dataSource = searchResultsDataSource + availableTimezoneTableView.delegate = searchResultsDataSource } deinit { @@ -353,75 +314,6 @@ class PreferencesViewController: ParentViewController { } extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate { - func numberOfRows(in _: NSTableView) -> Int { - return numberOfSearchResults() - } - - func tableView(_: NSTableView, isGroupRow row: Int) -> Bool { - let currentRowType = finalArray[row] - return - currentRowType == .timezoneHeader || - currentRowType == .cityHeader - } - - func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { - let currentRowType = finalArray[row] - return !(currentRowType == .timezoneHeader || currentRowType == .cityHeader) - } - - func tableView(_: NSTableView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet { - return proposedSelectionIndexes - } - - func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { - let currentRowType = finalArray[row] - - switch currentRowType { - case .timezoneHeader, .cityHeader: - return headerCell(tableView, currentRowType) - case .timezone: - return timezoneCell(tableView, currentRowType, row) - case .city: - return cityCell(tableView, currentRowType, row) - } - } - - private func timezoneCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { - if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { - let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray - guard !datasource.isEmpty else { - return nil - } - let index = searchField.stringValue.isEmpty ? row - 1 : row - message.sourceName.stringValue = datasource[index % datasource.count].formattedName - return message - } - return nil - } - - private func cityCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { - if let cityCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { - guard let timezoneData = filteredArray[row % filteredArray.count] as? TimezoneData else { - assertionFailure() - return nil - } - - cityCell.sourceName.stringValue = timezoneData.formattedAddress ?? "Error" - return cityCell - } - - return nil - } - - private func headerCell(_ tableView: NSTableView, _ headerType: RowType) -> NSView? { - if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "headerCell"), owner: self) as? HeaderTableViewCell { - message.headerField.stringValue = headerType == .timezoneHeader ? "Timezones" : "Places" - return message - } - - return nil - } - private func _markAsFavorite(_ dataObject: TimezoneData) { guard let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] else { return @@ -555,6 +447,8 @@ extension PreferencesViewController { self.placeholderLabel.placeholderString = "Searching for \(searchString)" + print(self.placeholderLabel.placeholderString ?? "") + self.dataTask = NetworkManager.task(with: self.generateSearchURL(), completionHandler: { [weak self] response, error in @@ -563,7 +457,7 @@ extension PreferencesViewController { OperationQueue.main.addOperation { if let errorPresent = error { self.findLocalSearchResultsForTimezones() - if self.timezoneFilteredArray.isEmpty { + if self.searchResultsDataSource.timezoneFilteredArray.isEmpty { self.presentError(errorPresent.localizedDescription) return } @@ -596,10 +490,10 @@ extension PreferencesViewController { } private func findLocalSearchResultsForTimezones() { - timezoneFilteredArray = [] + searchResultsDataSource.timezoneFilteredArray = [] let lowercasedSearchString = searchField.stringValue.lowercased() - timezoneFilteredArray = timezoneArray.filter { (timezoneMetadata) -> Bool in + searchResultsDataSource.timezoneFilteredArray = searchResultsDataSource.timezoneArray.filter { (timezoneMetadata) -> Bool in let tags = timezoneMetadata.tags for tag in tags where tag.contains(lowercasedSearchString) { return true @@ -607,7 +501,7 @@ extension PreferencesViewController { return false } - print(timezoneFilteredArray) + print(searchResultsDataSource.timezoneFilteredArray) } private func generateSearchURL() -> String { @@ -632,6 +526,7 @@ extension PreferencesViewController { } private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) { + var finalResults: [TimezoneData] = [] results.forEach { let location = $0.geometry.location let latitude = location.lat @@ -647,8 +542,9 @@ extension PreferencesViewController { CLPlaceIdentifier: $0.placeId, ] as [String: Any] - self.filteredArray.append(TimezoneData(with: totalPackage)) + finalResults.append(TimezoneData(with: totalPackage)) } + searchResultsDataSource.setFilteredArrayValue(finalResults) } private func prepareUIForPresentingResults() { @@ -659,7 +555,7 @@ extension PreferencesViewController { private func reloadSearchResults() { print("Reloading Search Results") - calculateArray() + searchResultsDataSource.calculateArray() availableTimezoneTableView.reloadData() } @@ -701,11 +597,6 @@ extension PreferencesViewController { placeholderLabel.isHidden = false } -// if NetworkManager.isConnected() == false || ProcessInfo.processInfo.arguments.contains("mockTimezoneDown") { -// resetStateAndShowDisconnectedMessage() -// return -// } - searchField.placeholderString = "Fetching data might take some time!" placeholderLabel.placeholderString = "Retrieving timezone data" availableTimezoneTableView.isHidden = true @@ -745,7 +636,7 @@ extension PreferencesViewController { } private func installTimezone(_ timezone: Timezone) { - guard let dataObject = self.filteredArray[self.availableTimezoneTableView.selectedRow % filteredArray.count] as? TimezoneData else { + guard let dataObject = self.searchResultsDataSource.filteredArray[self.availableTimezoneTableView.selectedRow % searchResultsDataSource.filteredArray.count] as? TimezoneData else { assertionFailure("Data was unexpectedly nil") return } @@ -782,7 +673,7 @@ extension PreferencesViewController { private func showMessage() { placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError isActivityInProgress = false - filteredArray = [] + searchResultsDataSource.cleanupFilterArray() reloadSearchResults() } @@ -807,7 +698,7 @@ extension PreferencesViewController { } private func updateViewState() { - filteredArray = [] + searchResultsDataSource.cleanupFilterArray() reloadSearchResults() refreshTimezoneTableView() refreshMainTable() @@ -816,47 +707,10 @@ extension PreferencesViewController { searchField.placeholderString = "Enter a city, state or country name" availableTimezoneTableView.isHidden = false isActivityInProgress = false - - timezoneMetadataDictionary = - ["IST": ["india", "indian", "kolkata", "calcutta", "mumbai", "delhi", "hyderabad", "noida"], - "PST": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"], - "UTC": ["utc", "universal"], - "EST": ["florida", "new york"]] - } - - private func setupTimezoneDatasource() { - timezoneArray = [] - - let anywhereOnEarth = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT-1200")!, - tags: ["aoe", "anywhere on earth"], - formattedName: "Anywhere on Earth") - timezoneArray.append(anywhereOnEarth) - - for (abbreviation, timezone) in TimeZone.abbreviationDictionary { - var tags: Set = [abbreviation.lowercased(), timezone.lowercased()] - var extraTags: [String] = [] - if let tagsPresent = timezoneMetadataDictionary[abbreviation] { - extraTags = tagsPresent - } - - extraTags.forEach { tag in - tags.insert(tag) - } - - let timezoneIdentifier = NSTimeZone(name: timezone)! - let formattedName = timezone + " (\(abbreviation))" - let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier, - tags: tags, - formattedName: formattedName) - timezoneArray.append(timezoneMetadata) - } - - print(TimeZone.knownTimeZoneIdentifiers.count) - print(timezoneArray.count) } @IBAction func addTimeZone(_: NSButton) { - filteredArray = [] + searchResultsDataSource.cleanupFilterArray() view.window?.beginSheet(timezonePanel, completionHandler: nil) } @@ -899,7 +753,7 @@ extension PreferencesViewController { } private func addTimezoneIfSearchStringIsEmpty() { - let currentRowType = finalArray[availableTimezoneTableView.selectedRow] + let currentRowType = searchResultsDataSource.placeForRow(availableTimezoneTableView.selectedRow) switch currentRowType { case .timezoneHeader, .cityHeader: @@ -913,7 +767,7 @@ extension PreferencesViewController { } private func addTimezoneIfSearchStringIsNotEmpty() { - let currentRowType = finalArray[availableTimezoneTableView.selectedRow] + let currentRowType = searchResultsDataSource.placeForRow(availableTimezoneTableView.selectedRow) switch currentRowType { case .timezoneHeader, .cityHeader: @@ -927,7 +781,7 @@ extension PreferencesViewController { } private func cleanupAfterInstallingCity() { - guard let dataObject = filteredArray[availableTimezoneTableView.selectedRow % filteredArray.count] as? TimezoneData else { + guard let dataObject = searchResultsDataSource.filteredArray[availableTimezoneTableView.selectedRow % searchResultsDataSource.filteredArray.count] as? TimezoneData else { assertionFailure("Data was unexpectedly nil") return } @@ -949,14 +803,14 @@ extension PreferencesViewController { data.setLabel(CLEmptyString) if searchField.stringValue.isEmpty == false { - let currentSelection = timezoneFilteredArray[availableTimezoneTableView.selectedRow % timezoneFilteredArray.count] + let currentSelection = searchResultsDataSource.timezoneFilteredArray[availableTimezoneTableView.selectedRow % searchResultsDataSource.timezoneFilteredArray.count] let metaInfo = metadata(for: currentSelection) data.timezoneID = metaInfo.0 data.formattedAddress = metaInfo.1.formattedName } else { - let currentSelection = timezoneArray[availableTimezoneTableView.selectedRow - 1] + let currentSelection = searchResultsDataSource.timezoneArray[availableTimezoneTableView.selectedRow - 1] let metaInfo = metadata(for: currentSelection) data.timezoneID = metaInfo.0 @@ -968,8 +822,8 @@ extension PreferencesViewController { let operationObject = TimezoneDataOperations(with: data) operationObject.saveObject() - filteredArray = [] - timezoneFilteredArray = [] + searchResultsDataSource.cleanupFilterArray() + searchResultsDataSource.timezoneFilteredArray = [] placeholderLabel.placeholderString = CLEmptyString searchField.stringValue = CLEmptyString @@ -994,8 +848,8 @@ extension PreferencesViewController { } @IBAction func closePanel(_: NSButton) { - filteredArray = [] - timezoneFilteredArray = [] + searchResultsDataSource.cleanupFilterArray() + searchResultsDataSource.timezoneFilteredArray = [] searchField.stringValue = CLEmptyString placeholderLabel.placeholderString = CLEmptyString searchField.placeholderString = "Enter a city, state or country name" @@ -1096,7 +950,7 @@ extension PreferencesViewController { @IBAction func filterArray(_: Any?) { messageLabel.stringValue = CLEmptyString - filteredArray = [] + searchResultsDataSource.cleanupFilterArray() if searchField.stringValue.count > 50 { isActivityInProgress = false @@ -1220,10 +1074,6 @@ extension PreferencesViewController: SRRecorderControlDelegate {} // Helpers extension PreferencesViewController { - private func numberOfSearchResults() -> Int { - return finalArray.count - } - private func insert(timezone: TimezoneData, at index: Int) { let encodedObject = NSKeyedArchiver.archivedData(withRootObject: timezone) var newDefaults = selectedTimeZones @@ -1232,14 +1082,6 @@ extension PreferencesViewController { } } -class SearchResultTableViewCell: NSTableCellView { - @IBOutlet var sourceName: NSTextField! -} - -class HeaderTableViewCell: NSTableCellView { - @IBOutlet var headerField: NSTextField! -} - extension PreferencesViewController: PreferenceSelectionUpdates { func markAsFavorite(_ dataObject: TimezoneData) { _markAsFavorite(dataObject) diff --git a/Clocker/Preferences/General/SearchDataSource.swift b/Clocker/Preferences/General/SearchDataSource.swift new file mode 100644 index 0000000..efb53b5 --- /dev/null +++ b/Clocker/Preferences/General/SearchDataSource.swift @@ -0,0 +1,185 @@ +// Copyright © 2015 Abhishek Banthia + +import Cocoa + +enum RowType { + case timezoneHeader + case cityHeader + case city + case timezone +} + +struct TimezoneMetadata { + let timezone: NSTimeZone + let tags: Set + let formattedName: String +} + +class SearchDataSource: NSObject { + private var searchField: NSSearchField! + private var finalArray: [RowType] = [] + private var dataTask: URLSessionDataTask? = .none + private var timezoneMetadataDictionary: [String: [String]] = + ["IST": ["india", "indian", "kolkata", "calcutta", "mumbai", "delhi", "hyderabad", "noida"], + "PST": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"], + "UTC": ["utc", "universal"], + "EST": ["florida", "new york"]] + + var filteredArray: [Any] = [] + var timezoneArray: [TimezoneMetadata] = [] + var timezoneFilteredArray: [TimezoneMetadata] = [] + + init(with searchField: NSSearchField) { + super.init() + self.searchField = searchField + setupTimezoneDatasource() + calculateArray() + } + + func cleanupFilterArray() { + filteredArray = [] + } + + func setFilteredArrayValue(_ newArray: [Any]) { + filteredArray = newArray + } + + func placeForRow(_ row: Int) -> RowType { + return finalArray[row] + } + + private func setupTimezoneDatasource() { + timezoneArray = [] + + let anywhereOnEarth = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT-1200")!, + tags: ["aoe", "anywhere on earth"], + formattedName: "Anywhere on Earth") + timezoneArray.append(anywhereOnEarth) + + for (abbreviation, timezone) in TimeZone.abbreviationDictionary { + var tags: Set = [abbreviation.lowercased(), timezone.lowercased()] + var extraTags: [String] = [] + if let tagsPresent = timezoneMetadataDictionary[abbreviation] { + extraTags = tagsPresent + } + + extraTags.forEach { tag in + tags.insert(tag) + } + + let timezoneIdentifier = NSTimeZone(name: timezone)! + let formattedName = timezone + " (\(abbreviation))" + let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier, + tags: tags, + formattedName: formattedName) + timezoneArray.append(timezoneMetadata) + } + + print(TimeZone.knownTimeZoneIdentifiers.count) + print(timezoneArray.count) + } + + func calculateArray() { + finalArray = [] + + func addTimezonesIfNeeded(_ data: [TimezoneMetadata]) { + if !data.isEmpty { + finalArray.append(.timezoneHeader) + } + data.forEach { _ in + finalArray.append(.timezone) + } + } + + if searchField.stringValue.isEmpty { + addTimezonesIfNeeded(timezoneArray) + } else { + if !filteredArray.isEmpty { + finalArray.append(.cityHeader) + } + filteredArray.forEach { _ in + finalArray.append(.city) + } + addTimezonesIfNeeded(timezoneFilteredArray) + } + } +} + +extension SearchDataSource: NSTableViewDataSource { + func numberOfRows(in _: NSTableView) -> Int { + return finalArray.count + } + + func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { + let currentRowType = finalArray[row] + + switch currentRowType { + case .timezoneHeader, .cityHeader: + return headerCell(tableView, currentRowType) + case .timezone: + return timezoneCell(tableView, currentRowType, row) + case .city: + return cityCell(tableView, currentRowType, row) + } + } +} + +extension SearchDataSource: NSTableViewDelegate { + func tableView(_: NSTableView, isGroupRow row: Int) -> Bool { + let currentRowType = finalArray[row] + return + currentRowType == .timezoneHeader || + currentRowType == .cityHeader + } + + func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { + let currentRowType = finalArray[row] + return !(currentRowType == .timezoneHeader || currentRowType == .cityHeader) + } +} + +extension SearchDataSource { + private func timezoneCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { + if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { + let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray + guard !datasource.isEmpty else { + return nil + } + let index = searchField.stringValue.isEmpty ? row - 1 : row + message.sourceName.stringValue = datasource[index % datasource.count].formattedName + return message + } + return nil + } + + private func cityCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? { + if let cityCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell { + guard let timezoneData = filteredArray[row % filteredArray.count] as? TimezoneData else { + assertionFailure() + return nil + } + + cityCell.sourceName.stringValue = timezoneData.formattedAddress ?? "Error" + return cityCell + } + + return nil + } + + private func headerCell(_ tableView: NSTableView, _ headerType: RowType) -> NSView? { + if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "headerCell"), owner: self) as? HeaderTableViewCell { + message.headerField.stringValue = headerType == .timezoneHeader ? "Timezones" : "Places" + return message + } + + return nil + } +} + +class SearchResultTableViewCell: NSTableCellView { + @IBOutlet var sourceName: NSTextField! +} + +class HeaderTableViewCell: NSTableCellView { + @IBOutlet var headerField: NSTextField! +}