I try to save two different images at the same time at one storage location
This is my function to save the information
var text: String = ""
var addedByUser: String?
var userImage: UIImage?
var jobImage: UIImage!
var downloadURL: String?
var userDownloadURL: String?
let ref: DatabaseReference!
init(text: String? = nil, jobImage: UIImage? = nil, addedByUser: String? = nil, userImage: UIImage? = nil) {
self.text = text!
self.jobImage = jobImage
self.addedByUser = addedByUser
self.userImage = userImage
ref = Database.database().reference().child("jobs").childByAutoId()
}
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
addedByUser = value["addedByUser"] as? String
downloadURL = value["imageDownloadURL"] as? String
userDownloadURL = value["imageUserDownloadURL"] as? String
}
}
func save() {
let newPostKey = ref.key
// save jobImage
if let imageData = jobImage.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("jobImages/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let postDictionary = [
"imageDownloadURL" : self.downloadURL!,
"imageUserDownloadURL" : self.userDownloadURL!,
"text" : self.text,
"addedByUser" : self.addedByUser!
] as [String : Any]
self.ref.setValue(postDictionary)
})
}
}
I tried following code
if let imageData = jobImage.jpegData(compressionQuality: 0.5), ((userImage?.jpegData(compressionQuality: 0.5)) != nil) {
But it's not working as then nothing get's saved in the database...
Do you have any ideas how I can solve it?
I believe the question is how do I upload an image to two different locations. It's unclear why there's an observe function so this answer ignores that as it may not be needed.
Starting with your code, your save function will look like this
func save() {
self.uploadImageTask(imageName: "my_image.png", toLocation: "jobImage")
self.uploadImageTask(imageName: "my_image.png", toLocation: "anotherLocation")
}
and then the upload function
func uploadImageTask(imageName: String, toLocation: String) {
let theImage = UIImage(named: imageName) //set up your image here
let data = UIImagePNGRepresentation(theImage)! //we're doing a PNG
let storage = Storage.storage()
let storageRef = storage.reference()
let locationRef = storageRef.child("images").child(toLocation)
let imageLocationRef = locationRef.child(imageName)
// Upload the file to the path "images/location/imageName"
let uploadTask = locationRef.putData(data, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
print("error while uploading")
return
}
let size = metadata.size // Metadata contains file metadata such as size, content-type.
print(size)
locationRef.downloadURL { (url, error) in
guard let downloadURL = url else {
print("an error occured after uploading and then downloading")
return
}
let x = downloadURL.absoluteString
print(x) //or build a dict and save to Firebase
}
}
}
the result is an image stored at
/images/jobImage/my_image.png
/images/anotherLocation/my_image.png
and it will also print the path to each image, which could be stored in Firebase.
Related
I'm playing with Swift and JSONPlaceholder. I want to retrieve all the data contained in: https://jsonplaceholder.typicode.com/photos
I created a function that is acceding to the url, downloading the JSON but then I don't know how can I obtain the title and the thumbnailUrl to pass then for populate the tableView. In the past I used this code but now it's not working because on the JSONPlaceholder there are no array.
Any help for re-arrange the code for read and obtain the jsonplaceholder elements?
func loadList(){
let url = URL(string: urlReceived)
var myNews = NewInfo()
let task = URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil{
print("ERROR")
}
else{
do {
if let content = data{
let myJson = try JSONSerialization.jsonObject(with: content, options: .mutableContainers)
//print(myJson)
if let jsonData = myJson as? [String : Any] {
if let myResults = jsonData["list"] as? [[String : Any]]{
//dump(myResults)
for value in myResults{
//Read time string from root
if let time = value ["dt_txt"] as? String{
myNews.time = time
}
//Read main container
if let main = value["main"]
as? [String : Any]{
if let temperature = main["temp"] as? Double {
myNews.temperature = String(temperature)
}
}
//Read from weather container
if let weather = value["weather"] as? [[String: Any]]{
for value in weather{
if let weatherContent = value["description"] as? String{
myNews.weatherDescription = weatherContent
}
}
}
self.myTableViewDataSource.append(myNews)
}
dump(self.myTableViewDataSource)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
catch{
}
}
}
task.resume()
}//End func
Okey, so with Alamofire + SwiftyJSON, you can do this:
func loadList(){
let url = "https://jsonplaceholder.typicode.com/photos"
AF.request(url).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
print(json)
for value in json.arrayValue {
let url = value.dictionaryValue["url"]!.stringValue
let albumId = value.dictionaryValue["albumId"]!.stringValue
let thumbnailUrl = value.dictionaryValue["thumbnailUrl"]!.stringValue
let id = value.dictionaryValue["id"]!.stringValue
let title = value.dictionaryValue["title"]!.stringValue
// Add this album to array.
let album = AlbumModel(id: id, albumId: albumId, title: title, thumbnailUrl: thumbnailUrl)
albums.append(album)
}
case .failure(let error):
print(error)
}
}
}
EDIT:
I made model for values
class AlbumModel {
var id: String?
var albumId: String?
var title: String?
var thumbnailUrl: String?
init(id: String?, albumId: String?, title: String?, thumbnailUrl: String?){
self.id = id
self.albumId = albumId
self.title = title
self.thumbnailUrl = thumbnailUrl
}
}
After that, just create an array like var albums = [AlbumModel]() and you can append all the albums to this. Easy to use after in tableViewcell (example: albums[indexPath.row].id)
func uploadImage(){
let storage = Storage.storage()
let storageRef = storage.reference()
let uploadData = self.imageView.image!.jpegData(compressionQuality: 0.75)
let imagesRef = storageRef.child("images/myImage.jpg") //not sure how is it done
let uploadTask = imagesRef.putData(uploadData!, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
return
}
let size = metadata.size
imagesRef.downloadURL { (url, error) in
guard let downloadURL = url else {
return
}
}
}
}
func retrieveData(){
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let userData = snapshot.value as? NSDictionary
print("Welcome back,", userData?["username"])
}) { (error) in
print(error.localizedDescription)
}
}
Hi, I'm looking for a way for user to upload image onto firebase based on their user id. Can anyone suggest how this can be achieved? Thanks in advance.
You can do something like this:
func sendMessageWithProperties(properties: [String: Any]) {
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = self.user?.id
let fromId = Auth.auth().currentUser?.uid
let timestamp: Int = Int(NSDate().timeIntervalSince1970)
var values: [String: Any] = ["toId": toId!,
"fromId": fromId!,
"timestamp": timestamp]
// To append other properties into values
properties.forEach({values[$0] = $1})
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error!)
return
}
let userMessageRef = Database.database().reference().child("user-messages").child(fromId!).child(toId!)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId: 1])
let receiverMessageRef = Database.database().reference().child("user-messages").child(toId!).child(fromId!)
receiverMessageRef.updateChildValues([messageId: 1])
}
}
I have looked at a lot of questions and I do not believe this is a result of reusing a cell as the new cells image is correct, but and existing cells image is incorrect and used to be correct. I'll post the images first so the issue is easier to understand.
I have a collectionView of image cells (similar to Instagrams user page). I'm fetching all of the data from Firebase. I get the first 12 posts on the initial loading of the screen. However, if you scroll down quickly an EXISTING cells image changes to a newly fetched image. I'm not sure why this is happening... Maybe it's a caching issue? The issue only occurs the first time you load the screen. I've tried setting the images to nil like this:
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.image = UIImage()
}
This didn't help the issue though.
Here is my cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! ImageCell
cell.indexPath = indexPath
cell.imageView.downloadImage(from: currentTablePosts[indexPath.row].pathToImage)
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.black.cgColor
return cell
}
Image downloading and caching:
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL! as NSString) {
self.image = imageToCache
return
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
// user an alert to display the error
if let topController = UIApplication.topViewController() {
Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
}
return
}
DispatchQueue.main.async {
// create UIImage
let imageToCache = UIImage(data: data!)
// add image to cache
imageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
self.image = imageToCache
}
}
task.resume()
}
}
Firebase Queries:
static func getInitialTablesPosts(tableNumber: String) {
tableReference.child(tableNumber).queryLimited(toLast: 12).observeSingleEvent(of: .value, with: { snap in
for child in snap.children {
let child = child as? DataSnapshot
if let post = child?.value as? [String: AnyObject] {
let posst = Post()
if let author = post["author"] as? String, let likes = post["likes"] as? Int, let pathToImage = post["pathToImage"] as? String, let postID = post["postID"] as? String, let postDescription = post["postDescription"] as? String, let timestamp = post["timestamp"] as? Double, let category = post["category"] as? String, let table = post["group"] as? String, let userID = post["userID"] as? String, let numberOfComments = post["numberOfComments"] as? Int, let region = post["region"] as? String {
posst.author = author
posst.likes = likes
posst.pathToImage = pathToImage
posst.postID = postID
posst.userID = userID
posst.fancyPostDescription = Helper.createAttributedString(author: author, postText: postDescription)
posst.postDescription = author + ": " + postDescription
posst.timestamp = timestamp
posst.table = table
posst.region = region
posst.category = category
posst.numberOfComments = numberOfComments
posst.userWhoPostedLabel = Helper.createAttributedPostLabel(username: author, table: table, region: region, category: category)
if let people = post["peopleWhoLike"] as? [String: AnyObject] {
for(_, person) in people {
posst.peopleWhoLike.append(person as! String)
}
}
currentTablePosts.insert(posst, at: 0)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableCollectionView"), object: nil)
} // end of if let
}
}
})
tableReference.removeAllObservers()
}
static func getMoreTablePosts(tableNumber: String, lastVisibleKey: String) {
print("FIRED...")
let currentNumberOfPosts = currentTablePosts.count
print("Number of posts before fetiching ", currentNumberOfPosts)
print("Oldest post key ", oldestTableKeys[tableNumber] ?? "not set yet", "***********")
tableReference.child(tableNumber).queryOrderedByKey().queryEnding(atValue: lastVisibleKey).queryLimited(toLast: 12).observeSingleEvent(of: .value, with: { snap in
for child in snap.children {
let child = child as? DataSnapshot
if let post = child?.value as? [String: AnyObject] {
if let id = post["postID"] as? String {
if id == lastVisibleKey {
return
}
}
let posst = Post()
if let author = post["author"] as? String, let likes = post["likes"] as? Int, let pathToImage = post["pathToImage"] as? String, let postID = post["postID"] as? String, let postDescription = post["postDescription"] as? String, let timestamp = post["timestamp"] as? Double, let category = post["category"] as? String, let table = post["group"] as? String, let userID = post["userID"] as? String, let numberOfComments = post["numberOfComments"] as? Int, let region = post["region"] as? String {
posst.author = author
posst.likes = likes
posst.pathToImage = pathToImage
posst.postID = postID
posst.userID = userID
posst.fancyPostDescription = Helper.createAttributedString(author: author, postText: postDescription)
posst.postDescription = author + ": " + postDescription
posst.timestamp = timestamp
posst.table = table
posst.region = region
posst.category = category
posst.numberOfComments = numberOfComments
posst.userWhoPostedLabel = Helper.createAttributedPostLabel(username: author, table: table, region: region, category: category)
if let people = post["peopleWhoLike"] as? [String: AnyObject] {
for(_, person) in people {
posst.peopleWhoLike.append(person as! String)
}
}
currentTablePosts.insert(posst, at: currentNumberOfPosts)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableCollectionView"), object: nil)
if let oldestTableKey = oldestTableKeys[tableNumber] {
if postID == oldestTableKey {
print("returning")
print("number of posts on return \(currentTablePosts.count)")
return
}
}
} // end if let
}
}
})
tableReference.removeAllObservers()
}
* UPDATE *
The image caching used here still had some issues in my experience. I have since moved on to using Kingfisher which is extremely easy to setup and use.
* OLD SOLUTION *
Found a solution based on the answer suggested in the comments.
I modified the extension I am using to cache my images. Although in the future I think I will subclass UIImageView. Here is the modified version of my code.
import UIKit
let userImageCache = NSCache<NSString, UIImage>()
let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString: String?
extension UIImageView {
public func imageFromServerURL(urlString: String, collectionView: UICollectionView, indexpath : IndexPath) {
imageURLString = urlString
if let url = URL(string: urlString) {
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil{
if let topController = UIApplication.topViewController() {
Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
}
return
}
DispatchQueue.main.async(execute: {
if let imgaeToCache = UIImage(data: data!){
if imageURLString == urlString {
self.image = imgaeToCache
}
imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling
collectionView.reloadItems(at: [indexpath])
}
})
}) .resume()
}
}
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL! as AnyObject) as? UIImage {
self.image = imageToCache
return
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
// user an alert to display the error
if let topController = UIApplication.topViewController() {
Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
}
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: imgURL! as AnyObject)
self.image = imageToCache
}
}
task.resume()
}
func downloadUserImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = userImageCache.object(forKey: imgURL! as NSString) {
self.image = imageToCache
return
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
// user an alert to display the error
if let topController = UIApplication.topViewController() {
Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
}
return
}
DispatchQueue.main.async {
// create UIImage
let imageToCache = UIImage(data: data!)
// add image to cache
userImageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
self.image = imageToCache
}
}
task.resume()
}
}
I created the first method to cache the images for the collectionView and the other methods are still used for tableViews. The cache was also changed from let imageCache = NSCache<NSString, UIImage>() to let imageCache = NSCache<AnyObject, AnyObject>()
What I have to is just let user pick photo, upload it on server and then decode back and display it. What I am doing now is encode image and then pass it to a model as Base64. I store it as bytea in PosgtgreSQL.
let imageb: NSData = UIImageJPEGRepresentation(image, 0.7)! as NSData
let dataDecoded: String = imageb.base64EncodedString(options: .lineLength64Characters)
let imageStr: String = dataDecoded.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
then inside my model I have
private var JSON: [String: Any] {
get {
return ["data": self.imageData]
}
}
then I post it on server using Alamofire
APILayer.shared.request(parameters: self.JSON, url: "photo/create", method: .post) { responce in
//some handling stuff
}
server side client gateway method
let photos = user.getPhotos()
try! response.setBody(json: PhotoDocument.jsonFrom(array: photos))
object methods
func getPhotos() -> [PhotoDocument] {
return PhotoDocumentMapper.search(id: self.id)
}
private var JSON: [String: Any] {
get {
return ["description": self.description, "importancy": self.importancy, "date": self.date, "data": self.imageData, "id": self.id]
}
}
class func jsonFrom(array: [PhotoDocument]) -> [String: Any] {
var jsons: [Any] = []
for photo in array {
jsons.append(photo.JSON)
}
return ["photos" : jsons]
}
datamapper method
class func search(id: Int) -> [PhotoDocument] {
let p = PGConnection()
let _ = p.connectdb("host=localhost port=5432 dbname=perfect")
let result = p.exec(statement: "select * from \"Photos\" where \"userId\" = $1", params: [id])
p.close()
var photos: [PhotoDocument] = []
let resultsNumber = result.numTuples() - 1
if resultsNumber != -1 {
for index in 0...resultsNumber {
let id = result.getFieldInt(tupleIndex: index, fieldIndex: 0)!
let description = result.getFieldString(tupleIndex: index, fieldIndex: 4)!
let date = result.getFieldString(tupleIndex: index, fieldIndex: 5)!
let imageData = result.getFieldString(tupleIndex: index, fieldIndex: 3)!
let importancy = result.getFieldInt(tupleIndex: index, fieldIndex: 2)!
let userId = result.getFieldInt(tupleIndex: index, fieldIndex: 1)!
let photo = PhotoDocument(userId: userId, description: description, date: date, id: id, imageData: imageData, importancy: importancy)
photos.append(photo)
}
}
return photos
}
then I receive all this data, and I receive huge String and try this, but it crashes on the first line
let dataDecoded: NSData = NSData(base64Encoded: photo.imageData, options: .ignoreUnknownCharacters)! //crash here
let decodedimage = UIImage(data: dataDecoded as Data)
self.test.image = decodedimage
What am I doing wrong? How can I store UIImage as Base64String as bytea of PostgreSQL?
So what I had to do is remove this line while encoding
let imageStr: String = dataDecoded.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
and change PostgreSQL field to be text and not bytea
I want to be able to load images and video to my UICollectionView.
This is my parse query code:
var arrayOfDetails = [Details]()
func queryFromParse(){
let query = PFQuery(className: "currentUploads")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let newObjects = objects as? [PFObject] {
for oneobject in newObjects {
let text = oneobject["imageText"] as! String
let username = oneobject["username"] as! String
let objID = oneobject.objectId!
let time = oneobject.createdAt!
let likedBy = oneobject["likedBy"] as! NSArray
let comments = oneobject["comments"] as! NSArray
if let userImage = oneobject["imageFile"] as? PFFile {
let userImage = oneobject["imageFile"] as! PFFile
let imageURL = userImage.url
let OneBigObject = Details(username: username, text: text, CreatedAt: time, image: imageURL!, objID: objID, likedBy: likedBy, comments: comments)
self.arrayOfDetails.append(OneBigObject)
dispatch_async(dispatch_get_main_queue()) { self.collectionView.reloadData() }
}
}
}
}
}
}
My struct details:
struct Details {
var username:String!
var text:String!
var CreatedAt:NSDate!
var image:String!
var objID:String!
var likedBy:NSArray
var comments:NSArray
init(username:String,text:String,CreatedAt:NSDate,image:String,objID:String,likedBy:NSArray,comments:NSArray){
self.username = username
self.text = text
self.CreatedAt = CreatedAt
self.image = image
self.objID = objID
self.likedBy = likedBy
self.comments = comments
}
}
In my cellForItemAtIndexPath i use let post = self.arrayOfDetails[indexPath.row] to get the current object, but i have a little problem here. In parse i store the images as File with name "uploaded_image.png", and the videos as File with name "uploaded_video.mp4". How can i make a UIImageView in the UICollectionViewCell if the file is a "uploaded_image.png" and a video player if the file is a "uploaded_video.mp4"?
Or any other suggestions?