I have file which is shared between my app and extensions:
Write to file from extension:
func writeToFile()
{
let file = "file.txt"
let text = "data" //just a text
let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.ml.test.apps")!
let fileURL = dir.appendingPathComponent(file)
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {/* error handling here */}
}
Read from the app:
func readFromFile()
{
let file = "file.txt"
let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.ml.test.apps")!
let fileURL = dir.appendingPathComponent(file)
do {
let text2 = try String(contentsOf: fileURL, encoding: .utf8)
NSLog("Dan: \(text2)")
}
catch {/* error handling here */}
}
My question is how can I observe changes to this file. In case the extension writes to it and changes data so the app will get notification change and read the file.
Here is a simple demo of approach based on usage NSFileCoordinator/NSFilePresenter pattern.
Tested with Xcode 11.4 / iOS 13.4
Application part. Here is a ViewController plays a file presenter role, for simplicity (if one controller can manage many files, then it is better to create explicit presenters per-file)
class ViewController: UIViewController, NSFilePresenter {
var presentedItemURL: URL?
var presentedItemOperationQueue: OperationQueue = OperationQueue.main
#IBOutlet weak var userNameField: UILabel!
func presentedItemDidChange() { // posted on changed existed file only
readFromFile()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// register for presentedItemDidChange work
NSFileCoordinator.addFilePresenter(self)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// unregister - required !!
NSFileCoordinator.removeFilePresenter(self)
}
override func viewDidLoad() {
super.viewDidLoad()
let file = "file.txt"
let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.test.apps")!
presentedItemURL = dir.appendingPathComponent(file)
readFromFile() // read previously stored data
}
private func readFromFile()
{
let coordinator = NSFileCoordinator(filePresenter: self)
coordinator.coordinate(readingItemAt: presentedItemURL!, options: [], error: nil) { url in
if let text2 = try? String(contentsOf: url, encoding: .utf8) {
userNameField.text = text2 // demo label in view for test
} else {
userNameField.text = "<no text>"
//just initial creation of file needed to observe following changes
coordinator.coordinate(writingItemAt: presentedItemURL!, options: .forReplacing, error: nil) { url in
do {
try "".write(to: url, atomically: false, encoding: .utf8)
}
catch { print("writing failed") }
}
}
}
}
}
Extension part (simple Today extension for demo having one button)
class TodayViewController: UIViewController, NCWidgetProviding, NSFilePresenter {
var presentedItemURL: URL?
var presentedItemOperationQueue: OperationQueue = OperationQueue.main
override func viewDidLoad() {
super.viewDidLoad()
let file = "file.txt"
let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.test.apps")!
presentedItemURL = dir.appendingPathComponent(file)
}
#IBAction func post(_ sender: Any) { // action on button in extension
writeToFile()
}
func writeToFile()
{
let text = "new data" //just a text
let coordinator = NSFileCoordinator(filePresenter: self)
coordinator.coordinate(writingItemAt: presentedItemURL!, options: .forReplacing, error: nil) { url in
do {
try text.write(to: url, atomically: false, encoding: .utf8)
}
catch { print("writing failed") }
}
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
completionHandler(NCUpdateResult.newData)
}
}
Related
I am trying to read Export chat Zip file but share extension loading a WhatsApp Zip attachment is not working.
I am using this code -:
override func viewDidLoad() {
super.viewDidLoad()
getURL()
}
private func getURL() {
let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
let itemProvider = extensionItem.attachments?.first
let zip_type = String(UTType.zip.identifier)
if itemProvider!.hasItemConformingToTypeIdentifier(zip_type) {
itemProvider!.loadItem(forTypeIdentifier: zip_type, options: nil, completionHandler: { (item, error) -> Void in
guard let url = item as? NSURL else { return }
OperationQueue.main.addOperation {
print("url\(url)")
self.path = url as URL
do {
let unzipDirectory = try Zip.quickUnzipFile(self.path)
print("unzipDirectory\(unzipDirectory)")
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(unzipDirectory.lastPathComponent)
print("fileURL\(fileURL)")
do {
let text2 = try String(contentsOf: fileURL, encoding: .utf8)
print(text2)
}
catch {/* error handling here */}
}
}
catch {
print("Something went wrong")
}
}
})
} else {
print("error")
}
}
override func isContentValid() -> Bool {
print("Hiii")
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
print("hello")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
in console error is:
[core] SLComposeServiceViewController got attachment coarseType 0
[core] SLComposeServiceViewController made no attachment for itemProvider conforming to public.file-url
Can anyone help please?
I would like to add a couple of images that are stored in variables to a QLPreviewController. The QuickLook datasource requires a fileURL which I'm not sure how to get for an image that is stored in a variable and not in disk or in the project.
Any pointers as to how I can solve this issue?
Below code can be used to to display image file from local and from URL in QLPreviewController.
import UIKit
import QuickLook
class ViewController: UIViewController {
lazy var previewItem = NSURL()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func displayLocalFile(_ sender: UIButton){
let previewController = QLPreviewController()
// Set the preview item to display
self.previewItem = self.getPreviewItem(withName: "bull.png")
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}
#IBAction func displayFileFromUrl(_ sender: UIButton){
// Download file
self.downloadfile(completion: {(success, fileLocationURL) in
if success {
// Set the preview item to display======
self.previewItem = fileLocationURL! as NSURL
// Display file
DispatchQueue.main.async {
let previewController = QLPreviewController()
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
}
}else{
debugPrint("File can't be downloaded")
}
})
}
func getPreviewItem(withName name: String) -> NSURL{
// Code to diplay file from the app bundle
let file = name.components(separatedBy: ".")
let path = Bundle.main.path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path!)
return url
}
func downloadfile(completion: #escaping (_ success: Bool,_ fileLocation: URL?) -> Void){
let itemUrl = URL(string: "https://developer.apple.com/wwdc20/images/hero/memoji/large/L7_2x.jpg")
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent("filename.jpg")
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
debugPrint("The file already exists at path")
completion(true, destinationUrl)
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: itemUrl!, completionHandler: { (location, response, error) -> Void in
guard let tempLocation = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: tempLocation, to: destinationUrl)
print("File moved to documents folder")
completion(true, destinationUrl)
} catch let error as NSError {
print(error.localizedDescription)
completion(false, nil)
}
}).resume()
}
}
}
//MARK:- QLPreviewController Datasource
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}
how can I get the source code from an URL on the logs?
This code is returning an error message on the logs instead of the HTML data.
Can you please help me and let me know what can I include/change?
Thank you!
import UIKit
class ViewController: UIViewController {
#IBOutlet var textField: UITextField!
#IBOutlet var display: UILabel!
#IBAction func send(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = URL (string: "https://www.weather-forecast.com/locations/London/forecasts/latest")!
let request = NSMutableURLRequest(url:url)
let task = URLSession.shared.dataTask (with:request as URLRequest) {
data, response, error in
if error != nil {
print (error!)
} else {
if let unrappedData = data {
let dataString = NSString(data: unrappedData, encoding: String.Encoding.utf8.rawValue)
print (dataString!)
}
}
}
task.resume()
}
}
We can get the html code from the URL like below,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .background).async {
// Background Thread
let myURLString = "https://www.weather-forecast.com/locations/London/forecasts/latest"
guard let myURL = URL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
let myHTMLString = try String(contentsOf: myURL, encoding: .ascii)
print("HTML : \(myHTMLString)")
} catch let error {
print("Error: \(error)")
}
DispatchQueue.main.async {
// Run UI Updates or call completion block
}
}
}
}
I am saving datas on on the json file on the first VC , load the data as well and display it when switching tab. When I kill the app or re run the app again, add new datas to the JSON file, only those new datas are on the JSON file, previous datas are gone(deleted without deleting them manually) and can not be load. How do I save the file so that next time I run the program it will just append to the previous data ?
class ViewController: UIViewController {
var game : Game?
var weekLeague : [[Game]]? = []
override func viewDidLoad() {
super.viewDidLoad()
creation()
}
#IBAction func endWLButton(_ sender: UIButton) {
if games != nil {
weekLeague?.append(games!)
}
save()
}
func save(){
guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileUrl = documentDirectoryUrl.appendingPathComponent("ArrayOfArray.json")
print(fileUrl)
let json = try? JSONEncoder().encode(weekLeague)
do {
try json?.write(to: fileUrl)
print(json!)
print(weekLeague)
print("JSON data was written to teh file successfully!")
}catch{
print(error)
}
}
func ShouldSendGame(game: Game) {
self.game = game
games?.append(game)
}
func creation(){
let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!
let jsonFilePath = documentsDirectoryPath.appendingPathComponent("ArrayOfArray.json")
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
// creating a .json file in the Documents folder
if !fileManager.fileExists(atPath: jsonFilePath!.path, isDirectory: &isDirectory) {
let created = fileManager.createFile(atPath: jsonFilePath!.path, contents: nil, attributes: nil)
if created {
print("File created ")
} else {
print("Couldn't create file for some reason")
}
} else {
print("File already exists")
}
}
}
class AllLeagueController : UITableViewController {
var arrayOfArrayGamesCopy : [[Game]] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.loadData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func loadData() {
guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileUrl = documentsDirectoryUrl.appendingPathComponent("ArrayOfArray.json")
do{
let data = try Data(contentsOf:fileUrl)
let decoder = JSONDecoder()
let jsonData = try decoder.decode([[Game]].self, from: data)
arrayOfArrayGamesCopy = jsonData
print("Succes")
print(jsonData.count)
} catch{
print(error)
}
}
}
You need to load data here before save ... Also you need to have separate class for saving and loading data .. dot do that in controller .. its against Single Responsibility Principle ...Your load function should return array of [Game] and save data should return success or failure
#IBAction func endWLButton(_ sender: UIButton) {
//load and set data in games from file ...then append current data and save to file
if games != nil {
weekLeague?.append(games!)
}
save()
}
I'm a beginner in iOS development and I am trying to make a simple app which Passing text from text file to UITextView in "Swift 3". I have looked up many tutorials on YouTube, stack and other websites, but all of them seem to either give me a lot of errors or are too hard for me too understand (as I am too unexperienced).
There are three buttons, and three text files, and one text view.
When user clicks on the first button, file1 will be loaded into the text field.
Some of my attempts, it's give me error on txt_view.text = txt1.txt
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txt_view: UITextView!
#IBAction func btn_txt1(_ sender: Any) {
txt_view.text = txt1.txt
}
#IBAction func btn_txt2(_ sender: Any) {
txt_view.text = txt2.txt
}
#IBAction func btn_txt3(_ sender: Any) {
txt_view.text = txt3.txt
}
override func viewDidLoad(){
super.viewDidLoad()
}
override func didReceiveMemoryWarning(){
super.didReceiveMemoryWarning()
}
}
This is probably the simplest way of doing it.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
#IBAction func readFile1(_ sender: Any) {
self.textView.text = load(file: "file1")
}
#IBAction func readFile2(_ sender: Any) {
self.textView.text = load(file: "file2")
}
#IBAction func readFile3(_ sender: Any) {
self.textView.text = load(file: "file3")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.textView.text = load(file: "file1")
}
func load(file name:String) -> String {
if let path = Bundle.main.path(forResource: name, ofType: "txt") {
if let contents = try? String(contentsOfFile: path) {
return contents
} else {
print("Error! - This file doesn't contain any text.")
}
} else {
print("Error! - This file doesn't exist.")
}
return ""
}
}
Following is the way to load the text file in the text view.
Swift 4.x
func loadFileContent(_ fileName: String) {
if let path = Bundle.main.path(forResource: fileName,
ofType: "txt") {
do {
let text = try String(contentsOfFile: path, encoding: .ascii)
openSourceTextView.text = text
} catch let error {
debugPrint(" Error - \(error.localizedDescription)")
}
}
NOTE:
If skip the encoding or using the encoding: .utf8 you will get the following errors -
The file “fileName.txt” couldn’t be opened because the text
encoding of its contents can’t be determined.
OR
The file “fileName.txt” couldn’t be opened using text encoding
Unicode (UTF-8).
so used encoding: .ascii while loading content of the text file.
Use this helper function:
func updateTextView(with fileName: String) {
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(fileName)
do {
txt_view.text = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
}
You can apply it to actions like so:
#IBAction func btn_txt1(_ sender: Any) {
updateTextView(with: txt1.txt)
}
You can't read data from text file by this way.
To do it, you have can do something like this :
let file = "txt1.txt" //this is the file. we will write to and read from it
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
catch {/* error handling here */}
//reading
do {
let text_view.txt = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
if you want to simplify the syntax a bit the following is a function that would accomplish the goal as well:
private func loadTextWithFileName(_ filename: String) -> String? {
guard let path = Bundle.main.path(forResource: filename, ofType: "txt"),
let contents = try? String(contentsOfFile: path) else { return nil }
return contents
}