NSMutableAttributedString to UITextField in Swift - ios

I need to make extra characters in textfield red, so I used NSMutableAttributedString, could anyone please show me how can I transfer a NSMutableAttributedString to UITextField
#IBOutlet weak var inputLimitField: UITextField!
#IBOutlet weak var characterCountLabel: UILabel!
private let allowedChars = 10
override func viewDidLoad() {
super.viewDidLoad()
characterCountLabel.text = "10/10"
inputLimitField.delegate = self
}
func checkRemainingChars() {
let charsInTextView = -(inputLimitField.text?.count ?? 0)
let remainingChars = allowedChars + charsInTextView
characterCountLabel.textColor = .black
inputLimitField.textColor = .black
inputLimitField.layer.borderColor = UIColor.systemGray6.cgColor
if remainingChars < 0 {
getColoredText(text: inputLimitField.text ?? "") // how to implement this NSMutableAttributedString in inputLimitField.text
characterCountLabel.textColor = .red
inputLimitField.layer.borderColor = UIColor.red.cgColor
inputLimitField.layer.cornerRadius = 6.0
inputLimitField.layer.borderWidth = 1.0
}
characterCountLabel.text = ("\(String(remainingChars))/10")
}
func textFieldDidChangeSelection(_ textField: UITextField) {
checkRemainingChars()
}
func getColoredText(text:String) -> NSMutableAttributedString{
let string:NSMutableAttributedString = NSMutableAttributedString(string: text)
string.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: allowedChars, length: string.length))
return string
}

getColoredText returns the attributed string. Just assign it to the attributedText property of the text field
inputLimitField.attributedText = getColoredText(text: inputLimitField.text!)
Note: the text property of UITextField is never nil. You can forced unwrap it.

Related

Tap gesture on custom Marker View using iOS-Charts

I've created a custom marker view that loads from xib. I want to detect tap on the marker so that I can perform certain action on my VC. How can I do this?
PerformanceHomeViewController:
#IBOutlet weak var segmentBarChartView: SegmentBarChartView!
override func viewDidLoad() {
super.viewDidLoad()
self.segmentBarChartView.setupMarkerView(customText: "%# of 30 Agents")
}
SegmentBarChartView.swift
func setupMarkerView(customText: String) {
let customMarker: CustomMarker = (CustomMarker.viewFromXib() as? CustomMarker)!
customMarker.backgroundColor = .black
customMarker.setup(font: AIAMetrics.barChartTitleFont, textColor: .white, customString: customText)
customMarker.chartView = chartView
chartView.marker = customMarker
}
CustomMarkerView
#IBOutlet weak var textLabel: UILabel!
open var font: UIFont = .systemFont(ofSize: 12)
open var textColor: UIColor = .white
open var customString: String = ""
override open func awakeFromNib() {
self.offset.x = -self.frame.size.width / 2.0
self.offset.y = -self.frame.size.height - 7.0
}
public func setup(font: UIFont, textColor: UIColor, customString: String)
{
self.font = font
self.textColor = textColor
self.customString = customString
}
open override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
let labelString = String(format: customString, String(format: "%.0f", entry.y))
textLabel.text = labelString
layoutIfNeeded()
setLabel(labelString)
layoutIfNeeded()
}
open func setLabel(_ newLabel: String)
{
var size = CGSize()
size.width = textLabel.frame.size.width + 25 + 27
size.height = textLabel.frame.size.height + 20
size.height = max(size.height, 52)
self.frame.size = size
}
SegmentBarChartView is an xib that contains the Bar chart.
Can Anyone provide code example on what I should do so that I can detect tap on the marker view and perform some action on VC?

TextView Counter On Typing And Count New Line Swift IOS

I want to count newlines ("\n") in a textview.
Or, more specifically, I want to make textview box to count the characters when typing ( +1 ) in label. Also count ( +2 ) when the text have new line and counter ( Label ) must be continuous.
Here is my code:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let allowedChars = 70
let charsInTextView = -txtmessage.text.count
let remainingChars = allowedChars + charsInTextView
if (text == "\n") {
let remainingChars = 70 - (txtmessage.text.count * 2 )
countlabel.text = String(remainingChars)
}
if (text != "\n"){
countlabel.text = String(remainingChars)
}
return true
}
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let textView = UITextView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100)))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
textView.delegate = self
textView.backgroundColor = .white
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
guard let text = textView.text else {
return
}
let totalLength = text.count
let newlineCount = text.filter {$0 == "\n"}.count
print("Total characters are \(totalLength) of which \(newlineCount) are newLines total of all characters counting newlines twice is \(totalLength + newlineCount)")
}
}
let v = ViewController()
v.preferredContentSize = CGSize(width: 1024, height: 768)
PlaygroundPage.current.liveView = v

Enable a login button only if all text fields have verified success

I want to enable login button once all the condition satisfied for text field. I have added editchange event to two text fields through starboard.By default i have adding one label to text field with red background color to indicate(wrong) once user enters correct values in textfield then i need to change label color to green color. It is not working as except.
My entire code:
import UIKit
import IQKeyboardManagerSwift
class SignInFormViewController: UIViewController , UITextFieldDelegate {
#IBOutlet weak var forgotPasswordButton: UIButton!
#IBOutlet weak var signInButton: UIButton!
// Existing User
#IBOutlet weak var existingEmailAddressTextField: UITextField!
#IBOutlet weak var existingPasswordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//Setting text fileds effect
self.makeTextFiledEffect(textField: existingEmailAddressTextField, backColor: UIColor.red, borderColor: UIColor.lightGray)
self.makeTextFiledEffect(textField: existingPasswordTextField, backColor: UIColor.red, borderColor: UIColor.lightGray)
//Disable sing button by default
signInButton.isEnabled = false
}
func makeTextFiledEffect(textField : UITextField, backColor : UIColor, borderColor : UIColor) {
for viewPrevious in textField.subviews{
if viewPrevious.tag == 1000{
viewPrevious.removeFromSuperview()
}
}
let arrowView = UILabel(frame: CGRect(x:0, y: 0, width: 5, height: textField.frame.size.height))
arrowView.tag = 1000
arrowView.backgroundColor = backColor
textField.changeDynamicBorderColor(borderColor: borderColor)
textField.addSubview(arrowView)
}
#IBAction func validateTextField(_ sender: UITextField) {
if sender.text?.count == 1 {
if sender.text?.first == " " {
sender.text = ""
return
}
}
guard
let emaiTextField = existingEmailAddressTextField.text, !emaiTextField.isEmpty && self.validateEmail(emaiTextField)==true ,
let password = existingPasswordTextField.text, !password.isEmpty
else {
self.makeTextFiledEffect(textField: sender, backColor: UIColor.red, borderColor: UIColor.lightGray)
self.signInButton.isEnabled = false
return
}
self.makeTextFiledEffect(textField: sender, backColor: UIColor.green, borderColor: UIColor.lightGray)
signInButton.isEnabled = true
}
#IBAction func signInButtonTapped(_ sender: UIButton) {
print("Tapped")
}
func validateEmail(_ candidate: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
return NSPredicate(format: "SELF MATCHES %#", emailRegex).evaluate(with: candidate)
}
}
extension UITextField
{
func changeDynamicBorderColor(borderColor : UIColor){
self.layer.borderColor = borderColor.cgColor
}
open override func draw(_ rect: CGRect) {
self.layer.cornerRadius = 10.0
self.layer.borderWidth = 1.0
// self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.masksToBounds = true
}
}
You can find a similar question : Enable a button in Swift only if all text fields have been filled out
Thanks in advance..
Follow the below basic steps to achieve your goal:-
Create a function which will perform all the validations(UI Level or API level or any validation). If any validation fails, it will return false, otherwise return true
loginButton.isEnabled = returned value of above function
Try this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
self.validateField()
return true
}
// MARK: - Validations
func validateField() -> Bool
{
if (self.isValidEmail(testStr: txtEmail.text!) && strPassword.characters.count > 0 )
{
btn.isUserInteractionEnabled = .true
btn.backgroundColor = .green
return true
}
else
{
btn.isUserInteractionEnabled = .false
btn.backgroundColor = .red
return false
}
}
func isValidEmail(testStr:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
if (emailTest.evaluate(with: testStr) == false)
{
print("Invalid Email")
return false
}
else
{
return true
}
}

How to set up a tap gesture only for specific ranges of a UILabel in Swift 3

I have an attributed string set to UILabel with multiple underlines , colors like below image
and I know How to setup a tap gesture for whole label (with enabling user interaction) and below is my code for what I have done including setting up underline and setting up font colors for multiple ranges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mylabel: UILabel!
var theString = "I have agree with the terms and conditions and privacy policy"
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = theString
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.printme))
mylabel.addGestureRecognizer(tap)
setUnderline(theText: theString)
// Do any additional setup after loading the view, typically from a nib.
}
func printme() {
print("print this")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setUnderline(theText : String) {
//set up underline
let textRange1 = NSMakeRange(22, 19)
let textRange2 = NSMakeRange(47, (theText.characters.count-47))
let attributedText = NSMutableAttributedString(string : theText)
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: textRange1)
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: textRange2)
//setup colors
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSRange(location: 22,length: 20))
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSRange(location: 47,length: (theText.characters.count-47)))
mylabel.attributedText = attributedText
}
The tap gesture work for whole label. what I want is when user tap on "terms and conditions" fire a different function and and when user tap on "privacy policy" fire another different function. how can I do that.
Note : I want to fire two different functions one for "terms and conditions" tap, and other for "privacy policy" tap, and do not want to just
open links
import UIKit
protocol SSGastureDelegate:NSObjectProtocol {
func callBack()
}
class SSUnderLineLabel: UILabel {
var tapGesture: UITapGestureRecognizer?
//Make weak refernece for SSGastureDelegate
weak var delegate:SSGastureDelegate?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.initialization()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialization()
}
func initialization(){
let newsString: NSMutableAttributedString = NSMutableAttributedString(string: self.text!)
let textRange = NSString(string: self.text!)
let substringRange = textRange.range(of: "Terms and Conditions") // You can add here for own specific under line substring
newsString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: substringRange)
// self.attributedText = newsString.copy() as? NSAttributedString
self.attributedText = newsString
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapResponse))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapGesture!)
}
func tapResponse(recognizer: UITapGestureRecognizer) {
let text = (self.text)!
let termsRange = (text as NSString).range(of: "Terms and Conditions")
if (tapGesture?.didTapAttributedTextInLabel(label: self, inRange: termsRange))! {
print("Tapped terms conditions")
self.delegate?.callBack()
}
else {
print("Tapped none ")
}
}
}
//MARK:UITapGestureRecognizer Extension
//MARK:
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,y:(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x:locationOfTouchInLabel.x - textContainerOffset.x,
y:locationOfTouchInLabel.y - textContainerOffset.y);
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}*`enter code here`
**How to use it:**
Assign this class for your label and follow these steps:
Demo for use class:
ViewController.swift
//===============
class ViewController: UIViewController,SSGastureDelegate {
#IBOutlet var underlineLbl: SSUnderLineLabel!
override func viewDidLoad() {
super.viewDidLoad()
self.underlineLbl.delegate = self
}
//Implement Delegate Method
func callBack()
{
//Open a specific vc for underline tap are`enter code here`a.
let termsconditionsVC = storyboard?.instantiateViewController(withIdentifier: "TermsConditionsVC") as! TermsConditionsVC
self.present(termsconditions, animated: true, completion: nil)
}
}*
I see that you need your terms and privacy links to be clickable, the simple way the ios gives is AttributedString, use following code for that:
let theString = "I have agree with the terms and conditions and privacy policy"
let someAttributedString = NSMutableAttributedString(string: theString)
someAttributedString.addAttribute(NSLinkAttributeName, value: "http://www.google.com", range: NSMakeRange(22, 20))
titleLabel.attributedText = someAttributedString
TTTAttributedLabel could be your best solution: https://github.com/TTTAttributedLabel/TTTAttributedLabel
TTTAttributedLabel has following delegate
- (void)attributedLabel:(__unused TTTAttributedLabel *)label
didSelectLinkWithURL:(NSURL *)url
{
//do whatever you want
}
Also if you need solution from tap gesture you can find the point on gesture tap and do you calculation to check where it is tapped to perform your action by gestureRecognizer.locationInView method

How to subclass a UILabel in swift with letter spacing set to 1.5 using NSKernAttribute

I am trying to subclass a UILabel so that it has a default kerning set to 1.5
then i am going to use that in my app with multiple labels. goal is to have default kern set out of the box so i can avoid repeated code all over the place also labels are set as mix of attributed and regular text
Example:
#IBoutlet weak var myLabel: CustomeLabelWithKern!
myLabel.attributedText = myAttributedText
#IBOutlet weak var myOtherLabelInADifferentViewController: CustomeLabelWithKern!
myOtherLabelInADifferentViewController.text "Foo Bar"
both of these label should have kern of 1.5
here is what i have so far
class CustomLabel: UILabel {
var kerning: CGFloat = 1.5
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setKerning(kerning)
}
private func setKerning(kern: CGFloat) {
guard let text = self.text else { return }
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
Here is what i have so far i think i will use this solution for now if anyone comes up with a better one i will be happy to try that as well
class CustomLabel: UILabel {
static var kerning: CGFloat = 1.5
override func awakeFromNib() {
super.awakeFromNib()
setKerning(CustomLabel.kerning)
}
func setKerning(kern: CGFloat) {
let text = self.text ?? ""
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
I can use it like this in my viewController
mylabel.text = "Hello World!" // this should be set to 1.5 by default but what if i am setting my label dynamically?
mylabel.setKerning(1.5) // Here i am passing the value so if the label is set dynamically set it will have correct spacing
// This also works if some of my labels have attributed text
myAttibutedLabel.attributedText = myAttributedText
myAttributedLabel.setKerning(1.5)
I think a this can be reduce to just an extension on UILabel class
like so
extension UILabel {
func setKerning(kern: CGFloat) {
let text = self.text ?? ""
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
How about subclassing UILabel like this:
class TestKerningLabel: UILabel {
func addKerning(kerningValue: Double) {
let attributedString = self.attributedText as! NSMutableAttributedString
attributedString.addAttribute(NSKernAttributeName, value: kerningValue, range: NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
And then use this in your VC:
let testLabel = TestKerningLabel()
testLabel.attributedText = NSAttributedString(string: "test")
testLabel.addKerning(kerningValue: 1.5)
view.addSubview(testLabel)

Resources