New Search rank system + New recent search result bar

This commit is contained in:
Chris Li 2016-06-23 11:20:01 -04:00
parent a693e4c601
commit 1e75216411
18 changed files with 277 additions and 339 deletions

10
BoostFactorTune.R Normal file
View 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)

View File

@ -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()
}
}

View 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
}
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 = ""
}

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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 {

View File

@ -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 */,

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)%")}

View File

@ -29,7 +29,6 @@
- (NSString *)getRandomPageUrl;
#pragma mark - search
- (NSArray *)search:(NSString *)searchTerm;
- (NSArray *)searchSuggestionsSmart:(NSString *)searchTerm;
- (NSArray *)searchUsingIndex:(NSString *)searchTerm;

View File

@ -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,