SwiftUI: إنشاء أقسام قابلة للتوسيع / ​​قابلة للطي في عرض القائمة



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

تم التطوير على Xcode 11.2 لنظام التشغيل macOS Catalina 10.15.1

ابدأ المشروع


إطلاق Xcode ، ملف - مشروع جديد - تطبيق عرض واحد. في مربع الحوار ، حدد لغة تطوير Swift ، وسوف نقوم بتكوين واجهة المستخدم باستخدام SwiftUI.



معطيات


كبيانات توضيحية ، سوف نستخدم بعض التعبيرات المجنحة المضحكة باللغة اللاتينية مع ترجمتها إلى الروسية.

أضف ملف Swift جديدًا إلى المشروع ، واسمه Data.swift واكتب ما يلي هناك:

struct QuoteDataModel : Identifiable { var id: String { return latin } var latin : String var russian : String var expanded = false } struct SectionDataModel : Identifiable { var id: Character { return letter } var letter : Character var quotes : [QuoteDataModel] var expanded = false } 

QuoteDataModel هو نموذج لتعبير واحد ، في المستقبل سوف يصبح محتوى كل خلية على حدة. فيه نقوم بتخزين النص الأصلي للتعبير وترجمته وعلامة الخلية "الموسعة" (افتراضيًا ، يتم "تصغيرها")

SectionDataModel هو نموذج لكل قسم منفصل ، وهنا نقوم بتخزين "حرف" القسم ، ومجموعة من علامات الاقتباس التي تبدأ بهذا الحرف وأيضًا علامة على القسم "الموسع" (بشكل افتراضي "إنهار" أيضًا)

في المستقبل ، سوف نعرض كل هذا في عرض قائمة ، مما يتطلب أن تتوافق البيانات الخاصة به مع بروتوكول التعريف . للقيام بذلك ، نقوم بتعريف خاصية المعرف ، والتي يجب أن تكون فريدة لكل عنصر في القائمة.

علاوة على ذلك ، في نفس ملف Data.swift ، نقوم بتكوين بياناتنا:

 var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "  ,   ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "   ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "    !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "      ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: "   .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "   ,   ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "  .")])] 

دعونا نتعامل مع الواجهة


الآن سوف نحدد كيف سيكون عنوان القسم وكل خلية ستبدو.

اختر ملف - جديد - ملف - عرض SwiftUI من القائمة. اسم الملف HeaderView.swift واستبدال محتوياته بما يلي:

 import SwiftUI struct HeaderView : View { var section : SectionDataModel var body: some View { HStack() { Spacer() Text(String(section.letter)) .font(.largeTitle) .foregroundColor(Color.black) Spacer() } .background(Color.yellow) } } struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: latinities[0]) } } 



الآن مرة أخرى ملف - جديد - ملف - عرض SwiftUI. اسم الملف QuoteView.swift واستبدال محتوياته بما يلي:

 import SwiftUI struct QuoteView: View { var quote : QuoteDataModel var body: some View { VStack(alignment: .leading, spacing: 5) { Text(quote.latin) .font(.title) if quote.expanded { Group() { Divider() Text(quote.russian).font(.body) }.transition(.move(edge: .top)).animation(.default) } } } } struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: latinities[0].quotes[0]) } } 



الآن افتح الملف ContentView.swift وقم بتغيير بنية ContentView كما يلي:

 struct ContentView: View { var body: some View { List { ForEach(latinities) { section in Section(header: HeaderView(section: section), footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) } } } } } .listStyle(GroupedListStyle()) } } 

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



كما فهمت ، فإن التطبيق الآن "ميت" تمامًا ولا يزال بعيدًا عن هدفنا النهائي. ولكن سرعان ما سوف نصلحها!

تعديل رأس القسم قليلا


العودة إلى ملف HeaderView.swift. داخل هيكل HeaderView ، مباشرة بعد النص ، أضف هذا:

 struct Triangle : Shape { func path(in rect: CGRect) -> Path { var path = Path() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: rect.height - 1)) path.addLine(to: CGPoint(x: sqrt(3)*(rect.height)/2, y: rect.height/2)) path.closeSubpath() return path } } 

ترجع هذه البنية مثلث متساوي الأضلاع. أضف الآن المثلث الخاص بنا إلى الرأس. داخل HStack ، قبل إضافة الفاصل الأول هذا:

 Triangle() .fill(Color.black) .overlay( Triangle() .stroke(Color.red, lineWidth: 5) ) .frame(width : 50, height : 50) .padding() .rotationEffect(.degrees(section.expanded ? 90 : 0), anchor: .init(x: 0.5, y: 0.5)).animation(.default)) 



تعديل البيانات


العودة إلى البيانات الخاصة بنا. افتح Data.swift و Wrap صفيفنا اللاتينية في فئة UserData جديدة ، مثل هذا:

 class UserData : ObservableObject { @Published var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "  ,   ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "   ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "    !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "      ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: "   .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "   ,   ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "  .")])] } 

تذكر أيضًا تحديد علامات اللاتين مثل Published .

ماذا فعلنا؟
ObservableObject هو كائن خاص لبياناتنا التي يمكن "ربطها" ببعض طريقة العرض. يقوم SwiftUI "بمراقبة" جميع التغييرات التي قد تؤثر على طريقة العرض ، وبعد تغيير البيانات ، يقوم بتغيير طريقة العرض.

بعد "التفاف" اللاتينيات ، كان لدينا الكثير من الأخطاء ، وسوف نقوم بتصحيحها. افتح HeaderView.swift وقم بتصحيح بنية HeaderView_Previews كما يلي:

 struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: UserData().latinities[0]) } } 

الآن قم بإجراء تغييرات مماثلة على QuoteView.swift :

 struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: UserData().latinities[0].quotes[0]) } } 

افتح الملف ContentView.swift وأضف هذا قبل إعلان النص

 @ObservedObject var userData = UserData() 

إحياء المشهد


العودة إلى الملف ContentView.swift. داخل بنية ContenView ، مباشرة بعد تعريف userData ، أضف وظيفتين:

 func sectionIndex(section : SectionDataModel) -> Int { userData.latinities.firstIndex(where: {$0.letter == section.letter})! } func quoteIndex(section : Int, quote : QuoteDataModel) -> Int { return userData.latinities[section].quotes.firstIndex(where: {$0.latin == quote.latin})! } 

إضافة المعدلات onTapGesture إلى رأس القسم والخلية التي نقوم بإنشائها . العرض النهائي لمحتوى الجسم :

 var body: some View { List { ForEach(userData.latinities) { section in Section(header: HeaderView(section: section) .onTapGesture { self.userData.latinities[self.sectionIndex(section: section)].expanded.toggle() }, footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) .onTapGesture { let sectionIndex = self.sectionIndex(section: section) let quoteIndex = self.quoteIndex(section: sectionIndex, quote: quote) self.userData.latinities[sectionIndex].quotes[quoteIndex].expanded.toggle() } } } } } } .listStyle(GroupedListStyle()) } 

تُرجع الدالتان إينديكس و quoteUndex فهرس الأقسام والعبارات التي تم تمريرها إليهما. بعد تلقي هذه الفهارس ، نقوم بتغيير قيم الخصائص الموسعة في صفيفنا من اللاتينيات ، مما يؤدي إلى طي / كشف القسم أو التعبير.



استنتاج


المشروع النهائي يمكن تحميله من هنا .

بعض الروابط المفيدة:



آمل أن يكون المنشور مفيدًا لك!

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


All Articles