I need help to save images in cloudkit - swift

In my application the user is able to select an image within his gallery and save it in a UIMAGEVIEW now the problem is that when I save that image in cloudkit gives me the following error
"Call can throw, but it is not marked with 'try' and the error is not handled"
#IBAction func Save(_ sender: Any) {
let codig = code.text
let precio = price.text
let imagen = imageCover.image
let record = CKRecord(recordType: "Productos", zoneID: zona.zoneID)
record.setObject(codig as __CKRecordObjCValue?, forKey: "code")
record.setObject(precio as __CKRecordObjCValue?, forKey: "costo")
let mngr = FileManager.default
let dir = mngr.urls(for: .documentDirectory, in: .userDomainMask)
let file = dir[0].appendingPathComponent("myimage").path
imagen?.jpegData(compressionQuality: 0.5)?.write(to: file as! URL)
let imgURL = NSURL.fileURL(withPath: file)
let imageAsset = CKAsset(fileURL: imgURL)
record.setObject(imageAsset, forKey: "imagecover")
self.navigationItem.backBarButtonItem?.isEnabled = false
database.save(record) { (record, error) in
DispatchQueue.main.async {
self.navigationItem.backBarButtonItem?.isEnabled = true
if let error = error {
print("Error \(error.localizedDescription)")
} else {
print("Save")
self.navigationController?.popViewController(animated: true)
}
}
}

Your problem is the following line:
imagen?.jpegData(compressionQuality: 0.5)?.write(to: file as! URL)
As you can see in the documentation(https://developer.apple.com/documentation/foundation/data/1779858-write) the function write(to:) can throw an error. You need to handle the error by either ignoring it
try? <function that throws error>
or by catching it using a do..catch block:
do {
try <function that throws error>
} catch {
// Handle error
}

Related

How to add or delete rule of safari content block list at run time

I am using in my project safari content blocker extension. when i set the rule in blockerList.json file statically and run the project every thing is working fine. Now i want to set my rule dynamically using the technic as it describes in below.
Guys please help me out to set the rule dynamically at run time.
I try this but i am getting an error when
load from viewcontroller class
fileprivate func saveRuleFile() {
let ruleList = [["trigger":["url-filter": ".*"],"action":["type": "block"]]]
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let encoded = try? encoder.encode(ruleList) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.****.***")
print("sharedContainerURL = \(String(describing: sharedContainerURL))")
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
if let destinationURL = sharedContainerURL?.appendingPathComponent("Rules.json") {
do {
try encoded.write(to: destinationURL)
} catch {
print (error)
}
}
}
}
And write this in ContentBlockerRequestHandler class
func beginRequest(with context: NSExtensionContext) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.****.***")
let sourceURL = sharedContainerURL?.appendingPathComponent("Rules.json")
let ruleAttachment = NSItemProvider(contentsOf: sourceURL)
let item = NSExtensionItem()
item.attachments = ([ruleAttachment] as! [NSItemProvider])
context.completeRequest(returningItems: [item], completionHandler: nil)
}
i try to load using
SFContentBlockerManager.reloadContentBlocker(withIdentifier: "com.app.*****", completionHandler: {(error) in
if error != nil{
print("error: \(error.debugDescription)")
}
})
when try to execute 3rd number block at run time i'm getting an error. But i go to the file path and checked the json is absolutely fine, its a valid json there.
Error Domain=WKErrorDomain Code=2 "(null)" UserInfo={NSHelpAnchor=Rule list compilation failed: Failed to parse the JSON String.}
Try to use JSONSerialization. It work great for me :)
fileprivate func saveRuleFile() {
let ruleList = [["trigger":["url-filter": ".*"],"action":["type": "block"]]]
let jsonAsData = try! JSONSerialization.data(withJSONObject: ruleList)
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.****.***")
print("sharedContainerURL = \(String(describing: sharedContainerURL))")
if let destinationURL = sharedContainerURL?.appendingPathComponent("Rules.json") {
do {
try jsonAsData.write(to: destinationURL)
} catch {
print (error)
}
}
}

Why after updating pods do I keep getting Value of type 'StorageMetadata' has no member 'downloadURL'?

I know this is because the StorageMetadata is not being used anymore. I saw other generic answers to similar questions point to this firebase documentation.: https://firebase.google.com/docs/storage/ios/upload-files I tried to apply it to my code, but it doesn't work. How should I apply it to my current function?
{
let uid = Auth.auth().currentUser!.uid
let newPostRef = Database.database().reference().child("people").child(uid).child("PhotoPosts")
let newPostRef1 = Database.database().reference().child("people").child(uid).child("PhotoPosts1")
let newPostKey = newPostRef.key
if let imageData = image.jpegData(compressionQuality: 0.001){
let imageStorageRef = Storage.storage().reference().child("images").child(uid)
let newImageRef = imageStorageRef.child(newPostRef.key!)
let newImageRef1 = imageStorageRef.child(newPostRef1.key!)
newImageRef.putData(imageData).observe(.success, handler: {(snapshot) in
self.imageDownloadURL = snapshot.metadata?.downloadURL()?.absoluteString
newPostRef.setValue(self.imageDownloadURL as Any)
})
newImageRef1.putData(imageData).observe(.success, handler: {(snapshot) in
self.imageDownloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let keyToPost = Database.database().reference().child("people").child(uid).childByAutoId().key
let f1: [String: Any] = [(keyToPost) : self.imageDownloadURL as Any]
newPostRef1.updateChildValues(f1)
})
let caption = ServerValue.timestamp() as! [String : Any]
Database.database().reference().child("people").child(uid).child("caption").setValue(caption)
}
}
Like I mentioned, I tried to apply the firebase documentation to my function as shown below. It still gives basically the same error. Where am I going wrong?
func save() {
let uid = Auth.auth().currentUser!.uid
let newPostRef = Database.database().reference().child("people").child(uid).child("PhotoPosts")
let newPostRef1 = Database.database().reference().child("people").child(uid).child("PhotoPosts1")
let newPostKey = newPostRef.key
if let imageData = image.jpegData(compressionQuality: 0.001){
let imageStorageRef = Storage.storage().reference().child("images").child(uid)
let newImageRef = imageStorageRef.child(newPostRef.key!)
let newImageRef1 = imageStorageRef.child(newPostRef1.key!)
newImageRef.downloadURL { url, error in
if let error = error {
} else {
newImageRef.putData(imageData).observe(.success, handler: {(snapshot) in
self.imageDownloadURL = snapshot.metadata?.downloadURL()?.absoluteString
newPostRef.setValue(self.imageDownloadURL as Any)
})
// Here you can get the download URL for 'simpleImage.jpg'
}
}
....
UPDATE after first answer: This is what I tried for the double database entry part:
newImageRef1.putData(imageData).observe(.success, handler: {(snapshot) in
newImageRef1.downloadURL { (url, error) in
guard let downloadURL = url else {
return
}
let keyToPost = Database.database().reference().child("people").child(uid).childByAutoId().key
let f1: [String: Any] = [(keyToPost) : self.imageDownloadURL as Any]
newPostRef1.updateChildValues(f1)
}
})
I get following error at let f1: Cannot convert value of type 'String?' to expected dictionary key type 'String'
You're getting:
Value of type 'StorageMetadata' has no member 'downloadURL'
If you look at the documentation for StorageMetadata you'll see that it indeed doesn't have a downloadURL member. This member was dropped in the SDK updates of May 2018, so is long gone.
The correct way to get the download URL is shown in the documentation on uploading data:
// Upload the file to the path "images/rivers.jpg"
let uploadTask = riversRef.putData(data, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
return
}
// Metadata contains file metadata such as size, content-type.
let size = metadata.size
// You can also access to download URL after upload.
riversRef.downloadURL { (url, error) in
guard let downloadURL = url else {
// Uh-oh, an error occurred!
return
}
}
}
In your case, that'd be something like:
newImageRef.putData(imageData).observe(.success, handler: {(snapshot) in
newImageRef.downloadURL { (url, error) in
guard let downloadURL = url else {
return
}
newPostRef.setValue(url)
}
})

Error when working with NSManagedObjectContext in background thread

So I keep getting this error when I save to core data.
Coredata[21468:13173906] [error] error: SQLCore dispatchRequest: exception handling request: , ** -_referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]! with userInfo of (null)
I recently finished a course on core data, the instructor brush a bit on the topic that core data isn't thread safe. So what he suggested is a child/parent context, so that is what I tried doing but kept getting the error from above. This is how my code looks.
struct Service {
static let shared = Service()
func downloadPokemonsFromServer(completion: #escaping ()->()) {
let urlString = "https://pokeapi.co/api/v2/pokemon?limit=9"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to fetch pokemon", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonJSON = try decoder.decode(PokemonsJSON.self, from: data)
pokemonJSON.pokemons.forEach { (JSONPokemon) in
//Works fine with privateContext here
let pokemon = Pokemon(context: privateContext)
pokemon.name = JSONPokemon.name
pokemon.url = JSONPokemon.detailUrl
}
//Why does the privateContext work here
//and doesn't crash my app.
try privateContext.save()
try privateContext.parent?.save()
completion()
} catch let err {
print("Unable to decode PokemonJSON. Error: ",err)
completion()
}
}.resume()
}
func fetchMoreDetails(pokemon: Pokemon, urlString: String, context: NSManagedObjectContext, completion: #escaping ()->()) {
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to get more details for pokemon", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
pokemonDetailJSON.types.forEach { (nestedType) in
// I can just change this to context and it works
// Why doesnt it work with privateContext and crashes my app
let type = Type(context: privateContext)
type.name = nestedType.type.name
pokemon.addToTypes(type)
}
try privateContext.save()
try privateContext.parent?.save()
completion()
} catch let err {
print("Unable to decode pokemon more details", err)
completion()
}
}.resume()
}
}
So in the downloadPokemonsFromSever, I make a call to an api that parse the json and saves it into Coredata. I followed my instructor instructions, by creating a privateContext and then setting its parent to my mainContext. Then I create a new Pokemon that has a name & url using my privateContext and NOT my mainContext. When I completely parse my Pokemon I go into another api that has more details on that Pokemon.
This is where my app starts to crash. As you can see from the fetchMoreDetails there is a parameter that is context. When I try to create a new Type with privateContext it crashes my app. When I use the context that is passed through it works fine. I would like to know why privateContext works inside downloadPokemonFromServer and not in fetchMoreDetails. I left a comment above the line that I think that crashes my app. This is how I call it in my ViewController, using this action.
#objc func handleRefresh() {
Service.shared.downloadPokemonsFromServer {
let context = CoreDataManager.shared.persistentContainer.viewContext
self.pokemonController.fetchedObjects?.forEach({ (pokemon) in
Service.shared.fetchMoreDetails(pokemon: pokemon, urlString: pokemon.url ?? "", context: context) {
}
})
}
tableView.refreshControl?.endRefreshing()
}

Writing data to a file with path using NSKeyedArchiver archivedData throws Unrecognized Selector for Swift 4.2

I am attempting to use the NSKeyedArchiver to write a Codable to disk.
All the questions I could find on the subject using deprecated methods. I can't find any SO questions or tutorials using the Swift 4 syntax.
I am getting the error
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
Which I am guessing is the try writeData.write(to: fullPath) line in my UsersSession class.
What is the proper way to write data to a file in Swift 4.2?
struct UserObject {
var name : String?
}
extension UserObject : Codable {
enum CodingKeys : String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
}
}
UserSession.swift
class UserSession {
static let shared = UserSession()
let fileName = "userdata.dat"
var user : UserObject?
lazy var fullPath : URL = {
return getDocumentsDirectory().appendingPathComponent(fileName)
}()
private init(){
print("FullPath: \(fullPath)")
user = UserObject()
load()
}
func save(){
guard let u = user else { print("invalid user data to save"); return}
do {
let writeData = try NSKeyedArchiver.archivedData(withRootObject: u, requiringSecureCoding: false)
try writeData.write(to: fullPath)
} catch {
print("Couldn't write user data file")
}
}
func load() {
guard let data = try? Data(contentsOf: fullPath, options: []) else {
print("No data found at location")
save()
return
}
guard let loadedUser = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UserObject else {
print("Couldn't read user data file.");
return
}
user = loadedUser
}
func getDocumentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
}
Since you are using Codable, you should first encode to Data and then archivedData. Here is the code:
func save(){
guard let u = user else { print("invalid user data to save"); return}
do {
// Encode to Data
let jsonData = try JSONEncoder().encode(u)
let writeData = try NSKeyedArchiver.archivedData(withRootObject: jsonData, requiringSecureCoding: false)
try writeData.write(to: fullPath)
} catch {
print("Couldn't write user data file")
}
}
func load() {
guard let data = try? Data(contentsOf: fullPath, options: []) else {
print("No data found at location")
save()
return
}
guard let loadedUserData = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Data else {
print("Couldn't read user data file.");
return
}
// Decode Data
user = try? JSONDecoder().decode(UserObject.self, from: loadedUserData)
}

Is it possible to load images recovered from a server in runtime into a plane object?

I have been asked to build an app that shows a catalog with AR, so what I need to do is pretty simple: when an user chooses a product I must load the image recovered in base64 from the server into a plane object. Is this possible with swift - arkit ? Or are all the sprites/images/textures required to be previously loaded into the assets folder?
You can definitely download resources from a server, save them to the device (e.g in NSDocumentsDirectory), and then load with the file URL. I do it for a similar use case as yours -at least it sounds so, per the description you gave-
EDIT
Here's the relevant code. I use Alamofire to download from the server and ZIPFoundation for unzipping. I believe that if you just need to download an image, it'll be a bit simpler, probably not needing the unzip part.
let modelsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func loadNodeWithID(_ id: String, completion: #escaping (SCNNode?) -> Void) {
// Check that assets for that model are not already downloaded
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
let dirExists = fileManager.fileExists(atPath: dirForModel.path)
if dirExists {
completion(loadNodeWithIdFromDisk(id))
} else {
let dumbURL = "http://yourserver/yourfile.zip"
downloadZip(from: dumbURL, at: id) {
if let url = $0 {
print("Downloaded and unzipped at: \(url.absoluteString)")
completion(self.loadNodeWithIdFromDisk(id))
} else {
print("Something went wrong!")
completion(nil)
}
}
}
}
func loadNodeWithIdFromDisk(_ id: String) -> SCNNode? {
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
do {
let files = try fileManager.contentsOfDirectory(atPath: dirForModel.path)
if let objFile = files.first(where: { $0.hasSuffix(".obj") }) {
let objScene = try? SCNScene(url: dirForModel.appendingPathComponent(objFile), options: nil)
let objNode = objScene?.rootNode.firstChild()
return objNode
} else {
print("No obj file in directory: \(dirForModel.path)")
return nil
}
} catch {
print("Could not enumarate files or load scene: \(error)")
return nil
}
}
func downloadZip(from urlString: String, at destFileName: String, completion: ((URL?) -> Void)?) {
print("Downloading \(urlString)")
let fullDestName = destFileName + ".zip"
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let fileURL = modelsDirectory.appendingPathComponent(fullDestName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
let error = response.error
if error == nil {
if let filePath = response.destinationURL?.path {
let nStr = NSString(string: filePath)
let id = NSString(string: nStr.lastPathComponent).deletingPathExtension
print(response)
print("file downloaded at: \(filePath)")
let fileManager = FileManager()
let sourceURL = URL(fileURLWithPath: filePath)
var destinationURL = modelsDirectory
destinationURL.appendPathComponent(id)
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
completion?(destinationURL)
} catch {
completion?(nil)
print("Extraction of ZIP archive failed with error: \(error)")
}
} else {
completion?(nil)
print("File path not found")
}
} else {
// Handle error
completion?(nil)
}
}
}