一个高性能、企业级的 iOS 富文本渲染框架,基于 YYText 重构,支持 Swift 5.5+ 和 iOS 13+
🚀 高性能 - 异步文本布局和渲染,优化的内存管理 🧵 线程安全 - 完全线程安全的实现,可在多线程环境中安全使用 🎨 富文本支持 - 支持扩展的 CoreText 属性和自定义文本效果 📱 多平台支持 - 支持 iOS、macOS、tvOS、watchOS 🔧 易于集成 - Swift Package Manager 支持,一行代码集成 📊 企业级 - 内置性能监控、内存优化和错误处理 🛡️ 安全日志 - 集成 FMLogger,提供完整的日志和调试支持
🎯 文本选择管理器 - 完整的文本选择管理器,支持范围选择、复制和编辑菜单
- 支持
TETextSelectionRange选择范围管理 - 提供
TETextSelectionManagerDelegate委托回调 - 集成编辑菜单和复制功能
- 支持选择状态管理和事件处理
🔄 排除路径系统 - 灵活的文本排除路径系统,支持复杂几何形状和内外排除模式
- 支持
UIBezierPath任意路径形状 - 提供内外两种排除模式(
inside/outside) - 内置矩形、圆形、椭圆等常用形状工厂方法
- 支持可配置的内边距和边界检测
🔍 调试可视化工具 - 强大的调试工具,可视化显示基线、行片段、字形边界等
- 实时显示文本基线、行片段边界
- 支持字形边界显示(字符级调试)
- 可视化排除路径和选择范围
- 显示文本附件和高亮区域
- 提供详细的调试信息数据结构
📈 性能分析器 - 详细的性能分析器,监控布局、渲染和内存使用指标
- 实时性能指标收集(布局时间、渲染时间、内存使用)
- 自动性能瓶颈检测和警告
- 支持性能历史记录和趋势分析
- 生成详细的性能报告
- 提供性能优化建议
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
- Swift 5.5+
- Xcode 13.0+
在 Package.swift 中添加:
dependencies: [ .package(url: "https://github.com/yourusername/TextEngineKit.git", from: "1.0.0") ]
或者在 Xcode 中添加:
- 打开 Xcode 项目
- 选择 File → Add Package Dependencies
- 输入 URL:
https://github.com/yourusername/TextEngineKit.git - 点击 Add Package
import TextEngineKit // 创建富文本标签 let label = TELabel() label.text = "Hello, TextEngineKit!" label.font = .systemFont(ofSize: 16) label.textColor = .label label.frame = CGRect(x: 20, y: 100, width: 200, height: 30) view.addSubview(label) // 创建富文本视图 let textView = TETextView() textView.attributedText = NSAttributedString(string: "富文本内容") textView.frame = CGRect(x: 20, y: 150, width: 300, height: 200) view.addSubview(textView)
import TextEngineKit // 创建带属性的文本 let text = NSMutableAttributedString(string: "TextEngineKit 富文本") text.setAttribute(.font, value: UIFont.boldSystemFont(ofSize: 24), range: NSRange(location: 0, length: 12)) text.setAttribute(.foregroundColor, value: UIColor.systemBlue, range: NSRange(location: 0, length: 12)) // 设置文本阴影 let shadow = TETextShadow() shadow.color = UIColor.black.withAlphaComponent(0.3) shadow.offset = CGSize(width: 1, height: 1) shadow.radius = 2 text.setAttribute(.textShadow, value: shadow, range: NSRange(location: 0, length: text.length)) // 设置文本边框 let border = TETextBorder() border.color = UIColor.systemRed border.width = 2 border.cornerRadius = 4 text.setAttribute(.textBorder, value: border, range: NSRange(location: 0, length: text.length)) label.attributedText = text
// 添加图片附件 let attachment = TETextAttachment() attachment.content = UIImage(systemName: "heart.fill") attachment.size = CGSize(width: 20, height: 20) let attachmentString = NSAttributedString(attachment: attachment) text.append(attachmentString)
// 设置文本高亮 let highlight = TETextHighlight() highlight.color = UIColor.systemYellow highlight.backgroundColor = UIColor.systemBlue highlight.tapAction = { containerView, text, range, rect in print("点击了高亮文本") } text.setTextHighlight(highlight, range: NSRange(location: 0, length: 12))
// 使用 TEAsyncLayer 进行高性能异步渲染 class CustomDrawingView: UIView { private let asyncLayer = TEAsyncLayer() override init(frame: CGRect) { super.init(frame: frame) layer.addSublayer(asyncLayer) asyncLayer.asyncDelegate = self asyncLayer.isAsyncEnabled = true // 启用异步渲染 } required init?(coder: NSCoder) { super.init(coder: coder) layer.addSublayer(asyncLayer) asyncLayer.asyncDelegate = self asyncLayer.isAsyncEnabled = true } } extension CustomDrawingView: TEAsyncLayerDelegate { func draw(in context: CGContext, size: CGSize) { // 在后台线程执行复杂的绘制操作 let path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)) context.setFillColor(UIColor.systemBlue.cgColor) context.addPath(path.cgPath) context.fillPath() } }
// 使用文本引擎进行完整的文本处理流程 let engine = TETextEngine() do { // 启动引擎 try engine.start() // 配置处理选项 let options = TEProcessingOptions( enableAsync: true, maxConcurrency: 4, cacheResult: true, timeout: 30.0 ) // 处理原始文本 let processResult = engine.processText("# Hello World\n\nThis is **bold** text.", options: options) switch processResult { case .success(let attributedString): print("处理成功,结果长度: \(attributedString.length)") // 布局文本 let containerSize = CGSize(width: 300, height: 200) let layoutResult = engine.layoutText(attributedString, containerSize: containerSize) switch layoutResult { case .success(let textLayout): print("布局成功,行数: \(textLayout.layoutManager.lineCount)") // 渲染到图形上下文 UIGraphicsBeginImageContextWithOptions(containerSize, false, 0) if let context = UIGraphicsGetCurrentContext() { let renderResult = engine.renderText(textLayout, in: context) if case .success = renderResult { print("渲染成功") } } UIGraphicsEndImageContext() case .failure(let error): print("布局失败: \(error)") } case .failure(let error): print("处理失败: \(error)") } } catch { print("引擎启动失败: \(error)") } // 停止引擎 engine.stop()
// 创建文本选择管理器 let selectionManager = TETextSelectionManager() selectionManager.setupContainerView(myTextView) // 启用文本选择 selectionManager.isSelectionEnabled = true selectionManager.selectionColor = .systemBlue // 监听选择变化 selectionManager.delegate = self // 扩展 UIViewController 以支持 TETextSelectionManagerDelegate extension ViewController: TETextSelectionManagerDelegate { func selectionManager(_ manager: TETextSelectionManager, didChangeSelection range: TETextSelectionRange?) { if let range = range { print("选择范围: \(range.location) - \(range.location + range.length)") } else { print("没有选择") } } func selectionManager(_ manager: TETextSelectionManager, shouldChangeSelection range: TETextSelectionRange?) -> Bool { // 可以在这里实现自定义的选择逻辑 return true } }
// 创建排除路径 let exclusionPath = TEExclusionPath(rect: CGRect(x: 50, y: 50, width: 100, height: 100)) // 创建圆形排除路径 let circlePath = TEExclusionPath.circle(center: CGPoint(x: 150, y: 150), radius: 50) // 创建椭圆排除路径 let ellipsePath = TEExclusionPath.ellipse(in: CGRect(x: 200, y: 200, width: 150, height: 80)) // 创建自定义路径 let customPath = UIBezierPath() customPath.move(to: CGPoint(x: 0, y: 0)) customPath.addLine(to: CGPoint(x: 100, y: 0)) customPath.addLine(to: CGPoint(x: 50, y: 100)) customPath.closePath() let customExclusionPath = TEExclusionPath(path: customPath, type: .inside) // 应用排除路径到文本布局 let layout = TETextLayout() layout.exclusionPaths = [exclusionPath, circlePath, ellipsePath]
// 启用调试模式 TETextDebugger.shared.enableDebugging() // 配置调试选项 var debugOptions = TETextDebugOptions() debugOptions.showBaselines = true debugOptions.baselineColor = .red debugOptions.showLineFragments = true debugOptions.showExclusionPaths = true debugOptions.exclusionPathColor = .purple debugOptions.showSelection = true debugOptions.selectionColor = .systemYellow // 应用调试选项 TETextDebugger.shared.updateOptions(debugOptions) // 调试特定视图 TETextDebugger.shared.debugLabel(myLabel) TETextDebugger.shared.debugTextView(myTextView) // 获取调试信息 let debugInfo = TETextDebugger.shared.getDebugInfo(for: myTextView) print("布局信息: \(debugInfo.layoutInfo)") print("性能信息: \(debugInfo.performanceInfo)") print("排除路径信息: \(debugInfo.exclusionPathInfo)")
// 启用性能分析 TEPerformanceProfiler.shared.startProfiling() // 配置分析选项 var profilingOptions = TEProfilingOptions() profilingOptions.enableLayoutProfiling = true profilingOptions.enableRenderProfiling = true profilingOptions.enableMemoryProfiling = true profilingOptions.reportingInterval = 1.0 // 每秒报告一次 // 应用分析选项 TEPerformanceProfiler.shared.updateOptions(profilingOptions) // 分析文本布局性能 let layoutMetrics = TEPerformanceProfiler.shared.profileLayout(attributedString, containerSize: CGSize(width: 300, height: 200)) print("布局时间: \(layoutMetrics.layoutTime) 秒") print("行数: \(layoutMetrics.lineCount)") print("字符数: \(layoutMetrics.characterCount)") print("缓存命中: \(layoutMetrics.cacheHit)") // 分析文本渲染性能 let renderMetrics = TEPerformanceProfiler.shared.profileRender(textLayout, in: graphicsContext) print("渲染时间: \(renderMetrics.renderTime) 秒") print("像素数: \(renderMetrics.pixelCount)") print("绘制调用: \(renderMetrics.drawCallCount)") // 获取整体性能报告 let performanceReport = TEPerformanceProfiler.shared.generateReport() print("平均布局时间: \(performanceReport.averageLayoutTime)") print("平均渲染时间: \(performanceReport.averageRenderTime)") print("总内存使用: \(performanceReport.totalMemoryUsage)") print("平均FPS: \(performanceReport.averageFPS)")
TextEngineKit 采用模块化架构设计,包含以下核心模块:
TETextRenderer- 核心文本渲染引擎TELayoutManager- 异步文本布局管理器TEAttributeSystem- 富文本属性系统TEAttachmentManager- 文本附件管理器
TELabel- 高性能富文本标签TETextView- 功能丰富的富文本视图TETextField- 支持富文本的输入框
TEParser- 文本解析器(支持 Markdown)TEHighlightManager- 文本高亮管理器TEClipboardManager- 剪贴板管理器TEPerformanceMonitor- 性能监控器TETextSelectionManager- 文本选择管理器TEExclusionPath- 排除路径系统TETextDebugger- 调试可视化工具TEPerformanceProfiler- 性能分析器
TextEngineKit 在性能方面进行了多项优化:
- 异步布局 - 使用后台线程进行文本布局计算
- 缓存机制 - 智能缓存文本布局结果
- 内存管理 - 优化的内存分配和释放策略
- 渲染优化 - 使用 CoreText 和 CoreGraphics 进行高效渲染
- 线程安全 - 完全线程安全的实现
TextEngineKit 集成了 FMLogger 日志系统,提供完整的调试和监控支持:
import TextEngineKit // 配置日志级别 TETextEngine.shared.configureLogging(.development) // 查看渲染性能日志 TETextEngine.shared.enablePerformanceLogging = true
文本引擎核心协议,定义了文本处理、布局和渲染的完整生命周期管理。
public protocol TETextEngineProtocol { var configuration: TEConfiguration { get set } var isRunning: Bool { get } func start() throws func stop() func reset() func performHealthCheck() -> Result<Bool, TETextEngineError> func processText(_ text: String, options: TEProcessingOptions?) -> Result<NSAttributedString, TETextEngineError> func layoutText(_ attributedString: NSAttributedString, containerSize: CGSize) -> Result<TETextLayout, TETextEngineError> func renderText(_ layout: TETextLayout, in context: CGContext) -> Result<Void, TETextEngineError> }
异步图层绘制委托协议。
public protocol TEAsyncLayerDelegate: AnyObject { func draw(in context: CGContext, size: CGSize) }
文本引擎主类,实现 TETextEngineProtocol。
let engine = TETextEngine() try engine.start() // 使用引擎... engine.stop()
高性能富文本标签。
let label = TELabel() label.text = "Hello World" label.font = .systemFont(ofSize: 16) label.textColor = .label
功能丰富的富文本视图。
let textView = TETextView() textView.attributedText = NSAttributedString(string: "富文本内容")
高性能异步渲染图层。
let asyncLayer = TEAsyncLayer() asyncLayer.asyncDelegate = self asyncLayer.isAsyncEnabled = true
文本处理选项。
public struct TEProcessingOptions { public var enableAsync: Bool // 是否启用异步处理 public var maxConcurrency: Int // 最大并发数 public var cacheResult: Bool // 是否缓存结果 public var timeout: TimeInterval // 超时时间(秒) }
文本布局信息。
public struct TETextLayout { public let attributedString: NSAttributedString public let containerSize: CGSize public let textContainer: TETextContainer public let layoutManager: TELayoutManager public let textStorage: Any? }
路径边界框,支持安全编码。
public final class TEPathBox: NSObject, NSSecureCoding { public let rect: CGRect public init(rect: CGRect) }
TextEngineKit 扩展了 NSAttributedString 支持以下属性:
.textShadow- 文本阴影.textBorder- 文本边框.textBackground- 文本背景.textAttachment- 文本附件.textHighlight- 文本高亮
文本选择管理器,提供完整的文本选择功能,支持范围选择、复制和编辑菜单。
public final class TETextSelectionManager { public weak var delegate: TETextSelectionManagerDelegate? public var selectedRange: TETextSelectionRange? { get } public var isSelectionEnabled: Bool public var selectionColor: UIColor public func setupContainerView(_ containerView: UIView) public func setSelection(range: TETextSelectionRange?) public func selectAll() public func clearSelection() public func copySelectedText() -> String? public func showEditMenu() } // 使用示例 let selectionManager = TETextSelectionManager() selectionManager.delegate = self selectionManager.isSelectionEnabled = true selectionManager.selectionColor = .systemBlue // 设置选择范围 let range = TETextSelectionRange(location: 10, length: 20) selectionManager.setSelection(range: range) // 全选 selectionManager.selectAll() // 复制选中文本 if let selectedText = selectionManager.copySelectedText() { UIPasteboard.general.string = selectedText }
排除路径系统,支持复杂几何形状的文本布局避让,提供灵活的内外排除模式。
public struct TEExclusionPath { public enum ExclusionType { case inside // 排除路径内部区域,文本围绕路径外部排列 case outside // 排除路径外部区域,文本仅在路径内部排列 } public let path: UIBezierPath public let padding: UIEdgeInsets public let type: ExclusionType public init(path: UIBezierPath, padding: UIEdgeInsets = .zero, type: ExclusionType = .inside) public static func rect(_ rect: CGRect, padding: UIEdgeInsets = .zero, type: ExclusionType = .inside) -> TEExclusionPath public static func circle(center: CGPoint, radius: CGFloat, padding: UIEdgeInsets = .zero, type: ExclusionType = .inside) -> TEExclusionPath public static func ellipse(in rect: CGRect, padding: UIEdgeInsets = .zero, type: ExclusionType = .inside) -> TEExclusionPath public func contains(_ point: CGPoint) -> Bool public var paddedBounds: CGRect { get } } // 使用示例 // 创建圆形排除路径(文本围绕圆形排列) let circlePath = TEExclusionPath.circle( center: CGPoint(x: 150, y: 150), radius: 50, padding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) // 创建矩形排除路径 textContainer.exclusionPaths = [ TEExclusionPath.rect(CGRect(x: 50, y: 50, width: 100, height: 100)), TEExclusionPath.ellipse(in: CGRect(x: 200, y: 100, width: 80, height: 60)) ] // 检查点是否在排除区域内 let point = CGPoint(x: 100, y: 100) if circlePath.contains(point) { print("点在排除区域内") }
调试可视化工具,提供文本布局的详细调试信息,支持实时显示基线、行片段、字形边界等。
public final class TETextDebugger { public static let shared: TETextDebugger public var options: TETextDebugOptions public weak var delegate: TETextDebuggerDelegate? public func enableDebugging() public func disableDebugging() public func updateOptions(_ options: TETextDebugOptions) public func debugLabel(_ label: TELabel) public func debugTextView(_ textView: TETextView) public func getDebugInfo(for view: UIView) -> TETextDebugInfo public func refreshDebugging() } public struct TETextDebugOptions { public var showBaselines: Bool // 显示文本基线 public var baselineColor: UIColor // 基线颜色(默认红色半透明) public var showLineFragments: Bool // 显示行片段边界 public var lineFragmentBorderColor: UIColor // 行片段完整边界颜色 public var lineFragmentUsedBorderColor: UIColor // 行片段使用区域颜色 public var showGlyphs: Bool // 显示字形边界(性能开销较大) public var glyphBorderColor: UIColor // 字形边界颜色 public var showExclusionPaths: Bool // 显示排除路径 public var exclusionPathColor: UIColor // 排除路径颜色 public var showSelection: Bool // 显示选择范围 public var selectionColor: UIColor // 选择范围颜色 public var showAttachments: Bool // 显示文本附件 public var attachmentColor: UIColor // 附件颜色 public var showHighlights: Bool // 显示文本高亮 public var highlightColor: UIColor // 高亮颜色 public var lineWidth: CGFloat // 调试线条宽度 public var debugFontSize: CGFloat // 调试文本字体大小 public var debugTextColor: UIColor // 调试文本颜色 } // 使用示例 // 启用调试模式 TETextDebugger.shared.enableDebugging() // 配置调试选项 var options = TETextDebugOptions() options.showBaselines = true options.showLineFragments = true options.showExclusionPaths = true options.baselineColor = .red.withAlphaComponent(0.5) options.lineFragmentBorderColor = .blue.withAlphaComponent(0.3) TETextDebugger.shared.updateOptions(options) // 调试标签 let label = TELabel() label.text = "调试文本示例" TETextDebugger.shared.debugLabel(label) // 获取调试信息 let debugInfo = TETextDebugger.shared.getDebugInfo(for: label) print("布局信息: \(debugInfo.layoutInfo)") print("性能信息: \(debugInfo.performanceInfo)") print("排除路径信息: \(debugInfo.exclusionPathInfo)") // 设置委托接收调试更新 class MyDebuggerDelegate: TETextDebuggerDelegate { func debugger(_ debugger: TETextDebugger, didUpdateDebugInfo info: TETextDebugInfo) { print("调试信息更新: 布局时间 \(info.performanceInfo.layoutTime)s") } func debugger(_ debugger: TETextDebugger, didChangeDebuggingState isDebugging: Bool) { print("调试状态变化: \(isDebugging ? "启用" : "禁用")") } }
性能分析器,提供详细的性能监控和分析功能,支持布局、渲染和内存使用指标的实时监控。
public final class TEPerformanceProfiler { public static let shared: TEPerformanceProfiler public weak var delegate: TEPerformanceProfilerDelegate? public var isProfilingEnabled: Bool public var thresholds: PerformanceThresholds public func startProfiling() public func stopProfiling() public func profileLabel(_ label: TELabel) -> TEPerformanceMetrics public func profileTextView(_ textView: TETextView) -> TEPerformanceMetrics public func profileTextRendering(attributedText: NSAttributedString, containerSize: CGSize, exclusionPaths: [TEExclusionPath]) -> TEPerformanceMetrics public func getPerformanceHistory() -> [TEPerformanceMetrics] public func getPerformanceReport() -> String public func resetPerformanceData() } public struct TEPerformanceMetrics { public struct LayoutMetrics { public let layoutTime: TimeInterval // 布局计算耗时(秒) public let lineCount: Int // 文本行数 public let glyphCount: Int // 字形数量 public let characterCount: Int // 字符数量 public let cacheHit: Bool // 是否命中缓存 public let memoryUsage: Int // 内存使用量(字节) } public struct RenderMetrics { public let renderTime: TimeInterval // 渲染绘制耗时(秒) public let pixelCount: Int // 处理的像素数量 public let drawCallCount: Int // 绘制调用次数 public let memoryUsage: Int // 内存使用量(字节) public let gpuUsage: Float // GPU使用率(0.0-1.0) } public struct OverallMetrics { public let totalTime: TimeInterval // 总处理耗时(秒) public let fps: Float // 帧率(FPS) public let cpuUsage: Float // CPU使用率(0.0-1.0) public let memoryUsage: Int // 内存使用量(字节) public let energyUsage: Float // 能耗使用情况(0.0-1.0) } public let layoutMetrics: LayoutMetrics public let renderMetrics: RenderMetrics public let overallMetrics: OverallMetrics public let timestamp: Date } public struct TEPerformanceBottleneck { public enum BottleneckType { case layoutSlow // 布局计算缓慢 case renderSlow // 渲染绘制缓慢 case memoryHigh // 内存使用过高 case cacheMiss // 缓存未命中 case gpuIntensive // GPU使用密集 case cpuIntensive // CPU使用密集 } public let type: BottleneckType public let severity: Float // 严重程度(0.0-1.0) public let description: String // 问题描述 public let suggestion: String // 优化建议 public let metrics: TEPerformanceMetrics // 相关性能指标 } // 使用示例 // 启用性能分析 TEPerformanceProfiler.shared.startProfiling() // 配置性能阈值 TEPerformanceProfiler.shared.thresholds.maxLayoutTime = 0.010 // 10ms TEPerformanceProfiler.shared.thresholds.maxMemoryUsage = 5 * 1024 * 1024 // 5MB TEPerformanceProfiler.shared.thresholds.minFPS = 45.0 // 45 FPS // 分析标签性能 let label = TELabel() label.text = "Hello World" let metrics = TEPerformanceProfiler.shared.profileLabel(label) print("布局时间: \(metrics.layoutMetrics.layoutTime * 1000)ms") print("渲染时间: \(metrics.renderMetrics.renderTime * 1000)ms") print("总时间: \(metrics.overallMetrics.totalTime * 1000)ms") print("FPS: \(metrics.overallMetrics.fps)") print("内存使用: \(formatBytes(metrics.overallMetrics.memoryUsage))") // 分析文本渲染性能 let text = NSAttributedString(string: "Sample text for performance testing") let size = CGSize(width: 200, height: 100) let renderMetrics = TEPerformanceProfiler.shared.profileTextRendering( attributedText: text, containerSize: size ) // 获取性能报告 let report = TEPerformanceProfiler.shared.getPerformanceReport() print(report) // 设置委托接收性能分析结果 class MyPerformanceDelegate: TEPerformanceProfilerDelegate { func profiler(_ profiler: TEPerformanceProfiler, didCompleteAnalysis metrics: TEPerformanceMetrics) { print("性能分析完成") print("布局时间: \(metrics.layoutMetrics.layoutTime * 1000)ms") print("渲染时间: \(metrics.renderMetrics.renderTime * 1000)ms") print("FPS: \(metrics.overallMetrics.fps)") } func profiler(_ profiler: TEPerformanceProfiler, didDetectBottleneck bottleneck: TEPerformanceBottleneck) { print("发现性能瓶颈!") print("类型: \(bottleneck.type)") print("描述: \(bottleneck.description)") print("建议: \(bottleneck.suggestion)") print("严重程度: \(bottleneck.severity * 100)%") } } TEPerformanceProfiler.shared.delegate = MyPerformanceDelegate() // 便捷扩展使用 let label = TELabel() label.text = "Hello World" // 启用性能分析 label.enablePerformanceProfiling() // 分析当前标签性能 let performanceMetrics = label.profilePerformance() print("布局时间: \(performanceMetrics.layoutMetrics.layoutTime * 1000)ms") // 禁用性能分析 label.disablePerformanceProfiling()
import TextEngineKit class TextSelectionViewController: UIViewController, TETextSelectionManagerDelegate { private let label = TELabel() private let selectionManager = TETextSelectionManager() override func viewDidLoad() { super.viewDidLoad() // 配置标签 label.text = "这是一段可选择的文本内容,支持范围选择、复制和编辑菜单功能。" label.frame = CGRect(x: 20, y: 100, width: 300, height: 100) label.numberOfLines = 0 view.addSubview(label) // 配置选择管理器 selectionManager.delegate = self selectionManager.isSelectionEnabled = true selectionManager.selectionColor = .systemBlue.withAlphaComponent(0.3) selectionManager.setupContainerView(label) // 添加选择按钮 let selectButton = UIButton(type: .system) selectButton.setTitle("选择全部", for: .normal) selectButton.addTarget(self, action: #selector(selectAllText), for: .touchUpInside) selectButton.frame = CGRect(x: 20, y: 220, width: 100, height: 44) view.addSubview(selectButton) let copyButton = UIButton(type: .system) copyButton.setTitle("复制选择", for: .normal) copyButton.addTarget(self, action: #selector(copySelectedText), for: .touchUpInside) copyButton.frame = CGRect(x: 140, y: 220, width: 100, height: 44) view.addSubview(copyButton) } @objc private func selectAllText() { selectionManager.selectAll() } @objc private func copySelectedText() { if let selectedText = selectionManager.copySelectedText() { UIPasteboard.general.string = selectedText print("已复制: \(selectedText)") } } // MARK: - TETextSelectionManagerDelegate func selectionManager(_ manager: TETextSelectionManager, didChangeSelection range: TETextSelectionRange?) { if let range = range { print("选择范围变化: \(range.location) - \(range.location + range.length)") } else { print("选择已清除") } } func selectionManager(_ manager: TETextSelectionManager, shouldChangeSelectionFrom oldRange: TETextSelectionRange?, to newRange: TETextSelectionRange?) -> Bool { print("允许选择范围从 \(oldRange?.location ?? -1) 变为 \(newRange?.location ?? -1)") return true } }
import TextEngineKit class ExclusionPathViewController: UIViewController { private let textView = TETextView() override func viewDidLoad() { super.viewDidLoad() // 创建长文本内容 let text = """ 这是一段很长的文本内容,用于演示排除路径功能。文本会围绕各种形状的元素进行排列, 包括圆形、矩形、椭圆等几何形状。排除路径系统支持复杂的文本布局避让,让文本排版更加灵活和美观。 通过设置不同的排除路径类型,可以实现文本围绕图像、自定义视图或其他UI元素的环绕效果。 这在创建杂志风格的布局、图文混排内容或复杂的文本展示界面时非常有用。 """ textView.attributedText = NSAttributedString(string: text) textView.frame = CGRect(x: 20, y: 100, width: 350, height: 400) textView.isEditable = false textView.isScrollEnabled = false view.addSubview(textView) // 创建复杂的排除路径 createComplexExclusionPaths() // 添加交互按钮 addExclusionPathControls() } private func createComplexExclusionPaths() { // 圆形排除路径(图像占位) let circlePath = TEExclusionPath.circle( center: CGPoint(x: 100, y: 150), radius: 40, padding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), type: .inside ) // 矩形排除路径(自定义视图占位) let rectPath = TEExclusionPath.rect( CGRect(x: 250, y: 200, width: 80, height: 60), padding: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5), type: .inside ) // 椭圆排除路径 let ellipsePath = TEExclusionPath.ellipse( in: CGRect(x: 50, y: 300, width: 120, height: 80), padding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), type: .inside ) // 应用排除路径到文本容器 textView.textContainer.exclusionPaths = [circlePath, rectPath, ellipsePath] // 添加视觉占位符 addPlaceholderViews() } private func addPlaceholderViews() { // 圆形占位符 let circleView = UIView(frame: CGRect(x: 60, y: 110, width: 80, height: 80)) circleView.backgroundColor = .systemBlue.withAlphaComponent(0.3) circleView.layer.cornerRadius = 40 view.addSubview(circleView) // 矩形占位符 let rectView = UIView(frame: CGRect(x: 245, y: 205, width: 90, height: 70)) rectView.backgroundColor = .systemGreen.withAlphaComponent(0.3) rectView.layer.cornerRadius = 8 view.addSubview(rectView) // 椭圆占位符 let ellipseView = UIView(frame: CGRect(x: 45, y: 305, width: 130, height: 90)) ellipseView.backgroundColor = .systemOrange.withAlphaComponent(0.3) ellipseView.layer.cornerRadius = 45 view.addSubview(ellipseView) } private func addExclusionPathControls() { let addButton = UIButton(type: .system) addButton.setTitle("添加随机排除路径", for: .normal) addButton.addTarget(self, action: #selector(addRandomExclusionPath), for: .touchUpInside) addButton.frame = CGRect(x: 20, y: 520, width: 200, height: 44) view.addSubview(addButton) let clearButton = UIButton(type: .system) clearButton.setTitle("清除所有排除路径", for: .normal) clearButton.addTarget(self, action: #selector(clearExclusionPaths), for: .touchUpInside) clearButton.frame = CGRect(x: 240, y: 520, width: 150, height: 44) view.addSubview(clearButton) } @objc private func addRandomExclusionPath() { let randomX = CGFloat.random(in: 50...300) let randomY = CGFloat.random(in: 150...400) let randomWidth = CGFloat.random(in: 40...100) let randomHeight = CGFloat.random(in: 40...100) let randomPath = TEExclusionPath.rect( CGRect(x: randomX, y: randomY, width: randomWidth, height: randomHeight), padding: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) ) var currentPaths = textView.textContainer.exclusionPaths currentPaths.append(randomPath) textView.textContainer.exclusionPaths = currentPaths // 添加视觉反馈 let placeholder = UIView(frame: CGRect(x: randomX, y: randomY, width: randomWidth, height: randomHeight)) placeholder.backgroundColor = .systemPurple.withAlphaComponent(0.3) placeholder.layer.cornerRadius = 8 placeholder.tag = 999 view.addSubview(placeholder) } @objc private func clearExclusionPaths() { textView.textContainer.exclusionPaths = [] // 清除所有占位符视图 view.subviews.filter { 0ドル.tag == 999 }.forEach { 0ドル.removeFromSuperview() } } }
import TextEngineKit class DebugVisualizationViewController: UIViewController, TETextDebuggerDelegate { private let label = TELabel() private let textView = TETextView() override func viewDidLoad() { super.viewDidLoad() setupUI() setupDebugger() addDebugControls() } private func setupUI() { // 配置标签 label.text = "调试可视化标签 - 可以显示基线、行片段、字形边界等调试信息" label.frame = CGRect(x: 20, y: 100, width: 350, height: 60) label.numberOfLines = 0 label.font = .systemFont(ofSize: 16) view.addSubview(label) // 配置文本视图 let textViewText = """ 调试可视化文本视图 - 支持更复杂的调试信息显示。 可以显示: • 文本基线(红色线条) • 行片段边界(完整矩形和使用矩形) • 字形边界(橙色矩形,性能开销较大) • 排除路径(紫色形状) • 选择范围(黄色高亮) • 文本附件(绿色边界) • 文本高亮(粉色背景) 调试信息有助于理解文本布局算法的工作原理。 """ textView.attributedText = NSAttributedString(string: textViewText) textView.frame = CGRect(x: 20, y: 180, width: 350, height: 250) textView.isEditable = false textView.isScrollEnabled = false textView.font = .systemFont(ofSize: 14) view.addSubview(textView) // 添加排除路径进行调试 let exclusionPath = TEExclusionPath.circle( center: CGPoint(x: 175, y: 280), radius: 50, padding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) textView.textContainer.exclusionPaths = [exclusionPath] } private func setupDebugger() { // 设置调试器委托 TETextDebugger.shared.delegate = self // 启用调试模式 TETextDebugger.shared.enableDebugging() // 配置调试选项 var options = TETextDebugOptions() options.showBaselines = true options.showLineFragments = true options.showExclusionPaths = true options.showSelection = true options.showAttachments = true options.showHighlights = true options.showGlyphs = false // 默认关闭,性能开销较大 options.baselineColor = .red.withAlphaComponent(0.5) options.lineFragmentBorderColor = .blue.withAlphaComponent(0.3) options.lineFragmentUsedBorderColor = .cyan.withAlphaComponent(0.3) options.exclusionPathColor = .purple.withAlphaComponent(0.4) options.selectionColor = .systemYellow.withAlphaComponent(0.3) options.attachmentColor = .green.withAlphaComponent(0.5) options.highlightColor = .systemPink.withAlphaComponent(0.3) options.glyphBorderColor = .orange.withAlphaComponent(0.3) options.lineWidth = 1.0 options.debugFontSize = 10.0 options.debugTextColor = .black TETextDebugger.shared.updateOptions(options) // 应用调试到视图 TETextDebugger.shared.debugLabel(label) TETextDebugger.shared.debugTextView(textView) } private func addDebugControls() { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 10 stackView.distribution = .fillEqually stackView.frame = CGRect(x: 20, y: 450, width: 350, height: 44) view.addSubview(stackView) // 基线显示开关 let baselineButton = UIButton(type: .system) baselineButton.setTitle("基线", for: .normal) baselineButton.addTarget(self, action: #selector(toggleBaselines), for: .touchUpInside) stackView.addArrangedSubview(baselineButton) // 行片段显示开关 let fragmentsButton = UIButton(type: .system) fragmentsButton.setTitle("行片段", for: .normal) fragmentsButton.addTarget(self, action: #selector(toggleLineFragments), for: .touchUpInside) stackView.addArrangedSubview(fragmentsButton) // 排除路径显示开关 let exclusionButton = UIButton(type: .system) exclusionButton.setTitle("排除路径", for: .normal) exclusionButton.addTarget(self, action: #selector(toggleExclusionPaths), for: .touchUpInside) stackView.addArrangedSubview(exclusionButton) // 字形显示开关 let glyphsButton = UIButton(type: .system) glyphsButton.setTitle("字形", for: .normal) glyphsButton.addTarget(self, action: #selector(toggleGlyphs), for: .touchUpInside) stackView.addArrangedSubview(glyphsButton) // 刷新调试按钮 let refreshButton = UIButton(type: .system) refreshButton.setTitle("刷新", for: .normal) refreshButton.addTarget(self, action: #selector(refreshDebugging), for: .touchUpInside) stackView.addArrangedSubview(refreshButton) } @objc private func toggleBaselines() { var options = TETextDebugger.shared.options options.showBaselines.toggle() TETextDebugger.shared.updateOptions(options) } @objc private func toggleLineFragments() { var options = TETextDebugger.shared.options options.showLineFragments.toggle() TETextDebugger.shared.updateOptions(options) } @objc private func toggleExclusionPaths() { var options = TETextDebugger.shared.options options.showExclusionPaths.toggle() TETextDebugger.shared.updateOptions(options) } @objc private func toggleGlyphs() { var options = TETextDebugger.shared.options options.showGlyphs.toggle() TETextDebugger.shared.updateOptions(options) } @objc private func refreshDebugging() { TETextDebugger.shared.refreshDebugging() } // MARK: - TETextDebuggerDelegate func debugger(_ debugger: TETextDebugger, didUpdateDebugInfo info: TETextDebugInfo) { print("调试信息更新:") print("- 布局时间: \(info.performanceInfo.layoutTime * 1000)ms") print("- 渲染时间: \(info.performanceInfo.renderTime * 1000)ms") print("- 总时间: \(info.performanceInfo.totalTime * 1000)ms") print("- 内存使用: \(formatBytes(info.performanceInfo.memoryUsage))") print("- 行片段数: \(info.layoutInfo.lineFragments.count)") print("- 排除路径数: \(info.exclusionPathInfo.paths.count)") } func debugger(_ debugger: TETextDebugger, didChangeDebuggingState isDebugging: Bool) { print("调试状态变化: \(isDebugging ? "启用" : "禁用")") } private func formatBytes(_ bytes: Int) -> String { let formatter = ByteCountFormatter() formatter.countStyle = .binary return formatter.string(fromByteCount: Int64(bytes)) } }
import TextEngineKit class PerformanceAnalysisViewController: UIViewController, TEPerformanceProfilerDelegate { private let label = TELabel() private let textView = TETextView() private let performanceLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() setupUI() setupPerformanceProfiler() addPerformanceControls() // 开始性能分析 TEPerformanceProfiler.shared.startProfiling() } private func setupUI() { // 配置性能显示标签 performanceLabel.text = "性能指标将在此显示" performanceLabel.frame = CGRect(x: 20, y: 50, width: 350, height: 40) performanceLabel.numberOfLines = 0 performanceLabel.font = .monospacedSystemFont(ofSize: 12, weight: .regular) performanceLabel.textColor = .systemGreen view.addSubview(performanceLabel) // 配置测试标签 label.text = "性能测试标签 - 用于分析布局性能" label.frame = CGRect(x: 20, y: 100, width: 350, height: 40) label.numberOfLines = 0 label.font = .systemFont(ofSize: 16) view.addSubview(label) // 配置测试文本视图 let textViewText = """ 性能测试文本视图 - 用于分析复杂文本的布局和渲染性能。 这段文本包含多行内容,可以测试文本引擎在处理复杂布局时的性能表现。 包括: • 多行文本布局计算 • 行片段边界计算 • 文本换行处理 • 内存使用优化 • 缓存机制效果 通过性能分析器可以详细了解文本处理的各个环节耗时情况。 """ textView.attributedText = NSAttributedString(string: textViewText) textView.frame = CGRect(x: 20, y: 160, width: 350, height: 200) textView.isEditable = false textView.isScrollEnabled = false textView.font = .systemFont(ofSize: 14) view.addSubview(textView) } private func setupPerformanceProfiler() { // 设置性能分析器委托 TEPerformanceProfiler.shared.delegate = self // 配置性能阈值 TEPerformanceProfiler.shared.thresholds.maxLayoutTime = 0.016 // 16ms (60fps) TEPerformanceProfiler.shared.thresholds.maxRenderTime = 0.016 // 16ms (60fps) TEPerformanceProfiler.shared.thresholds.maxMemoryUsage = 5 * 1024 * 1024 // 5MB TEPerformanceProfiler.shared.thresholds.minFPS = 30.0 // 30 FPS TEPerformanceProfiler.shared.thresholds.maxCPUUsage = 0.8 // 80% TEPerformanceProfiler.shared.thresholds.maxGPUUsage = 0.8 // 80% } private func addPerformanceControls() { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 10 stackView.distribution = .fillEqually stackView.frame = CGRect(x: 20, y: 380, width: 350, height: 44) view.addSubview(stackView) // 分析标签性能 let analyzeLabelButton = UIButton(type: .system) analyzeLabelButton.setTitle("分析标签", for: .normal) analyzeLabelButton.addTarget(self, action: #selector(analyzeLabelPerformance), for: .touchUpInside) stackView.addArrangedSubview(analyzeLabelButton) // 分析文本视图性能 let analyzeTextViewButton = UIButton(type: .system) analyzeTextViewButton.setTitle("分析文本视图", for: .normal) analyzeTextViewButton.addTarget(self, action: #selector(analyzeTextViewPerformance), for: .touchUpInside) stackView.addArrangedSubview(analyzeTextViewButton) // 生成性能报告 let reportButton = UIButton(type: .system) reportButton.setTitle("生成报告", for: .normal) reportButton.addTarget(self, action: #selector(generatePerformanceReport), for: .touchUpInside) stackView.addArrangedSubview(reportButton) // 重置性能数据 let resetButton = UIButton(type: .system) resetButton.setTitle("重置数据", for: .normal) resetButton.addTarget(self, action: #selector(resetPerformanceData), for: .touchUpInside) stackView.addArrangedSubview(resetButton) } @objc private func analyzeLabelPerformance() { // 修改标签内容以测试不同场景 label.text = "性能测试 - 时间戳: \(Date().timeIntervalSince1970)" // 分析标签性能 let metrics = TEPerformanceProfiler.shared.profileLabel(label) updatePerformanceDisplay(metrics: metrics, source: "标签") } @objc private func analyzeTextViewPerformance() { // 修改文本视图内容以测试不同场景 let newText = """ 性能测试文本 - 时间戳: \(Date().timeIntervalSince1970) 这是一段用于性能测试的多行文本内容。 包含多个段落和不同的文本格式。 第二段文本内容,用于测试文本引擎的 布局和渲染性能表现。 第三段文本,包含更多的内容以测试 复杂的文本处理性能。 """ textView.attributedText = NSAttributedString(string: newText) // 分析文本视图性能 let metrics = TEPerformanceProfiler.shared.profileTextView(textView) updatePerformanceDisplay(metrics: metrics, source: "文本视图") } @objc private func generatePerformanceReport() { let report = TEPerformanceProfiler.shared.getPerformanceReport() print("性能报告:\n\(report)") // 显示报告摘要 let history = TEPerformanceProfiler.shared.getPerformanceHistory() let summary = "历史记录数: \(history.count)\n报告已生成,请查看控制台输出" performanceLabel.text = summary } @objc private func resetPerformanceData() { TEPerformanceProfiler.shared.resetPerformanceData() performanceLabel.text = "性能数据已重置" } private func updatePerformanceDisplay(metrics: TEPerformanceMetrics, source: String) { let layoutTime = String(format: "%.2f", metrics.layoutMetrics.layoutTime * 1000) let renderTime = String(format: "%.2f", metrics.renderMetrics.renderTime * 1000) let totalTime = String(format: "%.2f", metrics.overallMetrics.totalTime * 1000) let fps = String(format: "%.1f", metrics.overallMetrics.fps) let memory = formatBytes(metrics.overallMetrics.memoryUsage) let cacheHit = metrics.layoutMetrics.cacheHit ? "✓" : "✗" performanceLabel.text = """ \(source)性能分析: 布局: \(layoutTime)ms | 渲染: \(renderTime)ms | 总计: \(totalTime)ms FPS: \(fps) | 内存: \(memory) | 缓存: \(cacheHit) """ } // MARK: - TEPerformanceProfilerDelegate func profiler(_ profiler: TEPerformanceProfiler, didCompleteAnalysis metrics: TEPerformanceMetrics) { print("性能分析完成:") print("- 布局时间: \(metrics.layoutMetrics.layoutTime * 1000)ms") print("- 渲染时间: \(metrics.renderMetrics.renderTime * 1000)ms") print("- 总时间: \(metrics.overallMetrics.totalTime * 1000)ms") print("- FPS: \(metrics.overallMetrics.fps)") print("- 内存使用: \(formatBytes(metrics.overallMetrics.memoryUsage))") print("- 缓存命中: \(metrics.layoutMetrics.cacheHit)") } func profiler(_ profiler: TEPerformanceProfiler, didDetectBottleneck bottleneck: TEPerformanceBottleneck) { print("发现性能瓶颈!") print("- 类型: \(bottleneck.type)") print("- 描述: \(bottleneck.description)") print("- 建议: \(bottleneck.suggestion)") print("- 严重程度: \(bottleneck.severity * 100)%") // 显示警告 performanceLabel.textColor = .systemRed performanceLabel.text = "⚠️ 性能警告: \(bottleneck.description)" // 3秒后恢复颜色 DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.performanceLabel.textColor = .systemGreen } } func profiler(_ profiler: TEPerformanceProfiler, didTriggerWarning warning: String, severity: Float) { print("性能警告: \(warning) (严重程度: \(severity))") if severity > 0.8 { performanceLabel.textColor = .systemOrange performanceLabel.text = "⚠️ \(warning)" // 2秒后恢复颜色 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.performanceLabel.textColor = .systemGreen } } } private func formatBytes(_ bytes: Int) -> String { let formatter = ByteCountFormatter() formatter.countStyle = .binary return formatter.string(fromByteCount: Int64(bytes)) } deinit { // 停止性能分析 TEPerformanceProfiler.shared.stopProfiling() } }
let options = TEProcessingOptions(cacheResult: true) // 启用结果缓存
let options = TEProcessingOptions(enableAsync: true, maxConcurrency: 4)
let options = TEProcessingOptions(timeout: 30.0) // 30秒超时
// 批量处理多个文本 let texts = ["文本1", "文本2", "文本3"] let results = texts.map { engine.processText(0ドル, options: options) }
- 减少复杂文本属性:过多的富文本属性会增加布局计算复杂度
- 使用布局缓存:启用
cacheResult选项缓存布局结果 - 避免频繁布局更新:批量更新文本内容,减少布局触发次数
- 异步渲染:使用
TEAsyncLayer进行复杂的绘制操作 - 减少过度绘制:优化视图层次结构,避免不必要的重绘
- 使用合适的图像格式:选择适当的图像压缩格式和尺寸
- 及时释放资源:使用完大文本后及时清理相关对象
- 合理设置缓存大小:根据应用需求调整缓存策略
- 监控内存使用:使用性能分析器监控内存使用情况
- 简化几何形状:使用简单的几何形状作为排除路径
- 减少排除路径数量:避免过多的排除路径影响布局性能
- 合理使用内边距:适当的内边距可以提高文本可读性
- 选择性启用调试元素:只开启需要的调试可视化选项
- 避免在生产环境使用:调试功能主要用于开发和测试阶段
- 注意字形显示性能:字形边界显示会有较大性能开销
TextEngineKit 内置了输入验证机制:
- URL 长度限制(最大 2048 字符)
- 只允许 HTTP/HTTPS 协议
- 过滤控制字符防止注入攻击
- 自动缓存管理
- 内存警告处理
- 合理的缓存大小限制
- 所有公共 API 都是线程安全的
- 异步操作有适当的同步机制
- 支持取消长时间运行的任务
欢迎提交 Issue 和 Pull Request 来改进 TextEngineKit。
- 遵循 Swift API 设计规范
- 所有公共接口必须有文档注释
- 提供使用示例
- 保持代码简洁,函数长度不超过 50 行
TextEngineKit 基于 MIT 许可证开源,详见 LICENSE 文件。
TextEngineKit 由 TextEngineKit 团队开发和维护。