NSCoding gamedata not saved swift - ios

At the moment, my data does not get retrieved in another new session and it uses the default values. Do I have to have an existing plist file for this to work? I tried using an existing file with no luck.
I followed the guide here http://battleofbrothers.com/sirryan/saving-game-data-in-spritekit
class GameData : NSObject, NSCoding {
/// Data to save
var variableC : Int = 3
/// Create of shared instance
class var sharedInstance: GameData {
struct Static {
static var instance: GameData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
var gamedata = GameData()
if let savedData = GameData.loadGame() {
gamedata = savedData
}
Static.instance = gamedata
}
return Static.instance!
}
override init() {
super.init()
}
required init(coder: NSCoder) {
super.init()
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(GameData.sharedInstance, forKey: "GameData")
}
class func loadGame() -> GameData? {
// load existing high scores or set up an empty array
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as! String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
let fileManager = NSFileManager.defaultManager()
// check if file exists
if !fileManager.fileExistsAtPath(path) {
// create an empty file if it doesn't exist
println("File doesn't exist")
if let bundle = NSBundle.mainBundle().pathForResource("DefaultFile", ofType: "plist") {
fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
}
}
if let rawData = NSData(contentsOfFile: path) {
// do we get serialized data back from the attempted path?
// if so, unarchive it into an AnyObject, and then convert to an array of HighScores, if possible
if let data = NSKeyedUnarchiver.unarchiveObjectWithData(rawData) as? GameData {
println("We loaded the data!")
return data
}
}
println("we returned Nil")
return nil
}
func save() {
// find the save directory our app has permission to use, and save the serialized version of self.scores - the HighScores array.
let saveData = NSKeyedArchiver.archivedDataWithRootObject(GameData.sharedInstance);
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
let documentsDirectory = paths.objectAtIndex(0)as! NSString;
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist");
saveData.writeToFile(path, atomically: true);
}
}
I load the gamedata in the init function with this
var gameData: GameData = GameData.sharedInstance
Updating the data
gameData.variableC = gameData.variableC + 1
gameData.save()
println(gameData.variableC)

You save the data but don't use it in your init. In initWithCoder:
required init(coder: NSCoder) {
super.init()
GameData.shaderInstance = coder.decodeObjectForKey("GameData")
}

Like Jozsef said, I forgot to decode the data. However I need to decode each of the individual variables and then copy it over to the GameData for it to work.
Here's
class GameData : NSObject, NSCoding {
/// Data to save
var variableC : Int! = 3
/// Create of shared instance
class var sharedInstance: GameData {
struct Static {
static var instance: GameData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
var gamedata = GameData()
if let savedData = GameData.loadGame() {
gamedata.variableC = savedData.variableC
}
Static.instance = gamedata
}
return Static.instance!
}
override init() {
super.init()
}
required init(coder: NSCoder) {
super.init()
self.variableC = coder.decodeObjectForKey("variableC") as? Int
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(GameData.sharedInstance.variableC, forKey: "variableC")
}
class func loadGame() -> GameData? {
// load existing high scores or set up an empty array
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as! String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
let fileManager = NSFileManager.defaultManager()
// check if file exists
if !fileManager.fileExistsAtPath(path) {
// create an empty file if it doesn't exist
println("File doesn't exist")
if let bundle = NSBundle.mainBundle().pathForResource("DefaultFile", ofType: "plist") {
fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
}
}
if let rawData = NSData(contentsOfFile: path) {
// do we get serialized data back from the attempted path?
// if so, unarchive it into an AnyObject, and then convert to an array of HighScores, if possible
if let data = NSKeyedUnarchiver.unarchiveObjectWithData(rawData) as? GameData {
println("We loaded the data!")
return data
}
}
return nil
}
func save() {
// find the save directory our app has permission to use, and save the serialized version of self.scores - the HighScores array.
let saveData = NSKeyedArchiver.archivedDataWithRootObject(GameData.sharedInstance);
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
let documentsDirectory = paths.objectAtIndex(0)as! NSString;
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist");
saveData.writeToFile(path, atomically: true);
}
}

Related

Establishing a Read-Write connection to a database in SQLite.swift

I'm working a project where I need to use a SQLite database to read, create , and edit different objects. I thought I had established the connection properly but, it turns out I had only established a read only connection. How do I modify this code to be a read-write connection using SQLite.swift
import Foundation
import SQLite
import UIKit
let path = Bundle.main.path(forResource: "Assignment2", ofType: "sqlite3")
//Array of customer structs to populate the table
var customerArray: [Customer] = []
class CustomerPageVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
//IBOutlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var addCustButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//Additional Setup
do {
//Search for DB in documents directory
let db = try Connection(path!)
let customers = Table("Customers")
//Define the columns of the table as expressions
let id = Expression<Int64>("CustomerID")
let name = Expression<String>("CustomerName")
let contactName = Expression<String>("ContactName")
let address = Expression<String>("Address")
let city = Expression<String>("City")
let postalCode = Expression<String>("PostalCode")
let country = Expression<String>("Country")
//Load the data from db file into customerArray
for customer in try db.prepare(customers) {
let cust = Customer(Int(customer[id]), customer[name], customer[contactName], customer[address], customer[city], customer[postalCode], customer[country])
customerArray.append(cust)
}
}
catch {
print(error)
}
tableView.delegate = self
tableView.dataSource = self
}
}
Edit there's a func copyDatabaseIfNeeded in the documentation so maybe my true question is in what context do I use this func to copy the database to the application support directory?
func copyDatabaseIfNeeded(sourcePath: String) -> Bool {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destinationPath = documents + "/db.sqlite3"
let exists = FileManager.default.fileExists(atPath: destinationPath)
guard !exists else { return false }
do {
try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath)
return true
} catch {
print("error during file copy: \(error)")
return false
}
}
You can find the documentation for SQLite.swift here https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#connecting-to-a-database
When database created in your document directory it will be there permanently till user delete the app or you delete that directory.
so read and write connection will occur when you save your sql file in this directory.
as you wanted I make a new example for you that I created recently.
import UIKit
import SQLite
class ViewController: UIViewController {
let customer = Table("Customer")
let id = Expression<Int64>("CustomerID")
let name = Expression<String>("CustomerName")
override func viewDidLoad() {
super.viewDidLoad()
let db = makeDBConnection()
createTable(db: db)
insertNewCustomer(db: db)
fetchDatabase(db: db)
}
private func makeDBConnection() -> Connection {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
let sourcePath = "\(path)/db.sqlite3"
_ = copyDatabaseIfNeeded(sourcePath: sourcePath)
return try! Connection(sourcePath)
}
private func createTable(db: Connection) {
//Define the columns of the table as expressions
do {
try db.run(customer.create(block: { table in
table.column(id, primaryKey: true)
table.column(name)
}))
} catch {
// This tells you table already created for second time you running this code
}
}
private func insertNewCustomer(db: Connection) {
// This will insert a new customer into your table each time app runs
let insert = customer.insert(name <- "Reza")
try! db.run(insert)
}
private func fetchDatabase(db: Connection) {
for customer in try! db.prepare(customer) {
print("id: \(customer[id]), name: \(customer[name])")
}
}
func copyDatabaseIfNeeded(sourcePath: String) -> Bool {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destinationPath = documents + "/db.sqlite3"
let exists = FileManager.default.fileExists(atPath: destinationPath)
guard !exists else { return false }
do {
try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath)
return true
} catch {
print("error during file copy: \(error)")
return false
}
}
}
the result is when I run the app each time:

NSCoding decodeObject always nil

I'm a new at swift, please help.
Try to archive and unarchive object and write/read it on disk.
I have a class FourLines
class FourLines: NSObject, NSCoding, NSSecureCoding, NSCopying {
static var supportsSecureCoding: Bool{
return true
}
private static let linesKey = "linesKey"
var lines: [String]?
override init() {
}
required init?(coder aDecoder: NSCoder) {
print("FourLines init")
lines = aDecoder.decodeObject(forKey: FourLines.linesKey) as? [String]
}
func encode(with aCoder: NSCoder) {
print("FourLines encode")
if let saveLines = lines {
aCoder.encode(saveLines, forKey: FourLines.linesKey)
}
}
func copy(with zone: NSZone? = nil) -> Any {
print("copy with zone")
let copy = FourLines()
if let linesToCopy = lines {
var newLines = Array<String>()
for line in linesToCopy {
newLines.append(line)
}
copy.lines = newLines
}
return copy
}
}
In ViewController i try to save and read data:
class ViewController: UIViewController {
private static let linesKey = "linesKey"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let fileURL = self.dataFileUrl()
if FileManager.default.fileExists(atPath: fileURL.path!) {
let codedData = try! Data(contentsOf: fileURL as URL)
print(codedData)
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: codedData)
if unarchiver.containsValue(forKey: ViewController.linesKey) {
print("viewDidLoad contains value")
} else {
print("viewDidLoad doesn't conains value")
}
let fourLines = unarchiver.decodeObject(forKey: ViewController.linesKey) as! FourLines?
print(fourLines?.lines?.count)
}
let app = UIApplication.shared
NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: app)
}
#objc func applicationWillResignActive(notification: NSNotification) {
print("applicationWillResignActive")
let fileURL = self.dataFileUrl()
print(fileURL)
let fourLines = FourLines()
let array = (self.lineFields as NSArray).value(forKey: "text") as! [String]
fourLines.lines = array
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(fourLines, forKey: ViewController.linesKey)
let data = archiver.encodedData
do {
try data.write(to: fileURL as URL)
} catch {
print("Error is \(error)")
}
}
#IBOutlet var lineFields: [UITextField]!
func dataFileUrl() -> NSURL {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
var url: NSURL?
url = URL(fileURLWithPath: "") as NSURL
do {
try url = urls.first!.appendingPathComponent("data.archive") as NSURL
} catch {
print("Error is \(error)")
}
return url!
}
}
When I resign app encode method invokes:
FourLines encode
And when I try to load it, I see file is created and it contains value, but I always have nil while decode fourLines object:
322 bytes
viewDidLoad contains value
nil
And init? coder aDecoder and copy with zone never invoke.
What do I wrong?
Your issue is that you never initialized your lines array. Change its declaration to non optional and initialize it with an empty array. Try like this:
class FourLines: NSObject, NSCoding, NSSecureCoding, NSCopying {
static var supportsSecureCoding: Bool { return true }
private static let linesKey = "linesKey"
var lines: [String] = []
override init() { }
required init?(coder aDecoder: NSCoder) {
print(#function)
lines = aDecoder.decodeObject(forKey: FourLines.linesKey) as? [String] ?? []
}
func encode(with aCoder: NSCoder) {
print(#function)
aCoder.encode(lines, forKey: FourLines.linesKey)
}
func copy(with zone: NSZone? = nil) -> Any {
print(#function)
let copy = FourLines()
var newLines = Array<String>()
for line in lines {
newLines.append(line)
}
copy.lines = newLines
return copy
}
}
Playground testing:
let fourLines = FourLines()
fourLines.lines = ["line1","line2","line3","line4"]
let data = try! NSKeyedArchiver.archivedData(withRootObject: fourLines, requiringSecureCoding: true)
let decodedFourlInes = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! FourLines
decodedFourlInes.lines // ["line1", "line2", "line3", "line4"]
Btw If you are trying to persist your textfield values your ViewController should look something like this:
class ViewController: UIViewController {
#IBOutlet var lineFields: [UITextField]!
private static let dataFileUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("lines.plist")
override func viewDidLoad() {
super.viewDidLoad()
do {
let lines = (try NSKeyedUnarchiver
.unarchiveTopLevelObjectWithData(Data(contentsOf: ViewController.dataFileUrl)) as! FourLines)
.lines
var index = 0
lineFields.forEach {
$0.addTarget(self, action: #selector(editingDidEnd), for: .editingDidEnd)
$0.text = lines[index]
index += 1
}
} catch {
print(error)
}
}
#objc func editingDidEnd(_ textField: UITextField) {
print(#function)
let fourLines = FourLines()
fourLines.lines = lineFields.map { $0.text! }
do {
try NSKeyedArchiver
.archivedData(withRootObject: fourLines, requiringSecureCoding: true)
.write(to: ViewController.dataFileUrl)
} catch {
print(error)
}
}
}

How can I Save Json Data and Show Offline in swift

I'm trying to save my json file and show it to offline. I'm trying this code but it is not working for me ..
let myData = NSKeyedArchiver.archivedData(withRootObject: self.data)
UserDefaults.standard.set(myData, forKey: "userJson")
UserDefaults.standard.synchronize()
Can any one suggest me better way to save data and show off line ?
You should not save JSON in the UserDefault, Instead save it in file in document directory
I have created generic class which allows to do it easily
//
// OfflineManager.swift
//
//
// Created by Prashant on 01/05/18.
// Copyright © 2018 Prashant. All rights reserved.
//
import UIKit
class OfflineManager: NSObject {
static let sharedManager = OfflineManager()
let LocalServiceCacheDownloadDir = "LocalData"
enum WSCacheKeys {
case CampignList
case CampignDetail(id:String)
case ScreenShotList
var value:String {
switch self {
case .CampignList:
return "CampignList"
case .CampignDetail(let id):
return id
case .ScreenShotList :
return "ScreenShotList"
}
}
}
func getBaseForCacheLocal(with fileName:String) -> String? {
let filePath = FileManager.default.getDocumentPath(forItemName: self.LocalServiceCacheDownloadDir)
if FileManager.default.directoryExists(atPath: filePath) {
return filePath.stringByAppendingPathComponent(fileName)
} else {
if FileManager.default.createDirectory(withFolderName: self.LocalServiceCacheDownloadDir) {
return filePath.stringByAppendingPathComponent(fileName)
}
}
return nil
}
//------------------------------------------------------------
#discardableResult
func cacheDataToLocal<T>(with Object:T,to key:WSCacheKeys) -> Bool {
let success = NSKeyedArchiver.archiveRootObject(Object, toFile: getBaseForCacheLocal(with: key.value)!)
if success {
print( "Local Data Cached\(String(describing: getBaseForCacheLocal(with: key.value)))")
} else {
print("Error")
}
return success
}
//------------------------------------------------------------
func loadCachedDataFromLocal<T>(with key:WSCacheKeys ) -> T? {
return NSKeyedUnarchiver.unarchiveObject(withFile: getBaseForCacheLocal(with: key.value)!) as? T
}
//------------------------------------------------------------
func removeAllCacheDirs () {
do {
try FileManager.default.removeItem(atPath: self.getBaseForCacheLocal(with: "")!)
} catch {
print("error in remove dir \(error.localizedDescription)")
}
}
//--------------------------------------------------------------------------------
}
Here is some helper methods of extension FileManager
public var getDocumentDirectoryPath: String {
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
return documentDirectory
}
public func getDocumentPath(forItemName name: String)-> String {
return getDocumentDirectoryPath.stringByAppendingPathComponent(name)
}
public func directoryExists(atPath filePath: String)-> Bool {
var isDir = ObjCBool(true)
return FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir )
}
public func createDirectory(withFolderName name: String)-> Bool {
let finalPath = getDocumentDirectoryPath.stringByAppendingPathComponent(name)
return createDirectory(atPath: finalPath)
}
Here Is String extension's method
public func stringByAppendingPathComponent(_ path: String) -> String {
let fileUrl = URL.init(fileURLWithPath: self)
let filePath = fileUrl.appendingPathComponent(path).path
return filePath
}
How to use it ?
To save
OfflineManager.sharedManager.cacheDataToLocal(with: object as! [String:Any], to: .CampignList)
To read data
DispatchQueue.global().async {
// GET OFFLINE DATA
if let object:[String:Any] = OfflineManager.sharedManager.loadCachedDataFromLocal(with: .CampignList) {
do {
let data = try JSONSerialization.data(withJSONObject: object, options: [])
let object = try CampaignListResponse.init(data: data)
self.arrCampignList = object.data ?? []
DispatchQueue.main.async {
self.tableVIew.reloadData()
}
} catch {
}
}
}
Note: You can define your own WSCacheKeys for type of your json like i am fetching some campaign list
You can use Realm or CoraData for saving data and showing it when you are offline.
Here is the official link for Realm.You can learn from here.
https://realm.io/docs/swift/latest

Trying to archive an instance of a class conforming to NSCoder

I am working on my first Swift iOS app, having trouble serializing and saving an object whose JSON I fetch from the server. I am using Gloss, a lightweight JSON-parsing library which defines a Decodable protocol through which an instance can be instantiated from JSON. My intention is to load a thing from JSON (a type alias for [String : AnyObject]) by first extracting its id, and then check whether I already have a local archived copy. If I do, unarchive this and get the image. If not, make an asynchronous request for the image file.
The problem is that Thing.localArchiveExists(id) always returns false. Things are successfully instantiated but they always re-fetch the image. I have checked on the file system and confirmed that no archive files are being written. However, I am not seeing "ERROR. Could not archive", which suggests to me that the save succeeded. Am I missing something about how to archive and save NSCoder objects? Thanks!
Here is my implementation of the Decodable protocol:
// MARK: Decodable protocol
// When a thing is loaded from JSON, we load its image from archive if possible.
required init?(json: JSON) {
guard let id: Int = "id" <~~ json else { return nil}
if Thing.localArchiveExists(id) {
guard let savedThing = NSKeyedUnarchiver.unarchiveObjectWithFile(Thing.archiveFilePath(id)) as? Thing else { return nil }
self.id = savedThing.id
self.name = savedThing.name
self.image = savedThing.image
self.imageUrl = savedThing.imageUrl
super.init()
print("Loaded Thing \(self.name) from archive")
}
else {
guard let name: String = "name" <~~ json else { return nil}
guard let imageUrl: NSURL = "url" <~~ json else { return nil}
self.id = id
self.name = name
self.imageUrl = imageUrl
super.init()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let data = NSData(contentsOfURL: imageUrl)
dispatch_async(dispatch_get_main_queue(), {
self.image = UIImage(data: data!)
guard self.save() else {
print("ERROR. Could not archive")
return
}
print("Loaded Thing \(self.name) from server")
})
}
}
}
Here are relevant parts of the Thing class:
// MARK: Properties
var id: Int?
var name: String
var imageUrl: NSURL?
var image: UIImage?
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("things")
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let imageKey = "image"
static let imageUrlKey = "imageUrl"
static let idKey = "id"
}
// Returns the file URL at which a Thing with the given ID should be saved.
class func archiveFilePath(id: Int) -> String {
return Thing.ArchiveURL.URLByAppendingPathComponent("thing\(id)").absoluteString
}
// Checks whether an archived copy of a Thing with the given ID exists.
class func localArchiveExists(id: Int) -> Bool {
let fileManager = NSFileManager.defaultManager()
return fileManager.fileExistsAtPath(Thing.archiveFilePath(id))
}
// MARK: NSCoding
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(name, forKey: PropertyKey.nameKey)
if image != nil {
coder.encodeObject(image!, forKey: PropertyKey.imageKey)
}
if imageUrl != nil {
coder.encodeObject(imageUrl!, forKey: PropertyKey.imageUrlKey)
}
coder.encodeInteger(id!, forKey: PropertyKey.idKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let image = aDecoder.decodeObjectForKey(PropertyKey.imageKey) as? UIImage
let imageUrl = aDecoder.decodeObjectForKey(PropertyKey.imageUrlKey) as? NSURL
let id = aDecoder.decodeIntegerForKey(PropertyKey.idKey)
// Must call designated initializer.
self.init(name: name, image: image, imageUrl: imageUrl, id: id)
}
func save() -> Bool {
// For some reason I can't archive to file.
return NSKeyedArchiver.archiveRootObject(self, toFile: Thing.archiveFilePath(self.id!))
}
I figured out my problem: the save failed because I had not yet created the directory in which I was trying to save my Thing.
func save() -> Bool {
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(self)
do {
try NSFileManager.defaultManager().createDirectoryAtURL(Thing.ArchiveURL, withIntermediateDirectories: true, attributes: [:])
try archivedData.writeToFile(Thing.archiveFilePath(self.id!), options: [])
return true
} catch {
print(error)
return false
}
}

write plist file swift

i have a plist file, this file populate a form with settings; when I finish writing each textfield or switch, value is saved correctly, refresh my table correctly and my info its correct. But when i change the screen and return, my plist file is empty, i check several options:
...
let path = NSBundle.mainBundle().pathForResource("perfilBitrubian", ofType: "plist")
descripcionCelda.writeToFile(path!, atomically: false)
or
...
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("perfilBitrubian.plist")
descripcionCelda.writeToFile(path!, atomically: false)
My list has hidden options; when i hide and show my table refresh data correctly, but if i change the process and return the form; my plist file don't have info.
i see this examples and I do not know what to do.
http://rebeloper.com/read-write-plist-file-swift/
and this:
http://www.appcoda.com/expandable-table-view/
Thanks!
Several things:
First, your first code block won't work. The app bundle is read-only. Trying to write a file to the bundle will always fail.
Next, property lists can only contain a very small set of objects, all of which must be NSObjects. Do a search on "property list object" in Xcode for more info.
The function writeToFile that you are calling (on a dictionary or array?) returns a Bool. Check the result to see if it's failing.
These are my methods, you have to pass the plist name (without extension), to write also pass the key and a dictionary or an array.
The plist is automatically copied to documents directory if not already done before:
public static func plistRead(plistName: String) -> [String: AnyObject]? {
let path = documentsPath().stringByAppendingPathComponent(plistName + ".plist")
let fileManager = NSFileManager.defaultManager()
if !(fileManager.fileExistsAtPath(path)) {
if let bundlePath = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist") {
do {
try fileManager.copyItemAtPath(bundlePath, toPath: path)
} catch let error as NSError {
print("Can't move plist from bundle to documents directory: " + error.localizedDescription)
}
} else {
print("No plist found!")
}
}
return NSDictionary(contentsOfFile: path) as? [String: AnyObject]
}
public static func plistWrite(plistName: String, key: String, data: AnyObject?) -> Bool {
let path = documentsPath().stringByAppendingPathComponent(plistName + ".plist")
let fileManager = NSFileManager.defaultManager()
if !(fileManager.fileExistsAtPath(path)) {
if let bundlePath = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist") {
do {
try fileManager.copyItemAtPath(bundlePath, toPath: path)
} catch let error as NSError {
print("Can't move plist from bundle to documents directory: " + error.localizedDescription)
return false
}
} else {
print("No plist found!")
return false
}
}
if let savedStock = NSMutableDictionary(contentsOfFile: path) {
if let data = data { savedStock.setObject(data, forKey: key) }
else { savedStock.removeObjectForKey(key) }
if savedStock.writeToFile(path, atomically: true) { return true }
else {
print("Can't save file at path: \(path)")
return false
}
}
else {
print("Can't create dictionary!")
return false
}
}
Update swift 3
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentDirectory = paths[0] as! String
let path = documentDirectory.appending("myData.plist")
let fileManager = FileManager.default
if(!fileManager.fileExists(atPath: path)){
if let bundlePath = Bundle.main.path(forResource: "myData", ofType: "plist"){
let result = NSMutableDictionary(contentsOfFile: bundlePath)
print("Bundle file myData.plist is -> \(result?.description)")
do{
try fileManager.copyItem(atPath: bundlePath, toPath: path)
}catch{
print("copy failure.")
}
}else{
print("file myData.plist not found.")
}
}else{
print("file myData.plist already exits at path.")
}
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("load myData.plist is ->\(resultDictionary?.description)")
let myDict = NSDictionary(contentsOfFile: path)
if let dict = myDict{
myItemValue = dict.object(forKey: myItemKey) as! String?
txtValue.text = myItemValue
}else{
print("load failure.")
}
Read and write plist file switf
class MainTableViewController: UITableViewController {
//MARK:- Properties
//reading data
var tableData = [String]()
//writing data
var dicData: NSMutableDictionary = ["name" : "data"]
//MARK:- View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
/* converting path to document directory since plist in bundle can't be modified */
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let path : NSString = documentsDirectory.appendingPathComponent("TableDataPropertyList.plist") as NSString
/* getting the path and paste the path in finder to see the plist in document directory */
print(" \(path) ")
dicData.write(toFile: path as String, atomically: false)
//path of plist in bundle to read
let mainPath = Bundle.main.path(forResource: "TableDataPropertyList", ofType: "plist")
let dict = NSDictionary(contentsOfFile: (mainPath)! as String)
tableData = dict!.object(forKey: "AppleDevice") as! [String]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath as IndexPath)
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
}

Resources