Clickable link within NSMutableAttributedString needs to show another viewcontroller / storyboard - swift

I have a UITextView and NSMutableAttributedString which has a clickable link. I need this to show another storyboard but not quite sure how I could do this.
What I have so far works for external links but not a storyboard. Any help would be appreciated.
Not sure if the below code is the best way to approach it or not?
class HomeViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "Already have an account? Log in")
attributedString.addAttribute(.link, value: "", range: NSRange(location: 25, length: 6))
textView.attributedText = attributedString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
UIApplication.shared.open(URL)
return false
}
}
EDIT
After NikR answer I've updated my code to the following but still no success. It's still loading Google.
class HomeViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "Already have an account? Log in")
attributedString.addAttribute(.link, value: "https://google.com", range: NSRange(location: 25, length: 6))
textView.attributedText = attributedString
textView.isSelectable = true
textView.isEditable = false
textView.delegate = self
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
let loginViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "loginViewController")
show(loginViewController, sender: self)
return true
}
}

Yes it's the right way.
But not call:
UIApplication.shared.open(URL)
Simply initiate your VC and show/present it:
let vc = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateInitialViewController()
show( vc, sender: self )
And don't forget to:
textView.delegate = self

you can set to your textView:
textView.dataDetectorTypes = .link

You should enable isSelectable and disable isEditable for your text view.
textView.isSelectable = true
textView.isEditable = false
Don't forget to do this
textView.delegate = self
This Delegate method will use to handle click
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
}

Related

UITextView Right Alignment in swift

i am trying text enter in textview right to left alignment but at that time textview starting position enter space not taking textview in swift
#IBOutlet weak var textview: UITextView!
#IBOutlet weak var sampleTF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textview.textAlignment = .right
textview.isScrollEnabled = true
textview.heightAnchor.constraint(equalToConstant: 52.0).isActive = true
textview.delegate = self
}
#IBAction func sampleButton(_ sender: Any) {
}
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: textView.frame.width, height: 200)
let estimateSize = textView.sizeThatFits(size)
guard textView.contentSize.height < 100.0 else { textview.isScrollEnabled = true; return}
textview.isScrollEnabled = false
textview.constraints.forEach { (constriant) in
if constriant.firstAttribute == .height {
constriant.constant = estimateSize.height
}
}
}
As seen on other posts it has to do with how spaces are handled (). You can add the following delegate code to replace spaces with non breaking spaces :
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText string: String) -> Bool {
if (textView == self.textView) {
let oldString = textView.text!
let newStart = oldString.index(oldString.startIndex, offsetBy: range.location)
let newEnd = oldString.index(oldString.startIndex, offsetBy: range.location + range.length)
let newString = oldString.replacingCharacters(in: newStart..<newEnd, with: string)
textView.text = newString.replacingOccurrences(of: " ", with: "\u{00a0}")
return false;
} else {
return true;
}
}

TextView capitalizes first two characters instead of one

So I've added placeholder functionality to UITextView, and now it capitalizes the first two characters of input text instead of one as it's supposed to.
I would appreciate you guys' help in solving this problem if you can help in any way. I've been trying, but I seem to be getting nowhere.
Here's the code:
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
textView.text = "Type something"
textView.textColor = UIColor.rgb(197, 197, 199)
}
}
extension ViewController: UITextViewDelegate {
// Put cursor at the beginning of TextView when editing begins
func textViewDidBeginEditing(_ textView: UITextView) {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Sets placeholder for UITextView
let updatedText = (textView.text as NSString).replacingCharacters(in: range, with: text)
if updatedText.isEmpty {
textView.text = "Type something"
textView.textColor = UIColor.rgb(197, 197, 199)
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
} else if textView.textColor == UIColor.rgb(197, 197, 199) && !text.isEmpty {
textView.textColor = UIColor.black
textView.text = text
} else {
return true
}
return false
}
// Prevents selecting the placeholder
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == UIColor.rgb(197, 197, 199) {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
}
Thank you!
The code below solves capitalization of the first 2 letters in text view.
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Type something"
textView.textColor = self.placeholderTextColor
} else {
textView.textColor = .black
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text.isEmpty {
let updatedText = (textView.text as NSString).replacingCharacters(in: range, with: text)
if updatedText.isEmpty {
textView.text = "Type something"
textView.textColor = self.placeholderTextColor
textView.selectedRange = NSRange(location: 0, length: 0)
}
} else {
if textView.text == "Type something" {
textView.text = ""
}
textView.textColor = .black
}
return true
}
The reason why the text view capitalized first 2 characters in text view is that you've returned false in below delegate method
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool

How to hide the keyboard when user press return button?

I have a UITextView in my ViewController, When user press on that textView, the keyboard shows up. This is not the problem. My problem is: How to hide the keyboard when user press return button in the keyboard? I have tried some functions but apparently they worked only with UITextFields.
import UIKit
class NewNoteVC: UIViewController, UITextViewDelegate {
#IBOutlet weak var newText: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
newText.text = "Start typing"
newText.textColor = UIColor.lightGray
newText.tintColor = UIColor(red: 251/255, green: 140/255, blue: 139/255, alpha: 1)
}
/// setting placeholdere
func textViewDidBeginEditing(_ textView: UITextView) {
if newText.textColor == UIColor.lightGray {
newText.text = nil
newText.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if newText.text.isEmpty {
newText.text = "Placeholder"
newText.textColor = UIColor.lightGray
}
}
/// limit characters
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let currentText = newText.text ?? ""
guard let stringRange = Range(range, in: currentText) else { return false }
let changedText = currentText.replacingCharacters(in: stringRange, with: text)
return changedText.count <= 1000
}
/// hide keyboard when user touch outside screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
/// hide keyboard when user press return button
#IBAction func backToMainPage(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Whatever the reason might be to dismiss keyboard on return of a UITextView the simplest solution would be:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
textView.resignFirstResponder()
}
{...}
}

How to pass the variable from a ViewController Class to other Struct?

I have no idea how to pass the data from the class to another struct. This is my ViewController.swift file. And I have another file called Meme.swift which is used to save the struct. I tried to put the struct in ViewController.swift file as well as Meme.swift file but I cannot access the value like topTextField.text and lowTextField.text and use them in the struct. Can anyone help me with this?
ViewController:
import UIKit
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var cameraButton: UIBarButtonItem!
#IBOutlet weak var bottomTextField: UITextField!
#IBOutlet weak var topTextField: UITextField!
#IBOutlet weak var imagePickerView: UIImageView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var actionButton: UIBarButtonItem!
let bottomTextFieldDelegate = BottomTextFieldDelegate()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
topTextField.text = "TOP"
bottomTextField.text = "BOTTOM"
topTextField.delegate = self
bottomTextField.delegate = self.bottomTextFieldDelegate
}
func textFieldDidBeginEditing(_ textField: UITextField) {
topTextField.text = ""
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
textField.resignFirstResponder()
actionButton.isEnabled = true
return true
}
#IBAction func shareAction(_ sender: Any) {
let image = generateMemedImage()
let controller = UIActivityViewController(activityItems: [image as Any], applicationActivities: nil)
self.present(controller, animated: true, completion: nil)
controller.completionWithItemsHandler = {(activityType: UIActivityType?, completed:Bool, returnedItems:[Any]?, error: Error?) in
if !completed {
debugPrint("cancelled")
return
}else{
self.save()
self.dismiss(animated: true, completion: nil)
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
cameraButton.isEnabled = UIImagePickerController.isSourceTypeAvailable(.camera)
configureTextField(textField: topTextField)
configureTextField(textField: bottomTextField)
topTextField.textAlignment = .center
bottomTextField.textAlignment = .center
subscribeToKeyboardNotifications()
actionButton.isEnabled = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
unsubscribeFromKeyboardNotifications()
}
func configureTextField(textField: UITextField) {
textField.defaultTextAttributes = memeTextAttributes
}
func save() -> Meme {
// Create the meme
let memedImage = generateMemedImage()
let meme = Meme(topText: topTextField.text!, bottomText: bottomTextField.text!, originalImage: imageView.image!, memedImage: memedImage)
return meme
}
func generateMemedImage() -> UIImage {
// TODO: Hide toolbar and navbar
navigationController?.setToolbarHidden(true, animated: true)
self.navigationController?.isNavigationBarHidden = true
// Render view to an image
UIGraphicsBeginImageContext(self.view.frame.size)
view.drawHierarchy(in: self.view.frame, afterScreenUpdates: true)
let memedImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
// TODO: Show toolbar and navbar
navigationController?.setToolbarHidden(false, animated: false)
self.navigationController?.isNavigationBarHidden = false
return memedImage
}
func keyboardWillShow(_ notification:Notification) {
view.frame.origin.y = 0 - getKeyboardHeight(notification)
}
func keyboardWillHide(_ notification:Notification) {
view.frame.origin.y = 0
}
func getKeyboardHeight(_ notification:Notification) -> CGFloat { //getting the height of keyboard and use it for func keyboardWillShow to relocate
//the keyboard using keyboardWillShow function
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue // of CGRect
return keyboardSize.cgRectValue.height
}
func subscribeToKeyboardNotifications() { //setting up the obeserver to be notified when keyboard is shown or not, then execute keyboardWillShow function
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: .UIKeyboardWillHide, object: nil)
}
func unsubscribeFromKeyboardNotifications() { //unsubscribe the notification
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
}
//setting the arributes of the text
let memeTextAttributes:[String:Any] = [
NSStrokeColorAttributeName: UIColor.black,
NSForegroundColorAttributeName: UIColor.white,
NSFontAttributeName: UIFont(name: "HelveticaNeue-CondensedBlack", size: 40)!,
NSStrokeWidthAttributeName: 3,]
#IBAction func pickAnImageFromAlbum(_ sender: Any) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
}
#IBAction func pickAnImageFromCamera(_ sender: Any) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
imagePickerView.image = image
imagePickerView.contentMode = .scaleAspectFit
dismiss(animated: true, completion: nil)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController){
dismiss(animated: true, completion: nil)
}
}
BottomFieldDelegate:
import Foundation
import UIKit
class BottomTextFieldDelegate: NSObject, UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let viewController = ViewController()
textField.resignFirstResponder()
viewController.view.endEditing(true)
let actionButton = viewController.actionButton
actionButton?.isEnabled = true
return true
}
}
First u need to move this function like save() and generateMemedImage() into your ViewController class and then you can access topTextField.text and lowTextField.text
Modify your struct like below, and do not put nsobject in struct
struct Meme {
var topText: String
var bottomText: String
var memedImage: UIImage
var originalImage: UIImage
init(topText: String, bottomText: String, originalImage: UIImage, memedImage: UIImage) {
self.topText = topText
self.bottomText = bottomText
self.originalImage = originalImage
self.memedImage = memedImage
}
}
Remove save() from Meme struct and put that code in your Viewcontroller file.
Modify your function which return Meme struct object.
func save() -> Meme {
// Create the meme
let meme = Meme(topText: topTextField.text!, bottomText: bottomTextField.text!, originalImage: imageView.image!, memedImage: memedImage)
return meme
}
And you can store in Array like below.
let array:[Meme] = []
array.append(save())

dismiss keyboard with a uiTextView

I am sure this is not that difficult, but I am having trouble finding info on how to dismiss a keyboard with the return/done key using a textview, not a textfield. here is what I have tried so far(which works with a textfield.)
Thanks very much in advance for any help!
// PostTravelQuestion.swift
class PostTravelQuestion: UIViewController, UITextViewDelegate {
#IBAction func closepostpage(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBOutlet var postquestion: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
postquestion.delegate = self
}
self addDoneToolBarToKeyboard:self.textView
/*func textViewShouldEndEditing(textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}*/
/*override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
postquestion.resignFirstResponder()
self.view.endEditing(true)
}*/
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textViewShouldReturn(textView: UITextView!) -> Bool {
self.view.endEditing(true);
return true;
}
}
This works for me:
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
/* Updated for Swift 4 */
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
/* Older versions of Swift */
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
}
Add UITextViewDelegate to your class and then set your delegate for your textView or your textField in viewDidLoad. Should look something like this:
// in viewDidLoad
textField.delegate = self
textView.delegate = self
Swift 3
// hides text views
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
// hides text fields
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (string == "\n") {
textField.resignFirstResponder()
return false
}
return true
}
Swift 2.0
The below syntax has been tested for Swift 1.2 & Swift 2.0
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
Below code will dismissing the keyboard when click return/done key on UITextView.
In Swift 3.0
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
if(text == "\n")
{
view.endEditing(true)
return false
}
else
{
return true
}
}
In Swift 2.2
func textView(textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
if text == "\n"
{
view.endEditing(true)
return false
}
else
{
return true
}
}
Easiest and best way to do this using UITextView Extension.
Credit: http://www.swiftdevcenter.com/uitextview-dismiss-keyboard-swift/
Your ViewController Class
class ViewController: UIViewController {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// 1
self.myTextView.addDoneButton(title: "Done", target: self, selector: #selector(tapDone(sender:)))
}
// 2
#objc func tapDone(sender: Any) {
self.view.endEditing(true)
}
}
Add UITextView Extension
extension UITextView {
func addDoneButton(title: String, target: Any, selector: Selector) {
let toolBar = UIToolbar(frame: CGRect(x: 0.0,
y: 0.0,
width: UIScreen.main.bounds.size.width,
height: 44.0))//1
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)//2
let barButton = UIBarButtonItem(title: title, style: .plain, target: target, action: selector)//3
toolBar.setItems([flexible, barButton], animated: false)//4
self.inputAccessoryView = toolBar//5
}
}
For more detail: visit full documentation
to hide the keyboard touch on any part outside the textbox or textviews in swift 4 use this peace of code in the ViewController class:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
super.touchesBegan(touches, with event: event)
}
Regards
Building on the answers of others (kudos!), here is my minimalistic take on it:
import UIKit
extension UITextView {
func withDoneButton(toolBarHeight: CGFloat = 44) {
guard UIDevice.current.userInterfaceIdiom == .phone else {
print("Adding Done button to the keyboard makes sense only on iPhones")
return
}
let toolBar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: toolBarHeight))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(endEditing))
toolBar.setItems([flexibleSpace, doneButton], animated: false)
inputAccessoryView = toolBar
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
textView.withDoneButton()
}