Merge branch 'release/0.2.4'

This commit is contained in:
Bob Sun 2017-04-09 15:30:23 -07:00
commit 9f2aa37b8d
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
38 changed files with 1036 additions and 554 deletions

View file

@ -3,5 +3,5 @@ github "radex/SwiftyUserDefaults"
github "libgit2/objective-git"
github "zahlz/SwiftPasscodeLock" "master"
github "bitserf/FavIcon"
github "kishikawakatsumi/KeychainAccess" "master"
github "kishikawakatsumi/KeychainAccess"
github "mattrubin/OneTimePassword"

View file

@ -8,10 +8,13 @@
/* Begin PBXBuildFile section */
94BA784B85E071D25EE89B59 /* libPods-pass.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */; };
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; };
A262A58D1E68749C006B0890 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = A27424D81E7C35960093F436 /* NotificationNames.swift */; };
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */; };
A2A89D691E954698003FB2D3 /* UITextFieldExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */; };
DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */; };
DC037CA81E4B898100609409 /* BasicStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA71E4B898100609409 /* BasicStaticTableViewController.swift */; };
DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */; };
@ -80,10 +83,13 @@
/* Begin PBXFileReference section */
274CCFCF32444A2FF46BE7F4 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = "<group>"; };
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = "<group>"; };
A262A58C1E68749C006B0890 /* Base32.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Base32.framework; path = Carthage/Build/iOS/Base32.framework; sourceTree = "<group>"; };
A27424D81E7C35960093F436 /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; };
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextFieldExtension.swift; sourceTree = "<group>"; };
ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; };
AEAD6B31EAF5D061447A68CC /* Pods-pass.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.release.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.release.xcconfig"; sourceTree = "<group>"; };
DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSourceComponentsTableViewController.swift; sourceTree = "<group>"; };
@ -215,6 +221,8 @@
DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */,
DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */,
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
);
path = Controllers;
sourceTree = "<group>";
@ -239,6 +247,7 @@
DCA049971E33586A00522E8F /* DefaultsKeys.swift */,
DC19400A1E4B36B60077E0A3 /* Utils.swift */,
A27424D81E7C35960093F436 /* NotificationNames.swift */,
A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -524,9 +533,11 @@
DC5F385B1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift in Sources */,
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */,
DCAAF7451E2FA66800AB94BC /* SettingsTableViewController.swift in Sources */,
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */,
DCFB77A71E502DF9008DE471 /* EditPasswordTableViewController.swift in Sources */,
DCA0499A1E335CC800522E8F /* GitServerSettingTableViewController.swift in Sources */,
DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */,
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */,
DCC277D21E30D6EA00402246 /* pass.xcdatamodeld in Sources */,
DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */,
DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */,
@ -544,6 +555,7 @@
DC1940001E49E1A60077E0A3 /* PasscodeLockConfiguration.swift in Sources */,
DC917BD71E2E8231000FDF54 /* AppDelegate.swift in Sources */,
DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */,
A2A89D691E954698003FB2D3 /* UITextFieldExtension.swift in Sources */,
DC037CBB1E4DD47B00609409 /* TextFieldTableViewCell.swift in Sources */,
DC193FFE1E49E0760077E0A3 /* PasscodeLockRepository.swift in Sources */,
DCA049981E33586A00522E8F /* DefaultsKeys.swift in Sources */,

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,6 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
private let passwordStore = PasswordStore.shared
override func viewDidLoad() {
navigationItemTitle = "About Repository"
super.viewDidLoad()
indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)

View file

@ -21,7 +21,6 @@ class AboutTableViewController: BasicStaticTableViewController {
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
]
navigationItemTitle = "About"
super.viewDidLoad()
}

View file

@ -19,6 +19,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password"],
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
[[.type: PasswordEditorCellType.textViewCell, .title: "additions"]],
[[.type: PasswordEditorCellType.scanQRCodeCell]]
]
super.viewDidLoad()
}
@ -54,13 +55,15 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveAddPasswordSegue" {let cells = tableView.visibleCells
super.prepare(for: segue, sender: sender)
if segue.identifier == "saveAddPasswordSegue" {
let cells = tableView.visibleCells
var cellContents = [String: String]()
for cell in cells {
let indexPath = tableView.indexPath(for: cell)!
let contentCell = cell as! ContentTableViewCell
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
if let cellContent = contentCell.getContent() {
if let indexPath = tableView.indexPath(for: cell),
let contentCell = cell as? ContentTableViewCell,
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
let cellContent = contentCell.getContent() {
cellContents[cellTitle] = cellContent
}
}

View file

@ -27,7 +27,6 @@ class AdvancedSettingsTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Advanced"
encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
encryptInASCIIArmoredTableViewCell.selectionStyle = .none

View file

@ -29,7 +29,9 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = navigationItemTitle
if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
@ -54,6 +56,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
switch cellDataStyle ?? .defaultStyle {
case .value1:
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
cell?.selectionStyle = .none
default:
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
}
@ -65,6 +68,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
cell?.accessoryType = accessoryType
} else {
cell?.accessoryType = .disclosureIndicator
cell?.selectionStyle = .default
}
cell?.textLabel?.text = cellData[CellDataKey.title] as? String

View file

@ -16,8 +16,8 @@ class CommitLogsTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
commits = passwordStore.getRecentCommits(count: 20)
navigationItem.title = "Recent Commit Logs"
navigationController!.navigationBar.topItem!.title = "About"
self.tableView.estimatedRowHeight = 50
self.tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@ -27,13 +27,16 @@ class CommitLogsTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.short
formatter.timeStyle = .none
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = .medium
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
let dateLabel = cell.viewWithTag(101) as! UILabel
let messageLabel = cell.viewWithTag(102) as! UILabel
dateLabel.text = dateString
messageLabel.text = commits[indexPath.row].message
let author = cell.contentView.viewWithTag(100) as? UILabel
let dateLabel = cell.contentView.viewWithTag(101) as? UILabel
let messageLabel = cell.contentView.viewWithTag(102) as? UILabel
author?.text = commits[indexPath.row].author?.name
dateLabel?.text = dateString
messageLabel?.text = commits[indexPath.row].message?.trimmingCharacters(in: .whitespacesAndNewlines)
return cell
}
}

View file

@ -15,7 +15,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
[[.type: PasswordEditorCellType.deletePasswordCell]],
[[.type: PasswordEditorCellType.scanQRCodeCell],
[.type: PasswordEditorCellType.deletePasswordCell]]
]
super.viewDidLoad()
}
@ -37,16 +38,16 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "saveEditPasswordSegue" {
let cells = tableView.visibleCells
var cellContents = [String: String]()
for cell in cells {
let indexPath = tableView.indexPath(for: cell)!
if let contentCell = cell as? ContentTableViewCell {
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
if let cellContent = contentCell.getContent() {
cellContents[cellTitle] = cellContent
}
if let indexPath = tableView.indexPath(for: cell),
let contentCell = cell as? ContentTableViewCell,
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
let cellContent = contentCell.getContent() {
cellContents[cellTitle] = cellContent
}
}
var plainText = ""

View file

@ -47,7 +47,6 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
}()
override func viewDidLoad() {
navigationItemTitle = "General"
tableData = [
// section 0
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],

View file

@ -9,12 +9,13 @@
import UIKit
import SwiftyUserDefaults
class GitSSHKeyArmorSettingTableViewController: UITableViewController {
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
@IBOutlet weak var armorPublicKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
var gitSSHPrivateKeyPassphrase: String?
let passwordStore = PasswordStore.shared
var doneBarButtonItem: UIBarButtonItem?
private var recentPastedText = ""
override func viewDidLoad() {
super.viewDidLoad()
@ -22,12 +23,8 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase
doneBarButtonItem = UIBarButtonItem(title: "Done",
style: UIBarButtonItemStyle.done,
target: self,
action: #selector(doneButtonTapped(_:)))
navigationItem.rightBarButtonItem = doneBarButtonItem
navigationItem.title = "SSH Key"
armorPublicKeyTextView.delegate = self
armorPrivateKeyTextView.delegate = self
}
private func createSavePassphraseAlert() -> UIAlertController {
@ -46,7 +43,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
return savePassphraseAlert
}
func doneButtonTapped(_ sender: Any) {
@IBAction func doneButtonTapped(_ sender: Any) {
Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text
Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
do {
@ -68,5 +65,17 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
self.present(alert, animated: true, completion: nil)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes somethint, get ready to clear in 10s
recentPastedText = text
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 10) { [weak weakSelf = self] in
if let pasteboardString = UIPasteboard.general.string,
pasteboardString == weakSelf?.recentPastedText {
UIPasteboard.general.string = ""
}
}
}
return true
}
}

View file

@ -0,0 +1,83 @@
//
// QRScannerController.swift
// pass
//
// Created by Yishi Lin on 10/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import UIKit
import AVFoundation
class OTPScannerController: QRScannerController {
var tempPassword: Password?
var scannedOTP: String?
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
override func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
supportedCodeTypes.contains(metadataObj.type),
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
// draw a bounds on the found QR code
qrCodeFrameView?.frame = barCodeObject.bounds
// check whether it is a valid result
if let scannedString = metadataObj.stringValue {
if let (accept, message) = delegate?.checkScannedOutput(line: scannedString) {
if accept == true {
captureSession?.stopRunning()
scannedOTP = scannedString
tempPassword = Password(name: "empty", plainText: scannedString)
// set scannerOutput
setupOneTimePasswordMessage()
} else {
scannerOutput.text = message
}
} else {
// no delegate, show the scanned result
scannerOutput.text = scannedString
}
} else {
scannerOutput.text = "No string value"
}
} else {
qrCodeFrameView?.frame = CGRect.zero
scannerOutput.text = "No QR code detected"
}
}
private func setupOneTimePasswordMessage() {
if let password = tempPassword {
if password.otpType == .hotp {
// hotp, no need to refresh
let (title, content) = password.getOtpStrings()!
scannerOutput.text = "\(title):\(content)"
} else if password.otpType == .totp {
// totp, refresh
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak weakSelf = self] timer in
let (title, content) = password.getOtpStrings()!
weakSelf?.scannerOutput.text = "\(title):\(content)"
}
}
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveAddScannedOTPSegue" {
return tempPassword != nil
}
return true
}
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// super.prepare(for: segue, sender: sender)
// if segue.identifier == "saveAddScannedOTPSegue" {
// delegate?.handleScannedOutput(line: scannedOTP)
// }
// }
}

View file

@ -44,7 +44,6 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
[CellDataKey.title: item[0], CellDataKey.action: "link", CellDataKey.link: item[1], CellDataKey.accessoryType: UITableViewCellAccessoryType.detailDisclosureButton, CellDataKey.detailDisclosureAction: #selector(actOnDetailDisclosureButton(_:)), CellDataKey.detailDisclosureData: item[2]]
)
}
navigationItemTitle = "Open Source Components"
super.viewDidLoad()
}

View file

@ -9,12 +9,14 @@
import UIKit
import SwiftyUserDefaults
class PGPKeyArmorSettingTableViewController: UITableViewController {
class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
@IBOutlet weak var armorPublicKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
var pgpPassphrase: String?
let passwordStore = PasswordStore.shared
private var recentPastedText = ""
override func viewDidLoad() {
super.viewDidLoad()
armorPublicKeyTextView.text = Defaults[.pgpPublicKeyArmor]
@ -22,7 +24,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
pgpPassphrase = passwordStore.pgpKeyPassphrase
}
private func createSavePassphraseAlert() -> UIAlertController {
private func createSavePassphraseAndSegueAlert() -> UIAlertController {
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
Defaults[.isRememberPassphraseOn] = false
@ -35,12 +37,26 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
return savePassphraseAlert
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "savePGPKeySegue" {
if armorPublicKeyTextView.text.isEmpty {
Utils.alert(title: "Cannot Save", message: "Please set public key first.", controller: self, completion: nil)
return false
}
if armorPrivateKeyTextView.text.isEmpty {
Utils.alert(title: "Cannot Save", message: "Please set private key first.", controller: self, completion: nil)
return false
}
}
return true
}
@IBAction func save(_ sender: Any) {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
let savePassphraseAlert = self.createSavePassphraseAlert()
self.present(savePassphraseAlert, animated: true, completion: nil)
let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase
@ -48,5 +64,18 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
})
self.present(alert, animated: true, completion: nil)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes something, get ready to clear in 10s
recentPastedText = text
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 10) { [weak weakSelf = self] in
if let pasteboardString = UIPasteboard.general.string,
pasteboardString == weakSelf?.recentPastedText {
UIPasteboard.general.string = ""
}
}
}
return true
}
}

View file

@ -24,6 +24,19 @@ class PGPKeySettingTableViewController: UITableViewController {
pgpPassphrase = passwordStore.pgpKeyPassphrase
}
private func createSavePassphraseAndSegueAlert() -> UIAlertController {
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
Defaults[.isRememberPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in
Defaults[.isRememberPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
return savePassphraseAlert
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "savePGPKeySegue" {
guard let pgpPublicKeyURL = URL(string: pgpPublicKeyURLTextField.text!) else {
@ -46,9 +59,8 @@ class PGPKeySettingTableViewController: UITableViewController {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
if self.shouldPerformSegue(withIdentifier: "savePGPKeySegue", sender: self) {
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}
let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase

View file

@ -386,11 +386,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
return;
}
// increase HOTP counter
password!.increaseHotpCounter()
// copy HOTP to pasteboard
if let plainPassword = password!.getOtp() {
// copy HOTP to pasteboard (will update counter)
if let plainPassword = password!.getNextHotp() {
Utils.copyToPasteboard(textToCopy: plainPassword)
}
@ -448,9 +445,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let titleData = tableDataItem.title
let contentData = tableDataItem.content
cell.delegatePasswordTableView = self
cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false)
cell.isURLCell = (titleData.lowercased() == "url" ? true : false)
cell.isHOTPCell = (titleData == "HMAC-based" ? true : false)
cell.cellData = LabelTableViewCellData(title: titleData, content: contentData)
cell.selectionStyle = .none
return cell

View file

@ -8,16 +8,17 @@
import UIKit
import SwiftyUserDefaults
import OneTimePassword
enum PasswordEditorCellType {
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell, scanQRCodeCell
}
enum PasswordEditorCellKey {
case type, title, content, placeholders
}
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, UIGestureRecognizerDelegate {
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate {
var tableData = [
[Dictionary<PasswordEditorCellKey, Any>]
@ -29,11 +30,13 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
private let passwordSection = 1
private let additionsSection = 2
private var hidePasswordSettings = true
private var fillPasswordCell: FillPasswordTableViewCell?
private var passwordLengthCell: SliderTableViewCell?
private var deletePasswordCell: UITableViewCell?
private var scanQRCodeCell: UITableViewCell?
override func loadView() {
super.loadView()
@ -42,11 +45,22 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
deletePasswordCell!.textLabel?.text = "Delete Password"
deletePasswordCell!.textLabel?.textColor = Globals.red
deletePasswordCell?.selectionStyle = .default
scanQRCodeCell = UITableViewCell(style: .default, reuseIdentifier: "default")
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
scanQRCodeCell?.textLabel?.textColor = Globals.blue
scanQRCodeCell?.selectionStyle = .default
// scanQRCodeCell?.imageView?.image = #imageLiteral(resourceName: "Camera").withRenderingMode(.alwaysTemplate)
// scanQRCodeCell?.imageView?.tintColor = Globals.blue
// scanQRCodeCell?.imageView?.contentMode = .scaleAspectFit
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = navigationItemTitle
if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
@ -83,6 +97,8 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
return passwordLengthCell!
case .deletePasswordCell:
return deletePasswordCell!
case .scanQRCodeCell:
return scanQRCodeCell!
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
@ -116,13 +132,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath) == deletePasswordCell {
let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == deletePasswordCell {
let alert = UIAlertController(title: "Delete Password?", message: nil, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Delete", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
self.performSegue(withIdentifier: "deletePasswordSegue", sender: self)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler:nil))
self.present(alert, animated: true, completion: nil)
} else if selectedCell == scanQRCodeCell {
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self)
}
tableView.deselectRow(at: indexPath, animated: true)
}
@ -165,4 +184,53 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
hidePasswordSettings = !hidePasswordSettings
tableView.reloadSections([passwordSection], with: .fade)
}
func insertScannedOTPFields(_ otpauth: String) {
// update tableData
if let additionsPlainText = (tableData[additionsSection][0][PasswordEditorCellKey.content] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines), additionsPlainText != "" {
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsPlainText + "\n" + otpauth
} else {
tableData[additionsSection][0][PasswordEditorCellKey.content] = otpauth
}
// reload
tableView.reloadSections([additionsSection], with: .none)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
if let url = URL(string: line), let _ = Token(url: url) {
return (accept: true, message: "Valid token URL")
} else {
return (accept: false, message: "Invalid token URL")
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
insertScannedOTPFields(line)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showQRScannerSegue" {
if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? QRScannerController {
viewController.delegate = self
}
} else if let viewController = segue.destination as? QRScannerController {
viewController.delegate = self
}
}
}
@IBAction private func cancelOTPScanner(segue: UIStoryboardSegue) {
}
@IBAction func saveScannedOTP(segue: UIStoryboardSegue) {
if let controller = segue.source as? OTPScannerController {
if let scannedOTP = controller.scannedOTP {
insertScannedOTPFields(scannedOTP)
}
}
}
}

View file

@ -11,25 +11,28 @@ import SVProgressHUD
import SwiftyUserDefaults
import PasscodeLock
enum PasswordsTableEntryType {
case password, dir
}
struct PasswordsTableEntry {
fileprivate class PasswordsTableEntry : NSObject {
var title: String
var isDir: Bool
var passwordEntity: PasswordEntity?
init(title: String, isDir: Bool, passwordEntity: PasswordEntity?) {
self.title = title
self.isDir = isDir
self.passwordEntity = passwordEntity
}
}
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate {
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableAllEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil
private let passwordStore = PasswordStore.shared
private var tapTabBarTime: TimeInterval = 0
private var sections : [(index: Int, length :Int, title: String)] = Array()
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
private var searchActive : Bool = false
private lazy var searchController: UISearchController = {
@ -80,8 +83,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private func initPasswordsTableEntries(parent: PasswordEntity?) {
passwordsTableEntries.removeAll()
passwordsTableAllEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
var passwordEntities = [PasswordEntity]()
var passwordAllEntities = [PasswordEntity]()
if Defaults[.isShowFolderOn] {
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
} else {
@ -90,6 +95,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
passwordsTableEntries = passwordEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
}
passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableAllEntries = passwordAllEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
}
parentPasswordEntity = parent
}
@ -149,17 +158,31 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
} catch {
DispatchQueue.main.async {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
SVProgressHUD.dismiss()
self.syncControl.endRefreshing()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
}
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if Defaults[.isShowFolderOn] {
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
} else {
searchController.searchBar.scopeButtonTitles = nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
tabBarController!.delegate = self
searchController.searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
definesPresentationContext = true
@ -196,13 +219,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].length
return sections[section].entries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longPressGestureRecognizer.minimumPressDuration = 0.6
if Defaults[.isShowFolderOn] {
if Defaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath)
@ -241,14 +264,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
var entry: PasswordsTableEntry
let index = sections[indexPath.section].index + indexPath.row
if searchController.isActive && searchController.searchBar.text != "" {
entry = filteredPasswordsTableEntries[index]
} else {
entry = passwordsTableEntries[index]
}
return entry
return sections[indexPath.section].entries[indexPath.row]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -268,7 +284,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
func backAction(_ sender: Any?) {
guard Defaults[.isShowFolderOn] else { return }
reloadTableView(parent: parentPasswordEntity?.parent, anim: transitionFromLeft)
var anim: CATransition? = transitionFromLeft
if parentPasswordEntity == nil {
anim = nil
}
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
}
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
@ -345,25 +365,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
private func generateSections(item: [PasswordsTableEntry]) {
sections.removeAll()
guard item.count != 0 else {
return
let collation = UILocalizedIndexedCollation.current()
let sectionTitles = collation.sectionIndexTitles
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
// initialize all sections
for i in 0..<sectionTitles.count {
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
}
var index = 0
for i in 0 ..< item.count {
let title = item[index].title.uppercased()
let commonPrefix = item[i].title.commonPrefix(with: title, options: .caseInsensitive)
if commonPrefix.characters.count == 0 {
let firstCharacter = title[title.startIndex]
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
sections.append(newSection)
index = i
}
// put entries into sections
for entry in item {
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[sectionNumber].entries.append(entry)
}
let title = item[index].title.uppercased()
let firstCharacter = title[title.startIndex]
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
sections.append(newSection)
// sort each list and set sectionTitles
for i in 0..<sectionTitles.count {
let entriesToSort = newSections[i].entries
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
}
// only keep non-empty sections
sections = newSections.filter {$0.entries.count > 0}
}
func actOnSearchNotification() {
@ -381,6 +406,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
return false
}
} else if identifier == "addPasswordSegue" {
guard self.passwordStore.publicKey != nil, self.passwordStore.storeRepository != nil else {
Utils.alert(title: "Cannot Add Password", message: "Please make sure PGP Key and Git Server are properly set.", controller: self, completion: nil)
return false
}
}
return true
}
@ -396,14 +426,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
return entry.title.lowercased().contains(searchText.lowercased())
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableEntries)
switch scope {
case "All":
filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in
return entry.title.lowercased().contains(searchText.lowercased())
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableAllEntries)
}
case "Current":
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
return entry.title.lowercased().contains(searchText.lowercased())
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableEntries)
}
default:
break
}
}
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
@ -466,10 +512,22 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
backAction(self)
}
}
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResults(for: searchController)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchController.searchBar.selectedScopeButtonIndex = 0
updateSearchResults(for: searchController)
}
}
extension PasswordsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
var scope = "All"
if let scopeButtonTitles = searchController.searchBar.scopeButtonTitles {
scope = scopeButtonTitles[searchController.searchBar.selectedScopeButtonIndex]
}
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
}
}

View file

@ -0,0 +1,128 @@
//
// QRScannerController.swift
// pass
//
// Created by Yishi Lin on 7/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import UIKit
import AVFoundation
import OneTimePassword
import SVProgressHUD
protocol QRScannerControllerDelegate {
func checkScannedOutput(line: String) -> (accept: Bool, message: String)
func handleScannedOutput(line: String)
}
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
@IBOutlet weak var scannerOutput: UILabel!
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
let supportedCodeTypes = [AVMetadataObjectTypeQRCode]
var delegate: QRScannerControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the front
scannerOutput.layer.cornerRadius = 10
scannerOutput.text = "No QR code detected"
view.bringSubview(toFront: scannerOutput)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
} catch {
print(error)
return
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
supportedCodeTypes.contains(metadataObj.type),
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
// draw a bounds on the found QR code
qrCodeFrameView?.frame = barCodeObject.bounds
// check whether it is a valid result
if let scanned = metadataObj.stringValue {
if let (accept, message) = delegate?.checkScannedOutput(line: scanned) {
scannerOutput.text = message
if accept == true {
captureSession?.stopRunning()
delegate?.handleScannedOutput(line: scanned)
DispatchQueue.main.async {
SVProgressHUD.showSuccess(withStatus: "Done")
SVProgressHUD.dismiss(withDelay: 1)
self.navigationController?.popViewController(animated: true)
}
}
} else {
// no delegate, show the scanned result
scannerOutput.text = scanned
}
} else {
scannerOutput.text = "No string value"
}
} else {
qrCodeFrameView?.frame = CGRect.zero
scannerOutput.text = "No QR code detected"
}
}
}

View file

@ -15,7 +15,9 @@ class RawPasswordViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Raw"
navigationItem.title = password?.name
rawPasswordTextView.textContainer.lineFragmentPadding = 0
rawPasswordTextView.textContainerInset = .zero
rawPasswordTextView.text = password?.plainText
}
}

View file

@ -20,17 +20,10 @@ class SSHKeySettingTableViewController: UITableViewController {
super.viewDidLoad()
privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.absoluteString
publicKeyURLTextField.text = Defaults[.gitSSHPublicKeyURL]?.absoluteString
var doneBarButtonItem: UIBarButtonItem?
doneBarButtonItem = UIBarButtonItem(title: "Done",
style: UIBarButtonItemStyle.done,
target: self,
action: #selector(doneButtonTapped(_:)))
navigationItem.rightBarButtonItem = doneBarButtonItem
navigationItem.title = "SSH Key"
}
func doneButtonTapped(_ sender: UIButton) {
@IBAction func doneButtonTapped(_ sender: UIButton) {
guard let publicKeyURL = URL(string: publicKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil)
return

View file

@ -27,7 +27,6 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
[CellDataKey.action: "link", CellDataKey.title: item[0], CellDataKey.link: item[1]]
)
}
navigationItemTitle = "Special Thanks"
super.viewDidLoad()
}
}

View file

@ -27,6 +27,7 @@ class Globals {
"Apple": (min: 15, max: 15, def: 15)]
static let passwordDots = "••••••••••••"
static let oneTimePasswordDots = "••••••"
static let passwordFonts = "Menlo"
// UI related

View file

@ -0,0 +1,34 @@
//
// UIViewControllerExtionsion.swift
// pass
//
// Created by Yishi Lin on 5/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import Foundation
import UIKit
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
@IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.nextField != nil {
textField.nextField?.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
}

View file

@ -50,7 +50,7 @@ class Utils {
static func randomString(length: Int) -> String {
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
let len = UInt32(letters.length)
var randomString = ""

View file

@ -17,13 +17,15 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2.3</string>
<string>0.2.4</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>We need to access your camera for scanning QR code.</string>
<key>UIApplicationShortcutItems</key>
<array>
<dict>

View file

@ -61,21 +61,20 @@ class Password {
private func initEverything(name: String, plainText: String) {
self.name = name
self.plainText = plainText
self.additions.removeAll()
self.additionKeys.removeAll()
// get password and additional fields
let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
$0 == "\n" || $0 == "\r\n"
}.map(String.init)
guard plainTextSplit.count > 0 else {
return;
}
self.password = plainTextSplit[0]
self.password = plainTextSplit.first ?? ""
if plainTextSplit.count == 2 {
(self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1])
}
// check whether the first line of the plainText looks like an otp entry
let (key, value) = Password.getKeyValuePair(from: plainTextSplit[0])
let (key, value) = Password.getKeyValuePair(from: self.password)
if Password.otpKeywords.contains(key ?? "") {
firstLineIsOTPField = true
self.additions[key!] = value
@ -99,12 +98,16 @@ class Password {
// return a key-value pair from the line
// key might be nil, if there is no ":" in the line
static private func getKeyValuePair(from line: String) -> (key: String?, value: String) {
let items = line.characters.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false).map{String($0).trimmingCharacters(in: .whitespaces)}
let items = line.components(separatedBy: ": ").map{String($0).trimmingCharacters(in: .whitespaces)}
var key : String? = nil
var value = ""
if items.count == 1 || (items[0].isEmpty && items[1].isEmpty) {
// no ":" found, or empty on both sides of ":" (e.g., " : ")
// no ": " found, or empty on both sides of ": "
value = line
// otpauth special case
if value.hasPrefix("otpauth://") {
key = "otpauth"
}
} else {
if !items[0].isEmpty {
key = items[0]
@ -265,21 +268,6 @@ class Password {
}
}
// it is guaranteed that it is a HOTP password when we call this
func increaseHotpCounter() {
var lines : [String] = []
self.plainText.enumerateLines() { line, _ in
let (key, value) = Password.getKeyValuePair(from: line)
if key == "otp_counter", let newValue = UInt64(value)?.advanced(by: 1) {
let newLine = "\(key!): \(newValue)"
lines.append(newLine)
} else {
lines.append(line)
}
}
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
}
// return the description and the password strings
func getOtpStrings() -> (description: String, otp: String)? {
guard let token = self.otpToken else {
@ -309,6 +297,37 @@ class Password {
}
}
// return the password strings
// it is guaranteed that it is a HOTP password when we call this
func getNextHotp() -> String? {
// increase the counter
otpToken = otpToken?.updatedToken()
// replace old HOTP settings with the new otpauth
var newOtpauth = try! otpToken?.toURL().absoluteString
newOtpauth?.append("&secret=")
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
var lines : [String] = []
self.plainText.enumerateLines() { line, _ in
let (key, _) = Password.getKeyValuePair(from: line)
if !Password.otpKeywords.contains(key ?? "") {
lines.append(line)
} else if key == "otpauth" && newOtpauth != nil {
lines.append(newOtpauth!)
// set to nil to prevent duplication
newOtpauth = nil
}
}
if newOtpauth != nil {
lines.append(newOtpauth!)
}
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
// get and return the password
return self.otpToken?.currentPassword
}
static func LooksLikeOTP(line: String) -> Bool {
let (key, _) = getKeyValuePair(from: line)
return Password.otpKeywords.contains(key ?? "")

View file

@ -220,27 +220,21 @@ class PasswordStore {
}
public func initPGPKey(_ keyType: PGPKeyType) throws {
var keyPath = ""
switch keyType {
case .public:
keyPath = Globals.pgpPublicKeyPath
case .secret:
keyPath = Globals.pgpPrivateKeyPath
default:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
}
if let key = importKey(from: keyPath) {
switch keyType {
case .public:
self.publicKey = key
case .secret:
self.privateKey = key
default:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
let keyPath = Globals.pgpPublicKeyPath
self.publicKey = importKey(from: keyPath)
if self.publicKey == nil {
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import the public PGP key."])
}
} else {
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
case .secret:
let keyPath = Globals.pgpPrivateKeyPath
self.privateKey = importKey(from: keyPath)
if self.privateKey == nil {
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import the private PGP key."])
}
default:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key: unknown PGP key type."])
}
}
@ -272,9 +266,7 @@ class PasswordStore {
let fm = FileManager.default
if fm.fileExists(atPath: keyPath) {
if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] {
if keys.count > 0 {
return keys[0]
}
return keys.first
}
}
return nil

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="90" id="KGk-i7-Jjw" customClass="FillPasswordTableViewCell" customModule="pass" customModuleProvider="target">
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="90" id="KGk-i7-Jjw" customClass="FillPasswordTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="90"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="89.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="password" textAlignment="natural" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="k0U-2N-YaX" userLabel="Password">
<rect key="frame" x="16" y="8" width="231" height="74"/>
<rect key="frame" x="15" y="11" width="225" height="68"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="9gX-VT-F9P"/>
</constraints>
@ -32,7 +32,7 @@
</connections>
</textField>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SZJ-aY-45Y" userLabel="Setting">
<rect key="frame" x="275" y="8" width="30" height="73.5"/>
<rect key="frame" x="275" y="11" width="30" height="68"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="D9D-FC-ANz"/>
</constraints>
@ -43,7 +43,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hTh-ek-Xam" userLabel="Generate">
<rect key="frame" x="245" y="8" width="30" height="73.5"/>
<rect key="frame" x="245" y="11" width="30" height="68"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="l0l-7B-Tws"/>
</constraints>
@ -56,7 +56,7 @@
</subviews>
<constraints>
<constraint firstItem="SZJ-aY-45Y" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="5XP-xZ-YWY"/>
<constraint firstAttribute="trailingMargin" secondItem="SZJ-aY-45Y" secondAttribute="trailing" constant="7" id="5bp-bm-9vv"/>
<constraint firstAttribute="trailingMargin" secondItem="SZJ-aY-45Y" secondAttribute="trailing" id="5bp-bm-9vv"/>
<constraint firstAttribute="bottomMargin" secondItem="SZJ-aY-45Y" secondAttribute="bottom" id="CMF-1f-MdG"/>
<constraint firstItem="hTh-ek-Xam" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="OjQ-mD-CWV"/>
<constraint firstItem="SZJ-aY-45Y" firstAttribute="leading" secondItem="hTh-ek-Xam" secondAttribute="trailing" id="UlR-NB-9So"/>
@ -64,7 +64,7 @@
<constraint firstAttribute="trailingMargin" secondItem="k0U-2N-YaX" secondAttribute="trailing" constant="65" id="fz0-HW-sCe"/>
<constraint firstAttribute="bottomMargin" secondItem="k0U-2N-YaX" secondAttribute="bottom" id="lRv-MC-Cru"/>
<constraint firstItem="k0U-2N-YaX" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="sUk-sk-JLT"/>
<constraint firstItem="k0U-2N-YaX" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="zGn-an-SlK"/>
<constraint firstItem="k0U-2N-YaX" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="zGn-an-SlK"/>
</constraints>
</tableViewCellContentView>
<connections>

View file

@ -9,7 +9,6 @@
import UIKit
import SVProgressHUD
struct LabelTableViewCellData {
var title: String
var content: String
@ -19,36 +18,53 @@ class LabelTableViewCell: UITableViewCell {
@IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
private enum CellType {
case password, URL, HOTP, other
}
var isPasswordCell = false
var isURLCell = false
var isReveal = false
var isHOTPCell = false
private var type = CellType.other
private var isReveal = false
weak var delegatePasswordTableView : PasswordDetailTableViewController?
var passwordDisplayButton: UIButton?
var buttons: UIView?
private var passwordDisplayButton: UIButton?
private var buttons: UIView?
var cellData: LabelTableViewCellData? {
didSet {
titleLabel.text = cellData?.title ?? ""
if isPasswordCell {
guard let title = cellData?.title, let content = cellData?.content else {
type = .other
return
}
titleLabel.text = title
switch title.lowercased() {
case "password":
type = .password
if isReveal {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "")
contentLabel.attributedText = Utils.attributedPassword(plainPassword: content)
} else {
contentLabel.text = Globals.passwordDots
if content == "" {
contentLabel.text = ""
} else {
contentLabel.text = Globals.passwordDots
}
}
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else if isHOTPCell {
case "hmac-based":
type = .HOTP
if isReveal {
contentLabel.text = cellData?.content ?? ""
contentLabel.text = content
} else {
contentLabel.text = Globals.passwordDots
contentLabel.text = Globals.oneTimePasswordDots
}
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else {
contentLabel.text = cellData?.content
case "url":
type = .URL
contentLabel.text = content
default:
type = .other
contentLabel.text = content
}
updateButtons()
}
@ -76,46 +92,51 @@ class LabelTableViewCell: UITableViewCell {
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if isPasswordCell {
switch type {
case .password:
if isReveal {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:))
return action == #selector(copy(_:)) || action == #selector(concealPassword(_:))
} else {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:))
return action == #selector(copy(_:)) || action == #selector(revealPassword(_:))
}
}
if isURLCell {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.openLink(_:))
}
if isHOTPCell {
case .URL:
return action == #selector(copy(_:)) || action == #selector(openLink(_:))
case .HOTP:
if isReveal {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
return action == #selector(copy(_:)) || action == #selector(concealPassword(_:)) || action == #selector(getNextHOTP(_:))
} else {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
return action == #selector(copy(_:)) || action == #selector(revealPassword(_:)) || action == #selector(getNextHOTP(_:))
}
default:
return action == #selector(copy(_:))
}
return action == #selector(copy(_:))
}
override func copy(_ sender: Any?) {
Utils.copyToPasteboard(textToCopy: cellData?.content)
}
func revealPassword(_ sender: Any?) {
if let plainPassword = cellData?.content {
if isHOTPCell {
contentLabel.text = plainPassword
} else {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
}
let plainPassword = cellData?.content ?? ""
if type == .password {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
} else {
contentLabel.text = ""
contentLabel.text = plainPassword
}
isReveal = true
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
}
func concealPassword(_ sender: Any?) {
contentLabel.text = Globals.passwordDots
if type == .password {
if cellData?.content.isEmpty == false {
contentLabel.text = Globals.passwordDots
} else {
contentLabel.text = ""
}
} else {
contentLabel.text = Globals.oneTimePasswordDots
}
isReveal = false
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
}
@ -130,7 +151,6 @@ class LabelTableViewCell: UITableViewCell {
}
}
func openLink(_ sender: Any?) {
// if isURLCell, passwordTableView should not be nil
delegatePasswordTableView!.openLink()
@ -141,10 +161,7 @@ class LabelTableViewCell: UITableViewCell {
delegatePasswordTableView!.getNextHOTP()
}
func updateButtons() {
passwordDisplayButton = nil
buttons = nil
private func updateButtons() {
// total width and height of a button
let height = min(self.bounds.height, 36.0)
let width = max(height * 0.8, Globals.tableCellButtonSize)
@ -153,16 +170,19 @@ class LabelTableViewCell: UITableViewCell {
let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0)
let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
if isPasswordCell {
// password button
passwordDisplayButton = UIButton(type: .system)
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
buttons = passwordDisplayButton
} else if isHOTPCell {
switch type {
case .password:
if let content = cellData?.content, content != "" {
// password button
passwordDisplayButton = UIButton(type: .system)
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
buttons = passwordDisplayButton
}
case .HOTP:
// hotp button
let nextButton = UIButton(type: .system)
nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height)
@ -184,6 +204,9 @@ class LabelTableViewCell: UITableViewCell {
buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height)
buttons!.addSubview(nextButton)
buttons!.addSubview(passwordDisplayButton!)
default:
passwordDisplayButton = nil
buttons = nil
}
}
}

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="labelCell" id="KGk-i7-Jjw" customClass="LabelTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="333" height="52"/>
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="labelCell" rowHeight="59" id="KGk-i7-Jjw" customClass="LabelTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="333" height="59"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="333" height="51.5"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="333" height="58.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yyr-cF-QN0">
<rect key="frame" x="16" y="22.5" width="309" height="21"/>
<rect key="frame" x="15" y="27" width="303" height="21"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="pgw-DF-LQa"/>
</constraints>
@ -29,15 +29,15 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dqz-7n-uEZ">
<rect key="frame" x="16" y="8" width="309" height="11.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<rect key="frame" x="15" y="11" width="303" height="13"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<color key="textColor" red="0.0" green="0.47843137254901957" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="Dqz-7n-uEZ" secondAttribute="trailing" id="228-VM-Z8Y"/>
<constraint firstItem="Dqz-7n-uEZ" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="CVm-uc-iVo"/>
<constraint firstItem="Dqz-7n-uEZ" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="CVm-uc-iVo"/>
<constraint firstAttribute="topMargin" secondItem="Dqz-7n-uEZ" secondAttribute="top" id="N4y-iT-CiY"/>
<constraint firstItem="yyr-cF-QN0" firstAttribute="leading" secondItem="Dqz-7n-uEZ" secondAttribute="leading" id="TpW-bu-QIx"/>
<constraint firstItem="yyr-cF-QN0" firstAttribute="width" secondItem="Dqz-7n-uEZ" secondAttribute="width" id="ZXa-fK-0Bg"/>
@ -49,7 +49,7 @@
<outlet property="contentLabel" destination="yyr-cF-QN0" id="lhr-oA-ofC"/>
<outlet property="titleLabel" destination="Dqz-7n-uEZ" id="Hac-od-A7K"/>
</connections>
<point key="canvasLocation" x="27.5" y="58"/>
<point key="canvasLocation" x="15.5" y="89.5"/>
</tableViewCell>
</objects>
</document>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -13,28 +13,28 @@
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="passwordDetailTitleTableViewCell" rowHeight="84" id="KGk-i7-Jjw" customClass="PasswordDetailTitleTableViewCell" customModule="pass" customModuleProvider="target">
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="passwordDetailTitleTableViewCell" rowHeight="84" id="KGk-i7-Jjw" customClass="PasswordDetailTitleTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="84"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="83.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" placeholderIntrinsicWidth="52" placeholderIntrinsicHeight="52" translatesAutoresizingMaskIntoConstraints="NO" id="gKV-Cd-wIk">
<rect key="frame" x="16" y="16" width="52" height="52"/>
<rect key="frame" x="15" y="15" width="54" height="54"/>
<constraints>
<constraint firstAttribute="height" priority="999" constant="52" id="Liw-y2-PDc"/>
<constraint firstAttribute="width" secondItem="gKV-Cd-wIk" secondAttribute="height" multiplier="1:1" id="rsw-IM-Eh1"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KvF-3d-EbG">
<rect key="frame" x="84" y="18" width="228" height="21"/>
<rect key="frame" x="85" y="21" width="220" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="category1 &gt; category2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ctk-b6-pjw">
<rect key="frame" x="84" y="45" width="228" height="16"/>
<rect key="frame" x="85" y="48" width="220" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@ -44,12 +44,12 @@
<constraint firstItem="KvF-3d-EbG" firstAttribute="leading" secondItem="gKV-Cd-wIk" secondAttribute="trailing" constant="16" id="B4p-uV-LTm"/>
<constraint firstItem="Ctk-b6-pjw" firstAttribute="leading" secondItem="KvF-3d-EbG" secondAttribute="leading" id="CfQ-pR-HTm"/>
<constraint firstAttribute="trailingMargin" secondItem="Ctk-b6-pjw" secondAttribute="trailing" id="GbW-iJ-02i"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="8" id="JX5-qI-8f8"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="4" id="JX5-qI-8f8"/>
<constraint firstItem="KvF-3d-EbG" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="10" id="OFd-QE-rRm"/>
<constraint firstAttribute="bottomMargin" secondItem="gKV-Cd-wIk" secondAttribute="bottom" constant="8" id="fyJ-BI-Jls"/>
<constraint firstAttribute="bottomMargin" secondItem="gKV-Cd-wIk" secondAttribute="bottom" constant="4" id="fyJ-BI-Jls"/>
<constraint firstAttribute="trailingMargin" secondItem="KvF-3d-EbG" secondAttribute="trailing" id="jPo-Fz-5h5"/>
<constraint firstItem="Ctk-b6-pjw" firstAttribute="top" secondItem="KvF-3d-EbG" secondAttribute="bottom" constant="6" id="wDX-Mb-bsB"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="wjc-N6-2b9"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="wjc-N6-2b9"/>
</constraints>
</tableViewCellContentView>
<connections>

View file

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="71" id="KGk-i7-Jjw" customClass="SliderTableViewCell" customModule="pass" customModuleProvider="target">
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="71" id="KGk-i7-Jjw" customClass="SliderTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="74"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="73.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="MwT-Jl-hhE">
<rect key="frame" x="61.5" y="22" width="204.5" height="31"/>
<rect key="frame" x="60.5" y="22.5" width="198.5" height="31"/>
<connections>
<action selector="handleSliderValueChange:" destination="KGk-i7-Jjw" eventType="valueChanged" id="WwM-ZE-yIB"/>
</connections>
</slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="88" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GJP-Fj-VZt" userLabel="Value">
<rect key="frame" x="281" y="8" width="24" height="57.5"/>
<rect key="frame" x="274" y="11" width="24" height="52"/>
<constraints>
<constraint firstAttribute="width" constant="24" id="tOG-yp-eFw"/>
</constraints>
@ -35,7 +35,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="t7T-HC-hUd" userLabel="Title">
<rect key="frame" x="16" y="8" width="30" height="57.5"/>
<rect key="frame" x="15" y="11" width="30" height="52"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="8Tz-Qo-Mkg"/>
</constraints>
@ -46,7 +46,7 @@
</subviews>
<constraints>
<constraint firstItem="MwT-Jl-hhE" firstAttribute="leading" secondItem="t7T-HC-hUd" secondAttribute="trailing" constant="17" id="Cc9-kV-nZf"/>
<constraint firstItem="t7T-HC-hUd" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="Kje-Zj-RaX"/>
<constraint firstItem="t7T-HC-hUd" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="Kje-Zj-RaX"/>
<constraint firstAttribute="trailingMargin" secondItem="GJP-Fj-VZt" secondAttribute="trailing" constant="7" id="LjD-JH-dRT"/>
<constraint firstItem="MwT-Jl-hhE" firstAttribute="centerY" secondItem="t7T-HC-hUd" secondAttribute="centerY" id="MHN-Vl-1le"/>
<constraint firstAttribute="bottomMargin" secondItem="GJP-Fj-VZt" secondAttribute="bottom" id="NmC-sS-9Yt"/>

View file

@ -1,36 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="69" id="KGk-i7-Jjw" customClass="TextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="69"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="68.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu">
<rect key="frame" x="16" y="8" width="296" height="39.5"/>
<rect key="frame" x="15" y="11" width="290" height="47"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="R1z-fU-Xr2"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" returnKeyType="next"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no"/>
</textField>
</subviews>
<constraints>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="3h4-9E-oDJ"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="3h4-9E-oDJ"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="FH4-4T-aaK"/>
<constraint firstAttribute="trailingMargin" secondItem="8Ky-UZ-sLu" secondAttribute="trailing" id="Nii-Cg-gC1"/>
<constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="h72-l3-Sb3"/>
@ -39,7 +39,7 @@
<connections>
<outlet property="contentTextField" destination="8Ky-UZ-sLu" id="pdS-Nl-e9s"/>
</connections>
<point key="canvasLocation" x="34" y="60"/>
<point key="canvasLocation" x="34" y="66.5"/>
</tableViewCell>
</objects>
</document>

View file

@ -13,6 +13,8 @@ class TextViewTableViewCell: ContentTableViewCell {
@IBOutlet weak var contentTextView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
self.contentTextView.textContainer.lineFragmentPadding = 0
self.contentTextView.textContainerInset = .zero
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="217" id="KGk-i7-Jjw" customClass="TextViewTableViewCell" customModule="pass" customModuleProvider="target">
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="217" id="KGk-i7-Jjw" customClass="TextViewTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="217"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="216.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xHX-Sh-1pR">
<rect key="frame" x="12" y="16" width="300" height="184.5"/>
<rect key="frame" x="15" y="11" width="290" height="195"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="120" id="Tvq-j8-Nvh"/>
@ -31,9 +31,9 @@
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="xHX-Sh-1pR" secondAttribute="trailing" id="LWS-JW-9dS"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="4" id="SRq-7t-Gyr"/>
<constraint firstAttribute="bottomMargin" secondItem="xHX-Sh-1pR" secondAttribute="bottom" constant="8" id="UPQ-jk-QJR"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="8" id="gwb-2C-4wp"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="SRq-7t-Gyr"/>
<constraint firstAttribute="bottomMargin" secondItem="xHX-Sh-1pR" secondAttribute="bottom" id="UPQ-jk-QJR"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="gwb-2C-4wp"/>
</constraints>
</tableViewCellContentView>
<connections>

View file

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TitleTextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TitleTextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HPC-rQ-39b">
<rect key="frame" x="16" y="8" width="296" height="15"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<rect key="frame" x="15" y="11" width="290" height="10"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<color key="textColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="123" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu">
<rect key="frame" x="16" y="26" width="296" height="21"/>
<rect key="frame" x="15" y="24" width="290" height="21"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="dHY-dw-mFe"/>
</constraints>
@ -39,7 +39,7 @@
<constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="11n-KG-zy6"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="trailing" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="42z-vE-AsJ"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="HPC-rQ-39b" secondAttribute="leading" id="8F6-w6-BDY"/>
<constraint firstItem="HPC-rQ-39b" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="8ps-9g-9zG"/>
<constraint firstItem="HPC-rQ-39b" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="8ps-9g-9zG"/>
<constraint firstAttribute="trailingMargin" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="Ayv-Bg-JxT"/>
<constraint firstItem="HPC-rQ-39b" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="JN2-lI-0ZY"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="HPC-rQ-39b" secondAttribute="bottom" constant="3" id="Pqs-gm-93j"/>