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

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:
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:
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":

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:
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.

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

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

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() {
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:

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:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
- metode ini memberi tahu tabel tampilan berapa banyak baris yang ingin kita tampilkan.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:
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.
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:
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.