I want to send images to firebase and then load them into the chat bubble. Right now after a user selects an image it gets loaded into firebase and also JSQPhotoMediaItem shows the image in the chat. However the other user only sees an empty bubble and when I reload the view it also shows as a blank bubble on my end as well. How can I fix the empty bubble and fill it with my photo url from Firebase.
class ChatViewController: JSQMessagesViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let incomingBubble = JSQMessagesBubbleImageFactory(bubble: UIImage.jsq_bubbleCompactTailless(), capInsets: UIEdgeInsets.zero).incomingMessagesBubbleImage(with: UIColor(white: 0.90, alpha: 1.0))
let incomingBubbleWithTail = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(white: 0.90, alpha: 1.0))
let outgoingBubble = JSQMessagesBubbleImageFactory(bubble: UIImage.jsq_bubbleCompactTailless(), capInsets: UIEdgeInsets.zero).outgoingMessagesBubbleImage(with: UIColor.red)
let outgoingBubbleWithTail = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.red)
var messages:[JSQMessage]!
var conversation:Conversation!
var conversationKey:String!
var partner:Users!
var partnerImage:UIImage?
var downloadRef:DatabaseReference?
#objc func handleUploadTap(){
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
present(imagePickerController, animated: true, completion: nil)
print("image tapped")
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage{
selectedImageFromPicker = editedImage
}else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage{
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker{
let mediaItem = JSQPhotoMediaItem(image: nil)
mediaItem?.appliesMediaViewMaskAsOutgoing = true
mediaItem?.image = UIImage(data: UIImageJPEGRepresentation(selectedImage, 0.5)!)
let sendMessage = JSQMessage(senderId: senderId, displayName: self.senderId, media: mediaItem)
self.messages.append(sendMessage!)
self.finishSendingMessage()
uploadToFirebaseStorageUsingImage(image: selectedImage)
}
dismiss(animated: true, completion: nil)
}
private func uploadToFirebaseStorageUsingImage(image: UIImage){
let imageName = NSUUID().uuidString
let ref = Storage.storage().reference().child("message_images").child(imageName)
if let uploadData = UIImageJPEGRepresentation(image, 0.3){
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("failed to load:", error)
return
}
if let imageUrl = metadata?.downloadURL()?.absoluteString{
self.sendMessageWithImageUrl(imageUrl: imageUrl)
}
})}}
private func sendMessageWithImageUrl(imageUrl: String){
guard let user = currentUser else { return }
let ref = Database.database().reference().child("conversations/threads/\(conversation.key)").childByAutoId()
let messageObject = [
"text":" ",
"recipient": conversation.partner_uid,
"sender":user.uid,
"senderName": user.firstLastName,
"imageUrl":imageUrl,
"timestamp": [".sv":"timestamp"]
] as [String:Any]
ref.setValue(messageObject, withCompletionBlock: { error, ref in
})
return self.finishSendingMessage(animated: true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: self, action: #selector(handleDismiss))
view.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
self.senderDisplayName = ""
if let user = Auth.auth().currentUser {
self.senderId = user.uid
} else {
self.senderId = ""
}
messages = [JSQMessage]()
let addImage = self.inputToolbar.contentView.leftBarButtonItem
addImage?.isUserInteractionEnabled = true
addImage?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleUploadTap)))
self.inputToolbar.contentView.rightBarButtonItem.setTitleColor(UIColor.red, for: .normal)
// self.inputToolbar.contentView.leftBarButtonItemWidth = 0
self.inputToolbar.contentView.textView.placeHolder = "New message"
self.inputToolbar.contentView.textView.keyboardAppearance = .light
//collectionView?.collectionViewLayout.incomingAvatarViewSize = CGSize(width: 32, height: 32)
collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero
collectionView?.collectionViewLayout.springinessEnabled = true
collectionView?.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
collectionView?.reloadData()
title = partner.firstLastName
conversation.printAll()
downloadRef = Database.database().reference().child("conversations/threads/\(conversation.key)")
downloadMessages()
}
#objc func handleDismiss() {
self.dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
conversation.printAll()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
downloadRef?.removeAllObservers()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.messages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
let data = self.messages[indexPath.row]
return data
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
switch(data.senderId) {
case self.senderId:
return self.outgoingBubble
default:
return self.incomingBubble
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
let data = messages[indexPath.row]
switch(data.senderId) {
case self.senderId:
return nil
default:
if partnerImage != nil {
let image = JSQMessagesAvatarImageFactory.avatarImage(with: partnerImage!, diameter: 48)
return image
}
return nil
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let data = messages[indexPath.row]
switch(data.senderId) {
case self.senderId:
cell.textView?.textColor = UIColor.white
default:
cell.textView?.textColor = UIColor.black
}
return cell
}
override func collectionView
(_ collectionView: JSQMessagesCollectionView!, attributedTextForCellTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
let currentItem = self.messages[indexPath.item]
if indexPath.item == 0 && messages.count > 8 {
return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: currentItem.date)
}
if indexPath.item > 0 {
let prevItem = self.messages[indexPath.item-1]
let gap = currentItem.date.timeIntervalSince(prevItem.date)
if gap > 1800 {
return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: currentItem.date)
}
} else {
return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: currentItem.date)
}
return nil
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellTopLabelAt indexPath: IndexPath!) -> CGFloat {
if indexPath.item == 0 && messages.count > 8 {
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
if indexPath.item > 0 {
let currentItem = self.messages[indexPath.item]
let prevItem = self.messages[indexPath.item-1]
let gap = currentItem.date.timeIntervalSince(prevItem.date)
if gap > 1800 {
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
if prevItem.senderId != currentItem.senderId {
return 1.0
} else {
return 0.0
}
} else {
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellBottomLabelAt indexPath: IndexPath!) -> CGFloat {
return 0.0
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
guard let user = currentUser else { return }
let ref = Database.database().reference().child("conversations/threads/\(conversation.key)").childByAutoId()
let messageObject = [
"recipient": conversation.partner_uid,
"sender":user.uid,
"senderName": user.firstLastName,
"text":text,
"timestamp": [".sv":"timestamp"],
"imageUrl": " "
] as [String:Any]
ref.setValue(messageObject, withCompletionBlock: { error, ref in
})
return self.finishSendingMessage(animated: true)
}
func downloadMessages() {
self.messages = []
downloadRef?.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String:AnyObject]
let recipient = dict["recipient"] as! String
let sender = dict["sender"] as! String
let text = dict["text"] as! String
let timestamp = dict["timestamp"] as! Double
let imageUrl = dict["imageUrl"] as! String
let date = NSDate(timeIntervalSince1970: timestamp/1000)
var img: UIImage?
let mediaItem = JSQPhotoMediaItem(image: nil)
mediaItem?.appliesMediaViewMaskAsOutgoing = (id == self.senderId)
let message = JSQMessage(senderId: sender, senderDisplayName: "", date: date as Date!, text: text, media: mediaItem)
if img != nil{
mediaItem?.image = img! as UIImage
self.collectionView!.reloadData()
}
self.messages.append(message!)
self.reloadMessagesView()
self.finishReceivingMessage(animated: true)
})
}
func reloadMessagesView() {
self.collectionView?.reloadData()
guard let user = Auth.auth().currentUser else{ return }
let ref = Database.database().reference().child("conversations/users/\(user.uid)/\(conversation.partner_uid)/seen")
ref.setValue(true)
}
}
in your function downloadMessages() you get the imageUrl
let imageUrl = dict["imageUrl"] as! String
but when you append the message to messages, you do nothing with that imageUrl?
let message = JSQMessage(senderId: sender, senderDisplayName: "", date: date as Date!, text: text )
self.messages.append(message!)
your JSQMessage with an image should include a mediaItem (JSQPhotoMediaItem, otherwise you are just loading an empty message
var img: UIImage?
let mediaItem = JSQPhotoMediaItem(image: nil) //not a string
mediaItem?.appliesMediaViewMaskAsOutgoing = (id == self.senderId)
message = JSQMessage(senderId: id, senderDisplayName: name, date: date, media: mediaItem)
//get img from firebase, with whatever method you use
if img != nil {
mediaItem?.image = img! as UIImage
self.collectionView!.reloadData()
}
self.messages.append(message)
//etc
You might want to track the upload task: StorageReference.putData return a UploadTask. Add an onCompleteListener (or onSuccessListener, I don't remember exactly) to that task, when it's done, you can retrieve the uri and send the message.
This is for android, but similar for ios
Related
I am trying to populate a TableView with custom cells that have an image in them downloaded from Firebase. The custom cell is not appearing in the Tableview. I believe I configure the cells with an array named 'posts', that is full of 'TimeLinePost', however when I print 'posts.count' for the 'numberOfRows' func 0 appears, so something is not working somewhere. I may also be making a mistake in how I downloading the data. Any assistance where I am going wrong would be great thanks.
This is the code for the TableView and contains the 'TimeLinePost' Class -
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView?
var posts = [TimeLinePost]()
private let storage = Storage.storage().reference()
override func viewDidLoad() {
super.viewDidLoad()
self.table?.register(TableViewCell.nib(), forCellReuseIdentifier: TableViewCell.identifier)
table?.delegate = self
table?.dataSource = self
table?.reloadData()
}
#IBAction func unwindSegue(_ sender: UIStoryboardSegue){
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(posts.count)
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for:indexPath) as! TableViewCell
cell.configure(with: posts[indexPath.row])
return cell
}
}
class TimeLinePost {
var image: String
init (image: String) {
self.image = image
}
}
This is the code for uploading the data -
struct MyKeys {
static let imagesFolder = "imagesFolder"
static let uid = "uid"
static let imagesURL = "imagesURL"
static let imagesCollection = "imagesCollection"
}
class uploadViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var imageDownloadUrl: String?
#IBOutlet weak var photoImageView: UIImageView!
var original: UIImage!
private let storage = Storage.storage().reference()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func choosePhoto() {
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = .photoLibrary
navigationController?.present(picker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
self.navigationController?.dismiss(animated: true, completion: nil)
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
photoImageView.image = image
original = image
}
}
#IBAction func uploadPhoto(_ sender: Any) {
guard let image = photoImageView.image,
let data = image.jpegData(compressionQuality: 1.0)
else {
print("Error")
return
}
let imageName = UUID().uuidString
let imageReference = Storage.storage().reference().child("images").child(imageName)
imageReference.putData(data, metadata: nil) { (metadata, error) in
guard error == nil else {
print("Failed to upload")
return
}
imageReference.downloadURL{ (url, error) in
if let error = error {
print("Error")
return
}
guard let url = url else {
print("Error")
return
}
let dataReference = Firestore.firestore().collection(MyKeys.imagesCollection).document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let data = [
MyKeys.uid: documentUid,
MyKeys.imagesURL: urlString,
]
dataReference.setData(data) { (error) in
if let error = error {
print("Error:\(error)")
return
}
UserDefaults.standard.set(documentUid, forKey: MyKeys.uid)
}
}
}
}
}
And this is the code for my custom cell -
class TableViewCell: UITableViewCell {
#IBOutlet var imagePost: UIImageView!
static let identifier = "TableViewCell"
static func nib() -> UINib {
return UINib(nibName: "TableViewCell", bundle: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(with posts: TimeLinePost) {
self.imagePost.image = UIImage(named: posts.image)
}
func downloadImage(){
guard let uid = UserDefaults.standard.value(forKey: MyKeys.uid) else {
print("Error1")
return
}
let query = Firestore.firestore().collection(MyKeys.imagesCollection).whereField(MyKeys.uid, isEqualTo: uid)
query.getDocuments { (snapshot, error) in
if let error = error {
print("Error2")
return
}
guard let snapshot = snapshot, let data = snapshot.documents.first?.data(), let urlString = data[MyKeys.imagesURL] as? String, let url = URL(string: urlString) else {
print("Error3")
return
}
let resource = ImageResource(downloadURL: url)
self.imagePost.kf.setImage(with: resource, completionHandler: { (result) in
switch result {
case .success(_):
print("Success")
return
case .failure(_):
print("Error4")
return
}
})
}
}
}
I am Trying to add an instance of ServerValue.timestamp() into my firebase database, when i run the app , the time stamps continuously increase here is the code, im not sure how to stop the timestamp from increasing in firebase
here is my custom class and my tableview class
class Story
{
var text: String = ""
var timestamp: String = ""
let ref: DatabaseReference!
init(text: String) {
self.text = text
ref = Database.database().reference().child("People").child("HomeFeed").child("Posts").childByAutoId()
}
init(snapshot: DataSnapshot)
{
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["Post"] as! String
ref.updateChildValues(["timestamp":ServerValue.timestamp()])
let id = ref.key
Database.database().reference().child("People").child("HomeFeed").child("Posts").child("\(id)").child("timestamp").observeSingleEvent(of: .value) { (snapshot) in
let dope = snapshot.value as! Double
let x = dope / 1000
let date = NSDate(timeIntervalSince1970: x)
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .medium
DispatchQueue.main.async {
self.timestamp = formatter.string(from: date as Date)
self.timestamp = "\(value["timestamp"])"
}
}
}
}
func save() {
ref.setValue(toDictionary())
}
func toDictionary() -> [String : Any]
{
return [
"Post" : text,
"timestamp" : timestamp
]
}
}
here is the tableview class
class TableViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
let databaseRef = Database.database().reference()
#IBOutlet weak var tableView: UITableView!
var rub: StorageReference!
#IBAction func createpost(_ sender: Any) {
self.databaseRef.child("ProfileInfo").child(Auth.auth().currentUser!.uid).child("ProfilePic").observe(DataEventType.value) { (snapshot) in
let profpic = snapshot.value as? String
self.databaseRef.child("ProfileInfo").child(Auth.auth().currentUser!.uid).child("Full Name").observe(DataEventType.value) { (snapshot) in }
let fullname = snapshot.value as? String
if profpic == nil && fullname == nil {
let alert = UIAlertController(title: "Need to create profile", message: nil, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "To create profile", style: UIAlertActionStyle.default, handler: { action in self.performSegue(withIdentifier: "ToCreateprof", sender: nil)}))
alert.addAction(UIAlertAction(title: "Dissmiss", style: UIAlertActionStyle.default, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}else {
self.performSegue(withIdentifier: "ToPost", sender: nil)
}
} //if no prof pic and name, no posting
}
#IBAction func toCreateorprofile(_ sender: Any) {
self.databaseRef.child("ProfileInfo").child(Auth.auth().currentUser!.uid).child("ProfilePic").observe(DataEventType.value) { (snapshot) in
let profpic = snapshot.value as? String
self.databaseRef.child("ProfileInfo").child(Auth.auth().currentUser!.uid).child("Full Name").observe(DataEventType.value) { (snapshot) in }
let fullname = snapshot.value as? String
if profpic != nil && fullname != nil {
self.performSegue(withIdentifier: "olduser", sender: nil)
}else {
self.performSegue(withIdentifier: "ToCreateprof", sender: nil)
}
}
}
let storiesRef = Database.database().reference().child("People").child("HomeFeed").child("Posts")
var stories = [Story]()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// download stories
storiesRef.observe(.value, with: { (snapshot) in
self.stories.removeAll()
for child in snapshot.children {
let childSnapshot = child as! DataSnapshot
let story = Story(snapshot: childSnapshot)
self.stories.insert(story, at: 0)
}
self.tableView.reloadData()
})
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
self.tableView.reloadData()
refreshControl.endRefreshing()
}
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(TableViewController.handleRefresh(_:)),
for: UIControlEvents.valueChanged)
refreshControl.tintColor = UIColor.purple
return refreshControl
}()
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.reloadData()
self.tableView.addSubview(self.refreshControl)
tableView.delegate = self
tableView.dataSource = self
self.tableView.estimatedRowHeight = 92.0
self.tableView.rowHeight = UITableViewAutomaticDimension
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return stories.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Story Cell", for: indexPath) as! StoryTableviewcell
let story = stories[indexPath.row]
cell.story = story
self.databaseRef.child("ProfileInfo").child(Auth.auth().currentUser!.uid).child("Full Name").observe(.value) { (snapshot) in
let name = snapshot.value as? String
if name != nil {
cell.fullnamepost.text = name
}
}
rub = Storage.storage().reference().storage.reference(forURL:"gs://people-3b93c.appspot.com").child("ProfilePic").child(Auth.auth().currentUser!.uid)
if rub != nil {
// Create a UIImage, add it to the array
rub.downloadURL(completion: { (url, error) in
if error != nil {
print(error?.localizedDescription as Any)
return
}
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error as Any)
return
}
guard let imageData = UIImage(data: data!) else { return }
DispatchQueue.main.async {
cell.profimage.image = imageData
}
}).resume()
})
}
return cell
}
}
I'm having a problem regarding a feature where You can delete a cell and so delete and event using an Alamofire JSON request.
When I swipe the cell and click delete, the app crashes, but the event get deleted successfully and with no errors, in facts on Laravel side I get the event deleted.
I tried everything, but I really can't figure out how to fix the crash.
Can someone help me please?
here is my .Swift code:
import UIKit
import Alamofire
class EventViewController: UITableViewController {
#objc var transition = ElasticTransition()
#objc let lgr = UIScreenEdgePanGestureRecognizer()
#objc let rgr = UIScreenEdgePanGestureRecognizer()
let rc = UIRefreshControl()
#IBOutlet weak var myTableView: UITableView!
var myTableViewDataSource = [NewInfo]()
let url = URL(string: "http://ns7records.com/staffapp/api/events/index")
override func viewDidLoad() {
super.viewDidLoad()
loadList()
// Add Refresh Control to Table View
if #available(iOS 10.0, *) {
tableView.refreshControl = rc
} else {
tableView.addSubview(rc)
}
// Configure Refresh Control
rc.addTarget(self, action: #selector(refreshTableData(_:)), for: .valueChanged)
let attributesRefresh = [kCTForegroundColorAttributeName: UIColor.white]
rc.attributedTitle = NSAttributedString(string: "Caricamento ...", attributes: attributesRefresh as [NSAttributedStringKey : Any])
DispatchQueue.main.async {
}
// MENU Core
// customization
transition.sticky = true
transition.showShadow = true
transition.panThreshold = 0.3
transition.transformType = .translateMid
// menu// gesture recognizer
lgr.addTarget(self, action: #selector(MyProfileViewController.handlePan(_:)))
rgr.addTarget(self, action: #selector(MyProfileViewController.handleRightPan(_:)))
lgr.edges = .left
rgr.edges = .right
view.addGestureRecognizer(lgr)
view.addGestureRecognizer(rgr)
}
#objc private func refreshTableData(_ sender: Any) {
// Fetch Table Data
//myTableViewDataSource.removeAll()
tableView.reloadData()
loadList()
}
func loadList(){
var myNews = NewInfo()
// URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//
// })
let task = URLSession.shared.dataTask(with:url!) {
(data, response, error) in
if error != nil
{
print("ERROR HERE..")
}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["data"] as? [[String : Any]]
{
//dump(myResults)
for value in myResults
{
if let myTitle = value["title"] as? String
{
//print(myTitle)
myNews.displayTitle = myTitle
}
if let myLocation = value["local"] as? String
{
myNews.location = myLocation
}
if let myDate = value["date"] as? String
{
myNews.date = myDate
}
if let myDescription = value["description"] as? String
{
myNews.description = myDescription
}
if let myCost = value["cost"] as? String
{
myNews.cost = myCost
}
if let myNumMembers = value["num_members"] as? String
{
myNews.num_members = myNumMembers
}
if let myNumMembers_conf = value["num_members_confirmed"] as? String
{
myNews.num_members_confirmed = myNumMembers_conf
}
if let myStartEvent = value["time_start"] as? String
{
myNews.startEvent = myStartEvent
}
if let myEndEvent = value["time_end"] as? String
{
myNews.endEvent = myEndEvent
}
if let myId = value["id"] as? Int
{
myNews.idEvent = myId
}
//x img
// if let myMultimedia = value["data"] as? [String : Any]
// {
if let mySrc = value["event_photo"] as? String
{
myNews.event_photo = mySrc
print(mySrc)
}
self.myTableViewDataSource.append(myNews)
}//end loop
dump(self.myTableViewDataSource)
DispatchQueue.main.async
{
self.tableView.reloadData()
self.rc.endRefreshing()
}
}
}
}
}
catch{
}
}
}
task.resume()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)->CGFloat {
return 150
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myTableViewDataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath)
let myImageView = myCell.viewWithTag(11) as! UIImageView
let myTitleLabel = myCell.viewWithTag(12) as! UILabel
let myLocation = myCell.viewWithTag(13) as! UILabel
let DateLabelCell = myCell.viewWithTag(14) as! UILabel
let numMembLabel = myCell.viewWithTag(15) as! UILabel
let numMembConfLabel = myCell.viewWithTag(16) as! UILabel
myTitleLabel.text = myTableViewDataSource[indexPath.row].displayTitle
myLocation.text = myTableViewDataSource[indexPath.row].location
DateLabelCell.text = myTableViewDataSource[indexPath.row].date
numMembLabel.text = myTableViewDataSource[indexPath.row].num_members
numMembConfLabel.text = myTableViewDataSource[indexPath.row].num_members_confirmed
if let imageURLString = myTableViewDataSource[indexPath.row].event_photo,
let imageURL = URL(string: AppConfig.public_server + imageURLString) {
myImageView.af_setImage(withURL: imageURL)
}
return myCell
}
//per passare da un viewcontroller a detailviewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? EventDetailViewController {
destination.model = myTableViewDataSource[(tableView.indexPathForSelectedRow?.row)!]
// Effetto onda
let vc = segue.destination
vc.transitioningDelegate = transition
vc.modalPresentationStyle = .custom
}
//menu
if let vc = segue.destination as? MenuViewController{
vc.transitioningDelegate = transition
vc.modalPresentationStyle = .custom
//endmenu
}
}
//menu slide
#objc func handlePan(_ pan:UIPanGestureRecognizer){
if pan.state == .began{
transition.edge = .left
transition.startInteractiveTransition(self, segueIdentifier: "menu", gestureRecognizer: pan)
}else{
_ = transition.updateInteractiveTransition(gestureRecognizer: pan)
}
}
//endmenuslide
////ximg
func loadImage(url: String, to imageView: UIImageView)
{
let url = URL(string: url )
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else
{
return
}
DispatchQueue.main.async
{
imageView.image = UIImage(data: data)
}
}.resume()
}
/// star to: (x eliminare row e x muove row)
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObjTemp = myTableViewDataSource[sourceIndexPath.item]
myTableViewDataSource.remove(at: sourceIndexPath.item)
myTableViewDataSource.insert(movedObjTemp, at: destinationIndexPath.item)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete){
// print(parameters)
let idEvent = (myTableViewDataSource[indexPath.item].idEvent)
let parameters = [
// "id": UserDefaults.standard.object(forKey: "userid")! ,
"id" : idEvent,
] as [String : Any]
let url = "http://www.ns7records.com/staffapp/public/api/deleteevent"
print(url)
Alamofire.request(url, method:.post, parameters:parameters,encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success:
print(response)
let JSON = response.result.value as? [String : Any]
//self.myTableView.reloadData()
let alert = UIAlertController(title: "Yeah!", message: "Evento modificato con successo!", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
// let data = JSON! ["data"] as! NSDictionary
if let jsonData = JSON as? [String : Any]
{
print(jsonData)
self.myTableViewDataSource.remove(at : indexPath.item)
self.myTableView.deleteRows(at: [indexPath], with: .automatic)
let indexPath = IndexPath(item: 0, section: 0)
//self.myTableView.deleteRows(at: [indexPath], with: .fade)
//self.myTableView.reloadData()
// }
// }
//}
}
case .failure(let error):
print(error)
let alert = UIAlertController(title: "Aia", message: "Non puoi cancellare questo evento!", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}
#IBAction func EditButtonTableView(_ sender: UIBarButtonItem) {
self.myTableView.isEditing = !self.myTableView.isEditing
sender.title = (self.myTableView.isEditing) ? "Done" : "Edit"
}
/// end to: (x eliminare row e x muove row)
}
// MARK: -
// MARK: UITableView Delegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
This is the full code for the chatViewController. Every time when I click send button, the text messages will have some duplicate and fly out of text box as shown in this picture.
However, when I exit the chat and re-enters,those message that fly out of the box are gone. It is not because of duplicated messages in Array, I believe it should be due to either Cache issue or the updating mechanism is wrong.
Every time when I close chat and re-enter, the fly out of text box chat does not exist, everything is fine. If I do not click on send Message,the Messages all reload fine.
It is only when I click send message, there will be a few (not all) messages that duplicate and fly out of box. When I exit the chat, and re-enter,the out of box chat will be gone and everything is normal.
I consulted with other programmers who saw my code and said it is most likely because when you click send button, it fetches complete dataset, and when it display on your screen, the old data on your screen (even though you have removed them from your array) messed up with the new data. I asked them how to solve it, they are not sure because they are Android developers.
A train of thought I was thinking: cache the old messages, add the new messages but make sure they do not conflict with the old messages in the cache?
import UIKit
import Firebase
import FirebaseStorage
import FirebaseDatabase
import FirebaseFirestore
import CoreData
class ChatViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var messageText: UITextField!
#IBAction func back(_ sender: Any) {
back()
}
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBAction func sendTextMessage(_ sender: Any) {
chats.removeAll()
self.sendDataToDatabase(message: messageText.text!)
messageText.text = nil
self.loadPosts()
self.loadPostsReceivedMessage()
delayCompletionHandler {
self.collectionView.reloadData()
}
}
let cellIdentifier = "chatNow"
var chats = [Chat]()
var posts = [Post]()
var receiverIDNumber = ""
var profileImageUrl = ""
var senderString = ""
var conversationsCounterInt = 0
var timestamp = String(Int(Date().timeIntervalSince1970))
let db = Firestore.firestore()
override func viewDidLoad() {
super.viewDidLoad()
let yourNibName = UINib(nibName: "ChatCollectionViewCell", bundle: nil)
collectionView.register(yourNibName, forCellWithReuseIdentifier: cellIdentifier)
self.collectionView.dataSource = self
self.collectionView.delegate = self
print(receiverIDNumber)
usernameLabel.text! = receiverIDNumber
setUpProfile()
set()
print("is there anything"+profileImageUrl)
//Making circular profile picture
profileImage.layer.borderWidth = 1.0
profileImage.layer.masksToBounds = false
profileImage.layer.borderColor = UIColor.white.cgColor
profileImage.layer.cornerRadius = profileImage.frame.size.width / 2
profileImage.clipsToBounds = true
print("waka"+senderString)
self.loadPosts()
self.loadPostsReceivedMessage()
delayCompletionHandler {
self.collectionView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.title = "Chat"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func back() {
let storyboard = UIStoryboard(name: "Main" , bundle: nil)
let tabViewController = storyboard.instantiateViewController(withIdentifier: "tab")
present(tabViewController, animated: true,completion: nil)
}
func set() {
let uid = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("users").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dic = snapshot.value as? [String: AnyObject] {
self.senderString = dic["username"] as! String
print("this"+self.senderString)
}
})
}
func sendDataToDatabase(message: String){
let ref = Database.database().reference()
let senderIDNumber = Auth.auth().currentUser?.uid
let timeStampString = String(Int(Date().timeIntervalSince1970))
db.collection("chats").addDocument(data: ["message": messageText.text!, "senderID": senderIDNumber!,"receiverID": receiverIDNumber,"timestamp": timeStampString,"profileUrl": profileImageUrl, "sender": self.senderString])
{ err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
func logout(){
let storyboard = UIStoryboard(name: "Main" , bundle: nil)
let loginViewController = storyboard.instantiateViewController(withIdentifier: "login")
present(loginViewController, animated: true,completion: nil)
}
func delayCompletionHandler(completion:#escaping (() -> ())) {
let delayInSeconds = 0.3
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
completion()
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return chats.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! ChatCollectionViewCell
let senderIDNumber = Auth.auth().currentUser?.uid
//Setup the messageReceived and messageSent
if chats[indexPath.row].senderID == senderIDNumber {
if let chatsText = chats[indexPath.row].message{
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatsText).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageSend.frame = CGRect(x:8,y:0,width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:0,y:0,width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
//showOutgoingMessage(text: chats[indexPath.row].message)
cell.messageSend.text = chats[indexPath.row].message
}
}
else {
cell.messageReceived.text = chats[indexPath.row].message
let chatsText = chats[indexPath.row].message
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatsText!).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageReceived.frame = CGRect(x:view.frame.width - estimatedFrame.width - 30,y:0,width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:view.frame.width - estimatedFrame.width - 30,y:0,width:estimatedFrame.width + 16 + 4, height:estimatedFrame.height + 20)
}
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
if let chatsText = chats[indexPath.row].message {
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatsText).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
return CGSize(width: view.frame.width, height: 200)
}
//Get Message sent
func loadPosts() {
let senderIDNumber = Auth.auth().currentUser?.uid
let chatsRef = db.collection ("chats").order (by: "timestamp", descending: false)
let sentListener = chatsRef.whereField ("senderID", isEqualTo: senderIDNumber!)
.whereField ("receiverID", isEqualTo: receiverIDNumber)
.addSnapshotListener() {
querySnapshot,
error in
guard let documentChanges = querySnapshot?.documentChanges else {
print ("Error fetching documents: \(error!)")
return
}
for documentChange in documentChanges {
if (documentChange.type == .added) {
let data = documentChange.document.data ()
print("Message send: \(data)")
let messageText = data["message"] as? String
let senderIDNumber = data["senderID"] as? String
let receiverIDNumber = data["receiverID"] as? String
let timestamp = data["timestamp"] as? String
guard let sender = data["sender"] as? String else {return}
// let conversationsCounter = document.data()["conversationsCounter"] as? Int
guard let profileUrl = data["profileUrl"] as? String else { return}
let chat = Chat(messageTextString: messageText!, senderIDNumber: senderIDNumber!, receiverIDNumber: receiverIDNumber!, timeStampString: timestamp!, profileImageUrl: profileUrl, senderString: sender)
self.chats.append(chat)
print(self.chats)
}
}
}
}
//Get message received
func loadPostsReceivedMessage() {
let chatsRef = db.collection("chats").order(by: "timestamp", descending: false)
print("thecurrentreceiver"+senderString)
print("thecurrentsender"+receiverIDNumber)
let receivedListener = chatsRef.whereField("receiverID", isEqualTo: senderString).whereField("sender", isEqualTo: receiverIDNumber)
.addSnapshotListener() {
querySnapshot,
error in
guard let documentChanges = querySnapshot?.documentChanges else {
print ("Error fetching documents: \(error!)")
return
}
for documentChange in documentChanges {
if (documentChange.type == .added) {
let data = documentChange.document.data ()
print("Message received: \(data)")
let messageText = data["message"] as? String
let senderIDNumber = data["senderID"] as? String
let receiverIDNumber = data["receiverID"] as? String
let timestamp = data["timestamp"] as? String
guard let sender = data["sender"] as? String else {return}
// let conversationsCounter = document.data()["conversationsCounter"] as? Int
guard let profileUrl = data["profileUrl"] as? String else { return}
let chat = Chat(messageTextString: messageText!, senderIDNumber: senderIDNumber!, receiverIDNumber: receiverIDNumber!, timeStampString: timestamp!, profileImageUrl: profileUrl, senderString: sender)
self.chats.append(chat)
print(self.chats)
self.chats.sort{$0.timestamp < $1.timestamp}
}
}
}
}
func setUpProfile() {
guard let url = URL(string: profileImageUrl) else { return}
let task = URLSession.shared.dataTask(with: url){ data, reponse, error in
if error != nil {
print(error!)
} else{
DispatchQueue.main.async(execute: {
self.profileImage.image = UIImage(data: data!)
})
}
}
task.resume()
}
}
I am creating a messaging app. I have this lazy var for a UIView in my messaging app. The View holds a textfield, and 2 buttons. The view is created programmatically. It is declared in a Vc ChatLog which displays the messages and handles the messaging functionality. When choose to present ChatLog through present(chatLogController, animated: true) it works as expected. However, whenever I place ChatLog in a ContainerView and present it through an embedded segue, the Input view does not appear or get executed. What could cause this?
Here's a picture to better explain:
Parent VC:
import UIKit
import Firebase
var segueUser: messageUser!
class MessagesVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
messages.removeAll()
messagesDictionary.removeAll()
tableView.reloadData()
tableView.tableFooterView = UIView()
observeUserMessages()
// Do any additional setup after loading the view.
}
var messages = [Message]()
var messagesDictionary = [String: Message]()
func observeUserMessages() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
// print(snapshot)
let userId = snapshot.key
Database.database().reference().child("user-messages").child(uid).child(userId).observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key
//print(messageId)
self.fetchMessageWithMessageId(messageId)
}, withCancel: nil)
}, withCancel: nil)
ref.observe(.childRemoved, with: { (snapshot) in
//print(snapshot.key)
//print(self.messagesDictionary)
self.messagesDictionary.removeValue(forKey: snapshot.key)
self.attemptReloadOfTable()
}, withCancel: nil)
}
fileprivate func fetchMessageWithMessageId(_ messageId: String) {
let messagesReference = Database.database().reference().child("messages").child(messageId)
messagesReference.observeSingleEvent(of: .value, with: { (snapshot) in
//print(snapshot)
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
if let chatPartnerId = message.chatPartnerId() {
self.messagesDictionary[chatPartnerId] = message
}
self.attemptReloadOfTable()
}
}, withCancel: nil)
}
fileprivate func attemptReloadOfTable() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.handleReloadTable), userInfo: nil, repeats: false)
}
var timer: Timer?
func handleReloadTable() {
self.messages = Array(self.messagesDictionary.values)
self.messages.sort(by: { (message1, message2) -> Bool in
return (message1.timestamp?.int32Value)! > (message2.timestamp?.int32Value)!
})
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(messages.count)
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "userMessageCell", for: indexPath) as! userMessageCell
let message = messages[indexPath.row]
cell.message = message
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = messages[indexPath.row]
print(message)
guard let chatPartnerId = message.chatPartnerId() else {
return
}
businessName = message.businessName!
let ref = Database.database().reference().child("businessSearch").child(chatPartnerId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let user = messageUser(dictionary: dictionary)
segueUser = user
user.id = chatPartnerId
//self.showChatControllerForUser(user)
self.performSegue(withIdentifier: "toChatLog", sender: nil)
}, withCancel: nil)
}
func showChatControllerForUser(_ user: messageUser) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.user = user
present(chatLogController, animated: true)
//navigationController?.pushViewController(chatLogController, animated: true)
}
#IBAction func backButtonPressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
SecondVC (created in Interface Builder):
import UIKit
import Firebase
import MobileCoreServices
import AVFoundation
class ChatLogFrameVC: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//nameLabel.text = businessName
}
#IBAction func infoButtonPressed(_ sender: Any) {
}
#IBAction func backButtonPressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
ChatLogVC:
import UIKit
import Firebase
import MobileCoreServices
import AVFoundation
class ChatLogController: UICollectionViewController, UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var user: messageUser!
var messages = [Message]()
func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid, let toId = user?.id else {
return
}
let userMessagesRef = Database.database().reference().child("user-messages").child(uid).child(toId)
userMessagesRef.observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageId)
messagesRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
self.messages.append(Message(dictionary: dictionary))
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
//scroll to the last index
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}, withCancel: nil)
}, withCancel: nil)
}
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.isHidden = false
collectionView?.collectionViewLayout = UICollectionViewFlowLayout()
user = segueUser
observeMessages()
print("CHAT LOG IS RUNNING")
collectionView?.showsVerticalScrollIndicator = false
collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
// collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
collectionView?.alwaysBounceVertical = true
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ChatMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.keyboardDismissMode = .interactive
//collectionView?.addSubview(topChatContainerView)
//collectionView?.bringSubview(toFront: topChatContainerView)
self.title = businessName
setupKeyboardObservers()
}
lazy var inputContainerView: ChatInputContainerView = {
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 64))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
func handleUploadTap() {
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
imagePickerController.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let videoUrl = info[UIImagePickerControllerMediaURL] as? URL {
//we selected a video
handleVideoSelectedForUrl(videoUrl)
} else {
//we selected an image
handleImageSelectedForInfo(info as [String : AnyObject])
}
dismiss(animated: true, completion: nil)
}
fileprivate func handleVideoSelectedForUrl(_ url: URL) {
let filename = UUID().uuidString + ".mov"
let uploadTask = Storage.storage().reference().child("message_movies").child(filename).putFile(from: url, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("Failed upload of video:", error!)
return
}
if let videoUrl = metadata?.downloadURL()?.absoluteString {
if let thumbnailImage = self.thumbnailImageForFileUrl(url) {
self.uploadToFirebaseStorageUsingImage(thumbnailImage, completion: { (imageUrl) in
let properties: [String: AnyObject] = ["imageUrl": imageUrl as AnyObject, "imageWidth": thumbnailImage.size.width as AnyObject, "imageHeight": thumbnailImage.size.height as AnyObject, "videoUrl": videoUrl as AnyObject]
self.sendMessageWithProperties(properties)
})
}
}
})
uploadTask.observe(.progress) { (snapshot) in
if let completedUnitCount = snapshot.progress?.completedUnitCount {
self.navigationItem.title = String(completedUnitCount)
}
}
uploadTask.observe(.success) { (snapshot) in
self.navigationItem.title = self.user?.name
}
}
fileprivate func thumbnailImageForFileUrl(_ fileUrl: URL) -> UIImage? {
let asset = AVAsset(url: fileUrl)
let imageGenerator = AVAssetImageGenerator(asset: asset)
do {
let thumbnailCGImage = try imageGenerator.copyCGImage(at: CMTimeMake(1, 60), actualTime: nil)
return UIImage(cgImage: thumbnailCGImage)
} catch let err {
print(err)
}
return nil
}
fileprivate func handleImageSelectedForInfo(_ info: [String: AnyObject]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
uploadToFirebaseStorageUsingImage(selectedImage, completion: { (imageUrl) in
self.sendMessageWithImageUrl(imageUrl, image: selectedImage)
})
}
}
fileprivate func uploadToFirebaseStorageUsingImage(_ image: UIImage, completion: #escaping (_ imageUrl: String) -> ()) {
let imageName = UUID().uuidString
let ref = Storage.storage().reference().child("message_images").child(imageName)
if let uploadData = UIImageJPEGRepresentation(image, 0.8) {
ref.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("Failed to upload image:", error!)
return
}
if let imageUrl = metadata?.downloadURL()?.absoluteString {
completion(imageUrl)
}
})
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
//
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func handleKeyboardDidShow() {
if messages.count > 0 {
let indexPath = IndexPath(item: messages.count - 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func handleKeyboardWillShow(_ notification: Notification) {
let keyboardFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
containerViewBottomAnchor?.constant = -keyboardFrame!.height
UIView.animate(withDuration: keyboardDuration!, animations: {
self.view.layoutIfNeeded()
})
}
func handleKeyboardWillHide(_ notification: Notification) {
let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
containerViewBottomAnchor?.constant = 0
UIView.animate(withDuration: keyboardDuration!, animations: {
self.view.layoutIfNeeded()
})
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.chatLogController = self
let message = messages[indexPath.item]
cell.message = message
cell.textView.text = message.text
setupCell(cell, message: message)
if let text = message.text {
//a text message
cell.bubbleWidthAnchor?.constant = estimateFrameForText(text).width + 32
cell.textView.isHidden = false
} else if message.imageUrl != nil {
//fall in here if its an image message
cell.bubbleWidthAnchor?.constant = 200
cell.textView.isHidden = true
}
cell.playButton.isHidden = message.videoUrl == nil
return cell
}
fileprivate func setupCell(_ cell: ChatMessageCell, message: Message) {
if let profileImageUrl = self.user?.profileImageUrl {
cell.profileImageView.loadImageUsingCacheWithUrlString(profileImageUrl)
}
if message.fromId == Auth.auth().currentUser?.uid {
//outgoing blue
cell.bubbleView.backgroundColor = ChatMessageCell.blueColor
cell.textView.textColor = UIColor.white
cell.profileImageView.isHidden = true
cell.bubbleViewRightAnchor?.isActive = true
cell.bubbleViewLeftAnchor?.isActive = false
} else {
//incoming gray
cell.bubbleView.backgroundColor = UIColor(red:0.81, green:0.81, blue:0.81, alpha:1.0)
cell.textView.textColor = UIColor.black
cell.profileImageView.isHidden = false
cell.bubbleViewRightAnchor?.isActive = false
cell.bubbleViewLeftAnchor?.isActive = true
}
if let messageImageUrl = message.imageUrl {
cell.messageImageView.loadImageUsingCacheWithUrlString(messageImageUrl)
cell.messageImageView.isHidden = false
cell.bubbleView.backgroundColor = UIColor.clear
} else {
cell.messageImageView.isHidden = true
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80
let message = messages[indexPath.item]
if let text = message.text {
height = estimateFrameForText(text).height + 20
} else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue {
// h1 / w1 = h2 / w2
// solve for h1
// h1 = h2 / w2 * w1
height = CGFloat(imageHeight / imageWidth * 200)
}
let width = UIScreen.main.bounds.width
return CGSize(width: width, height: height)
}
fileprivate func estimateFrameForText(_ text: String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: (UIFont(name: "Avenir Next", size: 17))!], context: nil)
}
var containerViewBottomAnchor: NSLayoutConstraint?
func handleSend() {
let properties = ["message": inputContainerView.inputTextField.text!]
sendMessageWithProperties(properties as [String : AnyObject])
}
fileprivate func sendMessageWithImageUrl(_ imageUrl: String, image: UIImage) {
let properties: [String: AnyObject] = ["imageUrl": imageUrl as AnyObject, "imageWidth": image.size.width as AnyObject, "imageHeight": image.size.height as AnyObject]
sendMessageWithProperties(properties)
}
fileprivate func sendMessageWithProperties(_ properties: [String: AnyObject]) {
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.id!
let fromId = Auth.auth().currentUser!.uid
let timestamp = Int(Date().timeIntervalSince1970)
var values: [String: AnyObject] = ["toID": toId as AnyObject, "fromID": fromId as AnyObject, "timestamp": timestamp as AnyObject, "firstName": firstName as AnyObject,"businessName": businessName as AnyObject]
//append properties dictionary onto values somehow??
//key $0, value $1
properties.forEach({values[$0] = $1})
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error!)
return
}
self.inputContainerView.inputTextField.text = nil
let userMessagesRef = Database.database().reference().child("user-messages").child(fromId).child(toId)
let messageId = childRef.key
userMessagesRef.updateChildValues([messageId: 1])
let recipientUserMessagesRef = Database.database().reference().child("user-messages").child(toId).child(fromId)
recipientUserMessagesRef.updateChildValues([messageId: 1])
}
}
var startingFrame: CGRect?
var blackBackgroundView: UIView?
var startingImageView: UIImageView?
//my custom zooming logic
func performZoomInForStartingImageView(_ startingImageView: UIImageView) {
self.startingImageView = startingImageView
self.startingImageView?.isHidden = true
startingFrame = startingImageView.superview?.convert(startingImageView.frame, to: nil)
let zoomingImageView = UIImageView(frame: startingFrame!)
zoomingImageView.backgroundColor = UIColor.red
zoomingImageView.image = startingImageView.image
zoomingImageView.isUserInteractionEnabled = true
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
blackBackgroundView = UIView(frame: keyWindow.frame)
blackBackgroundView?.backgroundColor = UIColor.black
blackBackgroundView?.alpha = 0
keyWindow.addSubview(blackBackgroundView!)
keyWindow.addSubview(zoomingImageView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackBackgroundView?.alpha = 1
self.inputContainerView.alpha = 0
// math?
// h2 / w1 = h1 / w1
// h2 = h1 / w1 * w1
let height = self.startingFrame!.height / self.startingFrame!.width * keyWindow.frame.width
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
zoomingImageView.center = keyWindow.center
}, completion: { (completed) in
// do nothing
})
}
}
func handleZoomOut(_ tapGesture: UITapGestureRecognizer) {
if let zoomOutImageView = tapGesture.view {
//need to animate back out to controller
zoomOutImageView.layer.cornerRadius = 16
zoomOutImageView.clipsToBounds = true
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
zoomOutImageView.frame = self.startingFrame!
self.blackBackgroundView?.alpha = 0
self.inputContainerView.alpha = 1
}, completion: { (completed) in
zoomOutImageView.removeFromSuperview()
self.startingImageView?.isHidden = false
})
}
}
}