SwiftUI:保留点击功能将独立文本改为段落式显示
解决方案:将独立文本合并为连续段落并保留交互
要实现将原有的独立换行文本改为连续段落展示,同时保留每个条目点击触发音频的功能,我们可以通过构建合并的富文本并精准识别点击位置来实现,适配iOS 16.0+和Xcode 15+环境。
步骤1:构建合并的富文本与索引范围映射
首先,我们需要将所有条目文本合并为一个完整的NSAttributedString,同时记录每个原条目对应的文本范围,这样后续可以通过点击位置反推对应的条目索引。
@State private var combinedNSAttributedText: NSAttributedString = NSAttributedString(string: "") @State private var nsIndexToRange: [Int: NSRange] = [:] // 富文本更新函数,在内容、语言、字体大小或选中状态变化时调用 private func updateCombinedText() { let mutableAttributedText = NSMutableAttributedString() nsIndexToRange.removeAll() for (index, sura) in content.enumerated() { let text = isBanglaSelected ? sura.transliterationBn ?? "" : sura.arabicUthmani ?? "" let currentLength = mutableAttributedText.length // 设置当前条目的字体与颜色 let font = isBanglaSelected ? UIFont(name: "Inter", size: customFontSize - 10) : UIFont(name: "KFGQPC Uthmanic Script HAFS", size: customFontSize) let textColor = index == suraDetailsVM.serial ? UIColor.deenThemeColor : UIColor.deenBlack let attributes: [NSAttributedString.Key: Any] = [ .font: font ?? UIFont.systemFont(ofSize: customFontSize), .foregroundColor: textColor ] // 添加当前条目文本 let segment = NSAttributedString(string: text, attributes: attributes) mutableAttributedText.append(segment) // 条目间添加空格分隔(可根据语言调整分隔符) if index != content.count - 1 { mutableAttributedText.append(NSAttributedString(string: " ", attributes: attributes)) } // 记录当前条目对应的文本范围 let segmentLength = text.count + (index != content.count - 1 ? 1 : 0) nsIndexToRange[index] = NSRange(location: currentLength, length: segmentLength) } combinedNSAttributedText = mutableAttributedText }
步骤2:用UITextView包装富文本以处理点击
由于SwiftUI原生Text难以精准识别点击位置对应的文本片段,我们通过UIViewRepresentable包装UITextView,利用其触摸检测能力匹配对应的条目索引。
struct TextViewWrapper: UIViewRepresentable { let attributedText: NSAttributedString let textAlignment: NSTextAlignment let onTapIndex: (Int) -> Void let indexToRange: [Int: NSRange] func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.attributedText = attributedText textView.textAlignment = textAlignment textView.isEditable = false textView.isSelectable = true textView.backgroundColor = .clear return textView } func updateUIView(_ uiView: UITextView, context: Context) { uiView.attributedText = attributedText uiView.textAlignment = textAlignment } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject { let parent: TextViewWrapper init(_ parent: TextViewWrapper) { self.parent = parent } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, let textView = touch.view as? UITextView else { return } let location = touch.location(in: textView) // 转换触摸位置为文本索引 let characterIndex = textView.layoutManager.characterIndex( for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil ) let clickedRange = NSRange(location: characterIndex, length: 1) // 匹配对应的条目索引并触发点击逻辑 for (index, range) in parent.indexToRange { if NSEqualRanges(NSIntersectionRange(range, clickedRange), clickedRange) { parent.onTapIndex(index) break } } super.touchesEnded(touches, with: event) } } }
步骤3:在主视图中集成并触发更新
最后,在主视图中替换原有的ForEach代码,使用我们的富文本包装器,并在相关状态变化时更新富文本:
// 替换原有的ForEach代码 TextViewWrapper( attributedText: combinedNSAttributedText, textAlignment: isBanglaSelected ? .left : .right, onTapIndex: { index in // 原有的点击逻辑完全保留 AudioPlayerManager.shared.isAudioClicked = true suraDetailsVM.serial = index suraDetailsVM.isPlayDictionary[index] = true if suraDetailsVM.isPlayDictionary[index] ?? false { audioControllerVM.suraDetailsAudio(index: index, suraDetailsVM: suraDetailsVM) } }, indexToRange: nsIndexToRange ) .padding(.vertical, 4) // 触发富文本更新的时机 .onAppear { updateCombinedText() } .onChange(of: content) { _ in updateCombinedText() } .onChange(of: isBanglaSelected) { _ in updateCombinedText() } .onChange(of: customFontSize) { _ in updateCombinedText() } .onChange(of: suraDetailsVM.serial) { _ in updateCombinedText() }
关键说明
- 该方案完全保留了原有的交互逻辑,包括点击触发音频、选中条目高亮等功能。
- 富文本的构建会自动适配孟加拉语/阿拉伯语的字体、对齐方式差异。
- 通过
UITextView的触摸检测,能够精准定位点击的条目,确保交互准确性。
内容的提问来源于stack exchange,提问作者Shohan Pramanik
相关产品推荐
相关产品推荐

