ตอนที่ 3: การเขียน DSL เค้าโครงอัตโนมัติด้วยตัวดำเนินการมากเกินไปของ Swift และตัวสร้างผลลัพธ์ ️

Nov 26 2022
คุณสามารถดูบทความนี้ได้ที่นี่: ครั้งล่าสุดที่เราสร้างส่วนหลักของ DSL ซึ่งช่วยให้ผู้ใช้สามารถแสดงข้อจำกัดในรูปแบบเลขคณิตได้ ในบทความนี้ เราจะขยายความรวมถึง: ขั้นแรก เราสร้างประเภทเพื่อยึดคู่ของสมอที่สอดคล้องกับโปรโตคอล LayoutAnchor ที่กำหนดไว้ในส่วนที่ 1

คุณยังสามารถดูบทความนี้ได้ที่นี่:

ครั้งที่แล้วเราสร้างส่วนหลักของ DSL ซึ่งช่วยให้ผู้ใช้สามารถแสดงข้อจำกัดในรูปแบบเลขคณิตได้

ในบทความนี้เราจะขยายความให้ครอบคลุมถึง:

  • พุกรวม — ขนาด กึ่งกลาง ขอบแนวนอน ขอบแนวตั้ง
  • สิ่งที่ใส่เข้าไป — อนุญาตให้ผู้ใช้ตั้งค่าสิ่งที่ใส่เข้าไปบนจุดยึดขอบแบบรวม
  • ตัวสร้างผลลัพธ์เพื่อจัดการเอาต์พุตของจุดยึดที่แตกต่างกัน ช่วยให้สร้างชุดงานและเปิดใช้งานข้อจำกัดได้ง่าย

ก่อนอื่น เราสร้างประเภทเพื่อยึดคู่ของสมอที่สอดคล้องกับโปรโตคอล LayoutAnchor ที่กำหนดไว้ในส่วนที่ 1

ที่นี่ฉันพิจารณาแก้ไข LayoutBlock เพื่อเก็บแองเคอร์ จากนั้น เมื่อสร้างข้อจำกัดจากบล็อกดังกล่าว 2 บล็อก เราสามารถซิปจุดยึดเข้าด้วยกันและวนซ้ำไปมา บีบจุดยึดให้ติดกันและส่งผ่านค่าคงที่/ตัวคูณที่เกี่ยวข้อง

มี 2 ​​ข้อเสีย:

  • แม้แต่นิพจน์เดียวที่มีจุดยึดพื้นฐานก็จะส่งคืนอาร์เรย์ของข้อจำกัด DSL มีความซับซ้อนจากมุมมองของผู้ใช้
  • ผู้ใช้อาจลองใช้จุดยึดแบบคอมโพสิต (ที่มีจุดยึด 2 หรือ 4 จุด) กับจุดยึดแบบเอกพจน์ เราสามารถจัดการสิ่งนี้ได้โดยการเพิกเฉยต่อจุดยึดเพิ่มเติม คอมไพเลอร์จะไม่เตือน อย่างไรก็ตาม การดำเนินการเหล่านี้และข้อจำกัดผลลัพธ์จะไม่มีความหมาย สิ่งนี้มีศักยภาพที่จะแนะนำข้อบกพร่องที่น่าหงุดหงิดให้กับโค้ดของผู้ใช้ — เป็นสิ่งที่เราต้องการหลีกเลี่ยง!!

ขั้นตอนที่ 11: ขยายโปรโตคอล LayoutAnchorPair

ส่วนขยายนี้จะให้บริการตามวัตถุประสงค์เดียวกันกับส่วนขยายของโปรโตคอล LayoutAnchor ที่กำหนดไว้ก่อนหน้านี้ โดยทำหน้าที่เป็นตัวห่อหุ้มที่เรียกใช้เมธอดของประเภทฐานและส่งคืนข้อจำกัดที่เป็นผลลัพธ์

ข้อแตกต่างที่สำคัญคือแต่ละเมธอดจะส่งคืนอาร์เรย์ของข้อจำกัด ซึ่งรวมข้อจำกัดของ LayoutAnchor 2 ประเภทเข้าด้วยกัน

เนื่องจากแองเคอร์ที่เราส่งไปยัง LayoutAnchorPair ถูกจำกัดไว้เฉพาะประเภท LayoutAnchor เราจึงสามารถจัดเตรียมการใช้งานเริ่มต้นได้อย่างง่ายดาย

นอกจากนี้ แทนที่จะใช้ค่าคงที่ วิธีการเหล่านี้ใช้ EdgeInsetPair ซึ่งจะเสนอความสามารถในการจัดหาค่าคงที่ที่แตกต่างกันให้กับแต่ละข้อจำกัด

แต่ละวิธีแมปค่าคงที่ 1 เพื่อจำกัดจุดยึด 1 และค่าคงที่ 2 บนจุดยึด 2

ขั้นตอนที่ 13: สร้างประเภท LayoutAnchor ที่เป็นรูปธรรม

ในส่วนที่ 1 และ 2 เราไม่ต้องสร้างประเภท LayoutAnchor ที่เป็นรูปธรรม เนื่องจากเราเพิ่งทำให้ NSLayoutAnchors เริ่มต้นสอดคล้องกับโปรโตคอล LayoutAnchor ในที่นี้ เราจำเป็นต้องจัดหาจุดยึดของเราเองที่สอดคล้องกับโปรโตคอล AnchorPair

การแจ้งเตือนของ typealiases ที่ใช้ก่อนหน้านี้:

เรากำหนดประเภทที่ซ้อนกันซึ่งเป็นไปตามโปรโตคอล EdgeInsetPair สิ่งนี้เป็นไปตามข้อกำหนดประเภทที่เกี่ยวข้องของ AnchorPair — Insets ประเภทคอนกรีตที่ปฏิบัติตามโปรโตคอลนี้จะถูกใช้ระหว่างการดำเนินการโอเวอร์โหลดเพื่อตั้งค่าสิ่งที่ใส่เข้าไป

เราใช้คุณสมบัติที่คำนวณได้เพื่อให้สอดคล้องกับโปรโตคอล LayoutAnchorPair และ EdgeInsetPair คุณสมบัติที่คำนวณได้เหล่านี้ส่งคืนคุณสมบัติภายในของ LayoutAnchorPair และ EdgeInsetPair

สิ่งสำคัญคือต้องทำให้แน่ใจว่าค่าคงที่ที่ระบุโดยประเภทสิ่งที่ใส่เข้าไปนั้นตรงกับจุดยึดที่กำหนดไว้ในคู่จุดยึดนี้ โดยเฉพาะในบริบทของส่วนขยายที่กำหนดไว้ในขั้นตอนสุดท้าย โดย ที่ค่าคงที่ 1 ใช้เพื่อจำกัด จุดยึด anchor1

โปรโตคอล "ทั่วไป" นี้ช่วยให้เรากำหนดส่วนขยายโปรโตคอลหนึ่งรายการซึ่งใช้ได้กับคู่สมอทั้งหมด หากเราปฏิบัติตามกฎที่กล่าวไว้ข้างต้น ในขณะเดียวกัน เราสามารถใช้ป้ายกำกับเฉพาะจุดยึดที่มีความหมายมากขึ้น — ด้านล่าง/ด้านบน — นอกส่วนขยาย เช่น เมื่อกำหนดโอเปอเรเตอร์โอเวอร์โหลด

โซลูชันอื่นจะนำมาซึ่งการมีส่วนขยายแยกต่างหากสำหรับทุกประเภท ซึ่งไม่เลวนักเนื่องจากจำนวนจุดยึดมีจำกัด แต่ฉันขี้เกียจที่นี่และพยายามสร้างวิธีแก้ปัญหาที่เป็นนามธรรม ทั้งสองวิธีนี้เป็นรายละเอียดการใช้งานซึ่งอยู่ภายในไลบรารีและสามารถเปลี่ยนแปลงได้ในอนาคตโดยไม่ทำลายการเปลี่ยนแปลง

โปรดแสดงความคิดเห็นหากคุณเชื่อว่าการออกแบบที่แตกต่างกันจะเหมาะสมที่สุด

จุดตัดสินใจทางสถาปัตยกรรมเพิ่มเติม:

  • สิ่งที่ใส่เข้าไปต้องเป็นประเภทที่แยกจากกันเนื่องจากเริ่มต้นและใช้ภายนอกจุดยึด
  • ประเภทที่ซ้อนกันจะใช้เพื่อป้องกันเนมสเปซและทำให้มันสะอาด ในขณะเดียวกันก็เน้นความจริงที่ว่าการใช้งานประเภท Inset ขึ้นอยู่กับการใช้งาน PairAnchor ที่เฉพาะเจาะจง

หมายเหตุ: การเพิ่มสิ่งที่แทรกเข้าไปนั้นไม่เหมือนกับการเพิ่มค่าคงที่ให้กับนิพจน์

คุณอาจสังเกตว่าเราได้เพิ่มตัว ดำเนินการ ลบให้กับค่าคงที่สูงสุดก่อนที่จะส่งคืนเป็นส่วนหนึ่งของอินเทอร์เฟซ EdgePair ในทำนองเดียวกัน การใช้งาน XAxisAnchorPairจะเพิ่มเครื่องหมายลบให้กับจุดยึดท้าย การกลับค่าคงหมายความว่าสิ่งที่ใส่เข้าไปจะทำหน้าที่เป็นสิ่งที่ใส่เข้าไปแทนที่จะขยับขอบแต่ละอันในทิศทางเดียวกัน

มุมมองสีแดงถูกจำกัดไว้ที่ 200 ในภาพที่หนึ่งและสอง

ในภาพด้านซ้าย จุดยึดขอบทั้งหมดของมุมมองสีน้ำเงินถูกกำหนดให้เท่ากับของมุมมองสีแดงบวกด้วย 40 ซึ่งส่งผลให้มุมมองสีน้ำเงินมีขนาดเท่ากันแต่เลื่อนไป 40 ตามแกนทั้งสอง แม้ว่าสิ่งนี้จะสมเหตุสมผลในแง่ของข้อจำกัด แต่นี่ไม่ใช่การดำเนินการทั่วไปในตัวมันเอง

การตั้งค่าการแทรกรอบมุมมองหรือการเพิ่มช่องว่างภายในมุมมองเป็นเรื่องปกติมากขึ้น ดังนั้นในบริบทของการจัดเตรียม API สำหรับนักแสดงที่รวมกันจึงเหมาะสมกว่า

ในภาพด้านขวา เราตั้งค่ามุมมองสีน้ำเงินให้เท่ากับมุมมองสีแดงบวกส่วนที่แทรกเป็น 40 โดยใช้ DSL นี้ สิ่งนี้ทำให้การผกผันของค่าคงตัวที่อธิบายไว้ข้างต้นสำเร็จ

ขั้นตอนที่ 14: ขยาย View & LayoutGuide เพื่อเริ่มต้นจุดยึดแบบคอมโพสิต

เช่นเดียวกับที่เราทำก่อนหน้านี้ เราขยาย ประเภท ViewและLayoutGuideด้วยคุณสมบัติที่คำนวณซึ่งเริ่มต้นLayoutBlocksเมื่อถูกเรียกใช้

ขั้นตอนที่ 15: โอเวอร์โหลดตัวดำเนินการ +/- เพื่อให้นิพจน์แทรกขอบได้

สำหรับจุดยึดขอบแนวนอนและแนวตั้ง เราต้องการให้ผู้ใช้สามารถระบุสิ่งที่ใส่เข้าไปได้ เพื่อให้บรรลุเป้าหมายนี้ เราได้ขยาย ประเภท UIEdgeInsetsเนื่องจากผู้ใช้ส่วนใหญ่ของ DSL นี้คุ้นเคยอยู่แล้ว

ส่วนขยายอนุญาตให้เริ่มต้นได้ด้วยการแทรกเพียงบน/ล่างหรือซ้าย/ขวา — ส่วนที่เหลือเริ่มต้นเป็น 0

เราจำเป็นต้องเพิ่มคุณสมบัติใหม่ในLayoutBlockเพื่อจัดเก็บ edgeInsets

ต่อไปเราจะโอเวอร์โหลดโอเปอเร เตอร์สำหรับอินพุต: LayoutBlock with UIEdgeInsets

เราแมปอินสแตนซ์ของUIEdgeInsetsที่ผู้ใช้ให้มา กับประเภทที่ซ้อนกันที่เกี่ยวข้องซึ่งกำหนดเป็นส่วนหนึ่งของLayoutAnchorPairที่ เป็นรูปธรรม

พารามิเตอร์พิเศษหรือไม่ถูกต้องที่ผู้ใช้ส่งผ่านโดยเป็นส่วนหนึ่งของ ประเภท UIEdgeInsetจะถูกละเว้น

ขั้นตอนที่ 15: Overload Comparison Operators เพื่อกำหนดความสัมพันธ์ของข้อจำกัด

หลักการยังคงเหมือนเดิม เราโอเวอร์โหลดตัวดำเนินการความสัมพันธ์สำหรับอินพุตLayoutBlocksและLayoutAnchorPair

หากผู้ใช้ระบุขอบแทรกไว้คู่หนึ่ง เราจะใช้มัน มิฉะนั้น เราจะสร้างคู่แทรกทั่วไปจากค่าคงที่ของLayoutBlocks โครงสร้างส่วนแทรกทั่วไปเป็นแบบห่อหุ้ม ซึ่งแตกต่างจากส่วนแทรกอื่นๆ ที่จะไม่ลบล้างด้านใดด้านหนึ่ง

ขั้นตอนที่ 16: Dimension LayoutAnchorPair

เช่นเดียวกับตัวยึดมิติเดียว ตัวยึดมิติคู่ — widthAnchorและheightAnchor — สามารถถูกจำกัดให้เป็นค่าคงที่ได้ ดังนั้นเราจึงต้องจัดเตรียมโอเปอเรเตอร์โอเวอร์โหลดแยกต่างหากเพื่อจัดการกับกรณีการใช้งานนี้

  • อนุญาตให้ผู้ใช้ DSL แก้ไขทั้งความสูงและความกว้างให้เป็นค่าคงที่เดียวกัน — การสร้างสี่เหลี่ยมจัตุรัส
  • อนุญาตให้ผู้ใช้ DSL แก้ไขSizeAnchorPairเป็นประเภท CGSize ซึ่งเหมาะสมกว่าในกรณีส่วนใหญ่ เนื่องจากมุมมองไม่ใช่สี่เหลี่ยมจัตุรัส

ขั้นตอนที่ 17: การใช้ตัวสร้างผลลัพธ์เพื่อจัดการประเภท [NSLayoutConstraint] และ NSLayoutConstraint

จุดยึดแบบคอมโพสิตสร้างปัญหาที่น่าสนใจ การใช้จุดยึดเหล่านี้ในนิพจน์ทำให้เกิดข้อ จำกัด มากมาย สิ่งนี้อาจทำให้ผู้ใช้ปลายทางของ DSL ยุ่งเหยิง

เราต้องการจัดเตรียมวิธีการรวมข้อจำกัดเหล่านี้เข้าด้วยกันโดยไม่มีรหัสต้นแบบ และเปิดใช้งานอย่างมีประสิทธิภาพ — ในคราวเดียว แทนที่จะแยกทีละรายการหรือแยกเป็นชุด

เปิดตัวในตัวสร้างผลลัพธ์ Swift 5.4 (หรือที่เรียกว่าตัวสร้างฟังก์ชัน) ช่วยให้คุณสร้างผลลัพธ์โดยใช้ 'บล็อกการสร้าง' โดยปริยายจากชุดส่วนประกอบ อันที่จริงแล้วพวกมันคือองค์ประกอบหลักเบื้องหลัง Swift UI

ใน DSL นี้ ผลลัพธ์สุดท้ายคืออาร์เรย์ของวัตถุNSLayoutConstraint

เรามีฟังก์ชันการสร้างซึ่งช่วยให้ตัวสร้างผลลัพธ์สามารถแก้ไขข้อจำกัดแต่ละข้อและอาร์เรย์ของข้อจำกัดให้เป็นอาร์เรย์เดียว ตรรกะทั้งหมดนี้ถูกซ่อนจากผู้ใช้ปลายทางของ DSL

ฟังก์ชันเหล่านี้ส่วนใหญ่ฉันคัดลอกโดยตรงจากตัวอย่างข้อเสนอตัวสร้างผลลัพธ์ที่มีวิวัฒนาการอย่างรวดเร็ว ฉันได้เพิ่มการทดสอบหน่วยเพื่อให้แน่ใจว่าทำงานได้อย่างถูกต้องใน DSL นี้

วางผลลัพธ์ทั้งหมดนี้ในสิ่งต่อไปนี้:

ตัวสร้างผลลัพธ์ยังช่วยให้เรารวมโฟลว์การควบคุมเพิ่มเติมภายในการปิด ซึ่งไม่สามารถทำได้กับอาร์เรย์

ขอบคุณที่อ่าน! บทความนี้ใช้เวลาเขียนหลายวัน — ดังนั้นหากคุณได้เรียนรู้สิ่งใหม่ๆ ฉันจะขอบคุณ ⭐ ใน repo นี้!

หากคุณมีคำแนะนำสำหรับฉัน อย่าอาย: และแบ่งปันประสบการณ์ของคุณ!

เวอร์ชันสุดท้ายของ DSL นี้มีสมอสี่มิติซึ่งทำจาก ประเภท AnchorPair คู่หนึ่ง ...

คุณสามารถค้นหารหัสทั้งหมดได้ที่นี่: