Ivan Barabanshchykov
Ivan Barabanshchykov

Reputation: 181

Is there a way to make correct hyphenation on UITextView/UILabel with text language different than in Settings?

My problem is that I have blocks of texts in different languages in my app, in some languages(hi german) words can be tooo long and I am using hyphenationFactor to have proper layout... But as said in the docs: "This property detects the user-selected language by examining the first item in preferredLanguages." and in my case there could be english in settings and german text on screen, so the grammar rules for hyphenation is wrong.

Is there a way to change language for this property or maybe some hack around of this? Thank you in advance!

Upvotes: 2

Views: 789

Answers (1)

Ivan Barabanshchykov
Ivan Barabanshchykov

Reputation: 181

Update 2022:

Before iOS 15, Apple strictly followed soft hyphen, while in iOS 15 they now only consider those as hyphenation opportunities.

So basically now we should use languageIdentifier attribute, lesser opportunities for our own custom hyphenation rules (like remove ugly, one side short, hyphenation break), but works grammatically correctly.

https://developer.apple.com/documentation/foundation/attributescopes/foundationattributes/3802173-languageidentifier

Old answer:

In the end a real help was this extension by Frank Rausch https://gist.github.com/frankrausch/0d2c91fad5e417a84aaa43bfb9c9aec8

Made some addition to detect dominant language of string and limit the words that should hyphenate by characters count.

import Foundation
import NaturalLanguage

extension String {
    
    func detectedLanguage(for string: String) -> String? {
        let recognizer = NLLanguageRecognizer()
        recognizer.processString(string)
        guard let languageCode = recognizer.dominantLanguage?.rawValue else { return nil }
        let detectedLanguage = Locale.current.localizedString(forIdentifier: languageCode)
        return detectedLanguage
    }
    
    func autoHyphenated() -> String {
        return self.hyphenated(languageCode: detectedLanguage(for: self) ?? "")
    }
    
    func hyphenated(languageCode: String) -> String {
        let locale = Locale(identifier: languageCode)
        return self.hyphenated(locale: locale)
    }
    
    func hyphenated(locale: Locale, wordMinimumLenght: Int = 13) -> String {
    guard CFStringIsHyphenationAvailableForLocale(locale as CFLocale) else {     return self }
        
        var s = self
        
        var words = s.components(separatedBy: " ")
        
        for index in 0..<words.count {
            if words[index].count > wordMinimumLenght && !words[index].contains("-") {
                let fullRange = CFRangeMake(0, words[index].utf16.count)
                var hyphenationLocations = [CFIndex]()
                for (i, _) in words[index].utf16.enumerated() {
                    let location: CFIndex = CFStringGetHyphenationLocationBeforeIndex(words[index] as CFString, i, fullRange, 0, locale as CFLocale, nil)
                    if hyphenationLocations.last != location {
                        hyphenationLocations.append(location)
                    }
                }
                for l in hyphenationLocations.reversed() {
                    guard l > 0 else { continue }
                    let strIndex = String.Index(utf16Offset: l, in: words[index])
                    words[index].insert("\u{00AD}", at: strIndex)
                }
            }
        }

        s = words.joined(separator: " ")
        
        return s
    }
}

Upvotes: 2

Related Questions