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:
Here’s what it looks like with several messages:
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?