Kontroler, tenang saja! Kami mengeluarkan kode dalam UIView

Apakah Anda memiliki UIViewController yang besar? Bagi banyak orang, ya. Di satu sisi, ini berfungsi dengan data, di sisi lain - dengan antarmuka.

Tugas memisahkan logika dari antarmuka dijelaskan dalam ratusan artikel tentang arsitektur: MVP, MVVM, VIPER. Mereka memecahkan masalah aliran data, tetapi tidak menjawab pertanyaan tentang bagaimana bekerja dengan antarmuka: di satu tempat tetap ada penciptaan elemen, tata letak, konfigurasi, pemrosesan input dan animasi.

Mari kita pisahkan tampilan dari pengontrol dan lihat bagaimana loadView () membantu kita.



Antarmuka aplikasi untuk iOS adalah hierarki UIView . Tugas setiap view : membuat elemen, menyesuaikan, mengatur di tempat, menghidupkan. Ini bisa dilihat dari metode yang ada di kelas UIView: addSubview(), drawRect(), layoutSubviews().

Jika Anda melihat metode kelas UIViewController , Anda dapat melihat bahwa ia mengelola view: memuat, merespons pemuatan layar, dan tindakan pengguna, menampilkan layar baru. Seringkali kode yang harus di UIView , kami menulis dalam subkelas dari UIViewController , ini membuatnya terlalu besar. Pisahkan itu.

loadView ()


Siklus hidup dari UIViewController dimulai dengan loadView() . Implementasi yang disederhanakan terlihat seperti ini:

 // CustomViewController.swift func loadView() { self.view = UIView() } 

Kita dapat mengganti metode dan menentukan kelas kita.

super.loadView() tidak perlu dipanggil!

 // CustomViewController.swift override func loadView() { self.view = CustomView() } 

Implementasi CustomView.swift
 // CustomView.swift final class CustomView { let square: UIView = UIView() init() { super.init() square.backgroundColor = .red addSubview(square) } } 


Pengontrol akan memuat CustomView, menambahkannya ke hierarki, mengekspos .frame . Properti .view akan menjadi kelas yang kita butuhkan:

 // CustomViewController.swift print(view) // CustomView 

Tetapi sementara kompiler tidak tahu tentang kelas dan percaya bahwa ada UIView normal. Mari kita perbaiki ini dengan fungsi tipe cor:

 // CustomViewController.swift func view() -> CustomView { return self.view as! CustomView } 

Sekarang Anda dapat melihat variabel CustomView :

 // CustomViewController.swift func viewDidLoad() { super.viewDidLoad() view().square //  } 

Sederhanakan dengan tipe terkait
Ruslan Kavetsky mengusulkan penghapusan duplikasi kode menggunakan ekspansi protokol:

 protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } } 

Untuk setiap pengontrol baru, Anda hanya perlu menentukan protokol dan subkelas untuk UIView melalui typealias :

 // CustomViewController.swift final class CustomViewController: UIViewController, ViewSpecificController { typealias RootView = CustomView func viewDidLoad() { super.viewDidLoad() view().square //  } } 

Kode dalam subkelas UIView


Membuat dan mengonfigurasi kontrol


Font, warna, konstanta, dan hierarki dapat diatur secara langsung di konstruktor CustomView:

 // CustomView.swift init() { super.init() backgroundColor = .lightGray addSubview(square) } 

layoutSubviews ()


Tempat terbaik untuk tata letak manual adalah metode layoutSubviews() . Disebut setiap kali ukuran view diubah, sehingga Anda dapat mengandalkan ukuran bounds untuk perhitungan yang benar:

 // CustomView.swift override func layoutSubviews() { super.layoutSubviews() square.frame = CGRect(x: 0, y: 0: width: 200, height: 200) square.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) } 

Kontrol pribadi, properti publik


Jika ada waktu, maka saya membuat kontrol property pribadi, tetapi saya mengelolanya melalui variabel publik atau fungsi "di bidang pengetahuan". Contoh sederhana:

 // CustomView.swift private let square = UIView() var squarePositionIsValid: Bool { didSet { square.backgroundColor = squarePositionIsValid? .green : .red } } func moveSquare(to newCenter: CGPoint) { square.center = newCenter } 

Keuntungan enkapsulasi: logika internal tersembunyi di balik antarmuka. Misalnya, validitas objek dapat ditunjukkan oleh warna area, bukan kotak, tetapi pengontrol tidak akan tahu apa-apa tentang itu.

Apa yang tersisa di viewDidLoad ()?


Jika Anda menggunakan Interface Builder, maka sering viewDidLoad() kosong. Jika Anda membuat view dalam kode, maka Anda harus mengikat tindakan mereka melalui pola aksi target, menambahkan UIGestureRecognizer atau mengikat delegasi.

Dapat dikustomisasi melalui Interface Builder


Subkelas untuk view dapat dikonfigurasi melalui Interface Builder (selanjutnya disebut IB).

Anda harus memilih objek view (bukan controller) dan mengatur kelasnya. Tidak perlu menulis loadView() Anda sendiri loadView() , controller akan melakukannya sendiri. Tetapi UIView masih harus UIView tipe UIView .



IBOutlet dalam UIView


Jika Anda memilih kontrol di dalam view , Asisten Editor mengenali kelas UIView dan menawarkannya sebagai file kedua dalam mode Otomatis. Jadi, Anda dapat mentransfer IBOutlet untuk view .



Jika tidak bekerja
Buka kelas CustomView secara manual, tulis IBOutlet . Sekarang Anda dapat menarik dengan penanda dan mengarahkan elemen di IB.



Jika Anda membuat antarmuka dalam kode, maka semua objek dapat diakses setelah init() , tetapi ketika bekerja dengan IB, akses ke IBOutlet hanya muncul setelah memuat antarmuka dari UIStoryboard dalam metode awakeFromNib() :

 // CustomView.swift func awakeFromNib() { super.awakeFromNib() square.layer.cornerRadius = 8 } 

IBAction di UIViewController


Menurut selera saya, pengontrol harus meninggalkan semua tindakan pengguna. Dari standar:

  • target-aksi dari kontrol
  • delegasikan implementasi di UIViewController
  • memblokir implementasi
  • reaksi terhadap Notification

Dalam hal ini, UIViewController hanya mengontrol antarmuka. Segala sesuatu yang berkaitan dengan logika bisnis harus dikeluarkan dari controller, tetapi ini adalah pilihan: MVP, VIPER, dll.

Objektif-c


Di Objective-C, Anda dapat sepenuhnya mengganti tipe UIView . Untuk melakukan ini, deklarasikan properti dengan kelas yang diinginkan, ganti setter dan getter , tentukan kelas:

 // CustomViewController.m @interface CustomViewController @property (nonatomic) CustomView *customView; @end @implementation - (void)setView:(CustomView *)view{ [super setView:view]; } - (CustomView *)view { return (CustomView *)super.view; } @end 

Akhirnya


Dalam contoh di GitHub, Anda dapat melihat pemisahan kelas untuk tugas sederhana: warna kotak tergantung pada posisinya (di area hijau itu hijau, di luar itu merah).

Semakin kompleks layar, semakin baik efeknya: pengontrol berkurang, kode ditransfer ke tempatnya. Kode hanya porting untuk view , tetapi enkapsulasi membuatnya mudah untuk berinteraksi dan membaca kode. Kadang-kadang view dapat digunakan kembali dengan pengontrol lain. Misalnya, berbagai pengontrol untuk iPhone dan iPad bereaksi dengan cara mereka sendiri terhadap tampilan keyboard, tetapi ini tidak mengubah kode view .

Saya menggunakan kode ini dalam proyek yang berbeda dan dengan orang yang berbeda, setiap kali tim menyambut penyederhanaan dan mengambil latihan. Saya harap Anda menikmatinya juga. Semua UIViewController mudah!

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


All Articles