Issue with applying paragraph style in SwiftUI
Asked Answered
B

2

6

I have a string of text of Arabic and English words, I need to add attribute using NSMutableAttributedString and add paragraph styling with NSMutableParagraphStyle. The problem is paragraph style doesn't apply to the text and not sure why. First I am detecting Arabic characters and changing the color (it works), and then trying to change line spacing or alignment which is not working. Here is the code:

let arabicChars = try? NSRegularExpression(pattern: "[\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF\\s]+", options: [])
arabicChars?.enumerateMatches(in: pureText, options: [], range: range) { (result, _, _) in
    if let subStringRange = result?.range(at: 0) {
        stringText.addAttribute(.foregroundColor, value: UIColor(.brown), range: subStringRange)
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .right
        paragraphStyle.lineSpacing = 1.2
        paragraphStyle.lineHeightMultiple = 1.2
        stringText.addAttribute(.paragraphStyle, value: paragraphStyle, range: subStringRange)
        stringText.addAttribute(.font, value: UIFont(name: "CustomFont", size: 16)!, range: subStringRange)
    }
    }

And then using the function in SwiftUI like this:

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 15) {
                Text(word.word?.capitalized ?? "")
                    .font(.system(size: 32, weight: .bold, design: .serif))
            
                Text(AttributedString(applyAttributedStyle(word.meaning)))
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        .padding(.horizontal)
    }
}

Here is the image that shows current result (left) vs right result: enter image description here

Edit 1: It works fine with UIKit's UITextView but not working on SwiftUI's Text

Edit 2: I have fixed the issue using UIViewRepresentable with UITextView

Basaltware answered 27/2, 2023 at 23:4 Comment(5)
Can you show an example of the text you are using and how it is currently being displayed? Are you using the attributed string in a label or text view? Maybe the string contains some special RTL or LTR codes due to the mixed languages and that could be messing with the alignment.Strom
Can you include a picture of the result?Strom
@Strom I added the imageBasaltware
I suspect that while the VStack is wide, the individual Text elements are only as wide as their content, does setting a frame on each Text help?Defenestration
@TristanBurnside No, it doesn't affect the alignment of attributed texts, it's only dealing with the frame of Text objectBasaltware
B
5

The reason this is true is that the SwiftUI Text view does not support all AttributedString attributes.

In particular, it only supports the attributes defined in AttributeScopes.SwiftUIAttributes, which does not include .paragraphStyle.

Currently the only solution is dropping to UIKit/AppKit, as you found for yourself.

This is some useful background reading: https://dimillian.medium.com/swiftui-attributedstring-is-not-there-yet-63d49e9f9c16

Baronet answered 16/8, 2023 at 7:26 Comment(0)
C
0

Apple not suppourt .natrual in SwiftUI, they add suppourt for AttributedString in SwiftUI but paragraphstyle not suppourted

There is a trick that is working and depend only in pure SwiftUI I have created it here

for Text use NaturalText

        import SwiftUI
    import NaturalLanguage
    
    struct NaturalText: View {
        @Environment(\.layoutDirection) private var layoutDirection
    
        var text : String
    
        var body: some View {
                Text(text)
                .frame(maxWidth: .infinity, alignment: naturalAlignment)
                .multilineTextAlignment(naturalTextAlignment)
        }
        
        private var naturalAlignment: Alignment {
            guard let dominantLanguage = dominantLanguage else {
                // If we can't identify the strings language, use the system language's natural alignment
                return .leading
            }
                    
            
            switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
            case .leftToRight:
                if layoutDirection == .rightToLeft {
                    return .trailing
                } else {
                    return .leading
                }
               
            case .rightToLeft:
                if layoutDirection == .leftToRight {
                    return .trailing
                } else {
                    return .leading
                }
            case .natural:
                return .leading
                
            @unknown default:
                return .leading
            }
        }
    
    
        
        private var naturalTextAlignment: TextAlignment {
            guard let dominantLanguage = dominantLanguage else {
                // If we can't identify the strings language, use the system language's natural alignment
                return .leading
            }
            
            
            switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
            case .leftToRight:
                if layoutDirection == .rightToLeft {
                    return .trailing
                } else {
                    return .leading
                }
               
            case .rightToLeft:
                if layoutDirection == .leftToRight {
                    return .trailing
                } else {
                    return .leading
                }
            case .natural:
                return .leading
            @unknown default:
                return .leading
            }
        }
        
        private var dominantLanguage: String? {
            let firstChar = "\(text.first ?? " ")"
               return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue
        }
        
        
    }

for TextEditor use NaturalTextEditor

import SwiftUI
import NaturalLanguage

struct NaturalTextEditor: View {
    @Environment(\.layoutDirection) private var layoutDirection

   @Binding var text: String

    var body: some View {
        ZStack {
            TextEditor(text: $text)
                .frame(alignment: naturalAlignment)
                .multilineTextAlignment(naturalTextAlignment)
                .onAppear {
                    UITextView.appearance().backgroundColor = .clear
                }
         }
        
    }
    
    private var naturalAlignment: Alignment {
        guard let dominantLanguage = dominantLanguage else {
            // If we can't identify the strings language, use the system language's natural alignment
            return .leading
        }
                
        
        switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
        case .leftToRight:
            if layoutDirection == .rightToLeft {
                return .trailing
            } else {
                return .leading
            }
           
        case .rightToLeft:
            if layoutDirection == .leftToRight {
                return .trailing
            } else {
                return .leading
            }
        case .natural:
            return .leading
            
        @unknown default:
            return .leading
        }
    }
    
    private var naturalTextAlignment: TextAlignment {
        guard let dominantLanguage = dominantLanguage else {
            // If we can't identify the strings language, use the system language's natural alignment
            return .leading
        }
        
        
        switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
        case .leftToRight:
            if layoutDirection == .rightToLeft {
                return .trailing
            } else {
                return .leading
            }
           
        case .rightToLeft:
            if layoutDirection == .leftToRight {
                return .trailing
            } else {
                return .leading
            }
        case .natural:
            return .leading
            
        @unknown default:
            return .leading
        }
    }

     private var dominantLanguage: String? {
        
        let firstChar = "\(text.first ?? " ")"
           return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue 
}
}

struct NaturalTextEditor_Previews: PreviewProvider {
    static var previews: some View {
        NaturalTextEditor(text: .constant("Test !!"))
    }
}

how I solved this issue ?

1- I checked the app language

2- I checked the text content itself depend on first letters using Apple AI NaturalLanguage

3- depend on app language if it's English that mean leading is left and trailing is right, but if app language is Arabic that mean leading is right and trailing is left

so now I know if app lagnuage is English but text is Arabic it will be RTL if the text is english it will be LTR same thing if the app lagnuage is Arabic, if the text is English it will be LTR but if the text is arabic it will be RTL

I'm using this code in all my production apps, working 100% fine

the result will be as you want it

enter image description here

Cymophane answered 3/11, 2023 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.