Commit c653c3fa authored by Quentin Muret's avatar Quentin Muret

conversation: add multiline textView

- add multiline textView
- replace the send button on the keyboard with the return button
- replace the emoji button with the send button when editing starts
- use a blur effect on the background of the MessageAccessoryView
- change the color of the TextView cursor to ringMain
- use a blur effect on the home indicator space background of the
  Conversation view on iPhone X and later
- adjust the bottom content of the tableView is automatically when
  the textView height changes
- fix the keyboard close button on iPad

Change-Id: Idd52c5eabbfbfe45e6d73340ac527083327eb55a
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent 0c4fe4c0
......@@ -284,6 +284,7 @@
66266FC021557D2F002757A6 /* ScanViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66266FBF21557D2F002757A6 /* ScanViewModel.swift */; };
66266FC4215C18F8002757A6 /* Emoji+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66266FC3215C18F8002757A6 /* Emoji+Helpers.swift */; };
66ACB430214AE28C00A94162 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66ACB42F214AE28C00A94162 /* ScanViewController.swift */; };
66E6381221764C2C005EA2B0 /* GrowingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E6381121764C2C005EA2B0 /* GrowingTextView.swift */; };
66F295DE2166A5930044ED6F /* Devices+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F295DD2166A5930044ED6F /* Devices+Helpers.swift */; };
/* End PBXBuildFile section */
......@@ -654,6 +655,7 @@
66266FBF21557D2F002757A6 /* ScanViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewModel.swift; sourceTree = "<group>"; };
66266FC3215C18F8002757A6 /* Emoji+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Emoji+Helpers.swift"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
66ACB42F214AE28C00A94162 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = "<group>"; };
66E6381121764C2C005EA2B0 /* GrowingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrowingTextView.swift; sourceTree = "<group>"; };
66F295DD2166A5930044ED6F /* Devices+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Devices+Helpers.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
......@@ -1322,9 +1324,10 @@
1A2D18B21F2915C500B2C785 /* ConversationViewController.storyboard */,
1A2D18CC1F29182500B2C785 /* ConversationViewController.swift */,
1A5DC02D1F3565640075E8EF /* ConversationViewModel.swift */,
1A2D18DE1F29197100B2C785 /* MessageAccessoryView.swift */,
1A2D18DF1F29197100B2C785 /* MessageAccessoryView.xib */,
1A2D18DE1F29197100B2C785 /* MessageAccessoryView.swift */,
1A2D18E41F29197100B2C785 /* MessageViewModel.swift */,
66E6381121764C2C005EA2B0 /* GrowingTextView.swift */,
);
path = Conversation;
sourceTree = "<group>";
......@@ -1866,6 +1869,7 @@
0E438A9A204F47E700402900 /* SettingsTableView.swift in Sources */,
0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */,
1A2D18A61F27F7A400B2C785 /* UIViewController+Rx.swift in Sources */,
66E6381221764C2C005EA2B0 /* GrowingTextView.swift in Sources */,
0E7CF4DB20164B6700CD967D /* ButtonsContainerView.swift in Sources */,
0E49097A1FEAC9E1005CAA50 /* CallViewController.swift in Sources */,
1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */,
......
......@@ -44,7 +44,8 @@ extension UIColor {
static let ringMsgCellReceivedText = UIColor(red: 48, green: 48, blue: 48, alpha: 1.0)
static let ringMsgCellTimeText = UIColor(red: 128, green: 128, blue: 128, alpha: 1.0)
static let ringMsgBackground = UIColor(red: 252, green: 252, blue: 252, alpha: 1.0)
static let ringMsgTextFieldBackground = UIColor(red: 252, green: 252, blue: 252, alpha: 1.0)
static let ringMsgTextFieldBackground = UIColor(red: 252, green: 252, blue: 252, alpha: 0)
static let ringMsgTextFieldBorder = UIColor(red: 220, green: 220, blue: 220, alpha: 1.0)
static let ringUITableViewCellSelection = UIColor(red: 209, green: 210, blue: 210, alpha: 1.0)
static let ringNavigationBar = UIColor(red: 235, green: 235, blue: 235, alpha: 1.0)
static let ringSuccess = UIColor(hex: 0x00b20b, alpha: 1.0)
......
......@@ -46,17 +46,31 @@
<constraint firstItem="NYW-Ie-8yB" firstAttribute="centerX" secondItem="6Wq-EJ-CAF" secondAttribute="centerX" id="vB4-hR-9sj"/>
</constraints>
</view>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XRu-HM-jhQ">
<rect key="frame" x="0.0" y="640" width="375" height="27"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="lK5-Aw-CC5">
<rect key="frame" x="0.0" y="0.0" width="375" height="27"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<constraints>
<constraint firstAttribute="height" constant="27" id="b0Q-Xd-v7V"/>
</constraints>
<blurEffect style="extraLight"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="6Wq-EJ-CAF" firstAttribute="leading" secondItem="mrp-Ty-hZO" secondAttribute="leading" id="5VA-aR-jIL"/>
<constraint firstItem="mrp-Ty-hZO" firstAttribute="trailing" secondItem="yc2-Jn-6vm" secondAttribute="trailing" id="6Ar-yh-iTT"/>
<constraint firstItem="XRu-HM-jhQ" firstAttribute="bottom" secondItem="yc2-Jn-6vm" secondAttribute="bottom" id="D1h-vM-lOK"/>
<constraint firstAttribute="bottom" secondItem="6Wq-EJ-CAF" secondAttribute="bottom" id="QKw-Wp-ff0"/>
<constraint firstItem="yc2-Jn-6vm" firstAttribute="top" secondItem="lhx-ny-Zct" secondAttribute="top" id="Qd0-eb-gyZ"/>
<constraint firstItem="mrp-Ty-hZO" firstAttribute="trailing" secondItem="6Wq-EJ-CAF" secondAttribute="trailing" id="W84-gc-kua"/>
<constraint firstItem="yc2-Jn-6vm" firstAttribute="leading" secondItem="mrp-Ty-hZO" secondAttribute="leading" id="Wzk-uM-Oge"/>
<constraint firstItem="mrp-Ty-hZO" firstAttribute="bottom" secondItem="yc2-Jn-6vm" secondAttribute="bottom" id="m6U-Gp-jhl"/>
<constraint firstItem="XRu-HM-jhQ" firstAttribute="centerX" secondItem="yc2-Jn-6vm" secondAttribute="centerX" id="Y16-31-gDX"/>
<constraint firstAttribute="bottom" secondItem="yc2-Jn-6vm" secondAttribute="bottom" id="m6U-Gp-jhl"/>
<constraint firstItem="6Wq-EJ-CAF" firstAttribute="top" secondItem="lhx-ny-Zct" secondAttribute="top" id="v3Q-NK-vb1"/>
<constraint firstItem="XRu-HM-jhQ" firstAttribute="width" secondItem="yc2-Jn-6vm" secondAttribute="width" id="vBi-yc-6H9"/>
</constraints>
<viewLayoutGuide key="safeArea" id="mrp-Ty-hZO"/>
</view>
......@@ -68,7 +82,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="lOF-r3-fSY" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="844" y="-1179"/>
<point key="canvasLocation" x="844" y="-1179.7601199400301"/>
</scene>
</scenes>
</document>
......@@ -27,7 +27,7 @@ import MobileCoreServices
// swiftlint:disable file_length
// swiftlint:disable type_body_length
class ConversationViewController: UIViewController, UITextFieldDelegate,
class ConversationViewController: UIViewController,
UIImagePickerControllerDelegate, UINavigationControllerDelegate,
UIDocumentPickerDelegate, StoryboardBased, ViewModelBased {
......@@ -43,6 +43,7 @@ class ConversationViewController: UIViewController, UITextFieldDelegate,
var textFieldShouldEndEditing = false
var bottomOffset: CGFloat = 0
let scrollOffsetThreshold: CGFloat = 600
var bottomHeight: CGFloat = 0.00
var keyboardDismissTapRecognizer: UITapGestureRecognizer!
......@@ -50,7 +51,6 @@ class ConversationViewController: UIViewController, UITextFieldDelegate,
super.viewDidLoad()
self.configureRingNavigationBar()
self.setupUI()
self.setupTableView()
self.setupBindings()
......@@ -229,15 +229,16 @@ class ConversationViewController: UIViewController, UITextFieldDelegate,
let device = UIDevice.modelName
switch device {
case "iPhone X", "iPhone XS", "iPhone XS Max", "iPhone XR" :
heightOffset = -30
heightOffset = -55
default :
heightOffset = 2
heightOffset = -20
}
self.view.addGestureRecognizer(keyboardDismissTapRecognizer)
}
self.tableView.contentInset.bottom = keyboardHeight + heightOffset
self.tableView.scrollIndicatorInsets.bottom = keyboardHeight + heightOffset
self.bottomHeight = keyboardHeight + heightOffset
self.scrollToBottom(animated: false)
self.updateBottomOffset()
......@@ -324,9 +325,8 @@ class ConversationViewController: UIViewController, UITextFieldDelegate,
self.messageAccessoryView.shareButton.tintColor = UIColor.ringMain
self.messageAccessoryView.cameraButton.tintColor = UIColor.ringMain
self.messageAccessoryView.messageTextField.delegate = self
self.messageAccessoryView.messageTextField.setPadding(8.0, 8.0)
self.messageAccessoryView.sendButton.contentVerticalAlignment = .fill
self.messageAccessoryView.sendButton.contentHorizontalAlignment = .fill
self.tableView.backgroundColor = UIColor.ringMsgBackground
self.messageAccessoryView.backgroundColor = UIColor.ringMsgTextFieldBackground
self.view.backgroundColor = UIColor.ringMsgTextFieldBackground
......@@ -509,17 +509,29 @@ class ConversationViewController: UIViewController, UITextFieldDelegate,
}()
func setupBindings() {
self.messageAccessoryView.emojisButton.rx.tap.subscribe(onNext: { [unowned self] _ in
self.viewModel.sendMessage(withContent: "👍")
}).disposed(by: self.disposeBag)
self.messageAccessoryView.messageTextField.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { [unowned self] _ in
guard let payload = self.messageAccessoryView.messageTextField.text, !payload.isEmpty else {
self.messageAccessoryView.sendButton.rx.tap.subscribe(onNext: { [unowned self] _ in
guard let payload = self.messageAccessoryView.messageTextView.text, !payload.isEmpty else {
return
}
self.viewModel.sendMessage(withContent: payload)
self.messageAccessoryView.messageTextField.text = ""
self.messageAccessoryView.messageTextView.text = ""
self.messageAccessoryView.setEmojiButtonVisibility(hide: false)
}).disposed(by: self.disposeBag)
self.messageAccessoryView.emojisButton.rx.tap.subscribe(onNext: { [unowned self] _ in
self.viewModel.sendMessage(withContent: "👍")
}).disposed(by: self.disposeBag)
self.messageAccessoryView.messageTextViewHeight.asObservable().subscribe(onNext: { height in
self.tableView.contentInset.bottom = self.bottomHeight + height - 35
self.tableView.scrollIndicatorInsets.bottom = self.bottomHeight + height - 35
self.scrollToBottom(animated: true)
self.updateBottomOffset()
})
self.messageAccessoryView.messageTextViewContent.asObservable().subscribe(onNext: { height in
self.messageAccessoryView.editingChanges()
})
}
// Avoid the keyboard to be hidden when the Send button is touched
......
//
// GrowingTextView.swift
// Pods
//
// Created by Kenneth Tsang on 17/2/2016.
// Copyright (c) 2016 Kenneth Tsang. All rights reserved.
//
import Foundation
import UIKit
@objc public protocol GrowingTextViewDelegate: UITextViewDelegate {
@objc optional func textViewDidChangeHeight(_ textView: GrowingTextView, height: CGFloat)
}
@IBDesignable @objc
open class GrowingTextView: UITextView {
override open var text: String! {
didSet { setNeedsDisplay() }
}
private var heightConstraint: NSLayoutConstraint?
public var height: CGFloat = 0.0
// Maximum length of text. 0 means no limit.
@IBInspectable open var maxLength: Int = 0
// Trim white space and newline characters when end editing. Default is true
@IBInspectable open var trimWhiteSpaceWhenEndEditing: Bool = true
// Customization
@IBInspectable open var minHeight: CGFloat = 0 {
didSet { forceLayoutSubviews() }
}
@IBInspectable open var maxHeight: CGFloat = 0 {
didSet { forceLayoutSubviews() }
}
@IBInspectable open var placeholder: String? {
didSet { setNeedsDisplay() }
}
@IBInspectable open var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) {
didSet { setNeedsDisplay() }
}
@IBInspectable open var attributedPlaceholder: NSAttributedString? {
didSet { setNeedsDisplay() }
}
// Initialize
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
contentMode = .redraw
associateConstraints()
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(textDidEndEditing), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
open override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: 30)
}
private func associateConstraints() {
// iterate through all text view's constraints and identify
// height,from: https://github.com/legranddamien/MBAutoGrowingTextView
for constraint in constraints {
if (constraint.firstAttribute == .height) {
if (constraint.relation == .equal) {
heightConstraint = constraint;
}
}
}
}
// Calculate and adjust textview's height
private var oldText: String = ""
private var oldSize: CGSize = .zero
private func forceLayoutSubviews() {
oldSize = .zero
setNeedsLayout()
layoutIfNeeded()
}
private var shouldScrollAfterHeightChanged = false
override open func layoutSubviews() {
super.layoutSubviews()
if text == oldText && bounds.size == oldSize { return }
oldText = text
oldSize = bounds.size
let size = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude))
var height = size.height
// Constrain minimum height
height = minHeight > 0 ? max(height, minHeight) : height
// Constrain maximum height
height = maxHeight > 0 ? min(height, maxHeight) : height
// Add height constraint if it is not found
if (heightConstraint == nil) {
heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: height)
addConstraint(heightConstraint!)
}
// Update height constraint if needed
if height != heightConstraint!.constant {
shouldScrollAfterHeightChanged = true
heightConstraint!.constant = height
self.height = height
if let delegate = delegate as? GrowingTextViewDelegate {
delegate.textViewDidChangeHeight?(self, height: height)
}
} else if shouldScrollAfterHeightChanged {
shouldScrollAfterHeightChanged = false
scrollToCorrectPosition()
}
}
private func scrollToCorrectPosition() {
if self.isFirstResponder {
self.scrollRangeToVisible(NSMakeRange(-1, 0)) // Scroll to bottom
} else {
self.scrollRangeToVisible(NSMakeRange(0, 0)) // Scroll to top
}
}
// Show placeholder if needed
override open func draw(_ rect: CGRect) {
super.draw(rect)
if text.isEmpty {
let xValue = textContainerInset.left + textContainer.lineFragmentPadding
let yValue = textContainerInset.top
let width = rect.size.width - xValue - textContainerInset.right
let height = rect.size.height - yValue - textContainerInset.bottom
let placeholderRect = CGRect(x: xValue, y: yValue, width: width, height: height)
if let attributedPlaceholder = attributedPlaceholder {
// Prefer to use attributedPlaceholder
attributedPlaceholder.draw(in: placeholderRect)
} else if let placeholder = placeholder {
// Otherwise user placeholder and inherit `text` attributes
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = textAlignment
var attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: placeholderColor,
.paragraphStyle: paragraphStyle
]
if let font = font {
attributes[.font] = font
}
placeholder.draw(in: placeholderRect, withAttributes: attributes)
}
}
}
// Trim white space and new line characters when end editing.
@objc func textDidEndEditing(notification: Notification) {
if let sender = notification.object as? GrowingTextView, sender == self {
if trimWhiteSpaceWhenEndEditing {
text = text?.trimmingCharacters(in: .whitespacesAndNewlines)
setNeedsDisplay()
}
scrollToCorrectPosition()
}
}
// Limit the length of text
@objc func textDidChange(notification: Notification) {
if let sender = notification.object as? GrowingTextView, sender == self {
if maxLength > 0 && text.count > maxLength {
let endIndex = text.index(text.startIndex, offsetBy: maxLength)
text = String(text[..<endIndex])
undoManager?.removeAllActions()
}
setNeedsDisplay()
}
}
}
......@@ -20,19 +20,25 @@
import UIKit
import Reusable
import RxSwift
class MessageAccessoryView: UIView, NibLoadable {
class MessageAccessoryView: UIView, NibLoadable, GrowingTextViewDelegate {
@IBOutlet weak var messageTextField: UITextField!
@IBOutlet weak var sendButton: UIButton!
@IBOutlet weak var shareButton: UIButton!
@IBOutlet weak var cameraButton: UIButton!
@IBOutlet weak var emojisButton: UIButton!
@IBOutlet weak var blurEffect: UIVisualEffectView!
@IBOutlet weak var messageTextView: GrowingTextView!
@IBOutlet weak var emojisButtonTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var messageTextFieldTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var sendButtonLeftConstraint: NSLayoutConstraint!
@IBOutlet weak var textViewHeightConstraints: NSLayoutConstraint!
var messageTextViewHeight = Variable<CGFloat>(0.00)
var messageTextViewContent = Variable<String>("")
override open func didMoveToWindow() {
super.didMoveToWindow()
self.setupMessageTextView()
if #available(iOS 11.0, *) {
guard let window = self.window else {
return
......@@ -44,12 +50,34 @@ class MessageAccessoryView: UIView, NibLoadable {
}
}
@IBAction func editingChanges(_ sender: Any) {
if self.messageTextField.text != nil {
if self.messageTextField.text!.count >= 1 {
if UIDevice.current.userInterfaceIdiom != .pad {
func setupMessageTextView() {
self.messageTextView.delegate = self
self.messageTextView.placeholder = "Type your message..."
self.messageTextView.layer.cornerRadius = 18
self.messageTextView.tintColor = UIColor.ringMain
self.messageTextView.textContainerInset = UIEdgeInsets(top: 8, left: 7, bottom: 8, right: 7)
self.messageTextView.layer.borderWidth = 1
self.messageTextView.layer.borderColor = UIColor.ringMsgTextFieldBorder.cgColor
self.messageTextView.maxHeight = 70
}
func textViewDidChangeHeight(_ textView: GrowingTextView, height: CGFloat) {
if height > self.messageTextViewHeight.value {
UIView.animate(withDuration: 0.2) {
self.layoutIfNeeded()
}
}
self.messageTextViewHeight.value = height
}
func textViewDidChange(_ textView: UITextView) {
self.messageTextViewContent.value = textView.text
}
func editingChanges() {
if self.messageTextView.text != nil {
if self.messageTextView.text!.count >= 1 {
setEmojiButtonVisibility(hide: true)
}
} else {
setEmojiButtonVisibility(hide: false)
}
......@@ -58,11 +86,15 @@ class MessageAccessoryView: UIView, NibLoadable {
}
}
func setEmojiButtonVisibility(hide: Bool) {
UIView.animate(withDuration: 0.3, animations: {
UIView.animate(withDuration: 0.2, animations: {
if hide {
self.emojisButtonTrailingConstraint.constant = -27
self.sendButtonLeftConstraint.constant = 13
self.sendButton.tintColor = UIColor.ringMain
} else {
self.emojisButtonTrailingConstraint.constant = 13
self.emojisButtonTrailingConstraint.constant = 14
self.sendButtonLeftConstraint.constant = 35
self.sendButton.tintColor = UIColor.ringMsgTextFieldBackground
}
self.layoutIfNeeded()
})
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina5_9" orientation="portrait">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
......@@ -12,99 +12,106 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="Fja-dy-lIy" customClass="MessageAccessoryView" customModule="Ring" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="315" height="58"/>
<rect key="frame" x="0.0" y="0.0" width="315" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="type your message..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="AJA-0c-Rp7">
<rect key="frame" x="85" y="12" width="177" height="34"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" returnKeyType="send" enablesReturnKeyAutomatically="YES"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="17"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" red="0.93333333333333335" green="0.93333333333333335" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1.2"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="editingChanges:" destination="Fja-dy-lIy" eventType="editingChanged" id="q7X-Ld-xPW"/>
</connections>
</textField>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yky-Ar-GQt" userLabel="Top Border View">
<rect key="frame" x="0.0" y="0.0" width="315" height="1.3333333333333333"/>
<color key="backgroundColor" red="0.93333333333333335" green="0.93333333333333335" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1.2" id="D7A-43-3TV"/>
</constraints>
</view>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UEs-8c-9hC">
<rect key="frame" x="0.0" y="23.666666666666668" width="315" height="56.333333333333329"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="H5g-m6-my5">
<rect key="frame" x="0.0" y="0.0" width="315" height="56.333333333333329"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="extraLight"/>
</visualEffectView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eZ6-Cr-td2" userLabel="Upload Button">
<rect key="frame" x="6" y="12" width="34" height="34"/>
<rect key="frame" x="6" y="35" width="34" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="efZ-wn-OTj"/>
<constraint firstAttribute="width" constant="34" id="i8S-m1-tB0"/>
</constraints>
<state key="normal" image="share_button"/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qCm-fg-RY1">
<rect key="frame" x="46" y="12" width="34" height="34"/>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ifq-LQ-X1n" userLabel="Send Button">
<rect key="frame" x="272" y="35" width="30" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="0I0-Zq-DPq"/>
<constraint firstAttribute="width" constant="34" id="Cby-RM-6vW"/>
<constraint firstAttribute="height" constant="26" id="9PM-CR-8PT"/>
<constraint firstAttribute="width" constant="28" id="cfz-1y-EkK"/>
</constraints>
<state key="normal" image="camera"/>
<inset key="contentEdgeInsets" minX="0.0" minY="4" maxX="0.0" maxY="-4"/>
<state key="normal" image="send_button">
<attributedString key="attributedTitle"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w2N-XG-vU1" userLabel="Emojis Button">
<rect key="frame" x="270" y="12" width="32" height="34"/>
<rect key="frame" x="271" y="35" width="30" height="34"/>
<state key="normal">
<attributedString key="attributedTitle">
<fragment content="👍">
<attributes>
<font key="NSFont" size="20" name="AppleColorEmoji"/>
<font key="NSFont" size="25" name="AppleColorEmoji"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
</attributedString>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qCm-fg-RY1">
<rect key="frame" x="46" y="35" width="34" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="0I0-Zq-DPq"/>
<constraint firstAttribute="width" constant="34" id="Cby-RM-6vW"/>
</constraints>
<state key="normal" image="camera"/>
</button>