why do interactive parts of UITextview require a long press? - swift

I need to make part of a text interactive, this is a screenshot of what I made so far, which is good, but the problems are
1- it requires a long tap
2- and force me to have the text selectable
any idea about how to solve this?
import UIKit
class ViewController: UIViewController, UITextViewDelegate
{
#IBOutlet weak var textView: UITextView!
override func viewDidLoad()
{
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "text with a link", attributes: nil)
attributedString.setSubstringAsLink(substring: "link", linkURL: "CUSTOM://WHATEVER")
let linkAttributes: [String : AnyObject] = [NSForegroundColorAttributeName : UIColor.redColor(), NSUnderlineColorAttributeName : UIColor.redColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue]
textView.linkTextAttributes = linkAttributes
textView.attributedText = attributedString
textView.selectable = true
textView.editable = false
textView.userInteractionEnabled = true
textView.delegate = self
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool
{
if URL == "CUSTOM://WHATEVER"
{
print("????")
}
else
{
print("!!!!")
}
return true
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NSMutableAttributedString
{
public func setSubstringAsLink(substring substring: String, linkURL: String) -> Bool
{
let range = self.mutableString.rangeOfString(substring)
if range.location != NSNotFound
{
self.addAttribute(NSLinkAttributeName, value: linkURL, range: range)
return true
}
return false
}
}

I don't know how to solve your specific issues but I guess you are better of subclassing UILabel instead of UITextView. Also, try to not re-invent the wheel, use TTAttributedLabel or similar libraries to do what you want.

I resolved this problem like below,
let attributedString = NSMutableAttributedString(string: "Press this link text")
attributedString.addAttribute(NSLinkAttributeName, value: "http://www.google.com", range: NSRange(location: 6, length: 14))
let linkAttributes = [
NSForegroundColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
agreementsTextView.delegate = self
agreementsTextView.attributedText = attributedString
agreementsTextView.linkTextAttributes = linkAttributes
// Delegate Method
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
if URL.absoluteString == "http://www.google.com" || URL.relativePath == "http://www.google.com" {
print("Tapped")
}
return false
}
Range might be wrong, this should fix your problem

Related

How do I underline characters in the comments using Swift

For example, I'm writing a function which I want to show an add method. I wanted to underline the comment above like so.
// This is the comment I want to underline
// Adding a few other things you can do with comments would also be helpful
func helpMeUnderlineThisComment(comment: String) -> String {
for char in comment {
if char == comment {
return char.underlined()
}
}
return nil
}
You can simply do it by making an extension:
extension NSAttributedString {
var underlined: NSAttributedString {
return applying(attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue])
}
func applying(attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {
let copy = NSMutableAttributedString(attributedString: self)
let range = (string as NSString).range(of: string)
copy.addAttributes(attributes, range: range)
return copy
}
}
How to use it?
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.textLabel.text = "Some Text"
self.textLabel.attributedText = textLabel.attributedText?.underlined
}}

Is it possible to add kerning to a TextField in SwiftUI?

To match a Styleguide I have to add kerning to a textfield, both placeholder and value itself.
With UIKit I was able to do so with:
class MyTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
// ...
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
//...
NSAttributedString.Key.kern: 0.3
])
self.attributedText = NSAttributedString(string: "", attributes: [
// ...
NSAttributedString.Key.kern: 0.3
])
}
}
In SwiftUI, I could figure out that I could apply a kerning effect to a Text element like so:
Text("My label with kerning")
.kerning(0.7)
Unfortunately, I could not find a way to apply a kerning style to neither a TextField's value nor placeholder. Any ideas on this one? Thanks in advance
There is a simple tutorial on HackingwithSwift that shows how to implement a UITextView. It can easily be adapted for UITextField.
Here is a quick example showing how to use UIViewRepresentable for you UITextField. Setting the kerning on both the text and the placeholder.
struct ContentView: View {
#State var text = ""
var body: some View {
MyTextField(text: $text, placeholder: "Placeholder")
}
}
struct MyTextField: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: Context) -> UITextField {
return UITextField()
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.attributedPlaceholder = NSAttributedString(string: self.placeholder, attributes: [
NSAttributedString.Key.kern: 0.3
])
uiView.attributedText = NSAttributedString(string: self.text, attributes: [
NSAttributedString.Key.kern: 0.3
])
}
}
Update
The above doesn't work for setting the kerning on the attributedText. Borrowing from the fantastic work done by Costantino Pistagna in his medium article we need to do a little more work.
Firstly we need to create a wrapped version of the UITextField that allows us access to the delegate methods.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
print(proposedValue)
self.attributedText = NSAttributedString(string: currentValue as String, attributes: [
NSAttributedString.Key.kern: 10
])
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
As the shouldChangeCharactersIn delegate method will get called every time the text changes, we should use that to update the attributedText value. I tried using first the proposedVale but it would double up the characters, it works as expected if we use the currentValue
Now we can use the WrappedTextField in the UIViewRepresentable.
struct SATextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
func makeUIView(context: UIViewRepresentableContext<SATextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
NSAttributedString.Key.kern: 10
])
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<SATextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
We set the attributed text for the placeholder in the makeUIView. The placeholder text is not being updated so we don't need to worry about changing that.
And here is how we use it:
struct ContentView: View {
#State var text = ""
var body: some View {
SATextField(tag: 0, placeholder: "Placeholder", changeHandler: { (newText) in
self.text = newText
}) {
// do something when the editing of this textfield ends
}
}
}

Custom Attributed string action in swift

I want to make the attributed string action in swift. I want append my custom variable to table view cell data.
I added colour and line also for that string, now i want to add the link for that added attributed string.
Please find my code with the following.
I add this code into cell for row at Index path.
let value = NSMutableAttributedString(string: " tap here.", attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!]).withTextColor(UIColor.disSatifyclr).withUnderlineColor(UIColor.disSatifyclr).withUnderlineStyle(.styleSingle)
if (cell.desclbl.text?.contains("more about donating, "))! {
let description = descriptions[indexPath.section][indexPath.row]
let attributedString = NSMutableAttributedString(string: description)
attributedString.append(value)
cell.desclbl.attributedText = attributedString
}
Here you can handle action using UITextView instead of UILabel on tap of string
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.attributedText = prepareLink()
textView.tintColor = UIColor.black
textView.isSelectable = true
textView.isEditable = false
textView.delegate = self
}
func prepareLink() -> NSMutableAttributedString {
let formattedString = NSMutableAttributedString(string: " tap here ")
let linkAttribute = [
NSAttributedString.Key.foregroundColor: UIColor.green,
NSAttributedString.Key.underlineColor: UIColor.green,
NSAttributedString.Key.underlineStyle: 1,
NSAttributedString.Key.link: URL(string: "http://www.google.com")!
] as [NSAttributedString.Key : Any]
formattedString.addAttributes(linkAttribute, range: NSMakeRange(0, formattedString.length))
return formattedString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if (URL.absoluteString == "http://www.google.com") {
// Do Something
}
return true
}
}
Note: If you have to continue with UILabel then you have to implement UITapGesture to handle action.

Simple clickable link in Cocoa and Swift

Im writing a desktop app in swift for mac 10.11 and I would like to add a link to the about page.
Very much like this: https://developer.apple.com/library/mac/qa/qa1487/_index.html
I haven't been able to find a good tutorial or reference.
Any help would be much appreciated
Swift 4, xCode 9
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func resetCursorRects() {
discardCursorRects()
addCursorRect(self.bounds, cursor: NSCursor.pointingHand)
}
override func awakeFromNib() {
super.awakeFromNib()
// TODO: Fix this and get the hover click to work.
let attributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.foregroundColor: NSColor.linkColor,
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with theEvent: NSEvent) {
if let localHref = URL(string: href) {
NSWorkspace.shared.open(localHref)
}
}
}
Modified the existing answers to allow for a substring of the label's text to become underlined and blue, so you can do something like: This is the answer
// A text field that can contain a hyperlink within a range of characters in the text.
#IBDesignable
public class SubstringLinkedTextField: NSTextField {
// the URL that will be opened when the link is clicked.
public var link: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'link' instead.")
#IBInspectable public var HREF: String {
get {
return self.link
}
set {
self.link = newValue
self.needsDisplay = true
}
}
// the substring within the field's text that will become an underlined link. if empty or no match found, the entire text will become the link.
public var linkText: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'linkText' instead.")
#IBInspectable public var LinkText: String {
get {
return self.linkText
}
set {
self.linkText = newValue
self.needsDisplay = true
}
}
override public func awakeFromNib() {
super.awakeFromNib()
self.allowsEditingTextAttributes = true
self.isSelectable = true
let url = URL(string: self.link)
let attributes: [NSAttributedStringKey: AnyObject] = [
NSAttributedStringKey(rawValue: NSAttributedStringKey.link.rawValue): url as AnyObject
]
let attributedStr = NSMutableAttributedString(string: self.stringValue)
if self.linkText.count > 0 {
if let range = self.stringValue.indexOf(substring: self.linkText) {
attributedStr.setAttributes(attributes, range: range)
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
self.attributedStringValue = attributedStr
}
}
The easiest way is to subclass NSTextField to create a HyperlinkTextField. Below is an example:
First, let's add a HyperlinkTextField class to your project:
// HyperlinkTextField.swift
import Cocoa
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blueColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(theEvent: NSEvent) {
NSWorkspace.sharedWorkspace().openURL(NSURL(string: self.href)!)
}
}
Next, in Interface Builder, drag a label from the Object library to your window.
Select that label, go to the menu View > Utilities > Show Identity Inspector (or press Cmd + Opt + 3) and change the class to HyperlinkTextField
Go to the Attributes Inspector (Cmd + Opt + 4) and set Href to the URL you want to visit.
The label shows black text in Interface Builder but everything will be fine when you run your app. Clicking on the label will open the link in your default browser.
One thing I couldn't achieve was to make the HyperlinkTextField shows up as blue and underlined in Interface Builder. Comments on how to do that are welcome.
Exactly what I needed. Here is the Swift3 version:
import Cocoa
#IBDesignable
class HyperTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blue,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared().open(URL(string: self.href)!)
}
}
let link = NSTextField()
link.isBezeled = false
link.drawsBackground = false
link.isEditable = false
link.isSelectable = true
link.allowsEditingTextAttributes = true
let url = URL(string: "http://www.google.com")
let linkTextAttributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue,
NSAttributedStringKey.foregroundColor: NSColor.blue,
NSAttributedStringKey.link: url as Any
]
let string = "whatever"
link.attributedStringValue = NSAttributedString(string: string, attributes: linkTextAttributes)
window.contentView?.addSubview(link)
swift 4.2
class HyperlinkTextField: NSTextField {
private var detector: NSDataDetector!
override func awakeFromNib() {
super.awakeFromNib()
detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
}
func setText(text: String ){
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(.link, value: url, range: match.range)
self.attributedStringValue = attributedString
}
}
}

Avoid copying code for a lot of buttons

Edit on my question:
Grimxn, I made a subclass and can see it works, because of the borderWidth and color. But I still have a couple of questions on how to add my function:
Should I code "func textField(textField: UITextField" or "func textField(textField: MyCustomTextField" ?
What should I do with "if textField == numberField01 {" ?
How do I 'call this' from the ViewController code ?
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField01: UITextField!
#IBOutlet weak var numberField02: MyCustomTextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField01.delegate = self
numberField01.keyboardType = UIKeyboardType.NumberPad
numberField02.delegate = self
numberField02.keyboardType = UIKeyboardType.NumberPad
}
class MyCustomTextField: UITextField {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.redColor().CGColor
self.layer.borderWidth = 1.5
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
var result = true
var prospectiveText = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
prospectiveText = prospectiveText.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
if textField == numberField01 {
if count(string)>0 {
let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789").invertedSet
let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
let resultingStringLengthIsLegal = count(prospectiveText) <= 4
let scanner = NSScanner(string: prospectiveText)
let resultingTextIsNumeric = scanner.scanDecimal(nil) && scanner.atEnd
result = replacementStringIsLegal && resultingStringLengthIsLegal && resultingTextIsNumeric
}
}
return result
}
}
}
Original question:
The following code is working fine for one textfield (numberField01). It makes sure the input is decimal only, places a decimal point, and prevents a user to paste in a non decimal string. But I have a lot more buttons... (numberField02 and up). How can I handle more buttons, without just copying my code for each button?
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField01: UITextField!
#IBOutlet weak var numberField02: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField01.delegate = self
numberField01.keyboardType = UIKeyboardType.NumberPad
numberField02.delegate = self
numberField02.keyboardType = UIKeyboardType.NumberPad
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Tap background to add decimal point and defocus keyboard
#IBAction func userTappedBackground(sender: AnyObject) {
for view in self.view.subviews as! [UIView] {
if let textField = view as? UITextField {
if count(numberField01.text) > 0 {
var numberString = numberField01.text
numberString = numberString.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
var numberFromString = Double(numberString.toInt()!) / 100
numberField01.text = String(format:"%.2f", numberFromString)
}
textField.resignFirstResponder()
}
}
}
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
var result = true
var prospectiveText = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
prospectiveText = prospectiveText.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
if textField == numberField01 {
if count(string)>0 {
let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789").invertedSet
let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
let resultingStringLengthIsLegal = count(prospectiveText) <= 4
let scanner = NSScanner(string: prospectiveText)
let resultingTextIsNumeric = scanner.scanDecimal(nil) && scanner.atEnd
result = replacementStringIsLegal && resultingStringLengthIsLegal && resultingTextIsNumeric
}
}
return result
}
}
So something similar to the following:
Button GetButtonCommonFeatures(Button myButton)
{
Write common code here....
e.g. myButton.delegate = self;...
return myButton;
}
Then call your method for each button. Lets take numberField01 for example. You will include the code in the method that applies to every button.
numberField01 = GetButtonCommonFeatures(numberField01);
Hope this helps