Send a struct array to widget on iOS 14 - swift

I have a struct array, for example:
struct Note {
let id: Int
let text: String
let timestamp: Int64
}
I want to send to the widget. As I searched, I can send an array through AppGroup with userDefaults, I need some tricks to send the struct array with it, but I need the its model on the widget side as well, which I don't have access to it.
Now, I want to know what is the best way to do that? convert it to Json and send via FileManager and encode it again on the widget side? Or use CoreData? or any other suggestion?
Thank you so much for your help in advance.

Assume you already set up your AppGroup; if not, check out this article Sharing data with a Widget
First, make your struct Codable:
struct Note: Codable {
let id: Int
let text: String
let timestamp: Int64
}
You could put your Struct file in a package and add it as your framework so you can use it in both your main app and your widget. Or you can add the file target membership on both your main app and your widget extension.
Then convert it to JSON, write to the File via FileManager, sample code: How to read files created by the app by iOS WidgetKit?
//Init your Note array
//Add your code here <-
let encoder = JSONEncoder()
do {
let jsonData = try encoder.encode(/*your note array*/)
// Write to the file system, you can follow the article above or make your own.
// Add your code here <-
WidgetCenter.shared.reloadTimelines(ofKind: "/*Your widget kind*/")
} catch {
Log.error("Save widget error: \(error.localizedDescription)")
}
Lastly, Decode in your widget timeline or a separate function:
if let jsonData = //Read your saved JSON file {
let decoder = JSONDecoder()
do {
let model = try decoder.decode([Note].self, from: jsonData)
// Do whatever you need to do with your model
} catch {
Log.error("Read widget error: \(error.localizedDescription)")
//Should display an error view
}
} else {
//Should display an error view
}

Related

In a SwiftUI Document App, how to save a document from within a function

The current version of Xcode (version 12.5.1) provides a template for a Document Based App for macOS providing the following document model:
struct MyDocument: FileDocument {
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
I want to add a method to this struct that passes my document to an external program, also saving the document before doing so:
func passMyDocumentToProgram() {
// Save document
// Pass document to external program
}
The problem is I don't know how to save a document like this.
The resulting app (built from the template) provides functionality (in the menu bar) to save a document, so I should be calling this existing functionality somehow.
From my understanding, the fileWrapper method in MyDocument returns a FileWrapper that has a write() method that can be used to save the document; however, the fileWrapper method requires a WriteConfiguration, and I don't how to create this. The documentation for WriteConfiguration is quite sparse and I have not been able to find anything fruitful online.
Update. The better question is how do I trigger my Document App to auto-save?
I figured out I can save my document with something like FileWrapper(regularFileWithContents: data).write( ... ), but this is a bad idea because your app will give you an error saying an external program modified the file.
SwiftUI Document Apps written with the FileDocument protocol auto-save their documents on certain events, like (un)focusing a window, so I'd like to know if there is a way I can trigger such an auto-save programatically.
Following a similar procedure to https://stackoverflow.com/a/68331797/16524160, we can try to get the implementation used by the Save... menu entry. Looking at
let menu = NSApp.mainMenu!.items.first(where: { $0.title == "File" })!
let submenu = menu.submenu!.items.first(where: { $0.title == "Saveā€¦" })!
submenu.target // nil
submenu.action // saveDocument:
I came up with the following method for MyDocument:
func save() {
NSApp.sendAction(#selector(NSDocument.save(_:)), to: nil, from: nil)
}
Note saveDocument has been renamed to save. From my understanding, this tries to send the save() selector to the first object in the key window which can react to it. In my case, the key window will contain the document user is editing, so this will save the document.

Issue with UserDefaults (converting data to array and back)

What I want to do:
I want to get an array from UserDefaults that I saved beforehand and append a custom object to it. Afterwards I want to encode it as a Data-type again and set this as the UserDefaults Key again.
My problem:
The encoding part is what is not working as intended for me.
It says: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000011a540
But I do not know how to fix this.
Below is my code for more context:
do {
let decoded = defaults.object(forKey: "ExArray") as! Data
var exo = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! [Exerc]
exo.append(datas[indexPath.row])
let enco = try NSKeyedArchiver.archivedData(withRootObject: exo, requiringSecureCoding: false) <- Here is the error
defaults.set(enco, forKey: "ExArray")
} catch {
print("Error encoding custom object NOSEARCHO")
}
This is how Exerc looks:
struct Exerc: Codable {
var title: String
var exID: String
}
Seems like you are not using the archiver features, so why don't you just use the codable?
do {
let key = "ExArray"
let decoded = defaults.data(forKey: key)!
var exo = try JSONDecoder().decode([Exerc].self, from: decoded)
exo.append(datas[indexPath.row])
let enco = try JSONEncoder().encode(exo)
defaults.set(enco, forKey: key)
} catch {
print("Error encoding/decoding custom object NOSEARCHO", error)
}
It just a simple refactored MVP of the original code, but you can even work a bit on this and make it human readable right in the plist file!

Remove string literals from elements inside an array

Please guys i need to parse a string to look like these in swift
"[{"question":9, "answer":25}", "question\":10, "answer":27}]"
where the index and value are dynamically gotten from a loop. I was able to get to these
["{\"question\":9, \"answer\":25}", "{\"question\":10, \"answer\":27}", "{\"question\":11, \"answer\":29}", "{\"question\":12, \"answer\":33}", "{\"question\":13, \"answer\":37}"]
so i have tried this
for i in 0..<answersForQuestionInPage.count{
let questions = answersForQuestionInPage[i] as Answer
do {
let data = try JSONEncoder().encode(questions)
// 2
let string = String(data: data, encoding: .utf8)!
answers.append(string)
print("This is the main value \(string)")
} catch{
}
}
this still gives me an array with this format
["{\"question\":9, \"answer\":25}", "{\"question\":10,
\"answer\":27}", "{\"question\":11, \"answer\":29}",
"{\"question\":12, \"answer\":33}", "{\"question\":13,
\"answer\":37}"]
with the object
"{\"question\":9, \"answer\":25}"
still wrapped in a string liteal " " what i want is for this return array to be in this format
[{"question":9, "answer":25}, {"question":10,
"answer":27}, {"question":11, "answer":29},
"{"question":12, "answer":33}, {"question":13,
"answer":37}]
I didn't understand the whole thing, but you said you need to parse the String, but I think you meant JSON. So, you can do it like this and get the values. Do let me know if it is what you needed, otherwise please add clarity in your question and I will edit and update my answer accordingly.
struct Quiz: Decodable {
let question, answer: Int
}
private func fetchQuizzes() {
//After getting the data from API, you can do this
guard let quiz = try? JSONDecoder().decode([Quiz].self,from: data) else { print("Unable to parse"); return }
print(quiz)
print(quiz.first?.answer) //First Answer
}
Just like Rob said before, you have a JSON here.
Using Robs code you decode the given JSON and create an array of Quiz objects (Robs struct).
You can now work with that array and transform it to your needs.

Can't unarchive custom Object with NSKeyedUnarchiver in AR Multiuser

I'm using MultipeerConnectivity to share SCNNodes position in a multiuser AR session.
When I archive (with NSKeyedArchiver.archivedData(withRootObject: someARNode, requiringSecureCoding: true) )
And unarchive (with if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:SCNNode.self, from: data) {)
Everything works fine, but now I'm trying to send a custom Object like this:
struct ARUser: Codable {
var idUser : String
var position: [Double]
}
When I try to unarchive the object received with the NSKeyedUnarchiver.unarchivedObject it let me error.
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:ARUser.self, from: data) {...}
I get the syntax error: Incorrect argument label in call (have 'ofClass:from:', expected 'ofClasses:from:')
But if I change the function as suggested by the compiler:
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClasses:[ARUser.self], from: data) {..}
I get the next syntax error: Cannot convert value of type 'ARUser.Type' to expected element type 'AnyObject.Type'
So, the question here is, what's the correct way to unarchive custom Objects?
Since here you use Codable
struct ARUser: Codable {
Then
do {
let dataToObject = try JSONDecoder().decode(ARUser.self,from:data)
let objectToData = try JSONEncoder().encode(dataToObject)
}
catch {
print(error)
}
NSKeyedUnarchiver is an old Objective-C stuff

How to get e-mail subject from message:// URL in OSX Swift

I have a desktop app that receives e-mail URLs ("message://" scheme) from the drag&drop pasteboard and I want to get the Subject from the relevant message. The only clue I have, so far, is that the QuickLook library might give me an information object where I can retrieve this info from.
Since the QuickLook API seems to be rather in flux at the moment and most examples show how to use it in iOS, I simply cannot find a way to set up my "Preview" object using a URL and get the information from there.
I would like to avoid setting up my project as a QuickLook plugin, or setting up the whole preview pane / view scaffolding; at the moment I just want to get out what QuickLook loads before it starts displaying, but I can't comprehend what paradigm Apple wants me to implement here.
XCode 7.3.1.
It turns out I misinterpreted the contents of draggingInfo.draggingPasteboard().types as a hierarchical list containing only one type of info (URL in this case).
Had to subscribe to dragged event type kUTTypeMessage as String and retrieve the e-mail subject from the pasteboard with stringForType("public.url-name")
EDIT: Note that the current Mail.app will sometimes create a stack of mails when you drag an e-mail thread. Although the method above still works to get the subject of the stack, there is no URL in the dragging info then and since there's no list of Message-IDs available either, I had to resort to scraping the user's mbox directory:
// See if we can resolve e-mail message meta data
if let mboxPath = pboard.stringForType("com.apple.mail.PasteboardTypeMessageTransfer") {
if let automatorPlist = pboard.propertyListForType("com.apple.mail.PasteboardTypeAutomator") {
// Get the latest e-mail in the thread
if let maxID = (automatorPlist.allObjects.flatMap({ $0["id"]! }) as AnyObject).valueForKeyPath("#max.self") as? Int {
// Read its meta data in the background
let emailItem = draggingEmailItem
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Find the e-mail file
if let path = Util.findEmlById(searchPath: mboxPath, id: maxID) {
// Read its contents
emailItem.properties = Util.metaDataFromEml(path)
dispatch_async(dispatch_get_main_queue(), {
// Update UI
});
}
}
}
}
}
Util funcs:
/* Searches the given path for <id>.eml[x] and returns its URL if found
*/
static func findEmlById(searchPath searchPath: String, id: Int)-> NSURL? {
let enumerator = NSFileManager.defaultManager().enumeratorAtPath(searchPath)
while let element = enumerator?.nextObject() as? NSString {
switch (element.lastPathComponent, element.pathExtension) {
case (let lpc, "emlx") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
case (let lpc, "eml") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
default: ()
}
}
return nil
}
/* Reads an eml[x] file and parses it, looking for e-mail meta data
*/
static func metaDataFromEml(path: NSURL)-> Dictionary<String, AnyObject> {
// TODO Support more fields
var properties: Dictionary<String, AnyObject> = [:]
do {
let emlxContent = try String(contentsOfURL: path, encoding: NSUTF8StringEncoding)
// Parse message ID from "...\nMessage-ID: <...>"
let messageIdStrMatches = emlxContent.regexMatches("[\\n\\r].*Message-ID:\\s*<([^\n\r]*)>")
if !messageIdStrMatches.isEmpty {
properties["messageId"] = messageIdStrMatches[0] as String
}
}
catch {
print("ERROR: Failed to open emlx file")
}
return properties
}
Note: If your app is sandboxed you will need the com.apple.security.temporary-exception.files.home-relative-path.read-only entitlement set to an array with one string in it: /Library/