โครงสร้างภายในเชิงเทคนิคของตัวแก้ไขข้อความของ Apple
(papereditor.app)- Paper ทำงานเป็นตัวแก้ไข
TextViewที่อิงกับ TextKit 1 บนแพลตฟอร์ม Apple โดยNSTextStorage,NSTextContainer,NSLayoutManager,TextView, และScrollViewร่วมกันสร้างประสบการณ์การแก้ไข NSAttributedStringใช้แสดง rich text ด้วยสตริงและแอตทริบิวต์ (attribute) ตามแต่ละช่วง และ Paper แยกเมตาแอตทริบิวต์สำหรับโครงสร้าง Markdown ออกจากสไตล์แอตทริบิวต์สำหรับการแสดงผลบนหน้าจอ- การอัปเดตสไตล์จะแตกต่างกันตามการเปิดเอกสาร การเปลี่ยนข้อความ และการเปลี่ยนการตั้งค่า โดยแอตทริบิวต์ที่มีผลต่อเลย์เอาต์จะถูกรวมเป็นทรานแซกชัน และ แอตทริบิวต์ตกแต่ง จะอัปเดตโดยไม่ต้องคำนวณเลย์เอาต์ใหม่
- ประสิทธิภาพการพิมพ์ดีขึ้นด้วยวิธีตรวจว่ากำลังแก้ไขรอบอักขระพิเศษของ Markdown หรือไม่ เพื่อหลีกเลี่ยงการพาร์สทั้งย่อหน้าใหม่ แต่โครงสร้างอย่าง บล็อกโค้ด ที่อาจมีผลต่อสไตล์ของทั้งเอกสารต้องมีการจัดการเป็นกรณียกเว้น
- การคัดลอก·วางทำงานบน UTI และการแทนข้อมูลหลายแบบใน pasteboard และ UTI กึ่งสาธารณะ ที่ Apple ไม่ได้กำหนดไว้ เช่น Markdown อาจก่อให้เกิดปัญหาการเลือกความสำคัญไม่ตรงกันระหว่างแอป รวมถึงการแปลงเป็น RTF และปัญหา UX
สแตกการแก้ไข TextView ของ Apple
- Paper สร้างขึ้นบนเฟรมเวิร์ก TextKit 1 รุ่นเก่า แต่แนวคิดและ abstraction เดียวกันยังคงมีอยู่ใน TextKit 2 หรืออยู่ภายใต้ API ที่ดีกว่า
- แม้
NSTextViewและUITextViewจะต่างกัน แต่ API คล้ายกันมากพอที่จะมองรวมเป็นตระกูลTextViewเดียวกันได้ TextViewเป็นคอมโพเนนต์ขนาดใหญ่ที่ซับซ้อนขึ้นทุกครั้งที่ OS ออกรุ่นใหม่ และแอป TextEdit ก็แทบประกอบด้วยTextViewเพียงตัวเดียว-
ชั้นหลักที่ประกอบกันเป็นตัวแก้ไข
NSTextStorage- เก็บสตริงข้อความต้นฉบับ
- เก็บแอตทริบิวต์แบบคู่สตริง-ค่า ที่ผูกกับช่วงข้อความ
- ส่งอีเวนต์เมื่อข้อความหรือแอตทริบิวต์เปลี่ยน
NSTextContainer- กำหนดรูปทรงและขนาดของพื้นที่ที่จะใส่ glyph
- ส่วนใหญ่เป็นสี่เหลี่ยม แต่ก็เป็นรูปทรงใดก็ได้
NSLayoutManager- ดูช่วงแอตทริบิวต์ของ
NSTextStorageแล้วคำนวณขนาดและระยะห่างของ glyph - ดึง vector glyph จากฟอนต์ และแปลงอักขระหนึ่งตัวให้เป็นหนึ่ง glyph หรือหลาย glyph
- คำนวณจุดเริ่ม·จุดสิ้นสุดของแต่ละบรรทัด จำนวนบรรทัด และความสูงรวมของข้อความทั้งหมด
- ดูช่วงแอตทริบิวต์ของ
TextView- วาดเลย์เอาต์ glyph ที่
NSLayoutManagerสร้างขึ้น - ซิงก์ความสูงของวิวกับความสูงเลย์เอาต์ข้อความปัจจุบัน
- จัดการอินพุต ช่วงที่เลือก caret typing attributes
textContainerInsetการป้อนตามคำบอก การคัดลอก·วาง การตรวจสะกด ฯลฯ
- วาดเลย์เอาต์ glyph ที่
ScrollView- จัดการส่วนที่มองเห็นของ
TextViewการเลื่อน แถบเลื่อน และการซูม - สามารถมี
contentInsetแยกจากtextContainerInsetของTextView
- จัดการส่วนที่มองเห็นของ
-
ความต่างของการเลื่อนใน AppKit และ UIKit
- ใน AppKit นั้น
NSScrollViewมีNSClipViewและNSScrollerสองตัว โดยNSClipViewเป็นตัวบรรจุNSTextViewทำให้หลายคลาสร่วมกันสร้างพฤติกรรมการเลื่อน - ใน UIKit นั้น
UITextViewสืบทอดจากUIScrollViewจึงรวมลอจิกการเลื่อนไว้ด้วยตัวเอง - เมื่อ caret เคลื่อนออกนอกพื้นที่มองเห็นที่ถูกจำกัดด้วย
contentInsetUITextViewจะเลื่อนอัตโนมัติ - พฤติกรรมในตัวแก้ไขบน iOS ที่เมื่อ caret ไปอยู่หลังคีย์บอร์ดแล้วจะเลื่อนไปบรรทัดถัดไป เกิดจากการตั้งค่า
contentInsetด้านล่างแบบไดนามิกให้เท่ากับความสูงคีย์บอร์ดปัจจุบัน
- ใน AppKit นั้น
NSAttributedString และการดูแอตทริบิวต์
NSAttributedStringเป็นพื้นฐานของการแก้ไข rich text ในเฟรมเวิร์กของ Apple และประกอบด้วยสองส่วน- สตริงข้อความธรรมดา
- คู่แอตทริบิวต์แบบสตริง-ค่าที่ผูกอยู่กับช่วงข้อความในสตริง
- แอตทริบิวต์ส่วนใหญ่มักใช้สำหรับจัดสไตล์ แต่ผู้พัฒนาก็สามารถแนบคู่สตริง-ค่าตามต้องการได้เช่นกัน
NSRangeประกอบด้วยlocationและlengthโดยNSMakeRange(10, 5)หมายถึงช่วงยาว 5 อักขระที่เริ่มจากตำแหน่ง 10 หรือก็คือตำแหน่ง 10~14- หากแอตทริบิวต์เดียวกันถูกกำหนดไว้หลายช่วงในตำแหน่งเดียวกัน ช่วงที่ถูกใช้ล่าสุดจะมีความสำคัญกว่า
-
ตรวจค่าแอตทริบิวต์และช่วงที่ตำแหน่งหนึ่ง
- ค่าของแอตทริบิวต์ที่ตำแหน่งหนึ่งตรวจได้ด้วย
attribute:atIndex:effectiveRange: - ถ้าค่าที่คืนมาเป็น
nilแปลว่าไม่มีแอตทริบิวต์นั้น และถ้ามีค่า คืนมาก็จะเป็นค่าของแอตทริบิวต์ เช่นออบเจ็กต์NSFontหรือUIFont - หากส่งพอยน์เตอร์
NSRangeไปในอาร์กิวเมนต์สุดท้าย จะได้รับช่วงที่แอตทริบิวต์มีค่าเดียวกันต่อเนื่องกัน หรือช่วงว่างที่ไม่มีแอตทริบิวต์นั้น - ตามเอกสาร
effectiveRangeไม่ได้รับประกันว่าจะเป็นช่วงกว้างสุดเสมอ และขึ้นอยู่กับการติดตั้งจริง - หากต้องการช่วงกว้างสุดที่รับประกันได้ ต้องใช้
attribute:atIndex:longestEffectiveRange:inRange: - ยังมีเมธอดที่ใช้ดึงชุดแอตทริบิวต์ทั้งหมด ณ ตำแหน่งหนึ่ง พร้อมช่วงที่ชุดนั้นต่อเนื่องกัน
- การไล่ดูแอตทริบิวต์ในช่วงหนึ่งสามารถใช้
enumerateAttribute:inRange:options:usingBlock:
- ค่าของแอตทริบิวต์ที่ตำแหน่งหนึ่งตรวจได้ด้วย
ไปป์ไลน์การจัดสไตล์ Markdown ของ Paper
- การจัดสไตล์ของ Paper ใช้วิธีใส่แอตทริบิวต์พิเศษที่เฟรมเวิร์กกำหนดลงบนช่วงข้อความ
- ก่อนจัดสไตล์ ยังใช้ เมตาแอตทริบิวต์แบบกำหนดเอง เพื่อระบุโครงสร้างของข้อความด้วย
-
เมตาแอตทริบิวต์และสไตล์แอตทริบิวต์
- เมตาแอตทริบิวต์
- ถูกกำหนดโดยตัวพาร์ส Markdown เพื่อระบุส่วนย่อยต่าง ๆ ของไวยากรณ์ Markdown
- เป็นคู่สตริง-ค่าแบบกำหนดเองที่มีไว้เพื่อข้อมูลเชิงความหมายล้วน ๆ
- ไม่ส่งผลต่อรูปลักษณ์ของข้อความโดยตรง
- มองได้ว่าเป็น AST แบบย่อสำหรับ Markdown
- สไตล์แอตทริบิวต์
- เป็นแอตทริบิวต์เชิงภาพที่ใช้ทับบนส่วนที่ถูกทำเครื่องหมายด้วยเมตาแอตทริบิวต์
- เป็นคู่สตริง-ค่าในตัวที่กำหนดโดย AppKit และ UIKit
- เมตาแอตทริบิวต์
-
อีเวนต์ที่ทำให้แอตทริบิวต์อัปเดต
- แอตทริบิวต์จะซิงก์ให้ตรงกับข้อความ Markdown ใน
NSTextStorageและการเปลี่ยนแปลงการตั้งค่าที่มีผลต่อข้อความ - ตอน เปิดเอกสาร จะอัปเดตทั้งเมตาแอตทริบิวต์และสไตล์แอตทริบิวต์ทั้งเอกสาร
- ตอน ข้อความเปลี่ยน จะอัปเดตบางส่วนของเมตาแอตทริบิวต์และสไตล์แอตทริบิวต์เฉพาะส่วนที่ได้รับผล
- ตอน การตั้งค่าเปลี่ยน จะไม่เปลี่ยนเมตาแอตทริบิวต์ และอัปเดตเฉพาะสไตล์แอตทริบิวต์ทั้งเอกสาร
- แอตทริบิวต์จะซิงก์ให้ตรงกับข้อความ Markdown ใน
-
ลำดับการอัปเดตและต้นทุนของเลย์เอาต์
- เริ่มจากเปิดทรานแซกชันการแก้ไขข้อความเพื่อรวมการเปลี่ยนแปลงแอตทริบิวต์เข้าด้วยกัน
- หากเปลี่ยนแอตทริบิวต์โดยไม่มีทรานแซกชัน จะทำให้
NSLayoutManagerคำนวณเลย์เอาต์ใหม่ทุกครั้งซึ่งมีต้นทุนสูง - จากนั้นพาร์สสตริง Markdown แล้วแบ่งออกเป็นชิ้นที่ถูกทำเครื่องหมายด้วยเมตาแอตทริบิวต์ ส่วนในกรณีเปลี่ยนการตั้งค่า ขั้นตอนนี้จะถูกข้ามเพราะโครงสร้าง Markdown ไม่เปลี่ยน
- ต่อจากนั้นอัปเดตแอตทริบิวต์เชิงภาพที่อาจมีผลต่อขนาดหรือจุดวาง glyph ก่อน โดย
NSParagraphStyleถูกมองว่าเป็นแอตทริบิวต์สำคัญที่สุด เพราะมีผลต่อเลย์เอาต์ของบรรทัดและย่อหน้า - หลังปิดทรานแซกชันแล้ว จึงอัปเดต แอตทริบิวต์ตกแต่ง ที่ไม่ส่งผลต่อเลย์เอาต์
- แอตทริบิวต์ตกแต่งตรงกับสิ่งที่ Apple เรียกว่า rendering attributes และอยู่ใน
NSLayoutManagerไม่ใช่NSTextStorageจึงไม่รับรู้ทรานแซกชัน
-
typing attributes
- typing attributes จะผูกกับแอตทริบิวต์ก่อนหน้า caret เมื่อช่วงที่เลือกว่าง และผูกกับแอตทริบิวต์ที่จุดเริ่มต้นของช่วงเลือกเมื่อช่วงที่เลือกไม่ว่าง
- เมื่อพิมพ์อักขระ ระบบจะใช้ typing attributes กับข้อความที่แทรกใหม่โดยอัตโนมัติ
- ในตัวแก้ไข Markdown เรื่องนี้สำคัญน้อยกว่า เพราะสไตล์ถูกอนุมานจากไวยากรณ์ Markdown
- แต่ในตัวแก้ไข rich text จะสำคัญกว่า เพราะสไตล์จะติดอยู่กับ caret จนกว่าจะปิดสไตล์หรือย้าย caret ไปตำแหน่งอื่น
- Preview Mode ของ Paper มอบประสบการณ์แก้ไขแบบ rich text ที่เน้น typing attributes ซึ่งเปิดปิดได้ภายในตัวแก้ไข Markdown
ประสิทธิภาพการพิมพ์และการใช้เมตาแอตทริบิวต์
- การแยกเมตา เลย์เอาต์ และแอตทริบิวต์ตกแต่ง ช่วยให้จัดการการเปลี่ยนแปลงบางอย่างในตัวแก้ไขได้รวดเร็ว
- การสลับโหมดสว่าง·มืดอัปเดตเฉพาะแอตทริบิวต์ตกแต่ง จึงเร็วมากเพราะไม่ทำให้เกิดการคำนวณเลย์เอาต์
- การเปลี่ยนการตั้งค่าอย่างการปรับขนาดข้อความ ยังต้องคำนวณเลย์เอาต์ใหม่ทั้งเอกสาร แต่ก็ยังเร็วกว่าการพาร์สโครงสร้าง Markdown ทั้งหมดใหม่
-
การเพิ่มประสิทธิภาพการพิมพ์ Markdown
- ปัจจัยด้านประสิทธิภาพที่สำคัญที่สุดของตัวแก้ไขข้อความคือ ความเร็วในการพิมพ์
- ด้วยลักษณะของ Markdown การเปลี่ยนข้อความเพียงครั้งเดียวอาจมีผลต่อสไตล์ของทั้งย่อหน้า
- วิธีที่พาร์สและจัดสไตล์ทั้งย่อหน้าใหม่ทุกครั้งที่กดแป้น เป็นวิธีที่ถูกต้องที่สุดในทางเทคนิค แต่ในย่อหน้าที่ยาวอาจทำให้การแก้ไขช้าลง
- Paper ใช้อัลกอริทึมที่แบ่งการทำงานตามอักขระถัดไปที่พิมพ์และอักขระรอบข้าง
- หากมีการพิมพ์สัญลักษณ์พิเศษของ Markdown หรือมีสัญลักษณ์พิเศษอยู่ใกล้ตำแหน่งแก้ไข ก็จะอัปเดตทั้งย่อหน้า
- นอกเหนือจากนั้น จะอาศัย typing attributes เพื่อเพิ่มความเร็วของตัวแก้ไขในสถานการณ์การพิมพ์ส่วนใหญ่
-
ข้อยกเว้นของบล็อกโค้ดและแคชของ value object
- บล็อกโค้ด เป็นโครงสร้าง Markdown แบบหลายย่อหน้าเพียงชนิดเดียวใน Paper
- ในเอกสารที่มีบล็อกโค้ด การกดแป้นครั้งเดียวอาจส่งผลต่อสไตล์ของทั้งเอกสาร
- Paper จะละเลยการประมวลผลบล็อกโค้ดเมื่อเอกสารยาวเกินจำนวนอักขระที่กำหนด เพื่อคงความเร็วในการแก้ไขให้ผู้ใช้ส่วนใหญ่ที่ไม่ได้สนใจโค้ด
- ตัวเลือกนี้ยังทำให้ Paper มีประโยชน์มากขึ้นกับผู้ใช้สายใกล้นักพัฒนาอีกด้วย
- ออบเจ็กต์ค่าของแอตทริบิวต์ที่ซับซ้อนจะถูกจัดสรรใหม่ทุกครั้งที่กดแป้น แต่หากการตั้งค่าที่มีผลต่อข้อความไม่เปลี่ยน ค่าพวกนี้ก็ไม่เปลี่ยนเช่นกัน จึงสามารถแคชและนำกลับมาใช้ได้
NSFont/UIFontNSColor/UIColorNSParagraphStyle
-
ฟีเจอร์ที่ใช้เมตาแอตทริบิวต์
- คีย์ลัดจัดรูปแบบ
- ต้องรู้ข้อมูลสไตล์เดิมของข้อความ Markdown ที่เลือกไว้ จึงจะสลับได้
- หากช่วงที่เลือกครอบสไตล์เดียวกันทั้งหมด ก็จะลบสไตล์นั้นออก
- หากช่วงที่เลือกไม่มีสไตล์เดียวกัน ก็จะเพิ่มสไตล์นั้น
- หากช่วงที่เลือกครอบสไตล์เดียวกันเพียงบางส่วน ก็จะย้ายสไตล์นั้นมาอยู่ที่ช่วงที่เลือก
- สไตล์ที่ชนกันและกำหนดประเภทของย่อหน้า เช่น heading และ blockquote ไม่สามารถผสมกันได้ จึงต้องลบสไตล์เดิมก่อนเพิ่มสไตล์ใหม่
- การย้ายบท
- เมื่อต้องย้ายไปยังขอบเขตของบทก่อนหน้าหรือถัดไป จะใช้เมตาแอตทริบิวต์เพื่อหา heading ตามตำแหน่ง caret
- Outline
- ไล่ดู heading ทั้งหมด และเมื่อแตะรายการใน outline ก็จะย้าย caret ไปยังบทนั้น
- การจัดเรียงบทใหม่
- สามารถเรียงลำดับบทใหม่ได้ใน outline
- การแปลงรูปแบบ
- หากต้องการแปลง Markdown เป็น RTF, HTML, DOCX จำเป็นต้องรู้โครงสร้างข้อความ
- เพราะ Paper ไม่รวมไลบรารีภายนอก จึงไล่ดูโมเดลข้อความที่พาร์สไว้ล่วงหน้าแล้วสร้างรูปแบบผลลัพธ์
- คีย์ลัดจัดรูปแบบ
คณิตศาสตร์ของ text container และพฤติกรรมการเลือก
-
การคำนวณความยาวบรรทัดและระยะขอบ
- กฎหลักของ text container คือรักษา ความยาวบรรทัด ที่ต้องการไว้ และแบ่งพื้นที่ที่เหลือเป็น inset สองด้าน
- มีบางกรณีที่ต้องสร้างความสมมาตรแบบหลอก ๆ เช่นเมื่อแท็ก heading ถูกวางไว้นอกโฟลว์ข้อความปกติ
- ในกรณีนี้จะเลื่อน text container ไปทางซ้าย และใช้
NSParagraphStyleเพื่อทำย่อหน้าเยื้อง - หากมีพื้นที่เพียงพอ จะรักษาระยะขอบให้ดูใกล้เคียงสมมาตรทางสายตา
- หากไม่มีพื้นที่เพิ่มเติม ก็จะยอมเสียความสมมาตรเพื่อคงความยาวบรรทัดที่กำหนดไว้ แต่จะทำเช่นนั้นตราบเท่าที่ยังมี padding ด้านขวาเหลืออยู่
- หากไม่มี padding เหลือแล้ว ระยะขอบขั้นต่ำจะมีความสำคัญมากกว่าความกว้างบรรทัดที่ต้องการ
- สามารถใช้การผสม
minและmaxเพื่อทำระยะขอบที่ค่อย ๆ พับตัวลงได้
-
selection anchor
- การเลือกข้อความจะมี จุดยึด อยู่เสมอ
- บน Mac เมื่อคลิกแล้วลาก จุดที่คลิกจะเป็นฐาน และเมื่อเลยจุดนั้นไป ทิศทางการเพิ่มหรือลดช่วงเลือกจะกลับด้าน
- บน iOS เมื่อจับลาก selection handle ด้านหนึ่ง อีกด้านจะกลายเป็น anchor และในทางกลับกันก็เช่นเดียวกัน
- ลอจิกเดียวกันนี้ใช้เมื่อขยายการเลือกด้วยคีย์บอร์ด
Option+ลูกศรซ้าย·ขวา ใช้ย้ายตามขอบเขตคำ และถ้ากดShiftร่วมด้วยก็จะเลือกทีละคำ- แม้จะเลือกด้วยการคลิก·ลาก แล้วค่อยใช้คีย์บอร์ดขยายหรือลดต่อ จุดคลิกแรกก็ยังคงเป็น anchor
-
selection affinity
- selection affinity เป็นตัวกำหนดว่าที่ขอบเขตของตัวขึ้นบรรทัดใหม่ จุดแทรกจะแสดงหลังอักขระสุดท้ายของบรรทัดก่อนหน้า หรือก่อนอักขระแรกของบรรทัดถัดไป
- หากย้าย caret ด้วยปุ่มลูกศร ระบบจะสลับบรรทัดอย่างง่ายบริเวณช่องว่างของจุดขึ้นบรรทัดใหม่
- หากใช้คีย์ลัดเพื่อย้ายไปท้ายบรรทัด caret อาจคงอยู่ในบรรทัดเดิมและแนบอยู่ด้านขวาของช่องว่างที่เป็นตัวขึ้นบรรทัดใหม่
TextViewยังใช้พฤติกรรม selection affinity คล้ายกันนี้ในสถานการณ์อื่นด้วย
-
Undo coalescing
- Undo coalescing คืออัลกอริทึมที่ตัดสินว่าจะรวมข้อความที่พิมพ์เมื่อใดให้เป็นการกระทำเดียวที่ undo ได้
- ดูเหมือน
TextViewจะใช้อัลกอริทึมแบบง่ายที่รวมสิ่งที่พิมพ์เข้าด้วยกันตราบเท่าที่ยังไม่ถูกขัดด้วยการกระทำอื่น - ต่อให้หยุดพักกลางคันแล้วพิมพ์ต่อ ก็ยังอาจถูกรวมเป็น undo เดียว และการขึ้นบรรทัดใหม่ก็ไม่ตัดการ coalescing
- Sublime ชอบแนวทางที่แยกคำที่พิมพ์แต่ละคำเป็นหน่วย undo และเมื่อพิมพ์คำยาวก็อาจแบ่งเป็นหน่วย undo ที่เล็กลงอีก
- ไม่มีคำตอบที่ถูกต้องเพียงแบบเดียวสำหรับพฤติกรรมนี้ และแต่ละตัวแก้ไขอาจมีกลยุทธ์ต่างกัน
UTI, pasteboard และการแลกเปลี่ยนข้อมูลระหว่างแอป
-
Uniform Type Identifiers
- UTI เป็นระบบลำดับชั้นของชนิดข้อมูลที่
conform toกันได้ คล้ายชนิดข้อมูลลูกสืบทอดจากชนิดข้อมูลแม่ - ชนิด
public.*ถูกกำหนดโดย Apple และใช้ระบุฟอร์แมตที่ใช้แพร่หลาย เช่นpublic.html,public.jpeg - นักพัฒนาสามารถสร้างตัวระบุของตนเองด้วยรูปแบบชื่อโดเมนกลับด้านเพื่อหลีกเลี่ยงการชนกัน
- ข้อดีของระบบลำดับชั้นคือ ถ้าแอปรองรับไฟล์ข้อความทุกแบบ ก็ประกาศเพียง
public.textได้โดยไม่ต้องไล่ระบุทีละฟอร์แมต - Paper ประกาศว่าสามารถเปิดไฟล์ข้อความได้ทุกชนิด ดังนั้นแม้ไม่มี highlighting ก็ยังเปิดฟอร์แมตข้อความอย่าง
.html,.rtfได้ - เพราะไฟล์เป็นแนวคิดข้ามแพลตฟอร์ม โดยพฤตินัยส่วนขยายไฟล์จึงถูกใช้เป็นตัวระบุ
- เพื่อความเข้ากันได้ UTI ทุกตัวสามารถกำหนดส่วนขยายไฟล์ที่เชื่อมโยงอย่างน้อยหนึ่งรายการได้
- Markdown ใช้กันอย่างแพร่หลาย แต่เป็นฟอร์แมตที่ Apple ไม่ได้กำหนดไว้ จึงเกิดกรณียุ่งยากที่เกี่ยวกับ UTI กึ่งสาธารณะ
- UTI เป็นระบบลำดับชั้นของชนิดข้อมูลที่
-
pasteboard และการแทนข้อมูลหลายแบบ
- pasteboard ของ Apple คือดิกชันนารีที่แมประหว่าง UTI กับข้อความแบบ serialized หรือข้อมูลไบนารี
- สามารถดูเนื้อหาใน pasteboard แบบเรียลไทม์ได้ด้วย Clipboard Viewer จาก Xcode Additional Tools
- การคัดลอกหนึ่งครั้งสามารถเขียนการแทนข้อมูลหลายแบบของข้อมูลเดียวกันพร้อมกันได้
- บางแอปยังใช้ตัวระบุ legacy ที่ไม่ใช่ UTI เช่น
NeXT Rich Text Format v1.0 pasteboard typeเพื่อความเข้ากันได้ย้อนหลัง - โดยทั่วไปตัวแก้ไขจะเลือกฟอร์แมตที่สมบูรณ์ที่สุดที่ตัวเองรองรับได้
Paste and Match StyleหรือPaste as Plain Textในตัวแก้ไข rich text จะบังคับให้ใช้ฟอร์แมตข้อความธรรมดาจาก pasteboard- สไตล์ที่ใช้กับข้อความที่วางมักจะมาจาก typing attributes
- drag and drop ก็ทำงานอยู่บน pasteboard อีกชุดหนึ่ง
- การ copy-paste มาตรฐานใช้ general pasteboard และการโต้ตอบข้ามแอปแบบกำหนดเองสามารถสร้าง custom pasteboard ได้
-
RTF และ
NSAttributedString- โดยพื้นฐานแล้ว RTF คือรูปแบบ serialized ของ
NSAttributedStringและในทางกลับกันNSAttributedStringก็คืออินเทอร์เฟซการเขียนโปรแกรมของ RTF TextViewทำงานอยู่บนNSTextStorageซึ่งเป็นคลาสลูกของNSAttributedStringจึงเข้ากันได้กับ pasteboard โดยพื้นฐาน- สามารถคัดลอกเนื้อหาไปยัง pasteboard ได้โดยไม่ต้องเขียนโค้ดเพิ่ม
- โดยพื้นฐานแล้ว RTF คือรูปแบบ serialized ของ
-
ปัญหาระหว่างแอปของ Markdown UTI
- แม้ตัวแก้ไข Markdown สองตัวจะรองรับโปรโตคอลมาตรฐานเหมือนกัน การคัดลอก·วางระหว่างกันก็ยังอาจมีปัญหา
- หากตัวแก้ไขตัวแรกส่งออก Markdown เป็น
public.textและส่ง rich text เป็นpublic.rtfตัวแก้ไขตัวที่สองอาจไม่รู้ว่านี่คือ Markdown แบบ native และเลือกpublic.rtf - ในกรณีนี้จะเกิดการแปลงซ้ำที่ไม่จำเป็น และเพราะแต่ละแอปแปลง Markdown↔RTF ไม่เหมือนกัน จึงอาจเกิดปัญหารูปแบบเล็ก ๆ เช่นการขึ้นบรรทัดเพิ่ม
- ความต่างด้านสไตล์ที่เป็นพื้นฐานระหว่าง Markdown และ RTF ก็อาจปรากฏให้เห็นด้วย
- หากทั้งสองแอปส่งออก
net.daringfireball.markdownUTI และให้ความสำคัญสูงกว่าpublic.rtfการทำงานจึงจะเป็นธรรมชาติ - Paper เคยพยายามส่งออก Markdown UTI แต่ดูเหมือนแอปอื่นจะไม่ได้ให้ความสำคัญเหนือ rich text
- Pages ให้ความสำคัญกับ
net.daringfireball.markdownมากกว่าpublic.rtfแต่กลับแทรกสตริง Markdown ต้นฉบับตรง ๆ โดยไม่แปลงเป็น rich text - ด้วยเหตุนี้ Paper จึงถอด Markdown UTI ออก
-
เหตุผลที่ Paper ให้ RTF และการแชร์บน iOS
- Paper ให้ RTF เพื่อให้คัดลอก·วางเข้าตัวแก้ไข rich text ได้อย่างเป็นธรรมชาติ
- ในฐานะแอปที่ทำตัวเป็นพลเมืองที่ดีของ OS ควรให้ข้อมูลที่คัดลอกมาในหลายฟอร์แมต เพื่อให้แอปรับเลือกฟอร์แมตที่สมบูรณ์ที่สุดที่ตัวเองรองรับ
- หากคัดลอกข้อความ Markdown จาก Paper ไปวางในแอป Mail ข้อความจะถูกวางเป็น rich text ที่จัดรูปแบบสวยงาม ไม่ใช่ Markdown ดิบ
- ฟีเจอร์ แชร์ บน iOS คล้ายกับ copy-paste แต่มี UI เพิ่มเข้ามา
- ในการแชร์บน iOS แอปสามารถส่งออกข้อมูลได้หลายฟอร์แมต และแอปรับจะเลือกฟอร์แมตที่ต้องการ
- ต่างจาก pasteboard ตรงที่ หากต้องการให้แอปแสดงในรายการแอปด้านบนของ UI แชร์บน iOS แอปต้องมี share extension ที่มี custom UI อย่างชัดเจน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ยอดเยี่ยมจริง ๆ ตอนนี้สำหรับแหล่งข้อมูลเริ่มต้นเกี่ยวกับ TextKit ผมคงจะอ่านบทความนี้ก่อน https://www.objc.io/issues/5-ios7/getting-to-know-textkit/
มันเป็นเอนจินเลย์เอาต์ใหม่ที่ใช้เป็นค่าเริ่มต้นในฟิลด์ข้อความของ iOS และ MacOS ปัจจุบัน และมีแนวโน้มสูงว่าจะกลายเป็นตัวเลือกเดียวในอนาคต
แอป TextEdit แทบทั้งหมดประกอบขึ้นจาก TextView ตัวเดียว
ฝั่ง Windows คงเทียบได้กับ WordPad ที่อิงจากคอนโทรล RichEdit อีกอย่างที่น่าสนใจคือ RTF แทบจะเป็นรูปแบบ serialization ของ NSAttributedString
คอนโทรล RichEdit ของ Windows ก็เป็นแบบเดียวกัน และในความเป็นจริงดูเหมือนว่าฝั่ง Windows จะทำมาก่อนด้วยซ้ำ: https://en.wikipedia.org/wiki/Rich_Text_Format
ส่วนที่บอกว่าจัดการแอตทริบิวต์สำหรับการตกแต่งนอก editing transaction ทำให้สับสนเล็กน้อย มีคำอธิบายว่า “มันอยู่ใน NSLayoutManager เอง ไม่ได้อยู่ใน NSTextStorage จึงไม่รู้จัก transaction เลย” แต่ แอตทริบิวต์สำหรับการตกแต่ง อย่างสีก็มักจะอยู่ใน NSTextStorage
ผู้เขียนหมายความว่าใช้การรองรับ temporary attributes ของ NSLayoutManager เพื่อจัดการสีที่นำไปใช้กับอักขระ Markdown หรือเปล่า? ปกติฟีเจอร์นี้ใช้ตอนลงสีคำที่สะกดผิด เลยสงสัยว่าจุดประสงค์คืออะไร
ข้อดีของการใช้ temporary attributes คือการเปลี่ยนแปลงทำได้เร็วกว่าการเรียก transaction ทั้งหมดของ NSTextStorage มาก เช่น ใน Paper การสลับโหมดสว่าง/มืดทำได้เร็วมาก
เป็นบทความที่ดีจริง ๆ และส่วนตัวผมก็กำลังจัดการกับ NSTextView อยู่พอดี เลยเหมาะกับจังหวะมาก
อยากรู้ว่าได้ข้อมูลที่จำเป็นสำหรับเขียนบทความแบบนี้มายังไง จากโค้ดของคนอื่น ประสบการณ์เจ็บปวด หรือ developer.apple.com?
ในยุคของเอกสาร DOM แม้จะดู Notion หรือ GitBook อยู่ แต่เวลาจะทำเรื่องเหมือนเวทมนตร์กับการ parse และจัดการข้อความ ก็มักจะย้อนกลับมาใช้ attributed string อยู่บ่อย ๆ
มันเป็นโครงสร้างที่สง่างามมาก ผมไม่เข้าใจว่าทำไมถึงไม่เป็นที่รู้จักกว่านี้ บทความก็ดีจนน่าเหลือเชื่อ
ในฐานะคนที่เคยพยายามสร้าง text editor เองทั้งหมดตั้งแต่ศูนย์ ถ้ามีบทความแบบนี้ตอนนั้นคงเป็นแหล่งข้อมูลชั้นยอดมาก
ใช้แอปนี้ได้ดีมาก ๆ มันมาแทน แอป Markdown อื่นทั้งหมดของผม รวมถึง Obsidian และ iA Writer
อยากให้มีเอกสารแบบนี้สำหรับ คอมโพเนนต์ iOS มากขึ้น
ในปี 2024 ยังดีที่อย่างน้อยก็ยังมีคนทำ Cocoa อยู่
ในฐานะคนที่พัฒนาแอป Android มานาน ผมรู้สึกว่าน่าสนใจที่ Apple ทำสิ่งต่าง ๆ แตกต่างออกไปเล็กน้อยและระมัดระวังกว่า
บน Android โดยพื้นฐานแล้วงานทั้งหมดที่เกี่ยวกับเลย์เอาต์และการเรนเดอร์จะถูกจัดการโดยคลาส Layout และคลาสย่อยของมัน ส่วน TextView จะ implement ส่วนหนึ่งของตรรกะการแก้ไขและการเลือกข้อความ ความแตกต่างเพียงอย่างเดียวระหว่าง EditText กับ TextView คือ EditText เปิดใช้ฟีเจอร์แก้ไขที่มีอยู่แล้วใน TextView
ปัญหาของแนวทางที่ค่อนข้างเป็นก้อนเดียวแบบนี้และ API ที่อ่อนแอคือ เมื่ออยากควบคุมการเรนเดอร์ข้อความของแอปให้ละเอียดขึ้นก็จะติดขัด เช่น ต่อให้ต้องการเข้าถึง glyph แต่ละตัวหลังจากเลย์เอาต์เสร็จแล้ว ก็ทำไม่ได้