I have an app that stores some information in coredata and reads them.
I'm writing a message extension of this application and I'd like to have this extension reading the same data but I always have empty response.
Here is the code I'm using in the main app:
context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
fetchImages(Date()){ (array, arrayData) in
for image in array{
imagesArray.insert(image, at:0)
}
}
I'm using exactly the same code in the extension but it does not read the data.
What I'm wondering about is that I'm not using the appGroupIdentifier anywhere in the code.
How can I do to achieve that?
Thanks.
Here is the code of fetchImages function:
func fetchImages(_ predicate:Date, completion:(_ array:[Image], _ arrayData:NSArray)->()){
var arrData = [NSManagedObject]()
var existingImages = [Image]()
let request :NSFetchrequest<NSFetchrequestResult> = NSFetchrequest(entityName: "Photo")
do {
let results = try context?.fetch(request)
var myImage = Image()
if ((results?.count) != nil) {
for result in results! {
myImage.imageUrl = (resultat as! NSManagedObject).value(forKey:"url") as! String
myImage.imageFileName = (resultat as! NSManagedObject).value(forKey:"imageFileName") as! String
existingImages.append(myImage)
arrData.append(result as! NSManagedObject)
}
} else{
print ("No photo.")
}
completion(existingImages, arrData as NSArray)
} catch{
print ("Error during CoreData request")
}
}
Turning on app groups is the first step, but now you need to tell Core Data to use the app group.
First you get the location of the shared group container, from FileManager. Use containerURL(forSecurityApplicationGroupIdentifier:) to get a file URL for the directory.
You can use that URL without changes if you want. It's probably a good idea to create a subdirectory in it to hold your Core Data files. If you do that, add a directory name to the URL with the appendingPathComponent() method on URL. Then use FileManager to create the new directory with the createDirectory(at:withIntermediateDirectories:attributes:) method.
Now that you have a shared directory to use, tell NSPersistentContainer to put its files there. You do that by using NSPersistentStoreDescription. The initializer can take a URL that tells it where to store its data.
Your code will be something approximating this:
let directory: URL = // URL for your shared directory as described above
let containerName: String = // Your persistent container name
let persistentContainer = NSPersistentContainer(name: containerName)
let persistentStoreDirectoryUrl = directory.appendingPathComponent(containerName)
guard let _ = try? FileManager.default.createDirectory(at: persistentStoreDirectoryUrl, withIntermediateDirectories: true, attributes: nil) else {
fatalError()
}
let persistentStoreUrl = persistentStoreDirectoryUrl.appendingPathComponent("\(containerName).sqlite")
let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreUrl)
persistentContainer.persistentStoreDescriptions = [ persistentStoreDescription ]
persistentContainer.loadPersistentStores {
...
}
Related
I am very new to learning to code in Swift. I am trying to make an application that keeps a list of people who are coming in. I want it to log the name they input, time of visit, and the nature of their visit. However, I want this to be able to be exported to a program like Numbers or Excel. I have found some info on storing the inputs from the user but those seem to get deleted if the app is closed. I can't seem to find any other info, but perhaps I'm just searching the wrong info. Any help or guidance is appreciated.
Just store your log in a Array and then with this function you safe it to a .csv file.
func saveCSV(_ name : String,_ customUrl : URL) -> Bool {
let fileName = "\(name).csv"
let b = customUrl.appendingPathComponent(fileName)
var csvText = ""
var id = "1"
var name = "test"
csvText = "ID,Name\n"
let newLine = "\(id),\(name))\n"
csvText.append(newLine)
//or create a loop
// Task is my custom Struct
var array : [Task]
for task in customArray {
let newLine = "\(task.ean),\(task.menge),\(task.name)\n"
csvText.append(newLine)
}
do {
try csvText.write(to: b, atomically: true, encoding: String.Encoding.utf8)
return true
} catch {
print("Failed to create file")
print("\(error)")
return false
}
}
func createDic()->URL?{
let documentsPath1 = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
// CSV is the folder name
let logsPath = documentsPath1.appendingPathComponent("CSV")
do {
try FileManager.default.createDirectory(atPath: logsPath!.path, withIntermediateDirectories: true, attributes: nil)
return logsPath
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
return nil
}
var customUrl = createDic()
and now you can call it :
if saveCSV(userList, customUrl){
print("success")
}
and after this you can do what you want with the .csv file
Writing to the file system allows users to write and retrieve string, image, etc., files to and from the application on their devices.
The images I write and retrieve to the app that appear when I run the simulator, for example, are unique to that app bundle, and differ from the images I write and retrieve from my device.
When I research how to write and read image files with Realm I am told not to save the image but the path. Images are too large to store. Got it.
But the file path I am saving naturally returns data native to the specific app bundle-- or returns nil. How might I write and retrieve image files/paths to and from Realm if the file path is unique to the device(s)? Do I need a separate image array?
First View Controller
let planet = planets[indexPath.item]
cell.name.text = planet.name
cell.system.text = planet.system
let imageData = (self.getDirectoryPath() as NSString).appendingPathComponent("image")
if self.fileManager.fileExists(atPath: imageData) {
// Populates every cell with the same image
cell.earthImage.image = UIImage(contentsOfFile: imageData)
// returns nil
cell.earthImage.image = UIImage(contentsOfFile: planet.image)
}
// Retrieve directory path for image
func getDirectoryPath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}
Second View Controller
let planet = Planet()
self.realm = try! Realm(configuration: config(user: SyncUser.current!))
planet.name = self.name.text!
planet.system = self.system.text!
let imageData = (self.getDirectoryPath() as NSString).appendingPathComponent("person")
if self.fileManager.fileExists(atPath: imageData) {
self.earthImage.image = UIImage(contentsOfFile: imageData)
// Why does this not write to realm?
planet.image = imageData
}
try! self.realm.write {
self.realm.add(planet)
}
Here, I am posting from the secondViewController where I would like to render these values in my collectionView in the firstViewController's numberOfItemsInSection_:) method.
Here's the model:
class Planet: Object {
#objc dynamic var name = String()
#objc dynamic var system = String()
#objc dynamic var image = String()
}
I retrieve translation from a remote server and save this in
Application\ Support/Translation/Translation.plist
What I basically want to do in my app is use something like
translate(input: "hello")
In order to translate hello to the translation that is saved in my plist file. I created a function but I always get nil when reading the contents. Anyone who knows what I am doing wrong?
import Foundation
open class Translations {
static func translate(input: String) -> String {
var translations: [String: String] = [:] //Translation data
let documentsDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
let directoryURL = documentsDirectory?.appendingPathComponent("Translation")
let file = directoryURL?.appendingPathComponent("Translation").appendingPathExtension("plist")
if let plistXML = FileManager.default.contents(atPath: (file?.absoluteString)!) {
do {//convert the data to a dictionary and handle errors.
translations = try PropertyListSerialization.propertyList(from: plistXML, options: [], format: nil) as! [String:String]
} catch {
print("Error reading plist: \(error)")
}
}
guard let translation = translations[input] else {
return input
}
return translation
}
}
You are reading from the Documents directory, where as you say earlier in your post your file does not reside in. Instead it resides in the Application Support directory.
Try to make sure you are saving and reading from the same location.
I would also recommend using an extension to String to make translating easier, like so:
extension String {
var translated: String {
return Translation.default?.translate(self) ?? self
}
}
Then you can simply do:
"SomeText".translated
This is how I'd implement translations:
public final class Translation {
static let `default`: Translation? = Translation()
let translations: [String: String]
init?() {
guard let documentsURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
return nil
}
let translationURL = documentsURL.appendingPathComponent("Translation").appendingPathComponent("Translation").appendingPathExtension("plist")
do {
let data = try Data(contentsOfURL: translationURL)
let propertyList = try PropertyListSerialization(from: data, options: [], format: nil)
if let list = propertyList as? [String: String] {
translations = list
} else {
return nil
}
} catch {
// Handle error
return nil
}
}
func translate(_ input: String) -> String {
guard let translated = translations[input] else {
return input
}
return translated
}
}
This has the advantage that you're not reading the propertyList every time from disk you want to run a translation. Keep in mind that this implementation of mine does not provide any support for refreshing the data once the app is running.
Alternatively you could move the init code to a separate method, and removing the nullability of the init method. Then whenever a new propertyList is downloaded you could simply call -refresh() or whatever you want really.
I'd like to use readStringFromURL method to obtain a file from a plist and then use it on insertDataInArrayFromPlist in order to display it or put it on CoreData, substituting let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension).
the ISSUE the try statement gives me this ERROR
Argument labels '(contentsOfURL:, usedEncoding:)' do not match any available overloads
in my viewDidLoad:
let obtainedfile = readStringFromURL(stringURL: kremoteSamplePlist)
print(obtainedfile ?? "nothing to print")
I retrive the file from web
func readStringFromURL(stringURL:String)-> String!{
if let url = NSURL(string: stringURL) {
do {
return try String(contentsOfURL: url, usedEncoding: nil)
} catch {
print("Cannot load contents")
return nil
}
} else {
print("String was not a URL")
return nil
}
}
then I put the data in a struct
func insertDataInArrayFromPlist(arrayOfEntities: inout [product]) {
let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension)
let localArray = NSArray(contentsOfFile: path!)!
for dict in localArray {
var futureEntity = product()
let bdict = dict as! [String: AnyObject]
futureEntity.name = bdict["Name"] as? String
futureEntity.ProductId = bdict["Product Id"] as? String
arrayOfEntities.append(futureEntity)
}
for element in arrayOfEntities {
print("name is \(element.name!), the id is \(element.ProductId!)")
}
}
Theres a library available via Cocoapods, CSV.swift by Yaslab. Allows you to import a csv directly in Swift code and convert to a data type of your own. Does the job for me.
https://github.com/yaslab/CSV.swift
I cannot retrieve an object saved as a NSkeyed archive in Swift 3, and am scracthcing my head. The object is successfully saved as a plist, but returned as nil when loading back in.
Here is the code I use:
The class itself to be saved as an object is fairly easy:
import Foundation
class ItemList:NSObject, NSCoding {
var name: String = "" //Name of the Item list
var contents: [Int] = [] //Ints referencing the CoreData PackItems
init (listname:String, ContentItems:[Int]) {
self.name=listname
self.contents=ContentItems
}
//MARK: NSCoding
public convenience required init?(coder aDecoder: NSCoder) {
let thename = aDecoder.decodeObject(forKey: "name") as! String
let thecontents = aDecoder.decodeObject(forKey: "contents") as! [Int]
self.init(listname: thename,ContentItems: thecontents)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name,forKey:"name")
aCoder.encode(self.contents, forKey: "contents")
}
}
The code to load and save the object:
class FileHandler: NSObject {
class func getDocumentsDirectory() -> URL {
let filemgr = FileManager.default
let urls = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let result:URL = urls.first!
return result
}
///This returns the contents of the handed file inside the Documents directory as the object it was saved as.
class func getFileAsObject(filename:String) -> AnyObject? {
let path = getDocumentsDirectory().appendingPathComponent(filename)
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.absoluteString) {
//Success
print("Loaded file '"+filename+"' from storage")
return result as AnyObject?
} else {
print("Error: Couldn't find requested object '"+filename+"' in storage at "+path.absoluteString)
return nil
}
}
///This saves the handed object under the given filename in the App's Documents directory.
class func saveObjectAsFile(filename:String, Object:AnyObject) {
let data = NSKeyedArchiver.archivedData(withRootObject: Object)
let fullPath = getDocumentsDirectory().appendingPathComponent(filename)
do {
try data.write(to: fullPath)
print("Wrote file '"+filename+"' to storage at "+fullPath.absoluteString)
} catch {
print("Error: Couldn't write file '"+filename+"' to storage")
}
}
}
...and finally, this is what I do to call it all up:
let testobject:ItemList = ItemList.init(listname: "testlist", ContentItems: [0,0,1,2])
FileHandler.saveObjectAsFile(filename:"Test.plist",Object:testobject)
let tobi = FileHandler.getFileAsObject(filename:"Test.plist") as! ItemList
Alas, I get this as output:
Wrote file 'Test.plist' to storage at file:///…/data/Containers/Data/Application/6747B038-B0F7-4B77-85A8-9EA02BC574FE/Documents/Test.plist
Error: Couldn't find requested object 'Test.plist' in storage at file:///…/data/Containers/Data/Application/6747B038-B0F7-4B77-85A8-9EA02BC574FE/Documents/Test.plist
Note that this is my own output -- so I do (and have checked) that the file was created correctly. But it just won't load. Can anyone tell me what I am doing wrong?
The problem is with the path you pass to unarchiveObject(withFile:).
Change:
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.absoluteString) {
to:
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.path) {
On a side note, you should use symmetric APIs for your writing and reading logic. When you write the data you archive a root object to a Data object and then write the Data object to a file. But when reading, you directly unarchive the object tree given a file path.
Either change your writing code to use archiveRootObject(_:toFile:) or change the reading code to load the Data from a file and then unarchive the data. Your current code works (once you fix the path issue) but it's not consistent.