<file_sep>//
//  PINDetailTableViewController.swift
//  PINKeeper
//
//  Created by <NAME> on 8/13/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit
import MagicalRecord

class PINDetailTableViewController: UITableViewController {
    
    @IBOutlet weak var descriptionField: UITextField! {
        didSet{
            descriptionField.delegate = self
        }
    }
    @IBOutlet weak var pinField: REFormattedNumberField!
    
    var pin: PIN? {
        didSet{
            refreshUI()
        }
    }
    
    private func refreshUI(){
        descriptionField?.text = pin?.desc
        pinField?.text = pin?.value
        pinField?.format = "XXXX"
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        refreshUI()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        descriptionField.becomeFirstResponder()
    }
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        pin?.desc = descriptionField.text ?? ""
        pin?.value = pinField.text ?? ""
        NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreWithCompletion { (success, error) -> Void in
            if success {
                println("Data saved successful.")
            }else{
                println("Error! Data saving failed with error: \(error?.localizedDescription)")
            }
        }
    }
    
    // MARK: - Actions

    @IBAction func viewTapped(sender: UITapGestureRecognizer) {
        view.endEditing(true)
    }
    
    // MARK: - Table view data source
    
    /*
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 0
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return 0
    }
    */

    /*
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as! UITableViewCell

        // Configure the cell...

        return cell
    }
    */

    /*
    // Override to support conditional editing of the table view.
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the specified item to be editable.
        return true
    }
    */

    /*
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
    */

    /*
    // Override to support rearranging the table view.
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the item to be re-orderable.
        return true
    }
    */

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */
}

extension PINDetailTableViewController: UITextFieldDelegate{
    func textFieldShouldReturn(sender: UITextField) -> Bool{
        if sender != descriptionField { return true }
        sender.resignFirstResponder()
        return true
    }
}
<file_sep>//
//  ViewController+Allerts.swift
//  PINKeeper
//
//  Created by <NAME> on 8/9/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit

extension UIViewController{
    func showAlertController(message: String) {
        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .Alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        presentViewController(alertController, animated: true, completion: nil)
    }
}<file_sep>//
//  PINsTableViewController.swift
//  PINKeeper
//
//  Created by <NAME> on 8/13/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit
import CoreData

class PINsTableViewController: UITableViewController {
    
    private struct Storyboard {
        static let showNewPINDetailsSegueID = "Show New PIN Details"
        static let showDetailsOfPINSegueID = "Show Details of PIN"
    }
    
    private var pins = [[PIN]()]

    override func viewDidLoad() {
        super.viewDidLoad()
        refreshData()
        
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }
    
    private func refreshData(){
        self.pins[0] = PIN.MR_findAll() as! [PIN]
        println("Found \(pins[0].count) records in database")
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        navigationItem.setHidesBackButton(true, animated: false)
        tableView.reloadData()
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return pins.count
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pins[section].count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(PINTableViewCell.reuseID, forIndexPath: indexPath) as! PINTableViewCell
        cell.pin = pins[indexPath.section][indexPath.row]
        return cell
    }

    // Override to support conditional editing of the table view.
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the specified item to be editable.
        return true
    }

    
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            pins[indexPath.section][indexPath.row].MR_deleteEntity()
            saveData()
            refreshData()
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
    
    private func saveData(){
        NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreWithCompletion { (success, error) -> Void in
            if success {
                println("Data saved successful.")
            }else{
                println("Error! Data saving failed with error: \(error?.localizedDescription)")
            }
        }
    }

    /*
    // Override to support rearranging the table view.
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the item to be re-orderable.
        return true
    }
    */

    
    // MARK: - Navigation

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == nil {
            println("Error! Unknown nil segue identifier.")
            return
        }
        switch segue.identifier!{
        case Storyboard.showNewPINDetailsSegueID:
            let newPIN = PIN.MR_createEntity()
            pins[0].append(newPIN)
            (segue.destinationViewController as! PINDetailTableViewController).pin = newPIN
        case Storyboard.showDetailsOfPINSegueID:
            let cell = sender as! UITableViewCell
            let indexPath = tableView.indexPathForCell(cell)!
            let pin = pins[indexPath.section][indexPath.row]
            (segue.destinationViewController as! PINDetailTableViewController).pin = pin
        default:
            break
            
        }
    }

    
    // MARK: - Actions

}
<file_sep>//
//  PIN.swift
//  PINKeeper
//
//  Created by <NAME> on 8/19/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import Foundation
import CoreData

@objc(PIN)
class PIN: NSManagedObject {
    @NSManaged var desc: String!
    @NSManaged var value: String!
}
<file_sep>//
//  PINTableViewCell.swift
//  PINKeeper
//
//  Created by <NAME> on 8/13/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit

class PINTableViewCell: UITableViewCell {

    static let reuseID = "PINTableViewCell"
    @IBOutlet weak var titleLabel: UILabel!{
        didSet{
            refreshUI()
        }
    }
    @IBOutlet weak var pinLabel: UILabel!{
        didSet{
            refreshUI()
        }
    }
    
    var pin: PIN?{
        didSet{
            refreshUI()
        }
    }
    
    private func refreshUI(){
        titleLabel?.text = pin?.desc
        pinLabel?.text = pin?.value
    }

    override func prepareForReuse() {
        titleLabel.text = ""
        pinLabel.text = ""
    }
}
<file_sep>//
//  UIButton+RoundedBorder.swift
//  App4Brand
//
//  Created by <NAME> on 7/25/15.
//  Copyright (c) 2015 App4Brand. All rights reserved.
//

import UIKit

extension UIView{
    /// If value is true the UIView has a rounded border with 1 point of width.
    /// Color of the border is equal to the tint color of the UIView.
    var roundedBorder: Bool{
        set(isRoundedBorder){
            if isRoundedBorder{
                self.layer.borderWidth = 1.0
                self.layer.borderColor = self.tintColor?.CGColor
                self.layer.cornerRadius = self.frame.size.height / 2
                self.layer.masksToBounds = true
            }else{
                self.layer.borderWidth = 0.0
                self.layer.borderColor = nil
                self.layer.cornerRadius = 0.0
                self.layer.masksToBounds = false
            }
        }
        get{
            return self.layer.masksToBounds && self.layer.borderWidth == self.frame.size.height / 2
        }
    }
}
<file_sep>source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.4'

# ignore all warnings from all pods
#inhibit_all_warnings!

use_frameworks!

pod 'UIView+Shake'
pod 'REFormattedNumberField'
pod 'MagicalRecord'
pod 'CryptoSwift'<file_sep>//
//  ViewController.swift
//  PINKeeper
//
//  Created by <NAME> on 8/9/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit
import LocalAuthentication
import AudioToolbox
import UIView_Shake

class AuthenticationViewController: UIViewController {
    
    struct Constants {
        static let DigitButtonWidthToTitleFontSizeRatio: CGFloat = 0.45
        struct Progress {
            static let progressColor = UIColor.lightGrayColor()
            struct Shaking {
                static let speed: NSTimeInterval = 0.1
                static let delta: CGFloat = 50
                static let times: Int32 = 6
            }
        }
    }
    
    // MARK: - Properties
    
    @IBOutlet var digitButtons: [UIButton]!
    @IBOutlet weak var progress1View: UIView!
    @IBOutlet weak var progress2View: UIView!
    @IBOutlet weak var progress3View: UIView!
    @IBOutlet weak var progress4View: UIView!
    @IBOutlet weak var deleteButton: UIButton!
    
    private let authenticationContext = LAContext()
    
    private var progressViews: [UIView]{
        return [progress1View, progress2View, progress3View, progress4View]
    }
    
    private var passCode = ""{
        didSet{
            if passCode == "" {
                enterProgress = 0
            }
        }
    }
    
    private var enterProgress = 0 { //from 0 to 4
        didSet{
            enterProgress = min(max(enterProgress, 0), 4)
            refreshProgressViews()
            deleteButton?.hidden = enterProgress == 0
        }
    }
    
    private func refreshProgressViews(){
        for (index, progressView) in enumerate(progressViews){
            if (index+1) <= enterProgress {
                switchOn(progressView)
            }else{
                switchOff(progressView)
            }
        }
    }
    
    private func switchOn(progressView: UIView){
        progressView.backgroundColor = Constants.Progress.progressColor
    }
    
    private func switchOff(progressView: UIView){
        progressView.backgroundColor = UIColor.clearColor()
    }
    
    private var isEnterEnded: Bool{
        return enterProgress == 4
    }
    
    // MARK: - ViewController life cycle
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        passCode = ""
        restrictRotationForiPhones()
        navigationController?.setNavigationBarHidden(true, animated: false)
    }
    
    private func restrictRotationForiPhones(){
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            AppDelegate.restrictRotation = true
        }
    }
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        if !Settings.isTouchIDBlocked {
            authenticateUserWithTouchID()
        }else{
            println("User authentication with TouchID is blocked. You can use the passcode and activate TouchID after that.")
        }
    }
    
    private func authenticateUserWithTouchID(){
        var error: NSError?
        if !checkIfTouchIDIsAvailable(&error) {
            println("Touch ID is not available")
            return
        }
        authenticationContext.localizedFallbackTitle = "Enter Passcode"
        authenticationContext.maxBiometryFailures = 3
        let reason = "Use your fingerprint to log in."
        authenticationContext.evaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply:
            {(succes: Bool, error: NSError!) in
                if succes {
                    println("Touch ID Authentication: Succeeded")
                    dispatch_async(dispatch_get_main_queue()){ self.dismissViewControllerAnimated(true, completion: nil) }
                }
                else {
                    if error == nil {
                        println("Authentication failed but error = nil!")
                        return
                    }
                    println("Touch ID Authentication: Failed!")
                    println("Error description: \(error!.localizedDescription)")
                    println("Error code: \(error!.code)")
                    switch error!.code {
                    case LAError.SystemCancel.rawValue:
                        println("Authentication was cancelled by the system")
                    case LAError.UserCancel.rawValue:
                        println("Authentication was cancelled by the user")
                    case LAError.UserFallback.rawValue:
                        println("User selected to enter passcode.")
                    case LAError.AuthenticationFailed.rawValue:
                        println("Authentication failed! Description: \(error!.localizedDescription)")
                        Settings.isTouchIDBlocked = true
                        println("TouchID blocked successfully.")
                        dispatch_async(dispatch_get_main_queue()){
                            self.showAlertController("Touch ID is locked out. You can use the passcode and activate TouchID after that.")
                        }
                    case LAError.PasscodeNotSet.rawValue:
                        println("Passcode is not set.")
                    case LAError.TouchIDNotAvailable.rawValue:
                        println("Touch ID is not available during evaluate policy.")
                    case LAError.TouchIDNotEnrolled.rawValue:
                        println("Touch ID is not enrolled.")
                    default:
                        println("Touch ID Authentication failed with unknown error code.")
                    }
                }
        })
    }
    
    private func checkIfTouchIDIsAvailable(inout error: NSError?)->Bool{
        return authenticationContext.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &error)
    }
    
    override func viewDidLayoutSubviews() {
        setAllDigitButtonsRounded()
        refreshDigitButtonsFontSizes()
        setAllProgressViewRounded()
    }
    
    private func setAllDigitButtonsRounded(){
        for button in digitButtons{
            button.roundedBorder = true
        }
    }
    
    private func refreshDigitButtonsFontSizes(){
        let buttonWidth = digitButtons[0].frame.size.width
        let newFontSize = buttonWidth * Constants.DigitButtonWidthToTitleFontSizeRatio
        for button in digitButtons{
            button.titleLabel?.font = UIFont.systemFontOfSize(newFontSize, weight: UIFontWeightThin)
        }
        println("Digit buttons font size: \(digitButtons[0].titleLabel?.font?.pointSize)")
        println("Digit buttons width: \(buttonWidth)")
    }
    
    private func setAllProgressViewRounded(){
        for view in progressViews{
            view.roundedBorder = true
        }
    }
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.setNavigationBarHidden(false, animated: false)
    }
    
    override func viewDidDisappear(animated: Bool) {
        AppDelegate.restrictRotation = false
    }
    
    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return UIStatusBarStyle.LightContent
    }
    
    // MARK: - Actions
    
    @IBAction func digitButtonTapped(sender: UIButton) {
        ++enterProgress
        passCode += sender.titleLabel?.text ?? ""
        if isEnterEnded {
            checkPasscode()
        }
    }
    
    private func checkPasscode(){
        if Authentication.checkPasscode(passCode){
            println("Enter passcode: Success.")
            dismissViewControllerAnimated(true, completion: nil)
        }else{
            println("Enter passcode: Failed!")
            vibrate()
            shakeProgressViews()
            passCode = ""
        }
    }
    
    private func vibrate(){
        AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
    }
    
    private func shakeProgressViews(){
        let times = Constants.Progress.Shaking.times
        let delta = Constants.Progress.Shaking.delta
        let speed = Constants.Progress.Shaking.speed
        for view in progressViews{
            view.shake(times, withDelta: delta, speed: speed)
        }
    }
    
    @IBAction func deleteTapped(sender: UIButton) {
        --enterProgress
        removeLastCharInPassCode()
    }
    
    private func removeLastCharInPassCode(){
        passCode.removeAtIndex(passCode.endIndex.predecessor())
    }

}

<file_sep>//
//  MainNavigationController.swift
//  PINKeeper
//
//  Created by <NAME> on 9/8/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import UIKit

class MainNavigationController: UINavigationController {

    override func viewDidLoad() {
        addObserverForRisigningActiveEvent()
        addObserverForLaunchingEvent()
    }
    
    private func addObserverForRisigningActiveEvent(){
        let center = NSNotificationCenter.defaultCenter()
        center.addObserverForName(UIApplicationWillResignActiveNotification, object: UIApplication.sharedApplication(), queue: NSOperationQueue.mainQueue()){ notification in
            println("App will enter foreground!!!!")
            self.showAuthenticationViewController()
        }
    }
    
    private func showAuthenticationViewController(){
        let authVC = storyboard?.instantiateViewControllerWithIdentifier("AuthenticationViewController") as! AuthenticationViewController
        presentViewController(authVC, animated: false, completion: nil)
    }
    
    private func addObserverForLaunchingEvent(){
        let center = NSNotificationCenter.defaultCenter()
        center.addObserverForName(UIApplicationDidFinishLaunchingNotification, object: UIApplication.sharedApplication(), queue: NSOperationQueue.mainQueue()){ notification in
            println("App is launching!!!!")
            self.showAuthenticationViewController()
        }
    }
}
<file_sep>//
//  Authentication.swift
//  PINKeeper
//
//  Created by <NAME> on 8/12/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import Foundation
import CryptoSwift

class Authentication{
    class func checkPasscode(passCode: String)->Bool{
        return passCode.sha512() == passCodeHash
    }
    
    static var passCodeHash: String! { return "0000".sha512() }
}<file_sep>//
//  Settings.swift
//  PINKeeper
//
//  Created by <NAME> on 8/13/15.
//  Copyright (c) 2015 <NAME>. All rights reserved.
//

import Foundation

class Settings {
    
    private struct Constants {
        struct UserDefaultsKeys {
            static let isTouchIDBlockedKey = "com.alexeytsymlov.PINKeeper.Settings.isTouchIDBlocked"
        }
    }
    
    private static let userDefaults = NSUserDefaults.standardUserDefaults()
    
    class var isTouchIDBlocked: Bool{
        set{
            userDefaults.setBool(newValue, forKey: Constants.UserDefaultsKeys.isTouchIDBlockedKey)
            if !userDefaults.synchronize(){ println("UserDefaults synchronization failed!") }
        }
        get{
            return userDefaults.boolForKey(Constants.UserDefaultsKeys.isTouchIDBlockedKey)
        }
    }
}