Swift Message Text Area - swift

I am creating a new app and I want to add message area like:
When user click the text area keyboard will shown and user start writing message right button will be send button and text area with will be grow like:
What is the easy way to do it?
I hava a idea to do it but I think it is not the the best way. I am thinking to solve it programmaticly. In viewLoad I will create button and text area programmaticly then when user starts writing I will create them again.

As explained in the official apple documentation you can work with UITextField delegate methods to intercept the differents states.
For example you can use textFieldDidBeginEditing(_:) to handle the moment when the user start to edit. Here , you can handle constraints to enlarge your textField (pay attention, you must working with constraints if you use autolayout, dont make frame modifications):
func textFieldDidBeginEditing(textField: UITextField!) {
//
// hide photo button
// change mic button to send button
self.textFieldTrailingConstraint.constant = 10.0
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
After that you can handle the other delegates:
func textFieldShouldEndEditing(textField: UITextField!) -> Bool {
return false
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
where you can repeat your animation or do whatever you want.

I'd also recommend to solve this in code. Loading the view you'll have to create all buttons and if the user starts writing you should recreate some of the buttons. But keep in Mind that you have to ensure, that the Textfield is the same instance or that you have to resign the responder to it by code.

In addition to textfield delegates, you need to handle keyboard as well:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillAppear:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillDisappear:", name: UIKeyboardWillHideNotification, object: nil)}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
NSNotificationCenter.defaultCenter().removeObserver(self)}
func keyboardWillAppear(notification: NSNotification){
var userInfo:NSDictionary = notification.userInfo!
var keyboardSize:CGSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue().size
var contentInsets:UIEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0)
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
var messageFrame:CGRect = self.commentField.frame
messageFrame.origin.y -= keyboardSize.height
self.commentField.frame = messageFrame}
func keyboardWillDisappear(notification: NSNotification){
var userInfo:NSDictionary = notification.userInfo!
var keyboardSize:CGSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue().size
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.25)
self.tableView.contentInset = UIEdgeInsetsZero
UIView.commitAnimations()
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
var messageFrame:CGRect = self.commentField.frame
messageFrame.origin.y += keyboardSize.height
self.commentField.frame = messageFrame}

Related

How can I show/hide a button added to the title bar of an NSWindow?

I have created a method in an NSWindow extension that allows me to add a button next to the text in the title bar. This is similar to the "down chevron" button that appears in the title bar of Pages and Numbers. When the button is clicked, an arbitrary code, expressed as a closure, is run.
While I have that part working fine, I would also like the button to be invisible most of the time and only become visible when the mouse is scrolled into the title bar area. This would be mimicking the way that Pages and Numbers displays the button.
However, I'm having difficulties getting the show/hide to work properly. I believe I can do it if I make it completely custom in the application delegate, and possibly by subclassing NSWindow, but I would really like to keep it as a single method in an NSWindow extension. In this way the code would be easily reusable in multiple applications.
To accomplish this I believe I need to inject an additional handler/listener that will tell me when the mouse enters and leaves the appropriate area. I can define the necessary area using an NSTrackingArea, but I haven't figured out how to "inject" an event listener without the need of subclasses. Does anyone know how (or if) such a thing is possible?
The key to handling the show/hide based on the mouse position was to use an NSTrackingArea to signify the portion that we are interested in, and to handle the mouse enter and mouse exit events. But since this can't be done directly on the title bar view (since we have to subclass the view in order to add the event handlers) we need to create an additional NSView that is invisible but covers the area we want to track.
I'll post the full code below, but the key parts related to this question are the TrackingHelper class defined near the bottom of the file and the way it is added to the titleBarView with its constrains set to be equal to the size of the title bar. The class itself is designed to take three closures, one for the mouse enter event, one for the mouse exit, and one for the action to take when the button is pressed. (Technically the latter doesn't really need to be part of the TrackingHelper, but it is a convenient place to put it to ensure it does not go out of scope while the UI still exists. A more correct solution would be to subclass NSButton to keep the closure, but I have always found subclassing NSButton to be a royal pain.)
Here is the full text of the solution. Note that this has a couple of things that depend on another library of mine - but they are not necessary for the understanding of this problem and are used to deal with the button image. If you wish to use this code you will need to replace the getImage function with one that creates the image you want. (And if you want to see what KSSCocoa is adding, you can obtain it from https://github.com/klassen-software-solutions/KSSCore)
//
// NSWindowExtension.swift
//
// Created by Steven W. Klassen on 2020-02-24.
//
import os
import Cocoa
import KSSCocoa
public extension NSWindow {
/**
Add an action button to the title bar. This will add a "down chevron" icon, similar to the one used in
Numbers and Pages, just to the right of the title in the title bar. When clicked it will run the given
lambda.
*/
#available(OSX 10.14, *)
func addTitleActionButton(_ lambda: #escaping () -> Void) -> NSButton {
guard let titleBarView = getTitleBarView() else {
fatalError("You can only add a title action to an app that has a title bar")
}
guard let titleTextField = getTextFieldChild(of: titleBarView) else {
fatalError("You can only add a title action to an app that has a title field")
}
let trackingHelper = TrackingHelper()
let actionButton = NSButton(image: getImage(),
target: trackingHelper,
action: #selector(trackingHelper.action))
actionButton.setButtonType(.momentaryPushIn)
actionButton.translatesAutoresizingMaskIntoConstraints = false
actionButton.isBordered = false
actionButton.isEnabled = false
actionButton.alphaValue = 0
trackingHelper.translatesAutoresizingMaskIntoConstraints = false
trackingHelper.onButtonAction = lambda
trackingHelper.onMouseEntered = {
actionButton.isEnabled = true
actionButton.alphaValue = 1
}
trackingHelper.onMouseExited = {
actionButton.isEnabled = false
actionButton.alphaValue = 0
}
titleBarView.addSubview(trackingHelper)
titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[trackingHelper]-0-|",
options: [], metrics: nil,
views: ["trackingHelper": trackingHelper]))
titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[trackingHelper]-0-|",
options: [], metrics: nil,
views: ["trackingHelper": trackingHelper]))
titleBarView.addSubview(actionButton)
titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[titleTextField]-[actionButton(==7)]",
options: [], metrics: nil,
views: ["actionButton": actionButton,
"titleTextField": titleTextField]))
titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-1-[actionButton]-3-|",
options: [], metrics: nil,
views: ["actionButton": actionButton]))
DistributedNotificationCenter.default().addObserver(
actionButton,
selector: #selector(actionButton.onThemeChanged(notification:)),
name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"),
object: nil
)
return actionButton
}
fileprivate func getTitleBarView() -> NSView? {
return standardWindowButton(.closeButton)?.superview
}
fileprivate func getTextFieldChild(of view: NSView) -> NSTextField? {
for subview in view.subviews {
if let textField = subview as? NSTextField {
return textField
}
}
return nil
}
}
fileprivate extension NSButton {
#available(OSX 10.14, *)
#objc func onThemeChanged(notification: NSNotification) {
image = image?.inverted()
}
}
#available(OSX 10.14, *)
fileprivate func getImage() -> NSImage {
var image = NSImage(sfSymbolName: "chevron.down")!
if NSApplication.shared.isDarkMode {
image = image.inverted()
}
return image
}
fileprivate final class TrackingHelper : NSView {
typealias Callback = ()->Void
var onMouseEntered: Callback? = nil
var onMouseExited: Callback? = nil
var onButtonAction: Callback? = nil
override func mouseEntered(with event: NSEvent) {
onMouseEntered?()
}
override func mouseExited(with event: NSEvent) {
onMouseExited?()
}
#objc func action() {
onButtonAction?()
}
override func updateTrackingAreas() {
super.updateTrackingAreas()
for trackingArea in self.trackingAreas {
self.removeTrackingArea(trackingArea)
}
let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways]
let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil)
self.addTrackingArea(trackingArea)
}
}

TextView automatically typing return key [Mac Catalyst]

I'm adapting my iPad app to Mac Catalyst and in the app I have a UITextView inside a UITableViewCell with some strange behavior. All of my textViews inside tableview cells are entering the return key. I just press on a textView and it's stuck pressing the return key making new lines (I'm not typing anything). I've tried using different keyboards and I'm getting the same outcome.
This doesn't happen on iPhone or iPad. This also doesn't happen all the time it's very random. Does anyone know how to fix this?
Here's my code:
class TextViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
textView.delegate = self
textView.isScrollEnabled = false
textView.returnKeyType = .done
}
}
// MARK: - textView functions
extension TextViewCell: UITextViewDelegate {
//grow textView as the user types
func textViewDidChange(_ textView: UITextView) {
let size = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude))
if size.height != newSize.height {
UIView.setAnimationsEnabled(false)
tableView?.beginUpdates()
tableView?.endUpdates()
UIView.setAnimationsEnabled(true)
if let thisIndexPath = tableView?.indexPath(for: self) {
tableView?.scrollToRow(at: thisIndexPath, at: .bottom, animated: false)
}
}
}
}
Has anyone else run into this issue and knows how to fix it???
Try performing any operation on the visible elements of the UITableView off the GUI thread. For some reason on Catalyst without this operations fail.
DispatchQueue.main.async{
// stop animations to prevent boucing
UIView.setAnimationsEnabled(false)
// cycle updates to allow frames to be recalculated
self.tableView.beginUpdates()
self.tableView.endUpdates()
// done with all the updates, reset back to normal
UIView.setAnimationsEnabled(true)
}

How to show and hide the keyboard with a subview

I have a custom UIView that is a subview on a UIViewController.
I have it added in my storyboard and set it to Hidden.
My subview is also within another UIView that I'm using as a 'blur view' which is also initially Hidden.
I have functions that will unhide & hide the subviews.
My custom subview has a UITextField. I can show the keyboard and move the subview up with no problems. When I type in the keyboard or dismiss it my subview moves up and to the left. When I try to show my subview again it shows incorrectly (up and to the left).
The custom subview starts at the center of my screen.
The goal is move it up when the keyboard shows so it will not cover the subview or the UITextField, allow the user to type in the UITextField, and then dismiss the keyboard and move the custom subview back to the center.
In my UIViewController:
// Showing the custom sub view
func displayCustomSubView() {
if let window = UIApplication.shared.keyWindow {
self.blurView.isHidden = false
self.customSubView.isHidden = false
self.blurView.frame = window.frame
self.customSubView.center = window.center
window.addSubview(self.blurView)
UIApplication.shared.keyWindow?.bringSubviewToFront(self.blurView)
}
}
// Hiding the custom sub view
// the custom sub view has a button I tap to hide
#objc func dismissCustomSubView() {
self.blurView.isHidden = true
self.customSubView.isHidden = true
}
// Show Keyboard
// Since I am using the window to make sure my blur view expands to the full frame, I have tried just moving the window up
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = -75
}
}
// Hide Keyboard
#objc func keyboardWillHide(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = 0
}
}
// Custom Subview Extension
extension CustomSubView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Added the Custom Subview Extension above.
First add this notification within your viewDidLoad(). And make a global variable called var keyboardH: CGFloat = 0:
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
And this function below:
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
self.keyboardH = keyboardHeight
}
This function is called every time the keyboard is present and will reveal the keyboard height and later we can use this variable.
So in your code:
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
let position = window.frame.origin.y - keyboardH
window.frame.origin.y = position
}
}

view move up in particular textfield delegate

I have to move the UIView in only last UITextField in Swift 3.0 on mentioned below delegate method using tag,
func textFieldDidBeginEditing(_ textField: UITextField) {
if (textField.tag == 4){
//UIView Up
}
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if (textField.tag == 4){
//UIView Down
}
return true
}
I tried many codes but none of them are working like notification,..etc.
You need to add Observers into the NotificationCenter for listening to both when Keyboard goes up and down (i'll assume your textfield outlet is lastTextField for this example to work but this obviously have to be adapted to whatever name you've had provide for it)
IBOutlet weak var passwordTextField: UITextField!
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
(Code above can be added in viewDidLoad())
Then you add methods to be executed when those notifications arrive, like this:
func keyboardWillShow(_ notification:Notification) {
if view.frame.origin.y >= 0 && lastTextField.isFirstResponder {
view.frame.origin.y -= getKeyboardHeight(notification)
}
}
func keyboardWillHide(_ notification:Notification) {
if view.frame.origin.y < 0 {
view.frame.origin.y += getKeyboardHeight(notification)
}
}
Validations within those methods prevent double execution like moving up/down twice when moving between textfields without resigning first responder which is common in cases like your (i assume your doing this for a form hence the clarification you only need it for the fourth textfield). Notice i'm only doing validation in for the specified textfield (with its outlet lastTextField) in the keyboardWillShow method, this in case you move thor another textfield while the keyboard is shown and resign responder from it in which case, even though it isn't the original place where you started, the view will return to its original place when the keyboard is hidden.
You'll also need a method for getting keyboard's height, this one can help with that:
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue // of CGRect
return keyboardSize.cgRectValue.height
}
Let me know how it goes but i just tested this same code on my app and it works so you should be fine.
PS: pay close attention to the storyboard (if you're using it) and that delegate for textfields are set up properly.
The problem you are trying to remedy is rather complicated, because it requires you to:
Find the textField which is firstResponder
Calculate where that TextField is relative to it's superViews
Determine the distance for the animation, so that the containing
superview doesnt jump out of the window, or jumps too
much/repeatedly
Animate the proper superView.
As you can see.. it's quite the algorithm. But luckily, I can help. However, this only works for a hierarchy which has the following layout:
superView (view in the case of UIViewController) > (N)containerSubviews > textFields
where N is an integer
or the following:
superView (view in the case of UIViewController) > textFields
The idea is to animate superView, based on which textField is firstResponser, and to calculate if it's position inside of the SCREEN implies that it either partially/totally obstructed by the Keyboard or that it is not positioned the way you want for editing. The advantage to this, over simply moving up the superView when the keyboard is shown in an arbitrary manner, is that your textField might not be positioned properly (ie; obstructed by the statusbar), and in the case where your textfields are in a ScrollView/TableView or CollectionView, you can simply scroll the texfield into the place you want instead. This allows you to compute that desired location.
First you need a method which will parse through a given superView, and look for which of it's subViews isFirstResponder:
func findActiveTextField(subviews : [UIView], textField : inout UITextField?) {
for view in subviews {
if let tf = view as? UITextField {
guard !tf.isFirstResponder else {
textField = tf; break
return
}
} else if !subviews.isEmpty {
findActiveTextField(subviews: view.subviews, textField: &textField)
}
}
}
Second, to aleviate the notification method, also make a method to manage the actual animation:
func moveFromDisplace(view: UIView, keyboardheight: CGFloat, comp: #escaping (()->())) {
//You check to see if the view passed is a textField.
if let texty = view as? UITextField {
//Ideally, you set some variables to animate with.
//Next step, you determine which textField you're using.
if texty == YourTextFieldA {
UIView.animate(withDuration: 0.5, animations: {
self./*the proper superView*/.center.y = //The value needed
})
comp()
return
}
if texty == YourTextFieldB {
// Now, since you know which textField is FirstResponder, you can calculate for each textField, if they will be cropped out by the keyboard or not, and to animate the main view up accordingly, or not if the textField is visible at the time the keyboard is called.
UIView.animate(withDuration: 0.5, animations: {
self./*the proper superView*/.center.y = //The Value needed
})
comp()
return
}
}
}
Finally, the method which is tied to the notification for the keyboardWillShow key; in this case, i have a UIViewController, with an optional view called profileFlow containing a bunch of UITextFields
func searchDisplace(notification: NSNotification) {
guard let userInfo:NSDictionary = notification.userInfo as NSDictionary else { return }
guard let keyboardFrame:NSValue = userInfo.value(forKey: UIKeyboardFrameEndUserInfoKey) as? NSValue else { return }
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let keybheight = keyboardHeight
var texty : UITextField? //Here is the potential textfield
var search : UISearchBar? //In my case, i also look out for searchBars.. So ignore this.
guard let logProfile = profileFlow else { return }
findActiveTextField(subviews: [logProfile], textField: &texty)
//Check if the parsing method found anything...
guard let text = texty else {
//Found something.. so determine if it should be animated..
moveFromDisplace(view: searchy, keybheight: keybheight, comp: {
value in
search = nil
})
return
}
//Didn't find anything..
}
Finally, you tie in this whole logic to the notification:
NotificationCenter.default.addObserver(self, selector: #selector(searchDisplace(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
I can't provide more content to the code, since it all depends on your view hierarchy, and how you want things to animate. So it's up to you to figure that out.
On a side note, usually, if you have so many textfields that to lay them out properly means they overstep the length of the screen.. it's probable that you could simplify your layout. A way to make this algorithm better would be to make sure you have all your textfields in one containing view, which again can become heavy for when, say, you use AutoLayout constraints. Odds are if you're in this situation, you can probably afford to add a flow of several views etc.
There is also the fact that i've never really needed to use this for iPhone views, more for iPad views, and even then for large forms only (e-commerce). So perhaps if you're not in that category, it might be worth reviewing your layout.
Another approach to this, is to use my approach, but to instead check for specific textFields right in the findActiveTextField() method if you only have a handful of textfields, and to animate things within findActiveTextField() as well if you know all of the possible positions they can be in.
Either way, i use inout parameters in this case, something worth looking into if you ask me.

Set textfield in UISearchBar

I'm trying to add SearchController to UINavigationBar. I'm trying to set UITextField of UISearchBar after back button of UINavigationController. I want some more space after back button
When I start searching it appears as
Whereas I should be able to view back button. Only textfield width should be decreased. Also after Cancel it should be again back to initial layout. Whereas it is displayed as below:
Below is my code
var searchResultController = UISearchController()
self.searchResultController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
controller.hidesNavigationBarDuringPresentation = false
self.navigationController?.navigationBar.addSubview(controller.searchBar)
return controller
})()
override func viewDidAppear(animated: Bool) {
for subView in searchResultController.searchBar.subviews{
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
var bounds: CGRect
bounds = textField.frame
bounds.size.width = self.view.bounds.width - 50
}
}
}
}
Please let me know how can I fix this.
Thanks in advance
For setting the UIsearchBar add it to Navigation's titleView as
self.navigationItem.titleView = controller.searchBar
For removing Cancel button we can use UISearchControllerDelegate method
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.showsCancelButton = false
}
Hope this may help any one.