Membuat Elemen Antarmuka Secara Programatis Menggunakan PureLayout (Bagian 2)

Halo, Habr! Saya hadir untuk Anda terjemahan artikel Membuat Elemen UI Secara Programatis Menggunakan PureLayout oleh Aly Yaka.

gambar

Selamat datang di bagian kedua artikel tentang pemrograman yang membuat antarmuka menggunakan PureLayout. Pada bagian pertama, kami menciptakan antarmuka pengguna aplikasi seluler sederhana sepenuhnya dalam kode, tanpa menggunakan Storyboards atau NIB. Dalam panduan ini, kami akan membahas beberapa elemen antarmuka pengguna yang paling umum digunakan di semua aplikasi:

  • UINavigationController / Bar
  • UITableView
  • Sesuaikan ukuran UITableViewCell


UINavigationController


Dalam aplikasi kami, Anda mungkin memerlukan bilah navigasi sehingga pengguna dapat pergi dari daftar kontak ke informasi terperinci tentang kontak tertentu, dan kemudian kembali ke daftar. UINavigationController dapat dengan mudah menyelesaikan masalah ini menggunakan bilah navigasi.

UINavigationController hanyalah tumpukan tempat Anda memindahkan banyak tampilan. Pengguna melihat tampilan paling atas (yang terakhir dipindahkan) sekarang (kecuali ketika Anda memiliki tampilan lain disajikan di atas ini, katakanlah favorit). Dan ketika Anda menekan pengontrol tampilan atas dari pengontrol navigasi, pengontrol navigasi secara otomatis membuat tombol "kembali" (kiri atas atau kanan tergantung pada preferensi bahasa perangkat saat ini), dan menekan tombol ini mengembalikan Anda ke tampilan sebelumnya.

Semua ini ditangani di luar kotak oleh pengontrol navigasi. Dan menambahkan satu lagi hanya akan mengambil satu baris kode tambahan (jika Anda tidak ingin menyesuaikan bilah navigasi).
Pergi ke AppDelegate.swift dan tambahkan baris kode berikut di bawah ini, biarkan viewController = ViewController ():

 let navigationController = UINavigationController(rootViewController: viewController) 

Sekarang ubah self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController . Pada baris pertama, kami membuat instance dari UINavigationController dan meneruskannya viewController kami sebagai rootViewController , yang merupakan pengontrol tampilan di bagian paling bawah tumpukan, yang berarti bahwa tidak akan pernah ada tombol kembali pada bilah navigasi tampilan ini. Kemudian kita beri jendela kita pengendali navigasi sebagai rootViewController , karena sekarang ia akan berisi semua tampilan dalam aplikasi.

Sekarang jalankan aplikasi Anda. Hasilnya akan terlihat seperti ini:

gambar

Sayangnya, ada yang tidak beres. Tampaknya bilah navigasi tumpang tindih dengan tampilan atas kami, dan kami memiliki beberapa cara untuk memperbaikinya:

  • Tambah ukuran tampilan atas kami agar sesuai dengan ketinggian bilah navigasi.
  • Setel properti isTranslucent dari bilah navigasi ke false . Ini akan membuat bilah navigasi buram (jika Anda tidak memperhatikannya sedikit transparan), dan sekarang tepi atas superview akan menjadi bagian bawah bilah navigasi.

Saya pribadi akan memilih opsi kedua, tetapi, Anda akan mempelajari yang pertama. Saya juga merekomendasikan untuk memeriksa dan membaca dengan seksama dokumen Apple di UINavigationController dan UINavigationBar :


Sekarang buka metode viewDidLoad dan tambahkan baris ini self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () , sehingga akan terlihat seperti ini:

 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() } 

Anda juga dapat menambahkan baris ini self.title = "John Doe" viewDidLoad , yang akan menambahkan "Profil" ke bilah navigasi sehingga pengguna tahu di mana dia berada saat ini. Jalankan aplikasi dan hasilnya akan terlihat seperti ini:

gambar

Refactoring View Controller kami


Sebelum melanjutkan, kita perlu mengurangi file ViewController.swift kita ViewController.swift kita hanya bisa menggunakan logika nyata, bukan hanya kode untuk elemen antarmuka pengguna. Kita dapat melakukan ini dengan membuat subkelas UIView dan memindahkan semua elemen antarmuka pengguna di sana. Alasan kami melakukan ini adalah untuk mengikuti pola arsitektur Model-View-Controller atau MVC. Pelajari lebih lanjut tentang MVC Model-View-Controller (MVC) di iOS: pendekatan modern .

Sekarang klik kanan pada folder ContactCard di Project Navigator dan pilih "File Baru":

gambar

Klik pada Cocoa Touch Class dan kemudian Next. Sekarang tulis "ProfileView" sebagai nama kelas, dan di sebelah "Subclass of:" pastikan untuk memasukkan "UIView". Itu hanya memberitahu Xcode untuk secara otomatis membuat kelas kita mewarisi dari UIView , dan itu akan menambahkan beberapa kode boilerplate. Sekarang klik Berikutnya, lalu Buat dan hapus kode yang dikomentari:

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

Dan sekarang kami siap untuk refactoring.

Potong dan rekatkan semua variabel malas dari pengontrol tampilan ke tampilan baru kami.
Di bawah variabel pending terakhir, ganti init(frame :) dengan mengetikkan init dan kemudian memilih hasil autocomplete pertama dari Xcode.

gambar

Akan muncul kesalahan yang menyatakan bahwa inisialisasi "diperlukan" "init (coder :)" harus disediakan oleh subkelas "UIView":

gambar

Anda dapat memperbaikinya dengan mengklik pada lingkaran merah dan kemudian Perbaiki.

gambar

Di setiap initializer yang diganti, Anda hampir selalu harus memanggil initializer superclass, jadi tambahkan baris kode ini di bagian atas metode: super.init (frame: frame) .
Potong dan tempel metode addSubviews() bawah inisialisasi dan hapus self.view sebelum setiap panggilan addSubview .

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

Kemudian panggil metode ini dari penginisialisasi:

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

Untuk pembatasan, updateConstraints() dan tambahkan panggilan di akhir fungsi ini (di mana ia akan selalu tetap):

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

Saat mengganti metode apa pun, selalu berguna untuk memeriksa dokumentasinya dengan mengunjungi dokumen Apple atau, lebih sederhana, menekan tombol Option (atau Alt) dan mengklik nama fungsi:

gambar

Potong dan rekatkan kode kendala dari pengontrol tampilan ke metode baru kami:

 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() } 

Sekarang kembali ke view controller dan inisialisasi instance let profileView = ProfileView(frame: .zero) melalui metode viewDidLoad let profileView = ProfileView(frame: .zero) , tambahkan sebagai subview ke ViewController .

Sekarang view controller kami telah direduksi menjadi beberapa baris kode!

 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() } } 

Untuk memastikan semuanya berfungsi sebagaimana mestinya, luncurkan aplikasi Anda dan periksa tampilannya.

Memiliki pengontrol ulasan yang tipis dan rapi harus selalu menjadi tujuan Anda. Ini mungkin membutuhkan banyak waktu, tetapi itu akan menyelamatkan Anda dari masalah yang tidak perlu selama pemeliharaan.

UITableView


Selanjutnya kita akan menambahkan UITableView untuk menyajikan informasi kontak seperti nomor telepon, alamat, dll.

Jika Anda belum melakukannya, buka dokumentasi Apple untuk memeriksa UITableView, UITableViewDataSource, dan UITableViewDelegate.


Pergi ke ViewController.swift dan tambahkan lazy var untuk tableView atas viewDidLoad() :

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

Jika Anda mencoba menjalankan aplikasi, Xcode akan mengeluh bahwa kelas ini bukan merupakan delegasi atau sumber data untuk UITableViewController , dan oleh karena itu kami akan menambahkan dua protokol ini ke kelas:

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

Sekali lagi, Xcode akan mengeluh tentang kelas yang tidak sesuai dengan protokol UITableViewDataSource , yang berarti ada metode wajib dalam protokol ini yang tidak didefinisikan di kelas. Untuk mengetahui metode mana yang harus Anda terapkan sambil memegang Cmd + Control, klik protokol UITableViewDataSource dalam definisi kelas dan Anda akan beralih ke definisi protokol. Untuk metode apa pun yang tidak didahului oleh kata optional , kelas yang sesuai dengan protokol ini harus diterapkan.

Di sini kita memiliki dua metode yang perlu kita terapkan:

  1. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int - metode ini memberi tahu tabel tampilan berapa banyak baris yang ingin kita tampilkan.
  2. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell - metode ini menanyakan sel di setiap baris. Di sini kami menginisialisasi (atau menggunakan kembali) sel dan memasukkan informasi yang ingin kami tampilkan kepada pengguna. Misalnya, sel pertama akan menampilkan nomor telepon, sel kedua akan menampilkan alamat, dan sebagainya.

Sekarang kembali ke ViewController.swift , mulai mengetik numberOfRowsInSection , dan ketika autocomplete muncul, pilih opsi pertama.

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

Hapus kode kata dan kembali sekarang 1.

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

Di bawah fungsi ini, mulai ketikkan cellForRowAt dan pilih metode pertama dari pelengkapan otomatis lagi.

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

Dan, sekali lagi, untuk saat ini, kembalikan UITableViewCell .

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

Sekarang, untuk menghubungkan tampilan tabel kami di dalam ProfileView , kami akan mendefinisikan penginisialisasi baru yang menggunakan tampilan tabel sebagai parameter, sehingga dapat menambahkannya sebagai subview dan mengatur batasan terkait untuk itu.

Pergi ke ProfileView.swift dan tambahkan atribut untuk tampilan tabel tepat di atas penginisialisasi:

var tableView: UITableView! ditentukan, oleh karena itu, kami tidak yakin bahwa itu akan konstan.

Sekarang ganti init (frame :) lama init (frame :) implementasi dengan:

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

Xcode sekarang akan mengeluh tentang init (frame :) hilang init (frame :) untuk ProfileView , jadi kembali ke ViewController.swift dan ganti let profileView = ProfileView (frame: .zero) dengan

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

Sekarang ProfileView kami memiliki tautan ke tampilan tabel, dan kami dapat menambahkannya sebagai subview dan menetapkan batasan yang benar untuk itu.
Kembali ke ProfileView.swift , tambahkan addSubview(tableView) di akhir addSubviews() dan atur batasan ini untuk updateConstraints() melalui super.updateConstraints :

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

Baris pertama menambahkan tiga batasan antara tampilan tabel dan superview-nya: sisi kanan, kiri, dan bawah tampilan tabel terlampir di sisi kanan, kiri, dan bawah tampilan profil.

Baris kedua melampirkan bagian atas tampilan tabel ke bagian bawah kontrol tersegmentasi dengan interval delapan poin di antara mereka. Luncurkan aplikasi dan hasilnya akan terlihat seperti ini:

gambar

Hebat, sekarang semuanya sudah ada, dan kita bisa mulai memperkenalkan sel kita.

UITableViewCell


Untuk mengimplementasikan UITableViewCell , kita hampir selalu perlu mensubkelas kelas ini, jadi klik kanan folder ContactCard di Project Navigator, lalu "File baru ...", lalu "Cocoa Touch Class" dan "Next".

Masukkan "UITableViewCell" di bidang "Subclass of:", dan Xcode akan secara otomatis mengisi nama kelas "TableViewCell". Masukkan "ProfileView" sebelum pelengkapan otomatis sehingga nama akhirnya adalah "ProfileInfoTableViewCell", lalu klik "Berikutnya" dan "Buat". Silakan dan hapus metode yang dibuat, karena kita tidak akan membutuhkannya. Jika Anda mau, Anda dapat membaca deskripsi mereka terlebih dahulu untuk memahami mengapa kami tidak membutuhkannya sekarang.

Seperti yang kami katakan sebelumnya, sel kami akan berisi informasi dasar, yang merupakan nama bidang dan deskripsinya, dan oleh karena itu kami memerlukan label untuknya.

 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 }() 

Dan sekarang kita akan mendefinisikan ulang penginisialisasi sehingga sel dapat dikonfigurasi:

 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") } 

Mengenai keterbatasan, kami akan melakukan sedikit berbeda, tetapi, bagaimanapun, sangat berguna:

 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() } 

Di sini kita mulai menggunakan UIEdgeInsets untuk mengatur jarak di sekitar setiap label. Objek UIEdgeInsets dapat dibuat menggunakan metode UIEdgeInsetsMake(top:, left:, bottom:, right:) bawah UIEdgeInsetsMake(top:, left:, bottom:, right:) . Misalnya, untuk titleLabel kami mengatakan bahwa kami ingin batas atas menjadi empat poin, dan kanan dan kiri menjadi delapan. Kami tidak peduli dengan bagian bawah, karena kami mengecualikannya, karena kami melampirkannya di bagian atas tanda deskripsi. Luangkan waktu sebentar untuk membaca dan memvisualisasikan semua kendala di kepala Anda.

Oke, sekarang kita bisa mulai menggambar sel dalam tampilan tabel kita. Mari kita beralih ke ViewController.swift dan mengubah inisialisasi malas tampilan tabel kami untuk mendaftarkan kelas sel ini dalam tampilan tabel dan mengatur ketinggian untuk setiap sel.

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

Kami juga menambahkan konstanta untuk pengenal penggunaan kembali sel. Pengidentifikasi ini digunakan untuk menghapus sel dari tampilan tabel ketika mereka ditampilkan. Ini adalah pengoptimalan yang dapat (dan seharusnya) digunakan untuk membantu UITableView menggunakan kembali sel yang sebelumnya disajikan untuk menampilkan konten baru alih-alih menggambar ulang sel baru dari awal.
Sekarang, saya tunjukkan cara menggunakan kembali sel dalam satu baris kode dalam metode cellForRowAt :

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

Di sini kami menginformasikan tampilan tabel tentang penarikan sel yang dapat digunakan kembali dari antrian menggunakan pengidentifikasi tempat kami mendaftarkan jalur ke sel yang akan muncul pengguna. Kemudian kami memaksa sel ke ProfileInfoTableViewCell untuk dapat mengakses propertinya sehingga kami dapat, misalnya, mengatur judul dan deskripsi. Ini dapat dilakukan dengan menggunakan hal berikut:

 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 } 

Sekarang atur numberOfRowsInSection untuk mengembalikan "3" dan meluncurkan aplikasi Anda.

gambar

Luar biasa bukan?

Sel Self-Sizing


Mungkin, dan kemungkinan besar, akan ada kasus ketika Anda ingin sel yang berbeda memiliki ketinggian sesuai dengan informasi di dalamnya, yang tidak diketahui sebelumnya. Untuk melakukan ini, Anda memerlukan tampilan tabel dengan dimensi yang dihitung secara otomatis, dan sebenarnya ada cara yang sangat sederhana untuk melakukan ini.

Pertama-tama, di ProfileInfoTableViewCell tambahkan baris ini ke descriptionLabel initializer malas Label:

 label.numberOfLines = 0 

Kembali ke ViewController dan tambahkan dua baris ini ke penginisialisasi tampilan tabel:

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

Di sini kami menginformasikan tampilan tabel bahwa tinggi baris harus memiliki nilai yang dihitung secara otomatis berdasarkan isinya.

Mengenai perkiraan tinggi baris:
"Memberikan estimasi non-negatif ketinggian baris dapat meningkatkan kinerja memuat tampilan tabel." - Apple Documents

Di ViewDidLoad kita perlu memuat ulang tampilan tabel agar perubahan ini berlaku:

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

Sekarang, lanjutkan dan tambahkan sel lain, menambah jumlah baris menjadi empat dan menambahkan switch lain ke cellForRow :

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

Sekarang jalankan aplikasi, dan itu akan terlihat seperti ini:

gambar

Kesimpulan


Luar biasa bukan? Dan sebagai pengingat mengapa kami benar-benar membuat kode antarmuka pengguna kami, berikut ini adalah seluruh posting blog yang ditulis oleh tim seluler kami tentang mengapa kami tidak menggunakan storyboard di Instabug .

Apa yang Anda lakukan dalam dua bagian dari pelajaran ini:

  • Menghapus file main.storyboard dari proyek Anda.
  • Kami membuat UIWindow terprogram dan menetapkannya rootViewController .
  • Dibuat berbagai elemen antarmuka pengguna dalam kode, seperti label, tampilan gambar, kontrol tersegmentasi, dan tampilan tabel dengan selnya.
  • UINavigationBar bersarang di aplikasi Anda.
  • Dibuat UITableViewCell ukuran dinamis.

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


All Articles