From 134d2747a035750d3bb588fe978e6860e558b9c0 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Sun, 13 Nov 2016 14:14:35 -0500 Subject: [PATCH] add custom search bar --- .../Controller/Main/MainController.swift | 6 +- .../Main/MainControllerShowHide.swift | 4 +- .../Search/RecentSearchController.swift | 2 +- .../Search/SearchResultController.swift | 6 +- Kiwix-iOS/Info.plist | 2 +- Kiwix-iOS/View/SearchBar-old.swift | 100 +++++++ Kiwix-iOS/View/SearchBar.swift | 250 +++++++++++++----- Kiwix-iOSWidgets/Bookmarks/Info.plist | 2 +- Kiwix.xcodeproj/project.pbxproj | 4 +- 9 files changed, 294 insertions(+), 82 deletions(-) create mode 100644 Kiwix-iOS/View/SearchBar-old.swift diff --git a/Kiwix-iOS/Controller/Main/MainController.swift b/Kiwix-iOS/Controller/Main/MainController.swift index a5595cc4..c71b6ee4 100644 --- a/Kiwix-iOS/Controller/Main/MainController.swift +++ b/Kiwix-iOS/Controller/Main/MainController.swift @@ -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) diff --git a/Kiwix-iOS/Controller/Main/MainControllerShowHide.swift b/Kiwix-iOS/Controller/Main/MainControllerShowHide.swift index 86f94a41..835d186a 100644 --- a/Kiwix-iOS/Controller/Main/MainControllerShowHide.swift +++ b/Kiwix-iOS/Controller/Main/MainControllerShowHide.swift @@ -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) } } } diff --git a/Kiwix-iOS/Controller/Search/RecentSearchController.swift b/Kiwix-iOS/Controller/Search/RecentSearchController.swift index 5e2b5e0c..5b529f89 100644 --- a/Kiwix-iOS/Controller/Search/RecentSearchController.swift +++ b/Kiwix-iOS/Controller/Search/RecentSearchController.swift @@ -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) } diff --git a/Kiwix-iOS/Controller/Search/SearchResultController.swift b/Kiwix-iOS/Controller/Search/SearchResultController.swift index ab16ffb5..081aaf30 100644 --- a/Kiwix-iOS/Controller/Search/SearchResultController.swift +++ b/Kiwix-iOS/Controller/Search/SearchResultController.swift @@ -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) diff --git a/Kiwix-iOS/Info.plist b/Kiwix-iOS/Info.plist index ab6f88ec..fdf7707a 100644 --- a/Kiwix-iOS/Info.plist +++ b/Kiwix-iOS/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 1.8.2409 + 1.8.2419 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Kiwix-iOS/View/SearchBar-old.swift b/Kiwix-iOS/View/SearchBar-old.swift new file mode 100644 index 00000000..97e9788e --- /dev/null +++ b/Kiwix-iOS/View/SearchBar-old.swift @@ -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 ? "..." : "") + } +} diff --git a/Kiwix-iOS/View/SearchBar.swift b/Kiwix-iOS/View/SearchBar.swift index 97e9788e..afaf0314 100644 --- a/Kiwix-iOS/View/SearchBar.swift +++ b/Kiwix-iOS/View/SearchBar.swift @@ -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, with event: UIEvent?) { + isTouching = true + animateIn() + _ = (superview as? SearchBar)?.becomeFirstResponder() + } + + override func touchesEnded(_ touches: Set, 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 } } diff --git a/Kiwix-iOSWidgets/Bookmarks/Info.plist b/Kiwix-iOSWidgets/Bookmarks/Info.plist index 98ddb42c..13eb70a2 100644 --- a/Kiwix-iOSWidgets/Bookmarks/Info.plist +++ b/Kiwix-iOSWidgets/Bookmarks/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 1.8.2409 + 1.8.2419 NSExtension NSExtensionMainStoryboard diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index c40446e9..513672df 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -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 */,