0

I’m making an ELIZA chatbot in Swift Playgrounds for iPad.

I have a custom cell type designed to look like a chat bubble, and a footer at the bottom of the table to hold the “enter text” field and the send button.

When there are only one or two messages onscreen, though, the footer does not stick to the bottom of the screen. I’d like it to always be at the bottom. Here’s what it looks like with only one or two messages:

enter image description here

Here’s what it looks like with several messages:

enter image description here

Here’s my code:

import UIKit
import PlaygroundSupport
import Foundation

class ViewController: UITableViewController {

    // Areay that holds messages
    var textMessages = [String]()
    let sampleTextField =  UITextField(frame: CGRect(x: 20, y: 20, width: 300, height: 40))

    override func viewDidLoad() {
        super.viewDidLoad()

            // Reguster custom cell
        tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "cell_1")
            // Turn off seperators
        tableView.separatorStyle = .none
            // Set header, footer height
        tableView.sectionFooterHeight = 75
    }

    // Custom header
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?{
        let customView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 75))
        customView.backgroundColor = UIColor.red
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 75))
        button.setTitle("Submit", for: .normal)

        customView.addSubview(button)
        return customView
    }

    //Custom footer
    override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?{
        let customView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 75))
        customView.backgroundColor = UIColor(white: 0.9, alpha: 1)

        sampleTextField.placeholder = "Enter text here"
        sampleTextField.font = UIFont.systemFont(ofSize: 15)
        sampleTextField.borderStyle = UITextField.BorderStyle.roundedRect
        sampleTextField.autocorrectionType = UITextAutocorrectionType.no
        sampleTextField.keyboardType = UIKeyboardType.default
        sampleTextField.returnKeyType = UIReturnKeyType.done
        sampleTextField.clearButtonMode = UITextField.ViewMode.whileEditing
        sampleTextField.contentVerticalAlignment = UIControl.ContentVerticalAlignment.center
        let buttonZ = UIButton(type: .custom)
        buttonZ.setImage(UIImage(named: "send.png"), for: .normal)
        buttonZ.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 0, right: 0)
        buttonZ.frame = CGRect(x: CGFloat(sampleTextField.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
        buttonZ.backgroundColor = .blue
        sampleTextField.rightView = buttonZ
        sampleTextField.rightViewMode = .always
        buttonZ.addTarget(self, action: #selector(updateView), for: .touchUpInside)
        //button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        //customView.addSubview(button)
        customView.addSubview(sampleTextField)
        return customView
    }

    @objc func updateView(){

        adddatextbruh()
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return textMessages.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1", for: indexPath) as! ChatMessageCell
        cell.selectionStyle = .none

        if indexPath.row % 2 == 1{
            cell.messageLabel.text = textMessages[indexPath.row]
            //cell.setupConstraints(side: 1)
            cell.bubbleBackgroundView.backgroundColor = UIColor(white: 0.9, alpha: 1)
            return cell
        }else{
            cell.messageLabel.text = textMessages[indexPath.row]
            //cell.setupConstraints(side: 0)
            cell.bubbleBackgroundView.backgroundColor = .blue
            /*
            let gradientLayer = CAGradientLayer()
            let colorTop : UIColor = .red
            let colorBottom : UIColor = .blue
            gradientLayer.colors = [colorTop.cgColor, colorBottom.cgColor]
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
            gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
            gradientLayer.locations = [NSNumber(floatLiteral: 0.0), NSNumber(floatLiteral: 1.0)]
            gradientLayer.frame = cell.bubbleBackgroundView.bounds

            cell.bubbleBackgroundView.layer.insertSublayer(gradientLayer, at: 0)
 */
            return cell
        }
    }
    //let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
    //        cell.textLabel?.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
    //        cell.textLabel?.numberOfLines = 0
    func adddatextbruh(){
        let botInstance = Bot()
        var writtenText = sampleTextField.text

        textMessages.append(writtenText!)

        tableView.beginUpdates()
        tableView.insertRows(at: [
            (NSIndexPath(row: textMessages.count-1, section: 0) as IndexPath)], with: .automatic)
        tableView.endUpdates()
        tableView.scrollToRow(at: IndexPath(row: textMessages.count-1, section: 0), at: UITableView.ScrollPosition.bottom, animated: true)
        var botReply = botInstance.replyTo(writtenText!)

        textMessages.append(botReply)

        tableView.beginUpdates()
        tableView.insertRows(at: [
            (NSIndexPath(row: textMessages.count-1, section: 0) as IndexPath)], with: .automatic)
        tableView.endUpdates()
        tableView.scrollToRow(at: IndexPath(row: textMessages.count-1, section: 0), at: UITableView.ScrollPosition.bottom, animated: true)
    }
}

class ChatMessageCell: UITableViewCell {

    let messageLabel = UILabel()
    let bubbleBackgroundView = UIView()
    var leadingAnchorConstant = CGFloat()

    func setupConstraints(side: Int){
        if side == 1{
            leadingAnchorConstant = frame.size.width - 176
        }else{
            leadingAnchorConstant = 32
        }
    }

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)


        bubbleBackgroundView.layer.shadowOpacity = 0.35
        bubbleBackgroundView.layer.shadowRadius = 6
        bubbleBackgroundView.layer.shadowOffset = CGSize(width: 0, height: 0)
        bubbleBackgroundView.layer.shadowColor = UIColor.black.cgColor

        bubbleBackgroundView.layer.cornerRadius = 25
        bubbleBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(bubbleBackgroundView)

        addSubview(messageLabel)
        //        messageLabel.backgroundColor = .green
        messageLabel.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
        messageLabel.numberOfLines = 0

        messageLabel.translatesAutoresizingMaskIntoConstraints = false

        // lets set up some constraints for our label
        let constraints = [messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 32),
                           messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32),
                           messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -32),
                           messageLabel.widthAnchor.constraint(equalToConstant: 250),

                           bubbleBackgroundView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
                           bubbleBackgroundView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
                           bubbleBackgroundView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
                           bubbleBackgroundView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16),
        ]

        NSLayoutConstraint.activate(constraints)

        //        messageLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

struct Bot {
    // Opening phrases
    private let openings = [
        "Hello! It's been a while. How are you feeling today?",
        "Hi! I've missed you. How are you feeling?",
        "Hello! I've been thinking about you. How are things?",
        "Hey, long time no see. How see things?",
        "Hey, I've missed you. What have you been up to lately? How are you feeling?",
        "Hey! Long time no see. What's up?",
        "Whats up! We haven't talked in a while. How are things?"
    ]

    // Closing phrases
    private let closings = [
        "Goodbye. It's been nice talking to you. I'm here whenever you need me.",
        "Thanks for talking with me. Goodbye.",
        "Goodbye. This has been a nice talk. I'll be right here whenever you need me",
        "Good-bye"
    ]

    // These are the bot's possible responses
    private let psychobabble = [
        ("i need (.*)", [
            "Why do you need %s?",
            "Would it really help you to get %s?",
            "Are you sure you need %s?",
        ]),
        ("why don'?t you ([^\\?]*)\\??", [
            "Do you really think I don't %s?",
            "Perhaps eventually I will %s.",
            "Do you really want me to %s?",
        ]),
        ("why can'?t I ([^\\?]*)\\??", [
            "Do you think you should be able to %s?",
            "If you could %s, what would you do?",
            "I don't know -- why do you feel you can't %s?",
            "Have you tried?",
        ]),
        ("i can'?t (.*)", [
            "How do you know you can't %s?",
            "Perhaps you could %s if you tried.",
            "What would it take for you to %s?",
            "What is stopping you from %s?"
        ]),
        ("i am (.*)", [
            "Did you come to me because you are %s?",
            "How long have you been %s?",
            "How do you feel about being %s?",
            "How does being %s make you feel?"
        ]),
        ("i'?m (.*)", [
            "How does being %s make you feel?",
            "Do you enjoy being %s?",
            "Why do you tell me you're %s?",
            "Why do you think you're %s?",
        ]),
        ("are you ([^\\?]*)\\??", [
            "Why does it matter whether I am %s?",
            "Would you prefer it if I were not %s?",
            "Perhaps you believe I am %s.",
            "I may be %s -- what do you think?",
        ]),
        ("what (.*)", [
            "Why do you ask?",
            "How would an answer to that help you?",
            "What do you think?",
        ]),
        ("how (.*)", [
            "How do you suppose?",
            "Perhaps you can answer your own question.",
            "What is it you're really asking?",
        ]),
        ("because (.*)", [
            "Is that the real reason?",
            "What other reasons come to mind?",
            "Does that reason apply to anything else?",
            "If %s, what else must be true?",
        ]),
        ("(.*) sorry (.*)", [
            "There are many times when no apology is needed.",
            "What feelings do you have when you apologize?",
        ]),
        ("^hello(.*)", [
            "Hello... I'm glad you could drop by today.",
            "Hi there... how are you today?",
            "Hello, how are you feeling today?",
        ]),
        ("^hi(.*)", [
            "Hello... I'm glad you could drop by today. I'm really happy you're here.",
            "Hi there... how are you today?",
            "Hello, how are you feeling today?",
            "Hey! I'm so happy you're here. How are things?"
        ]),
        ("^thanks(.*)", [
            "You're welcome!",
            "Anytime!",
        ]),
        ("^thank you(.*)", [
            "You're welcome!",
            "Anytime!",
            "No problem! I'll always be here for you"
        ]),
        ("^good morning(.*)", [
            "Good morning... I'm glad you could drop by today.",
            "Good morning... how are you today?",
            "Good morning, how are you feeling today?",
        ]),
        ("^good afternoon(.*)", [
            "Good afternoon... I'm glad you could drop by today.",
            "Good afternoon... how are you today?",
            "Good afternoon, how are you feeling today?",
        ]),
        ("I think (.*)", [
            "Do you doubt %s?",
            "Do you really think so?",
            "But you're not sure %s?",
        ]),
        ("(.*) friend (.*)", [
            "Tell me more about your friends.",
            "When you think of a friend, what comes to mind?",
            "Why don't you tell me about a childhood friend?",
        ]),
        ("yes", [
            "You seem quite sure.",
            "OK, but can you elaborate a bit?",
        ]),
        ("(.*) computer(.*)", [
            "Are you really talking about me?",
            "Does it seem strange to talk to a computer?",
            "How do computers make you feel?",
            "Do you feel threatened by computers?",
        ]),
        ("is it (.*)", [
            "Do you think it is %s?",
            "Perhaps it's %s -- what do you think?",
            "If it were %s, what would you do?",
            "It could well be that %s.",
        ]),
        ("it is (.*)", [
            "You seem very certain.",
            "If I told you that it probably isn't %s, what would you feel?",
        ]),
        ("can you ([^\\?]*)\\??", [
            "What makes you think I can't %s?",
            "If I could %s, then what?",
            "Why do you ask if I can %s?",
        ]),
        ("(.*)dream(.*)", [
            "Tell me more about your dream.",
        ]),
        ("can I ([^\\?]*)\\??", [
            "Perhaps you don't want to %s.",
            "Do you want to be able to %s?",
            "If you could %s, would you?",
        ]),
        ("you are (.*)", [
            "Why do you think I am %s?",
            "Does it please you to think that I'm %s?",
            "Perhaps you would like me to be %s.",
            "Perhaps you're really talking about yourself?",
        ]),
        ("you'?re (.*)", [
            "Why do you say I am %s?",
            "Why do you think I am %s?",
            "Are we talking about you, or me?",
        ]),
        ("i don'?t (.*)", [
            "Don't you really %s?",
            "Why don't you %s?",
            "Do you want to %s?",
        ]),
        ("i feel (.*)", [
            "Good, tell me more about these feelings.",
            "Do you often feel %s?",
            "When do you usually feel %s?",
            "When you feel %s, what do you do?",
        ]),
        ("i have (.*)", [
            "Why do you tell me that you've %s?",
            "Have you really %s?",
            "Now that you have %s, what will you do next?",
        ]),
        ("i would (.*)", [
            "Could you explain why you would %s?",
            "Why would you %s?",
            "Who else knows that you would %s?",
        ]),
        ("is there (.*)", [
            "Do you think there is %s?",
            "It's likely that there is %s.",
            "Would you like there to be %s?",
        ]),
        ("my (.*)", [
            "I see, your %s.",
            "Why do you say that your %s?",
            "When your %s, how do you feel?",
        ]),
        ("you (.*)", [
            "We should talk more about you, not me.",
            "Why do you say that about me?",
            "Why do you care whether I %s?",
        ]),
        ("why (.*)", [
            "Why don't you tell me the reason why %s?",
            "Why do you think %s?",
        ]),
        ("i want (.*)", [
            "What would it mean to you if you got %s?",
            "Why do you want %s?",
            "What would you do if you got %s?",
            "If you got %s, then what would you do?",
        ]),
        ("(.*) mother(.*)", [
            "Tell me more about your mother.",
            "What was your relationship with your mother like?",
            "How do you feel about your mother?",
            "How does this relate to your feelings today?",
            "Good family relations are important.",
        ]),
        ("(.*) father(.*)", [
            "Tell me more about your father.",
            "How did your father make you feel?",
            "How do you feel about your father?",
            "Does your relationship with your father relate to your feelings today?",
            "Do you have trouble showing affection with your family?",
            "Tell me more about your family."
        ]),
        ("(.*) child(.*)", [
            "Did you have close friends as a child?",
            "What is your favorite childhood memory?",
            "Do you remember any dreams or nightmares from childhood?",
            "Did the other children sometimes tease you?",
            "How do you think your childhood experiences relate to your feelings today?",
        ]),
        ("(.*) dream(.*)", [
            "What does this dream mean to you?",
            "How does this dream make you feel?",
            "Do you remember any dreams or nightmares from childhood?",
            "Do you often dream?",
            "Why is this dream important to you?",
        ]),
        ("(.*) are all(.*)", [
            "What is the meaning of this connection?",
            "What other connections do you see?",
            "How does this connection make you feel?",
            "Why do you think %s are all this way?"
        ]),
        ("(.*)\\?", [
            "Why do you ask that?",
            "Please consider whether you can answer your own question.",
            "Perhaps the answer lies within yourself?",
            "Why don't you tell me?",
        ])
    ]

    private let defaultResponses = [
        "Please, tell me more.",
        "Can you elaborate on that?",
        "How does saying that make you feel?",
        "I see. And what does that tell you?",
        "Let's change focus a bit... tell me about your family.",
        "Let's change focus a bit... tell me about your friends.",
        "Let's change focus a bit... tell me about your goals.",
        "Let's change focus a bit... tell me about your dreams and aspirations.",
        "How does that make you feel?",
        "I see. Is there something about that which tells you how you could improve your life?"
    ]

    // Quit keywords
    private let quitKeyWords = [
        "goodbye",
        "bye",
        "quit",
        "exit"
    ]

    // SOS keywords
    private let sosKeyWords = [
        "suicide",
        "suicidal",
        "kill",
        "death"
    ]

    let sosStatement = "It sounds like things are pretty tough right now. I know you're going through a lot, but you're not alone. Maybe you should think about calling the national suicide prevention hotline at: 1-800-273-8255. They offer free and confidential help."

    /*
     This is a table of "word reflections". It flips statements like "I want your car" to "You want my car".
     */
    private let reflectedWords = [
        "am":"are",
        "was":"were",
        "i":"you",
        "i'd": "you would",
        "i've": "you have",
        "i'll": "you will",
        "my": "your",
        "are": "am",
        "you've": "I have",
        "you'll": "I will",
        "your": "my",
        "yours": "mine",
        "you": "me",
        "me": "you"
    ]

    func botGoodbye() -> String {
        return closings.randomElement()!
    }

    func botHi() -> String {
        return openings.randomElement()!
    }

    // This function processes user input so that it can be used ro form a response. White space and new lines are removed, and all letters are set to lowercase.
    func process(_ input: String) -> String {
        let trimmed = input.trimmingCharacters(in:.whitespacesAndNewlines)
        let lowercased = trimmed.lowercased()
        return lowercased
    }

    // This function flips words (e.x. "I" becomes "you")
    func reflect(_ fragment: String) -> String {
        var words = fragment.components(separatedBy: " ")
        for (i, word) in words.enumerated() {
            if let reflectedWord = reflectedWords[word] {
                words[i] = reflectedWord
            }
        }
        return words.joined(separator: " ")
    }

    // This is the function that produces responses to user input
    func replyTo(_ input: String) -> String {
        // Tokenize and perform sentiment analysis, make all characters lowercase
        var processedinput = tokenize(tokenizerInput: input.lowercased())

        // Iterate over array, check for quit keywords and sos keywords
        for word in processedinput{
            if closings.contains(word){
                botGoodbye() //FIX THIS, NOT DONE
            }else if sosKeyWords.contains(word) {
                return sosStatement
            }
        }


        // Try to match input to one that the bot is programmed to recognize, construct a response
        for (pattern, responses) in psychobabble {
            let regexPrepared = processedinput.joined(separator: " ")

            let re = try! NSRegularExpression(pattern: pattern)
            let matches = re.matches(in: regexPrepared, range: NSRange(location: 0, length: regexPrepared.count))

            // If the statement matched any recognizable statements
            if matches.count > 0 {
                // There should only be one match
                let match = matches[0]

                // If we matched a regex capturing group in parentheses, get the first one.
                // The matched regex group will match a "fragment" that will form
                // part of the response, for added realism.
                var fragment = ""
                if match.numberOfRanges > 1 {
                    fragment = (regexPrepared as NSString).substring(with: match.range(at: 1))
                    fragment = reflect(fragment)
                }

                // Choose a random appropriate response, and format it with the
                // fragment, if needed.
                var response = responses.randomElement()
                response = response!.replacingOccurrences(of: "%s", with: fragment)
                return response!
            }

        }
        return defaultResponses.randomElement()!
    }
}

PlaygroundPage.current.liveView = ViewController()

How do I make the footer always stay at the bottom of the screen?

TylerP
  • 9,600
  • 4
  • 39
  • 43
  • Please check this link: https://stackoverflow.com/questions/15687370/tablefooterview-property-doesnt-fix-the-footer-at-the-bottom-of-the-table-view/15687449 – Khushbu May 16 '20 at 04:24
  • 1
    You can add the UITextField to the view controller's toolbar instead of the footer of your tableview section – Leo Dabus May 16 '20 at 04:40
  • Did you able to fix this issue? – RP89 Mar 05 '22 at 13:16

2 Answers2

2

You should add the enter TextField into container view in lieu of TableView footer Check the screenshot here

  • Contain view contains TableView
  • Contain View contains Enter TextFiew (Use AutoLayout and align it at the bottom)
class ViewController: UIViewController {

    @IBOutlet private weak var tableView: UITableView!

    // MARK: - View Cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        setupTableView()
    }

    private func setupTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.register(UINib.init(nibName: ChatMessageCell.identifier, bundle: nil), forCellReuseIdentifier: ChatMessageCell.identifier)
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 100
        tableView.backgroundColor = UIColor.clear
        tableView.tableFooterView = UIView()
    }
}

// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return // your number of sections
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return // number of rows in seciton
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: ChatMessageCell.identifier) as? ChatMessageCell {
            return cell
        }
        return UITableViewCell()
    }
}

// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    }
}
Tam-Thanh Le
  • 323
  • 1
  • 11
0

Why dont you just create a custom view outside the tableview and set auto-layout snap to top and snap to bottom. But in the bottom is >= 0

King.lbt
  • 843
  • 5
  • 15