Cấu trúc kỹ thuật bên trong của trình soạn thảo văn bản Apple
(papereditor.app)- Paper hoạt động như một trình biên tập
TextViewdựa trên TextKit 1 trên các nền tảng Apple, trong đóNSTextStorage,NSTextContainer,NSLayoutManager,TextViewvàScrollViewcùng nhau tạo nên trải nghiệm chỉnh sửa NSAttributedStringbiểu diễn rich text bằng chuỗi và thuộc tính (attribute) theo từng phạm vi, và Paper tách riêng các thuộc tính meta cho cấu trúc Markdown với các thuộc tính kiểu dáng cho hiển thị trên màn hình- Việc cập nhật kiểu dáng thay đổi theo lúc mở tài liệu, thay đổi văn bản và thay đổi cài đặt; các thuộc tính ảnh hưởng đến layout được gom vào transaction, còn thuộc tính trang trí được cập nhật mà không cần tính lại layout
- Hiệu năng nhập liệu được cải thiện bằng cách tránh parse lại cả đoạn văn nếu việc chỉnh sửa không diễn ra quanh các ký tự đặc biệt của Markdown, nhưng các cấu trúc có thể ảnh hưởng đến kiểu dáng của toàn bộ tài liệu như code block cần được xử lý ngoại lệ
- Sao chép·dán hoạt động trên nhiều biểu diễn dữ liệu của UTI và pasteboard; với các UTI bán công khai mà Apple không định nghĩa như Markdown, sự không nhất quán về mức ưu tiên giữa các ứng dụng có thể gây chuyển đổi RTF và vấn đề UX
Stack biên tập TextView của Apple
- Paper được xây dựng trên framework TextKit 1 cũ, và các khái niệm cũng như tầng trừu tượng tương tự vẫn tồn tại nguyên vẹn trong TextKit 2 hoặc dưới các API tốt hơn
NSTextViewvàUITextViewcó khác biệt, nhưng API đủ tương đồng để có thể xem như cùng một họTextViewTextViewlà một thành phần lớn ngày càng phức tạp theo từng bản phát hành OS, và ứng dụng TextEdit gần như được cấu thành từ mộtTextViewduy nhất-
Các lớp cốt lõi tạo nên trình biên tập
NSTextStorage- Lưu chuỗi văn bản gốc
- Lưu các thuộc tính là cặp chuỗi-giá trị gắn với các phạm vi văn bản
- Phát sinh sự kiện khi văn bản và thuộc tính thay đổi
NSTextContainer- Định nghĩa hình dạng và kích thước của vùng chứa glyph
- Thường là hình chữ nhật nhưng cũng có thể là hình dạng tùy ý
NSLayoutManager- Dựa vào các phạm vi thuộc tính của
NSTextStorageđể tính kích thước và khoảng cách glyph - Trích xuất glyph vector từ font và chuyển một ký tự thành một hoặc nhiều glyph
- Tính điểm bắt đầu·kết thúc của từng dòng, số dòng và chiều cao toàn bộ văn bản
- Dựa vào các phạm vi thuộc tính của
TextView- Vẽ layout glyph do
NSLayoutManagertạo ra - Đồng bộ chiều cao view với chiều cao layout văn bản hiện tại
- Quản lý nhập liệu, vùng chọn, caret, thuộc tính nhập,
textContainerInset, диктовку, sao chép·dán, kiểm tra chính tả, v.v.
- Vẽ layout glyph do
ScrollView- Quản lý phần hiển thị của
TextView, cuộn, thanh cuộn và zoom - Có thể có
contentInsetriêng ngoàitextContainerInsetcủaTextView
- Quản lý phần hiển thị của
-
Khác biệt cuộn giữa AppKit và UIKit
- Trong AppKit,
NSScrollViewchứaNSClipViewvà haiNSScroller, cònNSClipViewchứaNSTextView, nên nhiều lớp cùng phối hợp tạo nên hành vi cuộn - Trong UIKit,
UITextViewkế thừaUIScrollView, nên tự nó chứa luôn logic cuộn - Khi caret di chuyển ra ngoài vùng nhìn thấy bị giới hạn bởi
contentInset,UITextViewsẽ tự động cuộn - Hành vi ở trình biên tập iOS khi caret bị che sau bàn phím rồi tự cuộn sang dòng tiếp theo là do
contentInsetphía dưới được đặt động theo chiều cao bàn phím hiện tại
- Trong AppKit,
NSAttributedString và tra cứu thuộc tính
NSAttributedStringlà nền tảng của biên tập rich text trong framework Apple và gồm hai thành phần- Chuỗi văn bản thuần
- Các cặp thuộc tính chuỗi-giá trị gắn vào các phạm vi văn bản trong chuỗi
- Thuộc tính chủ yếu dùng để styling, nhưng lập trình viên cũng có thể gắn các cặp chuỗi-giá trị tùy ý theo nhu cầu
NSRangegồmlocationvàlength;NSMakeRange(10, 5)nghĩa là phạm vi 5 ký tự bắt đầu từ vị trí 10, tức vị trí 10~14- Nếu cùng một thuộc tính được gán cho nhiều phạm vi tại cùng một vị trí thì phạm vi được áp dụng sau cùng sẽ được ưu tiên
-
Kiểm tra thuộc tính và phạm vi tại một vị trí cụ thể
- Giá trị thuộc tính tại một vị trí cụ thể được kiểm tra bằng
attribute:atIndex:effectiveRange: - Nếu giá trị trả về là
nilthì thuộc tính đó không tồn tại; nếu có giá trị thì theo ví dụ sẽ là giá trị thuộc tính như đối tượngNSFonthoặcUIFont - Nếu truyền con trỏ
NSRangelàm tham số cuối, có thể nhận được phạm vi mà thuộc tính có cùng giá trị được nối tiếp, hoặc phạm vi trống không có thuộc tính đó - Theo tài liệu,
effectiveRangekhông nhất thiết là phạm vi lớn nhất và phụ thuộc vào cách triển khai - Nếu cần phạm vi lớn nhất được đảm bảo thì phải dùng
attribute:atIndex:longestEffectiveRange:inRange: - Cũng có phương thức để lấy toàn bộ tổ hợp thuộc tính tại một vị trí cụ thể cùng phạm vi mà tổ hợp đó được duy trì liên tục
- Để duyệt thuộc tính trong một phạm vi có thể dùng
enumerateAttribute:inRange:options:usingBlock:
- Giá trị thuộc tính tại một vị trí cụ thể được kiểm tra bằng
Pipeline styling Markdown của Paper
- Styling của Paper hoạt động bằng cách áp dụng các thuộc tính đặc biệt do framework định nghĩa lên các phạm vi văn bản
- Trước khi styling, nó cũng dùng thuộc tính meta tùy chỉnh để nhận diện cấu trúc văn bản
-
Thuộc tính meta và thuộc tính kiểu dáng
- Thuộc tính meta
- Được định nghĩa để parser Markdown nhận diện từng phần riêng lẻ của cú pháp Markdown
- Là các cặp chuỗi-giá trị tùy chỉnh thuần túy dành cho thông tin ngữ nghĩa
- Không ảnh hưởng đến hình thức hiển thị của văn bản
- Có thể xem như một AST đơn giản hóa cho Markdown
- Thuộc tính kiểu dáng
- Là các thuộc tính thị giác được áp dụng lên các phần đã được đánh dấu bằng thuộc tính meta
- Là các cặp chuỗi-giá trị dựng sẵn do AppKit và UIKit định nghĩa
- Thuộc tính meta
-
Các sự kiện kích hoạt cập nhật thuộc tính
- Thuộc tính được đồng bộ theo văn bản Markdown trong
NSTextStoragevà các thay đổi cài đặt ảnh hưởng đến văn bản - Khi mở tài liệu, toàn bộ thuộc tính meta và thuộc tính kiểu dáng được cập nhật đầy đủ
- Khi văn bản thay đổi, chỉ các thuộc tính meta và kiểu dáng của phần bị ảnh hưởng được cập nhật cục bộ
- Khi cài đặt thay đổi, không đụng đến thuộc tính meta mà chỉ cập nhật toàn bộ thuộc tính kiểu dáng
- Thuộc tính được đồng bộ theo văn bản Markdown trong
-
Thứ tự cập nhật và chi phí layout
- Trước tiên bắt đầu một transaction chỉnh sửa văn bản để gom các thay đổi thuộc tính lại
- Nếu thay đổi thuộc tính mà không có transaction, mỗi lần đều sẽ kích hoạt việc tính lại layout tốn kém của
NSLayoutManager - Chuỗi Markdown được parse rồi chia thành các mảnh được đánh dấu bằng thuộc tính meta; khi thay đổi cài đặt thì vì cấu trúc Markdown không đổi nên bỏ qua bước này
- Các thuộc tính thị giác có thể ảnh hưởng đến vị trí hoặc kích thước glyph được cập nhật trước, và
NSParagraphStyleđược xem là thuộc tính quan trọng nhất vì ảnh hưởng đến layout dòng và đoạn - Sau khi kết thúc transaction, các thuộc tính trang trí không ảnh hưởng đến layout mới được cập nhật
- Theo thuật ngữ của Apple, thuộc tính trang trí tương ứng với thuộc tính render, và vì chúng tồn tại trên
NSLayoutManagerchứ không phảiNSTextStoragenên không nhận biết transaction
-
Thuộc tính nhập
- Thuộc tính nhập (typing attributes) gắn với thuộc tính ở vị trí trước caret khi vùng chọn rỗng, hoặc với thuộc tính tại điểm bắt đầu vùng chọn khi vùng chọn không rỗng
- Khi nhập ký tự, thuộc tính nhập sẽ tự động được áp dụng cho văn bản mới chèn vào
- Trong trình biên tập Markdown, chúng ít quan trọng hơn vì kiểu dáng được suy ra từ cú pháp Markdown
- Trong trình biên tập rich text, chúng quan trọng hơn vì kiểu dáng sẽ bám theo caret cho đến khi tắt style hoặc di chuyển caret sang vị trí khác
- Preview Mode của Paper mang lại trải nghiệm biên tập rich text ngay trong trình biên tập Markdown, với các thuộc tính nhập có thể bật tắt được nhấn mạnh
Hiệu năng nhập liệu và cách dùng thuộc tính meta
- Việc tách biệt thuộc tính meta, layout và trang trí có lợi cho việc xử lý nhanh một số thay đổi trong trình biên tập
- Chuyển đổi light·dark mode chỉ cần cập nhật thuộc tính trang trí nên rất nhanh vì không gây ra layout
- Các thay đổi cài đặt như điều chỉnh cỡ chữ vẫn cần tính lại layout cho toàn bộ tài liệu, nhưng vẫn nhanh hơn so với việc parse lại toàn bộ cấu trúc Markdown
-
Tối ưu nhập Markdown
- Yếu tố hiệu năng quan trọng nhất của trình biên tập văn bản là tốc độ nhập liệu
- Do đặc tính của Markdown, chỉ một thay đổi văn bản cũng có thể ảnh hưởng đến style của cả đoạn văn
- Cách mỗi lần gõ phím đều parse và styling lại cả đoạn là chính xác nhất về mặt kỹ thuật, nhưng với đoạn dài có thể làm chậm tốc độ biên tập
- Paper dùng một thuật toán phân nhánh theo ký tự sắp được nhập và các ký tự lân cận
- Nếu nhập ký hiệu Markdown đặc biệt hoặc xung quanh vị trí chỉnh sửa có ký hiệu đặc biệt, nó sẽ cập nhật lại toàn bộ đoạn
- Nếu không, nó dựa vào thuộc tính nhập để tăng tốc độ biên tập trong phần lớn tình huống nhập liệu
-
Ngoại lệ code block và cache đối tượng giá trị
- Code block là cấu trúc Markdown đa đoạn duy nhất trong Paper
- Trong tài liệu có code block, chỉ một lần gõ phím cũng có thể ảnh hưởng đến style của toàn bộ tài liệu
- Với tài liệu vượt quá một số lượng ký tự nhất định, Paper bỏ qua xử lý code block để giữ trình biên tập nhanh cho đa số người dùng không quan tâm đến code
- Lựa chọn này cũng khiến Paper hữu ích hơn với cả nhóm người dùng gần với lập trình viên
- Các đối tượng giá trị thuộc tính phức tạp được cấp phát lại sau mỗi lần gõ phím, nhưng vì chúng không thay đổi nếu các cài đặt ảnh hưởng đến văn bản không đổi nên được cache để tái sử dụng
NSFont/UIFontNSColor/UIColorNSParagraphStyle
-
Các tính năng dùng thuộc tính meta
- Phím tắt định dạng
- Muốn toggle thì phải biết thông tin style hiện có của văn bản Markdown đang chọn
- Nếu vùng chọn bao trọn hoàn toàn cùng một style thì style đó sẽ bị loại bỏ
- Nếu vùng chọn không có style đó thì sẽ thêm style vào
- Nếu vùng chọn chỉ bao một phần cùng một style thì style sẽ được chuyển vào vùng chọn
- Các style xung đột dùng để xác định kiểu đoạn như heading và blockquote không thể trộn lẫn, nên phải loại bỏ trước khi thêm style mới
- Di chuyển chương
- Khi di chuyển đến ranh giới chương trước hoặc sau, thuộc tính meta được dùng để tìm heading dựa trên vị trí caret
- Outline
- Duyệt toàn bộ heading, và khi nhấn một mục outline thì caret sẽ di chuyển đến chương tương ứng
- Sắp xếp lại chương
- Có thể sắp xếp lại các chương trong outline
- Chuyển đổi định dạng
- Muốn chuyển Markdown sang RTF, HTML, DOCX thì phải biết cấu trúc văn bản
- Vì Paper không nhúng thư viện bên ngoài, nó duyệt mô hình văn bản đã parse sẵn để tạo định dạng đầu ra
- Phím tắt định dạng
Toán học text container và hành vi chọn
-
Tính toán độ dài dòng và lề
- Quy tắc cốt lõi của text container là giữ độ dài dòng mong muốn và chia phần không gian còn lại thành inset ở hai bên
- Cũng có những tình huống phải tạo cảm giác đối xứng giả, như khi thẻ heading được đặt ra ngoài luồng văn bản thông thường
- Lúc này text container được dịch sang trái, còn đoạn văn được thụt vào bằng
NSParagraphStyle - Nếu đủ không gian, lề sẽ được giữ gần đối xứng về mặt thị giác
- Nếu không còn đủ không gian, sự đối xứng sẽ bị phá vỡ để ưu tiên giữ độ dài dòng đã chỉ định, nhưng chỉ cho đến khi phía phải vẫn còn padding
- Khi không còn padding, lề tối thiểu sẽ được ưu tiên hơn độ rộng dòng mong muốn
- Có thể kết hợp
minvàmaxđể triển khai phần lề co lại dần theo từng bước
-
Anchor của vùng chọn
- Vùng chọn văn bản luôn có một điểm neo
- Trên Mac, khi click rồi kéo, điểm được click sẽ là mốc; khi kéo vượt qua điểm đó thì hướng tăng giảm của vùng chọn sẽ đảo ngược
- Trên iOS, khi kéo một tay nắm chọn thì phía đối diện trở thành anchor, và ngược lại cũng như vậy
- Cùng logic đó cũng áp dụng khi mở rộng vùng chọn bằng bàn phím
Option+mũi tên trái·phải sẽ di chuyển theo ranh giới từ, và nếu giữ thêmShiftthì sẽ chọn theo từng từ- Ngay cả khi đã chọn bằng click·drag rồi tiếp tục mở rộng hoặc thu hẹp bằng bàn phím, điểm click ban đầu vẫn là anchor
-
Selection affinity
- Selection affinity quyết định tại ranh giới xuống dòng, điểm chèn sẽ xuất hiện sau ký tự cuối của dòng trước hay trước ký tự đầu của dòng sau
- Khi di chuyển caret bằng phím mũi tên, nó chỉ đơn giản chuyển dòng quanh khoảng trắng ở điểm xuống dòng
- Khi dùng phím tắt để đi đến cuối dòng, caret có thể vẫn ở cùng dòng và bám vào phía phải của khoảng trắng xuống dòng
TextViewcũng thực hiện hành vi selection affinity tương tự trong các tình huống khác
-
Undo coalescing
- Undo coalescing là thuật toán quyết định khi nào gộp phần văn bản đã nhập thành một thao tác undo duy nhất
- Có vẻ
TextViewdùng một thuật toán đơn giản là gộp nội dung được nhập miễn là không bị gián đoạn bởi hành động khác - Dù tạm dừng giữa chừng rồi gõ tiếp thì vẫn được gộp thành một lần undo, và việc xuống dòng cũng không cắt coalescing
- Sublime thích cách gộp các từ riêng biệt trong quá trình nhập thành đơn vị undo, và khi nhập từ dài còn có thể chia thành các đơn vị undo nhỏ hơn
- Không có đáp án đúng tuyệt đối cho hành vi này, và mỗi trình biên tập có thể có chiến lược khác nhau
UTI, pasteboard và trao đổi dữ liệu giữa ứng dụng
-
Uniform Type Identifiers
- UTI là một hệ thống phân cấp trong đó các kiểu dữ liệu
conform tokiểu cha giống như kế thừa - Các kiểu
public.*do Apple định nghĩa, dùng để nhận diện các định dạng phổ biến nhưpublic.html,public.jpeg - Để tránh xung đột, nhà phát triển có thể tạo định danh riêng theo sơ đồ tên miền đảo ngược
- Ưu điểm của hệ thống phân cấp là nếu ứng dụng có thể xử lý mọi định dạng văn bản, nó chỉ cần khai báo
public.textthay vì liệt kê từng định dạng riêng lẻ - Paper khai báo rằng nó có thể mở mọi tệp văn bản, nên dù không có highlighting, nó vẫn có thể mở các định dạng văn bản như
.html,.rtf - Vì tệp là một khái niệm xuyên nền tảng, nên trên thực tế phần mở rộng tệp được dùng như định danh
- Để tương thích, mọi UTI có thể định nghĩa một hoặc nhiều phần mở rộng tệp liên kết
- Markdown rất phổ biến nhưng không phải định dạng do Apple định nghĩa, nên xuất hiện các trường hợp rắc rối liên quan đến UTI bán công khai
- UTI là một hệ thống phân cấp trong đó các kiểu dữ liệu
-
Pasteboard và nhiều biểu diễn
- Pasteboard của Apple là một dictionary ánh xạ UTI tới dữ liệu văn bản hoặc nhị phân đã được serialize
- Có thể xem nội dung pasteboard theo thời gian thực bằng Clipboard Viewer trong Xcode Additional Tools
- Một thao tác sao chép có thể одновременно ghi nhiều biểu diễn của cùng một dữ liệu
- Một số ứng dụng còn dùng cả định danh cũ không phải UTI như
NeXT Rich Text Format v1.0 pasteboard typeđể tương thích ngược - Trình biên tập thường chọn định dạng giàu nhất mà nó có thể xử lý
Paste and Match StylehoặcPaste as Plain Texttrong trình biên tập rich text sẽ buộc dùng định dạng văn bản thuần trên pasteboard- Style áp dụng cho văn bản được dán thường lấy từ thuộc tính nhập
- Kéo thả cũng hoạt động dựa trên một pasteboard khác
- Sao chép-dán chuẩn dùng general pasteboard, còn tương tác tùy biến giữa ứng dụng có thể dùng pasteboard tùy chỉnh
-
RTF và
NSAttributedString- RTF về cơ bản là dạng serialize của
NSAttributedString, và ngược lạiNSAttributedStringlà giao diện lập trình của RTF - Vì
TextViewhoạt động trênNSTextStorage, là lớp con củaNSAttributedString, nên mặc định tương thích với pasteboard - Có thể sao chép nội dung lên pasteboard mà không cần viết code riêng
- RTF về cơ bản là dạng serialize của
-
Vấn đề liên ứng dụng với Markdown UTI
- Ngay cả khi hai ứng dụng đều triển khai giao thức chuẩn, việc sao chép·dán giữa các trình biên tập Markdown vẫn có thể phát sinh vấn đề
- Nếu trình biên tập thứ nhất xuất Markdown dưới
public.textvà biểu diễn rich text dướipublic.rtf, trình biên tập thứ hai có thể không biết đó là Markdown gốc và chọnpublic.rtf - Khi đó sẽ xảy ra chuyển đổi kép không cần thiết, và vì mỗi ứng dụng chuyển đổi Markdown↔RTF khác nhau nên có thể phát sinh các lỗi định dạng nhỏ như thêm xuống dòng
- Những khác biệt căn bản về style giữa Markdown và RTF cũng có thể lộ ra
- Để hành vi tự nhiên, cả hai ứng dụng đều phải xuất UTI
net.daringfireball.markdownvà ưu tiên nó cao hơnpublic.rtf - Paper từng cố xuất Markdown UTI, nhưng có vẻ các ứng dụng khác không ưu tiên nó cao hơn rich text
- Pages ưu tiên
net.daringfireball.markdownhơnpublic.rtf, nhưng lại chèn nguyên chuỗi Markdown gốc thay vì chuyển nó thành rich text - Vì vậy Paper đã loại bỏ Markdown UTI
-
Vì sao Paper cung cấp RTF và chia sẻ trên iOS
- Paper cung cấp RTF để có thể sao chép·dán tự nhiên với các trình biên tập rich text
- Với tinh thần là một công dân OS tốt, nó nên cung cấp nhiều định dạng biểu diễn của dữ liệu đã sao chép để ứng dụng nhận có thể chọn định dạng giàu nhất mà mình xử lý được
- Nếu sao chép văn bản Markdown trong Paper rồi dán vào ứng dụng Mail, nội dung sẽ được dán thành rich text được định dạng đẹp mắt thay vì là biến thể Markdown
- Tính năng chia sẻ trên iOS tương tự copy-paste nhưng có thêm UI
- Trong chia sẻ trên iOS, ứng dụng xuất dữ liệu ở nhiều định dạng và ứng dụng nhận sẽ chọn định dạng nó muốn
- Khác với pasteboard, để xuất hiện trong danh sách ứng dụng phía trên của UI chia sẻ iOS, ứng dụng phải chủ động cung cấp share extension có UI tùy chỉnh
1 bình luận
Các ý kiến trên Hacker News
Thật sự xuất sắc. Giờ có lẽ tôi sẽ đọc bài này trước https://www.objc.io/issues/5-ios7/getting-to-know-textkit/ khi muốn nhập môn TextKit
Đây là engine layout mới hiện được dùng mặc định trong các trường văn bản trên iOS và MacOS, và rất có khả năng sẽ trở thành lựa chọn duy nhất trong tương lai
Ứng dụng TextEdit gần như hoàn toàn được cấu thành từ một TextView duy nhất
Bên Windows, thứ tương ứng có thể xem là WordPad dựa trên control RichEdit. Một điểm thú vị nữa là RTF về cơ bản là định dạng tuần tự hóa của NSAttributedString
Control RichEdit của Windows cũng tương tự, và trên thực tế có vẻ phần triển khai của Windows xuất hiện trước: https://en.wikipedia.org/wiki/Rich_Text_Format
Tôi hơi bối rối về phần xử lý các thuộc tính trang trí bên ngoài transaction chỉnh sửa. Có đoạn giải thích rằng “vì nó nằm trong chính NSLayoutManager chứ không nằm trong NSTextStorage nên thậm chí không biết đến transaction”, nhưng các thuộc tính trang trí như màu sắc thường nằm trong NSTextStorage
Ý tác giả là màu áp dụng cho các ký tự Markdown được xử lý bằng hỗ trợ thuộc tính tạm thời của NSLayoutManager sao? Đây thường là tính năng dùng để tô màu các từ sai chính tả, nên tôi tò mò mục đích ở đây là gì
Ưu điểm của việc dùng thuộc tính tạm thời là thay đổi nhanh hơn nhiều so với gọi toàn bộ transaction của NSTextStorage. Ví dụ trong Paper, việc chuyển đổi chế độ sáng/tối rất nhanh
Bài viết rất hay, và với cá nhân tôi thì cũng rất đúng lúc vì hiện tôi đang làm việc với NSTextView
Tôi tò mò không biết tác giả lấy thông tin cần thiết để viết một bài như thế này từ đâu. Từ mã của người khác, từ trải nghiệm đau thương, hay từ developer.apple.com?
Trong thời đại của tài liệu DOM, dù nhìn vào Notion hay GitBook, khi cần làm những việc như phép màu với phân tích cú pháp và thao tác văn bản, tôi thường quay lại với chuỗi có thuộc tính
Đây là một kiến trúc thật sự thanh nhã, tôi không hiểu vì sao nó lại ít được biết đến như vậy. Bài viết cũng hay đến khó tin
Từng có thời tôi cố tự xây một trình soạn thảo văn bản hoàn toàn từ đầu, nên nếu lúc đó có bài như thế này thì hẳn đã là một tài liệu cực kỳ quý giá
Tôi đang dùng ứng dụng này rất tốt. Nó đã thay thế tất cả ứng dụng Markdown khác của tôi, bao gồm cả Obsidian và iA Writer
Ước gì có tài liệu kiểu này cho nhiều component iOS hơn
Thật mừng là đến năm 2024 vẫn còn có người làm Cocoa
Với tư cách là người đã phát triển ứng dụng Android lâu năm, tôi thấy cách Apple tiếp cận công việc hơi khác và thận trọng hơn khá thú vị
Trên Android, về cơ bản mọi việc liên quan đến layout và rendering đều do lớp Layout và các lớp con của nó xử lý, còn TextView triển khai một phần logic chỉnh sửa và lựa chọn. Điểm khác biệt duy nhất giữa EditText và TextView là EditText bật sẵn các chức năng chỉnh sửa vốn đã có trong TextView
Vấn đề của cách tiếp cận hơi nguyên khối này cùng API nghèo nàn là bạn sẽ bị chặn khi muốn kiểm soát chi tiết hơn việc render văn bản trong ứng dụng. Ví dụ, dù muốn truy cập từng glyph sau khi layout xong cũng không thể