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

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
}
}

Related

How to Animate UIView on a line path without using UIBezierPath?

I have draw UIView say rectangle(player) and line, using context using draw(_ rect: CGRect) method.
I want to move the player on a line when clicking the play button.
I have searched lots of, but I found only for UIBezierpath but I want to use it with context
and I have tried some code name with playAnimation function.
class CanvasView : UIView{
// straight line
var lineStartPoint = CGPoint()
var lineEndpoint = CGPoint()
var lineStrokeList = [Lines]()
var lineDict : [String : Lines] = [:]
var lineActionDictionary : [String : [String : Lines]] = [:]
var linesPath : CGPath?
//player
var playerPoint : CGPoint?
var playerList = [Players]()
var playerDict : [Int : Players] = [:]
var selectedPlayerNumber : Int = 0
//action
var actionDict : [String : String] = ["1" : "1"]
var actionKeyIndex = String()
var actionList : [String] = [String]()
// common
var strokeWidth : CGFloat = 2.0
var currentColor = UIColor.black.cgColor
var imageSize : CGFloat = 30.0
private var selectedShape : ShapeType = .player
//MARK: Touches Began
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
switch selectedShape {
case .player:
playerPoint = touch.location(in: self)
playerDict.forEach { (key,value) in
if value.rect.contains(playerPoint ?? CGPoint()){
selectedPlayerNumber = Int(key)
self.playerDict.removeValue(forKey: selectedPlayerNumber)
return
}
}
setNeedsDisplay()
case .line:
let pos = touch.location(in: self)
lineStartPoint = pos
lineEndpoint = pos
setNeedsDisplay()
}
}
//MARK: Touches Moved
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let isfreeDrawSelected = isfreeDrawSelected
switch selectedShape {
case .player:
playerPoint = touch.location(in: self)
setNeedsDisplay()
case .line:
lineEndpoint = touch.location(in: self)
setNeedsDisplay()
}
}
//MARK: Touches Ended
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
switch selectedShape {
case .player:
if playerPoint != nil {
let point = CGPoint(x: playerPoint!.x, y: playerPoint!.y)
let x = playerPoint!.x - 15
let y = playerPoint!.y - 15
let playerRect = CGRect(x: x, y: y, width: imageSize, height: imageSize)
let playerstruct = Players(point: point,rect: playerRect)
playerList.append(playerstruct)
playerDict[selectedPlayerNumber] = playerstruct
playerPoint = nil
selectedPlayerNumber += 1
setNeedsDisplay()
}
case .line:
lineEndpoint = touch.location(in: self)
if !playerDict.isEmpty{
if lineActionDictionary[actionKeyIndex]?[String(selectedPlayerNumber)] != nil{
let alert = UIAlertController(title: "", message: "You can't add mutiple lines for action \(actionKeyIndex)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { alertAction in
debugPrint(self.lineActionDictionary)
}))
UIApplication.shared.windows
.last?.rootViewController?
.present(alert, animated: false, completion: nil)
}
else{
let line = Lines(startPoint: lineStartPoint, endPoint: lineEndpoint, color: currentColor, width: strokeWidth)
lineStrokeList.append(line)
lineDict[String(selectedPlayerNumber)] = line
if lineActionDictionary[actionKeyIndex] != nil {
lineActionDictionary[actionKeyIndex]!.updateValue(line, forKey: String(selectedPlayerNumber))
}else{
lineActionDictionary[actionKeyIndex] = lineDict
}
}
lineStartPoint = CGPoint()
lineEndpoint = CGPoint()
lineDict.removeAll()
setNeedsDisplay()
}
}
}
//MARK: Draw
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
if playerPoint != nil {
let x = (playerPoint?.x)! - 15
let y = (playerPoint?.y)! - 15
let player = CGRect(x: x, y: y, width: imageSize, height: imageSize)
context.addRect(player)
context.drawPath(using: .stroke)
linesPath = context.path
}
if !playerDict.isEmpty{
playerDict.forEach { (key,value) in
let x = value.point.x - 15
let y = value.point.y - 15
let player = CGRect(x: x, y: y, width: imageSize, height: imageSize)
let colorView = UIView(frame: player)
context.addRect(player)
context.drawPath(using: .stroke)
}
}
if lineStartPoint.x > 0 && lineEndpoint.y > 0{
context.setLineCap(.round)
context.setLineWidth(2.0)
context.move(to: lineStartPoint)
context.addLine(to: lineEndpoint)
context.setStrokeColor(UIColor.black.cgColor)
context.strokePath()
}
if !lineActionDictionary.isEmpty{
lineActionDictionary.forEach { (actionkey,actionvalue) in
actionvalue.forEach { (playerkey,playervalue) in
let startPoint = playervalue.startPoint
let endPoint = playervalue.endPoint
let color = playervalue.color
let width = playervalue.width
context.setLineCap(.round)
context.setLineWidth(width)
context.move(to: startPoint)
context.addLine(to: endPoint)
context.setStrokeColor(color)
context.strokePath()
}
}
}
}
func selectedShape(selctedShapes : ShapeTypes) {
selectedShape = selctedShapes
}
func playAnimation(){
switch selectedShape {
case .player:
break
case .line:
let player = playerList.first
let views = UIView(frame: player!.rect)
views.alpha = 1.0
UIView.animate(withDuration: 0.25, animations: {
views.frame = CGRect(x: 0, y: 10, width: 20, height: 20)
}, completion: {
(value: Bool) in
debugPrint(">>> Animation done.")
})
}
}
}
#ShapeTypes Enum
enum ShapeTypes : Int {
case player = 0
case line
}
#UIViewController
#available(iOS 14.0, *)
class TestVC : UIViewController{
#IBOutlet weak var colorPicker: UIImageView!
#IBOutlet weak var sliderMaxValueLbl: UILabel!
#IBOutlet weak var radiusSlider: UISlider!
#IBOutlet weak var tv: CanvasView!
#IBOutlet weak var shapeSegment : UISegmentedControl!
#IBOutlet weak var playerFormationPickerDoneBtn: UIButton!
#IBOutlet weak var playerFormationPicker: UIPickerView!
#IBOutlet weak var playerFormationTF: UITextField!
#IBOutlet weak var actionPickerDoneBtn: UIButton!
#IBOutlet weak var actionPicker: UIPickerView!
#IBOutlet weak var actionPickerTF: UITextField!
#IBOutlet weak var addActionBtn: UIBarButtonItem!
//playerformation pickerview
var playerFormationPickerData = String()
var playerFormationPickerSelectedIndex = Int()
var isPlayerFormationSelected : Bool = false
var playerViewArray: [PlayerView] = []
var pickerViewListData: [String] = [String]()
//action pickerview
var actionPickerData = String()
var actionPickerSelectedIndex = Int()
#IBAction func actionPickerDoneButtonAction(_ sender: UIButton) {
actionPickerDoneBtn.isHidden = true
actionPicker.isHidden = true
actionPickerTF.text = actionPickerData
tv.actionKeyIndex = actionPickerTF.text?.components(separatedBy: " ").last ?? ""
debugPrint(tv.actionKeyIndex)
}
func setSliderThumb(_ color: UIColor, _ width : CGFloat) {
let circleImage = makeCircleWith(size: CGSize(width: width, height: width),
backgroundColor: color)
radiusSlider.setThumbImage(circleImage, for: .normal)
radiusSlider.setThumbImage(circleImage, for: .highlighted)
}
fileprivate func makeCircleWith(size: CGSize, backgroundColor: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(backgroundColor.cgColor)
context?.setStrokeColor(UIColor.black.cgColor)
let bounds = CGRect(origin: .zero, size: size)
context?.addEllipse(in: bounds)
context?.drawPath(using: .fill)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
#IBAction func radiusSliderAction(_ sender: UISlider) {
let radius = Int(radiusSlider.value)
tv.radius = CGFloat(radius)
sliderMaxValueLbl.text = String(radius)
}
#IBAction func playAnimationAction(_ sender: UIBarButtonItem) {
tv.playAnimation()
}
override func viewDidLoad() {
super.viewDidLoad()
radiusSlider.minimumValue = 30
radiusSlider.maximumValue = 100
radiusSlider.value = 0
sliderMaxValueLbl.text = String(Int(radiusSlider.value))
setSliderThumb(UIColor.white, 20)
if let img = UIImage(named: "ground") { //background image
UIGraphicsBeginImageContext(tv.frame.size)
img.draw(in: tv.bounds)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = image {
tv.backgroundColor = UIColor(patternImage: image)
tv.contentMode = .scaleAspectFill
}
}
actionPicker.delegate = self
actionPicker.dataSource = self
actionPicker.isHidden = true
actionPickerTF.delegate = self
actionPickerTF.textAlignment = .center
actionPickerTF.placeholder = "Select action"
actionPickerDoneBtn.isHidden = true
actionPickerDoneBtn.tintColor = .systemBlue
tv.actionKeyIndex = tv.actionDict["1"] ?? ""
tv.actionList = ["action \(tv.actionKeyIndex)"]
actionPickerData = tv.actionList[0]
actionPickerTF.text = actionPickerData
tv.currentColor = UIColor.black.cgColor
}
#IBAction func addActionButton(_ sender: UIBarButtonItem) {
let count = tv.actionList.count + 1
let element = tv.lineActionDictionary.keys.max()
// let element = tv.actionDict.values.max() // find max dict value
guard let maxValue = element?.first?.wholeNumberValue else { return }
debugPrint(maxValue)
tv.actionKeyIndex = String(maxValue + 1)
actionPickerTF.text = "action \(tv.actionKeyIndex)"
tv.actionList.append("action \(tv.actionKeyIndex)")
actionPicker.reloadAllComponents()
}
#IBAction func selectShapeSegmentAction(_ sender: UISegmentedControl) {
let selectShape : ShapeType = ShapeTypes(rawValue: shapeSegment.selectedSegmentIndex) ?? .player
tv.selectedShape(selctedShape: selectShape)
}
}
#available(iOS 14.0, *)
extension TestVC : UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerView == actionPicker{
actionPickerData = tv.actionList[row]
actionPickerTF.text = actionPickerData
actionPickerSelectedIndex = actionPicker.selectedRow(inComponent: 0)
}
}
}
#available(iOS 14.0, *)
extension TestVC : UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return tv.actionList.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return tv.actionList[row]
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var title = UILabel()
if let view = view {
title = view as! UILabel
}
if pickerView == actionPicker {
title.text = tv.actionList[row]
}
title.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
title.textColor = UIColor.black
title.textAlignment = .center
return title
}
}
Lets say you have some customView and you want to simply temporary move it a little bit down. Here is lightweight solution.
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
func animateGoingDown() {
UIView.animate(withDuration: 0.5) {
self.customView.transform = CGAffineTransform(translationX: 0, y: 200)
}
}
func animateGoingToOriginalPosition() {
UIView.animate(withDuration: 0.5) {
self.customView.transform = .identity
}
}
Howefully this is what you are searching for. If you have some questions, do not hesitate to ask.

NSMutableAttributedString to UITextField in Swift

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.

CALayer border colour and width are not changing?

I have 5 textFields and my requirement is I have to set 0.2 border width initially, after that when tap on edit button I have to change border width from 0.2 to 0.8 and again tap on submit button I have to change border width from 0.8 to 0.2.
The thing is that border colour and width are not changing. Here below is my code:
class EditProductCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
//Create Border Line.
createBorderLine(0.2,UIColor.lightGray)
}
//Create Border Line on Text Field
func createBorderLine(_ width : CGFloat, _ color : UIColor)//_ width : CGFloat, _ color : UIColor)
{
setBottomBorder(textField: InvoiceDate, width: width,color : color)
setBottomBorder(textField: InvoiceNumber, width: width,color : color)
setBottomBorder(textField: modelNumber, width: width,color : color)
setBottomBorder(textField: productName, width: width,color : color)
setBottomBorder(textField: serialNumber, width: width,color : color)
self.layoutSubviews()
}
}
Here is another class:
class EditProductView: BaseViewController {
//TableView
#IBOutlet var tableView: UITableView!
//View Did Load
override func viewDidLoad() {
super.viewDidLoad()
//self.view.backgroundColor = hexStringToUIColor(hex: "#CCCCCC")
tableView.delegate = self
tableView.dataSource = self
//Button Submit
self.btnSubmit.isHidden = true
tableView.bounces = false
tableView.alwaysBounceVertical = false
hideKeyboardWhenTappedAround()
}
//Button Submit
#IBAction func btnSubmitAction(_ sender: Any) {
self.btnSubmit.isHidden = true
let index : NSIndexPath = NSIndexPath(row: 0, section: 0)
let tCell : EditProductCell = self.tableView.cellForRow(at: index as IndexPath) as! EditProductCell
//Change border line
tCell.createBorderLine(0.2, UIColor.lightGray)
tCell.btnEdit.isHidden = false
dismissKeyboard()
}
func btnEditAction()
{
btnSubmit.isHidden = false
let index : NSIndexPath = NSIndexPath(row: 0, section: 0)
let tCell : EditProductCell = self.tableView.cellForRow(at: index as IndexPath) as! EditProductCell
tCell.btnEdit.isHidden = true
//Create border line
tCell.createBorderLine(0.8, UIColor.black)
dismissKeyboard()
}
}
Here is set bottom border method in separate class:
//Set Bottom border line.
func setBottomBorder(textField: UITextField, width: CGFloat,color : UIColor) {
let border = CALayer()
border.name = "BottomBorder"
border.borderColor = color.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width,
width: textField.frame.size.width, height: width)
border.borderWidth = width
textField.borderStyle = UITextBorderStyle.none
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
You can see in my code that button submit and button edit. I am changing border line colour and width. Colour and width are not.
I also tried something in but not able to get width change.
override func layoutSublayers(of layer: CALayer) {
if (layer == self.layer)
{
layer.borderWidth = 0.3
}
}
//MARK:- TextField Delegate
extension EditProductCell : UITextFieldDelegate
{
func textFieldDidBeginEditing(_ textField: UITextField) {
if let sublayers = textField.layer.sublayers {
for layer: CALayer in sublayers {
if layer.name == "BottomBorder" {
layer.removeFromSuperlayer()
}
}
}
setBottomBorder(textField: textField, width: 0.8, color: hexStringToUIColor(hex: "#55ACEE"))
}
//TextField Did End Editing
func textFieldDidEndEditing(_ textField: UITextField) {
if let sublayers = textField.layer.sublayers {
for layer: CALayer in sublayers {
if layer.name == "BottomBorder" {
layer.removeFromSuperlayer()
}
}
}
setBottomBorder(textField: textField, width: 0.8,color : UIColor.black)
textField.resignFirstResponder()
}
//TextField Return Key
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Try to find next responder
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField
{
nextField.becomeFirstResponder()
}
else
{
textField.resignFirstResponder()
}
return false
}
}
Swift 4
shape.strokeColor = UIColor.lightGray.cgColor
shape.lineWidth = 1.0
If you try with numbers greater than 1 your code works. I think the border width need to be greater than 1.

Hashtag and attributed color issue in FFLabel library for ios swift

I am new on swift and on my project I've been trying to use custom label such as clickable '#' and '#' sub strings on the related label. I found a library that called as FFLabel on github. I've made some tests for FFLabel. Everything is good, but I want to use different colors for '#' and '#' sub strings. I' ve tried to make some changes on source code. However, it did not work correctly. Also, '#' sub strings is not working(it is not clickable). I guess that there is a regex issue in source code.
Here is the source code of FFLabel:
import UIKit
#objc
public protocol FFLabelDelegate: NSObjectProtocol {
optional func labelDidSelectedLinkText(label: FFLabel, text: String)
}
public class FFLabel: UILabel {
public var linkTextColor = UIColor(red: 0, green: 63.0/255.0, blue: 121.0/255.0, alpha: 1.0)
public weak var labelDelegate: FFLabelDelegate?
// MARK: - override properties
override public var text: String? {
didSet {
updateTextStorage()
}
}
override public var attributedText: NSAttributedString? {
didSet {
updateTextStorage()
}
}
override public var font: UIFont! {
didSet {
updateTextStorage()
}
}
override public var textColor: UIColor! {
didSet {
updateTextStorage()
}
}
// MARK: - upadte text storage and redraw text
private func updateTextStorage() {
if attributedText == nil {
return
}
let attrStringM = addLineBreak(attributedText!)
regexLinkRanges(attrStringM)
addLinkAttribute(attrStringM)
textStorage.setAttributedString(attrStringM)
setNeedsDisplay()
}
/// add link attribute
private func addLinkAttribute(attrStringM: NSMutableAttributedString) {
var range = NSRange(location: 0, length: 0)
var attributes = attrStringM.attributesAtIndex(0, effectiveRange: &range)
attributes[NSFontAttributeName] = font!
attributes[NSForegroundColorAttributeName] = textColor
attrStringM.addAttributes(attributes, range: range)
attributes[NSForegroundColorAttributeName] = linkTextColor
for r in linkRanges {
attrStringM.setAttributes(attributes, range: r)
}
}
/// use regex check all link ranges
private let patterns = ["[a-zA-Z]*://[a-zA-Z0-9/\\.]*", "#.*?#", "#[\\u4e00-\\u9fa5a-zA-Z0-9_-]*"]
private func regexLinkRanges(attrString: NSAttributedString) {
linkRanges.removeAll()
let regexRange = NSRange(location: 0, length: count(attrString.string))
for pattern in patterns {
let regex = NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.DotMatchesLineSeparators, error: nil)
let results = regex?.matchesInString(attrString.string, options: NSMatchingOptions(rawValue: 0), range: regexRange)
if let results = results {
for r in results {
linkRanges.append(r.rangeAtIndex(0))
}
}
}
}
/// add line break mode
private func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString {
let attrStringM = NSMutableAttributedString(attributedString: attrString)
var range = NSRange(location: 0, length: 0)
var attributes = attrStringM.attributesAtIndex(0, effectiveRange: &range)
var paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle
if paragraphStyle != nil {
paragraphStyle!.lineBreakMode = NSLineBreakMode.ByWordWrapping
} else {
// iOS 8.0 can not get the paragraphStyle directly
paragraphStyle = NSMutableParagraphStyle()
paragraphStyle!.lineBreakMode = NSLineBreakMode.ByWordWrapping
attributes[NSParagraphStyleAttributeName] = paragraphStyle
attrStringM.setAttributes(attributes, range: range)
}
return attrStringM
}
public override func drawTextInRect(rect: CGRect) {
let range = glyphsRange()
let offset = glyphsOffset(range)
layoutManager.drawBackgroundForGlyphRange(range, atPoint: offset)
layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPointZero)
}
private func glyphsRange() -> NSRange {
return NSRange(location: 0, length: textStorage.length)
}
private func glyphsOffset(range: NSRange) -> CGPoint {
let rect = layoutManager.boundingRectForGlyphRange(range, inTextContainer: textContainer)
let height = (bounds.height - rect.height) * 0.5
return CGPoint(x: 0, y: height)
}
// MARK: - touch events
public override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let location = (touches.first as! UITouch).locationInView(self)
selectedRange = linkRangeAtLocation(location)
modifySelectedAttribute(true)
}
public override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let location = (touches.first as! UITouch).locationInView(self)
if let range = linkRangeAtLocation(location) {
if !(range.location == selectedRange?.location && range.length == selectedRange?.length) {
modifySelectedAttribute(false)
selectedRange = range
modifySelectedAttribute(true)
}
} else {
modifySelectedAttribute(false)
}
}
public override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if selectedRange != nil {
let text = (textStorage.string as NSString).substringWithRange(selectedRange!)
labelDelegate?.labelDidSelectedLinkText!(self, text: text)
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC)))
dispatch_after(when, dispatch_get_main_queue()) {
self.modifySelectedAttribute(false)
}
}
}
public override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
modifySelectedAttribute(false)
}
private func modifySelectedAttribute(isSet: Bool) {
if selectedRange == nil {
return
}
var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil)
attributes[NSForegroundColorAttributeName] = linkTextColor
let range = selectedRange!
textStorage.addAttributes(attributes, range: range)
setNeedsDisplay()
}
private func linkRangeAtLocation(location: CGPoint) -> NSRange? {
if textStorage.length == 0 {
return nil
}
let offset = glyphsOffset(glyphsRange())
let point = CGPoint(x: offset.x + location.x, y: offset.y + location.y)
let index = layoutManager.glyphIndexForPoint(point, inTextContainer: textContainer)
for r in linkRanges {
if index >= r.location && index <= r.location + r.length {
return r
}
}
return nil
}
// MARK: - init functions
override public init(frame: CGRect) {
super.init(frame: frame)
prepareLabel()
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareLabel()
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
private func prepareLabel() {
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
userInteractionEnabled = true
}
// MARK: lazy properties
private lazy var linkRanges = [NSRange]()
private var selectedRange: NSRange?
private lazy var textStorage = NSTextStorage()
private lazy var layoutManager = NSLayoutManager()
private lazy var textContainer = NSTextContainer()
}
Thank you for your answers
King regards
Note: I solved hashtag clickable issue(regular expression issue) by changing the hashtag expression as #[\\u4e00-\\u9fa5a-zA-Z0-9_-]*. But the rest of the issues still going on.
You could also try ActiveLabel.swift which is an UILabel drop-in replacement supporting Hashtags (#), Mentions (#) and URLs (http://) written in Swift.
Maybe that's exactly what you're looking for. Giving usernames, hashtags and links different colors is as simple as this:
label.textColor = .blackColor()
label.hashtagColor = .blueColor()
label.mentionColor = .greenColor()
label.URLColor = .redColor()
Disclaimer: I'm the author of the library.

Add placeholder text inside UITextView in Swift?

How can I add a placeholder in a UITextView, similar to the one you can set for UITextField, in Swift?
Updated for Swift 4
UITextView doesn't inherently have a placeholder property so you'd have to create and manipulate one programmatically using UITextViewDelegate methods. I recommend using either solution #1 or #2 below depending on the desired behavior.
Note: For either solution, add UITextViewDelegate to the class and set textView.delegate = self to use the text view’s delegate methods.
Solution #1 - If you want the placeholder to disappear as soon as the user selects the text view:
First set the UITextView to contain the placeholder text and set it to a light gray color to mimic the look of a UITextField's placeholder text. Either do so in the viewDidLoad or upon the text view's creation.
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
Then when the user begins to edit the text view, if the text view contains a placeholder (i.e. if its text color is light gray) clear the placeholder text and set the text color to black in order to accommodate the user's entry.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
Then when the user finishes editing the text view and it's resigned as the first responder, if the text view is empty, reset its placeholder by re-adding the placeholder text and setting its color to light gray.
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
Solution #2 - If you want the placeholder to show whenever the text view is empty, even if the text view’s selected:
First set the placeholder in the viewDidLoad:
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.becomeFirstResponder()
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
(Note: Since the OP wanted to have the text view selected as soon as the view loads, I incorporated text view selection into the above code. If this is not your desired behavior and you do not want the text view selected upon view load, remove the last two lines from the above code chunk.)
Then utilize the shouldChangeTextInRange UITextViewDelegate method, like so:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Combine the textView text and the replacement text to
// create the updated text string
let currentText:String = textView.text
let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
// If updated text view will be empty, add the placeholder
// and set the cursor to the beginning of the text view
if updatedText.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
// Else if the text view's placeholder is showing and the
// length of the replacement string is greater than 0, set
// the text color to black then set its text to the
// replacement string
else if textView.textColor == UIColor.lightGray && !text.isEmpty {
textView.textColor = UIColor.black
textView.text = text
}
// For every other case, the text should change with the usual
// behavior...
else {
return true
}
// ...otherwise return false since the updates have already
// been made
return false
}
And also implement textViewDidChangeSelection to prevent the user from changing the position of the cursor while the placeholder's visible. (Note: textViewDidChangeSelection is called before the view loads so only check the text view's color if the window is visible):
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == UIColor.lightGray {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
Floating Placeholder
It's simple, safe and reliable to position a placeholder label above a text view, set its font, color and manage placeholder visibility by tracking changes to the text view's character count.
Update: Incorporated suggestions made by #RakshithaMurangaRodrigo in Feb 10, '23 comment
Swift 5:
class NotesViewController : UIViewController {
#IBOutlet var textView : UITextView!
var placeholderLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = .italicSystemFont(ofSize: (textView.font?.pointSize)!)
placeholderLabel.sizeToFit()
textView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
placeholderLabel.textColor = .tertiaryLabel
placeholderLabel.isHidden = !textView.text.isEmpty
}
}
extension NotesViewController : UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
placeholderLabel?.isHidden = !textView.text.isEmpty
}
func textViewDidEndEditing(_ textView: UITextView) {
placeholderLabel?.isHidden = !textView.text.isEmpty
}
func textViewDidBeginEditing(_ textView: UITextView) {
placeholderLabel?.isHidden = true
}
}
Swift:
Add your text view programmatically or via Interface Builder, if the last, create the outlet:
#IBOutlet weak var yourTextView: UITextView!
Please add the delegate (UITextViewDelegate):
class ViewController: UIViewController, UITextViewDelegate {
In the viewDidLoad method, do add the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourTextView.delegate = self
yourTextView.text = "Placeholder text goes right here..."
yourTextView.textColor = UIColor.lightGray
Now let me introduce the magic part, add this function:
func textViewDidBeginEditing(_ textView: UITextView) {
if yourTextView.textColor == UIColor.lightGray {
yourTextView.text = ""
yourTextView.textColor = UIColor.black
}
}
Do note that this will execute whenever editing starts, there we will check conditions to tell the state, using the color property.
Setting text to nil i do not recommend. Right after that, we set the text color to desired, in this case, black.
Now add this function too:
func textViewDidEndEditing(_ textView: UITextView) {
if yourTextView.text == "" {
yourTextView.text = "Placeholder text ..."
yourTextView.textColor = UIColor.lightGray
}
}
Let me insist, do not compare to nil, i have already tried that and it would not work. We then set the values back to placeholder style, and set the color back to placeholder color because it is a condition to check in textViewDidBeginEditing.
I am surprised that no one mentioned NSTextStorageDelegate. UITextViewDelegate's methods will only be triggered by user interaction, but not programmatically. E.g. when you set a text view's text property programmatically, you'll have to set the placeholder's visibility yourself, because the delegate methods will not be called.
However, with NSTextStorageDelegate's textStorage(_:didProcessEditing:range:changeInLength:) method, you'll be notified of any change to the text, even if it's done programmatically. Just assign it like this:
textView.textStorage.delegate = self
(In UITextView, this delegate property is nil by default, so it won't affect any default behaviour.)
Combine it with the UILabel technique #clearlight demonstrates, one can easily wrap the whole UITextView's placeholder implementation into an extension.
extension UITextView {
private class PlaceholderLabel: UILabel { }
private var placeholderLabel: PlaceholderLabel {
if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
return label
} else {
let label = PlaceholderLabel(frame: .zero)
label.font = font
addSubview(label)
return label
}
}
#IBInspectable
var placeholder: String {
get {
return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
}
set {
let placeholderLabel = self.placeholderLabel
placeholderLabel.text = newValue
placeholderLabel.numberOfLines = 0
let width = frame.width - textContainer.lineFragmentPadding * 2
let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
placeholderLabel.frame.size.height = size.height
placeholderLabel.frame.size.width = width
placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)
textStorage.delegate = self
}
}
}
extension UITextView: NSTextStorageDelegate {
public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
if editedMask.contains(.editedCharacters) {
placeholderLabel.isHidden = !text.isEmpty
}
}
}
Note that the use of a private (nested) class called PlaceholderLabel. It has no implementation at all, but it provides us a way to identify the placeholder label, which is far more 'swifty' than using the tag property.
With this approach, you can still assign the delegate of the UITextView to someone else.
You don't even have to change your text views' classes. Just add the extension(s) and you will be able to assign a placeholder string to every UITextView in your project, even in the Interface Builder.
I left out the implementation of a placeholderColor property for clarity reasons, but it can be implemented for just a few more lines with a similar computed variable to placeholder.
Based on some of the great suggestions here already, I was able to put together the following lightweight, Interface-Builder-compatible subclass of UITextView, which:
Includes configurable placeholder text, styled just like that of UITextField.
Doesn't require any additional subviews or constraints.
Doesn't require any delegation or other behaviour from the ViewController.
Doesn't require any notifications.
Keeps that placeholder text fully separated from any outside classes looking at the field's text property.
Any improvement suggestions are welcome, especially if there's any way to pull iOS's placeholder color programatically, rather than hard-coding it.
Swift v5:
import UIKit
#IBDesignable class TextViewWithPlaceholder: UITextView {
override var text: String! { // Ensures that the placeholder text is never returned as the field's text
get {
if showingPlaceholder {
return "" // When showing the placeholder, there's no real text to return
} else { return super.text }
}
set { super.text = newValue }
}
#IBInspectable var placeholderText: String = ""
#IBInspectable var placeholderTextColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0) // Standard iOS placeholder color (#C7C7CD). See https://stackoverflow.com/questions/31057746/whats-the-default-color-for-placeholder-text-in-uitextfield
private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
override func didMoveToWindow() {
super.didMoveToWindow()
if text.isEmpty {
showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
}
}
override func becomeFirstResponder() -> Bool {
// If the current text is the placeholder, remove it
if showingPlaceholder {
text = nil
textColor = nil // Put the text back to the default, unmodified color
showingPlaceholder = false
}
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
// If there's no text, put the placeholder back
if text.isEmpty {
showPlaceholderText()
}
return super.resignFirstResponder()
}
private func showPlaceholderText() {
showingPlaceholder = true
textColor = placeholderTextColor
text = placeholderText
}
}
I did this by using two different text views:
One in the background that is used as a placeholder.
One in the foreground (with a transparent background) that the user actually types in.
The idea is that once the user starts typing stuff in the foreground view, the placeholder in the background disappears (and reappears if the user deletes everything). So it behaves exactly like a placeholder for the single line text field.
Here's the code I used for it. Note that descriptionField is the field the user types in and descriptionPlaceholder is the one in the background.
func textViewDidChange(descriptionField: UITextView) {
if descriptionField.text.isEmpty == false {
descriptionPlaceholder.text = ""
} else {
descriptionPlaceholder.text = descriptionPlaceholderText
}
}
I tried to make code convenient from clearlight's answer.
extension UITextView{
func setPlaceholder() {
let placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholderLabel.sizeToFit()
placeholderLabel.tag = 222
placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !self.text.isEmpty
self.addSubview(placeholderLabel)
}
func checkPlaceholder() {
let placeholderLabel = self.viewWithTag(222) as! UILabel
placeholderLabel.isHidden = !self.text.isEmpty
}
}
usage
override func viewDidLoad() {
textView.delegate = self
textView.setPlaceholder()
}
func textViewDidChange(_ textView: UITextView) {
textView.checkPlaceholder()
}
Here is what I'm using for getting this job done.
#IBDesignable class UIPlaceholderTextView: UITextView {
var placeholderLabel: UILabel?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshPlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
}
#IBInspectable var placeholder: String? {
didSet {
refreshPlaceholder()
}
}
#IBInspectable var placeholderColor: UIColor? = .darkGray {
didSet {
refreshPlaceholder()
}
}
#IBInspectable var placeholderFontSize: CGFloat = 14 {
didSet {
refreshPlaceholder()
}
}
func refreshPlaceholder() {
if placeholderLabel == nil {
placeholderLabel = UILabel()
let contentView = self.subviews.first ?? self
contentView.addSubview(placeholderLabel!)
placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
}
placeholderLabel?.text = placeholder
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
}
#objc func textChanged() {
if self.placeholder?.isEmpty ?? true {
return
}
UIView.animate(withDuration: 0.25) {
if self.text.isEmpty {
self.placeholderLabel?.alpha = 1.0
} else {
self.placeholderLabel?.alpha = 0.0
}
}
}
override var text: String! {
didSet {
textChanged()
}
}
}
I know there're several approaches similar to this but the benefits from this one are that it can:
Set placeholder text, font size and color in IB.
No longer shows the warning of "Scroll View has ambiguous scrollable content" in IB.
Add animation to show/hide of placeholder.
Swift:
Add your TextView #IBOutlet:
#IBOutlet weak var txtViewMessage: UITextView!
In the viewWillAppear method, do add the following :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
txtViewMessage.delegate = self // Give TextViewMessage delegate Method
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
Please add the Delegate Using extension (UITextViewDelegate):
// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name" {
txtViewMessage.text = ""
txtViewMessage.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if txtViewMessage.text.isEmpty {
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
}
}
SET value in view load
txtVw!.autocorrectionType = UITextAutocorrectionType.No
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
func textViewDidBeginEditing(textView: UITextView) {
if (txtVw?.text == "Write your Placeholder")
{
txtVw!.text = nil
txtVw!.textColor = UIColor.blackColor()
}
}
func textViewDidEndEditing(textView: UITextView) {
if txtVw!.text.isEmpty
{
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
}
textView.resignFirstResponder()
}
One more solution (Swift 3):
import UIKit
protocol PlaceholderTextViewDelegate {
func placeholderTextViewDidChangeText(_ text:String)
func placeholderTextViewDidEndEditing(_ text:String)
}
final class PlaceholderTextView: UITextView {
var notifier:PlaceholderTextViewDelegate?
var placeholder: String? {
didSet {
placeholderLabel?.text = placeholder
}
}
var placeholderColor = UIColor.lightGray
var placeholderFont = UIFont.appMainFontForSize(14.0) {
didSet {
placeholderLabel?.font = placeholderFont
}
}
fileprivate var placeholderLabel: UILabel?
// MARK: - LifeCycle
init() {
super.init(frame: CGRect.zero, textContainer: nil)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)
placeholderLabel = UILabel()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.textAlignment = .left
placeholderLabel?.numberOfLines = 0
}
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel?.font = placeholderFont
var height:CGFloat = placeholderFont.lineHeight
if let data = placeholderLabel?.text {
let expectedDefaultWidth:CGFloat = bounds.size.width
let fontSize:CGFloat = placeholderFont.pointSize
let textView = UITextView()
textView.text = data
textView.font = UIFont.appMainFontForSize(fontSize)
let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
height: CGFloat.greatestFiniteMagnitude))
let expectedTextViewHeight = sizeForTextView.height
if expectedTextViewHeight > height {
height = expectedTextViewHeight
}
}
placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)
if text.isEmpty {
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
} else {
placeholderLabel?.removeFromSuperview()
}
}
func textDidChangeHandler(notification: Notification) {
layoutSubviews()
}
}
extension PlaceholderTextView : UITextViewDelegate {
// MARK: - UITextViewDelegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
notifier?.placeholderTextViewDidChangeText(textView.text)
}
func textViewDidEndEditing(_ textView: UITextView) {
notifier?.placeholderTextViewDidEndEditing(textView.text)
}
}
result
A simple and quick solution that works for me is:
#IBDesignable
class PlaceHolderTextView: UITextView {
#IBInspectable var placeholder: String = "" {
didSet{
updatePlaceHolder()
}
}
#IBInspectable var placeholderColor: UIColor = UIColor.gray {
didSet {
updatePlaceHolder()
}
}
private var originalTextColor = UIColor.darkText
private var originalText: String = ""
private func updatePlaceHolder() {
if self.text == "" || self.text == placeholder {
self.text = placeholder
self.textColor = placeholderColor
if let color = self.textColor {
self.originalTextColor = color
}
self.originalText = ""
} else {
self.textColor = self.originalTextColor
self.originalText = self.text
}
}
override func becomeFirstResponder() -> Bool {
let result = super.becomeFirstResponder()
self.text = self.originalText
self.textColor = self.originalTextColor
return result
}
override func resignFirstResponder() -> Bool {
let result = super.resignFirstResponder()
updatePlaceHolder()
return result
}
}
We can implement textview PlaceHolder quite easily if we are Using pod IQKeyboardManagerSwift in our project just have to follow 4 steps
We have to assign the class IQTextView to our TextView class.
We have to import the IQKeyboardManagerSwift in our Controller page
Last but not least make the outlet of the textView on the Controller page // if you want :)
Give the textView some place holder text through the storyboard inspectable
Swift 3.2
extension EditProfileVC:UITextViewDelegate{
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
}
First when user start editing textViewDidBeginEditing call and then check the if colour of text grey means user didn't write anything then set as textview nil and change the colour to black for user texting.
When user end editing textViewDidEndEditing is call and check if user doesn't write anything in textview then text set as grey colour with text "PlaceHolder"
Here is my way of solving this problem (Swift 4):
The idea was to make the simplest possible solution which allows to use placeholders of different colors, resizes to placeholders size, will not overwrite a delegate meanwhile keeping all UITextView functions work as expected.
import UIKit
class PlaceholderTextView: UITextView {
var placeholderColor: UIColor = .lightGray
var defaultTextColor: UIColor = .black
private var isShowingPlaceholder = false {
didSet {
if isShowingPlaceholder {
text = placeholder
textColor = placeholderColor
} else {
textColor = defaultTextColor
}
}
}
var placeholder: String? {
didSet {
isShowingPlaceholder = !hasText
}
}
#objc private func textViewDidBeginEditing(notification: Notification) {
textColor = defaultTextColor
if isShowingPlaceholder { text = nil }
}
#objc private func textViewDidEndEditing(notification: Notification) {
isShowingPlaceholder = !hasText
}
// MARK: - Construction -
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
}
// MARK: - Destruction -
deinit { NotificationCenter.default.removeObserver(self) }
}
Swift Answer
Here is the custom class, that animates placeholder.
class CustomTextView: UITextView {
// MARK: - public
public var placeHolderText: String? = "Enter Reason.."
public lazy var placeHolderLabel: UILabel! = {
let placeHolderLabel = UILabel(frame: .zero)
placeHolderLabel.numberOfLines = 0
placeHolderLabel.backgroundColor = .clear
placeHolderLabel.alpha = 0.5
return placeHolderLabel
}()
// MARK: - Init
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
enableNotifications()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
enableNotifications()
}
func setup() {
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.sizeToFit()
}
// MARK: - Cycle
override func awakeFromNib() {
super.awakeFromNib()
textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 8)
returnKeyType = .done
addSubview(placeHolderLabel)
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.textColor = textColor
placeHolderLabel.font = font
placeHolderLabel.text = placeHolderText
bringSubviewToFront(placeHolderLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
// MARK: - Notifications
private func enableNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(_:)), name: UITextView.textDidChangeNotification , object: nil)
}
#objc func textDidChangeNotification(_ notify: Notification) {
guard self == notify.object as? UITextView else { return }
guard placeHolderText != nil else { return }
UIView.animate(withDuration: 0.25, animations: {
self.placeHolderLabel.alpha = (self.text.count == 0) ? 0.5 : 0
}, completion: nil)
}
}
I don't know why people over complicate this issue so much.... It's fairly straight forward and simple. Here's a subclass of UITextView that provides the requested functionality.
- (void)customInit
{
self.contentMode = UIViewContentModeRedraw;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
- (void)textChanged:(NSNotification *)notification
{
if (notification.object == self) {
if(self.textStorage.length != 0 || !self.textStorage.length) {
[self setNeedsDisplay];
}
}
}
#pragma mark - Setters
- (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
{
self.placeholderText = placeholderText;
self.placeholderTextFont = font;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor lightGrayColor] setFill];
if (self.textStorage.length != 0) {
return;
}
CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
NSDictionary *attributes = #{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
[self.placeholderText drawInRect:inset withAttributes:attributes];
}`
This is my ready to use solution if you are working with multiple text views
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
// Set cursor to the beginning if placeholder is set
if textView.textColor == UIColor.lightGrayColor() {
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
return true
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Remove placeholder
if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
textView.text = ""
textView.textColor = UIColor.blackColor()
}
if text == "\n" {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(textView: UITextView) {
// Set placeholder if text is empty
if textView.text.isEmpty {
textView.text = NSLocalizedString("Hint", comment: "hint")
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
}
func textViewDidChangeSelection(textView: UITextView) {
// Set cursor to the beginning if placeholder is set
let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
// Do not change position recursively
if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
textView.selectedTextRange = firstPosition
}
}
Swift - I wrote a class that inherited UITextView and I added a UILabel as a subview to act as a placeholder.
import UIKit
#IBDesignable
class HintedTextView: UITextView {
#IBInspectable var hintText: String = "hintText" {
didSet{
hintLabel.text = hintText
}
}
private lazy var hintLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFontOfSize(16)
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
private func setupView() {
translatesAutoresizingMaskIntoConstraints = false
delegate = self
font = UIFont.systemFontOfSize(16)
addSubview(hintLabel)
NSLayoutConstraint.activateConstraints([
hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
hintLabel.heightAnchor.constraintEqualToConstant(30)
])
}
override func layoutSubviews() {
super.layoutSubviews()
setupView()
}
}
Swift 3.1
This extension worked well for me: https://github.com/devxoul/UITextView-Placeholder
Here is a code snippet:
Install it via pod:
pod 'UITextView+Placeholder', '~> 1.2'
Import it to your class
import UITextView_Placeholder
And add placeholder property to your already created UITextView
textView.placeholder = "Put some detail"
Thats it...
Here how it looks (Third box is a UITextView)
I had to dispatch queue to get my placeholder text to reappear once editing was completed.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Description" {
textView.text = nil
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
DispatchQueue.main.async {
textView.text = "Description"
}
}
}
Swift 4, 4.2 and 5
[![#IBOutlet var detailTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
detailTextView.delegate = self
}
extension ContactUsViewController : UITextViewDelegate {
public func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Write your message here..." {
detailTextView.text = ""
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.86)
}
textView.becomeFirstResponder()
}
public func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == "" {
detailTextView.text = "Write your message here..."
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.30)
}
textView.resignFirstResponder()
}
[![}][1]][1]
Contrary to just about every answer on this post, UITextView does have a placeholder property. For reasons beyond my comprehension, it is only exposed in IB, as such:
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="My Placeholder"/>
</userDefinedRuntimeAttributes>
So if you are using storyboards and a static placeholder will suffice, just set the property on the inspector.
You can also set this property in code like this:
textView.setValue("My Placeholder", forKeyPath: "placeholder")
Its cloudy as to weather this is accessed via private API, as the property is exposed.
I haven't tried submitting with this method. But I will be submitting this way shortly and will update this answer accordingly.
UPDATE:
I have shipped this code in multiple releases with no issues from Apple.
UPDATE:
This will only work in Xcode pre 11.2
Swift 5.2
Standalone class
Use this if you want a class which you can use anywhere as it is self contained
import UIKit
class PlaceHolderTextView:UITextView, UITextViewDelegate{
var placeholderText = "placeholderText"
override func willMove(toSuperview newSuperview: UIView?) {
textColor = .lightText
delegate = self
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == placeholderText{
placeholderText = textView.text
textView.text = ""
textView.textColor = .darkText
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == ""{
textView.text = placeholderText
textColor = .lightText
}
}
}
The key here is the willMove(toSuperView:) function as it allows you to setup the view before being added to another view's hierarchy (similar to viewDidLoad/viewWillAppear in ViewControllers)
No need to add any third party library. Just use below code...
class SubmitReviewVC : UIViewController, UITextViewDelegate {
#IBOutlet var txtMessage : UITextView!
var lblPlaceHolder : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
txtMessage.delegate = self
lblPlaceHolder = UILabel()
lblPlaceHolder.text = "Enter message..."
lblPlaceHolder.font = UIFont.systemFont(ofSize: txtMessage.font!.pointSize)
lblPlaceHolder.sizeToFit()
txtMessage.addSubview(lblPlaceHolder)
lblPlaceHolder.frame.origin = CGPoint(x: 5, y: (txtMessage.font?.pointSize)! / 2)
lblPlaceHolder.textColor = UIColor.lightGray
lblPlaceHolder.isHidden = !txtMessage.text.isEmpty
}
func textViewDidChange(_ textView: UITextView) {
lblPlaceHolder.isHidden = !textView.text.isEmpty
}
}
There is no such property in ios to add placeholder directly in TextView rather you can add a label and show/hide on the change in textView. SWIFT 2.0 and make sure to implement the textviewdelegate
func textViewDidChange(TextView: UITextView)
{
if txtShortDescription.text == ""
{
self.lblShortDescription.hidden = false
}
else
{
self.lblShortDescription.hidden = true
}
}
I like #nerdist's solution. Based on that, I created an extension to UITextView:
import Foundation
import UIKit
extension UITextView
{
private func add(_ placeholder: UILabel) {
for view in self.subviews {
if let lbl = view as? UILabel {
if lbl.text == placeholder.text {
lbl.removeFromSuperview()
}
}
}
self.addSubview(placeholder)
}
func addPlaceholder(_ placeholder: UILabel?) {
if let ph = placeholder {
ph.numberOfLines = 0 // support for multiple lines
ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
ph.sizeToFit()
self.add(ph)
ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
ph.textColor = UIColor(white: 0, alpha: 0.3)
updateVisibility(ph)
}
}
func updateVisibility(_ placeHolder: UILabel?) {
if let ph = placeHolder {
ph.isHidden = !self.text.isEmpty
}
}
}
In a ViewController class, for example, this is how I use it:
class MyViewController: UIViewController, UITextViewDelegate {
private var notePlaceholder: UILabel!
#IBOutlet weak var txtNote: UITextView!
...
// UIViewController
override func viewDidLoad() {
notePlaceholder = UILabel()
notePlaceholder.text = "title\nsubtitle\nmore..."
txtNote.addPlaceholder(notePlaceholder)
...
}
// UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
txtNote.updateVisbility(notePlaceholder)
...
}
Placeholder on UITextview!
UPDATE:
In case you change textview's text in code, remember to call updateVisibitly method to hide placeholder:
txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.
To prevent the placeholder being added more than once, a private add() function is added in extension.
In swift2.2:
public class CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
#IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
#IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public var textContainerInset: UIEdgeInsets {
didSet {
updateConstraintsForPlaceholderLabel()
}
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(textDidChange),
name: UITextViewTextDidChangeNotification,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clearColor()
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .Width,
relatedBy: .Equal,
toItem: self,
attribute: .Width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
}
#objc private func textDidChange() {
placeholderLabel.hidden = !text.isEmpty
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self,
name: UITextViewTextDidChangeNotification,
object: nil)
}
}
In swift3:
import UIKit
class CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
#IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
#IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public var textContainerInset: UIEdgeInsets {
didSet {
updateConstraintsForPlaceholderLabel()
}
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clear
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
}
#objc private func textDidChange() {
placeholderLabel.isHidden = !text.isEmpty
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
}
}
I wrote a class in swift. You need to import this class whenever required.
I can't add comment because of reputation. add one more delegate need in #clearlight answer.
func textViewDidBeginEditing(_ textView: UITextView) {
cell.placeholderLabel.isHidden = !textView.text.isEmpty
}
is need
because textViewDidChange is not called first time
no there is not any placeholder available for textview. you have to put label above it when user enter in textview then hide it or set by default value when user enters remove all values.

Resources