【问题标题】:SWIFT: Make voice over stop reading a truncated stringSWIFT:让语音停止阅读截断的字符串
【发布时间】:2021-04-16 13:35:43
【问题描述】:

在标签中,我设置了一个大字符串并设置了它的 .lineBreakMode = .buTrucatingTail,但是当我这样做并尝试在其上使用 VoiceOver 时,我最终会读取整个字符串,而不仅仅是屏幕上的内容,这里是一个例子:

string.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
srring.lineBreakMode = .buTrucatingTail

这是屏幕上显示的内容:

Lorem ipsum dolor sit amet,consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua。 Ut enim ad minim veniam, quis nostrud exercitation ullamco...

但是旁白会读出整个字符串。

有谁知道如何让它在截断三个点处停止?或者如何将可访问性标签设置为屏幕上的内容(因为文本长度会因设备而异)?

提前致谢。

【问题讨论】:

  • 你试过string.accessibilityHint = "Lorem ipsum dolor sit amet short"吗?
  • 我不太明白简短的事情
  • 截断仅用于显示。 VoiceOver 将始终读出字符串的整个文本。为了达到您的目标,您可以使用 TextKit 来获取最后看到的字符的索引,以便创建要读取的可访问文本。 ????
  • @XLE_22 这似乎很有希望,你能说得更详细一点吗?
  • 您不应该尝试截断accessibilityLabel 中的字符串。这用于所有自信的技术,而不仅仅是 VoiceOver。您在问题中注意到截断随设备大小而变化,您应该将 VoiceOver 视为可以“显示”任意长度字符串的设备。

标签: ios swift xcode accessibility voiceover


【解决方案1】:

[...] 当我这样做并尝试对其使用 VoiceOver 时,我最终会阅读整个字符串,而不仅仅是屏幕上的内容 [...] 旁白会阅读整个字符串。

正如我在评论中所说,截断仅用于显示。
VoiceOver 将始终读出字符串的整个文本,它不关心屏幕上的内容。
它与accessibilityLabel 完全一样,可能与显示的不同:这里,accessibilityLabel 是整个字符串内容。 ?

有谁知道如何让它在截断三个点处停止?

你的问题引起了我的好奇心,我决定研究这个问题。
我发现了一个使用 TextKit 的解决方案,其基础知识应该是已知的:如果不是这样的话⟹Apple doc?

⚠️ 主要思想是确定最后一个可见字符的索引,以便提取显示的子字符串并将其分配给最后的accessibilityLabel。 ⚠️

初始假设:使用 UILabel 使用与问题中定义的 lineBreakMode 相同的@(byTruncationTail)。
我在操场上编写了以下整个代码,并使用 Xcode 空白项目确认结果以进行检查。?

import Foundation
import UIKit

extension UILabel {
    var displayedLines: (number: Int?, lastIndex: Int?) {
    
        guard let text = text else {
            return (nil, nil)
        }
    
    let attributes: [NSAttributedString.Key: UIFont] = [.font:font]
    let attributedString = NSAttributedString(string: text,
                                              attributes: attributes)
    
    //Beginning of the TextKit basics...
    let textStorage = NSTextStorage(attributedString: attributedString)
    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)
    
    let textContainerSize = CGSize(width: frame.size.width,
                                   height: CGFloat.greatestFiniteMagnitude)
    let textContainer = NSTextContainer(size: textContainerSize)
    textContainer.lineFragmentPadding = 0.0 //Crucial to get the most accurate results.
    textContainer.lineBreakMode = .byTruncatingTail
    
    layoutManager.addTextContainer(textContainer)
    //... --> end of the TextKit basics
    
    var glyphRangeMax = NSRange()
    let characterRange = NSMakeRange(0, attributedString.length)
    //The 'glyphRangeMax' variable will store the appropriate value thanks to the following method.
    layoutManager.glyphRange(forCharacterRange: characterRange,
                             actualCharacterRange: &glyphRangeMax)
    
    print("line : sentence -> last word")
    print("----------------------------")
    
    var truncationRange = NSRange()
    var fragmentNumber = ((self.numberOfLines == 0) ? 1 : 0)
    var globalHeight: CGFloat = 0.0
    
    //Each line fragment of the layout manager is enumerated until the truncation is found.
    layoutManager.enumerateLineFragments(forGlyphRange: glyphRangeMax) { rect, usedRect, textContainer, glyphRange, stop in
        
        globalHeight += rect.size.height
        
        if (self.numberOfLines == 0) {
            if (globalHeight > self.frame.size.height) {
                print("⚠️ Constraint ⟹ height of the label ⚠️")
                stop.pointee = true //Stops the enumeration and returns the results immediately.
            }
        } else {
            if (fragmentNumber == self.numberOfLines) {
                print("⚠️ Constraint ⟹ number of lines ⚠️")
                stop.pointee = true
            }
        }
        
        if (stop.pointee.boolValue == false) {
            fragmentNumber += 1
            truncationRange = NSRange()
            layoutManager.characterRange(forGlyphRange: NSMakeRange(glyphRange.location, glyphRange.length),
                                         actualGlyphRange: &truncationRange)
            
            let sentenceInFragment = self.sentenceIn(truncationRange)
            let line = (self.numberOfLines == 0) ? (fragmentNumber - 1) : fragmentNumber
            print("\(line) : \(sentenceInFragment) -> \(lastWordIn(sentenceInFragment))")
        }
    }
    
    let lines = ((self.numberOfLines == 0) ? (fragmentNumber - 1) : fragmentNumber)
    return (lines, (truncationRange.location + truncationRange.length - 2))
}

//Function to get the sentence of a line fragment
func sentenceIn(_ range: NSRange) -> String {
    
    var extractedString = String()
    
    if let text = self.text {
        
        let indexB = text.index(text.startIndex,
                                offsetBy:range.location)
        let indexF = text.index(text.startIndex,
                                offsetBy:range.location+range.length)
        
        extractedString = String(text[indexB..<indexF])
    }
    return extractedString
}
}


//Function to get the last word of the line fragment
func lastWordIn(_ sentence: String) -> String {

    var words = sentence.components(separatedBy: " ")
    words.removeAll(where: { $0 == "" })

    return (words.last == nil) ? "o_O_o" : (words.last)!
}

完成后,我们需要创建标签:

let labelFrame = CGRect(x: 20,
                        y: 50,
                        width: 150,
                        height: 100)
var testLabel = UILabel(frame: labelFrame)
testLabel.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

...并测试代码:

testLabel.numberOfLines = 3

if let _ = testLabel.text {
    let range = NSMakeRange(0,testLabel.displayedLines.lastIndex! + 1)
    print("\nNew accessibility label to be implemented ⟹ \"\   (testLabel.sentenceIn(range))\"")
}

playground 的 Debug Area 用 3 行显示结果: ...如果打算使用“尽可能多的行”,我们会得到: 这似乎工作得很好。 ?

在您自己的代码中包含此基本原理,您将能够让 VoiceOver 读取屏幕上显示的标签的截断内容。 ??

【讨论】:

  • Thnx,这是一个了不起的详尽答案,感谢您付出了很多努力和时间来编写它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
相关资源
最近更新 更多