skip to Main Content

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:

Button appearance in LTR

I want to make it look like this when the device is configured for an RTL language (e.g. Arabic, Hebrew):

Desired look in RTL mode

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?

2

Answers


  1. Chosen as BEST ANSWER

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

  2.             let preferredLanguage = NSLocale.preferredLanguages[0]
                if preferredLanguage ==  "en" {
                    button.semanticContentAttribute = .forceLeftToRight
                }else if preferredLanguage == "ar" {
                    button.semanticContentAttribute = .forceRightToLeft
                }
        
        
       
        The source code is at the below repository link:
    
    https://github.com/ahmetbostanciklioglu/ButtonAlignmentLeftToRightAndRightToLeft.git
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search