I have an app where user selects 5 imagesfrom photo library, then i send to send those 5 UIImages to a nodejs server and store them in a mysql database, however i am getting many issues with this approach when i fetch the data from the database and try to convert back to uiimage, also many people here have said that its better to store the image url or path to image in the database instead of the actual image , so i have decided to try that approach however i do not know how to get an image url? Or how to get the path of the image? My app still needs to send an image from the iOS device, to nodejs server, store the actual image, and retrieve the images so other users can see it but where and how do i store the image?
UPDATE: so my issue is I start with a UIImage in swift, convert it to base64 string representation, send to nodejs server, and insert it into mysql database, but when i send the base64 string representation from the mysql table back to the ios device i get an error trying to convert the string back to a uiimage, i convert the string back to Data object and then try to create a uiimage from that data object but the uiimage is always nil, and i dont get an error description from xcode so i do not know how to go about this?
func sendToNodeServer()
{
let url: URL = URL(string: "http://localhost:8081/testInsert")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let imageData:NSData = UIImagePNGRepresentation(imV.image!)! as NSData
let strBase64:String = imageData.base64EncodedString(options: .lineLength64Characters)
let paramString = "pic=" + strBase64
request.httpBody = paramString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request)
{ data, response, error in
if error != nil
{
print("error in web request")
}
else
{
}
}//completion handler end
task.resume() //start the web reuqest
}
func getFromNodeServer()
{
let url: URL = URL(string: "http://localhost:8081/testgrab")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request)
{ data, response, error in
if error != nil
{
print("error in web request")
}
else
{
DispatchQueue.main.async
{
self.parseWeb2(stuff: data) //stoer results in class member
}
}
}//completion handler end
task.resume() //start the web reuqest
}
func parseWeb2(stuff: Data?)
{
if stuff != nil
{
if let dataAsAny: NSArray = try? JSONSerialization.jsonObject(with: stuff!, options: .mutableContainers) as! NSArray
{
let dic: [String: AnyObject] = dataAsAny[0] as! [String: AnyObject]
let str: String = dic["value"] as! String
let data: NSData = NSData(base64Encoded: str, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)!
//ERROR, UIImage is always nil
let p: UIImage = UIImage(data: data as Data)!
imV.image = p
}
}
}
//nodejs code
//import modules
var path = require("path");
var bodyParser = require('body-parser');
var express = require('express');
var mysql = require("mysql");
var fs = require('fs');
var app = express();
app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true, parameterLimit:50000}));
var con = mysql.createConnection(
{ /*my credentials*/ } );
app.post('/testInsert', function (req, res)
{
var queryString = "Insert into Test(value) VALUES ( ? );";
con.query(queryString,[req.body.pic],function(err,rows)
{
if(err) throw err;
});//CON.QUERY end
});
app.post('/testgrab', function (req, res)
{
var queryString = "select test.value from test where id = 3;";
con.query(queryString,function(err,rows)
{
if(err) throw err;
res.send(rows);
});//CON.QUERY end
});
//mysql has 1 table called test with 2 colums (id,value) of type (int, medium text)
I solved my problem, the issue was that base 64 string contains the characters +/= which aren't allowed in URL encoding so i had to convert my base 64 string to correct url encoded format then i upload the string and can retrieve it correctly, to url encode the string i added percent escapes for the +=/ chars
Related
In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}
You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}
I'm trying to send an uploaded image to server. I'm getting the image from photos successfully, and I'm attaching it to a UIImageView in ViewController. Now I need to send this image to server along with other data. I'm able to send all data successfully except the image.
Here is my func:
func placeOrder(withOrder: Order) {
let returnedJobId: String? = UserDefaults.standard.object(forKey: "jobId") as? String
let returnedOrderPrice: String? = UserDefaults.standard.object(forKey: "orderPrice") as? String
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let currentDateTime = formatter.string(from: Date())
let selectedImage = imagePlaceHolder.image!
let uploadedFile = selectedImage.jpegData(compressionQuality: 0.5)
DispatchQueue.main.async {
let date = self.chosenTimeDateTextFieldDisplay.text!
let address = self.addressField.text!
let phone = self.phoneField.text!
let comments = self.commentsEntryView.text!
let file = uploadedFile
let jobId = returnedJobId!
let price = returnedOrderPrice!
let headers = [
"content-type" : "multipart/form-data",//application/x-www-form-urlencoded",
"cache-control": "no-cache",
"postman-token": "dded3e97-77a5-5632-93b7-dec77d26ba99"
]
let user = CoreDataFetcher().returnUser()
let provider = user.provider_id
let userID = user.id
let userType = user.user_type
let postData = NSMutableData(data: "data={\"user_type\":\"\(userType)\",\"job_id\":\"\(jobId)\",\"user_id\":\"\(userID)\",\"provider_id\":\"\(provider)\",\"order_placing_time\":\"\(currentDateTime)\",\"order_start_time\":\"\(date)\",\"order_address\":\"\(address)\",\"order_phone\":\"\(phone)\",\"order_comments\":\"\(comments)\",\"order_price\":\"\(price)\",\"$_FILES\":\"\(file!)\"}".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "http://Api/v2/placeOrder")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
if let dataNew = data, let responseString = String(data: dataNew, encoding: .utf8) {
print(responseString)
DispatchQueue.main.async {
do {
let fetcher = CoreDataFetcher()
let json = try JSON(data: data!, options: .allowFragments)
let answer = json["answer"]
let status = json["status"]
let orderID = answer.int!
if status == "ok" {
print("Status is OK")
}
fetcher.addOrderID(orderId: orderID, toOrder: withOrder)
print("Order id has been saved!")
} catch {
print("Order ID Counldn't be Saved!")
}
}
}
}
})
dataTask.resume()
}
}
This method isn't working. as it should be made a multipart form data
How to rewrite my func to be a multipart form data to convert the image to PNG file and attach it in the API request?
The answer to your question strongly depends on how you are trying to send your data to server. Ironically you have provided no information about that at all. If this is JSON based API then two most likely solutions are:
You will need to convert your data to base64 string
You will need to send a multipart form data
This should all be covered somewhere in the documentation of your API. For base64 things are very simple; you can use image.pngData()?.base64EncodedString(). The multipart form data is a bit more complicated but you can find a lot of posts on it like this one.
For non-JSON I guess you could just POST raw data with correct content type header. But this again depends on the API implementation.
In any case you could also try to find what the error is if any.
Getting an error since the latest update.
Can't get data returned from https website.
Please help
Code:
func GetValueFromWebsite(_ Url: String, ShowError: Bool) -> String {
var xml:String = ""
let url = URL(string: Url)
let request = NSMutableURLRequest(url: url!)
let session = URLSession.shared
session.sendSynchronousRequest(request: request) { data, response, error in
xml = String(data: data! as Data, encoding: String.Encoding.utf8)!
xml = xml.replacingOccurrences(of: "\"", with: "")
xml = xml.replacingOccurrences(of: "\r\n", with: "")
}
return xml
}
Encoding an UIImage as a Base64 string works on the device, but transferring the string to the server somehow corrupts the string and prevents the server from successfully decoding the image.
Any suggestions on the problem?
// Define params
params["thumbnail_base64"] = imageToBase64(blockSet.thumbnailURL)
...
// Convert params -> query string
let postString = buildQueryString(params)
// Define upload URL
let uploadURL = NSURL(string: RootURL + UploadFilePath)!
// Hit server
let request = NSMutableURLRequest(URL: uploadURL)
request.HTTPMethod = "POST"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
...
private func buildQueryString(parameters: [String:String], omitQuestionMark: Bool = false) -> String {
var urlVars = [String]()
for (k, var v) in parameters {
v = v.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
urlVars += [k + "=" + "\(v)"]
}
return ((urlVars.isEmpty || omitQuestionMark) ? "" : "?") + urlVars.joinWithSeparator("&")
}
private func imageToBase64(filename: String) -> String {
// Get image path
let imagePath = getFilePath(filename)
// Convert image to base64 or return empty string
if let imageData = NSData(contentsOfFile: imagePath) {
let base64String = imageData.base64EncodedStringWithOptions(.EncodingEndLineWithLineFeed)
return base64String
} else {
printError("Error converting image to Base64: missing image. Filename: \(filename)")
return ""
}
}
The problem is with the queryString, base64 is long text with many characters, let JSON do the work for you
Use the next (with some example of NodeJS)
let params = NSMutableDictionary();
//you can only set `serializable` values
params.setValue(imageToBase64(),forKey:"base64")
params.setValue(username,forKey:"username")
params.setValue(["array","of","string"],forKey:"arr")
let uploadURL = NSURL(string: theURL)!
// Hit server
let request = NSMutableURLRequest(URL: uploadURL)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions(rawValue: 0))
request.HTTPBody = jsonData
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
}).resume()
} catch let error as NSError {
print(error)
}
nodejs:
var buffer = new Buffer(request.body["base64"], 'base64')
fs.writeFile('test.jpeg',buffer,"base64"); //Works
var username = request.body["username"];
var someStringsArr = request.body["arr"]
by the way...
you wrote the function buildQueryString, which is already exists in Foundation
let urlComponents = NSURLComponents(string: "http://myUrl.com/getApi/")!
urlComponents.queryItems = [NSURLQueryItem]()
urlComponents.queryItems!.append(NSURLQueryItem(name:"myKeyA",value:"myValueA"))
urlComponents.queryItems!.append(NSURLQueryItem(name:"myKeyB",value:"myValueB"))
print(urlComponents.URL!) //http://myUrl.com/getApi/?myKeyA=myValueA&myKeyB=myValueB
Use url query if want to send GET parameters via the URL
I'm using CloudKit in my app as a way to persist data remotely. One of my records has an CKAsset property that holds an image. When I'm fetching the records, I realized that it's taking so much time to finish the query. After multiple testings, I concluded that when you query records, CloutKit downloads the entire Asset file with the record object. Hence, when you obtain the Asset from the record object and request it's fileURL, it gives you a local file path URL and not an HTTP kind of URL. This is as issue to me because you have to let the user wait so much time for the entire records to be downloaded (with their associated images) before the query ends. Coming from a Parse background, Parse used to give you the HTTP URL and the query is fast enough to load the UI with the objects while loading the images async. My question is, how can I achieve what Parse does? I need to restrict the queries from downloading the Assets data and obtain a link to load the images asynchronously.
You can use CloudKit JavaScript for accessing url of asset. Here is an example;
(Used Alamofire and SwiftyJSON)
func testRecordRequest() -> Request {
let urlString = "https://api.apple-cloudkit.com/database/1/" + Constants.container + "/development/public/records/query?ckAPIToken=" + Constants.cloudKitAPIToken
let query = ["recordType": "TestRecord"]
return Alamofire.request(.POST, urlString, parameters: ["query": query], encoding: .JSON, headers: nil)
}
JSON response contains a "downloadURL" for the asset.
"downloadURL": "https://cvws.icloud-content.com/B/.../${f}?o=AmVtU..."
"${f}" seems like a variable so change it to anything you like.
let downloadURLString = json["fields"][FieldNames.image]["value"]["downloadURL"].stringValue
let recordName = json["recordName"].stringValue
let fileName = recordName + ".jpg"
let imageURLString = downloadURLString.stringByReplacingOccurrencesOfString("${f}", withString: fileName)
And now we can use this urlString to create a NSURL and use it with any image&cache solutions such as Kingfisher, HanekeSwift etc.
(You may also want to save image type png/jpg)
Make two separate calls for the same record. The first call should fetch all the NON-asset fields you want, and then second request should fetch the required assets.
Something like:
let dataQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
dataQueryOperation.desiredKeys = ["name", "age"] // etc
database.addOperation(dataQueryOperation)
let imageQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
imageQueryOperation.desiredKeys = ["images"]
database.addOperation(imageQueryOperation)
If need, refactor this into a method so you can easily make a new CKQueryOperation for every asset-containing field.
Happy hunting.
Like others have said you cannot get the url(web) of the CKAsset. So your best options are
1. Use a fetch operation with progress per individual UIImageView. I have built a custom one that shows a progress to the user. Cache is not included but you can make a class and adopt NSCoding and save the entire record to cache directory. Here you can see a fetch that i have a completion on to send the asset back to where i call it from to combine it with my other data.
// only get the asset in this fetch. we have everything else
let operation = CKFetchRecordsOperation(recordIDs: [myRecordID])
operation.desiredKeys = ["GameTurnImage"]
operation.perRecordProgressBlock = {
record,progress in
dispatch_async(dispatch_get_main_queue(), {
self.progressIndicatorView.progress = CGFloat(progress)
})
}
operation.perRecordCompletionBlock = {
record,recordID,error in
if let _ = record{
let asset = record!.valueForKey("GameTurnImage") as? CKAsset
if let _ = asset{
let url = asset!.fileURL
let imageData = NSData(contentsOfFile: url.path!)!
dispatch_async(dispatch_get_main_queue(), {
self.image = UIImage(data: imageData)
self.progressIndicatorView.reveal()
})
completion(asset!)
}
}
}
CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
The other option is to store images on an AWS server or something comparable and then you can use something like SDWebImage to do all of the cache or heavy lifting and save a string in the CKRecord to the image.
I have asked several people about a CKAsset feature to expose a url. I don't know about the JS Api for CloudKit but there might be a way to do it with this but i will let others commment on that.
Using the Post method from #anilgoktas, we can also get the download URL without using Alamofire and SwiftyJSON, although it may be a little more complicated and not as neat.
func Request() {
let container = "INSERT CONTAINER NAME"
let cloudKitAPIToken = "INSERT API TOKEN"
let urlString = "https://api.apple-cloudkit.com/database/1/" + container + "/development/public/records/query?ckAPIToken=" + cloudKitAPIToken
let query = ["recordType": "testRecord"]
//create the url with URL
let url = URL(string: urlString)! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: ["query": query], options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
DispatchQueue.main.async {
for i in json.values {
if let sub = i as? NSArray {
for j in sub {
if let dict = j as? NSDictionary, let subDict = dict["fields"] as? NSDictionary, let titleDict = subDict["title"] as? [String:String], let title = titleDict["value"], let videoDict = subDict["video"] as? NSDictionary, let vidVal = videoDict["value"] as? NSDictionary, let url = vidVal["downloadURL"] as? String {
let downloadURL = url.replacingOccurrences(of: "${f}", with: "\(title).jpg")
print(title)
print(downloadURL)
}
}
}
}
// handle json...
}
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
Here is my approach.
Let's say I have Record Type : Books
BookID (auto-id or your unique id)
BookName string
BookImage CKAsset
BookURL string
Incase I use CKAssest I store in BookURL :
"Asset:\BookID.png "
Incase I used external server to store the images, I use normal URL in BookURL
"http://myexternalServer\images\BookID.png"
Queries :
with desiredKeys I query all fields without BookImage(CKAsset) field.
if BookURL is empty , there is no image for the book.
if BookURL start with "Asset:\" I query for BookImage from cloudkit
if BookURL is normal URL I download the image from external server (NSUrlSession)
This way , any time I can change and decide how to store image, on cloudkit(CKAssets) or on external server (http downloads)