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 search in request url to inspector floret #134

Merged
merged 17 commits into from
Feb 18, 2019
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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Changelog
## Unreleased
* **improvement** Changed the name of the framework from `Cauli` to `Cauliframework`. [#135](https://github.com/cauliframework/cauli/issues/135)

## Unreleased
* **improvement** Changed the `max` and `all` functions on `RecordSelector` to be public. [#136](https://github.com/cauliframework/cauli/issues/136)
* **improvement** Added a search to the Inspector Floret that allows filtering the record list by URL.

## 0.9
* First public release
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,23 @@ internal class InspectorRecordTableViewCell: UITableViewCell {
@IBOutlet private weak var contentTypeLabel: UILabel!
@IBOutlet private weak var statusCodeLabel: TagLabel!

var record: Record? {
didSet {
if let record = record {
load(from: record)
}
}
}

private func load(from record: Record) {
internal func configure(with record: Record, stringToHighlight: String?) {
if let requestStarted = record.requestStarted {
timeLabel.text = InspectorRecordTableViewCell.timeFormatter.string(from: requestStarted)
} else {
timeLabel.text = ""
}
methodLabel.text = record.designatedRequest.httpMethod
pathLabel.text = record.designatedRequest.url?.absoluteString
let pathString = record.designatedRequest.url?.absoluteString ?? ""
let pathAttributedString = NSMutableAttributedString(string: pathString)
if let stringToHighlight = stringToHighlight {
var rangeToSearch = pathString.startIndex..<pathString.endIndex
while let matchingRange = pathString.range(of: stringToHighlight, options: String.CompareOptions.caseInsensitive, range: rangeToSearch) {
pathAttributedString.addAttributes([.font: UIFont.boldSystemFont(ofSize: pathLabel.font.pointSize), .foregroundColor: tintColor], range: NSRange(matchingRange, in: pathString))
rangeToSearch = matchingRange.upperBound..<pathString.endIndex
}
}
pathLabel.attributedText = pathAttributedString
switch record.result {
case nil:
contentTypeLabel.text = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,38 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="GET" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gpj-7q-WGE" customClass="TagLabel" customModule="Cauli" customModuleProvider="target">
<rect key="frame" x="11" y="9" width="55" height="27"/>
<rect key="frame" x="11" y="9" width="55" height="17"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="18:24:12" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iLL-7a-gbP">
<rect key="frame" x="11" y="45.5" width="55" height="27"/>
<rect key="frame" x="11" y="55.5" width="55" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" horizontalCompressionResistancePriority="752" verticalCompressionResistancePriority="752" text="200" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dwe-2a-sT2" customClass="TagLabel" customModule="Cauli" customModuleProvider="target">
<rect key="frame" x="281" y="45.5" width="44" height="27"/>
<rect key="frame" x="297" y="55.5" width="28" height="17"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="application/json" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8QC-S5-nAc">
<rect key="frame" x="80" y="45.5" width="192" height="27"/>
<rect key="frame" x="80" y="55.5" width="208" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="/organizations/cauliframework" textAlignment="natural" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Daq-5Q-ci8">
<rect key="frame" x="80" y="9" width="245" height="27.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Daq-5Q-ci8">
<rect key="frame" x="80" y="9" width="245" height="37.5"/>
<attributedString key="attributedText">
<fragment content="/organizations/cauliframework">
<attributes>
<font key="NSFont" metaFont="system" size="14"/>
</attributes>
</fragment>
</attributedString>
<nil key="highlightedColor"/>
</label>
</subviews>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,18 @@ internal class InspectorTableViewController: UITableViewController {
private static let recordPageSize = 20

var cauli: Cauli
var records: [Record] = []
let dataSource = InspectorTableViewDatasource()

private lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.delegate = self
searchController.searchBar.autocapitalizationType = .none
searchController.searchBar.autocorrectionType = .no
return searchController
}()

init(_ cauli: Cauli) {
self.cauli = cauli
Expand All @@ -42,32 +53,29 @@ internal class InspectorTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Records"
let bundle = Bundle(for: InspectorTableViewController.self)
let nib = UINib(nibName: InspectorRecordTableViewCell.nibName, bundle: bundle)
tableView.register(nib, forCellReuseIdentifier: InspectorRecordTableViewCell.reuseIdentifier)
dataSource.setup(tableView: tableView)
definesPresentationContext = true
if #available(iOS 11.0, *) {
navigationItem.searchController = searchController
} else {
tableView.tableHeaderView = searchController.searchBar
}
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
records = cauli.storage.records(InspectorTableViewController.recordPageSize, after: nil)
let records = cauli.storage.records(InspectorTableViewController.recordPageSize, after: nil)
dataSource.append(records: records, to: tableView)
searchController.searchBar.isHidden = false
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return records.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: InspectorRecordTableViewCell.reuseIdentifier, for: indexPath) as? InspectorRecordTableViewCell else {
fatalError("Unable to dequeue a cell")
}
let record = records[indexPath.row]
cell.record = record
cell.accessoryType = .disclosureIndicator
return cell
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
searchController.searchBar.isHidden = true
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let record = records[indexPath.row]
let record = dataSource.record(at: indexPath)
let recordTableViewController = RecordTableViewController(record)
navigationController?.pushViewController(recordTableViewController, animated: true)
}
Expand All @@ -78,26 +86,43 @@ internal class InspectorTableViewController: UITableViewController {
let distanceToBottom = scrollView.contentSize.height - scrollView.frame.height - scrollView.contentOffset.y
guard !scrolledToEnd, !isLoading, distanceToBottom < 100 else { return }
isLoading = true
let newRecords = cauli.storage.records(InspectorTableViewController.recordPageSize, after: records.last)
if newRecords.isEmpty {
let newRecords = cauli.storage.records(InspectorTableViewController.recordPageSize, after: dataSource.items.last)
guard !newRecords.isEmpty else {
isLoading = false
scrolledToEnd = true
} else if #available(iOS 11, *) {
tableView.performBatchUpdates({
let indexPaths = (records.count..<(records.count + newRecords.count)).map { IndexPath(row: $0, section: 0) }
tableView.insertRows(at: indexPaths, with: .bottom)
records.append(contentsOf: newRecords)
}, completion: { [weak self] _ in
self?.isLoading = false
})
} else {
let indexPaths = (records.count..<(records.count + newRecords.count)).map { IndexPath(row: $0, section: 0) }
tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: .bottom)
records.append(contentsOf: newRecords)
tableView.endUpdates()
self.isLoading = false
return
}

dataSource.append(records: newRecords, to: tableView, completion: { [weak self] _ in
self?.isLoading = false
})
}

}

// MARK: - UISearchResultsUpdating

extension InspectorTableViewController: UISearchResultsUpdating {

func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text ?? ""
dataSource.filterString = searchString
tableView.reloadData()
}

}

// MARK: - UISearchBarDelegate

extension InspectorTableViewController: UISearchBarDelegate {

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

func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
dataSource.filterString = nil
tableView.reloadData()
}

}
113 changes: 113 additions & 0 deletions Cauli/Florets/Inspector/Record List/InspectorTableViewDatasource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// Copyright (c) 2018 cauli.works
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import UIKit

internal class InspectorTableViewDatasource: NSObject {

internal private(set) var items: [Record] = [] {
didSet {
filteredItems = self.filteredItems(in: items, with: filter)
}
}
internal private(set) var filteredItems: [Record] = []
private var filter: RecordSelector? {
didSet {
filteredItems = self.filteredItems(in: items, with: filter)
}
}
internal var filterString: String? {
didSet {
updateFilter(with: filterString)
}
}

private func filteredItems(in array: [Record], with filter: RecordSelector?) -> [Record] {
guard let filter = filter else { return array }
return array.filter(filter.selects)
}

private func updateFilter(with filterString: String?) {
guard let filterString = filterString, !filterString.isEmpty else {
filter = nil
return
}
filter = RecordSelector(selects: { record in
guard let urlString = record.designatedRequest.url?.absoluteString else {
return false
}
return urlString.range(of: filterString, options: String.CompareOptions.caseInsensitive) != nil
})
}

private func filter(records: [Record]) -> [Record] {
return filteredItems(in: records, with: filter)
}

private func performBatchUpdate(in tableView: UITableView, updates: (()->Void), completion: ((_ finished: Bool)->Void)? = nil) {
if #available(iOS 11, *) {
tableView.performBatchUpdates(updates, completion: completion)
} else {
tableView.beginUpdates()
updates()
tableView.endUpdates()
completion?(true)
}
}

internal func append(records: [Record], to tableView: UITableView, completion: ((_ finished: Bool)->Void)? = nil) {
performBatchUpdate(in: tableView, updates: {
let numberOfExistingRecords = filteredItems.count
let numberOfAddedRecords = filter(records: records).count
let indexPaths = (numberOfExistingRecords..<(numberOfExistingRecords + numberOfAddedRecords)).map { IndexPath(row: $0, section: 0) }
tableView.insertRows(at: indexPaths, with: .bottom)
items += records
}, completion: completion)
}

}

extension InspectorTableViewDatasource: UITableViewDataSource {
internal func setup(tableView: UITableView) {
tableView.dataSource = self
let bundle = Bundle(for: InspectorTableViewController.self)
let nib = UINib(nibName: InspectorRecordTableViewCell.nibName, bundle: bundle)
tableView.register(nib, forCellReuseIdentifier: InspectorRecordTableViewCell.reuseIdentifier)
}

internal func record(at indexPath: IndexPath) -> Record {
return filteredItems[indexPath.row]
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredItems.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: InspectorRecordTableViewCell.reuseIdentifier, for: indexPath) as? InspectorRecordTableViewCell else {
fatalError("Unable to dequeue a cell")
}
cell.configure(with: record(at: indexPath), stringToHighlight: filterString)
cell.accessoryType = .disclosureIndicator
return cell
}
}
Loading