Dalam storyboard, bagaimana cara membuat sel khusus untuk digunakan dengan banyak pengontrol?

216

Saya mencoba menggunakan storyboard di aplikasi yang sedang saya kerjakan. Dalam aplikasi ada Daftar dan Pengguna dan masing-masing berisi koleksi yang lain (anggota daftar, daftar yang dimiliki oleh pengguna). Jadi, sesuai, saya punya ListCelldan UserCellkelas. Tujuannya adalah agar semua itu dapat digunakan kembali di seluruh aplikasi (yaitu, di salah satu pengontrol tampilan tabel saya).

Di situlah saya mengalami masalah.

Bagaimana cara membuat sel tampilan tabel kustom di storyboard yang dapat digunakan kembali di pengontrol tampilan apa pun?

Inilah beberapa hal spesifik yang telah saya coba sejauh ini.

  • Di Pengendali # 1, menambahkan sel prototipe, mengatur kelas ke UITableViewCellsubkelas saya , mengatur id reuse, menambahkan label dan kabel mereka ke outlet kelas. Di Controller # 2, tambahkan sel prototipe kosong, atur ke kelas yang sama dan gunakan kembali id ​​seperti sebelumnya. Saat dijalankan, label tidak pernah muncul ketika sel ditampilkan di Kontroler # 2. Bekerja dengan baik di Kontroler # 1.

  • Didesain setiap jenis sel dalam NIB yang berbeda dan terhubung ke kelas sel yang sesuai. Di storyboard, tambahkan sel prototipe kosong dan atur kelasnya dan gunakan kembali id ​​untuk merujuk ke kelas sel saya. Dalam viewDidLoadmetode pengontrol , daftarkan file NIB tersebut untuk id penggunaan kembali. Ketika ditampilkan, sel-sel di kedua kontroler kosong seperti prototipe.

  • Terus prototipe di kedua kontroler kosong dan mengatur kelas dan menggunakan kembali id ​​ke kelas sel saya. Membuat UI sel sepenuhnya dalam kode. Sel bekerja dengan baik di semua pengontrol.

Dalam kasus kedua saya menduga bahwa prototipe selalu mengesampingkan NIB dan jika saya membunuh sel-sel prototipe, mendaftarkan NIB saya untuk id reuse akan bekerja. Tapi kemudian saya tidak akan bisa mengatur segues dari sel ke frame lain, yang sebenarnya adalah inti dari menggunakan storyboard.

Pada akhir hari, saya ingin dua hal: menghubungkan aliran berdasarkan tampilan tabel di storyboard dan menentukan tata letak sel secara visual daripada dalam kode. Saya tidak bisa melihat bagaimana mendapatkan keduanya sejauh ini.

Cliff W
sumber

Jawaban:

205

Seperti yang saya pahami, Anda ingin:

  1. Desain sel dalam IB yang dapat digunakan dalam beberapa adegan storyboard.
  2. Konfigurasikan segmen storyboard unik dari sel itu, tergantung pada adegan tempat sel itu berada.

Sayangnya, saat ini tidak ada cara untuk melakukan ini. Untuk memahami mengapa upaya Anda sebelumnya tidak berhasil, Anda perlu memahami lebih lanjut tentang cara storyboard dan sel tampilan tabel prototipe bekerja. (Jika Anda tidak peduli mengapa upaya lain ini tidak berhasil, jangan ragu untuk pergi sekarang. Saya tidak punya solusi ajaib untuk Anda, selain menyarankan agar Anda mengajukan bug.)

Papan cerita, pada dasarnya, tidak lebih dari sekumpulan file .xib. Saat Anda memuat pengontrol tampilan tabel yang memiliki beberapa sel prototipe dari storyboard, inilah yang terjadi:

  • Setiap sel prototipe sebenarnya tertanam mini-nib. Jadi ketika pengontrol tampilan tabel memuat, itu berjalan melalui masing-masing nibs dan panggilan sel prototipe -[UITableView registerNib:forCellReuseIdentifier:].
  • Tampilan tabel meminta controller untuk sel.
  • Anda mungkin menelepon -[UITableView dequeueReusableCellWithIdentifier:]
  • Saat Anda meminta sel dengan pengenal penggunaan kembali yang diberikan, ia memeriksa apakah nibnya telah terdaftar. Jika ya, ia membuat instance sel itu. Ini terdiri dari langkah-langkah berikut:

    1. Lihatlah kelas sel, sebagaimana didefinisikan dalam ujung sel. Panggil [[CellClass alloc] initWithCoder:].
    2. The -initWithCoder:Metode melewati dan menambahkan subviews dan set properti yang didefinisikan dalam pena. ( IBOutletMungkin juga terhubung di sini, meskipun saya belum mengujinya; itu mungkin terjadi di -awakeFromNib)
  • Anda mengkonfigurasi sel Anda seperti yang Anda inginkan.

Hal penting yang perlu diperhatikan di sini adalah ada perbedaan antara kelas sel dan tampilan visual sel. Anda bisa membuat dua sel prototipe terpisah dari kelas yang sama, tetapi dengan subview mereka yang ditata dengan sangat berbeda. Bahkan, jika Anda menggunakan UITableViewCellgaya default , inilah yang sebenarnya terjadi. Gaya "Default" dan gaya "Subtitle", misalnya, keduanya diwakili oleh UITableViewCellkelas yang sama .

Ini penting : Kelas sel tidak memiliki korelasi satu-ke-satu dengan hierarki tampilan tertentu . Hirarki tampilan sepenuhnya ditentukan oleh apa yang ada di sel prototipe yang didaftarkan dengan pengontrol khusus ini.

Perhatikan, juga, bahwa pengidentifikasi ulang sel tidak terdaftar di beberapa apotik sel global. Pengidentifikasi ulang menggunakan hanya digunakan dalam konteks satu UITableViewcontoh.


Dengan informasi ini, mari kita lihat apa yang terjadi dalam upaya Anda di atas.

Di Controller # 1, menambahkan sel prototipe, mengatur kelas ke subkelas UITableViewCell saya, mengatur id reuse, menambahkan label dan kabel mereka ke outlet kelas. Di Controller # 2, tambahkan sel prototipe kosong, atur ke kelas yang sama dan gunakan kembali id ​​seperti sebelumnya. Saat dijalankan, label tidak pernah muncul ketika sel ditampilkan di Kontroler # 2. Bekerja dengan baik di Kontroler # 1.

Ini diharapkan. Sementara kedua sel memiliki kelas yang sama, hierarki tampilan yang diteruskan ke sel di Controller # 2 sepenuhnya tanpa subview. Jadi Anda mendapatkan sel kosong, yang persis seperti yang Anda masukkan ke dalam prototipe.

Didesain setiap jenis sel dalam NIB yang berbeda dan terhubung ke kelas sel yang sesuai. Di storyboard, tambahkan sel prototipe kosong dan atur kelasnya dan gunakan kembali id ​​untuk merujuk ke kelas sel saya. Dalam metode viewDidLoad dari pengontrol, mendaftarkan file NIB tersebut untuk id penggunaan kembali. Ketika ditampilkan, sel-sel di kedua kontroler kosong seperti prototipe.

Sekali lagi, ini diharapkan. Pengidentifikasi kembali menggunakan tidak dibagikan antara adegan storyboard atau biji, sehingga fakta bahwa semua sel yang berbeda memiliki pengidentifikasi menggunakan kembali yang sama tidak ada artinya. Sel yang Anda dapatkan kembali dari tampilan tabel akan memiliki tampilan yang cocok dengan sel prototipe di adegan storyboard itu.

Namun solusi ini sudah dekat. Seperti yang Anda catat, Anda bisa secara terprogram memanggil -[UITableView registerNib:forCellReuseIdentifier:], melewati UINibsel yang mengandung, dan Anda akan mendapatkan kembali sel yang sama. (Ini bukan karena prototipe itu "mengungguli" ujung pena; Anda hanya belum mendaftarkan ujung biji ke tampilan meja, jadi masih melihat ujung pena yang tertanam di storyboard.) Sayangnya, ada kelemahan dengan pendekatan ini - tidak ada cara untuk menyambungkan storyboard segues ke sel di nib mandiri.

Terus prototipe di kedua kontroler kosong dan mengatur kelas dan menggunakan kembali id ​​ke kelas sel saya. Membuat UI sel sepenuhnya dalam kode. Sel bekerja dengan baik di semua pengontrol.

Tentu saja. Semoga ini tidak mengejutkan.


Jadi, itu sebabnya itu tidak berhasil. Anda dapat mendesain sel-sel Anda di nibs mandiri dan menggunakannya dalam beberapa adegan storyboard; Anda saat ini tidak bisa menghubungkan segues storyboard ke sel-sel itu. Semoga, bagaimanapun, Anda telah belajar sesuatu dalam proses membaca ini.

BJ Homer
sumber
Ah, saya mengerti. Anda berhasil mengatasi kesalahpahaman saya — hierarki pandangan benar-benar terlepas dari kelas saya. Jelas dalam retrospeksi! Terima kasih atas jawabannya.
Cliff W
Tidak lagi mustahil, sepertinya: stackoverflow.com/questions/8574188/…
Rich Apodaca
7
@ RichApodaca Saya menyebutkan solusi itu dalam jawaban saya. Tapi itu tidak ada di storyboard; itu dalam Nib yang terpisah. Jadi, Anda tidak dapat menghubungkan segues atau melakukan hal-hal storyboard lainnya. Oleh karena itu, itu tidak sepenuhnya menjawab pertanyaan awal.
BJ Homer
Pada XCode8 solusi berikut ini tampaknya berfungsi jika Anda menginginkan solusi storyboard saja. Langkah 1) Buat sel prototipe Anda dalam tampilan tabel di ViewController # 1 dan kaitkan dengan kelas UITableViewCell kustom Langkah 2) Salin / Tempel sel itu di tampilan tabel ViewController # 2. Seiring waktu, Anda harus ingat untuk secara manual menyebarkan pembaruan ke salinan sel dengan menghapus salinan yang Anda buat di storyboard dan menempelkannya kembali dalam prototipe yang diperbarui.
jengelsma
Jawaban yang bagus, saya punya pertanyaan lanjutan:> "Sebuah storyboard, pada dasarnya, tidak lebih dari sekumpulan file .xib" jika ini masalahnya, mengapa begitu sulit untuk menanamkan xib di papan cerita?
willcwf
58

Terlepas dari jawaban hebat dari BJ Homer, saya merasa saya punya solusi. Sejauh pengujian saya berjalan, itu berfungsi.

Konsep: Buat kelas khusus untuk sel xib. Di sana Anda dapat menunggu acara sentuh dan melakukan segue secara terprogram. Sekarang yang kita butuhkan hanyalah referensi ke controller yang melakukan Segue. Solusi saya adalah mengaturnya tableView:cellForRowAtIndexPath:.

Contoh

Saya memiliki DetailedTaskCell.xibsel tabel yang ingin saya gunakan dalam beberapa tampilan tabel:

DetailedTaskCell.xib

Ada kelas khusus TaskGuessTableCelluntuk sel itu:

masukkan deskripsi gambar di sini

Ini adalah dimana keajaiban terjadi.

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

Saya memiliki beberapa segues tetapi mereka semua memiliki nama yang sama: "FinishedTask". Jika Anda perlu fleksibel di sini, saya sarankan untuk menambahkan properti lain.

ViewController terlihat seperti ini:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

Mungkin ada cara yang lebih elegan untuk mencapai hal yang sama tetapi - itu berhasil! :)

ericteubert
sumber
1
Terima kasih, saya menerapkan pendekatan ini dalam proyek saya. Anda dapat menimpa metode ini sebagai gantinya sehingga Anda tidak harus mendapatkan indexPath dan memilih sendiri baris: - (batal) setSelected: animasi (BOOL) yang dipilih: (BOOL) animasi {[super setSelected: animated animated: animated]; if (terpilih) [self.controller performSegueWithIdentifier: self.segue sender: self]; } Saya pikir super akan memilih sel ketika memanggil [super touchesEnded: touches withEvent: event] ;. Apakah Anda tahu kapan itu dipilih jika tidak ada di sana?
thejaz
9
Perhatikan bahwa dengan solusi ini, Anda memicu segue setiap kali sentuhan berakhir di dalam sel. Ini termasuk jika Anda hanya menggulir sel, tidak benar-benar mencoba untuk memilihnya. Anda mungkin lebih beruntung -setSelected:mengganti di sel, dan memicu segue hanya saat beralih dari NOke YES.
BJ Homer
Aku memang lebih beruntung setSelected:, BJ. Terima kasih. Memang, ini adalah solusi yang salah ( rasanya salah), tetapi pada saat yang sama, itu berfungsi, jadi saya menggunakannya sampai ini diperbaiki (atau sesuatu berubah di pengadilan Apple).
Ben Kreeger
16

Saya mencari ini dan saya menemukan jawaban ini oleh Richard Venable. Ini bekerja untuk saya.

iOS 5 menyertakan metode baru di UITableView: registerNib: forCellReuseIdentifier:

Untuk menggunakannya, letakkan UITableViewCell di nib. Itu harus menjadi satu-satunya objek root di nib.

Anda dapat mendaftarkan nib setelah memuat tableView Anda, kemudian ketika Anda memanggil dequeueReusableCellWithIdentifier: dengan pengenal sel, itu akan menariknya dari nib, sama seperti jika Anda telah menggunakan sel prototipe Storyboard.

Odrakir
sumber
10

BJ Homer telah memberikan penjelasan yang bagus tentang apa yang sedang terjadi.

Dari sudut pandang praktis saya akan menambahkan bahwa, mengingat Anda tidak dapat memiliki sel sebagai xibs DAN menghubungkan segues, yang terbaik untuk memilih adalah memiliki sel sebagai xib - transisi jauh lebih mudah untuk dipertahankan daripada tata letak sel dan properti di beberapa tempat. , dan segue Anda kemungkinan akan berbeda dari pengontrol yang berbeda pula. Anda dapat mendefinisikan segue langsung dari pengontrol tampilan tabel ke pengontrol berikutnya, dan melakukannya dalam kode. .

Catatan selanjutnya adalah bahwa memiliki sel Anda sebagai file xib yang terpisah mencegah Anda dapat menghubungkan tindakan apa pun dll langsung ke pengontrol tampilan tabel (toh, saya belum menyelesaikan ini - Anda tidak dapat mendefinisikan pemilik file sebagai sesuatu yang bermakna ). Saya mengatasi ini dengan mendefinisikan sebuah protokol yang pengontrol tampilan tabel sel diharapkan sesuai dengan dan menambahkan controller sebagai properti lemah, mirip dengan delegasi, di cellForRowAtIndexPath.

jrturton
sumber
10

Cepat 3

BJ Homer memberikan penjelasan yang bagus, Ini membantu saya memahami konsepnya. Untuk make a custom cell reusable in storyboard, yang dapat digunakan dalam TableViewController apa pun yang harus kita mix the Storyboard and xibdekati. Misalkan kita memiliki sel bernama CustomCellyang akan digunakan dalam TableViewControllerOnedan TableViewControllerTwo. Saya membuatnya dalam beberapa langkah.
1. File> Baru> Klik File> Pilih Cocoa Touch Class> klik Next> Beri Nama kelas Anda (misalnya CustomCell)> pilih Subkelas sebagai UITableVieCell> Centang kotak juga buat file XIB dan tekan Berikutnya.
2. Kustomisasi sel seperti yang Anda inginkan dan atur pengenal di atribut inspector untuk sel, di sini kita akan menetapkan sebagai CellIdentifier. Pengidentifikasi ini akan digunakan di ViewController Anda untuk mengidentifikasi dan menggunakan kembali Cell.
3. Sekarang kita harusregister this celldi ViewController kami viewDidLoad. Tidak perlu metode inisialisasi.
4. Sekarang kita bisa menggunakan sel khusus ini di tableView apa pun.

Di TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}
Kunal Kumar
sumber
5

Saya menemukan cara untuk memuat sel untuk VC yang sama, tidak diuji untuk segues. Ini bisa menjadi solusi untuk membuat sel dalam nib yang terpisah

Katakanlah Anda memiliki satu VC dan 2 tabel dan Anda ingin mendesain sel di storyboard dan menggunakannya di kedua tabel.

(mis: tabel dan bidang pencarian dengan UISearchController dengan tabel untuk hasil dan Anda ingin menggunakan Cell yang sama di keduanya)

Ketika controller meminta sel lakukan ini:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * identifier = @"CELL_ID";

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

Dan di sini Anda memiliki sel Anda dari storyboard

João Nunes
sumber
Saya mencoba ini dan itu tampaknya berhasil NAMUN sel tidak pernah digunakan kembali. Sistem selalu membuat dan menghapus sel baru setiap kali.
GilroyKilroy
Ini adalah rekomendasi yang sama yang dapat ditemukan di Menambahkan Bilah Pencarian ke Table View With Storyboards . Jika Anda tertarik, ada penjelasan yang lebih mendalam tentang solusi ini di sana (cari tableView:cellForRowAtIndexPath:).
Senseful
tapi ini kurang teks dan menjawab pertanyaan
João Nunes