I'm using the ShareLink to share an FileDocument which contains a String. The FileDocument is conform to the Transferable protocol.
This is the FileDocument Struct:
struct TransferableDocument: FileDocument, Transferable {
static var transferRepresentation: some TransferRepresentation
{
DataRepresentation(exportedContentType: .text) { log in
log.convertToData()
}
}
// tell the system to support only text
static var readableContentTypes: [UTType] = [.text]
// by default the document is empty
var text = ""
// this initializer creates a empty document
init(initialText: String = "") {
text = initialText
}
// this initializer loads data that has been saved previously
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
text = String(decoding: data, as: UTF8.self)
}
}
// this will be called when the system wants to write the data to disk
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(text.utf8)
return FileWrapper(regularFileWithContents: data)
}
func convertToData() -> Data
{
return text.data(using: .ascii) ?? Data()
}
}
And this is the ShareLink:
var doc: TransferableDocument
{
return TransferableDocument(initialText: "I'm a String")
}
ShareLink(item: doc ,preview: SharePreview("logfile"))
{
Text("Share")
}
When using AirDrop, the filename is set to the SharePreview title, in this case "logfile". When sharing it to Apps like Mail, the filename is simply set to "text".
Is there any way to set a default filename?
We had a similar issue and added an additional FileRepresentation to the transferRepresentation func with the appropriate file name configured when saving out the data. With that in place when shared to Mail the appropriate file name was used.
static var transferRepresentation: some TransferRepresentation
{
DataRepresentation(exportedContentType: .text) { log in
log.convertToData()
}
FileRepresentation(exportedContentType: .text) { log in
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("logfile").appendingPathExtension("txt")
try log.convertToData().write(to: fileURL)
return SentTransferredFile(fileURL)
}
}
Related
I'm building a document application and I want to offer a lot of export options, most of them just different forms of text file, plane text, markdown, json etc.
I have a small sample app, see code, that works, allowing me to export some text as plane text to a .txt file, or as markdown as a .md file. But that's just 2 of the 5-10 formats I want to support and this already has a lot of repetitive code in it.
This is my .info tab:
So, my question is, how can I reduce this to idealy just one .fileExporter( call and still have the different file extensions on the exported files.
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
#State private var showingTXTExporter = false
#State private var showingMDExporter = false
#State var txtDocument = txtFile()
#State var mdDocument = mdFile()
var body: some View {
VStack {
Text("Save as TXT file!")
.padding().onTapGesture {
txtDocument.setText(text: "Title \n Body of the message. \n")
showingTXTExporter.toggle()
}.fileExporter(isPresented: $showingTXTExporter, document: txtDocument, contentType: .txtFile, defaultFilename: "email as text") { result in
switch result {
case .success(let url):
print("Saved to \(url)")
case .failure(let error):
print(error.localizedDescription)
}
}
Text("Save as MD file!")
.padding().onTapGesture {
mdDocument.setText(text: "# Title \n Body of the message. \n")
showingMDExporter.toggle()
}.fileExporter(isPresented: $showingMDExporter, document: mdDocument, contentType: .mdFile, defaultFilename: "email as markdown") { result in
switch result {
case .success(let url):
print("Saved to \(url)")
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
extension UTType {
static let txtFile = UTType(exportedAs: "com.myappurl.txt")
static let mdFile = UTType(exportedAs: "com.myappurl.md")
}
// Setting up my txt document type
struct txtFile: FileDocument {
// tell the system we support only plain text
static var readableContentTypes = [UTType.txtFile]
// by default our document is empty
var text = ""
// a simple initializer that creates new, empty documents
init(initialText: String = "") {
text = initialText
}
mutating func setText(text: String) {
self.text = text
}
// this initializer loads data that has been saved previously
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
text = String(decoding: data, as: UTF8.self)
}
}
// this will be called when the system wants to write our data to disk
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(text.utf8)
return FileWrapper(regularFileWithContents: data)
}
}
// Setting up my markdown document
// this feels very wasteful, there must be a better way!
struct mdFile: FileDocument {
// tell the system we support only plain text
static var readableContentTypes = [UTType.mdFile]
// by default our document is empty
var text = ""
// a simple initializer that creates new, empty documents
init(initialText: String = "") {
text = initialText
}
mutating func setText(text: String) {
self.text = text
}
// this initializer loads data that has been saved previously
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
text = String(decoding: data, as: UTF8.self)
}
}
// this will be called when the system wants to write our data to disk
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(text.utf8)
return FileWrapper(regularFileWithContents: data)
}
}
I am trying to allow users to pick folders using the DocumentGroup context in SwiftUI, having defined a SourceFolderDocument class inheriting from ReferenceFileDocument.
However, when launching the app, only elements conforming to .sourceCode are pickable in the file picker. What would be the correct way to pick folders to open in SwiftUI?
Thank you in advance!
Here is the class in question:
final class SourceFolderDocument: ReferenceFileDocument{
static var readableContentTypes: [UTType] {[.folder, .sourceCode, .directory]}
init(configuration: ReadConfiguration) throws {
var documents : [SourceFile] = []
if(configuration.file.isDirectory){
guard let wrappers = configuration.file.fileWrappers
else{
throw CocoaError(.fileReadCorruptFile)
}
var documents : [SourceFile] = []
for document in wrappers {
if let fileContents = document.value.regularFileContents{
documents.append(SourceFile(name: document.key, contents: String(data: fileContents, encoding: .utf8)!, fileUTType: .sourceCode))
}
}
}else{
guard let data = configuration.file.regularFileContents
else{
throw CocoaError(.fileReadCorruptFile)
}
documents.append(SourceFile(name: configuration.file.filename ?? "blank", contents: String(data: data, encoding: .utf8) ?? "", fileUTType: .sourceCode))
}
self.sourceFolder = SourceFolder(name: configuration.file.filename!, documents: documents)
}
func snapshot(contentType: UTType) throws -> SourceFolder {
sourceFolder
}
func fileWrapper(snapshot: SourceFolder, configuration: WriteConfiguration) throws -> FileWrapper {
var fileWrappers: [String : FileWrapper] = [:]
for document in snapshot.documents {
fileWrappers[document.name] = FileWrapper(regularFileWithContents: document.contents.data(using: .utf8)!)
}
let fileWrapper = FileWrapper(directoryWithFileWrappers: fileWrappers)
return fileWrapper
}
init(){
sourceFolder = .defaultFolder
}
typealias Snapshot = SourceFolder
#Published var sourceFolder : SourceFolder
}
Since the menu is only using NSOpenPanel, I was about to suggest to create a small function:
fileprivate func openPanel() {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
if panel.runModal() == .OK,
let url = panel.url {
Task {
try? await openDocument(at: url)
}
}
}
And then append to your view the new "Open Folder" menu:
.commandsReplaced {
CommandGroup(after: .newItem) {
Section {
Button("Open Folders") {
openPanel()
}
}
}
}
This would be creating (or replacing) the existing menus and it should also be possible to replace the "Open" menu.
I'm trying to read a database and copy the rows into an array in swiftUI but I don't know the correct syntax to append the array. Here is my databaseHelper.swift
import Foundation
import SQLite
class DatabaseHelper {
var database: Connection!
var path: String
let buttonsTable = "Button"
var db: String
var english: String
var categoryID: Int
var indonesian: String
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
func queryDatabase() -> [ButtonData] {
var buttonVars = [ButtonData]()
do {
let buttons = try self.database.prepare(self.buttonsTable)
for user in buttons {
// print("English: \(user[self.english]), ID: \(user[self.ID]), Indonesian: \(user[self.indonesian])")
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
return buttonVars
}
}
struct ButtonData: Hashable {
let english: String
let categoryID: Int
let indonesian: String
}
I'm not sure what is really happening in append statement. I want to append columns in the row but it doesn't accept anything.
The error message I am getting is...
"Cannot subscript a value of type 'Statement.Element' (aka 'Array>') with an argument of type 'String'
Thanks in advance :)
buttonTable is a sqlite.swift table and needs to be more than a string.
At some point you need to define your table like this:
static let buttonsTable = Table("Button")
static let english = Expression<String>("english")
static let indonesian = Expression<String>("indonesian")
static let categoryID = Expression<Int>("categoryID")
Assuming your db already exists, you should then be able to access it.
This section looks correct:
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
But then you would access it like this:
func queryDatabase() -> [ButtonData] {
var buttonVars = [ButtonData]()
do {
let buttons = try self.database.prepare(self.buttonsTable)
for row in buttons {
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
return buttonVars
}
I'll often put my return stuff in a map, like this:
func queryDatabase() -> [ButtonData] {
do {
return try self.database.prepare(self.buttonsTable).compactMap { row in
ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
// Return an empty array if we caught an error
return []
}
I am new to SwiftUI and Swift . I got a Search Bar and a Listview whenever a user types something in the searchbar I do an http request and new data comes in . The issue is that the list is not updating with the new data and I think I know why . I need to pass my SearchBar response into the ObservedObject variable . I was reading this swiftui ObservedObject function call in all view however I still didn't find my answer . This is my code
struct RegistrationView: View {
#State private var searchTerm: String = ""
#State var txt = "" // Txt has the SearchBar text
#ObservedObject var getData = datas(location: "") // I need to pass it here
var body: some View {
VStack {
Text("Registration")
searchView(txt: $txt)
// datas(location: txt)
NavigationView {
List(getData.jsonData.filter{ txt == "" ? true : $0.name.localizedCaseInsensitiveContains(txt)}) { i in
ListRow(name: i.name,statelong: i.statelong)
}
}
.padding(.top, 5.0)
}
}
}
class datas: ObservableObject
{
#Published var jsonData = [datatype]()
init(location: String) {
let session = URLSession(configuration: .default)
if location == "" {
return
}
let parameter = "location=\(location)"
if location == "" {
return
}
let url = URL(string:"url")!
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
session.dataTask(with:request, completionHandler: {(data, response, error) in
do
{
if data != nil
{
let fetch = try JSONDecoder().decode([datatype].self, from: data!)
DispatchQueue.main.async {
self.jsonData = fetch
print(fetch)
}
}
}
catch
{
print(error.localizedDescription)
}
}).resume()
}
}
In the above code I want to pass in the txt variable into the getData variable or do something like this #ObservedObject var getData = datas(location: txt) . When the SearchBar is updated then txt gets whatever is inserted into the SearchBar .
If I do something like this
#ObservedObject var getData = datas(location: "Me")
Then the list will update and correctly have everything that starts with Me my only issue is getting the SearchBar value inside datas so I don't have to hardcode things . As stated before I need to pass in txt to datas . Any help would be great
You don't need to init the class with that variable. You can just make a function for that and fetch it when ever you need. It could be just once.
class datas: ObservableObject {
#Published var jsonData = [datatype]()
func get(location: String) {
let session = URLSession(configuration: .default)
guard !location.isEmpty else { return }
let parameter = "location=\(location)"
let url = URL(string:"url")!
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
session.dataTask(with:request, completionHandler: {(data, response, error) in
do {
guard data != nil else { return }
let fetch = try JSONDecoder().decode([datatype].self, from: data!)
DispatchQueue.main.async {
self.jsonData = fetch
print(fetch)
}
} catch {
print(error.localizedDescription)
}
}).resume()
}
}
I have a class that saves the state of something, in my case some variable of the ViewController, but sometimes it loads wrong or old data, but I can't figure out why.
Maybe somebody can have a look of my code and see if it makes sense.
class TopFlopState: Codable, PersistenceState {
var group: Groups = .large {
didSet {
save()
}
}
var base: Bases = .usd {
didSet {
save()
}
}
var valueOne: StatIntervalBaseModel = StatIntervalBaseModel(stat: "ppc", interval: "24h", base: "usd") {
didSet {
save()
}
}
init(){
let savedValues = load()
if savedValues != nil {
self.group = savedValues!.group
self.base = savedValues!.base
self.valueOne = savedValues!.valueOne
}
}
}
This is the PersistenceState protocol:
/**
Saves and Loads the class, enum etc. with UserDefaults.
Has to conform to Codable.
Uses as Key, the name of the class, enum etc.
*/
protocol PersistenceState {
}
extension PersistenceState where Self: Codable {
private var keyUserDefaults: String {
return String(describing: self)
}
func save() {
saveUserDefaults(withKey: keyUserDefaults, myType: self)
}
func load() -> Self? {
return loadUserDefaults(withKey: keyUserDefaults)
}
private func saveUserDefaults<T: Codable>(withKey key: String, myType: T){
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch {
print("Save Failed")
}
}
private func loadUserDefaults<T: Codable>(withKey key: String) -> T? {
guard let data = UserDefaults.standard.object(forKey: key) as? Data else { return nil }
do {
let decoded = try PropertyListDecoder().decode(T.self, from: data)
return decoded
} catch {
print("Decoding failed for key", key)
return nil
}
}
}
If a value gets set to the value it should automatically save, but like I set sometimes it saves the right values but loads the wrong ones...
In my opinion, It return the cache. Because in Apple official documentation, it state
UserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value
Maybe you can change the flow, when to save the data. In your code show that you call save() 3 times in init().