2017-01-19 21:15:47 +08:00
|
|
|
//
|
2017-02-03 13:01:41 +08:00
|
|
|
// PasswordsViewController.swift
|
2017-01-19 21:15:47 +08:00
|
|
|
// pass
|
|
|
|
|
//
|
2017-02-03 13:01:41 +08:00
|
|
|
// Created by Mingshen Sun on 3/2/2017.
|
2017-01-19 21:15:47 +08:00
|
|
|
// Copyright © 2017 Bob Sun. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
import Result
|
|
|
|
|
import SVProgressHUD
|
2017-02-06 10:55:24 +08:00
|
|
|
import SwiftyUserDefaults
|
2017-02-07 20:57:06 +08:00
|
|
|
import PasscodeLock
|
2017-01-19 21:15:47 +08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
2017-01-19 21:15:47 +08:00
|
|
|
private var passwordEntities: [PasswordEntity]?
|
2017-01-23 12:48:20 +08:00
|
|
|
var filteredPasswordEntities = [PasswordEntity]()
|
2017-02-02 15:03:34 +08:00
|
|
|
var sections : [(index: Int, length :Int, title: String)] = Array()
|
2017-02-03 13:01:41 +08:00
|
|
|
var searchActive : Bool = false
|
|
|
|
|
let searchController = UISearchController(searchResultsController: nil)
|
2017-02-04 11:30:57 +08:00
|
|
|
lazy var refreshControl: UIRefreshControl = {
|
|
|
|
|
let refreshControl = UIRefreshControl()
|
|
|
|
|
refreshControl.addTarget(self, action: #selector(PasswordsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
|
|
|
|
|
return refreshControl
|
|
|
|
|
}()
|
|
|
|
|
let searchBarView = UIView(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 44))
|
2017-02-03 13:01:41 +08:00
|
|
|
|
|
|
|
|
@IBOutlet weak var tableView: UITableView!
|
2017-01-23 13:43:06 +08:00
|
|
|
|
2017-02-10 22:15:01 +08:00
|
|
|
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
|
|
|
|
|
if let controller = segue.source as? AddPasswordTableViewController {
|
2017-02-11 19:48:47 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
|
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
|
|
|
SVProgressHUD.show(withStatus: "Saving")
|
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
|
|
|
PasswordStore.shared.add(password: controller.password!, progressBlock: { progress in
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showProgress(progress, status: "Encrypting")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showSuccess(withStatus: "Done")
|
|
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
|
|
|
NotificationCenter.default.post(Notification(name: Notification.Name("passwordUpdated")))
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-10 22:15:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-02-04 11:47:57 +08:00
|
|
|
func syncPasswords() {
|
2017-02-08 01:29:00 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
|
|
|
SVProgressHUD.setDefaultStyle(.light)
|
2017-02-04 15:23:14 +08:00
|
|
|
SVProgressHUD.show(withStatus: "Sync Password Store")
|
2017-02-15 16:51:12 +08:00
|
|
|
let numberOfUnsyncedPasswords = PasswordStore.shared.getNumberOfUnsyncedPasswords()
|
2017-02-04 11:35:28 +08:00
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
2017-02-04 14:24:59 +08:00
|
|
|
do {
|
|
|
|
|
try PasswordStore.shared.pullRepository(transferProgressBlock: {(git_transfer_progress, stop) in
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Pull Remote Repository")
|
|
|
|
|
}
|
|
|
|
|
})
|
2017-02-15 16:51:12 +08:00
|
|
|
if numberOfUnsyncedPasswords > 0 {
|
2017-02-12 13:31:29 +08:00
|
|
|
try PasswordStore.shared.pushRepository(transferProgressBlock: {(current, total, bytes, stop) in
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showProgress(Float(current)/Float(total), status: "Push Remote Repository")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-01-23 17:36:10 +08:00
|
|
|
DispatchQueue.main.async {
|
2017-02-15 16:51:12 +08:00
|
|
|
PasswordStore.shared.updatePasswordEntityCoreData()
|
2017-02-04 15:15:57 +08:00
|
|
|
self.passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
|
|
|
|
self.reloadTableView(data: self.passwordEntities!)
|
2017-02-12 01:59:40 +08:00
|
|
|
PasswordStore.shared.setAllSynced()
|
|
|
|
|
self.setNavigationItemTitle()
|
2017-02-06 19:13:33 +08:00
|
|
|
Defaults[.lastUpdatedTime] = Date()
|
2017-02-17 21:06:28 +08:00
|
|
|
Defaults[.gitRepositoryPasswordAttempts] = 0
|
2017-01-23 17:36:10 +08:00
|
|
|
SVProgressHUD.showSuccess(withStatus: "Done")
|
|
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
|
|
|
}
|
2017-02-04 14:24:59 +08:00
|
|
|
} catch {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
2017-02-09 11:17:36 +08:00
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
2017-02-04 14:24:59 +08:00
|
|
|
}
|
2017-01-23 16:29:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 21:15:47 +08:00
|
|
|
override func viewDidLoad() {
|
|
|
|
|
super.viewDidLoad()
|
2017-02-12 01:59:40 +08:00
|
|
|
setNavigationItemTitle()
|
2017-01-19 21:15:47 +08:00
|
|
|
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
2017-02-03 13:20:03 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordUpdatedNotification), name: NSNotification.Name(rawValue: "passwordUpdated"), object: nil)
|
2017-02-07 16:45:14 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordStoreErasedNotification), name: NSNotification.Name(rawValue: "passwordStoreErased"), object: nil)
|
2017-02-08 12:47:05 +08:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnSearchNotification), name: NSNotification.Name(rawValue: "search"), object: nil)
|
2017-02-07 16:45:14 +08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
generateSections(item: passwordEntities!)
|
|
|
|
|
tableView.delegate = self
|
|
|
|
|
tableView.dataSource = self
|
2017-01-23 12:48:20 +08:00
|
|
|
searchController.searchResultsUpdater = self
|
|
|
|
|
searchController.dimsBackgroundDuringPresentation = false
|
2017-02-07 17:24:23 +08:00
|
|
|
searchController.searchBar.isTranslucent = false
|
|
|
|
|
searchController.searchBar.backgroundColor = UIColor.gray
|
2017-02-03 13:01:41 +08:00
|
|
|
searchController.searchBar.sizeToFit()
|
2017-01-23 12:48:20 +08:00
|
|
|
definesPresentationContext = true
|
2017-02-03 13:01:41 +08:00
|
|
|
searchBarView.addSubview(searchController.searchBar)
|
|
|
|
|
view.addSubview(searchBarView)
|
2017-02-04 11:30:57 +08:00
|
|
|
tableView.insertSubview(refreshControl, at: 0)
|
2017-02-06 10:55:24 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
2017-02-06 19:13:33 +08:00
|
|
|
updateRefreshControlTitle()
|
2017-02-02 15:03:34 +08:00
|
|
|
}
|
|
|
|
|
|
2017-02-03 14:20:52 +08:00
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
|
|
super.viewWillAppear(animated)
|
|
|
|
|
if let path = tableView.indexPathForSelectedRow {
|
|
|
|
|
tableView.deselectRow(at: path, animated: false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-07 13:23:18 +08:00
|
|
|
override func viewDidLayoutSubviews() {
|
|
|
|
|
super.viewDidLayoutSubviews()
|
|
|
|
|
searchBarView.frame = CGRect(x: 0, y: navigationController!.navigationBar.bounds.size.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width, height: 44)
|
|
|
|
|
searchController.searchBar.sizeToFit()
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
2017-02-02 15:03:34 +08:00
|
|
|
return sections.count
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
|
|
|
return sections[section].length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
|
|
|
|
var password: PasswordEntity
|
|
|
|
|
let index = sections[indexPath.section].index + indexPath.row
|
|
|
|
|
if searchController.isActive && searchController.searchBar.text != "" {
|
|
|
|
|
password = filteredPasswordEntities[index]
|
|
|
|
|
} else {
|
|
|
|
|
password = passwordEntities![index]
|
|
|
|
|
}
|
2017-02-12 01:59:40 +08:00
|
|
|
if password.synced {
|
|
|
|
|
cell.textLabel?.text = password.name!
|
|
|
|
|
} else {
|
|
|
|
|
cell.textLabel?.text = "↻ \(password.name!)"
|
|
|
|
|
}
|
2017-02-08 01:29:00 +08:00
|
|
|
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
2017-02-08 10:40:48 +08:00
|
|
|
longPressGestureRecognizer.minimumPressDuration = 0.6
|
2017-02-08 01:29:00 +08:00
|
|
|
cell.addGestureRecognizer(longPressGestureRecognizer)
|
2017-02-03 13:01:41 +08:00
|
|
|
return cell
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-08 01:29:00 +08:00
|
|
|
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
|
|
|
|
if gesture.state == UIGestureRecognizerState.began {
|
|
|
|
|
let touchPoint = gesture.location(in: tableView)
|
|
|
|
|
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
|
|
|
|
|
copyToPasteboard(from: indexPath)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
|
|
|
return sections[section].title
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
|
|
|
|
return sections.map { $0.title }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
|
|
|
|
return index
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-04 11:30:57 +08:00
|
|
|
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
2017-02-08 01:29:00 +08:00
|
|
|
copyToPasteboard(from: indexPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func copyToPasteboard(from indexPath: IndexPath) {
|
2017-02-15 11:26:22 +08:00
|
|
|
if Defaults[.pgpKeyID] == nil {
|
2017-02-16 00:54:42 +08:00
|
|
|
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
2017-02-15 11:26:22 +08:00
|
|
|
return
|
|
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
let index = sections[indexPath.section].index + indexPath.row
|
|
|
|
|
let password: PasswordEntity
|
|
|
|
|
if searchController.isActive && searchController.searchBar.text != "" {
|
|
|
|
|
password = filteredPasswordEntities[index]
|
|
|
|
|
} else {
|
|
|
|
|
password = passwordEntities![index]
|
|
|
|
|
}
|
2017-02-08 19:18:10 +08:00
|
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
2017-02-20 16:07:32 +08:00
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
2017-02-08 19:18:10 +08:00
|
|
|
SVProgressHUD.setDefaultStyle(.dark)
|
|
|
|
|
SVProgressHUD.show(withStatus: "Decrypting")
|
|
|
|
|
DispatchQueue.global(qos: .userInteractive).async {
|
|
|
|
|
var decryptedPassword: Password?
|
|
|
|
|
do {
|
|
|
|
|
decryptedPassword = try password.decrypt()!
|
2017-02-15 11:37:19 +08:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
UIPasteboard.general.string = decryptedPassword?.password
|
|
|
|
|
SVProgressHUD.showSuccess(withStatus: "Password Copied")
|
|
|
|
|
SVProgressHUD.dismiss(withDelay: 0.6)
|
|
|
|
|
}
|
2017-02-08 19:18:10 +08:00
|
|
|
} catch {
|
|
|
|
|
print(error)
|
2017-02-15 11:37:19 +08:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
|
|
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
|
|
|
}
|
2017-02-08 19:18:10 +08:00
|
|
|
}
|
2017-02-06 14:28:57 +08:00
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
}
|
|
|
|
|
|
2017-02-02 15:03:34 +08:00
|
|
|
func generateSections(item: [PasswordEntity]) {
|
|
|
|
|
sections.removeAll()
|
|
|
|
|
if item.count == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var index = 0
|
|
|
|
|
for i in 0 ..< item.count {
|
|
|
|
|
let name = item[index].name!.uppercased()
|
|
|
|
|
let commonPrefix = item[i].name!.commonPrefix(with: name, options: .caseInsensitive)
|
|
|
|
|
if commonPrefix.characters.count == 0 {
|
|
|
|
|
let firstCharacter = name[name.startIndex]
|
|
|
|
|
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
|
|
|
|
|
sections.append(newSection)
|
|
|
|
|
index = i
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let name = item[index].name!.uppercased()
|
|
|
|
|
let firstCharacter = name[name.startIndex]
|
|
|
|
|
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
|
|
|
|
|
sections.append(newSection)
|
2017-01-23 12:48:20 +08:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 21:15:47 +08:00
|
|
|
func actOnPasswordUpdatedNotification() {
|
|
|
|
|
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
2017-02-04 11:30:57 +08:00
|
|
|
reloadTableView(data: passwordEntities!)
|
2017-02-12 01:59:40 +08:00
|
|
|
setNavigationItemTitle()
|
|
|
|
|
}
|
|
|
|
|
private func setNavigationItemTitle() {
|
|
|
|
|
let numberOfUnsynced = PasswordStore.shared.getNumberOfUnsyncedPasswords()
|
|
|
|
|
if numberOfUnsynced == 0 {
|
|
|
|
|
navigationItem.title = "Password Store"
|
|
|
|
|
} else {
|
|
|
|
|
navigationItem.title = "Password Store (\(numberOfUnsynced))"
|
|
|
|
|
}
|
2017-01-19 21:15:47 +08:00
|
|
|
}
|
2017-02-06 10:55:24 +08:00
|
|
|
|
2017-02-07 16:45:14 +08:00
|
|
|
func actOnPasswordStoreErasedNotification() {
|
|
|
|
|
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
|
|
|
|
reloadTableView(data: passwordEntities!)
|
2017-02-20 16:58:15 +08:00
|
|
|
setNavigationItemTitle()
|
2017-02-07 16:45:14 +08:00
|
|
|
}
|
2017-02-08 12:47:05 +08:00
|
|
|
|
|
|
|
|
func actOnSearchNotification() {
|
|
|
|
|
searchController.searchBar.becomeFirstResponder()
|
|
|
|
|
}
|
2017-02-07 16:45:14 +08:00
|
|
|
|
|
|
|
|
|
2017-02-06 10:55:24 +08:00
|
|
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
|
|
|
|
if identifier == "showPasswordDetail" {
|
2017-02-15 11:26:22 +08:00
|
|
|
if Defaults[.pgpKeyID] == nil {
|
2017-02-16 00:54:42 +08:00
|
|
|
Utils.alert(title: "Cannot Show Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
2017-02-06 10:55:24 +08:00
|
|
|
if let s = sender as? UITableViewCell {
|
|
|
|
|
let selectedIndexPath = tableView.indexPath(for: s)!
|
|
|
|
|
tableView.deselectRow(at: selectedIndexPath, animated: true)
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
2017-02-03 13:01:41 +08:00
|
|
|
|
2017-01-22 01:42:36 +08:00
|
|
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
|
|
|
|
if segue.identifier == "showPasswordDetail" {
|
2017-02-02 21:04:31 +08:00
|
|
|
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
2017-02-02 15:03:34 +08:00
|
|
|
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
|
|
|
|
let index = sections[selectedIndexPath.section].index + selectedIndexPath.row
|
2017-02-06 14:28:57 +08:00
|
|
|
let passwordEntity: PasswordEntity
|
2017-01-23 12:48:20 +08:00
|
|
|
if searchController.isActive && searchController.searchBar.text != "" {
|
2017-02-06 14:28:57 +08:00
|
|
|
passwordEntity = filteredPasswordEntities[index]
|
2017-01-23 12:48:20 +08:00
|
|
|
} else {
|
2017-02-06 14:28:57 +08:00
|
|
|
passwordEntity = passwordEntities![index]
|
2017-01-23 12:48:20 +08:00
|
|
|
}
|
2017-02-06 14:28:57 +08:00
|
|
|
viewController.passwordEntity = passwordEntity
|
2017-02-06 21:53:54 +08:00
|
|
|
let passwordCategoryEntities = PasswordStore.shared.fetchPasswordCategoryEntityCoreData(password: passwordEntity)
|
|
|
|
|
viewController.passwordCategoryEntities = passwordCategoryEntities
|
2017-01-22 01:42:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
|
|
|
|
filteredPasswordEntities = passwordEntities!.filter { password in
|
|
|
|
|
return password.name!.lowercased().contains(searchText.lowercased())
|
|
|
|
|
}
|
|
|
|
|
if searchController.isActive && searchController.searchBar.text != "" {
|
2017-02-04 11:30:57 +08:00
|
|
|
reloadTableView(data: filteredPasswordEntities)
|
2017-02-03 13:01:41 +08:00
|
|
|
} else {
|
2017-02-04 11:30:57 +08:00
|
|
|
reloadTableView(data: passwordEntities!)
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
}
|
|
|
|
|
|
2017-02-06 19:13:33 +08:00
|
|
|
func updateRefreshControlTitle() {
|
|
|
|
|
var atribbutedTitle = "Pull to Sync Password Store"
|
2017-02-10 15:32:01 +08:00
|
|
|
atribbutedTitle = "Last Synced: \(Utils.getLastUpdatedTimeString())"
|
2017-02-06 19:13:33 +08:00
|
|
|
refreshControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-04 11:30:57 +08:00
|
|
|
func reloadTableView (data: [PasswordEntity]) {
|
|
|
|
|
generateSections(item: data)
|
2017-02-03 13:01:41 +08:00
|
|
|
tableView.reloadData()
|
2017-02-06 19:13:33 +08:00
|
|
|
updateRefreshControlTitle()
|
2017-02-03 13:01:41 +08:00
|
|
|
}
|
2017-02-04 11:30:57 +08:00
|
|
|
|
|
|
|
|
func handleRefresh(_ refreshControl: UIRefreshControl) {
|
2017-02-04 11:47:57 +08:00
|
|
|
syncPasswords()
|
2017-02-04 11:30:57 +08:00
|
|
|
refreshControl.endRefreshing()
|
|
|
|
|
}
|
2017-01-19 21:15:47 +08:00
|
|
|
}
|
2017-01-23 16:29:36 +08:00
|
|
|
|
2017-02-03 13:01:41 +08:00
|
|
|
extension PasswordsViewController: UISearchResultsUpdating {
|
2017-01-23 16:29:36 +08:00
|
|
|
func updateSearchResults(for searchController: UISearchController) {
|
|
|
|
|
filterContentForSearchText(searchText: searchController.searchBar.text!)
|
|
|
|
|
}
|
|
|
|
|
}
|