Search code examples
swiftuiviewcontrolleruinavigationcontrolleruitabbarcontrollerjsqmessagesviewcontroller

App crashes after action inside tab bar view controller


I have an app which has a tab bar controller. And inside one tab there is a navigation controller, which has a ViewController. Now this View contains a messaging interface and my problem was that with the addition of the tab bar, my text box at the bottom of the view was being cut off. To solve this, I did some research and simply added the single line:

self.edgesForExtendedLayout = .None

which solved the problem. My text box was displaying and the user could select it and modify it etc. However when the user hits send, my app crashes with the message:

fatal error: unexpectedly found nil while unwrapping an Optional value

I have no idea what the problem could be because everything works perfectly up until that point...

The entire code for my ViewController is as follows:

    class ChatViewController: JSQMessagesViewController, CLLocationManagerDelegate {

    // MARK: Properties

    //Location
    var city: String = ""
    var state: String = ""
    var country: String = ""
    var locationManager = CLLocationManager()
    var locationId: String = ""

     func getLocation() -> String {
        if city == ("") && state == ("") && country == (""){
            return "Anonymous"
        }
        else {
            if country == ("United States") {
                return self.city + ", " + self.state
            }
            else {
                return self.city + ", " + self.state + ", " + self.country
            }
        }
     }

    //Firebase
    var rootRef = FIRDatabase.database().reference()
    var messageRef: FIRDatabaseReference!
    var locationRef: FIRDatabaseReference!

    //JSQMessages
    var messages = [JSQMessage]()

    var outgoingBubbleImageView: JSQMessagesBubbleImage!
    var incomingBubbleImageView: JSQMessagesBubbleImage!



    override func viewDidLoad() {
        super.viewDidLoad()

        self.edgesForExtendedLayout = .None

        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()

        if CLLocationManager.locationServicesEnabled() {
            //collect user's location
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
            locationManager.requestLocation()
            locationManager.startUpdatingLocation()
        }

        title = "Group Chat"
        setupBubbles()
        // No avatars
        collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
        collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero

        // Remove file upload icon
        self.inputToolbar.contentView.leftBarButtonItem = nil;

        messageRef = rootRef.child("messages")
        locationRef = rootRef.child("locations")
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        observeMessages()
    }

  override func viewDidDisappear(animated: Bool) {
    super.viewDidDisappear(animated)
  }


    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        //--- CLGeocode to get address of current location ---//
        CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in

            if let pm = placemarks?.first
            {
                self.displayLocationInfo(pm)
            }

        })

    }


    func displayLocationInfo(placemark: CLPlacemark?)
    {
        if let containsPlacemark = placemark
        {
            //stop updating location
            locationManager.stopUpdatingLocation()

            self.city = (containsPlacemark.locality != nil) ? containsPlacemark.locality! : ""
            self.state = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea! : ""
            self.country = (containsPlacemark.country != nil) ? containsPlacemark.country! : ""

            print(getLocation())

        }

    }


    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Error while updating location " + error.localizedDescription)
    }


    override func collectionView(collectionView: JSQMessagesCollectionView!,
                                 messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return messages[indexPath.item]
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!,
                                 messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
        let message = messages[indexPath.item] // 1
        if message.senderId == senderId { // 2
            return outgoingBubbleImageView
        } else {
            return incomingBubbleImageView
        }
    }

    override func collectionView(collectionView: UICollectionView,
                                 numberOfItemsInSection section: Int) -> Int {
        return messages.count
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!,
                                 avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        return nil
    }

    private func setupBubbles() {
        let factory = JSQMessagesBubbleImageFactory()
        outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
            UIColor.jsq_messageBubbleBlueColor())
        incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
            UIColor.jsq_messageBubbleLightGrayColor())
    }

    func addMessage(id: String, text: String) {
        let message = JSQMessage(senderId: id, displayName: "", text: text)

        messages.append(message)
    }

    override func collectionView(collectionView: UICollectionView,
                                 cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath)
            as! JSQMessagesCollectionViewCell

        let message = messages[indexPath.item]

        if message.senderId == senderId {
            cell.textView!.textColor = UIColor.whiteColor()
        } else {
            cell.textView!.textColor = UIColor.blackColor()
        }

        return cell
    }

    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
                                     senderDisplayName: String!, date: NSDate!) {

        let itemRef = messageRef.childByAutoId()
        let messageItem = [
            "text": text,
            "senderId": senderId
        ]
        itemRef.setValue(messageItem)

        let locRef = locationRef.childByAutoId()
        let locItem = [
            senderId : [
                "location": getLocation()
            ]
        ]

        locRef.setValue(locItem)

        // Retrieve data from Firebase Realtime database

        FIRDatabase.database().reference().child("locations").child(senderId).child("location").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
            self.locationId = snapshot.value!["location"] as! String
        }) { (error) in
            print(error.localizedDescription)
        }


        JSQSystemSoundPlayer.jsq_playMessageSentSound()

        finishSendingMessage()

    }

    private func observeMessages() {

        let messagesQuery = messageRef.queryLimitedToLast(25)

        messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in

            let id = snapshot.value!["senderId"] as! String
            let text = snapshot.value!["text"] as! String

            self.addMessage(id, text: text)

            self.finishReceivingMessage()
        }
    }


    override func textViewDidChange(textView: UITextView) {
        super.textViewDidChange(textView)
    }


    override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {

        let message = messages[indexPath.item]

        // Call data I have retrieved below with message 
        let text = "From: " + locationId

        if message.senderId == senderId {
            return nil
        } else {
            return NSAttributedString(string: text)
        }

    }

    override func collectionView(collectionView: JSQMessagesCollectionView, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout, heightForCellBottomLabelAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return kJSQMessagesCollectionViewCellLabelHeightDefault
    }


}

Thanks for any help in advance!


Solution

  • The problem occurred after the user hit the send button. I just added the line self.edgesForExtendedLayout = .None into the specific action and it worked fine!