【发布时间】:2018-05-17 07:45:48
【问题描述】:
我正在开发一个使用计时器每 30 秒生成一次顶部的应用程序。我从扫描的 QR 码中获取密钥并将它们附加到一个模型数组中,对于每个密钥我生成 tOTP 并将它们附加到另一个模型数组中。使用该模型数组,我正在填充 tableview。
tableviewcell 包含用于显示 otp 的标签和用于跟踪进度的自定义循环进度视图。主要问题是我无法与其他 TOTP 应用程序(如 Google Authenticator)保持同步。当我每秒运行计时器时,我可以每秒从 TOTP 生成器库生成 otps,并且可以通过重新加载 tableview 来更新标签。但是这个功能会影响进度视图,当我每秒运行计时器以生成 otp 并重新加载 tableview 时删除编辑 tableview 单元格。 希望有人帮助...
这是我的代码...
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var tOTPModel = [TOTP]()
var secretKeys = [String]()
var generator: OTPGenerator?
var timer: Timer?
var currentTimeInterval = TimeInterval()
@IBOutlet weak var tOtpTableView: UITableView!
@IBOutlet weak var btnInfo: UIBarButtonItem!
@IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if self.tOTPS.isEmpty == false {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
@IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
@objc func generateTOTP() {
self.tOTPModel = []
for tOtpObject in self.tOTPS {
self.generator = Generator.generatorWithSecretKey(key: tOtpObject.secretKey)
let tOtp = (self.generator as! TOTPGenerator).generateOTP()
self.tOTPModel.append(TOTP(secretKey: tOtp!, issuer: tOtpObject.issuer, scheme: tOtpObject.scheme, createdDate: tOtpObject.createdDate))
}
self.tOtpTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPModel[indexPath.section]
cell.lblTOTP.text = tOTP.secretKey.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}
这是模型对象...
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
}
}
这是生成 TOTP 的生成器类...
class Generator {
static func generatorWithSecretKey(key: String) -> OTPGenerator {
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
let secretKey = MF_Base32Codec.data(fromBase32String: key)
return TOTPGenerator(secret: secretKey, algorithm: OTPGenerator.defaultAlgorithm(), digits: 6, period: 30)
}
}
【问题讨论】:
-
为什么要重新加载整个表?从 tableview 中获取可见单元格并更新它们。
-
我正在更新模型数组并使用该数组填充 tableview。我正在调用计时器选择器方法来做到这一点。
-
如果我重新加载可见单元格,我会崩溃
-
你应该解决崩溃。仅当底层数据集发生重大更改时才应重新加载整个 tableview。
-
generateOTP是在每次调用它时创建一个新的 OTP,还是在内部使用当前时间以便 OTP 与当前时间而不是对generateOTP的调用次数相协调?