Swift - custom default component with delegate - ios

I want to write a custom text view by inheriting default UITextView. My implementation uses some of the methods from the delegate of the original component:
class CustomTextView: UITextView {
fileprivate func applyStyles() {
self.layer.cornerRadius = 5
self.layer.borderColor = .black
self.layer.borderWidth = 5
self.clipsToBounds = true
}
override func awakeFromNib() {
super.awakeFromNib()
applyStyles()
delegate = self
}
}
extension CustomTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
print("aaaa")
}
func textViewDidEndEditing(_ textView: UITextView) {
print("bbbb")
}
}
Now I want to use this text view everywhere instead of the standard one, but in the current implementation, I lose the ability to set another delegate to the component without losing the delegate functions of my own.
I mean if I will create a component in other class like this:
let customView = CustomTextView()
customView.delegate = self
textViewDidBeginEditing and textViewDidEndEditing functions in CustomTextView will not be called. How can I get around this limitation? Thanks.

The trick is to override the delegate property, so that you can capture any value that is assigned and then call that delegate after your code is done. For this to work, your subclass will need to implement all of the UITextViewDelegate functions in order to pass the invocation on to the "real" delegate:
class CustomTextView: UITextView {
private weak var externalDelegate: UITextViewDelegate?
override var delegate: UITextViewDelegate? {
set {
self.externalDelegate = newValue
}
get {
return self.externalDelegate
}
}
fileprivate func applyStyles() {
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.black.cgColor
self.layer.borderWidth = 5
self.clipsToBounds = true
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
super.delegate = self
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = self
}
}
extension CustomTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
print("aaaa")
self.externalDelegate?.textViewDidBeginEditing?(textView)
}
func textViewDidEndEditing(_ textView: UITextView) {
print("bbbb")
self.externalDelegate?.textViewDidEndEditing?(textView)
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return self.externalDelegate?.textViewShouldEndEditing?(textView) ?? true
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
return self.externalDelegate?.textViewShouldEndEditing?(textView) ?? true
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return (self.externalDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text)) ?? true
}
func textViewDidChange(_ textView: UITextView) {
self.externalDelegate?.textViewDidChange?(textView)
}
func textViewDidChangeSelection(_ textView: UITextView) {
self.externalDelegate?.textViewDidChangeSelection?(textView)
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return self.externalDelegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? true
}
func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return self.externalDelegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? true
}
}

Despite the reason may be unclear behind the fact that you want the callback to be triggered both inside the custom textview class and the parentVc , but you can do
class CustomTextView: UITextView , UITextViewDelegate {
weak var parentVC:VCName?
func textViewDidBeginEditing(_ textView: UITextView) {
print("aaaa")
parentVC?.textViewDidBeginEditing(self)
}
let customView = CustomTextView()
customView.parentVC = self
or do the reverse which is to make the parentVc as the delegate and call the methods of textView subclass from it

Related

UITextView make isEditable true

I am trying to build a simple text editor using swiftUI and UITextView. In UITextView I am trying to use link detection which requires isEditable property to be false. But when I make isEditable false I am not able to make it true again and I am not able to type any text. No delegate method works when you make isEditable false.
Can anyone tell me how to make isEditable true again ?
UITextView, UIViewRepresentable
import SwiftUI
struct TextView: UIViewRepresentable {
#Binding var text: String
let textView = UITextView()
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
func textViewDidEndEditing(_ textView: UITextView) {
textView.isEditable = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
textView.delegate = context.coordinator
textView.dataDetectorTypes = .link
textView.becomeFirstResponder()
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
ContentView
import SwiftUI
struct ContentView: View {
#State private var text = "www.apple.com"
var body: some View {
TextView(text: $text)
}
}
Thank You!

UITextView Save data

I wanted to know how to save the data in a UITextView once the application is double tapped out/force quitted. Is there some code I can write on the textViewDidChange call that can automatically save the input? The following is my code:
class Coordinator : NSObject,UITextViewDelegate {
var parent : MultiTextField
init(parent1 : MultiTextField) {
parent = parent1
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.textColor = .black
}
func textViewDidChange(_ textView: UITextView) {
self.parent.obj.size = textView.contentSize.height
}
func textViewDidEndEditing(_ textView: UITextView) {
print("Editing is done")
}
You could save the content in userDefault in textViewDidChange ; and reload content from the userDefault when you load the view.

View coordinator/delegate disappear function?

Is there a function for the view coordinator/delegate for when the view disappears?
I'm trying to create a textview that "autosaves" notes people enter into core data. Basically it just waits 2 seconds since the last textViewDidChange and then saves the data.
It uses a simple Boolean variable for the "queue" to ensure that it won't try and save multiple times within those 2 seconds.
Here is the code for that:
struct BodyTextView: UIViewRepresentable {
#Environment(\.managedObjectContext) var managedObjectContext
#Binding var text: String
var note: Note
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator : NSObject, UITextViewDelegate {
var textView: BodyTextView
var saveQueued: Bool = false
init(_ textView: BodyTextView) {
self.textView = textView
}
func textViewDidChange(_ textView: UITextView) {
self.textView.text = textView.text
if (!saveQueued) {
saveQueued = true
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) {_ in
print("saving from textViewDidChange: " + textView.text)
self.textView.note.body = textView.text
saveContext(self.textView.managedObjectContext)
self.saveQueued = false
}
}
}
}
}
This works well so far.
My problem is that if the view disappears before the 2 seconds is up it doesn't save the data. I'm guessing it is because when the view is dismissed it cancels the timer within the struct.
I'd like it to also save when the BodyTextView disappears as well.
Something like this:
func textViewDidDissapear(_ textView: UITextView) {
print("saving from textViewDidDissapear: " + textView.text)
self.textView.note.body = textView.text
saveContext(self.textView.managedObjectContext)
}
How do I do this?
The life cycle methods for SwiftUI.View's are a modifier on the view itself. so If you want to know when the view disappears add an .onDisappear() modifier to it.

Recognise a text by tapping on it in a textField Swift

I am a newbie and want to make a custom class for a UITextView where I will be able to get the text by tapping on it within the UITextView. How to create Custom UITextView class?
Try this, I have created custom textview class and protocol that you need to confirm in your class and implement that method to get text of textview every time when you click textview.
class CustomTextView: UITextView {
weak var delegateCustomTV: CustomTextViewDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.textViewTapped))
self.addGestureRecognizer(tapGestureRecognizer)
}
#objc func textViewTapped() {
delegateCustomTV?.preparedText(text: self.text ?? "")
}
}
protocol CustomTextViewDelegate: class {
func preparedText(text: String)
}
use like i have used below,
class yourViewController: UIViewController, CustomTextViewDelegate {
#IBOutlet weak var textView: CustomTextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegateCustomTV = self
}
func preparedText(text: String) {
// You will get your text here when you click on textview
print(text)
}
}
Try this
override func viewDidLoad() {
super.viewDidLoad()
self.textView.delegate = self
}
extension YourViewController: UITextViewDelegate
{
//MARK:- TextView Editing begins Function
func textViewDidBeginEditing(_ textView: UITextView)
{
//TextView Editing Begin Function
}
// MARK:- TextView text replaced Function
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
//Character changed in textView
return true
}
// MARK:- TextView End Editing function
func textViewDidEndEditing(_ textView: UITextView)
{
//End editing of textView
}
}
if you want to get text every time you tap textview you have to add tap gesture
override func viewDidLoad() {
super.viewDidLoad()
self.textView.delegate = self
let tap = UITapGestureRecognizer(target: self, action: #selector(self.textViewTapped(_:)))
self.textView.addGestureRecognizer(tap)
}
#objc func textViewTapped(_ sender: UITapGestureRecognizer){
print(textView.text)
}

extending UITextView and triggering the delegate for onChange events

This code works and fires:
class ViewControllerDisplay: UIViewController, UITextViewDelegate {
//.....
let textView = MyUITextView(frame: CGRect(x:10, y:20, width:270, height:65))
textView.params["dataname"] = _dataname
textView.delegate = self
view.addSubview(textView)
func textViewDidChange(_ textView: UITextView) {
print("YAAAA" + textView.text);
}
however this does not fire
func textViewDidChange(_ textView: MyUITextView) {
print("YAAAA" + textView.text);
}
here is the MyUITextView code
import UIKit
class MyUITextView: UITextView{
var params: Dictionary<String, Any>
required init?(coder aDecoder: NSCoder) {
self.params = [:]
super.init(coder: aDecoder)
}
init(frame: CGRect) {
self.params = [:]
super.init(frame: frame, textContainer: nil)
}
}
so how do I extend the delegate to include MyUITextView in the delegate 'onChange'
The method textViewDidChange (textView: MyUITextView) was not called because in the protocol UITextViewDelegate that method is specified like func textViewDidChange ( textView: UITextView)
If you need to call specifically from the custom class MyUITextView, you need to write a protocol for this class, something like:
protocol MyUITextViewDelegate {
func myTextViewDidChange(_ textView: MyUITextView)
}
Add a field with type MyUITextViewDelegate? in class MyUITextView:
var myDelegate: MyUITextViewDelegate?
Initialize it for example in the viewDidLoad class of the ViewControllerDisplay class:
override func viewDidLoad() {
super.viewDidLoad()
...
textView.myDelegate = self
...
}
The class MyUITextView need to subscribe to the UITextViewDelegate protocol and implement the textViewDidChange (_ textView: UITextView) method in which our protocol is called from myDelegate if it exists:
class MyUITextView: UITextView, UITextViewDelegate {
...
func someInitMethod() {
delegate = self
}
...
func textViewDidChange(_ textView: UITextView) {
myDelegate?.myTextViewDidChange(self)
}
}
And finally, by signing the class ViewControllerDisplay on the protocol MyUITextViewDelegate and implement the necessary method:
class ViewControllerDisplay: UIViewController, MyUITextViewDelegate {
...
func textViewDidChange(_ textView: MyUITextView) {
print("YAAAA" + textView.text);
}
}
ok I figured it out by casting
func textViewDidChange(_ textView: UITextView) {
if let sender = textView as? MyUITextView {
let dataname = sender.params["dataname"] as? String ?? ""
print("MyUITextView: dataname" + dataname + " = " + sender.text!)
}
}

Resources