本方案實現基於 Core Text 的富文本編輯器,核心流程:
| 類 | 職責 |
|---|---|
TextTool | 處理觸摸事件、管理臨時 UITextView、鍵盤通知響應 |
TextDrawingInfo | 封裝 Core Text 繪製所需數據(路徑、文本、屬性、顏色、字體) |
DoodleView | 畫布視圖,管理 drawables 數組,統一調度繪製 |
// touchesBegan: 記錄起始點
// touchesMoved: 空實現(由 drawTemporary 繪製預覽矩形)
// touchesEnded: 核心邏輯
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 1. 計算最終矩形 rect
// 2. 過濾過小矩形 (距離 < 50pt)
// 3. 創建半透明遮罩 backgroundShade (tag=SHADE_TAG)
// 4. 創建 UITextView,frame = rect
// 5. 註冊鍵盤通知 (WillShow/DidShow/WillHide/DidHide)
// 6. 計算鍵盤遮擋距離 viewSlideDistance
// 7. 設置 delegate,成為第一響應者彈出鍵盤
}
- (void)keyboardWillShow:(NSNotification *)note {
// 根據方向調整 view.frame.origin (x/y ±= viewSlideDistance)
[UIView beginAnimations:@"viewSlideUp" context:NULL];
// ... 修改 frame ...
[UIView commitAnimations];
}
- (void)keyboardWillHide:(NSNotification *)note {
// 反向動畫恢復位置
}
關鍵點:預先計算 viewSlideDistance 確保輸入框不被鍵盤遮擋,支持四種設備方向。
- (void)textViewDidEndEditing:(UITextView *)textView {
// 1. 創建 TextDrawingInfo 封裝:路徑、文本、顏色、字體
// 2. 加入 doodleView.drawables
// 3. 清理:移除遮罩、textView、註銷通知
// 4. 觸發重繪
}
TextDrawingInfo.draw
- (void)draw {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 1. 創建屬性字符串
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:self.text];
[attrStr addAttribute:(NSString *)kCTForegroundColorAttributeName
value:(id)self.strokeColor.CGColor
range:NSMakeRange(0, self.text.length)];
// 2. 創建 Framesetter -> Frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrStr.length), self.path.CGPath, NULL);
CFRelease(framesetter);
// 3. 坐標系翻轉 (Core Text Y軸向上,UIKit Y軸向下)
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0, self.path.bounds.origin.y);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -(self.path.bounds.origin.y + self.path.bounds.size.height));
// 4. 繪製並釋放
CTFrameDraw(frame, ctx);
CGContextRestoreGState(ctx);
CFRelease(frame);
}
Core Text 繼承自 Mac OS X,坐標系原點在左下角(Y向上);UIKit 原點在左上角(Y向下)。繪製前必須通過 CGContextScaleCTM(ctx, 1, -1) 翻轉 Y 軸,並配合平移使內容位置正確。
kCTForegroundColorAttributeName:文字顏色(CGColor)kCTFontAttributeName:字體(CTFontRef)kCTUnderlineStyleAttributeName:下劃線樣式kCTParagraphStyleAttributeName:段落樣式(行間距、換行模式)
// 行間距
CTParagraphStyleSetting lineSpacing = {
.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment,
.value = &spacing, // CGFloat
.valueSize = sizeof(CGFloat)
};
// 換行模式
CTParagraphStyleSetting lineBreak = {
.spec = kCTParagraphStyleSpecifierLineBreakMode,
.value = &mode, // CTLineBreakMode
.valueSize = sizeof(CTLineBreakMode)
};
CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 2);
[attrStr addAttribute:(NSString *)kCTParagraphStyleAttributeName value:(id)style range:NSMakeRange(0, attrStr.length)];
CFRelease(style);
CATextLayer
對於簡單場景,可直接使用 CATextLayer 避免手動 Core Text 繪製:
CATextLayer *layer = [CATextLayer layer];
layer.string = attributedString; // 直接支持 NSAttributedString
layer.frame = CGRectMake(...);
[self.view.layer addSublayer:layer];
優勢:自動處理坐標系、動畫友好、代碼極簡。劣勢:複雜排版(多欄、環繞、自定義截斷)能力弱於 Core Text。
CTFrameRef, CTFramesetterRef) 需手動 CFReleaseCTFramesetterRef 避免重複創建;大文本考慮分頁/虛擬化UIAccessibility 協議支持 VoiceOver本方案展示了從底層觸摸交互到高級排版渲染的完整鏈路。Core Text 雖學習曲線陡峭,但提供了最精細的文本佈局控制能力,是實現專業級文本編輯器、排版引擎的基石。理解其坐標系、屬性字符串、Framesetter/Frame 模型是掌握 Core Text 的關鍵。