2 คะแนน โดย GN⁺ 2024-03-06 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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 การป้อนตามคำบอก การคัดลอก·วาง การตรวจสะกด ฯลฯ
    • ScrollView
      • จัดการส่วนที่มองเห็นของ TextView การเลื่อน แถบเลื่อน และการซูม
      • สามารถมี contentInset แยกจาก textContainerInset ของ TextView
  • ความต่างของการเลื่อนใน AppKit และ UIKit

    • ใน AppKit นั้น NSScrollView มี NSClipView และ NSScroller สองตัว โดย NSClipView เป็นตัวบรรจุ NSTextView ทำให้หลายคลาสร่วมกันสร้างพฤติกรรมการเลื่อน
    • ใน UIKit นั้น UITextView สืบทอดจาก UIScrollView จึงรวมลอจิกการเลื่อนไว้ด้วยตัวเอง
    • เมื่อ caret เคลื่อนออกนอกพื้นที่มองเห็นที่ถูกจำกัดด้วย contentInset UITextView จะเลื่อนอัตโนมัติ
    • พฤติกรรมในตัวแก้ไขบน iOS ที่เมื่อ caret ไปอยู่หลังคีย์บอร์ดแล้วจะเลื่อนไปบรรทัดถัดไป เกิดจากการตั้งค่า contentInset ด้านล่างแบบไดนามิกให้เท่ากับความสูงคีย์บอร์ดปัจจุบัน

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 และการเปลี่ยนแปลงการตั้งค่าที่มีผลต่อข้อความ
    • ตอน เปิดเอกสาร จะอัปเดตทั้งเมตาแอตทริบิวต์และสไตล์แอตทริบิวต์ทั้งเอกสาร
    • ตอน ข้อความเปลี่ยน จะอัปเดตบางส่วนของเมตาแอตทริบิวต์และสไตล์แอตทริบิวต์เฉพาะส่วนที่ได้รับผล
    • ตอน การตั้งค่าเปลี่ยน จะไม่เปลี่ยนเมตาแอตทริบิวต์ และอัปเดตเฉพาะสไตล์แอตทริบิวต์ทั้งเอกสาร
  • ลำดับการอัปเดตและต้นทุนของเลย์เอาต์

    • เริ่มจากเปิดทรานแซกชันการแก้ไขข้อความเพื่อรวมการเปลี่ยนแปลงแอตทริบิวต์เข้าด้วยกัน
    • หากเปลี่ยนแอตทริบิวต์โดยไม่มีทรานแซกชัน จะทำให้ 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/UIFont
      • NSColor/UIColor
      • NSParagraphStyle
  • ฟีเจอร์ที่ใช้เมตาแอตทริบิวต์

    • คีย์ลัดจัดรูปแบบ
      • ต้องรู้ข้อมูลสไตล์เดิมของข้อความ 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 กึ่งสาธารณะ
  • 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 ได้โดยไม่ต้องเขียนโค้ดเพิ่ม
  • ปัญหาระหว่างแอปของ Markdown UTI

    • แม้ตัวแก้ไข Markdown สองตัวจะรองรับโปรโตคอลมาตรฐานเหมือนกัน การคัดลอก·วางระหว่างกันก็ยังอาจมีปัญหา
    • หากตัวแก้ไขตัวแรกส่งออก Markdown เป็น public.text และส่ง rich text เป็น public.rtf ตัวแก้ไขตัวที่สองอาจไม่รู้ว่านี่คือ Markdown แบบ native และเลือก public.rtf
    • ในกรณีนี้จะเกิดการแปลงซ้ำที่ไม่จำเป็น และเพราะแต่ละแอปแปลง Markdown↔RTF ไม่เหมือนกัน จึงอาจเกิดปัญหารูปแบบเล็ก ๆ เช่นการขึ้นบรรทัดเพิ่ม
    • ความต่างด้านสไตล์ที่เป็นพื้นฐานระหว่าง Markdown และ RTF ก็อาจปรากฏให้เห็นด้วย
    • หากทั้งสองแอปส่งออก net.daringfireball.markdown UTI และให้ความสำคัญสูงกว่า 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 ความคิดเห็น

 
GN⁺ 2024-03-06
ความคิดเห็นจาก Hacker News
  • ยอดเยี่ยมจริง ๆ ตอนนี้สำหรับแหล่งข้อมูลเริ่มต้นเกี่ยวกับ TextKit ผมคงจะอ่านบทความนี้ก่อน https://www.objc.io/issues/5-ios7/getting-to-know-textkit/

    • ถ้าเป็น iOS 15.0 ขึ้นไป หรือ MacOS Monterey ขึ้นไป ควรดู TextKit 2 ดู https://developer.apple.com/documentation/appkit/textkit/usi...
      มันเป็นเอนจินเลย์เอาต์ใหม่ที่ใช้เป็นค่าเริ่มต้นในฟิลด์ข้อความของ 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 หรือเปล่า? ปกติฟีเจอร์นี้ใช้ตอนลงสีคำที่สะกดผิด เลยสงสัยว่าจุดประสงค์คืออะไร

    • ใช่แล้ว NSTextStorage ก็สามารถเก็บแอตทริบิวต์แบบเดียวกับ temporary attributes ได้เช่นกัน ใน TextKit 2 เรียกสิ่งนี้ว่า rendering attributes
      ข้อดีของการใช้ temporary attributes คือการเปลี่ยนแปลงทำได้เร็วกว่าการเรียก transaction ทั้งหมดของ NSTextStorage มาก เช่น ใน Paper การสลับโหมดสว่าง/มืดทำได้เร็วมาก
  • เป็นบทความที่ดีจริง ๆ และส่วนตัวผมก็กำลังจัดการกับ NSTextView อยู่พอดี เลยเหมาะกับจังหวะมาก
    อยากรู้ว่าได้ข้อมูลที่จำเป็นสำหรับเขียนบทความแบบนี้มายังไง จากโค้ดของคนอื่น ประสบการณ์เจ็บปวด หรือ developer.apple.com?

    • ผมทำงานกับ Paper มา 9 ปี แล้ว และระหว่างนั้นก็มีเวลามากพอที่จะศึกษาว่าโครงสร้างทั้งหมดทำงานอย่างไร
  • ในยุคของเอกสาร DOM แม้จะดู Notion หรือ GitBook อยู่ แต่เวลาจะทำเรื่องเหมือนเวทมนตร์กับการ parse และจัดการข้อความ ก็มักจะย้อนกลับมาใช้ attributed string อยู่บ่อย ๆ
    มันเป็นโครงสร้างที่สง่างามมาก ผมไม่เข้าใจว่าทำไมถึงไม่เป็นที่รู้จักกว่านี้ บทความก็ดีจนน่าเหลือเชื่อ

    • ถ้ามีตัวอย่างก็อยากเรียนรู้เพิ่มเติม
    • สงสัยว่ามีคอมโพเนนต์เว็บ rich text editor ที่ใช้ attributed string ไหม เบื่อ DOM แล้ว
  • ในฐานะคนที่เคยพยายามสร้าง text editor เองทั้งหมดตั้งแต่ศูนย์ ถ้ามีบทความแบบนี้ตอนนั้นคงเป็นแหล่งข้อมูลชั้นยอดมาก

  • ใช้แอปนี้ได้ดีมาก ๆ มันมาแทน แอป Markdown อื่นทั้งหมดของผม รวมถึง Obsidian และ iA Writer

    • ชื่อ Paper ก็เหมาะดี แต่ถ้าจำไม่ผิด มีแอป iOS ที่ชื่อ “Paper” อย่างน้อยอีกสองแอป
    • น่าสนใจ แอปสองตัวนั้นเป็นแอปหลักที่ผมใช้เขียนอยู่พอดี คงต้องลองดูแอปนี้ด้วย
  • อยากให้มีเอกสารแบบนี้สำหรับ คอมโพเนนต์ iOS มากขึ้น

  • ในปี 2024 ยังดีที่อย่างน้อยก็ยังมีคนทำ Cocoa อยู่

  • ในฐานะคนที่พัฒนาแอป Android มานาน ผมรู้สึกว่าน่าสนใจที่ Apple ทำสิ่งต่าง ๆ แตกต่างออกไปเล็กน้อยและระมัดระวังกว่า
    บน Android โดยพื้นฐานแล้วงานทั้งหมดที่เกี่ยวกับเลย์เอาต์และการเรนเดอร์จะถูกจัดการโดยคลาส Layout และคลาสย่อยของมัน ส่วน TextView จะ implement ส่วนหนึ่งของตรรกะการแก้ไขและการเลือกข้อความ ความแตกต่างเพียงอย่างเดียวระหว่าง EditText กับ TextView คือ EditText เปิดใช้ฟีเจอร์แก้ไขที่มีอยู่แล้วใน TextView
    ปัญหาของแนวทางที่ค่อนข้างเป็นก้อนเดียวแบบนี้และ API ที่อ่อนแอคือ เมื่ออยากควบคุมการเรนเดอร์ข้อความของแอปให้ละเอียดขึ้นก็จะติดขัด เช่น ต่อให้ต้องการเข้าถึง glyph แต่ละตัวหลังจากเลย์เอาต์เสร็จแล้ว ก็ทำไม่ได้