How to use UIEditMenu with WKWebView in iOS 16? - ios

In iOS 16 UIMenuController is deprecated. Instead, we should use UIEditMenuInteraction. In the WWDC notes I found simple example how to use editMenu in UITextView with native gestures:
#available(iOS 16.0, *)
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
}
func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
var additionalActions: [UIMenuElement] = []
if textView.selectedRange.length > 0 {
let highlightAction = [UIAction(title: "Highlight") { _ in
print("Highlight")
}]
additionalActions.append(contentsOf:highlightAction)
}
let selectAction = [UIAction(title: "Select") { _ in
print("Select")
}]
additionalActions.append(contentsOf:selectAction)
return UIMenu(children: additionalActions)
}
}
But how to use editMenu for text selections in WKWebView with native gestures?
I started experimenting with WKWebView and wrote this code:
#available(iOS 16.0, *)
class MenuViewController: UIViewController, UIEditMenuInteractionDelegate, WKNavigationDelegate {
#IBOutlet weak var myWebView: WKWebView!
var editMenuInteraction: UIEditMenuInteraction?
override func viewDidLoad() {
super.viewDidLoad()
setupEditMenuInteraction()
setupWKWebView()
}
func setupEditMenuInteraction() {
editMenuInteraction = UIEditMenuInteraction(delegate: self)
myWebView.addInteraction(editMenuInteraction!)
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
myWebView.addGestureRecognizer(longPressGestureRecognizer)
}
#objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard gestureRecognizer.state == .began else { return }
let configuration = UIEditMenuConfiguration(
identifier: "textViewEdit",
sourcePoint: gestureRecognizer.location(in: myWebView)
)
editMenuInteraction?.presentEditMenu(with: configuration)
}
func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
var actions = suggestedActions
let customMenu = UIMenu(title: "", options: .displayInline, children: [
UIAction(title: "menuItem1") { _ in
print("menuItem1")
},
UIAction(title: "menuItem2") { _ in
print("menuItem2")
},
UIAction(title: "menuItem3") { _ in
print("menuItem3")
}
])
actions.append(customMenu)
return UIMenu(children: customMenu.children)
}
}
This code works fine but the problem is that UILongPressGestureRecognizer is not what I need. I need to show editMenu after text is selected like native select gesture. What type of gesture should I use instead of UILongPressGestureRecognizer? Because now in my WKWebView I call editMenu with native text select gesture and my editMenu with long tap gesture. It's not very good solution.

Related

MessageKit: the added input bar button only appears after I sent a message

I'm building an app using MessageKit.
I added an InputBarItem to the input bar, but it doesn't appear when the view first appears. Only after I pressed "send", the item shows up.
Below is part of the ChatViewController. Any idea why this happens?
Thank you!
class ChatViewController: MessagesViewController {
....
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
...
messageInputBar.delegate = self
setupConstraints()
}
}
extension ChatViewController: MessagesDataSource {
...
}
// MARK: - MessageInputBarDelegate
extension ChatViewController: InputBarAccessoryViewDelegate {
#objc
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
processInputBar(messageInputBar)
}
private func makeButton(named: String) -> InputBarButtonItem {
return InputBarButtonItem()
.configure {
$0.spacing = .fixed(10)
$0.image = UIImage(systemName: named)?.withRenderingMode(.alwaysTemplate)
$0.setSize(CGSize(width: 25, height: 25), animated: false)
$0.tintColor = .blue
}
}
func processInputBar(_ inputBar: InputBarAccessoryView) {
let components = inputBar.inputTextView.components
inputBar.inputTextView.text = String()
let items = [
makeButton(named: "plus")
]
inputBar.setLeftStackViewWidthConstant(to: 36, animated: false)
inputBar.setStackViewItems(items, forStack: .left, animated: false)
}
}
It is happening because of following code -
#objc
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
processInputBar(messageInputBar)
}
If you see here the delegate function says didPressSendButtonWith and you are calling your function to add item here. So after pressing send your function gets called adding the item to input bar.
Thanks.

Add Custom UIMenuController in SwiftUI

I want to highlight some sentence inside the TextEditor by giving selection on the text, then I want a new custom UIMenuController but in Swift UI, I've followed some tutorial and it works in UIKit but it can't appear when I try to implement in inside swiftUI using UIRepresentable, any ideas to add that custom UIMenuController in SwiftUI?
class Coordinator: NSObject, UITextViewDelegate
{
var text: Binding<String>
var model: ScriptField_Model
init(_ text: Binding<String>, _ model: ScriptField_Model) {
self.text = text
self.model = model
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
self.model.text = textView.text
}
func textViewDidChangeSelection(_ textView: UITextView) {
let menuItem1: UIMenuItem = UIMenuItem(title: "Menu 1", action: #selector(onMenu1(sender:)))
// Store MenuItem in array.
let myMenuItems: [UIMenuItem] = [menuItem1]
// Added MenuItem to MenuController.
UIMenuController.shared.menuItems = myMenuItems
UIMenuController.shared.hideMenu()
}
func textFieldShouldReturn(_ textView: UITextView) -> Bool {
return true
}
#objc internal func onMenu1(sender: UIMenuItem) {
print("onMenu1")
}
}
this is the part i try to add when select the sentence :
func textViewDidChangeSelection(_ textView: UITextView) {
let menuItem1: UIMenuItem = UIMenuItem(title: "Menu 1", action: #selector(onMenu1(sender:)))
// Store MenuItem in array.
let myMenuItems: [UIMenuItem] = [menuItem1]
// Added MenuItem to MenuController.
UIMenuController.shared.menuItems = myMenuItems
UIMenuController.shared.hideMenu()
}
Any Help and reference, i will appreciate it.. thankyouu
first create class like CVEDictTextView
import Foundation
import SwiftUI
class CVEDictTextView: UITextView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
let newInstanceItem = UIMenuItem(title: "Lookup", action:#selector(lookup))
UIMenuController.shared.menuItems = [newInstanceItem]
UIMenuController.shared.hideMenu()
if(action == #selector(lookup)){
return true
}
return false
}
#objc func lookup() {
if(self.selectedRange.location != NSNotFound ){
let str = self.value(forKey: "text") as! String
var str2 = String(str.dropFirst(self.selectedRange.location))
str2 = String(str2.dropLast(str2.count - self.selectedRange.length))
print(str2)
}
}
}
and create TextView UIViewRepresentable
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = CVEDictTextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.isEditable = false
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
in SwitfUI view
using it
#State private var message = "hello how are you \n\t are you ok? email : hung.phuoc.tran#gmail.com"
#State private var textStyle = UIFont.TextStyle.body
TextView(text: $message, textStyle: $textStyle)
.padding(.horizontal)

How do I allow text selection on a Text label in SwiftUI?

When I create a text view:
Text("Hello World")
I can't allow the user to select text when they long press.
I've looked at using a TextField but that doesn't seem to allow for turning off text editing.
I just want to be able to display a body of text and allow the user to highlight a selection using the system text selector.
Thanks!
iOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+
As of Xcode 13.0 beta 2 you can use
Text("Selectable text")
.textSelection(.enabled)
Text("Non selectable text")
.textSelection(.disabled)
// applying `textSelection` to a container
// enables text selection for all `Text` views inside it
VStack {
Text("Selectable text1")
Text("Selectable text2")
// disable selection only for this `Text` view
Text("Non selectable text")
.textSelection(.disabled)
}.textSelection(.enabled)
See also the textSelection Documentation.
iOS 14 and lower
Using TextField("", text: .constant("Some text")) has two problems:
Minor: The cursor shows up when selecting
Mayor: When a user selects some text he can tap in the context menu cut, paste and other items which can change the text regardless of using .constant(...)
My solution to this problem involves subclassing UITextField and using UIViewRepresentable to bridge between UIKit and SwiftUI.
At the end I provide the full code to copy and paste into a playground in Xcode 11.3 on macOS 10.14
Subclassing the UITextField:
/// This subclass is needed since we want to customize the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {
/// (Not used for this workaround, see below for the full code) Binding from the `CustomTextField` so changes of the text can be observed by `SwiftUI`
fileprivate var _textBinding: Binding<String>!
/// If it is `true` the text field behaves normally.
/// If it is `false` the text cannot be modified only selected, copied and so on.
fileprivate var _isEditable = true {
didSet {
// set the input view so the keyboard does not show up if it is edited
self.inputView = self._isEditable ? nil : UIView()
// do not show autocorrection if it is not editable
self.autocorrectionType = self._isEditable ? .default : .no
}
}
// change the cursor to have zero size
override func caretRect(for position: UITextPosition) -> CGRect {
return self._isEditable ? super.caretRect(for: position) : .zero
}
// override this method to customize the displayed items of 'UIMenuController' (the context menu when selecting text)
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// disable 'cut', 'delete', 'paste','_promptForReplace:'
// if it is not editable
if (!_isEditable) {
switch action {
case #selector(cut(_:)),
#selector(delete(_:)),
#selector(paste(_:)):
return false
default:
// do not show 'Replace...' which can also replace text
// Note: This selector is private and may change
if (action == Selector("_promptForReplace:")) {
return false
}
}
}
return super.canPerformAction(action, withSender: sender)
}
// === UITextFieldDelegate methods
func textFieldDidChangeSelection(_ textField: UITextField) {
// update the text of the binding
self._textBinding.wrappedValue = textField.text ?? ""
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Allow changing the text depending on `self._isEditable`
return self._isEditable
}
}
Using UIViewRepresentable to implement SelectableText
struct SelectableText: UIViewRepresentable {
private var text: String
private var selectable: Bool
init(_ text: String, selectable: Bool = true) {
self.text = text
self.selectable = selectable
}
func makeUIView(context: Context) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}
func updateUIView(_ uiView: CustomUITextField, context: Context) {
uiView.text = self.text
uiView._textBinding = .constant(self.text)
uiView._isEditable = false
uiView.isEnabled = self.selectable
}
func selectable(_ selectable: Bool) -> SelectableText {
return SelectableText(self.text, selectable: selectable)
}
}
The full code
In the full code below I also implemented a CustomTextField where editing can be turned off but still be selectable.
Playground view
Code
import PlaygroundSupport
import SwiftUI
/// This subclass is needed since we want to customize the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {
/// Binding from the `CustomTextField` so changes of the text can be observed by `SwiftUI`
fileprivate var _textBinding: Binding<String>!
/// If it is `true` the text field behaves normally.
/// If it is `false` the text cannot be modified only selected, copied and so on.
fileprivate var _isEditable = true {
didSet {
// set the input view so the keyboard does not show up if it is edited
self.inputView = self._isEditable ? nil : UIView()
// do not show autocorrection if it is not editable
self.autocorrectionType = self._isEditable ? .default : .no
}
}
// change the cursor to have zero size
override func caretRect(for position: UITextPosition) -> CGRect {
return self._isEditable ? super.caretRect(for: position) : .zero
}
// override this method to customize the displayed items of 'UIMenuController' (the context menu when selecting text)
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// disable 'cut', 'delete', 'paste','_promptForReplace:'
// if it is not editable
if (!_isEditable) {
switch action {
case #selector(cut(_:)),
#selector(delete(_:)),
#selector(paste(_:)):
return false
default:
// do not show 'Replace...' which can also replace text
// Note: This selector is private and may change
if (action == Selector("_promptForReplace:")) {
return false
}
}
}
return super.canPerformAction(action, withSender: sender)
}
// === UITextFieldDelegate methods
func textFieldDidChangeSelection(_ textField: UITextField) {
// update the text of the binding
self._textBinding.wrappedValue = textField.text ?? ""
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Allow changing the text depending on `self._isEditable`
return self._isEditable
}
}
struct CustomTextField: UIViewRepresentable {
#Binding private var text: String
private var isEditable: Bool
init(text: Binding<String>, isEditable: Bool = true) {
self._text = text
self.isEditable = isEditable
}
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
return textField
}
func updateUIView(_ uiView: CustomUITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = self.text
uiView._textBinding = self.$text
uiView._isEditable = self.isEditable
}
func isEditable(editable: Bool) -> CustomTextField {
return CustomTextField(text: self.$text, isEditable: editable)
}
}
struct SelectableText: UIViewRepresentable {
private var text: String
private var selectable: Bool
init(_ text: String, selectable: Bool = true) {
self.text = text
self.selectable = selectable
}
func makeUIView(context: Context) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}
func updateUIView(_ uiView: CustomUITextField, context: Context) {
uiView.text = self.text
uiView._textBinding = .constant(self.text)
uiView._isEditable = false
uiView.isEnabled = self.selectable
}
func selectable(_ selectable: Bool) -> SelectableText {
return SelectableText(self.text, selectable: selectable)
}
}
struct TextTestView: View {
#State private var selectableText = true
var body: some View {
VStack {
// Even though the text should be constant, it is not because the user can select and e.g. 'cut' the text
TextField("", text: .constant("Test SwiftUI TextField"))
.background(Color(red: 0.5, green: 0.5, blue: 1))
// This view behaves like the `SelectableText` however the layout behaves like a `TextField`
CustomTextField(text: .constant("Test `CustomTextField`"))
.isEditable(editable: false)
.background(Color.green)
// A non selectable normal `Text`
Text("Test SwiftUI `Text`")
.background(Color.red)
// A selectable `text` where the selection ability can be changed by the button below
SelectableText("Test `SelectableText` maybe selectable")
.selectable(self.selectableText)
.background(Color.orange)
Button(action: {
self.selectableText.toggle()
}) {
Text("`SelectableText` can be selected: \(self.selectableText.description)")
}
// A selectable `text` which cannot be changed
SelectableText("Test `SelectableText` always selectable")
.background(Color.yellow)
}.padding()
}
}
let viewController = UIHostingController(rootView: TextTestView())
viewController.view.frame = CGRect(x: 0, y: 0, width: 400, height: 200)
PlaygroundPage.current.liveView = viewController.view
A simple workaround solution I found is to just use context menus instead:
Text($someText)
.contextMenu(ContextMenu(menuItems: {
Button("Copy", action: {
UIPasteboard.general.string = someText
})
}))
I ran into a similar problem, where I wanted in essence to select the text without allowing editing. In my case, I wanted to show the UIMenuController when the text was tapped on, without allowing editing of the text or showing the cursor or keyboard. Building on the prior answers:
import SwiftUI
import UIKit
struct SelectableText: UIViewRepresentable {
var text: String
#Binding var isSelected: Bool
func makeUIView(context: Context) -> SelectableLabel {
let label = SelectableLabel()
label.textColor = .white
label.font = .systemFont(ofSize: 60, weight: .light)
label.minimumScaleFactor = 0.6
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .right
label.numberOfLines = 1
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.text = text
return label
}
func updateUIView(_ uiView: SelectableLabel, context: Context) {
uiView.text = text
if isSelected {
uiView.showMenu()
} else {
let _ = uiView.resignFirstResponder()
}
}
}
class SelectableLabel: UILabel {
override var canBecomeFirstResponder: Bool {
return true
}
override init(frame: CGRect) {
super.init(frame: .zero)
highlightedTextColor = .gray
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(copy(_:)), #selector(paste(_:)), #selector(delete(_:)):
return true
default:
return super.canPerformAction(action, withSender: sender)
}
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = self.stringValue
}
override func paste(_ sender: Any?) {
guard let string = UIPasteboard.general.string else { return }
NotificationCenter.default.post(name: Notification.Name.Paste, object: nil, userInfo: [Keys.PastedString: string])
}
override func delete(_ sender: Any?) {
NotificationCenter.default.post(name: Notification.Name.Delete, object: nil)
}
override func resignFirstResponder() -> Bool {
isHighlighted = false
return super.resignFirstResponder()
}
public func showMenu() {
becomeFirstResponder()
isHighlighted = true
let menu = UIMenuController.shared
menu.showMenu(from: self, rect: bounds)
}
}
I use custom paste and delete notifications to message my model object, where the paste and delete actions are processed to update the display appropriately, which works for my purposes. Bindings could also be used.
To use:
SelectableText(text: text, isSelected: self.$isSelected)
.onTapGesture {
self.isSelected.toggle()
}
.onReceive(NotificationCenter.default.publisher(for: UIMenuController.willHideMenuNotification)) { _ in
self.isSelected = false
}
Or we can use something like this when we want show "copy" tooltip for text without allowing editing.
As benefit we will have possibility to using native view "Text" which gives us the opportunity using native methods ".font()", ".foregroundColor()" and etc. Also we can use it for group of views, for exapmple cell.
Building on the prior answers.
Playground code
import PlaygroundSupport
import SwiftUI
private class SelectableUIView: UIView {
var text: String?
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
func setup() {
self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu)))
}
#objc func showMenu(_ recognizer: UILongPressGestureRecognizer) {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.showMenu(from: self, rect: frame)
}
}
override func copy(_ sender: Any?) {
let board = UIPasteboard.general
board.string = text
UIMenuController.shared.hideMenu()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.copy)
}
}
struct SelectableView: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UIView {
let view = SelectableUIView()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let view = uiView as? SelectableUIView else {
return
}
view.text = text
}
}
struct SelectableContainer<Content: View>: View {
private let content: () -> Content
private var text: String
public init(text: String, #ViewBuilder content: #escaping () -> Content) {
self.text = text
self.content = content
}
public var body: some View {
ZStack {
content()
SelectableView(text: text)
.layoutPriority(-1)
}
}
}
struct SelectableText: View {
private var text: String
public init(_ text: String) {
self.text = text
}
public var body: some View {
ZStack {
Text(text)
SelectableView(text: text)
.layoutPriority(-1)
}
}
}
struct TextTestView: View {
#State private var text = "text"
var body: some View {
VStack {
SelectableContainer(text: text) {
VStack(alignment: .leading) {
Text("Header")
.font(.body)
Text(text)
.background(Color.orange)
}
}
.background(Color.yellow)
SelectableText(text)
.background(Color.black)
.foregroundColor(.white)
.font(.largeTitle)
}.padding()
}
}
let viewController = UIHostingController(rootView: TextTestView())
viewController.view.frame = CGRect(x: 0, y: 0, width: 400, height: 200)
PlaygroundPage.current.liveView = viewController.view
Playground view
Playground view

Only want Copy and Share from UIMenuController, but code still being wonky

Here's what my textView looks like right now. It is a textview inside a scrollview.
I am trying to replace the usual UIMenuController menu items with Save and Delete but not getting there. Can someone help me out?
Here's my code:
import UIKit
class DetailViewController: UIViewController, UIGestureRecognizerDelegate, {
var selectedStory : URL!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var textSlider: UISlider! {
didSet {
configureSlider()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let storyText = try? String(contentsOf: selectedStory)
textView.text = storyText
textView.isUserInteractionEnabled = true
let longPressGR = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
longPressGR.minimumPressDuration = 0.3 //
textView.addGestureRecognizer(longPressGR)
}
// MARK: - UIGestureRecognizer
#objc func longPressHandler(sender: UILongPressGestureRecognizer) {
guard sender.state == .began,
let senderView = sender.view,
let superView = sender.view?.superview
else { return }
senderView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(senderView.frame, in: superView)
UIMenuController.shared.setMenuVisible(true, animated: true)
}
override var canBecomeFirstResponder: Bool {
return true
}
}
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
extension UIScrollView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
I'm getting 2 issues:
When I tap the screen, only the Share is showing up and the Copy is not.
The Share button shows up randomly near the center, not on the text that is selected, like so.
First of all, remove UITextView that is inside UIScrollView because UIScrollView itself is the parent class of UITextView. It will place the UIMenuController at appropriate frame.
Remove longPressGR and longPressHandler methods.
Replace this method,
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action.description == "copy:" || action.description == "_share:" {
return true
} else {
return false
}
}
}
You will get following output.

UIView container disappears when I move it's Y origin down

I have UIView which is container for button "send" and textView.
When I starting editing I have keyboard on the screen and I change Y origin of the container to move it higher and not be covered by keyboard.
When I finish editing I got keyboard hidden and my container(UIView with button and textView) just disappears! But if I turn simulator right or left this became visible again.
Here is the animation code that move my container up and down:
func textViewDidChange(textView: UITextView) {
self.messagePromptLabel.hidden = messageTextView.hasText() ? true : false
}
func textViewDidEndEditing(textView: UITextView) {
if !self.messageTextView.hasText() {
self.messagePromptLabel.hidden = false
}
}
func didTapScrollView(){
self.view.endEditing(true)
}
func keyboardWasShown(notification: NSNotification) {
let dict: NSDictionary = notification.userInfo!
let keyboardSize: NSValue = dict.valueForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let frameKeyboardSize: CGRect = keyboardSize.CGRectValue()
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.chatScrollView.frame.origin.y -= frameKeyboardSize.height
self.messageView.frame.origin.y -= frameKeyboardSize.height
}) { (finished: Bool) -> Void in
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.chatScrollView.frame.origin.y = self.chatScrollViewOriginY!
self.messageView.frame.origin.y = self.messageTextViewOriginY!
}) { (finished: Bool) -> Void in
}
}
Some additional code
This code may help find out something.
#IBOutlet weak var messageTextView: UITextView!
#IBOutlet weak var chatScrollView: UIScrollView!
#IBOutlet weak var messagePromptLabel: UILabel!
#IBOutlet weak var messageView: UIView!
var chatScrollViewOriginY: CGFloat?
var messageTextViewOriginY: CGFloat?
var messageArray = [String]()
var senderArray = [String]()
var currentUserImage: UIImage?
var recipientImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
messageTextView.addSubview(messagePromptLabel)
self.title = recipientNickname
chatScrollViewOriginY = self.chatScrollView.frame.origin.y
messageTextViewOriginY = self.messageTextView.frame.origin.y
print(" messageTextViewOriginY \(messageTextViewOriginY)")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "didTapScrollView")
tapGestureRecognizer.numberOfTouchesRequired = 1
chatScrollView.addGestureRecognizer(tapGestureRecognizer)
chatScrollView.backgroundColor = UIColor.redColor()
}
func textViewDidChange(textView: UITextView) {
self.messagePromptLabel.hidden = messageTextView.hasText() ? true : false
}
func textViewDidEndEditing(textView: UITextView) {
if !self.messageTextView.hasText() {
self.messagePromptLabel.hidden = false
}
}
func didTapScrollView(){
self.view.endEditing(true)
}
Question:
What make my UIView container invisible all the time when I hidding keyboard and make it visible when I turn my phone left or right?
You need to add this code in your send button action:
self.view.endEditing(true)

Resources