Show loading image until response - swiftui

I'm sending via AlamoFire an audio file to a REST API and get a JSON response. Meanwhile I would like to display a loading image (e.g. animated circle) until I receive the response. My current solution is so far:
let voiceData = try? Data(contentsOf: audioRecorder.url)
let uploadUrl = "<server ip>"
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(voiceData!, withName: "file", fileName: "recording.m4a", mimeType: "audio/mpeg")
},
to: uploadUrl, method: .post)
.responseJSON { resp in
print(resp)
}
Now how can I show the loading image in my SwiftUI View?

I'm not sure about AlamoFire, but in order to do 2 things in parallel, you must use async methods, so this is what you could do (in JavaScript):
document.querySelector("#getInfoBtn").addEventListener('click',() => {
toggleInfoBtnSpinner()
setTimeout(AFUpload, 10)
});
let toggleInfoBtnSpinner = () => {
const btn = document.querySelector("#getInfoBtn")
const spinner = btn.querySelector("#infoSpinner")
if (spinner == null) {
btn.innerHTML = `<span id="infoSpinner" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>Search`
} else {
spinner.remove()
btn.innerHTML = `Search`
}
btn.disabled = !btn.disabled
}
This will set the spinner start and then, you'll execute your AFUpload function after 10 ms as an async function and you can trigger something rom that function to let the system know it finished

Related

Swiftui FileManager/URLSession not writing to documentDirectory when running as background task

Hope you're doing well!
I've built an app that generates a view from a .csv file that I have hosted on my website. I've previously managed to get everything working as expected where I called the csv from the website and wrote the contents directly to a variable and then processed it from there. Obviously this wasn't good practice as the app started mis-behaving when the internet couldn't be accessed (despite writing in connectivity checks).
I've now built out the app to call the URL, save the csv with Filemanager, then when the app refreshes, it will use FileManager.default.replaceItemAt to replace the previous version if there is internet connectivity, if not the app builds from the previously stored .csv
This all works fine when the app is running, however I'm running into issues with the background processing task. It seems the app doesn't have permissions to write with FileManager when it is executed from the background task. Is there an additional step I'm missing when using this in background tasks? I've attempted to use FileManager.default.removeItem followed by FileManager.default.copyItem instead of replaceItemAt but it doesn't seem to make a difference as expected.
UPDATE 22/06 - Still scouring the internet for similar issues or examples I think I might be going down the wrong rabbit hole here. This could be issues with the way the new background task has been configured for retrieving data from my website, although the background tasks worked fine before there seems to be a bit more legwork needed for this method to work as a background task.
func handleAppRefresh(task: BGProcessingTask) {
//Schedules another refresh
scheduleAppRefresh()
DispatchQueue.global(qos: .background).async {
pullData()
print("BG Background Task fired")
}
pullData() will call loadCSV() and then do some data processing. At the moment I'm just using a print straight after loadCSV() is called to validate if the downloads etc are successful.
// Function to pass the string above into variables set in the csvevent struct
func loadCSV(from csvName: String) -> [CSVEvent] {
var csvToStruct = [CSVEvent]()
// Creates destination filepath & filename
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("testcsv.csv")
//Create URL to the source file to be downloaded
let fileURL = URL(string: "https://example.com/testcsv.csv")!
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("File downloaded Successfully. Response: \(statusCode)")
}
do {
let _ = try FileManager.default.replaceItemAt(destinationFileUrl, withItemAt: tempLocalUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error" )
}
}
task.resume()
let data = readCSV(inputFile: "testcsv.csv")
var rows = data.components(separatedBy: "\n")
rows.removeFirst()
// Iterates through each row and sets values
for row in rows {
let csvColumns = row.components(separatedBy: ",")
let csveventStruct = CSVEvent.init(raw: csvColumns)
csvToStruct.append(csveventStruct)
}
print("LoadCSV has run and created testcsv.csv")
return csvToStruct
}
Any help or pointers to why these files aren't being updated in background tasks but are working fine in app would be massively appreciated!
Thanks in advance.
EDIT: adding new BGProcessingTask
func handleAppRefresh(task: BGProcessingTask) {
//Schedules another refresh
print("BG Background Task fired")
scheduleAppRefresh()
Task.detached {
do {
let events = try await loadCSV(from: "Eventtest").filter { !dateInPast(value: $0.date) }
print(events)
pullData(events: events)
} catch {
print(error)
}
}
}
The problem is not the background task per se, the problem is the asynchronous behavior of downloadTask. readCSV is executed before the data is downloaded.
In Swift 5.5 and later async/await provides asynchronous behavior but the code can be written continuously.
func loadCSV(from csvName: String) async throws -> [CSVEvent] {
var csvToStruct = [CSVEvent]()
// Creates destination filepath & filename
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("testcsv.csv")
//Create URL to the source file to be downloaded
let fileURL = URL(string: "https://example.com/testcsv.csv")!
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let (url, response) = try await session.download(for: request)
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("File downloaded Successfully. Response: \(statusCode)")
}
let _ = try FileManager.default.replaceItemAt(destinationFileUrl, withItemAt: url)
let data = readCSV(inputFile: "testcsv.csv")
var rows = data.components(separatedBy: "\n")
rows.removeFirst()
// Iterates through each row and sets values
for row in rows {
let csvColumns = row.components(separatedBy: ",")
let csveventStruct = CSVEvent.init(raw: csvColumns)
csvToStruct.append(csveventStruct)
}
print("LoadCSV has run and created testcsv.csv")
return csvToStruct
}
To call the function you have to wrap it in a detached Task which replaces the GCD queue
Task.detached {
do {
let events = try await loadCSV(csvName: "Foo")
print("BG Background Task fired")
} catch {
print(error)
}
}

how to upload images in flutter

hello i wonder to upload images in flutter
i try to use http.MultipartRequest
like this
request.fields["name"] = "$RegisterName";
request.fields["description"] = "$RegisterDescription";
request.fields["caution"] = "$RegisterCaution";
request.fields["price"] = "$RegisterPrice";
request.fields["price_prop"] = "$RegisterPriceProp";
request.fields["user.id"] = "1";
request.fields["lend"] = "$RegisterCategory";
request.fields["category"] = "Digital";
request.fields["place_option"] = "true";
var multipartFile = http.MultipartFile.fromBytes(
'file',
(await rootBundle.load('assets/images/main_1.jpg')).buffer.asUint8List(),
filename: 'test01.jpg',
contentType: MediaType('image', 'jpg'),
);
request.files.add(multipartFile);
var response = await request.send();
if (response.statusCode == 200) print('Upload');
}
but this code is not working
if i use this code, upload only another data
upload things
then json type is this
json type image
i want upload images files ...:(
i use this to send picture with formData
var head = Api().bearerHeader; ////just bearerToken
var request = http.MultipartRequest(
'POST',
Uri.parse(
'https://c.....'));
request.files
.add(await http.MultipartFile.fromPath('TITLEOFFORMDATA', imageFile.path));
request.headers.addAll(head);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
String varo = await response.stream.bytesToString();
}
This is how you can send image to your server with MultipartRequest with http package
try {
final uri = Uri.parse(your_url);
final request = http.MultipartRequest('POST', uri);
final multipartFile =
await http.MultipartFile.fromPath('Image', 'your_path_of_image'); // Image is the parameter name
request.files.add(multipartFile);
request.fields['userId_if_required'] = value;
final response = await request.send();
if (response.statusCode == 200) {
print('success');
} else {
print('Something went wrong');
}
} catch (e) {
print('Something went wrong');
}
How to upload your image to a Django rest API server
this will work for sure, let me know if you have any issues.
Please be sure to add the necessary packages to your pubspec.yaml file
image_picker
http
if there is some I missed please ask me or add it and add as a reply
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:io';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
final _picker = ImagePicker();
File? _image;
// use this to send your image
Future<void>uploadImage(filePath) async {
// your token if needed
try{
var headers = {
'Authorization':
'Bearer ' + "token",
};
// your endpoint and request method
var request = http.MultipartRequest(
'POST',
Uri.parse("https://api.imgur.com/3/image"));
request.fields
.addAll({'yourFieldNameKey1': 'yourFieldNameValue1', 'yourFieldNameKey2': 'yourFieldNameValue2'});
request.files.add(await http.MultipartFile.fromPath(
'yourPictureKey', filePath));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
} else {
print(response.reasonPhrase);
}
}catch(e){
print(e);
}
}
// Use this to pick your image
Future<void> _openImagePicker() async {
try {
var pickedImage = await _picker.pickImage(source: ImageSource.gallery);
if (pickedImage != null) {
setState(() {
_image = File(pickedImage.path);
});
uploadImage(pickedImage.path);
}
} catch (e) {
//print(e);
}
}

UIWebView: ics and vcard-Links not handled

I do have a UIWebView included where a public URL is loaded; unfortunately, vcard and ical-Links are not handled, i.e. nothing happens when I click on them.
I tried to set all data detectors, no luck unfortunately.
In the Xcode-log, I get this here when clicking on such a link:
2017-07-14 13:43:00.982413+0200 xxx[2208:967973] WF: _userSettingsForUser mobile: {
filterBlacklist = (
);
filterWhitelist = (
);
restrictWeb = 1;
useContentFilter = 0;
useContentFilterOverrides = 0;
whitelistEnabled = 0;
}
In Safari, the same stuff works as expected.
If I use UIApplication.shared.openURL(icsOrVcardUrl) Safari gets opened and from there everything works as expected again, but I don't want the user to leave the app...
EDIT
This doesn't work either:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let url = request.url {
if url.absoluteString.contains("=vcard&") || url.absoluteString.contains("/ical/") {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:url)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
DispatchQueue.main.async {
self.documentController.url = tempLocalUrl
self.documentController.presentPreview(animated: true)
}
}
}
task.resume()
return false
}
}
return true
}
Use a UIDocumentInteractionController to preview without leaving your app.
I tested it quickly with an .ics file and it works fine.
Implement the UIDocumentInteractionControllerDelegate protocol
extension MainViewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self;
}
}
Create an instance of the interaction controller:
let documentController = UIDocumentInteractionController()
Intercept the clicks in your UIWebView in shouldStartLoadWithRequest, return false for links you want to handle with the in-app preview and true for all the rest. And finally:
func previewDocument(_ url: URL) {
documentController.url = url
documentController.presentPreview(animated: true)
}
Here it is in the simulator
EDIT:
In response to the comment to this answer:
The reason it doesn't work for you is because the UIDocumentInteractionController depends on the file extension. The extension of the temp file is .tmp
Renaming the file after the download solves the problem. Quick and dirty example:
let task = session.downloadTask(with: url!) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
do {
let filemgr = FileManager.default
let newUrl = tempLocalUrl.appendingPathExtension("ics")
try filemgr.moveItem(at: tempLocalUrl, to: newUrl)
DispatchQueue.main.async {
self.documentController.url = newUrl
self.documentController.presentPreview(animated: true)
}
} catch let error {
print("Error!!!: \(error.localizedDescription)")
}
}
}
task.resume()
In this case it is advisable to clean after yourself, because the file won't be deleted after the task completes although the OS will delete it eventually, when space is needed. If you often access the same urls, Library/Caches/ may be a better place for this files, just come up with good naming schema, and check if the file doesn't exist already.

Getting 100x100 profile pic using Facebook API, Firebase and Swift

My project had been getting the URL string for the medium sized profile pic using this code:
let downloadMediumPicTask = session.dataTask(with: mediumProfPictureURL) { (data, response, error)
in
// The download has finished.
if let e2 = error {
print("Error downloading profile picture: \(e2)")
} else {
if let res2 = response as? HTTPURLResponse {
print("Downloaded medium profile picture with response code \(res2.statusCode)")
if let imageData2 = data {
mediumProfilePictureUIImageFile = UIImage(data: imageData2)!
print("mediumProfilePictureUIImageFile has now been defined as: \(mediumProfilePictureUIImageFile).")
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
downloadMediumPicTask.resume()
It crashes here giving a 403 response code. The URL that is being referenced is an expired signature URL from Facebook. Firebase doesn't adjust to get the new appropriate URL, and it was from Firebase that I had been getting the URL. I can't figure out how to get it directly as tried below:
func getUrlOfMediumProfilePic(){
if (FBSDKAccessToken.current() != nil) {
let graphPathPart2 = "me/picture"
let paramsPart2 = ["type":"medium", "redirect":"false"]
let completionHandlerPart2 = { (connection: FBSDKGraphRequestConnection?, result: Any?, error: Error?) in
if let error = error {
print("Medium picture graph call contained an error: \(error.localizedDescription)")
return
} else {
guard connection != nil else {
print("getURLOfLargeProfilePic() function aborted bc connection failed.")
return
}
let results = result! as! NSDictionary
let dataDict = results["data"] as! NSDictionary
stringOfMediumProfilePicURLaka100x100 = dataDict["url"] as! String
print("medium picture graph call results define stringOfMediumProfilePicURLaka100x100 as: \(stringOfMediumProfilePicURLaka100x100)")
}
}
let graphRequest = FBSDKGraphRequest(graphPath: graphPathPart2, parameters: paramsPart2)!
graphRequest.start(completionHandler: completionHandlerPart2)
}else{
print("User not logged in when getURLOfMediumProfilePic() function was run.")
return
}
}
This code yields an error with code 8.
Have you tried this:
https://graph.facebook.com/{id}/picture?width=100&height=100
I don't know swift, so I can't help about syntax. I think you can make http request to url and get image.
Hope this help :)

AWS S3 Bucket Upload using CollectionFS and cfs-s3 meteor package

I am using Meteor.js with Amazon S3 Bucket for uploading and storing photos. I am using the meteorite packges collectionFS and aws-s3. I have setup my aws-s3 connection correctly and the images collection is working fine.
Client side event handler:
'click .submit': function(evt, templ) {
var user = Meteor.user();
var photoFile = $('#photoInput').get(0).files[0];
if(photoFile){
var readPhoto = new FileReader();
readPhoto.onload = function(event) {
photodata = event.target.result;
console.log("calling method");
Meteor.call('uploadPhoto', photodata, user);
};
}
And my server side method:
'uploadPhoto': function uploadPhoto(photodata, user) {
var tag = Random.id([10] + "jpg");
var photoObj = new FS.File({name: tag});
photoObj.attachData(photodata);
console.log("s3 method called");
Images.insert(photoObj, function (err, fileObj) {
if(err){
console.log(err, err.stack)
}else{
console.log(fileObj._id);
}
});
The file that is selected is a .jpg image file but upon upload I get this error on the server method:
Exception while invoking method 'uploadPhoto' Error: DataMan constructor received data that it doesn't support
And no matter whether I directly pass the image file, or attach it as data or use the fileReader to read as text/binary/string. I still get that error. Please advise.
Ok, maybe some thoughts. I have done things with collectionFS some months ago, so take care to the docs, because my examples maybe not 100% correct.
Credentials should be set via environment variables. So your key and secret is available on server only. Check this link for further reading.
Ok first, here is some example code which is working for me. Check yours for differences.
Template helper:
'dropped #dropzone': function(event, template) {
addImage(event);
}
Function addImage:
function addImagePreview(event) {
//Go throw each file,
FS.Utility.eachFile(event, function(file) {
//Some Validationchecks
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
var fsFile = new FS.File(image.src);
//setMetadata, that is validated in collection
//just own user can update/remove fsFile
fsFile.metadata = {owner: Meteor.userId()};
PostImages.insert(fsFile, function (err, fileObj) {
if(err) {
console.log(err);
}
});
};
})(file);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
});
}
Ok, your next point is the validation. The validation can be done with allow/deny rules and with a filter on the FS.Collection. This way you can do all your validation AND insert via client.
Example:
PostImages = new FS.Collection('profileImages', {
stores: [profileImagesStore],
filter: {
maxSize: 3145728,
allow: {
contentTypes: ['image/*'],
extensions: ['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG']
}
},
onInvalid: function(message) {
console.log(message);
}
});
PostImages.allow({
insert: function(userId, doc) {
return (userId && doc.metadata.owner === userId);
},
update: function(userId, doc, fieldNames, modifier) {
return (userId === doc.metadata.owner);
},
remove: function(userId, doc) {
return false;
},
download: function(userId) {
return true;
},
fetch: []
});
Here you will find another example click
Another point of error is maybe your aws configuration. Have you done everything like it is written here?
Based on this post click it seems that this error occures when FS.File() is not constructed correctly. So maybe this should be you first way to start.
A lot for reading so i hope this helps you :)