SwiftUI على الرفوف

في كل مرة يظهر إطار عمل جديد بلغة برمجة ، عاجلاً أم آجلاً ، يظهر أشخاص يتعلمون اللغة منه. ربما كان هذا هو الحال في تطوير iOS في وقت ظهور Swift: في البداية كان يعتبر إضافة إلى الهدف- C - لكنني لم أجده بالفعل. الآن ، إذا بدأت من الصفر ، فلم يعد اختيار اللغة يستحق ذلك. سويفت هو أبعد من المنافسة.

نفس الشيء ، ولكن على نطاق أصغر ، يحدث مع الأطر. ظهور SwiftUI ليست استثناء. ربما أكون ممثل الجيل الأول من المطورين الذين بدأوا بتعلم SwiftUI ، متجاهلين UIKit. هذا له ثمن - حتى الآن لا يوجد سوى عدد قليل جدًا من المواد التدريبية وأمثلة من قانون العمل. نعم ، تحتوي الشبكة بالفعل على عدد من المقالات التي توضح ميزة معينة ، وأداة معينة. في نفس الموقع www.hackingwithswift.com ، يوجد بالفعل الكثير من أمثلة التعليمات البرمجية مع التفسيرات. ومع ذلك ، يفعلون القليل لمساعدة أولئك الذين يقررون تعلم SwiftUI من نقطة الصفر ، مثلي. معظم المواد على الشبكة هي إجابات لأسئلة محددة وصيغتها. يمكن للمطور ذي الخبرة أن يكتشف بسهولة كيف يعمل كل شيء ولماذا ولماذا يجب تطبيقه. للمبتدئين ، تحتاج أولاً إلى فهم السؤال الذي يجب طرحه ، وعندها فقط يمكنه الوصول إلى هذه المقالات.



تحت القصاصة ، سأحاول تنظيم ما تمكنت من تعلمه في الوقت الحالي. شكل المقالة هو دليل تقريبًا ، على الرغم من أنه ورقة الغش التي جمعتها بواسطتي بالشكل الذي أود أن أقرأه بنفسي في بداية رحلتي. للمطورين ذوي الخبرة الذين لم يتعمقوا بعد في SwiftUI ، هناك أيضًا بضعة أمثلة شيفرة مثيرة للاهتمام ، ويمكن قراءة التفسيرات النصية قطريًا.

آمل أن يوفر لك هذا المقال بعض الوقت عندما تريد أيضًا أن تشعر بالسحر قليلاً.

بالنسبة للمبتدئين ، قليلا عن نفسك


ليس لدي عمليا أي خلفية لتطوير المحمول ، ولدي خبرة كبيرة
في 1S لا يمكن أن تساعد كثيرا هنا. حول كيف ولماذا قررت أن أتعلم SwiftUI ، سأخبركم بعض الوقت الآخر ، إذا كان ذلك سيكون ممتعًا لشخص ما ، بالطبع.

لقد حدث أن بداية الانغماس في تطوير الأجهزة المحمولة تزامنت مع إصدار iOS 13 و SwiftUI. هذه علامة ، فكرت ، وقررت البدء بها فورًا ، متجاهلة UIKit. لقد وجدت أنه من قبيل الصدفة المسلية أنني بدأت العمل مع 1c في مثل هذه الأوقات: ثم ظهرت النماذج المدارة فقط. في حالة 1C ، استغرق تعميم التكنولوجيا الجديدة ما يقرب من خمس سنوات. في كل مرة يتم فيها توجيه أحد المطورين لتنفيذ بعض الوظائف الجديدة ، واجه خيارًا: القيام بذلك بسرعة وموثوقية ، باستخدام أدوات مألوفة ، أو قضاء الكثير من الوقت في التركيز على وظائف جديدة ، وبدون ضمانات للنتائج. عادة ما يتم الاختيار لصالح السرعة والجودة في الوقت الحالي ، وتم تأخير وقت الاستثمار في أدوات جديدة لفترة طويلة جدًا.

الآن ، على ما يبدو ، فإن الوضع مع SwiftUI هو نفسه تقريبا. الجميع مهتمون ، والجميع يدركون أن هذا هو المستقبل ، لكن حتى الآن قليلون قد قضوا وقتًا في دراسته. ما لم مشاريع الحيوانات الأليفة.

على العموم ، لم يكن من المهم بالنسبة لي أي إطار للدراسة ، وقررت أن أغتنم الفرصة على الرغم من الرأي العام بأنه من الممكن إطلاقه في الإنتاج خلال عام أو عامين. وبما أنه حدث أن كنت من بين الرواد ، فقد قررت تبادل الخبرات العملية. أريد أن أقول إنني لست خبيراً ، وبصفة عامة في تطوير الأجهزة المحمولة - غلاية. ومع ذلك ، فقد ذهبت بالفعل بطريقة معينة ، قمت خلالها بالبحث في جميع الإنترنت بحثًا عن المعلومات ، ويمكنني أن أقول بثقة إنها ليست كافية وأنها غير منظمة من الناحية العملية. ولكن في روسيا ، بالطبع ، هو غير موجود عمليا. إذا كان الأمر كذلك ، فقد قررت جمع قوتي ، ودفع مجمع المحتال بعيدا ، وتبادل مع المجتمع ما تمكنت من معرفة نفسي. سأنتقل من الافتراض بأن القارئ يعرف بالفعل على الأقل الحد الأدنى من SwiftUI ، ولن أقوم بفك تشفير أشياء مثل VStack{…} Text(…) ، إلخ.

أؤكد مرة أخرى أنني سوف أصف المزيد من انطباعاتي عن محاولات تحقيق النتيجة المرجوة من SwiftUI. لم أستطع أن أفهم شيئًا ما ، ومن بعض التجارب لاستخلاص استنتاجات خاطئة أو غير دقيقة ، لذلك فإن أي تصحيحات وتوضيحات موضع ترحيب كبير.

للمطورين ذوي الخبرة ، قد يبدو هذا المقال مليئًا بأوصاف الأشياء الواضحة ، لكن لا تحكم بشكل صارم. لم تتم كتابة دروس عن الدمى على SwiftUI.

ماذا بحق الجحيم هو هذا SwiftUI؟


لذلك ، ربما أبدأ بما هو كل شيء ، هذا هو SwiftUI الخاص بك. هنا مرة أخرى ، ماضي الأول يأتي. أصبح القياس مع النماذج المدارة أقوى عندما شاهدت بعض مقاطع الفيديو التعليمية حول كيفية تخطيط الواجهات في Storyboard (أي عند العمل مع UIKit). أخذت حنينًا وفقًا لنماذج "لا يمكن السيطرة عليها" في 1s: وضع يدوي للعناصر على النموذج ، وخاصة الارتباطات ... أوه ، عندما تحدث مؤلف الفيديو التدريبي لمدة 20 دقيقة تقريبًا عن تعقيدات ربط العناصر المختلفة ببعضها البعض وحواف الشاشة ، تذكرت بابتسامة 1C - كان كل شيء هو نفسه قبل الأشكال التي تسيطر عليها. حسنا ، تقريبا ... أسوأ قليلا ، بطبيعة الحال ، حسنا ، وبالتالي - أسهل. و SwiftUI عبارة عن نماذج مدارة من Apple تقريبًا. لا الارتباطات. لا القصص المصورة و segways. يمكنك ببساطة وصف هيكل طريقة العرض الخاصة بك في التعليمات البرمجية. وهذا كل شيء. يتم تعيين جميع المعلمات والأحجام ، وما إلى ذلك مباشرة في التعليمات البرمجية - ولكن ببساطة شديدة. بتعبير أدق ، يمكنك تحرير معلمات الكائنات الموجودة في Canvas ، ولكن لهذا ، تحتاج أولاً إلى إضافتها في التعليمات البرمجية. بصراحة ، لا أعرف كيف سيعمل ذلك في فرق التطوير الكبيرة ، حيث يكون من المعتاد فصل تخطيط التصميم ومحتوى العرض نفسه ، لكن كمطور مستقل أحب هذا النهج حقًا.

النمط التعريفي


يفترض SwiftUI أن وصف هيكل العرض الخاص بك بالكامل في التعليمات البرمجية. علاوة على ذلك ، تقدم لنا Apple أسلوبًا معبرًا لكتابة هذا الرمز. هذا هو ، شيء من هذا القبيل:
"هذا منظر. يتكون ((لسبب ما أريد أن أقول "عرض" ، وبالتالي ، تطبيق الانحراف على كلمة أنثى) يتكون من حقلين نصي وصورة واحدة. يتم ترتيب حقول النص واحدة تلو الأخرى بشكل أفقي. الصورة تحتها وحوافها مقطوعة على شكل دائرة. "
يبدو غير عادي ، أليس كذلك؟ عادة ، في الكود الذي نصفه العملية نفسها ، ما يجب القيام به لتحقيق النتيجة التي لدينا في رأسنا:
"أدخل كتلة ، وأدخل حقل نص في هذه الكتلة ، متبوعًا بحقل نص آخر ، وبعد ذلك ، التقط صورة ، واقص حوافها عن طريق تقريبها ولصقها أدناه."
يبدو وكأنه تعليمات للأثاث من ايكيا. وفي swiftUI ، نرى على الفور النتيجة التي يجب أن تكون. حتى بدون Canvas أو تصحيح الأخطاء ، يعكس بنية التعليمات البرمجية بشكل واضح بنية طريقة العرض. من الواضح ماذا وفي تسلسل سيتم عرض وبأي آثار.

مقال ممتاز حول FunctionBuilder ، وكيف يتيح لك كتابة التعليمات البرمجية بأسلوب تعريفي موجود بالفعل على Habré .

من حيث المبدأ ، لقد كتب الكثير عن الأسلوب التعريفي ومزاياه ، لذلك سأقلبه. سأضيف من نفسي أنني اعتدت عليه قليلاً وشعرت حقًا بمدى ملائمة كتابة الكود بهذا النمط عندما يتعلق الأمر بالواجهات. مع هذا ، ضرب أبل ما يسمى بولسيي!

ماذا يتكون العرض من؟


ولكن دعونا نلقي نظرة فاحصة. تقترح Apple أن النمط التعريفي مثل هذا:

 struct ContentView: View { var text1 = "some text" var text2 = "some more text" var body: some View { VStack{ Text(text1) .padding() .frame(width: 100, height: 50) Text(text2) .background(Color.gray) .border(Color.green) } } } 

يرجى ملاحظة ، View هو هيكل مع بعض المعلمات. لجعل View الهيكل ، نحتاج إلى تعيين نص المعلمة المحسوبة ، والذي يعرض some View . سنتحدث عن هذا لاحقا. محتوى النص body: some View { … } الإغلاق body: some View { … } للإغلاق هو وصف لما سيتم عرضه على الشاشة. في الواقع ، هذا هو كل ما هو مطلوب لهيكلنا لتلبية متطلبات بروتوكول العرض. أقترح التركيز في المقام الأول على body .

وهكذا ، الرفوف


في المجموع ، لقد عدت ثلاثة أنواع من العناصر التي تم بناء نص العرض منها:

  • عرض آخر
    أي كل طريقة عرض تحتوي على View أخرى أو أكثر. هذه ، بدورها ، يمكن أن تحتوي أيضًا على كل من View النظام المعرفة مسبقًا مثل Text() ، والعادات المعقدة والمخصصة التي كتبها المطور. اتضح أنه نوع من دمية التعشيش مع مستوى غير محدود من التعشيش.
  • معدلات
    بمساعدة من المعدلات ، يحدث كل السحر. شكرا لهم ، ونقول لفترة وجيزة و SwiftUI أي نوع من الرؤية نريد أن نرى. كيف يعمل ، سنظل على دراية بها ، ولكن الشيء الرئيسي هو أن المعدلات تضيف القطعة المطلوبة إلى محتوى View معين.
  • حاويات
    الحاويات الأولى التي يبدأ بـ "Hello، world" HStack هي HStack و VStack . بعد ذلك بقليل ، تظهر Group Section وغيرها. في الواقع ، تعتبر الحاويات هي نفس طريقة العرض ، لكن لها ميزة. تقوم بتمرير بعض المحتويات التي تريد عرضها. تتمثل الميزة الكاملة للحاوية في أنه يجب عليها تجميع عناصر هذا المحتوى وعرضها بطريقة أو بأخرى. وبهذا المعنى ، تشبه الحاويات المعدلات ، مع وجود الفرق الوحيد في أن المعدلات تهدف إلى تغيير طريقة عرض واحدة جاهزة ، وتقوم الحاويات بترتيب طريقة العرض هذه (عناصر المحتوى ، أو كتل بناء الجملة التعريفية) بترتيب معين ، على سبيل المثال ، عموديًا أو أفقيًا ( VStack{...} HStack{...} ). هناك أيضًا حاويات محددة ، مثل ForEach أو GeometryReader ، سنتحدث عنها لاحقًا.

    بشكل عام ، أعتبر الحاويات بمثابة أي طريقة عرض ، والتي يمكن تمرير المحتوى إليها كمعلمة.

وهذا كل شيء. يمكن أن تعزى جميع عناصر SwiftUI الأصيلة إلى أحد هذه الأنواع. نعم ، هذا لا يكفي لملء العرض الخاص بك بالوظائف ، ولكن هذا كل ما تحتاجه لإظهار وظيفتك على الشاشة.

.modifiers () - كيف يتم ترتيبها؟


لنبدأ مع أبسط. معدل هو في الواقع شيء بسيط للغاية. إنه يأخذ بعض View ، ويطبق بعض التغييرات عليها (أم يفعلها؟) ، ويعيدها مرة أخرى. أي يعد المُعدل وظيفة View نفسها ، والتي ترجع self ، بعد إجراء بعض التعديلات مسبقًا.

في ما يلي مثال على الكود الذي أعلن بتعديله. بتعبير أدق ، أفرط في زيادة حجم frame(width:height:) التعديل الحالي frame(width:height:) ، والذي يمكنك من خلاله إصلاح الأبعاد المحددة لطريقة عرض معينة. من المربع المخصص لذلك ، تحتاج إلى تحديد العرض والارتفاع ، وكنت بحاجة لتمرير كائن CGSize إليه بحجة واحدة ، وهو وصف للطول والعرض فقط. لماذا أحتاج هذا ، سأقول بعد قليل.

 struct FrameFromSize: ViewModifier{ let size: CGSize func body(content: Content) -> some View { content .frame(width: size.width, height: size.height) } } 

باستخدام هذا الرمز ، قمنا بإنشاء هيكل يتوافق مع بروتوكول ViewModifier . يتطلب هذا البروتوكول منا أن يتم تنفيذ وظيفة body() في هذا الهيكل ، والذي سيكون مدخلاته عبارة عن بعض Content ، وسيكون للإخراج some View : نفس النوع مثل المعلمة الأساسية للعرض الخاص بنا (سنتحدث عن بعض طريقة العرض أدناه) . أي نوع من Content هذا؟

محتوى + ViewBuilder = عرض


في الوثائق المضمنة عنه تقول:
"المحتوى" هو وكيل للعرض الذي سيطبق عليه المعدل الذي يمثله "النفس".
هذا هو نوع الوكيل ، وهو عبارة عن عرض مسبق يمكن من خلاله تطبيق المعدلات. وهناك نوع من المنتجات شبه المصنعة. في الواقع ، Content عبارة عن إغلاق نمط تعريفي يصف بنية View . وبالتالي ، إذا أطلقنا على هذا المعدل لبعض المنظر ، كل ما يفعله هو الحصول على إغلاق من body ونقله إلى وظيفة body ، والتي نضيف فيها خمسة سنتات إلى هذا الإغلاق.

مرة أخرى ، يعد View عبارة عن هيكل يقوم في المقام الأول بتخزين جميع المعلمات اللازمة لإنشاء صورة على الشاشة. بما في ذلك تعليمات التجميع ، والتي Content . وبالتالي ، فإن الإغلاق بنمط ViewBuilder ( Content ) الذي تتم معالجته باستخدام ViewBuilder يعيدنا إلى طريقة العرض.

دعنا نعود إلى معدل لدينا. من حيث المبدأ ، يكون إعلان بنية FrameFromSize كافيًا بالفعل لبدء تطبيقه. داخل body يمكننا أن نكتب مثل هذا:

 RoundedRectangle(cornerRadius: 4).modifier(FrameFromSize(size: size)) 

تعد أداة modifier طريقة بروتوكولات عرض تستخرج المحتوى من View تعديلها ، ViewBuilder إلى وظيفة هيكل هيكل المعدل ، وتمرر النتيجة إلى معالجة ViewBuilder ، أو إلى المعدل التالي ، إذا كان لدينا سلسلة من التعديلات.

ولكن يمكنك جعله أكثر إيجازًا من خلال الإعلان عن معدل التعديل الخاص بك كدالة ، وبالتالي توسيع قدرات بروتوكول العرض.

 extension View{ func frame(_ size: CGSize) -> some View { self.modifier(FrameFromSize(size: size)) } } 

في هذه الحالة ، .frame(width: height:) تعديل .frame(width: height:) متغير آخر من معلمات .frame(width: height:) . الآن ، يمكننا استخدام خيار استدعاء frame(size:) modifier لأي View . كما اتضح ، لا شيء معقد.

قليلا عن الاخطاء
بالمناسبة ، اعتقدت أنه ليس من الضروري توسيع البروتوكول بالكامل ، فسيكون كافياً توسيع RoundedRectangle على وجه التحديد في حالتي ، وكان ينبغي أن يكون الأمر RoundedRectangle ، كما بدا لي - ولكن يبدو أن Xcode لم يتوقع مثل هذا الوقاحة ، وسقط مع خطأ غير مفهوم " Abort trap: 6 "واقتراح لإرسال تفريغ للمطورين. بشكل عام ، في SwiftUI ، وصف الأخطاء حتى الآن غالبًا ما لا يكشف سبب هذا الخطأ تمامًا.

بنفس الطريقة ، يمكنك إنشاء أي معدّلات مخصصة واستخدامها بنفس طريقة SwiftUI المدمجة:

 RoundedRectangle(cornerRadius: 4).frame(size) 

مريح ، بإيجاز ، بشكل واضح.

أتصور سلسلة من التعديلات بينما تعلق الخرزات على خيط - عرضنا. هذا التشبيه صحيح أيضًا بمعنى أن الترتيب الذي تسمى به التعديلات مهم.



تقريبا كل شيء في SwiftUI هو عرض
بالمناسبة ، ملاحظة مثيرة للاهتمام. كمعلمة إدخال ، الخلفية لا تقبل اللون ، ولكن عرض. أي فئة اللون ليست مجرد وصف للألوان ، إنها طريقة عرض كاملة ، حيث يمكن تطبيق المعدلات والمزيد. وكخلفية ، وفقًا لذلك ، يمكنك تمرير عرض آخر.

المعدلات - فقط من أجل التعديلات
ربما تجدر الإشارة إلى نقطة أخرى. يتم ببساطة تجاهل المعدلات التي لا تغير محتوى المصدر بواسطة SwiftUI ولا يتم استدعاؤها. أي لن تتمكن من إنشاء مشغل بناءً على المعدل الذي يسبب بعض الأحداث ، لكن لا يقوم بأي إجراءات مع المحتوى. تدفعنا Apple باستمرار إلى التخلي عن بعض الإجراءات في وقت التشغيل عند تقديم الواجهة والثقة بالنمط التعريفي.

لا يزال عرض


في وقت سابق تحدثنا عن مكونات الجسم ، أو هيئة View ، أو تعليمات التجميع الخاصة به. دعنا نعود إلى View نفسه. بادئ ذي بدء ، هو هيكل يمكن فيه الإعلان عن بعض المعلمات ، body هو واحد منهم فقط. كما قلنا من قبل ، ومعرفة Content ، فإن النص هو تعليمة حول كيفية تجميع العرض المطلوب ، وهو إغلاق بأسلوب تعريفي. ولكن ماذا يجب أن نعيد إغلاقنا؟

بعض الرأي - الراحة




لقد توصلنا بسلاسة إلى سؤال لم أتمكن من اكتشافه لفترة طويلة ، على الرغم من أن هذا لم يمنعني من كتابة رمز العمل. ما هذا some View ؟ تشير الوثائق إلى أن هذا الوصف هو "نوع نتائج غير شفاف" - لكن هذا ليس منطقيًا.

الكلمة الأساسية هي إصدار "عام" لوصف نوع يتم إرجاعه بواسطة إغلاق لا يعتمد على أي شيء آخر غير الكود نفسه. أي يجب أن تكون نتيجة الوصول إلى الخاصية المحسوبة لنص العرض الخاص بنا بمثابة بنية ترضي بروتوكول العرض. قد يكون هناك الكثير منها - نص ، صورة ، أو ربما بعض الهيكل الذي أعلنته. الشريحة الكاملة للكلمة الرئيسية هي إعلان "عام" يتوافق مع بروتوكول العرض. يتم تحديده بشكل ثابت بواسطة الكود الذي يتم تنفيذه داخل النص الأساسي للعرض الخاص بك ، ويمكن لـ Xcode تحليل هذا الرمز وحساب التوقيع المحدد لقيمة الإرجاع (جيدًا ، دائمًا تقريبًا) . وبعضها مجرد محاولة لعدم تحميل المطور احتفالات لا داعي لها. يكفي للمطور أن يقول: "سيكون هناك نوع من المشاهدة" ، وأي منها - قم بفرزها بنفسك. المفتاح هنا هو أن النوع الملموس لا يتم تحديده من خلال معلمات الإدخال ، كما هو الحال مع النوع العام المعتاد ، ولكن مباشرةً بواسطة الكود. لذلك ، أعلاه ، عام نقلت.

يجب أن يكون Xcode قادرًا على تحديد نوع معين دون معرفة القيم التي تمررها إلى هذه البنية بالضبط. من المهم فهم ذلك - بعد التجميع ، يتم استبدال تعبير Some View بنوع معين من View الخاصة بك. هذا النوع محدد تمامًا ، ويمكن أن يكون معقدًا تمامًا ، على سبيل المثال ، مثل هذا: Group<TupleView<(Text, ForEach<[SomeClass], SomeClass.ID, Text>)>> .

يمكن استعادة نموذج التعليمة البرمجية من هذا النوع:

 Group{ Text(…) ForEach(…){(value: SomeClass) in Text(…) } } 

ForEach ، كما يمكن رؤيته من توقيع الكتابة ، ليست حلقة وقت تشغيل. هذا مجرد View تم بناؤها على أساس مجموعة من كائنات SomeClass . subView المرتبط بعنصر المجموعة ، تتم الإشارة إلى معرف العنصر ، ويتم إنشاء عرض subView النوع لكل عنصر لكل عنصر. ForEach دمج Text و ForEach في TupleView ، ويتم وضع كل هذا في Group . سنتحدث أكثر عن ForEach بمزيد من التفاصيل.

تخيل كم ستكون الكتابة إذا اضطررنا لوصف توقيع دقيق مثل نص المعلمة؟ لتجنب ذلك ، تم إنشاء some الكلمات الرئيسية.

ملخص
some ، هذا "عام - بالعكس". نحصل على النوع الكلاسيكي من خارج الوظيفة ، ونعرف بالفعل النوع المحدد من النوع العام ، ويحدد Xcode كيف تعمل وظيفتنا. some- ما لا يعتمد على معلمات الإدخال ، ولكن فقط على الكود نفسه. هذا ببساطة عبارة عن اختصار ، والذي لا يسمح بتحديد نوع معين ، ولكن يشير فقط إلى عائلة القيمة التي تم إرجاعها بواسطة الدالة (بروتوكول).

بعض الرأي - والعواقب


إن النهج المتبع لحساب النوع الثابت للتعبير داخل الجسم يثير ، في رأيي ، نقطتين مهمتين:

  • عند التحويل البرمجي ، يقوم Xcode بتوزيع محتويات النص من أجل حساب نوع الإرجاع المحدد. في الهيئات المعقدة ، قد يستغرق هذا بعض الوقت. في بعض الهيئات المعقدة بشكل خاص ، قد لا يكون قادرًا على التعامل مع الوقت المعقول على الإطلاق ، وسوف يقول ذلك مباشرةً.

    بشكل عام ، يجب أن تظل طريقة العرض بسيطة قدر الإمكان. توضع الهياكل المعقدة في طريقة عرض منفصلة. وبالتالي ، يتم استبدال سلاسل كاملة من الأنواع الحقيقية بنوع واحد - CustomView الخاص بك ، والذي يسمح للمترجم بعدم الوقوع في كل هذه الفوضى.
    بالمناسبة ، من المريح حقًا تصحيح قطعة صغيرة من المنظر الكبير ، هنا ، أثناء الطيران ، وتلقي ومراقبة النتيجة في Canvas.
  • لا يمكننا التحكم مباشرة في التدفق. إذا كان لا يزال بإمكان SwiftUI معالجته عن طريق إنشاء "عرض Schrödinger" من النوع <_ConditionalContent <Text ، TextField >> ، فيمكن فقط استخدام عامل تشغيل الحالة الثلاثية لتحديد قيمة معلمة محددة ، وليس نوعًا ، أو حتى لتحديد سلسلة من المعدلات.

    ولكن الأمر يستحق استعادة نفس الترتيب من المعدلات ، ومثل هذا السجل يتوقف عن أن يكون مشكلة.

ما عدا الجسم


ومع ذلك ، قد تكون هناك معلمات أخرى في الهيكل يمكنك العمل بها. كمعلمات يمكننا أن نعلن الأشياء التالية.

المعلمات الخارجية


هذه هي معلمات بنية بسيطة يجب أن نمررها من الخارج أثناء التهيئة حتى يتمكن العرض من عرضها بطريقة أو بأخرى:

 struct TextView: View { let textValue: String var body: some View { Text(textValue) } } 

في هذا المثال ، TextView لبنية TextView هي معلمة يجب ملؤها خارجياً لأنه لا يحتوي على قيمة افتراضية. نظرًا لأن الهياكل تدعم الإنشاء التلقائي للمُهيئات ، يمكننا استخدام طريقة العرض هذه ببساطة:

  TextView(textValue: "some text") 

من الخارج ، يمكنك أيضًا نقل عمليات الإغلاق التي يلزم تنفيذها عند وقوع حدث ما. على سبيل المثال ، يقوم Button(lable:action:) بذلك: ينفذ إغلاق الإجراء الذي تم تمريره عند النقر فوق الزر.

الدولة - المعلمات


تستخدم SwiftUI بشكل نشط ميزة Swift 5.1 الجديدة - خاصية التفاف الملكية .

بادئ ذي بدء ، هذه متغيرات حالة - معلمات مخزنة لهيكلنا ، يجب أن ينعكس تغييرها على الشاشة. @State — , @ObservedObject — . ObservableObject — , (View, @ObservedObject ) . @Published .

, , ObservableObjectPublisher , willSet() , , , .

, , body — ? - State-, - State- body . , body — , , stateless . View , , body . . State- View . , , , . , body — . , , , .

didSet willSet , - . , . , — - , .

State :

 struct ContentView: View { @State var tapCount = 0 var body: some View { VStack { Button(action: {self.tapCount += 1}, label: { Text("Tap count \(tapCount)") }) } } } 

Binding-


, - View @State @ObservedObject . View ? SwiftUI PropertyWrapper — @Binding . . View , , , , View . @State — , , . , @Binding . Property Wrapper, , , View . inout View . , , View, . inout , $, , . React .

 struct ContentView: View { @State var tapCount = 0 var body: some View { VStack{ SomeView(count: $tapCount) Text("you tap \(tapCount) times") } } } 

. @Binding var tapCount: Int , , Int ,

 Binding<Int> 

, , View .

 struct SomeView: View{ @Binding var tapCount: Int init(count: Binding<Int>){ self._tapCount = count //    -    } var body: some View{ Button(action: {self.tapCount += 1}, label: { Text("Tap me") }) } } 

, init , - @PropertyWrapper self._ — , self . , self._ . .

, , - PropertyWrapper , -,

 Binding<Int> 

Int .wrappedValue .

,
, Binding . View View. View , @Binding-. , View State @Binding — , State- Binding. -, , .

EnvironmentObject


, EnvironmentObjectBinding , View , .

 ContentView().environmentObject(session) 

, , - , View. , , - , EnvironmentObject , View. View, , , @EnvironmentObject

  @EnvironmentObject var session: Session 

. EnvironmentObject , . 3-, , , , . EnvironmentObject , View . , Binding .

@Environment — . — , .. . , , ( ), , .. , CoreData:

 @Environment(\.managedObjectContext) var moc: NSManagedObjectContext 

, CoreData SwiftUI . , , . .

Custom @PropertyWrapper


, PropertyWrapper — setter- getter-, , property wrapper . , getter{} setter{} , , View , . , PropertyWrapper UserDefaults .

 @propertyWrapper struct UserDefault<T> { var key: String var initialValue: T var wrappedValue: T { set { UserDefaults.standard.set(newValue, forKey: key) } get { UserDefaults.standard.object(forKey: key) as? T ?? initialValue } } } 

, UserDefaults . Apple , , , , .

, - ( ), , UserDefaults , :

 enum UserPreferences { @UserDefault(key: "isCheatModeEnabled", initialValue: false) static var isCheatModeEnabled: Bool @UserDefault(key: "highestScore", initialValue: 10000) static var highestScore: Int @UserDefault(key: "nickname", initialValue: "cloudstrife97") static var nickname: String } 

, , , .

 UserPreferences.isCheatModeEnabled = true UserPreferences.highestScore = 25000 UserPreferences.nickname = "squallleonhart” 

.


, , . , body . , , View . , . , — . , @ViewBuilder , View, View, ( ). , — . VStack , HStack , . , View, Content , , View . , View . , HStack{Text(…)} TupleView<Text, Image> .

, , View , — , , body. , , Text(«a») Text(«b») HStack . offset() position() , , HStack :
HStack(spacing:, alingment:, context:).
, , . — .

ForEach


ForEach . , . , , - forEach(…) . , ForEach View , . أي , , — .
, ForEach - , , — , , , , ( List ).

ForEach : ( data: RandomAccesCollection ), ( id: Hashable ) ( content: ()->Content ). : , ForEach Content — .. . , content , ForEach , .

ForEach , RandomAccesCollection . sorted(by:) , RandomAccesCollection .

ForEachsubView , . , SwiftUI , subView . , View . . Hashable , , id: \.self . , . , Identifiable — . , id subView . - , , Hashable — :

 ForEach(values, id: \.value){item in …} 

, valuesSomeObject , value: Int . , View , . , - . View 1 1 ( ), , @Binding View .

, , Identifiable, . على سبيل المثال ، مثل هذا:
 ForEach(keys.indices){ind in SomeView(key: self.keys[ind]) } 

, , . , . , , , , , , JSON . , .

, Content , ForEach . , , (.. , 2 — ). , Groupe{} — .

. , ViewBuilder . , .frame(size:) ? . ( , ). CGSize, . , size, .ftame(width: size.width, height: size.height) — . , — .

Custom container View


, . , “1:N” . dict: [KeyObject: [SomeObject]] .

, KeyObject ( Hashable ), — — SomeObject .

 class SomeObject: Identifiable{ let value: Int public let id: UUID = UUID() init(value: Int){ self.value = value } } class KeyObject: Hashable, Comparable{ var name: String init(name: String){ self.name = name } static func < (lhs: KeyObject, rhs: KeyObject) -> Bool { lhs.name < rhs.name } static func == (lhs: KeyObject, rhs: KeyObject) -> Bool { return lhs.name == rhs.name } func hash(into hasher: inout Hasher) { hasher.combine(name) } } 

- , , . , , generic. , , :

 struct TreeView<K: Hashable, V: Identifiable, KeyContent, ValueContent>: View where K: Comparable, KeyContent: View, ValueContent: View{ let data: [K: [V]] let keyContent: (K)->KeyContent let valueContent: (V)->ValueContent var body: some View{ VStack(alignment: .leading, spacing: 0){ ForEach(data.keys.sorted(), id: \.self){(key: K) in VStack(alignment: .trailing, spacing: 0){ self.keyContent(key) ForEach(self.data[key]!){(value: V) in self.valueContent(value) } } } } } } 

, [K: [V]] ( K — - , V — , ), : , — . , ViewBuilder- ( ), ForEach . RandomAccessCollection , dict.keys , . Comparable KeyObject .

ForEach . , ( \.self ) View . , .. Hashable . , SomeObject Identifiable . — id. id . — , — id. , . .. , id. id — . — id , ForEach .

:

 struct ContentView: View { let dict: [KeyObject: [SomeObject]] = [ KeyObject(name: "1st group") : [SomeObject(value: 1), SomeObject(value: 2), SomeObject(value: 3)], KeyObject(name: "2nd group") : [SomeObject(value: 4), SomeObject(value: 5), SomeObject(value: 6)], KeyObject(name: "3rd group") : [SomeObject(value: 7), SomeObject(value: 8), SomeObject(value: 9)] ] var body: some View { TreeView(data: dict, keyContent: {keyObject in Text("the key is: \(keyObject.name)") } ){valueObject in Text("value: \(valueObject.value)") } } } 

Canvas:



to be continued


. , , CoreData SwiftUI, , , , SwiftUI , . , , .

, — . .

Source: https://habr.com/ru/post/ar485548/


All Articles