I have a UIButton
that holds an NSAttributedString
that contains an icon (which is an NSAttributedString
itself) followed by a text.
It looks something like this:
I want to make it look like this when the device is configured for an RTL language (e.g. Arabic, Hebrew):
The string is built like this:
var iconText = NSAttributedString(fontName: fontName, fontSize: fontPointSize, fontValue: fontValue, color: color ?? iconColor)
let iconTextRange = NSRange(location: 0, length: iconText.count)
let iconAttrs: [NSAttributedString.Key: Any] = [.font: icon.font(pointSize: pointSize),
.foregroundColor: iconColor]
if !text.isEmpty {
iconText = "\(iconText) \(text)"
}
let attributeString = NSMutableAttributedString(string: iconText, attributes: textAttrs)
attributeString.addAttributes(iconAttrs, range: iconTextRange)
return attributeString
As you can see, first the icon is created using a font, then it's concatenated to the text.
In other parts of the app, I managed to make NSAttributedString
's RTL-compliant with this little piece of code:
public extension NSAttributedString {
/// Returns an identical attributed string that'll adjust its direction based on the device's configured language.
func rightToLeftAdjusted() -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.baseWritingDirection = .natural
let attrs: [NSAttributedString.Key: Any] = [.paragraphStyle: paragraphStyle]
let range = NSRange(location: 0, length: length)
let copy = NSMutableAttributedString(attributedString: self)
copy.addAttributes(attrs, range: range)
return copy
}
}
Unfortunately, in this specific case, it doesn't seem to work, the star ALWAYS stays to the left of the text.
Are there any other ways of achieving this?
Thanks to everyone who helped on the matter.
The solution I got to was, instead of appending the icon as a font, I converted it to a UIImage
, then to an NSTextAttachment
.
That way, the image flipped to the right side of the text when the app is running in RTL mode.
func addDecorator(icon: DrawbleIcon, pointSize: CGFloat, to text: String, textAttrs: [NSAttributedString.Key: Any]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
appendIcon(to: attributedString, icon: icon, pointSize: pointSize)
// Append text
if !text.isEmpty {
attributedString.append(NSAttributedString(string: text, attributes: textAttrs))
}
// Adjust text to right-to-left devices
let writingDirection: NSWritingDirection = UIApplication.isRightToLeft ? .rightToLeft : .leftToRight
let leftToRightAttrs: [NSAttributedString.Key: Any] = [.writingDirection: [NSNumber(value: writingDirection.rawValue)]]
attributedString.addAttributes(leftToRightAttrs, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
private func appendIcon(to attributedString: NSMutableAttributedString, icon: DrawbleIcon, pointSize: CGFloat) {
let iconColor = icon.foreColor
let iconAttrs: [NSAttributedString.Key: Any] = [.font: icon.font(pointSize: pointSize),
.foregroundColor: iconColor]
if let iconText = icon.attributedString(fontPointSize: pointSize, color: nil),
let font = iconText.attributes(at: 0, effectiveRange: nil)[.font] as? UIFont,
let iconImage = NSAttributedString(string: iconText.string, attributes: iconAttrs).image() {
let textAttachment = NSTextAttachment(image: iconImage)
let imageSize = iconImage.size
// Fix image height - without this, the image is higher than the text.
textAttachment.bounds = CGRect(x: CGFloat(0), y: (font.capHeight - imageSize.height) / 2, width: imageSize.width, height: imageSize.height)
attributedString.append(NSAttributedString(attachment: textAttachment))
attributedString.append(NSAttributedString(string: " "))
}
}
private extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: size).image { _ in
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
}
}
}
private extension NSAttributedString {
func image(size: CGSize? = nil) -> UIImage? {
string.image(withAttributes: attributes(at: 0, effectiveRange: nil), size: size)
}
}