Ios 如何在Swift中使用NSCoding从ViewController保存数据
我需要使用NSCoding将数据从segued ViewController(“ScoreView.swift”)保存到“ScoreHistory.swift”。我试过了,但是数据没有显示在“ScoreTableViewController.swift”中。我错过了什么 我有这个ScoreView.swift,它有以下代码:(请注意,这是一个“分段”视图,其中数据已从另一个ViewController传递) 我有ScoreHistory.swift,它有以下代码:Ios 如何在Swift中使用NSCoding从ViewController保存数据,ios,swift,nscoding,Ios,Swift,Nscoding,我需要使用NSCoding将数据从segued ViewController(“ScoreView.swift”)保存到“ScoreHistory.swift”。我试过了,但是数据没有显示在“ScoreTableViewController.swift”中。我错过了什么 我有这个ScoreView.swift,它有以下代码:(请注意,这是一个“分段”视图,其中数据已从另一个ViewController传递) 我有ScoreHistory.swift,它有以下代码: class ScoreHist
class ScoreHistory: NSObject, NSCoding {
// MARK: Properties
var datePlayed: String
var totalScore: String
var totalAnswered: String
var totalDuration: String
var gameStatus: String
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("scores")
// MARK: Types
struct PropertyKey {
static let datePlayedKey = "datePlayed"
static let totalScoreKey = "totalScore"
static let totalAnsweredKey = "totalAnswered"
static let totalDurationKey = "totalDuration"
static let gameStatusKey = "gameStatus"
}
// MARK: Initialization
init?(datePlayed: String, totalScore: String, totalAnswered: String, totalDuration: String, gameStatus: String) {
// Initialize stored properties.
self.datePlayed = datePlayed
self.totalScore = totalScore
self.totalAnswered = totalAnswered
self.totalDuration = totalDuration
self.gameStatus = gameStatus
super.init()
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(datePlayed, forKey: PropertyKey.datePlayedKey)
aCoder.encodeObject(totalScore, forKey: PropertyKey.totalScoreKey)
aCoder.encodeObject(totalAnswered, forKey: PropertyKey.totalAnsweredKey)
aCoder.encodeObject(totalDuration, forKey: PropertyKey.totalDurationKey)
aCoder.encodeObject(gameStatus, forKey: PropertyKey.gameStatusKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let datePlayed = aDecoder.decodeObjectForKey(PropertyKey.datePlayedKey) as! String
let totalScore = aDecoder.decodeObjectForKey(PropertyKey.totalScoreKey) as! String
let totalAnswered = aDecoder.decodeObjectForKey(PropertyKey.totalAnsweredKey) as! String
let totalDuration = aDecoder.decodeObjectForKey(PropertyKey.totalDurationKey) as! String
let gameStatus = aDecoder.decodeObjectForKey(PropertyKey.gameStatusKey) as! String
// Must call designated initializer.
self.init(datePlayed: datePlayed, totalScore: totalScore, totalAnswered: totalAnswered, totalDuration: totalDuration, gameStatus: gameStatus)
}
}
以下是ScoreTableViewController.swift的完整代码:
class ScoreTableViewController: UITableViewController {
// MARK: Properties
var scores = [ScoreHistory]()
var dateToday = NSDate()
override func viewDidLoad() {
super.viewDidLoad()
// Load any saved scores, otherwise load sample data.
if let savedScores = loadScores() {
scores += savedScores
} else {
// Load the sample data.
loadSampleScores()
}
}
func loadSampleScores() {
let score1 = ScoreHistory(datePlayed: dateToday.description, totalScore: "0", totalAnswered: "0", totalDuration: "0", gameStatus: "started")!
scores += [score1]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return scores.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "ScoreHistoryTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ScoreHistoryTableViewCell
// Fetches the appropriate note for the data source layout.
let score = scores[indexPath.row]
cell.datePlayedLabel.text = score.datePlayed
cell.totalScoreLabel.text = score.datePlayed
cell.totalScoreLabel.text = score.totalScore
cell.totalAnsweredLabel.text = score.totalAnswered
cell.totalDurationLabel.text = score.totalDuration
cell.gameStatusLabel.text = score.gameStatus
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false 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
scores.removeAtIndex(indexPath.row)
saveScores()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
// 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?) {
if segue.identifier == "ShowDetail" {
let scoreDetailViewController = segue.destinationViewController as! ScoreViewController
// Get the cell that generated this segue.
if let selectedScoreCell = sender as? ScoreHistoryTableViewCell {
let indexPath = tableView.indexPathForCell(selectedScoreCell)!
let selectedScore = scores[indexPath.row]
scoreDetailViewController.score = selectedScore
}
}
}
// MARK: NSCoding
func saveScores() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(scores, toFile: ScoreHistory.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save scores...")
}
}
func loadScores() -> [ScoreHistory]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(ScoreHistory.ArchiveURL.path!) as? [ScoreHistory]
}
@IBAction func unwindToScoreList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ScoreViewController, score = sourceViewController.score {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing note.
scores[selectedIndexPath.row] = score
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
// Add a new score.
let newIndexPath = NSIndexPath(forRow: scores.count, inSection: 0)
scores.append(score)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
saveScores()
}
}
}
}
目标:我的目标是记录/存储用户完成测验游戏时“ScoreView.swift”中的所有会话数据。
“ScoreView”在每次测验游戏后显示,我计划在“ScoreHistory.swift”中记录每个测验结果。我该怎么做?您的
加载分数功能正在加载存档的分数数组:
func loadScores() -> [ScoreHistory]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(ScoreHistory.ArchiveURL.path!) as? [ScoreHistory]
}
在你的序列中,你只存档了一个分数。您不能存档ScoreHistory
实例,而希望取消存档ScoreHistory
数组。您目前拥有:
score = ScoreHistory(datePlayed: datePlayed, totalScore: totalScore, totalAnswered: totalAnswered, totalDuration: totalDuration, gameStatus: gameStatus)
您需要将此更改为:
var scores = loadScores() ?? []
score = ScoreHistory(datePlayed: datePlayed, totalScore: totalScore, totalAnswered: totalAnswered, totalDuration: totalDuration, gameStatus: gameStatus)
scores.append(score)
saveScores(scores)
其中loadScores
和saveScores
与ScoreTableViewController
中的代码相同,尽管我已经添加了分数以另存为参数,因为该代码创建了一个局部变量
更新:很晚了,我没有足够的注意。您需要处理返回nil的loadScores,当然scores
应该是var
而不是let
,否则您将无法添加到它。有了这些更改,分数
不再是可选的,因此您不需要将其展开。最简单的解决方案是将更改后的值从UITextField
实例保存回分数视图中的分数
实例(为什么score
是可选的,因为您总是通过一个非可选的score
实例??)并松开该段。
然后将数组保存在ScoreTableViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if backMenu === sender {
score?.datePlayed = datePlayedLabel.text ?? ""
score?.totalScore = totalScoreLabel.text ?? ""
score?.totalAnswered = totalAnsweredLabel.text ?? ""
score?.totalDuration = totalDurationLabel.text ?? ""
score?.gameStatus = gameStatusLabel.text ?? ""
}
}
在ScoreView
中没有存档!在ScoreView
中,您正在存档单个对象,但在另一个控制器中,您正在取消存档一个潜在数组。请添加更多信息,说明您将要完成的任务。@vadian谢谢您的回答,但我该如何做?我不知道您的目标是什么。@vadian这就像一个“Score History”视图,在该视图中,用户将能够看到其所有问答游戏结果……我正在努力研究如何从“ScoreView”场景获取数据,以将其存储到“ScoreHistory”中。"正如@vadian所说,你只写了一个分数历史记录。如果你想保存所有过去的分数,你必须每次都读写所有的分数。这意味着在你的序列中,如果你看起来要在历史记录中添加一个分数,你必须将其全部取消归档,添加一个,然后再次归档。我得到以下e“scores.append(score)”中的错误//类型为“[ScoreHistory]”的值没有成员“append”My bad-您的loadScores函数返回一个可选值,因此需要使用“!”将其展开.但是你考虑过@vadian的解决方案吗?只在一个位置存档/不存档要好得多。ScoreTableViewController中还没有显示分数…我缺少什么?我非常感谢你的帮助。只显示了示例分数。当没有保存分数时,loadScores将返回nil,这不是你想要的。如果是nil,则返回cre我吃了一个新的空数组。我更新了答案。谢谢@vadian,我稍后会尝试并让你知道:)我不太明白,我使用UILabel获取分数,但不使用用户输入获取数据。。抱歉,我对Swifti很陌生如果你只使用标签,为什么要将所有值来回存储到变量中?这些值是从ScoreView自动生成的,例如,在用户完成考试后,ScoreView会显示他正确获得的答案数。。我需要将这些数据存储在ScoreHistory中。对不起,您的代码很混乱。我在代码中看到,您将viewDidLoad
中的标签设置为变量值(为空字符串),然后如果score
不是nil
(但始终不是nil
),则标签将设置为通过的分数的值,并且再次出现在视图中。然后在prepareforsgue
中,将标签中的值存储回新的局部变量,并创建一个新的ScoreHistory
实例。将通过的分数保持为模型并只更新标签可能更容易。然后在“展开”任务中,将该实例传递回源代码视图控制器并保存阵列。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if backMenu === sender {
score?.datePlayed = datePlayedLabel.text ?? ""
score?.totalScore = totalScoreLabel.text ?? ""
score?.totalAnswered = totalAnsweredLabel.text ?? ""
score?.totalDuration = totalDurationLabel.text ?? ""
score?.gameStatus = gameStatusLabel.text ?? ""
}
}