إنشاء عناصر واجهة برمجياً باستخدام PureLayout (الجزء 2)

مرحبا يا هبر! أقدم إليكم ترجمة المقال إنشاء عناصر واجهة المستخدم برمجياً باستخدام PureLayout بواسطة Aly Yaka.

صورة

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

  • UINavigationController / Bar
  • UITableView
  • التحجيم الذاتي UITableViewCell


UINavigationController


في تطبيقنا ، ربما تحتاج إلى شريط تنقل بحيث يمكن للمستخدم الانتقال من قائمة جهات الاتصال إلى معلومات مفصلة حول جهة اتصال معينة ، ثم العودة مرة أخرى إلى القائمة. يمكن لـ UINavigationController حل هذه المشكلة بسهولة باستخدام شريط التنقل.

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

كل هذا يتم معالجته خارج الصندوق بواسطة وحدة التحكم في التنقل. ستحتاج إضافة واحدة أخرى إلى سطر إضافي واحد فقط من التعليمات البرمجية (إذا كنت لا ترغب في تخصيص شريط التنقل).
انتقل إلى AppDelegate.swift وأضف السطر التالي من التعليمات البرمجية أدناه ، دع viewController = ViewController ():

 let navigationController = UINavigationController(rootViewController: viewController) 

الآن تغيير self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController . في السطر الأول ، أنشأنا مثيل UINavigationController على UINavigationController لدينا باعتباره rootViewController ، والذي هو وحدة تحكم العرض في أسفل المكدس ، مما يعني أنه لن يكون هناك زر للخلف على شريط التنقل في طريقة العرض هذه. ثم نعطي نافذتنا وحدة تحكم في الملاحة باسم rootViewController ، لأنه rootViewController الآن على جميع المشاهدات في التطبيق.

الآن قم بتشغيل التطبيق الخاص بك. يجب أن تبدو النتيجة كما يلي:

صورة

لسوء الحظ ، حدث خطأ ما. يبدو أن شريط التنقل يتداخل مع عرضنا العلوي ، ولدينا عدة طرق لإصلاح هذا:

  • قم بزيادة حجم upperView بنا لتناسب ارتفاع شريط التنقل.
  • قم بتعيين الخاصية isTranslucent لشريط التنقل إلى false . سيؤدي ذلك إلى جعل شريط التنقل معتمًا (إذا لم تكن لاحظت أنه شفاف بعض الشيء) ، والآن ستصبح الحافة العلوية من superview أسفل شريط التنقل.

أنا شخصياً سأختار الخيار الثاني ، لكنك ستدرس الأول. أوصي أيضًا بالتحقق من مستندات Apple وقراءتها بعناية على UINavigationController و UINavigationBar :


انتقل الآن إلى طريقة viewDidLoad وأضف هذا السطر self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () ، بحيث يبدو كالتالي:

 override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.view.backgroundColor = .white self.addSubviews() self.setupConstraints() self.view.bringSubview(toFront: avatar) self.view.setNeedsUpdateConstraints() } 

يمكنك أيضًا إضافة هذا السطر self.title = "John Doe" viewDidLoad ، والذي سيضيف "ملف تعريف" إلى شريط التنقل حتى يعرف المستخدم مكان وجوده حاليًا. قم بتشغيل التطبيق وستكون النتيجة كما يلي:

صورة

إعادة بيع دينا عرض المراقب المالي


قبل المتابعة ، نحتاج إلى تقليل ملف ViewController.swift بنا ViewController.swift نتمكن من استخدام المنطق الحقيقي فقط ، وليس فقط رمز عناصر واجهة المستخدم. يمكننا القيام بذلك عن طريق إنشاء فئة فرعية من UIView ونقل جميع عناصر واجهة المستخدم الخاصة بنا هناك. السبب وراء قيامنا بذلك هو اتباع النموذج المعماري لطراز العرض أو MVC المعماري. تعرف على المزيد حول MVC Model-View-Controller (MVC) على نظام التشغيل iOS: أسلوب حديث .

الآن انقر بزر الماوس الأيمن على مجلد ContactCard في Project Navigator وحدد "ملف جديد":

صورة

انقر فوق Cocoa Touch Class ثم التالي. الآن اكتب "ProfileView" كاسم للفئة ، وبجانب "Subclass of:" تأكد من إدخال "UIView". إنها فقط تطلب من Xcode أن تجعل صفنا يرث تلقائيًا من UIView ، وسيضيف بعضًا من التعليمات البرمجية. الآن انقر فوق "التالي" ، ثم قم بإنشاء وحذف رمز التعليق علق:

 /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ 

ونحن الآن على استعداد لإعادة بيع المساكن.

قص ولصق كل المتغيرات البطيئة من وحدة التحكم في العرض في طريقة العرض الجديدة.
أسفل المتغير الأخير المعلق ، تجاوز init(frame :) عن طريق كتابة init ثم حدد أول نتيجة للإكمال التلقائي من Xcode.

صورة

سيظهر خطأ يوضح أنه يجب توفير المُهيئ "المطلوب" "init (coder :)" بواسطة فئة فرعية من "UIView":

صورة

يمكنك إصلاح ذلك عن طريق النقر على الدائرة الحمراء ثم إصلاح.

صورة

في أي مهيئ تم تجاوزه ، يجب عليك دائمًا استدعاء مُهيئ الطبقة الفائقة ، لذا أضف سطر التعليمات البرمجية هذا في الجزء العلوي من الأسلوب: super.init (frame: frame) .
قص ولصق طريقة addSubviews() أسفل self.view وحذف self.view قبل كل مكالمة addSubview .

 func addSubviews() { addSubview(avatar) addSubview(upperView) addSubview(segmentedControl) addSubview(editButton) } 

ثم اتصل بهذه الطريقة من المُهيئ:

 override init(frame: CGRect) { super.init(frame: frame) addSubviews() bringSubview(toFront: avatar) } 

بالنسبة للقيود ، تجاوز updateConstraints() وأضف مكالمة في نهاية هذه الوظيفة (حيث ستبقى دائمًا):

 override func updateConstraints() { // Insert code here super.updateConstraints() // Always at the bottom of the function } 

عند تجاوز أي طريقة ، يكون من المفيد دائمًا التحقق من الوثائق من خلال زيارة مستندات Apple أو ، ببساطة ، الضغط باستمرار على المفتاح Option (أو Alt) والنقر على اسم الوظيفة:

صورة

قص ولصق رمز القيد من وحدة التحكم في طريقة العرض الجديدة:

 override func updateConstraints() { avatar.autoAlignAxis(toSuperviewAxis: .vertical) avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0) upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0) segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0) editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0) editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) super.updateConstraints() } 

انتقل الآن مرة أخرى إلى وحدة تحكم العرض وتهيئة مثيل ProfileView عبر طريقة viewDidLoad let profileView = ProfileView(frame: .zero) ، أضفه كطريقة عرض فرعية إلى ViewController .

الآن تم تخفيض تحكم عرضنا إلى بضعة أسطر من التعليمات البرمجية!

 import UIKit import PureLayout class ViewController: UIViewController { let profileView = ProfileView(frame: .zero) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.title = "Profile" self.view.backgroundColor = .white self.view.addSubview(self.profileView) self.profileView.autoPinEdgesToSuperviewEdges() self.view.layoutIfNeeded() } } 

للتأكد من أن كل شيء يعمل كما هو مقصود ، قم بتشغيل التطبيق الخاص بك وتحقق من شكله.

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

UITableView


بعد ذلك ، سنضيف UITableView لتقديم معلومات الاتصال مثل رقم الهاتف والعنوان وما إلى ذلك.

إذا لم تكن قد قمت بذلك بالفعل ، فانتقل إلى وثائق Apple للتحقق من UITableView و UITableViewDataSource و UITableViewDelegate.


انتقل إلى ViewController.swift وأضف lazy var viewDidLoad() أعلاه viewDidLoad() :

 lazy var tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self return tableView }() 

إذا حاولت تشغيل التطبيق ، فسوف تشكو Xcode من أن هذا الفصل ليس مفوضًا أو مصدر بيانات لـ UITableViewController ، وبالتالي سنضيف هذين البروتوكولين إلى الفصل:

 class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { . . . 

مرة أخرى ، سوف يشكو Xcode من فئة لا تتوافق مع بروتوكول UITableViewDataSource ، مما يعني أن هناك طرق إلزامية في هذا البروتوكول لم يتم تعريفها في الفصل الدراسي. لمعرفة أي من هذه الطرق يجب عليك تنفيذها أثناء الضغط على Cmd + Control ، انقر فوق بروتوكول UITableViewDataSource في تعريف الفئة وسوف تنتقل إلى تعريف البروتوكول. لأي طريقة لا تسبقها كلمة " optional ، يجب تنفيذ فئة متوافقة مع هذا البروتوكول.

هنا لدينا طريقتان نحتاج إلى تنفيذهما:

  1. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int - تخبر هذه الطريقة طريقة عرض الجدول بعدد الصفوف التي نريد عرضها.
  2. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell تستعلم هذه الطريقة الخلية في كل صف. هنا نقوم بتهيئة (أو إعادة استخدام) الخلية وإدراج المعلومات التي نريد عرضها للمستخدم. على سبيل المثال ، ستعرض الخلية الأولى رقم الهاتف ، وستعرض الخلية الثانية العنوان وما إلى ذلك.

انتقل الآن إلى ViewController.swift ، وابدأ بكتابة numberOfRowsInSection ، وعندما يظهر الإكمال التلقائي ، حدد الخيار الأول.

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { <#code#> } 

حذف رمز الكلمة والعودة الآن 1.

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } 

ضمن هذه الوظيفة ، ابدأ في كتابة cellForRowAt وحدد الطريقة الأولى من الإكمال التلقائي مرة أخرى.

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { <#code#> } 

ومرة أخرى ، الآن ، إرجاع UITableViewCell .

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } 

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

انتقل إلى ProfileView.swift وأضف سمة عرض الجدول مباشرة أعلى ProfileView.swift :

var tableView: UITableView! مصممة ، لذلك ، نحن لسنا متأكدين من أنه سيكون ثابتا.

استبدل الآن init (frame :) بتنفيذ:

 init(tableView: UITableView) { super.init(frame: .zero) self.tableView = tableView addSubviews() bringSubview(toFront: avatar) } 

سوف يشكو Xcode الآن من init (frame :) المفقود init (frame :) لـ ProfileView ، لذا ارجع إلى ViewController.swift واسم let profileView = ProfileView (frame: .zero) بـ

 lazy var profileView: UIView = { return ProfileView(tableView: self.tableView) }() 

الآن لدينا ProfileView لديه رابط لعرض الجدول ، ويمكننا إضافته بمثابة عرض فرعي وتعيين القيود الصحيحة لذلك.
العودة إلى ProfileView.swift ، أضف addSubview(tableView) في نهاية addSubviews() وقم بتعيين هذه القيود على updateConstraints() على super.updateConstraints :

 tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) tableView.autoPinEdge(.top, to: .bottom, of: segmentedControl, withOffset: 8) 

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

يربط السطر الثاني الجزء العلوي من عرض الجدول بأسفل عنصر التحكم المجزأ بفاصل زمني قدره ثماني نقاط بينهما. قم بتشغيل التطبيق وستكون النتيجة كما يلي:

صورة

عظيم ، الآن كل شيء في مكانه ، ويمكننا البدء في تقديم خلايانا.

UITableViewCell


لتنفيذ UITableViewCell ، سنحتاج دائمًا إلى تصنيف هذه الفئة ، لذا انقر بزر الماوس الأيمن على مجلد ContactCard في Project Navigator ، ثم "ملف جديد ..." ، ثم "Cocoa Touch Class" و "Next".

أدخل "UITableViewCell" في الحقل "Subclass of:" ، وسيقوم Xcode تلقائيًا بتعبئة اسم الفصل "TableViewCell". أدخل "ProfileView" قبل الإكمال التلقائي حتى يكون الاسم النهائي هو "ProfileInfoTableViewCell" ، ثم انقر فوق "التالي" و "إنشاء". المضي قدما وحذف الأساليب التي تم إنشاؤها ، لأننا لن نحتاج إليها. إذا أردت ، يمكنك أولاً قراءة أوصافها لفهم سبب عدم حاجتنا إليها في الوقت الحالي.

كما قلنا سابقًا ، ستحتوي خليتنا على معلومات أساسية ، وهي اسم الحقل ووصفه ، وبالتالي نحتاج إلى تسميات لهم.

 lazy var titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Title" return label }() lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Description" label.textColor = .gray return label }() 

والآن سنعيد تعريف المُهيئ بحيث يمكن تكوين الخلية:

 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 

فيما يتعلق بالقيود ، سنفعل شيئًا مختلفًا بعض الشيء ، ومع ذلك ، فإنه مفيد للغاية:

 override func updateConstraints() { let titleInsets = UIEdgeInsetsMake(16, 16, 0, 8) titleLabel.autoPinEdgesToSuperviewEdges(with: titleInsets, excludingEdge: .bottom) let descInsets = UIEdgeInsetsMake(0, 16, 4, 8) descriptionLabel.autoPinEdgesToSuperviewEdges(with: descInsets, excludingEdge: .top) descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 16) super.updateConstraints() } 

هنا نبدأ في استخدام UIEdgeInsets لتعيين التباعد حول كل تسمية. يمكن إنشاء كائن UIEdgeInsets باستخدام UIEdgeInsetsMake(top:, left:, bottom:, right:) method. على سبيل المثال ، بالنسبة إلى titleLabel نقول إننا نريد أن يكون الحد الأعلى أربع نقاط ، واليمين واليسار ثماني نقاط. نحن لا نهتم بالجزء السفلي ، لأننا نستبعده ، لأننا نعلقه في الجزء العلوي من علامة الوصف. يستغرق دقيقة واحدة لقراءة وتصور كل القيد في رأسك.

حسنًا ، الآن يمكننا البدء في رسم الخلايا في عرض الجدول الخاص بنا. دعنا ننتقل إلى ViewController.swift وتغيير التهيئة البطيئة لعرض الجدول لدينا لتسجيل هذه الفئة من الخلايا في عرض الجدول وتعيين الارتفاع لكل خلية.

 let profileInfoCellReuseIdentifier = "profileInfoCellReuseIdentifier" lazy var tableView: UITableView = { ... tableView.register(ProfileInfoTableViewCell.self, forCellReuseIdentifier: profileInfoCellReuseIdentifier) tableView.rowHeight = 68 return tableView }() 

نضيف أيضًا ثابتًا لمعرف إعادة استخدام الخلية. يستخدم هذا المعرف لإزالة الخلايا من عرض الجدول عندما يتم عرضها. هذا هو التحسين الذي يمكن (وينبغي) استخدامه لمساعدة UITableView إعادة استخدام الخلايا التي تم تقديمها مسبقًا لعرض محتوى جديد بدلاً من إعادة رسم الخلية الجديدة من البداية.
الآن ، اسمحوا لي أن أريك كيفية إعادة استخدام الخلايا في سطر واحد من التعليمات البرمجية في طريقة cellForRowAt :

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: profileInfoCellReuseIdentifier, for: indexPath) as! ProfileInfoTableViewCell return cell } 

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

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... switch indexPath.row { case 0: cell.titleLabel.text = "Phone Number" cell.descriptionLabel.text = "+234567890" case 1: cell.titleLabel.text = "Email" cell.descriptionLabel.text = "john@doe.co" case 2: cell.titleLabel.text = "LinkedIn" cell.descriptionLabel.text = "www.linkedin.com/john-doe" default: break } return cell } 

الآن قم بتعيين numberOfRowsInSection لإرجاع "3" وإطلاق التطبيق الخاص بك.

صورة

حق مدهش؟

خلايا التحجيم الذاتي


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

أولاً وقبل كل شيء ، في ProfileInfoTableViewCell أضف هذا السطر إلى descriptionLabel المُهيئ البطيء descriptionLabel :

 label.numberOfLines = 0 

العودة إلى ViewController وإضافة هذين السطرين إلى مُهيئ عرض الجدول:

 lazy var tableView: UITableView = { ... tableView.estimatedRowHeight = 64 tableView.rowHeight = UITableViewAutomaticDimension return tableView }() 

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

فيما يتعلق بارتفاع الصف المقدر:
"توفير تقدير غير سلبي لارتفاع الصفوف يمكن أن يحسن أداء تحميل عرض الجدول." - مستندات Apple

في ViewDidLoad نحتاج إلى إعادة تحميل عرض الجدول لتصبح هذه التغييرات نافذة المفعول:

 override func viewDidLoad() { super.viewDidLoad() ... DispatchQueue.main.async { self.tableView.reloadData() } } 

تابع الآن وأضف خلية أخرى ، وزاد عدد الصفوف إلى أربعة cellForRow switch أخرى إلى cellForRow :

 case 3: cell.titleLabel.text = "Address" cell.descriptionLabel.text = "45, Walt Disney St.\n37485, Mickey Mouse State" 

قم الآن بتشغيل التطبيق ، ويجب أن يبدو مثل هذا:

صورة

استنتاج


حق مدهش؟ وكتذكير بالسبب في قيامنا بترميز واجهة المستخدم الخاصة بنا ، إليك مدونة كاملة مكتوبة بواسطة فريق الجوّال لدينا حول سبب عدم استخدام لوحات العمل في Instabug .

ماذا فعلت في جزأين من هذا الدرس:

  • main.storyboard ملف main.storyboard من مشروعك.
  • أنشأنا UIWindow برمجياً rootViewController .
  • إنشاء عناصر واجهة مستخدم متعددة في التعليمة البرمجية ، مثل التسميات وطرق عرض الصور وعناصر التحكم المقطعية وطرق العرض مع خلاياهم.
  • UINavigationBar المتداخلة في التطبيق الخاص بك.
  • إنشاء حجم ديناميكي UITableViewCell .

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


All Articles