Detect user input in UITextView (besides delegation) - ios

I'm subclassing UITextView and want to handle user input inside it. It's not an option to take advantage of delegation because it should be possible to set a delegate to something different. Anyone knows how can I accomplish this?

There is a workaround for this. You can use UITextViewTextDidChange notification.
class UITextViewPlus: UITextView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(textChange(_:)), name: .UITextViewTextDidChange, object: nil)
}
func textChange(_ sender: NSNotification) {
guard let textView = sender.object as? UITextViewPlus, textView == self else {
// ignoring text change of any other UITextView
return
}
// do something
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Note: Just keep in mind UITextViewTextDidChange notification is posted for any text change in any UITextView.

Related

Custom UIMenuController using button event error There can only be one UIMenuController instance

I create a button that when you hold, it will show the standard ios menu button for pasting a text, but I'm getting an error saying There can only be one UIMenuController instance. when I hold the button 2 times, how can I fix this?
Here is my code
override init(frame: CGRect) {
super.init(frame: frame)
self.configureView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.configureView()
}
private func configureView() {
guard let view = self.loadViewFromNib(nibName: "CustomView") else { return }
view.frame = self.bounds
self.addSubview(view)
button.addTarget(self, action: #selector(holdButton), for: .touchDown)
}
#objc func holdButton(_ sender: UIButton) {
let menuController = UIMenuController()
menuController.setTargetRect(sender.frame, in: charTextField)
menuController.setMenuVisible(true, animated: true)
}
Also, how can I listen to the user when he clicked the paste button?
I want it to call this function when he clicked paste.
func pasteClick() {
print("pasted", clipboardString())
}
Use the default singleton instance provided (.shared) by UIMenuController instead of creating an instance of your own.
#objc func holdButton(_ sender: UIButton) {
UIMenuController.shared.setTargetRect(sender.frame, in: charTextField)
UIMenuController.shared.setMenuVisible(true, animated: true)
}
Quoting from apple doc:
The singleton UIMenuController instance is referred to as the editing
menu.....

Custom UIControl element with global state for all instances

I have drawer controller presenting menu in the iOS app.
This menu is toggled by pressing menu buttons (UIButton) available on each screen.
As you can see in the mock: menu buttons can have red dot showing that new content is available - for this case I simply have two images for menu button without dot and with it.
I thought about making custom UIControl with "global" property for this dot. Is it the right way?
class MenuButton : UIButton {
static var showNotificationDot : Bool = false
}
For example you could create subclass UIButton and add observer.
class MyButton: UIButton {
static let notificationKey = NSNotification.Name(rawValue: "MyButtonNotificationKey")
override init(frame: CGRect) {
super.init(frame: frame)
self.subcribeForChangingState()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fileprivate func subcribeForChangingState() {
NotificationCenter.default.addObserver(forName: MyButton.notificationKey, object: nil, queue: nil) { notificaton in
if let state = notificaton.object as? Bool {
self.changeState(active: state)
}
}
}
fileprivate func changeState(active: Bool) {
//change ui of all instances
print(active)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
And change UI from any place like this:
NotificationCenter.default.post(name: MyButton.notificationKey, object: true)

How to make link, phone number clickable (Same behaviour as in textview) in a custom view where i draw text?

I made a view and draw text on it and i want that if any text contains link(Hyperlink) or Phone Number It would be clickable (Same Behaviour As in Text View) So how to Achieve it ?
Code For View In which i am Drawing Text :-
class DrawRectCellView: UIView {
var text: NSAttributedString?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Only override drawRect: if you perform custom drawing.
override func draw(_ rect: CGRect)
{
UIColor.white.setFill()
UIGraphicsGetCurrentContext()?.fill(rect)
// Drawing code
if let attributedText = text {
attributedText.draw(in: rect)
}
}
}
Code For TableCell :-
class DrawRectCell: UITableViewCell {
var cellView: DrawRectCellView?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Initialization code
cellView = DrawRectCellView(frame: self.frame)
if let cell = cellView {
cell.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
cell.contentMode = UIViewContentMode.redraw
}
self.contentView.addSubview(cellView!)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTextString(_ text: NSAttributedString) {
if let view = cellView {
view.text = text
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
i am Setting Text like = www.google.com or any phone number its showing as normal text only (Not Showing Like In textview (it makes it clickable))
First you need to detect your text contain url or numbers like this way.
let input = "This is a test with the URL https://www.hackingwithswift.com to be detected."
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let url = input[range]
print(url)
}
if you detect url after setting to the textview you need to add UITapGestureRecognizer on UITextView like this way.
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap")
textview.addGestureRecognizer(tapGesture)
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .began {
//write your code here for url tap
}
}
You can add UITapGestureRecognizer on DrawRectCellView and set the IndexPath as tag. In the selector method of UITapGestureRecognizer you can get the subviews info of the tapped contentView containing text.
To highlight certain text within the TextView:
Let's say you have your textView textView, then you can use this code to highlight URLs, phone numbers, etc.:
textView.dataDetectorTypes = [] // doesn't work if textfield isEditable is set to true
textView.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.blue] // the default is blue anyway
If you want to only include some specific data types to be detected, add them to the array like so:
textView.dataDetectorTypes = [.link]
Here's a list of types:
https://developer.apple.com/documentation/uikit/uidatadetectortypes
To make an entire view tappable:
You can add a UITapGestureRecognizer to the view (or not if it does not contain a phone number or hyperlink) like so:
https://stackoverflow.com/a/28675664/7270113
This answer may not be using the Swift version you are using. If so the compiler will tell you how to change it, so it will work.
If you don't like using Selectors, I recommend using the library Closures
https://github.com/vhesener/Closures, specifically with this https://vhesener.github.io/Closures/Extensions/UITapGestureRecognizer.html

iOS keyboard dismissal with multiple UIGestureRecognizers

In my main app view which contains both a UITextField and a UITableView, I have the "usual" code using a UITapGestureRecognizer to dismiss the virtual keyboard if a tap is detected outside of the keyboard while I'm editing the contents of the UITextField.
An added feature is that this is only enabled if the virtual keyboard is actually shown - I don't want "background taps" to cause editing to end if the virtual keyboard isn't visible, but nor do I want background taps to trigger their normal behaviour - they should be consumed if the virtual keyboard is currently showing.
override func viewDidLoad() {
...
tapper = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
view.addGestureRecognizer(tapper!)
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
view.removeGestureRecognizer(tapper!)
}
#IBAction func viewTapped(_ sender: AnyObject) {
view.endEditing(false)
}
This mostly works, except that the UITableView has interactive header cells which each also have a UITapGestureRecognizer attached.
The net result is that if I click on a header cell the gesture recognizer on that cell gets fired, and not the one on the parent view, and the keyboard doesn't get dismissed. If I click on the data cells instead, everything works fine.
If it matters, my UITableView has its own UIViewController subclass and is contained in a nested UIView - the table is too complicated to have that code in my main view controller.
How can I prevent the sub-view's gesture recognizers from handling these taps when the parent view's recognizer is attached so that the parent view can handle them instead?
I've implemented what I consider a "temporary" solution by also observing the virtual keyboard notifications in the UITableView's controller, tracking the keyboard visibility state, and then implementing this UIGestureRecognizerDelegate method on the header cells' gesture recognizer:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return !keyboardShowing
}
This duplicates a certain amount of functionality from the main view in the sub-view which really shouldn't need to know about the state of the keyboard. I'm still looking for a method that can be handled entirely from within the parent view.
EDIT - with thanks to #Tommy for the hint, I now have a better solution that removes any need to track the keyboard state in the sub-view.
My parent view no longer uses a UIGestureRecognizer, but instead uses a custom subclass of UIView to track touch events, and conditionally ignore them:
class KeyboardDismissingView: UIView {
private var keyboardVisible = false
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let r = super.hitTest(point, with: event) else { return nil }
var v : UIView! = r
while v != nil {
if v is UITextField {
return r
}
v = v.superview
}
if keyboardVisible {
self.endEditing(false)
return nil
}
return r
}
func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
keyboardVisible = true
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
keyboardVisible = false
}
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
}

Custom UIView with tapGesture

I have a drawing app. Inside my VC there are five imageViews with five colors in them. I want to be able to click on the imageView and change the stroke color. It can be easily done if I repeat myself in the viewcontroller by adding gesture Recognizers to each UIImageView and have their individual "selector" function. Such as
func redTapped() {}
func blueTapped() {}
However, I want to be able to make the code more clear by creating a custom class (ColorImageView.Swift) for these ImageViews so that when I assign the class to these buttons, they automatically gets the tap gesture and my VC automatically receives the information about which one is tapped. At the moment, I can get a "imagePressed" printed out for each image that gets assigned to my class. However, I have no way of distinguishing which one were pressed. Below are my code for ColorImageView.Swift
import Foundation
class ColorImageView: UIImageView {
private func initialize() {
let touchGesture = UITapGestureRecognizer(target: self, action: #selector(ColorImageView.imagePressed(_:)))
touchGesture.numberOfTapsRequired = 1
self.userInteractionEnabled = true
self.addGestureRecognizer(touchGesture)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func imagePressed(gestureRecognizer: UITapGestureRecognizer) {
print("image pressed \(gestureRecognizer)")
}
}
My imageView names are red.png, green.png, blue.png...etc
Thanks
You can get the tag easily.It works fine.
func imagePressed(gestureRecognizer: UITapGestureRecognizer)
{
print("image pressed \(gestureRecognizer)")
let tappedImageVIew = gestureRecognizer.view as! UIImageView
print("image pressed \(tappedImageVIew.tag)")
}

Resources