add custom search bar

This commit is contained in:
Chris Li 2016-11-13 14:14:35 -05:00
parent 7666dbd550
commit 134d2747a0
9 changed files with 294 additions and 82 deletions

View File

@ -37,7 +37,7 @@ class MainController: UIViewController {
newArticle?.addObserver(self, forKeyPath: "isBookmarked", options: .new, context: context)
}
didSet {
searchBar.articleTitle = article?.title
// searchBar.articleTitle = article?.title
configureBookmarkButton()
configureUserActivity()
}
@ -123,10 +123,10 @@ class MainController: UIViewController {
toolbarItems?.removeAll()
navigationItem.leftBarButtonItems = [navigateLeftButton, navigateRightButton, tableOfContentButton]
navigationItem.rightBarButtonItems = [settingButton, libraryButton, bookmarkButton]
searchBar.setShowsCancelButton(false, animated: true)
// searchBar.setShowsCancelButton(false, animated: true)
case .compact:
if !searchBar.isFirstResponder {navigationController?.isToolbarHidden = false}
if searchBar.isFirstResponder {searchBar.setShowsCancelButton(true, animated: true)}
// if searchBar.isFirstResponder {searchBar.setShowsCancelButton(true, animated: true)}
navigationItem.leftBarButtonItems?.removeAll()
navigationItem.rightBarButtonItems?.removeAll()
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

View File

@ -35,7 +35,7 @@ extension MainController {
if UIDevice.current.userInterfaceIdiom == .pad {
navigationItem.setRightBarButton(cancelButton, animated: animated)
} else if UIDevice.current.userInterfaceIdiom == .phone {
searchBar.setShowsCancelButton(true, animated: animated)
// searchBar.setShowsCancelButton(true, animated: animated)
}
}
}
@ -51,7 +51,7 @@ extension MainController {
if UIDevice.current.userInterfaceIdiom == .pad {
navigationItem.setRightBarButton(nil, animated: animated)
} else if UIDevice.current.userInterfaceIdiom == .phone {
searchBar.setShowsCancelButton(false, animated: animated)
// searchBar.setShowsCancelButton(false, animated: animated)
}
}
}

View File

@ -62,7 +62,7 @@ class RecentSearchController: UIViewController, UICollectionViewDataSource, UICo
let searchController = parent?.parent as? SearchController,
let cell = collectionView.cellForItem(at: indexPath) as? LocalLangCell,
let text = cell.label.text else {return}
mainVC.searchBar.searchTerm = text
// mainVC.searchBar.searchTerm = text
searchController.startSearch(text, delayed: false)
collectionView.deselectItem(at: indexPath, animated: true)
}

View File

@ -119,9 +119,9 @@ class SearchResultController: SearchTableViewController, UITableViewDataSource,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let searchTerm = Controllers.main.searchBar.searchTerm {
Preference.RecentSearch.add(term: searchTerm)
}
// if let searchTerm = Controllers.main.searchBar.searchTerm {
// Preference.RecentSearch.add(term: searchTerm)
// }
let result = searchResults[indexPath.row]
// let operation = ArticleLoadOperation(bookID: result.bookID, articleTitle: result.title)

View File

@ -49,7 +49,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.8.2409</string>
<string>1.8.2419</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@ -0,0 +1,100 @@
//
// SearchBar.swift
// Kiwix
//
// Created by Chris Li on 1/22/16.
// Copyright © 2016 Chris. All rights reserved.
//
import UIKit
class SearchBar: UISearchBar, UISearchBarDelegate {
var searchTerm: String? {
didSet {
text = searchTerm
}
}
var articleTitle: String? {
didSet {
configurePlaceholder()
}
}
fileprivate var textField: UITextField {
return value(forKey: "searchField") as! UITextField
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init() {
self.init(frame: CGRect.zero)
self.searchBarStyle = .minimal
self.autocapitalizationType = .none
self.placeholder = LocalizedStrings.search
self.returnKeyType = .go
self.delegate = self
}
// MARK: - UISearchBarDelegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
text = searchTerm
configurePlaceholder()
Controllers.main.showSearch(animated: true)
let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(0.05 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { [unowned self] in
self.textField.selectAll(nil)
})
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
text = nil
configurePlaceholder()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
Controllers.main.hideSearch(animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchTerm = searchText
Controllers.search.startSearch(searchText, delayed: true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
Controllers.search.searchResultController?.selectFirstResultIfPossible()
}
// MARK: - Helper
fileprivate func configurePlaceholder() {
if textField.isEditing {
placeholder = LocalizedStrings.search
} else {
placeholder = articleTitle ?? LocalizedStrings.search
}
}
fileprivate func truncatedPlaceHolderString(_ string: String?, searchBar: UISearchBar) -> String? {
guard let string = string,
let labelFont = textField.font else {return nil}
let preferredSize = CGSize(width: searchBar.frame.width - 45.0, height: 1000)
var rect = (string as NSString).boundingRect(with: preferredSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: labelFont], context: nil)
var truncatedString = string as NSString
var istruncated = false
while rect.height > textField.frame.height {
istruncated = true
truncatedString = truncatedString.substring(to: truncatedString.length - 2) as NSString
rect = truncatedString.boundingRect(with: preferredSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: labelFont], context: nil)
}
return truncatedString as String + (istruncated ? "..." : "")
}
}

View File

@ -1,100 +1,212 @@
//
// SearchBar.swift
// Kiwix
// SearchBar
//
// Created by Chris Li on 1/22/16.
// Copyright © 2016 Chris. All rights reserved.
// Created by Chris Li on 9/2/16.
// Copyright © 2016 Chris Li. All rights reserved.
//
import UIKit
class SearchBar: UISearchBar, UISearchBarDelegate {
var searchTerm: String? {
didSet {
text = searchTerm
}
}
class SearchBar: UIView {
var articleTitle: String? {
didSet {
configurePlaceholder()
}
}
let backgroundView = SearchBarBackgroundView()
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
private(set) var textField: UITextField = SearchBarTextField()
fileprivate var textField: UITextField {
return value(forKey: "searchField") as! UITextField
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
convenience init() {
self.init(frame: CGRect.zero)
self.searchBarStyle = .minimal
self.autocapitalizationType = .none
self.placeholder = LocalizedStrings.search
self.returnKeyType = .go
self.delegate = self
}
// MARK: - UISearchBarDelegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
text = searchTerm
configurePlaceholder()
Controllers.main.showSearch(animated: true)
let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(0.05 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { [unowned self] in
self.textField.selectAll(nil)
})
convenience init(textField: UITextField) {
self.init(frame: CGRect.zero)
self.textField = textField
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
text = nil
configurePlaceholder()
override func willMove(toSuperview newSuperview: UIView?) {
guard let superview = newSuperview else {return}
frame = CGRect(x: 0, y: 0, width: superview.frame.width, height: superview.frame.height)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
Controllers.main.hideSearch(animated: true)
deinit {
NotificationCenter.default.removeObserver(self, name: .UITextFieldTextDidEndEditing, object: textField)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchTerm = searchText
Controllers.search.startSearch(searchText, delayed: true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
Controllers.search.searchResultController?.selectFirstResultIfPossible()
}
// MARK: - Helper
fileprivate func configurePlaceholder() {
if textField.isEditing {
placeholder = LocalizedStrings.search
} else {
placeholder = articleTitle ?? LocalizedStrings.search
}
}
fileprivate func truncatedPlaceHolderString(_ string: String?, searchBar: UISearchBar) -> String? {
guard let string = string,
let labelFont = textField.font else {return nil}
let preferredSize = CGSize(width: searchBar.frame.width - 45.0, height: 1000)
var rect = (string as NSString).boundingRect(with: preferredSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: labelFont], context: nil)
func setup() {
translatesAutoresizingMaskIntoConstraints = true
autoresizingMask = [.flexibleWidth, .flexibleHeight]
var truncatedString = string as NSString
var istruncated = false
while rect.height > textField.frame.height {
istruncated = true
truncatedString = truncatedString.substring(to: truncatedString.length - 2) as NSString
rect = truncatedString.boundingRect(with: preferredSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: labelFont], context: nil)
}
return truncatedString as String + (istruncated ? "..." : "")
addSubview(backgroundView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", options: .alignAllCenterY, metrics: nil, views: ["view": backgroundView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[view]-|", options: .alignAllCenterX, metrics: nil, views: ["view": backgroundView]))
backgroundView.addSubview(visualEffectView)
addSubview(textField)
textField.isUserInteractionEnabled = false
textField.translatesAutoresizingMaskIntoConstraints = false
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[view]-|", options: .alignAllCenterY, metrics: nil, views: ["view": textField]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[view]-|", options: .alignAllCenterX, metrics: nil, views: ["view": textField]))
NotificationCenter.default.addObserver(self, selector: #selector(SearchBar.textFieldDidEndEditing), name: .UITextFieldTextDidEndEditing, object: textField)
}
// MARK: - Responder
override func becomeFirstResponder() -> Bool {
textField.isUserInteractionEnabled = true
textField.textColor = UIColor.black
textField.becomeFirstResponder()
return true
}
override func resignFirstResponder() -> Bool {
textField.resignFirstResponder()
return true
}
// MARK: -
func textFieldDidEndEditing() {
textField.isUserInteractionEnabled = false
textField.textColor = UIColor.darkGray
}
}
class SearchBarTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func setup() {
placeholder = "Search"
font = UIFont.systemFont(ofSize: 14.0, weight: UIFontWeightRegular + 0.1)
autocorrectionType = .no
autocapitalizationType = .none
clearButtonMode = .whileEditing
}
override var text: String? {
didSet {
let size = CGSize(width: 1000, height: 28)
let rect = text?.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
print(rect)
}
}
// MARK: - Rect overrides
override func textRect(forBounds bounds: CGRect) -> CGRect {
let rect = super.textRect(forBounds: bounds)
return rect.offsetBy(dx: 50, dy: 1)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return super.textRect(forBounds: bounds).insetBy(dx: 4, dy: 0).offsetBy(dx: 0, dy: 1)
}
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
return super.leftViewRect(forBounds: bounds).offsetBy(dx: -2, dy: 0)
}
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
return super.clearButtonRect(forBounds: bounds).offsetBy(dx: 10, dy: 0)
}
}
class SearchBarBackgroundView: UIView {
var isTouching = false
var isAnimatingIn = false
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func setup() {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = UIColor.lightGray
alpha = 0.3
layer.cornerRadius = 4.0
layer.masksToBounds = true
}
// MARK: - Override
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
animateIn()
_ = (superview as? SearchBar)?.becomeFirstResponder()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = false
guard !isAnimatingIn else {return}
animateOut()
}
// MARK: - Animations
func animateIn() {
isAnimatingIn = true
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseOut, animations: {
self.backgroundColor = UIColor.gray
}) { (completed) in
self.isAnimatingIn = false
guard !self.isTouching else {return}
self.animateOut()
}
}
func animateOut() {
UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseIn, animations: {
self.backgroundColor = UIColor.lightGray
}) { (completed) in
}
}
}
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: 1000, height: 28)
let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}

View File

@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.8.2409</string>
<string>1.8.2419</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>

View File

@ -25,7 +25,6 @@
971A102F1D022AD5007FC62C /* Logo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10271D022AD5007FC62C /* Logo.swift */; };
971A10301D022AD5007FC62C /* LTBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10281D022AD5007FC62C /* LTBarButtonItem.swift */; };
971A10311D022AD5007FC62C /* RefreshHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10291D022AD5007FC62C /* RefreshHUD.swift */; };
971A10321D022AD5007FC62C /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A102A1D022AD5007FC62C /* SearchBar.swift */; };
971A10521D022D9D007FC62C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10511D022D9D007FC62C /* AppDelegate.swift */; };
971A107E1D022F74007FC62C /* DownloaderLearnMore.html in Resources */ = {isa = PBXBuildFile; fileRef = 971A107A1D022F74007FC62C /* DownloaderLearnMore.html */; };
971A107F1D022F74007FC62C /* ImportBookLearnMore.html in Resources */ = {isa = PBXBuildFile; fileRef = 971A107B1D022F74007FC62C /* ImportBookLearnMore.html */; };
@ -76,6 +75,7 @@
9779C3141D4575AD0064CC8E /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E609F01D103DED00EBCB9D /* NotificationCenter.framework */; };
9779C3171D4575AE0064CC8E /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779C3161D4575AE0064CC8E /* TodayViewController.swift */; };
9779C31E1D4575AE0064CC8E /* Bookmarks.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 9779C3131D4575AD0064CC8E /* Bookmarks.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
977AE7F91DD8F22400F1E581 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A102A1D022AD5007FC62C /* SearchBar.swift */; };
977B954D1DD4C40400F6F62B /* ScanLocalBookOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */; };
97A08C151DD263B90070D0E4 /* Book.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D681341D6F711A00E5FA99 /* Book.swift */; };
97A127C91D777CF100FB204D /* RecentSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A127C51D777CF100FB204D /* RecentSearchController.swift */; };
@ -1141,7 +1141,6 @@
970E7F741D9DB0FC00741290 /* 1.8.xcmappingmodel in Sources */,
973207A01DD1983D00EDD3DC /* DownloadTasksController.swift in Sources */,
97A1FD321D6F723D00A80EE2 /* resourceTools.cpp in Sources */,
971A10321D022AD5007FC62C /* SearchBar.swift in Sources */,
97A1FD451D6F728200A80EE2 /* StringTools.swift in Sources */,
97D681411D6F712800E5FA99 /* DownloadTask+CoreDataProperties.swift in Sources */,
97D681391D6F711A00E5FA99 /* DownloadTask.swift in Sources */,
@ -1175,6 +1174,7 @@
973208271DD2238B00EDD3DC /* GlobalQueue.swift in Sources */,
97A1FD181D6F71CE00A80EE2 /* SearchResult.swift in Sources */,
973207A11DD1983D00EDD3DC /* LocalBooksController.swift in Sources */,
977AE7F91DD8F22400F1E581 /* SearchBar.swift in Sources */,
97ED50111DD257D00089E9B6 /* Kiwix.xcdatamodeld in Sources */,
973DD4281D36E3E4009D45DB /* SettingDetailController.swift in Sources */,
973207A41DD1983D00EDD3DC /* EmptyTableConfigExtension.swift in Sources */,