Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added UISearchController on BodyDetailViewController #10

Merged
merged 1 commit into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Added possibility to search in body text
  • Loading branch information
Alex Petrarca committed Jul 23, 2018
commit 696bd04c7380d816658c4ed1b1cd7159675dd0f9
45 changes: 45 additions & 0 deletions Sources/Extension/UITextView_Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// UITextView_Extension.swift
// Wormholy-iOS
//
// Created by Alex Petrarca on 18/07/18.
// Copyright © 2018 Wormholy. All rights reserved.
//

import UIKit

extension UITextView {
func highlights(text: String?, with color: UIColor = UIColor.green, font: UIFont = UIFont.boldSystemFont(ofSize: 14)) -> [NSTextCheckingResult] {

guard let keywordSearch = text?.lowercased(), let textViewText = self.text?.lowercased() else { return [] }

let attributed = NSMutableAttributedString(string: textViewText)
attributed.addAttribute(.font, value: UIFont.systemFont(ofSize: 14), range: NSRange(location: 0, length: self.attributedText.length))

do {
let regex = try NSRegularExpression(pattern: keywordSearch, options: .caseInsensitive)
let matches = regex.matches(in: textViewText, options: [], range: NSMakeRange(0, textViewText.count))

matches.forEach {
attributed.addAttribute(.backgroundColor, value: color, range: $0.range)
attributed.addAttribute(.font, value: font, range: $0.range)
}
self.attributedText = attributed

return matches

} catch let error {
print(error)
}
return []
}

func convertRange(range: NSRange) -> UITextRange? {
let beginning = self.beginningOfDocument
if let start = self.position(from: beginning, offset: range.location),
let end = self.position(from: start, offset: range.length) {
return self.textRange(from: start, to: end)
}
return nil
}
}
2 changes: 2 additions & 0 deletions Sources/Support Files/Colors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ struct Colors {
}

struct UI{
static public let wordsInEvidence = UIColor(hexString: "#dadfe1")
static public let wordFocus = UIColor(hexString: "#f7ca18")
}

struct Gray{
Expand Down
176 changes: 169 additions & 7 deletions Sources/UI/BodyDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,36 @@
import UIKit

class BodyDetailViewController: WHBaseViewController {

@IBOutlet weak var constraintBottomViewInput: NSLayoutConstraint!
@IBOutlet weak var toolBar: UIToolbar!
@IBOutlet weak var labelWordFinded: UILabel!
@IBOutlet weak var textView: WHTextView!
@IBOutlet weak var buttonPrevious: UIBarButtonItem!
@IBOutlet weak var buttonNext: UIBarButtonItem!

static let kPadding: CGFloat = 10.0

var searchController: UISearchController?
var highlightedWords: [NSTextCheckingResult] = []
var data: Data?
var indexOfWord: Int = 0

deinit {
NotificationCenter.default.removeObserver(self)
}

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(BodyDetailViewController.handleKeyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(BodyDetailViewController.handleKeyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

let shareButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareContent))
navigationItem.rightBarButtonItems = [shareButton]
let searchButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearch))
navigationItem.rightBarButtonItems = [searchButton, shareButton]

buttonPrevious.isEnabled = false
buttonNext.isEnabled = false
addSearchController()
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -32,21 +54,117 @@ class BodyDetailViewController: WHBaseViewController {

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

// MARK: - Search
func addSearchController(){
searchController = UISearchController(searchResultsController: nil)
searchController?.searchResultsUpdater = self
searchController?.searchBar.returnKeyType = .done
searchController?.searchBar.delegate = self
if #available(iOS 9.1, *) {
searchController?.obscuresBackgroundDuringPresentation = false
} else {
// Fallback
}
searchController?.searchBar.placeholder = "Search"
if #available(iOS 11.0, *) {
navigationItem.searchController = searchController
} else {
navigationItem.titleView = searchController?.searchBar
}
definesPresentationContext = true
}

@IBAction func previousStep(_ sender: UIBarButtonItem?) {
indexOfWord -= 1
if indexOfWord < 0 {
indexOfWord = highlightedWords.count - 1
}
getCursor()
}

@IBAction func nextStep(_ sender: UIBarButtonItem?) {
indexOfWord += 1
if indexOfWord >= highlightedWords.count {
indexOfWord = 0
}
getCursor()
}

func getCursor() {
let value = highlightedWords[indexOfWord]
if let range = textView.convertRange(range: value.range) {
let rect = textView.firstRect(for: range)

labelWordFinded.text = "\(indexOfWord + 1) of \(highlightedWords.count)"
let focusRect = CGRect(origin: textView.contentOffset, size: textView.frame.size)
if !focusRect.contains(rect) {
textView.setContentOffset(CGPoint(x: 0, y: rect.origin.y - BodyDetailViewController.kPadding), animated: true)
}
cursorAnimation(with: value.range)
}
}

func setupSearch(text: String?) {
highlightedWords.removeAll()
highlightedWords = textView.highlights(text: text, with: Colors.UI.wordsInEvidence)

indexOfWord = 0

if highlightedWords.count != 0 {
getCursor()
buttonPrevious.isEnabled = true
buttonNext.isEnabled = true
}
else {
buttonPrevious.isEnabled = false
buttonNext.isEnabled = false
labelWordFinded.text = "0 of 0"
}
}

@objc func shareContent(){
if let text = textView.text{
let textShare = [text]
let activityViewController = UIActivityViewController(activityItems: textShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
let textShare = [text]
let activityViewController = UIActivityViewController(activityItems: textShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}

@objc func showSearch() {
searchController?.isActive = true
}

// MARK: - Keyboard

@objc func handleKeyboardWillShow(_ sender: NSNotification) {
guard let keyboardSize = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size else { return }

animationInputView(with: -keyboardSize.height, notification: sender)
}

@objc func handleKeyboardWillHide(_ sender: NSNotification) {
animationInputView(with: 0.0, notification: sender)
}

func animationInputView(with height: CGFloat, notification: NSNotification) {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber

self.constraintBottomViewInput.constant = height

UIView.animate(withDuration: duration?.doubleValue ?? 0.0, delay: 0.0, options: UIViewAnimationOptions(rawValue: UIViewAnimationOptions.RawValue((curve?.intValue)!)), animations: {
self.view.layoutIfNeeded()
})
}

/*
// MARK: - Navigation

Expand All @@ -58,3 +176,47 @@ class BodyDetailViewController: WHBaseViewController {
*/

}

extension BodyDetailViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text?.isEmpty == false {
setupSearch(text: searchController.searchBar.text)
}
else {
resetSearchText()
}
}
}

extension BodyDetailViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}

fileprivate extension BodyDetailViewController {
func resetSearchText() {
let attributedString = NSMutableAttributedString(attributedString: self.textView.attributedText)
attributedString.addAttribute(.backgroundColor, value: UIColor.clear, range: NSRange(location: 0, length: self.textView.attributedText.length))
attributedString.addAttribute(.font, value: UIFont.systemFont(ofSize: 14), range: NSRange(location: 0, length: self.textView.attributedText.length))

self.textView.attributedText = attributedString
self.labelWordFinded.text = "0 of 0"
self.buttonPrevious.isEnabled = false
self.buttonNext.isEnabled = false
}

func cursorAnimation(with range: NSRange) {
let attributedString = NSMutableAttributedString(attributedString: self.textView.attributedText)

highlightedWords.forEach {
attributedString.addAttribute(.backgroundColor, value: Colors.UI.wordsInEvidence, range: $0.range)
attributedString.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 14), range: $0.range)
}
self.textView.attributedText = attributedString

attributedString.addAttribute(.backgroundColor, value: Colors.UI.wordFocus, range: range)
self.textView.attributedText = attributedString
}
}

55 changes: 50 additions & 5 deletions Sources/UI/Flow.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -117,29 +117,74 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="fpK-qH-d5h" customClass="WHTextView" customModule="Wormholy" customModuleProvider="target">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="onDrag" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="fpK-qH-d5h" customClass="WHTextView" customModule="Wormholy" customModuleProvider="target">
<rect key="frame" x="0.0" y="64" width="375" height="559"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="50N-HR-LmK">
<rect key="frame" x="0.0" y="623" width="375" height="44"/>
<items>
<barButtonItem style="plain" systemItem="flexibleSpace" id="RNb-pa-Z9b"/>
<barButtonItem style="plain" id="Dvu-s4-6n6">
<view key="customView" contentMode="scaleToFill" id="wDm-Tn-1Yd">
<rect key="frame" x="133" y="5.5" width="177" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0 of 0" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VxO-VX-XW4">
<rect key="frame" x="8" y="6" width="161" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.51784205436706543" green="0.58159953355789185" blue="0.58617168664932251" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="VxO-VX-XW4" firstAttribute="leading" secondItem="wDm-Tn-1Yd" secondAttribute="leading" constant="8" id="1M4-or-p61"/>
<constraint firstAttribute="trailing" secondItem="VxO-VX-XW4" secondAttribute="trailing" constant="8" id="7tD-VQ-WrV"/>
<constraint firstItem="VxO-VX-XW4" firstAttribute="centerY" secondItem="wDm-Tn-1Yd" secondAttribute="centerY" id="tj5-2Q-HIO"/>
</constraints>
</view>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</barButtonItem>
<barButtonItem title="&lt;" id="0uV-bv-w6Y">
<connections>
<action selector="previousStep:" destination="S40-LP-XSy" id="9vW-vP-iLe"/>
</connections>
</barButtonItem>
<barButtonItem title="&gt;" id="usk-ma-BJD">
<connections>
<action selector="nextStep:" destination="S40-LP-XSy" id="kud-2b-ETO"/>
</connections>
</barButtonItem>
</items>
</toolbar>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="50N-HR-LmK" firstAttribute="bottom" secondItem="Y9P-Kk-3xC" secondAttribute="bottom" id="8iS-pr-kZD"/>
<constraint firstItem="fpK-qH-d5h" firstAttribute="top" secondItem="Y9P-Kk-3xC" secondAttribute="top" id="G1r-th-Ci7"/>
<constraint firstItem="fpK-qH-d5h" firstAttribute="leading" secondItem="Y9P-Kk-3xC" secondAttribute="leading" id="LcP-XH-gTf"/>
<constraint firstItem="Y9P-Kk-3xC" firstAttribute="bottom" secondItem="fpK-qH-d5h" secondAttribute="bottom" id="RLZ-Wr-Z9V"/>
<constraint firstItem="50N-HR-LmK" firstAttribute="leading" secondItem="Y9P-Kk-3xC" secondAttribute="leading" id="NDW-xa-Ffi"/>
<constraint firstItem="Y9P-Kk-3xC" firstAttribute="trailing" secondItem="fpK-qH-d5h" secondAttribute="trailing" id="ey8-iu-S0g"/>
<constraint firstItem="fpK-qH-d5h" firstAttribute="top" secondItem="Y9P-Kk-3xC" secondAttribute="top" id="jfn-FH-a4p"/>
<constraint firstItem="50N-HR-LmK" firstAttribute="trailing" secondItem="Y9P-Kk-3xC" secondAttribute="trailing" id="ne9-h7-K6x"/>
<constraint firstItem="50N-HR-LmK" firstAttribute="top" secondItem="fpK-qH-d5h" secondAttribute="bottom" id="rIu-y7-dC5"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Y9P-Kk-3xC"/>
</view>
<connections>
<outlet property="buttonNext" destination="usk-ma-BJD" id="WOF-OQ-9lC"/>
<outlet property="buttonPrevious" destination="0uV-bv-w6Y" id="EV2-uG-w6x"/>
<outlet property="constraintBottomViewInput" destination="8iS-pr-kZD" id="gPb-Ca-TPx"/>
<outlet property="labelWordFinded" destination="VxO-VX-XW4" id="n8Q-3A-lhj"/>
<outlet property="textView" destination="fpK-qH-d5h" id="9qA-qk-Zwk"/>
<outlet property="toolBar" destination="50N-HR-LmK" id="0YO-MP-cGV"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="BEJ-Hi-Vka" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1945" y="148"/>
<point key="canvasLocation" x="1944.8" y="147.97601199400302"/>
</scene>
</scenes>
</document>
Loading