show bullets in label on tableview cell swift ios - ios

I have a table view, inside the table view I have multiple cells as shows in attached image.
I want to show amenities items like : air conditioning, swimming pool, gym, tv cable etc like bullets.
How can I achieve this
I have all these value in my model coming from API response.
let ac = "Air Conditioning"
let tv = "TV Cable"
let washing = "Washing Machine"
let connectivity = "Wi-Fi"
let gym = "Gym"

Good case for stack views...
Vertical UIStackView
Each "row" is a Horizontal UIStackView with Distribution: Fill Equally
Looks like this - showing Bounds Rectangles:
without the Bounds Rectangles:
view hierarchy:
Edit
A second approach (which you may find easier)...
Use a single horizontal stack view with a single label on each "side" and use .attributedText.
Set the .attributedText of each label to a formatted, bulleted attributed string.
The cell prototype could look like this:
Then, use this code (slightly modified from https://stackoverflow.com/a/46889728/6257435) to generate a formatted, bulleted attributed string:
func add(bulletList strings: [String],
font: UIFont,
indentation: CGFloat = 15,
lineSpacing: CGFloat = 3,
paragraphSpacing: CGFloat = 10,
textColor: UIColor = .black,
bulletColor: UIColor = .red) -> NSAttributedString {
func createParagraphAttirbute() -> NSParagraphStyle {
var paragraphStyle: NSMutableParagraphStyle
let nonOptions = NSDictionary() as! [NSTextTab.OptionKey: Any]
paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.tabStops = [
NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
paragraphStyle.defaultTabInterval = indentation
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.paragraphSpacing = paragraphSpacing
paragraphStyle.headIndent = indentation
return paragraphStyle
}
let bulletPoint = "\u{2022}"
let textAttributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: textColor]
let bulletAttributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: bulletColor]
let buffer = NSMutableAttributedString.init()
for string in strings {
var formattedString = "\(bulletPoint)\t\(string)"
// don't add newLine if it's the last bullet
if string != strings.last {
formattedString += "\n"
}
let attributedString = NSMutableAttributedString(string: formattedString)
let paragraphStyle = createParagraphAttirbute()
attributedString.addAttributes(
[NSAttributedString.Key.paragraphStyle : paragraphStyle],
range: NSMakeRange(0, attributedString.length))
attributedString.addAttributes(
textAttributes,
range: NSMakeRange(0, attributedString.length))
let string:NSString = NSString(string: formattedString)
let rangeForBullet:NSRange = string.range(of: bulletPoint)
attributedString.addAttributes(bulletAttributes, range: rangeForBullet)
buffer.append(attributedString)
}
return buffer
}
A quick implementation of that using your image as sample data results in this:

Related

How do you bold the first line in an NSMutableParagraphStyle?

I have a class called "rectangle" to make custom UILabels. I override "draw" in the rectangle class. When I instantiate the label, I want the FIRST line of text to show up in bolded font. I know how to solve this by manually getting the range for each string... however, I have more than 300 strings to do. The strings are currently in an array, formatted like so: "Happy \n Birthday". How can I make the word "Happy" bold?
var messageText = "Happy \n Birthday"
let rectanglePath = UIBezierPath(rect: rectangleRect)
context.saveGState()
UIColor.white.setFill()
rectanglePath.fill()
context.restoreGState()
darkPurple.setStroke()
rectanglePath.lineWidth = 0.5
rectanglePath.lineCapStyle = .square
rectanglePath.lineJoinStyle = .round
rectanglePath.stroke()
let rectangleStyle = NSMutableParagraphStyle()
rectangleStyle.alignment = .center
let rectangleFontAttributes = [
.font: UIFont.myCustomFont(true),
.foregroundColor: UIColor.black,
.paragraphStyle: rectangleStyle,
] as [NSAttributedString.Key: Any]
let rectangleTextHeight: CGFloat = messageText.boundingRect(with: CGSize(width: rectangleRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: rectangleFontAttributes, context: nil).height
context.saveGState()
context.clip(to: rectangleRect)
messageText.draw(in: CGRect(x: rectangleRect.minX, y: rectangleRect.minY + (rectangleRect.height - rectangleTextHeight) / 2, width: rectangleRect.width, height: rectangleTextHeight), withAttributes: rectangleFontAttributes)
context.restoreGState()
You can find the first by separating the string by newline:
let firstLine = "Happy \n Birthday".split(separator: "\n").first
This will give you the first line of the string. (long text multi lining doesn't count) then you can find the range using this and apply the bold effect.
How this works:
You need to set the label the way that accepts multiline:
Find the range of first line
Convert it to nsRange
Apply attributes to the range
Here is a fully working example:
import UIKit
import PlaygroundSupport
extension StringProtocol where Index == String.Index {
func nsRange(from range: Range<Index>) -> NSRange {
return NSRange(range, in: self)
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.numberOfLines = 0
label.text = "Happy \n Birthday"
label.textColor = .black
let text = "Happy \n Birthday"
let attributedString = NSMutableAttributedString(string: text)
let firstLine = text.split(separator: "\n").first!
let range = text.range(of: firstLine)!
attributedString.addAttributes([.font : UIFont.boldSystemFont(ofSize: 14)], range: text.nsRange(from: range))
label.attributedText = attributedString
label.sizeToFit()
view.addSubview(label)
self.view = view
}
}
PlaygroundPage.current.liveView = MyViewController()

UILabel - How to add space between lines in Swift 3

I've UILabel with paragraphic information (multiline text) and I want add some spaces between lines, similar to this image.
Please help me to do it. I've tried to checkout all documentation of apple developer regarding Label and line spacing but could find.
From Interface Builder (Storyboard/XIB):
Programmatically:
SWift 4
Using label extension
extension UILabel {
// Pass value for any one of both parameters and see result
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// Line spacing attribute
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString
Swift 3
let label = UILabel()
let stringValue = "Sample text"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
attrString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
label.attributedText = attrString
#IBOutlet weak var myLabel: UILabel!
let textForLabel = “Lorem Ipsum is simply dummy text of the printing and
typesetting industry. Lorem Ipsum has been the industry’s standard dummy text
ever since the 1500s, when an unknown printer took a galley of type and
scrambled it to make a type specimen book. It has survived not only five
centuries, but also the leap into electronic typesetting, remaining
essentially unchanged.”
let paragraphStyle = NSMutableParagraphStyle()
//line height size
paragraphStyle.lineSpacing = 1.4
let attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle,
range:NSMakeRange(0, attrString.length))
myLabel.attributedText = attrString
myLabel.textAlignment = NSTextAlignment.Center

How to calculate height of nsattributed string with line spacing dynamically

im trying to calculate the height of a UILabel with LineSpacing attribute. The weird thing is that calculated value of the height of the normal label.text is lower then the label.attributedText with its lineheight. it looks like i'm doing something wrong, but cant find what, so please help :D.
The provided code is specially made for SO to make it compact and clear, it is implemented differently in my project.
extension NSAttributedString {
func heightWithWidth(width: CGFloat) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.max)
let boundingBox = self.boundingRectWithSize(maxSize, options: [.UsesLineFragmentOrigin, .UsesFontLeading, .UsesDeviceMetrics], context: nil)
return boundingBox.height
}
}
extension UILabel {
func getHeightWithGivenWidthAndLineHeight(lineHeight: CGFloat, labelWidth: CGFloat) -> CGFloat {
let text = self.text
if let text = text {
let attributeString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = lineHeight
attributeString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(0, text.characters.count))
let height = attributeString.heightWithWidth(labelWidth)
self.attributedText = attributeString
return height
}
return 0
}
I call this by
let contentHeight = contentLabel.text! == "" ? 0 : contentLabel.getHeightWithGivenWidthAndLineHeight(3, labelWidth: labelWidth)
Working with normal strings (without spacing) works perfectly, when i use attributedstring with lineSpacing it fails to calculate the correct value.
You can just use UILabel's sizeThatFits. For example:
let text = "This is\nSome\nGreat\nText"
let contentHeight = contentLabel.text! == "" ? 0 : contentLabel.getHeightWidthGivenWidthAndLineHeight(6, labelWidth: labelWidth)
//returns 73.2
But just setting
contentLabel.attributedText = contentLabel.attributedString //attributedString is same as getHeightWidthGivenWidthAndLineHeight
let size = contentLabel.sizeThatFits(contentLabel.frame.size)
//returns (w 49.5,h 99.5)
Code for attributedString added to your extension, if you need to see that:
var attributedString:NSAttributedString?{
if let text = self.text{
let attributeString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = 6
attributeString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(0, text.characters.count))
return attributeString
}
return nil
}
I updated my Extension this way to set the line height and return the new label height at the same time. Thanx to beyowulf
extension UILabel {
func setLineHeight(lineHeight: CGFloat, labelWidth: CGFloat) -> CGFloat {
let text = self.text
if let text = text {
let attributeString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = lineHeight
attributeString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(0, text.characters.count))
self.attributedText = attributeString
return self.sizeThatFits(CGSize(width: labelWidth, height: 20)).height
}
return 0
}
}

Is it possible to set a different colour for one character in a UILabel?

I've been working on a small project using Xcode. It uses a lot of labels, textfields, etc. I've finished with most of the layout, the constrains, and the forms, titles, etc. After which, the client announces that for all required fields, there should be a red asterisk next to the label.
Call me lazy, but I'd rather not go back to all of my forms, add in a lot of labels with asterisks on them, and re-do my auto-layout to accommodate the new labels.
So, is there a way to change the colour of a specific character (in this case, the asterisk) in a UILabel, while the rest of the text stays black?
You can use NSMutableAttributedString.
You can set specific range of your string with different color, font, size, ...
E.g:
var range = NSRange(location:2,length:1) // specific location. This means "range" handle 1 character at location 2
attributedString = NSMutableAttributedString(string: originalString, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 18.0)!])
// here you change the character to red color
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: range)
label.attributedText = attributedString
Ref: Use multiple font colors in a single label - Swift
You can change a lot of attribute of String.
Ref from apple: https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSMutableAttributedString_Class/index.html
let text = "Sample text *"
let range = (text as NSString).rangeOfString("*")
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor() , range: range)
//Apply to the label
myLabel.attributedText = attributedString;
UILabel have an .attributedText property of type NSAttributedString.
Declaration
#NSCopying var attributedText: NSAttributedString?
You let your asterix * have a single .redColor() attribute (NSForegroundColorAttributeName), whereas the rest of the new label simply uses the same text as before, however also contained in an NSAttributedTextString.
An example follows below using a function to repeatedly update your existing labels to attributed strings prefixed with a red *:
class ViewController: UIViewController {
#IBOutlet weak var myFirstLabel: UILabel!
#IBOutlet weak var mySecondLabel: UILabel!
let myPrefixCharacter = "*"
let myPrefixColor = UIColor.redColor()
// ...
override func viewDidLoad() {
super.viewDidLoad()
// ...
/* update labels to attributed strings */
updateLabelToAttributedString(myFirstLabel)
updateLabelToAttributedString(mySecondLabel)
// ...
}
func updateLabelToAttributedString(label: UILabel) {
/* original label text as NSAttributedString, prefixed with " " */
let attr = [ NSForegroundColorAttributeName: myPrefixColor ]
let myNewLabelText = NSMutableAttributedString(string: myPrefixCharacter, attributes: attr)
let myOrigLabelText = NSAttributedString(string: " " + (label.text ?? ""))
/* set new label text as attributed string */
myNewLabelText.appendAttributedString(myOrigLabelText)
label.attributedText = myNewLabelText
}
// ...
}
Swift 4
(Note: notation for attributed string key is changed in swift 4)
Here is an extension for NSMutableAttributedString, that add/set color on string/text.
extension NSMutableAttributedString {
func setColor(color: UIColor, forText stringValue: String) {
let range: NSRange = self.mutableString.range(of: stringValue, options: .caseInsensitive)
self.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: range)
}
}
Now, try above extension with UILabel and see result
let label = UILabel()
label.frame = CGRect(x: 40, y: 100, width: 280, height: 200)
let red = "red"
let blue = "blue"
let green = "green"
let stringValue = "\(red)\n\(blue)\n&\n\(green)"
label.textColor = UIColor.lightGray
label.numberOfLines = 0
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: stringValue)
attributedString.setColor(color: UIColor.red, forText: red) // or use direct value for text "red"
attributedString.setColor(color: UIColor.blue, forText: blue) // or use direct value for text "blue"
attributedString.setColor(color: UIColor.green, forText: green) // or use direct value for text "green"
label.font = UIFont.systemFont(ofSize: 26)
label.attributedText = attributedString
self.view.addSubview(label)
func updateAttributedStringWithCharacter(title : String, uilabel: UILabel) {
let text = title + "*"
let range = (text as NSString).range(of: "*")
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.red , range: range)
uilabel.attributedText = attributedString }
I know this is an old post, but i want to share my approach (which is based on the answer from dfri) i just made it a function for convenience.
func lastCharOnColor(label: UILabel, color: UIColor, length: Int) {
//First we get the text.
let string = label.text
//Get number of characters on string and based on that get last character index.
let characterCounter = string?.characters.count
let lastCharacterIndex = characterCounter!-1
//Set Range
let range = NSRange(location: lastCharacterIndex, length: length)
let attributedString = NSMutableAttributedString(string: string!, attributes: nil)
//Set label
attributedString.addAttribute(NSForegroundColorAttributeName, value: color, range: range)
label.attributedText = attributedString
}
I use this function to just set the last character from a label to a certain color like this:
lastCharOnColor(label: self.labelname, color: UIColor.red, length: 1)
Hope this helps anyone.
Here is an extension for simply making mandatory labels by appending a red *
Swift 5
extension UILabel {
func makeTextMandatory() {
let text = self.text ?? "" + " *"
let range = (text as NSString).range(of: " *")
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.red , range: range)
self.attributedText = attributedString
}
}
Usage :
dobLabel.makeTextMandatory()

How control line-spacing of UILabels?

I'm using CustomCell instead of UITableViewCell on UITableView.
I put two UILables on CustomCell.
Refered this site: here , but I failed...
Question : How can I control line spacing of UILabels?
Starting from iOS 6 you can set an attributed string to the UILabel. Check the following :
NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
[paragrahStyle setLineSpacing:40];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [labelText length])];
cell.label.attributedText = attributedString;
Programmatically:
SWift 4
Using label extension
extension UILabel {
// Pass value for any one of both parameters and see result
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// Line spacing attribute
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString
Swift 3
Just copy & execute this code to see result.
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
attrString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
label.attributedText = attrString
From Interface Builder:

Resources