CPDFSearchResultsViewController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. //
  2. // CPDFSearchResultsViewController.swift
  3. // ComPDFKit_Tools
  4. //
  5. // Copyright © 2014-2024 PDF Technologies, Inc. All Rights Reserved.
  6. //
  7. // THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
  8. // AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT.
  9. // UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
  10. // This notice may not be removed from this file.
  11. //
  12. import UIKit
  13. import ComPDFKit
  14. protocol CPDFSearchResultsDelegate: AnyObject {
  15. func searchResultsView(_ resultVC: CPDFSearchResultsViewController, forSelection selection: CPDFSelection, indexPath: IndexPath)
  16. func searchResultsViewControllerDismiss(_ searchResultsViewController: CPDFSearchResultsViewController)
  17. }
  18. let kTextSearch_Content_Length_Max = 100
  19. class CPDFSearchResultsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  20. weak var delegate: CPDFSearchResultsDelegate?
  21. public weak var pdfListView:CPDFListView?
  22. public var searchString:String?
  23. private var resultArray: [Any]?
  24. private var keyword: String?
  25. private var document: CPDFDocument?
  26. private var tableView: UITableView?
  27. private var searchResultView: UIView?
  28. private var searchResultLabel: UILabel?
  29. private var pageLabel: UILabel?
  30. private var backBtn: UIButton?
  31. // MARK: - Initializers
  32. init(resultArray: [Any], keyword: String, document: CPDFDocument) {
  33. super.init(nibName: nil, bundle: nil)
  34. // Initialization code
  35. self.resultArray = resultArray
  36. self.keyword = keyword
  37. self.document = document
  38. }
  39. // MARK: - UIViewController Methods
  40. override func viewDidLoad() {
  41. super.viewDidLoad()
  42. title = NSLocalizedString("Results", comment: "")
  43. tableView = UITableView(frame: view.frame, style: .plain)
  44. tableView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  45. tableView?.delegate = self
  46. tableView?.dataSource = self
  47. view.backgroundColor = UIColor.white
  48. tableView?.rowHeight = UITableView.automaticDimension
  49. tableView?.estimatedRowHeight = 60
  50. tableView?.tableFooterView = UIView()
  51. tableView?.separatorStyle = .none
  52. tableView?.register(CPDFSearchViewCell.self, forCellReuseIdentifier: "cell")
  53. if(tableView != nil) {
  54. view.addSubview(tableView!)
  55. }
  56. searchResultView = UIView()
  57. searchResultLabel = UILabel()
  58. pageLabel = UILabel()
  59. searchResultView?.backgroundColor = CPDFColorUtils.CAnnotationSampleBackgoundColor()
  60. pageLabel?.font = UIFont.systemFont(ofSize: 14)
  61. pageLabel?.text = NSLocalizedString("Page", comment: "")
  62. pageLabel?.textAlignment = .right
  63. pageLabel?.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1.0)
  64. searchResultLabel?.font = UIFont.systemFont(ofSize: 14)
  65. searchResultLabel?.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1.0)
  66. if(searchResultLabel != nil) {
  67. searchResultView?.addSubview(searchResultLabel!)
  68. }
  69. if(pageLabel != nil) {
  70. searchResultView?.addSubview(pageLabel!)
  71. }
  72. if(searchResultView != nil) {
  73. view.addSubview(searchResultView!)
  74. }
  75. updatePreferredContentSize(with: traitCollection)
  76. backBtn = UIButton()
  77. backBtn?.autoresizingMask = .flexibleLeftMargin
  78. backBtn?.setImage(UIImage(named: "CPDFViewImageBack", in: Bundle(for: self.classForCoder), compatibleWith: nil), for: .normal)
  79. backBtn?.addTarget(self, action: #selector(buttonItemClicked_Back(_:)), for: .touchUpInside)
  80. backBtn?.sizeToFit()
  81. if(backBtn != nil) {
  82. let backItem = UIBarButtonItem(customView: backBtn!)
  83. self.navigationItem.leftBarButtonItems = [backItem];
  84. }
  85. view.backgroundColor = CPDFColorUtils.CPDFViewControllerBackgroundColor()
  86. var datas: [CPDFSelection] = []
  87. if let resultArray = self.resultArray as? [[CPDFSelection]] {
  88. for results in resultArray {
  89. for selection in results {
  90. datas.append(selection)
  91. }
  92. }
  93. }
  94. searchResultLabel?.text = "\(datas.count) \(NSLocalizedString("Resultss", comment: ""))"
  95. self.searchResultLabel?.sizeToFit()
  96. }
  97. required init?(coder aDecoder: NSCoder) {
  98. super.init(coder: aDecoder)
  99. }
  100. // MARK: - Action
  101. @objc func buttonItemClicked_Back(_ sender: UIButton) {
  102. self.delegate?.searchResultsViewControllerDismiss(self)
  103. }
  104. @objc func textField_ShouldReturn(_ sender: UIButton) {
  105. self.resignFirstResponder()
  106. }
  107. // MARK: - Private Methods
  108. override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
  109. super.willTransition(to: newCollection, with: coordinator)
  110. updatePreferredContentSize(with: newCollection)
  111. }
  112. func updatePreferredContentSize(with traitCollection: UITraitCollection) {
  113. let width = UIScreen.main.bounds.size.width
  114. let height = UIScreen.main.bounds.size.height
  115. let mWidth = min(width, height)
  116. let mHeight = max(width, height)
  117. let currentDevice = UIDevice.current
  118. if currentDevice.userInterfaceIdiom == .pad {
  119. // This is an iPad
  120. self.preferredContentSize = CGSize(width: self.view.bounds.size.width, height: traitCollection.verticalSizeClass == .compact ? mWidth * 0.7 : mHeight * 0.7)
  121. } else {
  122. // This is an iPhone or iPod touch
  123. self.preferredContentSize = CGSize(width: self.view.bounds.size.width, height: traitCollection.verticalSizeClass == .compact ? mWidth * 0.9 : mHeight * 0.9)
  124. }
  125. }
  126. private lazy var loadingView: CActivityIndicatorView = {
  127. if #available(iOS 13.0, *) {
  128. loadingView = CActivityIndicatorView(style: .large)
  129. } else {
  130. loadingView = CActivityIndicatorView(style: .gray)
  131. }
  132. loadingView.center = self.view?.center ?? CGPoint.zero
  133. loadingView.autoresizingMask = [.flexibleTopMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleRightMargin]
  134. return loadingView
  135. }()
  136. override func viewWillLayoutSubviews() {
  137. super.viewWillLayoutSubviews()
  138. if #available(iOS 11.0, *) {
  139. tableView?.frame = CGRect(x: view.safeAreaInsets.left, y: view.safeAreaInsets.top + 28, width: view.frame.size.width - view.safeAreaInsets.left - view.safeAreaInsets.right, height: view.frame.size.height - view.safeAreaInsets.bottom - view.safeAreaInsets.top-28)
  140. searchResultView?.frame = CGRect(x: view.safeAreaInsets.left, y: view.safeAreaInsets.top , width: view.frame.size.width - view.safeAreaInsets.left - view.safeAreaInsets.right, height: 28)
  141. searchResultLabel?.frame = CGRect(x: 20, y: 4, width: 200, height: 20)
  142. pageLabel?.frame = CGRect(x: view.frame.size.width - 50, y: 4, width: 40, height: 20)
  143. } else {
  144. searchResultView?.frame = CGRect(x: 20, y: self.navigationController?.navigationBar.frame.maxY ?? 0, width: view.frame.size.width - 20, height: 28)
  145. searchResultLabel?.frame = CGRect(x: 20, y: 4, width: 200, height: 20)
  146. pageLabel?.frame = CGRect(x: view.frame.size.width - 50, y: 4, width: 40, height: 20)
  147. tableView?.frame = CGRect(x: view.bounds.origin.x, y: 28, width: view.bounds.size.width, height: view.bounds.size.height-28)
  148. }
  149. }
  150. func trimMultipleSpaces(_ input: String) -> Int {
  151. let trimmedString = input.trimmingCharacters(in: .whitespacesAndNewlines)
  152. var count = trimmedString.count
  153. if input.hasPrefix(" ") {
  154. count += 1
  155. }
  156. if input.hasSuffix(" ") {
  157. count += 1
  158. }
  159. return count
  160. }
  161. @objc func getAttributedString(with selection: CPDFSelection?) -> NSMutableAttributedString? {
  162. guard let currentPage = selection?.page else {
  163. return nil
  164. }
  165. let range = selection?.range
  166. var startLocation: UInt = 0
  167. let maxLocation: UInt = 20
  168. var keyLocation = 0
  169. let maxEndLocation: UInt = 80
  170. if (range?.location ?? 0) > maxLocation {
  171. startLocation = UInt(range?.location ?? 0) - maxLocation
  172. keyLocation = Int(maxLocation)
  173. } else {
  174. startLocation = 0
  175. keyLocation = Int(range?.location ?? 0)
  176. }
  177. var endLocation: UInt = 0
  178. if UInt(range?.location ?? 0) + UInt(maxEndLocation) > currentPage.numberOfCharacters {
  179. endLocation = UInt(currentPage.numberOfCharacters)
  180. } else {
  181. endLocation = UInt(range?.location ?? 0) + maxEndLocation
  182. }
  183. var attributed: NSMutableAttributedString?
  184. if endLocation > startLocation {
  185. if let currentString = currentPage.string(for: NSRange(location: Int(startLocation), length: Int(endLocation - startLocation))) {
  186. let count = trimMultipleSpaces(self.keyword ?? "")
  187. let tRange = NSRange(location: keyLocation, length: count)
  188. if tRange.location != NSNotFound {
  189. attributed = NSMutableAttributedString(string: currentString)
  190. let paragraphStyle = NSMutableParagraphStyle()
  191. paragraphStyle.firstLineHeadIndent = 10.0
  192. paragraphStyle.headIndent = 10.0
  193. paragraphStyle.lineBreakMode = .byCharWrapping
  194. let font = UIFont(name: "HelveticaNeue-Medium", size: 13.0)
  195. let dic1: [NSAttributedString.Key: Any] = [.font: font ?? UIFont.systemFont(ofSize: 12), .paragraphStyle: paragraphStyle]
  196. let range1 = (attributed?.string ?? "") as NSString
  197. attributed?.setAttributes(dic1, range: range1.range(of: range1 as String))
  198. let dic2: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor(red: 102.0/255.0, green: 102.0/255.0, blue: 102.0/255.0, alpha: 1.0)]
  199. attributed?.addAttributes(dic2, range: NSRange(location: 0, length: (attributed?.length ?? 0)))
  200. let dic3: [NSAttributedString.Key: Any] = [.backgroundColor: UIColor(red: 1.0, green: 220.0/255.0, blue: 27.0/255.0, alpha: 1.0)]
  201. if (attributed?.length ?? 0) >= (tRange.length + tRange.location) {
  202. attributed?.addAttributes(dic3, range: tRange)
  203. }
  204. }
  205. }
  206. }
  207. return attributed
  208. }
  209. // MARK: - UITableViewDataSource
  210. func numberOfSections(in tableView: UITableView) -> Int {
  211. return self.resultArray?.count ?? 0
  212. }
  213. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  214. let array = self.resultArray?[section] as? NSArray
  215. return array?.count ?? 0
  216. }
  217. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CPDFSearchViewCell ?? CPDFSearchViewCell(style: .subtitle, reuseIdentifier: "cell")
  218. let selection = (self.resultArray?[indexPath.section] as? NSArray)?[indexPath.row]
  219. cell.contentLabel?.attributedText = getAttributedString(with: selection as? CPDFSelection)
  220. return cell
  221. }
  222. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  223. let selection = (self.resultArray?[indexPath.section] as? NSArray)?[indexPath.row]
  224. guard let attributeText = getAttributedString(with: selection as? CPDFSelection) else {
  225. return 0
  226. }
  227. let cellWidth = tableView.frame.size.width
  228. let padding = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
  229. let framesetter = CTFramesetterCreateWithAttributedString(attributeText as CFAttributedString)
  230. let suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, CGSize(width: cellWidth - padding.left - padding.right, height: CGFloat.greatestFiniteMagnitude), nil)
  231. let cellHeight = suggestedSize.height + padding.top + padding.bottom
  232. return cellHeight
  233. }
  234. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  235. return 30
  236. }
  237. func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  238. guard let array = self.resultArray?[section] as? [CPDFSelection], let selection = array.first, let pageIndex = self.document?.index(for: selection.page) else {
  239. return nil
  240. }
  241. let countStr = String(format: NSLocalizedString("%ld", comment: ""), pageIndex + 1)
  242. let view = UITableViewHeaderFooterView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: 30))
  243. view.autoresizingMask = .flexibleWidth
  244. view.backgroundColor = UIColor(red: 250.0/255.0, green: 252.0/255.0, blue: 255.0/255.0, alpha: 1.0)
  245. let sublabel = UILabel()
  246. sublabel.font = UIFont.systemFont(ofSize: 14)
  247. sublabel.text = countStr
  248. sublabel.textColor = UIColor(red: 67.0/255.0, green: 71.0/255.0, blue: 77.0/255.0, alpha: 1.0)
  249. sublabel.sizeToFit()
  250. sublabel.frame = CGRect(x: view.bounds.size.width - sublabel.bounds.size.width - 10, y: 0,
  251. width: sublabel.bounds.size.width, height: view.bounds.size.height)
  252. sublabel.autoresizingMask = .flexibleLeftMargin
  253. view.contentView.addSubview(sublabel)
  254. return view
  255. }
  256. // MARK: - UITableViewDelegate
  257. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  258. tableView.deselectRow(at: indexPath, animated: true)
  259. dismiss(animated: true) {
  260. let selection = (self.resultArray?[indexPath.section] as? NSArray)?[indexPath.row]
  261. if(selection != nil && selection is CPDFSelection) {
  262. self.delegate?.searchResultsView(self, forSelection: selection as! CPDFSelection, indexPath: indexPath)
  263. }
  264. }
  265. }
  266. }