You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 11:58:11