mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-22 11:03:21 -04:00
New Search rank system + New recent search result bar
This commit is contained in:
parent
a693e4c601
commit
1e75216411
10
BoostFactorTune.R
Normal file
10
BoostFactorTune.R
Normal file
@ -0,0 +1,10 @@
|
||||
boost_factor <- function(base,m,n, x) {
|
||||
return(log(m*x+n, base=base))
|
||||
}
|
||||
|
||||
base=exp(1)
|
||||
|
||||
|
||||
n=base^0.1
|
||||
m=(base^1-base^0.1)/0.5
|
||||
boost_factor(base, m, n, 0.01)
|
@ -1,148 +0,0 @@
|
||||
//
|
||||
// LangLocalCVC.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by Chris Li on 6/19/16.
|
||||
// Copyright © 2016 Chris. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class LangLocalCVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NSFetchedResultsControllerDelegate {
|
||||
|
||||
@IBOutlet weak var collectionView: UICollectionView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.alwaysBounceHorizontal = true
|
||||
}
|
||||
|
||||
// MARK: - CollectionView Data Source
|
||||
|
||||
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
|
||||
return fetchedResultController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return fetchedResultController.sections?[section].numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
|
||||
configureCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureCell(cell: UICollectionViewCell, atIndexPath indexPath: NSIndexPath) {
|
||||
guard let language = fetchedResultController.objectAtIndexPath(indexPath) as? Language,
|
||||
let cell = cell as? LocalLangCell else {return}
|
||||
cell.label.text = language.name
|
||||
}
|
||||
|
||||
// MARK: - CollectionView Delegate FlowLayout
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
|
||||
let height: CGFloat = 30
|
||||
guard let language = fetchedResultController.objectAtIndexPath(indexPath) as? Language,
|
||||
let name = language.name else {return CGSizeMake(30, height)}
|
||||
let font = UIFont.systemFontOfSize(17.0, weight: UIFontWeightRegular)
|
||||
let size = name.boundingRectWithSize(CGSizeMake(200, height),
|
||||
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
|
||||
attributes: [NSFontAttributeName: font], context: nil)
|
||||
return CGSizeMake(size.width + 30, height)
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
|
||||
let numberOfItems = collectionView.numberOfItemsInSection(section)
|
||||
|
||||
var width: CGFloat = 0
|
||||
for item in 0..<numberOfItems {
|
||||
let size = self.collectionView(collectionView, layout: collectionViewLayout, sizeForItemAtIndexPath: NSIndexPath(forItem: item, inSection: section))
|
||||
width += size.width
|
||||
}
|
||||
width += 10.0 * CGFloat(numberOfItems - 1)
|
||||
|
||||
let hInset = max((collectionView.frame.width - width) / 2, 0)
|
||||
return UIEdgeInsetsMake(0, hInset, 0, hInset)
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
// MARK: - Fetched Result Controller
|
||||
|
||||
var blockOperation = NSBlockOperation()
|
||||
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
|
||||
lazy var fetchedResultController: NSFetchedResultsController = {
|
||||
let fetchRequest = NSFetchRequest(entityName: "Language")
|
||||
let descriptor = NSSortDescriptor(key: "name", ascending: true)
|
||||
let predicate = NSPredicate(format: "books.isLocal CONTAINS true")
|
||||
fetchRequest.sortDescriptors = [descriptor]
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
fetchRequest.fetchLimit = 5
|
||||
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: "LangLocalFRC")
|
||||
fetchedResultsController.delegate = self
|
||||
fetchedResultsController.performFetch(deleteCache: false)
|
||||
return fetchedResultsController
|
||||
}()
|
||||
|
||||
private var batchUpdateOperation = [NSBlockOperation]()
|
||||
|
||||
private func addUpdateBlock(processingBlock:(Void)->Void) {
|
||||
batchUpdateOperation.append(NSBlockOperation(block: processingBlock))
|
||||
}
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
switch type {
|
||||
case .Insert:
|
||||
guard let newIndexPath = newIndexPath else {return}
|
||||
addUpdateBlock {self.collectionView.insertItemsAtIndexPaths([newIndexPath])}
|
||||
case .Update:
|
||||
guard let indexPath = indexPath else {return}
|
||||
addUpdateBlock {self.collectionView.reloadItemsAtIndexPaths([indexPath])}
|
||||
case .Move:
|
||||
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {return}
|
||||
addUpdateBlock {self.collectionView.moveItemAtIndexPath(indexPath, toIndexPath: newIndexPath)}
|
||||
case .Delete:
|
||||
guard let indexPath = indexPath else {return}
|
||||
addUpdateBlock {self.collectionView.deleteItemsAtIndexPaths([indexPath])}
|
||||
}
|
||||
}
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
switch type {
|
||||
case .Insert:
|
||||
addUpdateBlock {self.collectionView.insertSections(NSIndexSet(index: sectionIndex))}
|
||||
case .Update:
|
||||
addUpdateBlock {self.collectionView.reloadSections(NSIndexSet(index: sectionIndex))}
|
||||
case .Delete:
|
||||
addUpdateBlock {self.collectionView.deleteSections(NSIndexSet(index: sectionIndex))}
|
||||
case .Move:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
collectionView.performBatchUpdates({ () -> Void in
|
||||
for operation in self.batchUpdateOperation {
|
||||
operation.start()
|
||||
}
|
||||
}, completion: { (finished) -> Void in
|
||||
self.batchUpdateOperation.removeAll(keepCapacity: false)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
for operation in batchUpdateOperation {
|
||||
operation.cancel()
|
||||
}
|
||||
batchUpdateOperation.removeAll()
|
||||
}
|
||||
|
||||
|
||||
}
|
98
Kiwix-iOS/Controller/Search/RecentSearchCVC.swift
Normal file
98
Kiwix-iOS/Controller/Search/RecentSearchCVC.swift
Normal file
@ -0,0 +1,98 @@
|
||||
//
|
||||
// RecentSearchCVC.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by Chris Li on 6/19/16.
|
||||
// Copyright © 2016 Chris. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class RecentSearchCVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NSFetchedResultsControllerDelegate {
|
||||
|
||||
@IBOutlet weak var collectionView: UICollectionView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.alwaysBounceHorizontal = true
|
||||
collectionView.showsHorizontalScrollIndicator = false
|
||||
}
|
||||
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
collectionView.reloadData()
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CollectionView Data Source
|
||||
|
||||
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return Preference.recentSearchTerms.count
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
|
||||
configureCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureCell(cell: UICollectionViewCell, atIndexPath indexPath: NSIndexPath) {
|
||||
guard let cell = cell as? LocalLangCell else {return}
|
||||
cell.label.text = Preference.recentSearchTerms[indexPath.item]
|
||||
}
|
||||
|
||||
// MARK: - CollectionView Delegate
|
||||
|
||||
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
|
||||
guard let mainVC = parentViewController?.parentViewController?.parentViewController as? MainVC,
|
||||
let searchVC = parentViewController?.parentViewController as? SearchVC,
|
||||
let cell = collectionView.cellForItemAtIndexPath(indexPath) as? LocalLangCell,
|
||||
let text = cell.label.text else {return}
|
||||
mainVC.searchBar.text = text
|
||||
searchVC.searchText = text
|
||||
collectionView.deselectItemAtIndexPath(indexPath, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - CollectionView Delegate FlowLayout
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
|
||||
let height: CGFloat = 30
|
||||
let text = Preference.recentSearchTerms[indexPath.item]
|
||||
let font = UIFont.systemFontOfSize(17.0, weight: UIFontWeightRegular)
|
||||
let size = text.boundingRectWithSize(CGSizeMake(200, height),
|
||||
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
|
||||
attributes: [NSFontAttributeName: font], context: nil)
|
||||
return CGSizeMake(size.width + 30, height)
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
|
||||
let numberOfItems = collectionView.numberOfItemsInSection(section)
|
||||
|
||||
var width: CGFloat = 0
|
||||
for item in 0..<numberOfItems {
|
||||
let size = self.collectionView(collectionView, layout: collectionViewLayout, sizeForItemAtIndexPath: NSIndexPath(forItem: item, inSection: section))
|
||||
width += size.width
|
||||
}
|
||||
width += 10.0 * CGFloat(numberOfItems - 1)
|
||||
|
||||
let hInset = max((collectionView.frame.width - width) / 2, 10)
|
||||
return UIEdgeInsetsMake(0, hInset, 0, hInset)
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
|
||||
return 10
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
//
|
||||
// SearchHistoryTBVC.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by Chris Li on 4/15/16.
|
||||
// Copyright © 2016 Chris. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class SearchHistoryTBVC: UITableViewController, NSFetchedResultsControllerDelegate {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
// MARK: - Fetched Results Controller
|
||||
|
||||
let managedObjectContext = UIApplication.appDelegate.managedObjectContext
|
||||
lazy var fetchedResultController: NSFetchedResultsController = {
|
||||
let fetchRequest = NSFetchRequest(entityName: "Article")
|
||||
let lastReadDescriptor = NSSortDescriptor(key: "lastReadDate", ascending: false)
|
||||
fetchRequest.sortDescriptors = [lastReadDescriptor]
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: "HistoryFRC")
|
||||
fetchedResultsController.delegate = self
|
||||
fetchedResultsController.performFetch(deleteCache: false)
|
||||
return fetchedResultsController
|
||||
}()
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return fetchedResultController.sections?[section].numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
let cellIdentifier = "Cell"
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
|
||||
self.configureCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
|
||||
guard let article = fetchedResultController.objectAtIndexPath(indexPath) as? Article else {return}
|
||||
cell.textLabel?.text = article.title
|
||||
}
|
||||
|
||||
// MARK: - Table view delegates
|
||||
|
||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
guard let mainVC = parentViewController?.parentViewController?.parentViewController as? MainVC,
|
||||
let article = fetchedResultController.objectAtIndexPath(indexPath) as? Article else {return}
|
||||
mainVC.hideSearch()
|
||||
mainVC.load(article.url)
|
||||
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Fetched Result Controller Delegate
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
switch type {
|
||||
case .Insert:
|
||||
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
|
||||
case .Delete:
|
||||
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
switch type {
|
||||
case .Insert:
|
||||
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
|
||||
case .Delete:
|
||||
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
|
||||
case .Update:
|
||||
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
|
||||
case .Move:
|
||||
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
|
||||
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
|
||||
}
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ class SearchResultTBVC: UIViewController, UITableViewDataSource, UITableViewDele
|
||||
|
||||
func configureArticleCell(cell: ArticleCell, result: SearchResult) {
|
||||
guard let book = Book.fetch(result.bookID, context: UIApplication.appDelegate.managedObjectContext) else {return}
|
||||
cell.titleLabel.text = result.title
|
||||
cell.titleLabel.text = result.title + "(\(result.distance), \(result.percent ?? -1), \(result.score))"
|
||||
cell.hasPicIndicator.backgroundColor = book.hasPic ? UIColor.havePicTintColor : UIColor.lightGrayColor()
|
||||
cell.favIcon.image = book.favIcon != nil ? UIImage(data: book.favIcon!) : nil
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import CoreData
|
||||
class SearchScopeSelectVC: UIViewController, UITableViewDelegate, UITableViewDataSource, TableCellDelegate, NSFetchedResultsControllerDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var toolBar: UIToolbar!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@ -20,10 +21,9 @@ class SearchScopeSelectVC: UIViewController, UITableViewDelegate, UITableViewDat
|
||||
tableView.tableHeaderView = UIView()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
|
||||
let topInset: CGFloat = traitCollection.verticalSizeClass == .Regular ? 64.0 : 44.0
|
||||
tableView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0)
|
||||
tableView.scrollIndicatorInsets = UIEdgeInsetsMake(topInset, 0, 0, 0)
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Fetched Results Controller
|
||||
@ -52,32 +52,18 @@ class SearchScopeSelectVC: UIViewController, UITableViewDelegate, UITableViewDat
|
||||
// MARK: - Table view data source
|
||||
|
||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
return (fetchedResultController.sections?.count ?? 0) + 1
|
||||
return fetchedResultController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if section == tableView.numberOfSections - 1 {
|
||||
return 5
|
||||
} else {
|
||||
guard let sectionInfo = fetchedResultController.sections?[section] else {return 0}
|
||||
return sectionInfo.numberOfObjects
|
||||
}
|
||||
guard let sectionInfo = fetchedResultController.sections?[section] else {return 0}
|
||||
return sectionInfo.numberOfObjects
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
if indexPath.section == tableView.numberOfSections - 1 {
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
|
||||
self.configureRecentSearchCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
} else {
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("CheckMarkBookCell", forIndexPath: indexPath)
|
||||
self.configureBookCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
func configureRecentSearchCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
|
||||
cell.textLabel?.text = "placeholder"
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("CheckMarkBookCell", forIndexPath: indexPath)
|
||||
self.configureBookCell(cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureBookCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
|
||||
@ -95,13 +81,9 @@ class SearchScopeSelectVC: UIViewController, UITableViewDelegate, UITableViewDat
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
if section == tableView.numberOfSections - 1 {
|
||||
return "Recent Searches"
|
||||
} else {
|
||||
guard tableView.numberOfSections > 1 else {return nil}
|
||||
guard let languageName = fetchedResultController.sections?[section].name else {return nil}
|
||||
return languageName
|
||||
}
|
||||
guard tableView.numberOfSections > 1 else {return nil}
|
||||
guard let languageName = fetchedResultController.sections?[section].name else {return nil}
|
||||
return languageName
|
||||
}
|
||||
|
||||
// MARK: Table view delegate
|
||||
@ -119,7 +101,7 @@ class SearchScopeSelectVC: UIViewController, UITableViewDelegate, UITableViewDat
|
||||
|
||||
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
guard let mainVC = parentViewController?.parentViewController as? MainVC,
|
||||
let book = fetchedResultController.objectAtIndexPath(indexPath) as? Book else {return}
|
||||
let book = fetchedResultController.objectAtIndexPath(indexPath) as? Book else {return}
|
||||
mainVC.hideSearch()
|
||||
mainVC.loadMainPage(book)
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
|
@ -35,6 +35,8 @@ class SearchVC: UIViewController, UISearchBarDelegate, UIGestureRecognizerDelega
|
||||
|
||||
override func viewWillDisappear(animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
guard searchText != "" else {return}
|
||||
Preference.recentSearchTerms.insert(searchText, atIndex: 0)
|
||||
searchText = ""
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>414</string>
|
||||
<string>525</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
@ -366,7 +366,7 @@
|
||||
<!--Search Scope SelectVC-->
|
||||
<scene sceneID="D7X-oq-0zg">
|
||||
<objects>
|
||||
<viewController id="6mg-ey-R4I" customClass="SearchScopeSelectVC" customModule="Kiwix" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController automaticallyAdjustsScrollViewInsets="NO" id="6mg-ey-R4I" customClass="SearchScopeSelectVC" customModule="Kiwix" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="zo0-tg-4E4"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Tmc-y1-RDA"/>
|
||||
@ -376,11 +376,11 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="N78-0h-zb7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="108" width="600" height="492"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="CheckMarkBookCell" id="QXK-je-ISm" customClass="CheckMarkBookCell" customModule="Kiwix">
|
||||
<rect key="frame" x="0.0" y="92" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="28" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="QXK-je-ISm" id="bj5-Xo-SR6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
@ -504,7 +504,7 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="ff0-K9-MIu" style="IBUITableViewCellStyleDefault" id="mws-kS-13l">
|
||||
<rect key="frame" x="0.0" y="136" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="72" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="mws-kS-13l" id="kHF-OB-kpr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
@ -522,13 +522,26 @@
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
</tableView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xsY-LL-2as" customClass="DropShadowView" customModule="Kiwix" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="6A0-j8-csD"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<segue destination="x8e-ps-NUY" kind="embed" id="1G1-dy-u1M"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="xsY-LL-2as" secondAttribute="trailing" id="0Y8-BQ-wxl"/>
|
||||
<constraint firstItem="N78-0h-zb7" firstAttribute="top" secondItem="xsY-LL-2as" secondAttribute="bottom" id="4XG-Av-G6S"/>
|
||||
<constraint firstItem="Tmc-y1-RDA" firstAttribute="top" secondItem="N78-0h-zb7" secondAttribute="bottom" id="7NT-cy-Kqp"/>
|
||||
<constraint firstItem="N78-0h-zb7" firstAttribute="top" secondItem="zo0-tg-4E4" secondAttribute="bottom" constant="-64" id="C1c-FJ-IFr"/>
|
||||
<constraint firstItem="N78-0h-zb7" firstAttribute="top" secondItem="zo0-tg-4E4" secondAttribute="bottom" constant="44" id="C1c-FJ-IFr"/>
|
||||
<constraint firstAttribute="trailing" secondItem="N78-0h-zb7" secondAttribute="trailing" id="CPN-ym-DCz"/>
|
||||
<constraint firstItem="N78-0h-zb7" firstAttribute="top" secondItem="xId-x3-dwx" secondAttribute="top" id="T0d-Ah-Ztr"/>
|
||||
<constraint firstItem="xsY-LL-2as" firstAttribute="leading" secondItem="xId-x3-dwx" secondAttribute="leading" id="UBV-XT-6uX"/>
|
||||
<constraint firstItem="xsY-LL-2as" firstAttribute="top" secondItem="zo0-tg-4E4" secondAttribute="bottom" id="gDO-1F-hDx"/>
|
||||
<constraint firstItem="N78-0h-zb7" firstAttribute="leading" secondItem="xId-x3-dwx" secondAttribute="leading" id="tov-n3-ofO"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
@ -1150,21 +1163,21 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3329" y="-979"/>
|
||||
</scene>
|
||||
<!--Lang LocalCVC-->
|
||||
<!--Recent SearchCVC-->
|
||||
<scene sceneID="jLU-Rv-hcv">
|
||||
<objects>
|
||||
<viewController id="x8e-ps-NUY" customClass="LangLocalCVC" customModule="Kiwix" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="x8e-ps-NUY" customClass="RecentSearchCVC" customModule="Kiwix" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="cm9-Ho-8rR"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="M4a-gw-9IU"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="HQ0-6h-SSN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="oLp-jV-WAb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<color key="backgroundColor" red="0.96078431372549022" green="0.96078431372549022" blue="0.96078431372549022" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="SEj-84-HqB">
|
||||
<size key="itemSize" width="102" height="42"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
@ -1173,7 +1186,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="yvP-cy-f4q" customClass="LocalLangCell" customModule="Kiwix" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="102" height="42"/>
|
||||
<rect key="frame" x="0.0" y="1" width="102" height="42"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="102" height="42"/>
|
||||
@ -1215,7 +1228,20 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8l6-By-GDL" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2594" y="-979"/>
|
||||
<point key="canvasLocation" x="2600" y="-1129"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="iOA-8v-xbz">
|
||||
<objects>
|
||||
<viewController id="pJE-0z-aFb" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="9AO-X6-IxZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kua-SW-WKF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
@ -12,7 +12,7 @@ class LocalLangCell: UICollectionViewCell {
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
override func awakeFromNib() {
|
||||
layer.cornerRadius = 10.0
|
||||
layer.cornerRadius = 15.0
|
||||
layer.masksToBounds = true
|
||||
backgroundColor = UIColor.themeColor
|
||||
}
|
||||
|
@ -8,15 +8,13 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TabsContainerView: UIView {
|
||||
class DropShadowView: UIView {
|
||||
|
||||
override func drawRect(rect: CGRect) {
|
||||
layer.masksToBounds = false
|
||||
|
||||
layer.shadowOffset = CGSizeMake(0, 0)
|
||||
layer.shadowRadius = 4.0
|
||||
layer.shadowOpacity = 0.5
|
||||
layer.shadowRadius = 2.0
|
||||
layer.shadowOpacity = 0.5
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
@ -24,4 +22,4 @@ class TabsContainerView: UIView {
|
||||
layer.shadowPath = UIBezierPath(rect: bounds).CGPath
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -77,6 +77,58 @@ extension UITableView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UINavigationBar {
|
||||
func hideBottomHairline() {
|
||||
let navigationBarImageView = hairlineImageViewInNavigationBar(self)
|
||||
navigationBarImageView!.hidden = true
|
||||
}
|
||||
|
||||
func showBottomHairline() {
|
||||
let navigationBarImageView = hairlineImageViewInNavigationBar(self)
|
||||
navigationBarImageView!.hidden = false
|
||||
}
|
||||
|
||||
private func hairlineImageViewInNavigationBar(view: UIView) -> UIImageView? {
|
||||
if view.isKindOfClass(UIImageView) && view.bounds.height <= 1.0 {
|
||||
return (view as! UIImageView)
|
||||
}
|
||||
|
||||
let subviews = (view.subviews as [UIView])
|
||||
for subview: UIView in subviews {
|
||||
if let imageView: UIImageView = hairlineImageViewInNavigationBar(subview) {
|
||||
return imageView
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension UIToolbar {
|
||||
func hideHairline() {
|
||||
let navigationBarImageView = hairlineImageViewInToolbar(self)
|
||||
navigationBarImageView!.hidden = true
|
||||
}
|
||||
|
||||
func showHairline() {
|
||||
let navigationBarImageView = hairlineImageViewInToolbar(self)
|
||||
navigationBarImageView!.hidden = false
|
||||
}
|
||||
|
||||
private func hairlineImageViewInToolbar(view: UIView) -> UIImageView? {
|
||||
if view.isKindOfClass(UIImageView) && view.bounds.height <= 1.0 {
|
||||
return (view as! UIImageView)
|
||||
}
|
||||
|
||||
let subviews = (view.subviews as [UIView])
|
||||
for subview: UIView in subviews {
|
||||
if let imageView: UIImageView = hairlineImageViewInToolbar(subview) {
|
||||
return imageView
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Controller
|
||||
|
||||
extension UIAlertController {
|
||||
|
@ -257,7 +257,7 @@
|
||||
97A7140A1C274FCB00951244 /* DirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A714091C274FCB00951244 /* DirectoryMonitor.swift */; };
|
||||
97B50C7F1CA1E4810010BD79 /* UIOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B50C7E1CA1E4810010BD79 /* UIOperations.swift */; };
|
||||
97BA32A51CEBC36300339A47 /* RootWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BA32A31CEBC29500339A47 /* RootWindowController.swift */; };
|
||||
97D452BC1D16FF010033666F /* LangLocalCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D452BB1D16FF010033666F /* LangLocalCVC.swift */; };
|
||||
97D452BC1D16FF010033666F /* RecentSearchCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D452BB1D16FF010033666F /* RecentSearchCVC.swift */; };
|
||||
97D452BE1D1723FF0033666F /* CollectionViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D452BD1D1723FF0033666F /* CollectionViewCells.swift */; };
|
||||
97DF23551CE807A1003E1E5A /* GlobalOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DF23541CE807A1003E1E5A /* GlobalOperationQueue.swift */; };
|
||||
97E609F11D103DED00EBCB9D /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E609F01D103DED00EBCB9D /* NotificationCenter.framework */; };
|
||||
@ -531,7 +531,7 @@
|
||||
97A714091C274FCB00951244 /* DirectoryMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryMonitor.swift; sourceTree = "<group>"; };
|
||||
97B50C7E1CA1E4810010BD79 /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIOperations.swift; sourceTree = "<group>"; };
|
||||
97BA32A31CEBC29500339A47 /* RootWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RootWindowController.swift; path = "Kiwix-OSX/Controllers/RootWindowController.swift"; sourceTree = SOURCE_ROOT; };
|
||||
97D452BB1D16FF010033666F /* LangLocalCVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LangLocalCVC.swift; path = "Kiwix-iOS/Controller/Search/LangLocalCVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
97D452BB1D16FF010033666F /* RecentSearchCVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RecentSearchCVC.swift; path = "Kiwix-iOS/Controller/Search/RecentSearchCVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
97D452BD1D1723FF0033666F /* CollectionViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCells.swift; sourceTree = "<group>"; };
|
||||
97D452BF1D1871E70033666F /* SearchHistoryTBVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchHistoryTBVC.swift; path = "Kiwix-iOS/Controller/Search/SearchHistoryTBVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
97D452C01D1871E70033666F /* SearchLocalBooksCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchLocalBooksCVC.swift; path = "Kiwix-iOS/Controller/Search/SearchLocalBooksCVC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
@ -892,9 +892,9 @@
|
||||
971187051CEB426E00B9909D /* libkiwix */,
|
||||
9779987A1C1E1C9600B1DD5E /* Extensions.swift */,
|
||||
97E891681CA976E90001CA32 /* FileManager.swift */,
|
||||
971A10711D022E74007FC62C /* KiwixURLProtocol.swift */,
|
||||
973C8D5B1C25F945007272F9 /* Preference.swift */,
|
||||
979C51511CECA9AF001707F2 /* StringTools.swift */,
|
||||
971A10711D022E74007FC62C /* KiwixURLProtocol.swift */,
|
||||
97254FDD1C26442F0056950B /* ZIMMultiReader */,
|
||||
);
|
||||
name = Shared;
|
||||
@ -1025,7 +1025,6 @@
|
||||
972B007D1C35DBAB00B5FDC5 /* MainVC */,
|
||||
97E108221C5D5A0D00E27FD3 /* Search */,
|
||||
9771DC4B1C37278E009ECFF0 /* Setting */,
|
||||
97D452BB1D16FF010033666F /* LangLocalCVC.swift */,
|
||||
);
|
||||
name = Controllers;
|
||||
path = Kiwix;
|
||||
@ -1243,6 +1242,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
971A10451D022CB2007FC62C /* SearchVC.swift */,
|
||||
97D452BB1D16FF010033666F /* RecentSearchCVC.swift */,
|
||||
971A10481D022CBE007FC62C /* SearchScopeSelectVC.swift */,
|
||||
971A10471D022CBE007FC62C /* SearchResultTBVC.swift */,
|
||||
9768957A1CB6A35E00F02686 /* Old */,
|
||||
@ -1817,7 +1817,7 @@
|
||||
979CB6C11D05C520005E1BA1 /* OperationQueue.swift in Sources */,
|
||||
97DF23551CE807A1003E1E5A /* GlobalOperationQueue.swift in Sources */,
|
||||
979CB6711D05C44F005E1BA1 /* SilentCondition.swift in Sources */,
|
||||
97D452BC1D16FF010033666F /* LangLocalCVC.swift in Sources */,
|
||||
97D452BC1D16FF010033666F /* RecentSearchCVC.swift in Sources */,
|
||||
979CB6651D05C44F005E1BA1 /* NoCancelledDependencies.swift in Sources */,
|
||||
971A10681D022E0A007FC62C /* MainVCWebViewD.swift in Sources */,
|
||||
970103FB1C6824FA00DC48F6 /* RefreshLibraryOperation.swift in Sources */,
|
||||
|
@ -15,9 +15,18 @@ class Preference {
|
||||
set{Defaults[.hasShowGetStartedAlert] = newValue}
|
||||
}
|
||||
|
||||
// MARK: - Recent Search
|
||||
|
||||
class func addRecentSearchTerm(searchTerm: String) {
|
||||
recentSearchTerms.insert(searchTerm, atIndex: 0)
|
||||
}
|
||||
|
||||
class var recentSearchTerms: [String] {
|
||||
get{return Defaults[.recentSearchTerms]}
|
||||
set{Defaults[.recentSearchTerms] = newValue}
|
||||
set{
|
||||
let searchTerms = NSOrderedSet(array: newValue).array as! [String]
|
||||
Defaults[.recentSearchTerms] = searchTerms.count > 20 ? Array(searchTerms[0..<20]) : searchTerms
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Reading
|
||||
|
@ -24,7 +24,9 @@ class SearchOperation: GroupOperation {
|
||||
let managedObjectContext = UIApplication.appDelegate.managedObjectContext
|
||||
guard let book = Book.fetch(id, context: managedObjectContext) else {continue}
|
||||
guard book.includeInSearch else {continue}
|
||||
let operation = SingleBookSearchOperation(zimReader: zimReader, searchTerm: searchTerm.lowercaseString, completionHandler: { [unowned sortOperation] (results) in
|
||||
let operation = SingleBookSearchOperation(zimReader: zimReader,
|
||||
searchTerm: searchTerm.lowercaseString,
|
||||
completionHandler: { [unowned sortOperation] (results) in
|
||||
sortOperation.results += results
|
||||
})
|
||||
|
||||
@ -46,6 +48,7 @@ private class SingleBookSearchOperation: Operation {
|
||||
let zimReader: ZimReader
|
||||
let searchTerm: String
|
||||
let completionHandler: ([SearchResult]) -> Void
|
||||
private var results = [String: SearchResult]()
|
||||
|
||||
init(zimReader: ZimReader, searchTerm: String, completionHandler: ([SearchResult]) -> Void) {
|
||||
self.zimReader = zimReader
|
||||
@ -54,17 +57,14 @@ private class SingleBookSearchOperation: Operation {
|
||||
}
|
||||
|
||||
override private func execute() {
|
||||
guard let resultDics = zimReader.search(searchTerm) as? [[String: AnyObject]] else {
|
||||
finish()
|
||||
return
|
||||
let indexedDics = zimReader.searchUsingIndex(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let titleDics = zimReader.searchSuggestionsSmart(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result
|
||||
for dic in mixedDics {
|
||||
guard let result = SearchResult (rawResult: dic) else {continue}
|
||||
results[result.title] = result
|
||||
}
|
||||
var results = [SearchResult]()
|
||||
for dic in resultDics {
|
||||
guard let result = SearchResult(rawResult: dic) else {continue}
|
||||
print(result)
|
||||
results.append(result)
|
||||
}
|
||||
completionHandler(results)
|
||||
completionHandler(Array(results.values))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@ -90,13 +90,10 @@ private class SortSearchResultsOperation: Operation {
|
||||
*/
|
||||
private func sort() {
|
||||
results.sortInPlace { (result0, result1) -> Bool in
|
||||
let result0Percent = result0.percent ?? -1
|
||||
let result1Percent = result1.percent ?? -1
|
||||
|
||||
if result0Percent == result1Percent {
|
||||
return titleCaseInsensitiveCompare(result0, result1: result1)
|
||||
if result0.score != result1.score {
|
||||
return result0.score < result1.score
|
||||
} else {
|
||||
return result0Percent > result1Percent
|
||||
return titleCaseInsensitiveCompare(result0, result1: result1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,24 +163,43 @@ extension ZimReader {
|
||||
class SearchResult: CustomStringConvertible {
|
||||
let bookID: String
|
||||
let title: String
|
||||
let percent: Double? // range: 0-100
|
||||
let distance: Int64 // Levenshtein distance, non negative integer
|
||||
let path: String?
|
||||
let snippet: String?
|
||||
|
||||
let percent: Double? // range: 0-100
|
||||
let distance: Int // Levenshtein distance, non negative integer
|
||||
let score: Double
|
||||
|
||||
init?(rawResult: [String: AnyObject]) {
|
||||
let title = (rawResult["title"] as? String) ?? ""
|
||||
self.bookID = (rawResult["bookID"] as? String) ?? ""
|
||||
self.title = (rawResult["title"] as? String) ?? ""
|
||||
|
||||
self.percent = (rawResult["percent"] as? NSNumber)?.doubleValue
|
||||
self.distance = (rawResult["distance"]as? NSNumber)?.longLongValue ?? 0
|
||||
self.title = title
|
||||
self.path = rawResult["path"] as? String
|
||||
self.snippet = rawResult["snippet"] as? String
|
||||
|
||||
let percent = (rawResult["percent"] as? NSNumber)?.doubleValue
|
||||
let distance = (rawResult["distance"]as? NSNumber)?.integerValue ?? title.characters.count
|
||||
let score: Double = {
|
||||
if let percent = percent {
|
||||
return SearchResult.calculateScore(percent / 100) * Double(distance)
|
||||
} else {
|
||||
return Double(distance)
|
||||
}
|
||||
}()
|
||||
self.percent = percent
|
||||
self.distance = distance
|
||||
self.score = score
|
||||
|
||||
if bookID == "" {return nil}
|
||||
if title == "" {return nil}
|
||||
}
|
||||
|
||||
class func calculateScore(prob: Double) -> Double {
|
||||
let m = 1.9709635999
|
||||
let n = 2.2255409285
|
||||
return log(m * (1-prob) + n) / log(2.71828)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
var parts = [bookID, title]
|
||||
if let percent = percent {parts.append("\(percent)%")}
|
||||
|
@ -29,7 +29,6 @@
|
||||
- (NSString *)getRandomPageUrl;
|
||||
|
||||
#pragma mark - search
|
||||
- (NSArray *)search:(NSString *)searchTerm;
|
||||
- (NSArray *)searchSuggestionsSmart:(NSString *)searchTerm;
|
||||
- (NSArray *)searchUsingIndex:(NSString *)searchTerm;
|
||||
|
||||
|
@ -45,14 +45,6 @@
|
||||
|
||||
#pragma mark - search
|
||||
|
||||
- (NSArray *)search:(NSString *)searchTerm {
|
||||
if(_db == nil) {
|
||||
return [self searchSuggestionsSmart:searchTerm];
|
||||
} else {
|
||||
return [self searchUsingIndex:searchTerm];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)searchSuggestionsSmart:(NSString *)searchTerm {
|
||||
string searchTermC = [searchTerm cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
int count = SEARCH_SUGGESTIONS_COUNT;
|
||||
@ -63,7 +55,7 @@
|
||||
string titleC;
|
||||
while (_reader->getNextSuggestion(titleC)) {
|
||||
NSString *title = [NSString stringWithUTF8String:titleC.c_str()];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title]];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title.lowercaseString]];
|
||||
[results addObject:@{@"title": title,
|
||||
@"bookID": bookID,
|
||||
@"distance": distance}];
|
||||
@ -73,6 +65,7 @@
|
||||
}
|
||||
|
||||
- (NSArray *)searchUsingIndex:(NSString *)searchTerm {
|
||||
if(_db == nil) {return @[];}
|
||||
try {
|
||||
NSArray *searchTerms = [searchTerm componentsSeparatedByString:@" "];
|
||||
NSString *bookID = [self getID];
|
||||
@ -98,7 +91,7 @@
|
||||
NSString *path = [NSString stringWithUTF8String:doc.get_data().c_str()];
|
||||
NSString *title = [NSString stringWithUTF8String:doc.get_value(0).c_str()];
|
||||
NSString *snippet = [NSString stringWithUTF8String:doc.get_value(1).c_str()];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title]];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title.lowercaseString]];
|
||||
|
||||
NSDictionary *result = @{@"percent": percent,
|
||||
@"path": path,
|
||||
|
Loading…
x
Reference in New Issue
Block a user