Bagian 3: Menulis DSL Tata Letak Otomatis dengan Overloading Operator Swift dan Pembuat Hasil ️

Anda juga dapat menonton artikel ini di sini:
Terakhir kali kami membuat bagian utama DSL, memungkinkan pengguna akhir untuk mengekspresikan batasan dengan cara aritmatika.
Pada artikel ini kami akan memperluas ini untuk memasukkan:
- Jangkar gabungan — ukuran, tengah, horizontalEdges, verticalEdges
- Insets — memungkinkan pengguna untuk mengatur insets pada jangkar tepi gabungan
- Pembuat Hasil untuk menangani output dari jangkar yang berbeda, memungkinkan pengelompokan dan aktivasi kendala yang mudah.
Pertama kita membuat tipe untuk menampung sepasang jangkar yang sesuai dengan protokol LayoutAnchor yang ditentukan di Bagian 1.
Di sini saya mempertimbangkan untuk memodifikasi LayoutBlock untuk menampung serangkaian jangkar. Kemudian, saat membuat batasan dari 2 blok seperti itu, kita dapat menyatukan jangkar dan mengulanginya, membatasi jangkar satu sama lain dan meneruskan konstanta/pengganda yang relevan.
Ada 2 kerugian:
- Bahkan ekspresi tunggal dengan jangkar dasar akan mengembalikan serangkaian kendala. Ini memperumit DSL dari sudut pandang pengguna.
- Seorang pengguna dapat mencoba dan menggunakan jangkar komposit (dengan 2 atau 4 jangkar) dengan jangkar tunggal. Kami dapat menangani ini dengan mengabaikan jangkar tambahan. Kompiler tidak akan menghasilkan peringatan. Namun, operasi ini dan kendala yang dihasilkan tidak akan berarti. Ini berpotensi memperkenalkan bug yang membuat frustrasi ke dalam kode pengguna akhir — sesuatu yang ingin kita hindari!!
Langkah 11: Perluas protokol LayoutAnchorPair.
Ekstensi ini akan melayani tujuan yang sama dengan ekstensi protokol LayoutAnchor yang didefinisikan sebelumnya — ekstensi ini bertindak sebagai pembungkus yang memanggil metode dari tipe dasar dan mengembalikan batasan yang dihasilkan.
Perbedaan utama di sini adalah bahwa setiap metode mengembalikan larik batasan, yang menggabungkan batasan dari 2 tipe LayoutAnchor.
Karena jangkar yang kami teruskan ke LayoutAnchorPair dibatasi ke jenis LayoutAnchor, kami dapat menyediakan implementasi default dengan mudah.
Selain itu, alih-alih konstanta, metode ini menggunakan EdgeInsetPair yang akan menawarkan kemampuan untuk menyediakan konstanta yang berbeda untuk setiap batasan.
Setiap metode memetakan konstanta1 untuk membatasi jangkar1 dan konstanta2 pada jangkar2.
Langkah 13: Buat tipe LayoutAnchor yang konkret.
Di Bagian 1 & 2 kita tidak perlu membuat tipe LayoutAnchor yang konkret karena kita baru saja membuat NSLayoutAnchors default sesuai dengan protokol LayoutAnchor. Namun di sini, kita perlu menyediakan jangkar kita sendiri yang sesuai dengan protokol AnchorPair.
Pengingat typealiase yang digunakan sebelumnya:
Kami mendefinisikan tipe bersarang yang memenuhi protokol EdgeInsetPair. Ini memenuhi persyaratan tipe terkait AnchorPair — Insets. Jenis konkret yang mengikuti protokol ini akan digunakan selama operasi kelebihan muatan untuk menyetel inset.
Kami menggunakan properti yang dihitung agar sesuai dengan protokol LayoutAnchorPair dan EdgeInsetPair. Properti yang dihitung ini mengembalikan properti internal LayoutAnchorPair dan EdgeInsetPair.
Di sini penting untuk memastikan bahwa konstanta yang disediakan oleh tipe inset cocok dengan jangkar yang ditentukan pada pasangan jangkar ini. Khususnya dalam konteks ekstensi yang ditentukan pada langkah terakhir, di mana constant1 digunakan untuk membatasi anchor1 .
Protokol "generik" ini memungkinkan kita untuk menentukan satu ekstensi protokol yang berlaku untuk semua pasangan jangkar. Asalkan kita mengikuti aturan yang dibahas di atas. Pada saat yang sama kita dapat menggunakan label khusus jangkar yang lebih bermakna — bawah/atas — di luar ekstensi. Seperti saat mendefinisikan kelebihan operator.
Solusi alternatif akan memerlukan ekstensi terpisah untuk semua jenis - yang tidak terlalu buruk karena jumlah jangkar terbatas. Tapi saya malas di sini dan mencoba membuat solusi abstrak. Either way, ini adalah detail implementasi yang internal ke perpustakaan dan dapat diubah di masa mendatang tanpa merusak perubahan.
Silakan tinggalkan komentar jika Anda percaya desain yang berbeda akan optimal.
Lebih banyak poin keputusan arsitektur:
- Inset harus merupakan jenis yang terpisah karena diinisialisasi dan digunakan di luar jangkar.
- Tipe bersarang digunakan untuk melindungi namespace dan menjaganya tetap bersih, sekaligus menyoroti fakta bahwa implementasi tipe Inset bergantung pada implementasi PairAnchor tertentu.
Catatan: Menambahkan inset tidak sama dengan menambahkan konstanta ke ekspresi.
Anda mungkin telah memperhatikan bahwa kami menambahkan operator minus ke konstanta teratas sebelum mengembalikannya sebagai bagian dari antarmuka EdgePair. Demikian pula, implementasi XAxisAnchorPair menambahkan minus pada trailing anchor. Membalikkan konstanta berarti inset akan berfungsi sebagai inset daripada menggeser setiap sisi ke arah yang sama.

Di dalam gambar kiri, semua jangkar tepi tampilan biru disetel sama dengan tampilan merah ditambah 40. Yang menghasilkan tampilan biru memiliki ukuran yang sama tetapi digeser 40 sepanjang kedua sumbu. Sementara ini masuk akal dalam hal kendala, ini bukanlah operasi yang umum.
Menyetel sisipan di sekitar tampilan atau menambahkan padding di sekitar tampilan jauh lebih umum. Karenanya dalam konteks menyediakan API untuk aktor gabungan lebih masuk akal.
Di gambar kanan, kami menyetel tampilan biru agar sama dengan tampilan merah ditambah sisipan 40 menggunakan DSL ini. Ini dicapai dengan inversi konstanta yang dijelaskan di atas.
Langkah 14: Perpanjang View & LayoutGuide untuk menginisialisasi jangkar komposit.
Sama seperti yang kita lakukan sebelumnya, kita memperluas tipe View dan LayoutGuide dengan properti terkomputasi yang menginisialisasi LayoutBlocks saat dipanggil.
Langkah 15: Bebankan operator +/- untuk mengizinkan ekspresi dengan sisipan tepi.
Untuk jangkar tepi horizontal dan tepi vertikal, kami ingin pengguna dapat menentukan inset. Untuk mencapainya, kami memperluas jenis UIEdgeInsets karena sudah tidak asing lagi bagi sebagian besar pengguna DSL ini.
Ekstensi memungkinkan inisialisasi hanya dengan inset atas/bawah atau kiri/kanan — sisanya default ke 0.
Kita juga perlu menambahkan properti baru ke LayoutBlock untuk menyimpan edgeInsets.
Selanjutnya kita membebani operator untuk input: LayoutBlock dengan UIEdgeInsets .
Kami memetakan instance UIEdgeInsets yang disediakan oleh pengguna, ke tipe bersarang relevan yang didefinisikan sebagai bagian dari LayoutAnchorPair konkret .
Parameter ekstra atau salah yang diteruskan oleh pengguna sebagai bagian dari tipe UIEdgeInset akan diabaikan.
Langkah 15: Overload Comparison Operator untuk mendefinisikan hubungan kendala.
Prinsipnya tetap sama seperti sebelumnya. Kami membebani operator relasi untuk input LayoutBlocks dan LayoutAnchorPair .
Jika pengguna menyediakan sepasang inset tepi — kami menggunakannya, jika tidak, kami membuat sepasang inset generik dari konstanta LayoutBlocks . Struktur inset generik adalah pembungkus, tidak seperti inset lainnya, ia tidak meniadakan salah satu sisi.
Langkah 16: Tata Letak DimensiAnchorPair
Sama seperti jangkar dimensi tunggal, sepasang jangkar dimensi — jangkar lebar dan jangkar tinggi — dapat dibatasi ke konstanta. Oleh karena itu, kami harus menyediakan kelebihan beban operator yang terpisah untuk menangani kasus penggunaan ini.
- Izinkan pengguna DSL untuk menetapkan tinggi dan lebar ke konstanta yang sama — membuat persegi.
- Izinkan pengguna DSL untuk memperbaiki SizeAnchorPair ke jenis CGSize — lebih masuk akal dalam banyak kasus karena tampilan bukan persegi.
Langkah 17: Menggunakan Pembuat Hasil untuk menangani tipe [NSLayoutConstraint] dan NSLayoutConstraint.
Jangkar komposit menciptakan masalah yang menarik. Menggunakan jangkar ini dalam ekspresi menghasilkan serangkaian kendala. Ini mungkin menjadi berantakan bagi pengguna akhir DSL.
Kami ingin menyediakan cara untuk menyatukan kendala ini tanpa kode boilerplate dan mengaktifkannya secara efektif — sekaligus, bukan secara individual atau dalam batch terpisah.
Diperkenalkan di Swift 5.4 result builder (juga dikenal sebagai function builder) memungkinkan Anda membangun hasil menggunakan 'build blocks' secara implisit dari serangkaian komponen. Faktanya, mereka adalah blok bangunan yang mendasari di balik Swift UI.
Dalam DSL ini, hasil akhirnya adalah larik objek NSLayoutConstraint .
Kami menyediakan fungsi build, yang memungkinkan pembuat hasil menyelesaikan batasan individu dan larik batasan menjadi satu larik. Semua logika ini disembunyikan dari pengguna akhir DSL.
Sebagian besar fungsi ini saya salin langsung dari contoh proposal pembangun hasil evolusi cepat . Saya menambahkan tes unit untuk memastikan mereka bekerja dengan benar di DSL ini.
Menempatkan semua hasil ini sebagai berikut:
Pembuat hasil juga memungkinkan kami untuk menyertakan aliran kontrol tambahan dalam penutupan, yang tidak mungkin dilakukan dengan larik.
Itu saja, terima kasih telah membaca! Artikel ini membutuhkan waktu berhari-hari untuk ditulis — jadi jika Anda mempelajari sesuatu yang baru, saya akan menghargai ⭐ di repo ini!
Jika Anda memiliki saran untuk saya, jangan malu: dan bagikan pengalaman Anda!
Versi terakhir dari DSL ini mencakup jangkar empat dimensi yang terbuat dari sepasang tipe AnchorPair …
Anda dapat menemukan semua kode di sini: