2

I have an attributed string in Swift which displays an icon next to a username. This works great, my implementation looks like this:

attributedUsername = NSMutableAttributedString(string: "username")
let iconAttachment = NSTextAttachment()
let iconImage = UIImage(named: "userIcon")
iconAttachment.image = iconImage
iconAttachment.bounds = CGRect(x: 0, y: -3, width: 14, height: 14)
let iconString = NSAttributedString(attachment: verifiedAttachment)
attributedUsername.append(iconString)

usernameLabel.attributedText = attributedUsername

However, sometimes a username is too large to fit on one line, wrapping the username on a second line (numberOfLines = 0). This is OK, but if the username is just long enough to fit on-screen, then the image wraps to the next line. I am wondering if there is any way to keep the icon wrapped to the end of the username. What I am looking to achieve, where * is the icon, is:

username *

longer username *

a very long
username *

instead of:

username *

longer username *

a very long username
*

So basically I want the icon to stick together with the last part of the username (if possible). If the username doesn't contain spaces and is too long, then it should just be wrapped on the next line because that would be standard implementation. Any suggestions?

PennyWise
  • 595
  • 2
  • 12
  • 37
  • Wouldn't it be better to have the image beforehand, with the username after? Then you can use `.lineBreakMode = .byTruncatingTail` so the username ends with `a very long user...` if it is too long. I just think it would look visually wrong for a username to wrap over multiple lines. – George Apr 06 '19 at 19:54
  • That wouldn't fit my design unfortunately. The situation is not common - a username usually truncates indeed and doesn't wrap on multiple lines, however there are situations where it can happen. The example where it happens now is to provide feedback about the likes underneath a post: `Username, username and 13 others liked your post.` - this could have two long usernames truncate and it might happen right before the icon, so then I want to keep them together. – PennyWise Apr 06 '19 at 19:59
  • Ok I understand now. Just a suggestion, so you obviously don't have to do it, but you could create a Twitter-like UI on mobile. When you go to the notifications section, there will be profile photos in a row. Underneath, it says something like `Username and 2 others liked your reply`. Maybe that could look cleaner and more organised? – George Apr 06 '19 at 20:04
  • It is an option to show the profile pictures. It would be nice to display this like Twitter, Instagram and other social networks where it shows the first, let's say 10 profile pictures and +13 at the end. However, this would be a bit strange when there are only one or two likes. Therefor we are now looking into a way to display it like the following situations: `Username liked your post.`, `Username and Username liked your post` and if there are more likes it would go into `Username, Username and 1 other liked your post.` - but the issue there is thus what I asked in OP. – PennyWise Apr 06 '19 at 20:09
  • For the record: it's not about notifications but rather about a post and displaying the likes underneath. Think Instagram feed, right beneath a picture. – PennyWise Apr 06 '19 at 20:10
  • I made an image, I imagine you want it look like [this](https://imgur.com/a/uzqrnj2)? Feel free to add it to your post if it is what you want. – George Apr 06 '19 at 20:35
  • Precisely. I am now looking into the solution provided by @ajeferson which looks very promising, if you are interested in the same. – PennyWise Apr 06 '19 at 20:36
  • I'm just looking around :) – George Apr 06 '19 at 20:36

1 Answers1

2

Well, I am not sure if you can do this by setting some option in NSAttributedString, but you can easy achieve that with a simple algorithm.

First, move the code that creates the attributed string to a function, since we'll be using it to calculate the width. Make sure to also set the font attribute, so it's possible to get the correct size out of the attributed string:

func attributedString(for text: String) -> NSAttributedString {
    let attributedText = NSMutableAttributedString(string: text)
    let iconAttachment = NSTextAttachment()
    let iconImage = UIImage(named: "star")
    iconAttachment.image = iconImage
    iconAttachment.bounds = CGRect(x: 0, y: -3, width: 14, height: 14)
    let iconString = NSAttributedString(attachment: iconAttachment)
    attributedText.append(iconString)
    attributedText.setAttributes([.font: UIFont(name: "Avenir-Book", size: 15)!],
                                 range: NSRange((text.startIndex..<text.endIndex), in: text))
    return attributedText
}

Then:

let text = "some really really really really long usernameeeeeeeee"
let attributedText = attributedString(for: text)
let maxWidth = ... 

if attributedText.size().width > maxWidth { // A line break is required
    let lastWord = text.components(separatedBy: " ").last!
    let attributedLastWord = attributedString(for: lastWord)
    if attributedLastWord.size().width < maxWidth { // Forcing image to stick to last word
        var fixedText = text
        fixedText.insert("\n", at: text.index(text.endIndex, offsetBy: -lastWord.count))
        label.attributedText = attributedString(for: fixedText)
    } else {
        label.attributedText = attributedText
    }
} else {
    label.attributedText = attributedText
}

Of course you will want to remove the force unwrap and other not so good practices. Those are just for brevity, though. I hope you got the idea.

alanpaivaa
  • 1,959
  • 1
  • 14
  • 23
  • Very interesting approach. This would also work when there are 2 usernames in my string, right? As it works on the last component. E.g. the string could be "Username *, username * and 12 others liked your post.", it would clip on the second one, right? – PennyWise Apr 06 '19 at 20:34
  • Yeah, it should clip on the last one. – alanpaivaa Apr 06 '19 at 20:46