I'm trying to set a UILabel text as follows (All the following code is swift3)
// button Action which select a random text
#IBAction func getResultButton(_ sender: TransitionButton) {
if( typeTextField.text == "ABC" && pickedCompanyField.text != "XYZ"){
// query
sender.startAnimation()
// sleep(5)
sender.stopAnimation(animationStyle: .normal)
} else if( typeTextField.text == "DEF" && pickedCompanyField.text != "XYZ"){
// query
sender.startAnimation()
sender.stopAnimation(animationStyle: .shake)
// DispatchQueue.main.async {
self.resultLabel.text = "56.36"
// }
}
}
and I use the following subclass
import UIKit
class CpLabel: UILabel {
override public var canBecomeFirstResponder: Bool {
get {
return true
}
}
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
func sharedInit() {
isUserInteractionEnabled = true
addGestureRecognizer(UILongPressGestureRecognizer(
target: self,
action: #selector(showMenu(sender:))
))
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
UIMenuController.shared.setMenuVisible(false, animated: true)
}
func showMenu(sender: Any?) {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.setTargetRect(bounds, in: self)
menu.setMenuVisible(true, animated: true)
}
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return (action == #selector(copy(_:)))
}
}
I get this Thread Error ... I don't know what is causing it ? I have tried to use DispatchQue... function to see if the error regarding main thread stuff but it seems not ? SO Any? Help will be appreciated
Error screenshot:
thanks guys I found the issue ... It was declared as UILabel and its sub classed to CpLabel , So I changed the deceleration it to CpLabel and it worked..
Related
I know this is an often asked question but I have a specific case of the issue as I abstracted the tapGestureRecognizer so it can be re-used without initialiazing it everytime.
Here is how I use UITapGestureRecognizer on UIlabels :
In UILabel extension :
func setOnClickListener(action: #escaping (_: Any?) -> Void, clickedObject: Any? = nil){
let tapRecogniser = ClickListener(target: self, action: #selector(onViewClicked), clickedObject: clickedObject, onClick: action)
self.addGestureRecognizer(tapRecogniser)
}
#objc func onViewClicked(_ sender: ClickListener) {
sender.onClick?(sender.clickedObject)
}
The ClickListener class :
class ClickListener: UITapGestureRecognizer {
var onClick : ((_: Any?) -> Void)?
var clickedObject: Any?
init(target: Any?, action: Selector?, clickedObject: Any? = nil, onClick: ((_: Any?) -> Void)? = nil) {
self.clickedObject = clickedObject
self.onClick = onClick
super.init(target: target, action: action)
}
}
And here how I call it in my view :
class VoteBoxView: UIView {
private var updateVoteButton: UILabel! = UILabel()
override init(frame: CGRect) {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func initLayout() {
self.addSubview(updateVoteButton)
self.updateVoteButton.text = "Modifier"
self.updateVoteButton.setToSecondaryText()
self.updateVoteButton.setToSecondaryTextColor()
self.updateVoteButton.underline()
self.updateVoteButton.snp.makeConstraints{ (make) -> Void in
make.top.equalTo(self).offset(15)
make.right.equalTo(self)
}
self.initActions()
}
func initActions() {
updateVoteButton.isUserInteractionEnabled = true
updateVoteButton.setOnClickListener(action: updateVote, clickedObject: nil)
}
func updateVote(clickedObject: Any?) {
self.toggleResults(active: false)
}
}
I definitely succeeded to make it work in other cases but there is no differences I can find that would cause it not to work here.
The problem was that I didn't set the correct constraints on my view so the tap was blocked by my ViewController view. I found out by adding colored background on everything.
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.
Does UILabel have any value that can be set in order to make it selectable?
I have a label that I want to be selectable, (long press and a copy btn shows up) kinda like in Safari.
Self-contained Solution (Swift 5)
You can adapt the solution from #BJHSolutions and NSHipster to make the following self-contained SelectableLabel:
import UIKit
/// Label that allows selection with long-press gesture, e.g. for copy-paste.
class SelectableLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
addGestureRecognizer(
UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress(_:))
)
)
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
// MARK: - UIResponderStandardEditActions
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
}
// MARK: - Long-press Handler
#objc func handleLongPress(_ recognizer: UIGestureRecognizer) {
if recognizer.state == .began,
let recognizerView = recognizer.view,
let recognizerSuperview = recognizerView.superview {
recognizerView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperview)
UIMenuController.shared.setMenuVisible(true, animated:true)
}
}
}
Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.
Create a subclass of UILabel and implement the following methods:
override func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
return (action == "copy:")
}
// MARK: - UIResponderStandardEditActions
override func copy(sender: AnyObject?) {
UIPasteboard.generalPasteboard().string = text
}
Then in your view controller, you can add a long press gesture to your label:
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)
and handle the long press with this method:
func handleLongPressGesture(recognizer: UIGestureRecognizer) {
if let recognizerView = recognizer.view,
recognizerSuperView = recognizerView.superview
{
let menuController = UIMenuController.sharedMenuController()
menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
menuController.setMenuVisible(true, animated:true)
recognizerView.becomeFirstResponder()
}}
NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.
UILabel inherits from UIView so you can just add a long press gesture recognizer to the label. Note that you have to change isUserInteractionEnabled to true, because it defaults to false for labels.
import UIKit
class ViewController: UIViewController {
let label = UILabel()
override func viewDidLoad() {
view.addSubview(label)
label.text = "hello"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressLabel(longPressGestureRecognizer:)))
label.addGestureRecognizer(longPressGestureRecognizer)
label.isUserInteractionEnabled = true
}
#objc private func longPressLabel (longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .began {
print("long press began")
} else if longPressGestureRecognizer.state == .ended {
print("long press ended")
}
}
}
I've implemented a UILabel subclass that provides all of the functionality needed. Note that if you're using this with interface builder, you'll need to adjust the init methods.
/// A label that can be copied.
class CopyableLabel: UILabel
{
// MARK: - Initialisation
/// Creates a new label.
init()
{
super.init(frame: .zero)
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
self.addGestureRecognizer(gestureRecognizer)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// MARK: - Responder chain
override var canBecomeFirstResponder: Bool
{
return true
}
// MARK: - Actions
/// Method called when a long press is triggered.
func handleLongPressGesture(_ gestuerRecognizer: UILongPressGestureRecognizer)
{
guard let superview = self.superview else { return }
let menuController = UIMenuController.shared
menuController.setTargetRect(self.frame, in: superview)
menuController.setMenuVisible(true, animated:true)
self.becomeFirstResponder()
}
override func copy(_ sender: Any?)
{
UIPasteboard.general.string = self.text
}
}
I have a custom UIInputViewController, let's call it MyInputViewController. I set the a text field's input view to the view of a singleton instance of my input controller. Inserting, deleting and moving the cursor works fine however when I call self.dismissKeyboard() in the input view controller (method is called, print() writes to the console), it doesn't do anything.
What could be wrong? It got it to work a few days ago however I couldn't remember what might be the error.
class MyInputController: UIInputViewController {
// SINGLETON:
static let keyboard: MyInputController = MyInputController()
class func setSharedKeyboardForTextField(textField: UITextField) {
textField.inputAccessoryView = nil
textField.inputView = keyboard.view
}
// MARK: initializers
private init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func loadView() {
super.loadView()
self.view.translatesAutoresizingMaskIntoConstraints = false=
}
override func viewDidLoad() {
//set up buttons
}
// MARK: helper functions
func inputText() -> String {
return (self.textDocumentProxy.documentContextBeforeInput ?? "") + (self.textDocumentProxy.documentContextAfterInput ?? "")
}
// MARK: animations
private func grow(sender: MathematicalKeyboardKey) {
sender.superview?.bringSubviewToFront(sender)
UIView.animateWithDuration(0.1) { () -> Void in
sender.transform = scaleTransform
}
}
private func shrink(sender: MathematicalKeyboardKey) {
sender.superview?.sendSubviewToBack(sender)
UIView.animateWithDuration(0.1) { () -> Void in
sender.transform = CGAffineTransformIdentity
}
}
// MARK: actions
func keyTouched(sender: MathematicalKeyboardKey) {
// works
grow(sender)
}
func keyExit(sender: MathematicalKeyboardKey) {
// works
shrink(sender)
}
func resign(sender: MathematicalKeyboardKey) {
// called but doesn't work
dismissKeyboard()
shrink(sender)
}
func keyPressed(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.insertText(sender.insertion)
shrink(sender)
}
func remove(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.deleteBackward()
shrink(sender)
}
func clear(sender: MathematicalKeyboardKey) {
// works
while self.textDocumentProxy.hasText() {
self.textDocumentProxy.deleteBackward()
}
shrink(sender)
}
func moveLeft(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1)
shrink(sender)
}
func moveRight(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.adjustTextPositionByCharacterOffset(1)
shrink(sender)
}
}
You can use ;
resignFirstResponder()
method instead of
dismissKeyboard()
method.
#IBAction func hideKeyboard(sender: UIButton) {
resignFirstResponder()
}
I have a list of URL addresses in a iOS Label object. It does not appear that the user can select and copy an item from the list and paste it into their Safari browser on the iOS device. Is there a way to accomplish this?
This feature is not there in UILabel.
You need to use UITextField or UITextView . Also do not forget to change its appearance and using
[... setEditable:NO];
It is actually possible to do with an UILabel, only you'll have to do some subclassing.
End result:
when your user long presses the label, he or she will see a copy-balloon.
Here are the steps to allow for making a label copy-able (as I can recall):
subclass UILabel
set userInteractionEnabled = YES
override canBecomeFirstResponder and return true
add a UILongPressGestureRecognizer
become first responder & present UIMenuController
Swift 3:
let menu = UIMenuController.shared
if !menu.isMenuVisible {
self.becomeFirstResponder()
menu.setTargetRect(self.bounds, in: self)
menu.setMenuVisible(true, animated: true)
}
override canPerformAction to allow copy
Swift 3:
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.copy(_:))
}
override copy method, UIPasteboard the text & hide UIMenuController
Swift 3:
let menu = UIMenuController.shared
let labelText = self.text ?? self.attributedText?.string
if let uLabelText = labelText {
let clipBoard = UIPasteboard.general
clipBoard.string = uText
}
menu.setMenuVisible(false, animated: true)
Here is swift 5 version of JoriDor's solution;
class CopyableLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
sharedInit()
}
func sharedInit() {
isUserInteractionEnabled = true
addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(showMenu(sender:))))
}
#objc
func showMenu(sender: AnyObject?) {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.setTargetRect(bounds, in: self)
menu.setMenuVisible(true, animated: true)
}
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.copy(_:)) {
return true
}
return false
}
override func copy(_ sender: Any?) {
let board = UIPasteboard.general
board.string = text
let menu = UIMenuController.shared
menu.setMenuVisible(false, animated: true)
}
}