我正在创建一个消息传递应用。我在我的消息传递应用程序中为UIView提供了这个懒惰的var。 View包含一个文本字段和2个按钮。视图以编程方式创建。它在Vc ChatLog中声明,它显示消息并处理消息传递功能。当选择通过present(chatLogController, animated: true)
呈现ChatLog时,它按预期工作。但是,每当我将ChatLog放在ContainerView中并通过嵌入的segue呈现它时,Input视图就不会出现或执行。什么可能导致这种情况?
这是一张更好解释的图片:
家长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(在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
})
}
}
}