12

So I want to make something like this using swift and xcode:

enter image description here

Where I get each dot from an array. What I have thought is to make a UILabel and make a for loop that iterates over the array and in each iteration it adds to the label \u{2022} + content. I know \u{2022} is the dot point in unicode, the problem is that I need a way to make the list divided in two columns as shown and to make the dot point color yellow. This cant be done if I add the dots programmatically as I described above because the default color would be black. As the number of dots varies from the array contents for example if the array is of size 3 then only 3 dots would show 2 in the left and one to the right I need a way to meet this requirement, the other method I thought was of having two table views that takes half the screen and add this elements to each table view depending on the array. What should be the best practice here or is there a way to make this in the storyboard in a form that is dependent of an array.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
ravelinx
  • 1,557
  • 4
  • 18
  • 26
  • Might need to use CoreText for that , see the tutorial [here](https://www.raywenderlich.com/4147/core-text-tutorial-for-ios-making-a-magazine-app) – Tj3n Mar 29 '17 at 02:53
  • 2
    Have you looked at UICollectionView? I haven't used it myself much, but this looks like a perfect use case for this. – Thomas Müller Mar 29 '17 at 03:05
  • So what you really want to know is how to draw text in two columns, and the bullet part is just a red herring, right? – matt Mar 29 '17 at 03:14
  • "This cant be done if I add the dots programmatically as I described above because the default color would be black." Flapdoodle. Changing text color programmatically is easy. – matt Mar 29 '17 at 03:16
  • @matt yeah thats somewhat the idea. Yes programmatically I can change the color of an entire text but not a part of it, this would imply that the dot and text should be two different labels and that would make the problem more complex. – ravelinx Mar 29 '17 at 03:18
  • No, you're wrong. An attributed string lets you color individual glyphs. In fact the whole thing could be done with a single view if you wanted. – matt Mar 29 '17 at 03:23
  • @matt Well then that simplifies the problem to organizing the data in the two columns – ravelinx Mar 29 '17 at 03:26
  • 1
    That's right, and I've got an example that shows you how to do that. https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch10p543drawingWithTextKit/ch23p815attributedStringDrawing3/StyledText.swift – matt Mar 29 '17 at 03:41
  • 1
    Use collectionview or stackview with UILabel attributedString http://stackoverflow.com/a/42721312/7250862 – RajeshKumar R Mar 29 '17 at 05:02

4 Answers4

13

I was not happy with the solutions above. So here is a Swifty function to get a bullet point list:

func bulletPointList(strings: [String]) -> NSAttributedString {
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.headIndent = 15
    paragraphStyle.minimumLineHeight = 22
    paragraphStyle.maximumLineHeight = 22
    paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: 15)]

    let stringAttributes = [
        NSAttributedString.Key.font: regularSystem(size: 22),
        NSAttributedString.Key.foregroundColor: UIColor.black,
        NSAttributedString.Key.paragraphStyle: paragraphStyle
    ]

    let string = strings.map({ "•\t\($0)" }).joined(separator: "\n")

    return NSAttributedString(string: string,
                              attributes: stringAttributes)
}

And this is how you use it:

label.numberOfLines = 0
label.attributedText = bulletPointList(strings: ["Foo", "Bar", "Lol"])
Alessandro Francucci
  • 1,528
  • 17
  • 25
Nico S.
  • 3,056
  • 1
  • 30
  • 64
  • this is good for the list without changing bullet color. So it has same color as text and I was looking for this particular styling or else I could have just put the bullet in the text. and keep it simple. – CodeOverRide Mar 04 '21 at 01:23
10

For Swift 5, you can use this class:

class NSAttributedStringHelper {
    static func createBulletedList(fromStringArray strings: [String], font: UIFont? = nil) -> NSAttributedString {

        let fullAttributedString = NSMutableAttributedString()
        let attributesDictionary: [NSAttributedString.Key: Any]
    
        if let font = font {
            attributesDictionary = [NSAttributedString.Key.font: font]
        } else {
            attributesDictionary = [NSAttributedString.Key: Any]()
        }
   
        for index in 0..<strings.count {
            let bulletPoint: String = "\u{2022}"
            var formattedString: String = "\(bulletPoint) \(strings[index])"
        
            if index < strings.count - 1 {
                formattedString = "\(formattedString)\n"
            }
        
            let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: formattedString, attributes: attributesDictionary)
            let paragraphStyle = NSAttributedStringHelper.createParagraphAttribute()
   attributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraphStyle], range: NSMakeRange(0, attributedString.length))
        fullAttributedString.append(attributedString)
       }
    
        return fullAttributedString
    }

    private static func createParagraphAttribute() -> NSParagraphStyle {
        let paragraphStyle: NSMutableParagraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: 15, options: NSDictionary() as! [NSTextTab.OptionKey : Any])]
        paragraphStyle.defaultTabInterval = 15
        paragraphStyle.firstLineHeadIndent = 0
        paragraphStyle.headIndent = 11
        return paragraphStyle
    }
}

To use it:

let stringArray = ["first row", "second row", "third row"]
label.attributedText = NSAttributedStringHelper.createBulletedList(fromStringArray: stringArray, font: UIFont.systemFont(ofSize: 15))
pableiros
  • 14,932
  • 12
  • 99
  • 105
  • 1
    This solution is just perfect. Without much effort, it outputs the bullet list like in any good looking document. Thanks for sharing this code. – zeeshan Jul 26 '19 at 13:23
  • thanks for this cool snippet, one question, how could you put all the value in the center? I tried to do the text alignment in your helper class to center, but it didn't work, also my storyboard alignment is already set on center. – Arash Afsharpour Dec 08 '19 at 14:42
  • Thanks for this solution. However, the force cast while defining tab stops can be avoided. `[NSTextTab(textAlignment: .left, location: 15, options: [NSTextTab.OptionKey: Any]())]` Also, we can use NSRange instead of NSMakeRange while adding the attributes. `NSRange(location: 0, length: attributedString.length)` – James Selvakumar Nov 30 '21 at 02:12
8

use 2 labels inside a view for the columns. both labels being multulined

class Helper {

    static func bulletedList(strings:[String], textColor:UIColor, font:UIFont, bulletColor:UIColor, bulletSize:BulletSize) -> NSAttributedString {
        let textAttributesDictionary = [NSFontAttributeName : font, NSForegroundColorAttributeName:textColor]

        let bulletAttributesDictionary = [NSFontAttributeName : font.withSize(bulletSize.rawValue), NSForegroundColorAttributeName:bulletColor]
        let fullAttributedString = NSMutableAttributedString.init()

        for string: String in strings
        {
            let bulletPoint: String = "\u{2022}"
            let formattedString: String = "\(bulletPoint) \(string)\n"
            let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: formattedString)
            let paragraphStyle = createParagraphAttribute()

            attributedString.addAttributes([NSParagraphStyleAttributeName: paragraphStyle], range: NSMakeRange(0, attributedString.length))
            attributedString.addAttributes(textAttributesDictionary, range: NSMakeRange(0, attributedString.length))

            let string:NSString = NSString(string: formattedString)
            let rangeForBullet:NSRange = string.range(of: bulletPoint)

            attributedString.addAttributes(bulletAttributesDictionary, range: rangeForBullet)
            fullAttributedString.append(attributedString)
        }
        return fullAttributedString
    }

    static func createParagraphAttribute() -> NSParagraphStyle {

        var paragraphStyle: NSMutableParagraphStyle
        paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: 15, options: NSDictionary() as! [String : AnyObject])]
        paragraphStyle.defaultTabInterval = 15
        paragraphStyle.firstLineHeadIndent = 0
        paragraphStyle.lineSpacing = 3
        paragraphStyle.headIndent = 10
        return paragraphStyle
    }
}

and simply use Helper.bulletedList to create your bulletted list as Attributed text for the label

pableiros
  • 14,932
  • 12
  • 99
  • 105
simply_me
  • 384
  • 1
  • 11
2

In Swift tabStop will work with the following changes

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = 0 // 0 means unlimited
paragraphStyle.maximumLineHeight = 0
paragraphStyle.firstLineHeadIndent = 30
paragraphStyle.headIndent = 0
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: 15, options: Dictionary<NSTextTab.OptionKey, Any>())]
paragraphStyle.defaultTabInterval = 10  //changing defaultTabInterval changes the distance between black dot & text
paragraphStyle.lineSpacing = 5
Zubair
  • 915
  • 2
  • 9
  • 28