Cara yang benar untuk memuat Nib untuk subclass UIView

98

Saya sadar pertanyaan ini telah ditanyakan sebelumnya tetapi jawabannya bertentangan dan saya bingung, jadi tolong jangan nyalakan saya.

Saya ingin memiliki UIViewsubclass yang dapat digunakan kembali di seluruh aplikasi saya. Saya ingin menjelaskan antarmuka menggunakan file pena.

Sekarang katakanlah itu adalah tampilan indikator pemuatan dengan indikator aktivitas di dalamnya. Saya ingin pada beberapa acara untuk membuat contoh tampilan ini dan menganimasikan ke tampilan pengontrol tampilan. Saya bisa menggambarkan antarmuka tampilan tanpa masalah secara terprogram, membuat elemen secara terprogram dan mengatur bingkai mereka di dalam metode init, dll.

Bagaimana saya bisa melakukan ini menggunakan pena? Mempertahankan ukuran yang diberikan dalam pembuat antarmuka tanpa harus menyetel bingkai.

Saya telah berhasil melakukannya seperti ini, tetapi saya yakin itu salah (ini hanya tampilan dengan picker di dalamnya):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Tapi saya menimpa diri sendiri, dan ketika saya memberi contoh:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Saya harus mengatur frame secara manual daripada mengambilnya dari IB.

EDIT: Saya ingin membuat tampilan kustom ini dalam pengontrol tampilan, dan memiliki akses untuk mengontrol elemen tampilan. Saya tidak ingin pengontrol tampilan baru.

Terima kasih

EDIT: Saya tidak tahu apakah ini praktik terbaik, saya yakin tidak, tapi beginilah cara saya melakukannya:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

Praktek yang benar masih dibutuhkan

Haruskah ini dilakukan dengan menggunakan pengontrol tampilan dan init dengan nama pena? Haruskah saya menyetel ujung pena dalam beberapa metode inisialisasi UIView dalam kode? Atau apa yang telah saya lakukan baik-baik saja?

Adam Waite
sumber
Tapi bukankah baris pertama kode hampir sama dengan yang Anda gunakan di pertanyaan awal initWithDataSource? Bagaimanapun ini akan berhasil bahkan jika Anda memberi pemilik sebagai 'Nihil'.
Rakesh
Perlu dicatat bahwa saat ini dalam 99,99999% kasus, seseorang hanya akan menggunakan tampilan penampung , yang sekarang digunakan di mana saja dan selalu di iOS. artikel
petunjuk
perhatikan bahwa Anda dapat mengganti [NSString stringWithFormat: @ "% @", [FSASelectView class]] dengan NSStringFromClass ([FSASelectView class])
naomimichiko

Jawaban:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

Saya menggunakan ini untuk menginisialisasi tampilan kustom yang dapat digunakan kembali yang saya miliki.


Perhatikan bahwa Anda dapat menggunakan "firstObject" di bagian akhir, ini sedikit lebih bersih. "firstObject" adalah metode praktis untuk NSArray dan NSMutableArray.

Berikut adalah contoh umum, memuat xib untuk digunakan sebagai header tabel. Di file Anda YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Biasanya, di TopArea.xib, Anda akan mengklik Pemilik File dan mengatur pemilik file ke KelasAnda . Kemudian sebenarnya di YourClass.h Anda akan memiliki properti IBOutlet. Di TopArea.xib, Anda dapat menyeret kontrol ke outlet tersebut.

Jangan lupa TopArea.xib, Anda mungkin harus mengklik Tampilan itu sendiri dan menyeretnya ke beberapa outlet, jadi Anda memiliki kendali atasnya, jika perlu. (Tip yang sangat berharga adalah ketika Anda melakukan ini untuk baris sel tabel, Anda benar-benar harus melakukannya - Anda harus menghubungkan tampilan itu sendiri ke properti yang relevan dalam kode Anda.)

Premsuraj
sumber
tidak ada initWithNibName: metode untuk UIView, ini hanya untuk UIViewController
meronix
Itu untuk pengontrol tampilan, saya ingin menggunakan UIView dan memiliki pengontrol yang membuatnya untuk memilikinya.
Adam Waite
Maaf, saya menyalin kode yang salah karena tergesa-gesa, saya telah memperbarui jawabannya, silakan lihat.
Premsuraj
Saya akan menandai ini dengan benar. Saya tidak tahu apakah itu praktik terbaik tetapi berhasil.
Adam Waite
@Joe Pukulan Maaf atas benjolannya, tetapi mengapa orang harus melakukan ini: "Jangan lupa bahwa di TopArea.xib, Anda mungkin harus mengklik Tampilan itu sendiri dan menyeretnya ke beberapa outlet, jadi Anda memiliki kendali atasnya , jika diperlukan." Bisakah Anda menggunakan myViewObject untuk mengakses tampilan yang mendasarinya karena ini adalah UIView itu sendiri? Mengapa ada kebutuhan akan properti?
pengguna1686342
39

Jika Anda ingin menjaga Anda CustomViewdan yang xibindependen File's Owner, kemudian ikuti langkah-langkah ini

  • Biarkan File's Ownerbidang kosong.
  • Klik pada tampilan aktual di xibfile Anda CustomViewdan tetapkan Custom Classsebagai CustomView(nama kelas tampilan kustom Anda)
  • Menambahkan IBOutletdalam .hfile tampilan kustom Anda.
  • Di .xibfile tampilan kustom Anda, klik pada tampilan dan masuk Connection Inspector. Di sini Anda akan melihat semua IBOutlet Anda yang Anda tentukan dalam .hfile
  • Hubungkan mereka dengan tampilan masing-masing.

dalam .mfile CustomViewkelas Anda , ganti initmetode sebagai berikut

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Sekarang ketika Anda ingin memuat Anda CustomView, gunakan baris kode berikut [[CustomView alloc] init];

Ans
sumber
1
Pada iOS 9, ini adalah satu-satunya solusi yang tercantum di halaman ini yang benar-benar berfungsi untuk saya (solusi lain menyebabkan crash).
hidup
Perhatikan bahwa pendekatan ini tidak kompatibel dengan memuat tampilan kustom Anda dari XIB yang ada karena tidak memanggil -init. Sebaliknya itu akan memanggil initWithFrame:dan -awakeFromNib. Jika Anda menulis kode yang sama untuk memuat tampilan Anda seperti dalam -initmetode, Anda akan memasuki loop tak terbatas.
Debu
25

Ikuti langkah-langkah berikut ini

  1. Buat kelas dengan tipe MyView .h / .m UIView.
  2. Buat xib dengan nama yang sama MyView.xib.
  3. Sekarang ubah kelas Pemilik File menjadi UIViewControllerdari NSObjectdalam xib. Lihat gambar di bawah masukkan deskripsi gambar di sini
  4. Hubungkan Tampilan Pemilik File ke Tampilan Anda. Lihat gambar di bawah masukkan deskripsi gambar di sini

  5. Ubah kelas View Anda menjadi MyView. Sama seperti 3.

  6. Kontrol tempat membuat IBOutlets.

Berikut kode untuk memuat View:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Semoga membantu.

Klarifikasi :

UIViewControllerdigunakan untuk memuat xib Anda dan Tampilan yang UIViewControllersebenarnya MyViewtelah Anda tetapkan di xib MyView ..

Demo Saya telah membuat demo ambil di sini

iphonic
sumber
Mengapa orang-orang memilih ini, salah? Saya belum mencobanya, tapi sepertinya ini tidak akan melakukan apa yang saya inginkan.
Adam Waite
Saya tidak memilih, tetapi itu karena saya salah membaca jawaban Anda (mengira Anda menetapkan subkelas UIView sebagai pemilik file). Maaf soal itu.
Rakesh
2
Di file xib Anda, abaikan pemilik file. Kemudian Anda bisa menghindari keharusan membuat UIViewController hanya dengan melakukan MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Ya tentu saja, tetapi ini hanyalah cara lain untuk melakukannya.
iphonic
1
Ini untuk membuat subkelas UIView yang dapat digunakan kembali, bukan menggunakan UIViewController untuk tujuan itu.
arielyz
11

Menjawab pertanyaan saya sendiri sekitar 2 tahun kemudian di sini tapi ...

Ini menggunakan ekstensi protokol sehingga Anda dapat melakukannya tanpa kode tambahan untuk semua kelas.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Adam Waite
sumber
8

Di Swift:

Misalnya, nama kelas khusus Anda adalah InfoView

Pertama, Anda membuat file InfoView.xibdan InfoView.swiftmenyukai ini:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Kemudian mengatur File's Owneruntuk UIViewControllerseperti ini:

masukkan deskripsi gambar di sini

Ubah nama Anda Viewmenjadi InfoView:

masukkan deskripsi gambar di sini

Klik kanan ke File's Ownerdan hubungkan viewbidang Anda dengan InfoView:

masukkan deskripsi gambar di sini

Pastikan bahwa nama kelas adalah InfoView:

masukkan deskripsi gambar di sini

Dan setelah ini Anda dapat menambahkan tombol tindakan ke di kelas khusus Anda tanpa masalah:

masukkan deskripsi gambar di sini

Dan penggunaan kelas khusus ini di MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit.dll
sumber
2

Anda juga bisa menginisialisasi xib menggunakan pengontrol tampilan dan menggunakan viewController.view. atau lakukan seperti yang Anda lakukan. Hanya membuat UIViewsubclass sebagai pengontrolnya UIViewadalah ide yang buruk.

Jika Anda tidak memiliki outlet dari tampilan kustom Anda, maka Anda dapat langsung menggunakan UIViewControllerkelas untuk menginisialisasi.

Pembaruan: Dalam kasus Anda:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

Jika tidak, Anda harus membuat kebiasaan UIViewController(untuk menjadikannya sebagai pemilik file sehingga outlet terhubung dengan benar).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Dimanapun Anda ingin menambahkan tampilan yang dapat Anda gunakan.

[parentView addSubview:yCustCon.view];

Namun melewatkan pengontrol tampilan lain (sudah digunakan untuk tampilan lain) sebagai pemilik saat memuat xib bukanlah ide yang baik karena properti tampilan pengontrol akan diubah dan ketika Anda ingin mengakses tampilan asli, Anda tidak akan punya referensi untuk itu.

EDIT: Anda akan menghadapi masalah ini jika Anda telah menyiapkan xib baru Anda dengan pemilik file sebagai UIViewControllerkelas utama yang sama dan mengikat properti tampilan ke tampilan xib baru.

yaitu;

  • YourMainViewController - manages - mainView
  • CustomView - perlu memuat dari xib jika diperlukan.

Kode di bawah ini akan menyebabkan kebingungan di kemudian hari, jika Anda menulisnya di dalam tampilan tidak dimuat YourMainViewController. Itu karena self.viewmulai saat ini akan merujuk ke customview Anda

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
sumber
Ok, jadi pada dasarnya, praktik terbaik menyatakan bahwa setiap UIView harus memiliki pengontrol terkait?
Adam Waite
Belum tentu. Tetapi saat memuat tampilan dari xib terpisah, itu akan menjadi cara bebas masalah. Dokumen apel pra-IOS5 mengatakan satu pengontrol tampilan tidak boleh digunakan untuk mengontrol lebih dari satu adegan (xib) dan sebaliknya.
Rakesh
Xib sebenarnya hanya seukuran tampilan peringatan
Adam Waite
Saya pribadi merasa, itu tergantung kompleksitas kode / xib apakah Anda harus membuat pengontrol baru atau tidak. Jika Anda dapat menangani masalah yang disebabkan dengan sukses dan jika Anda tidak melihat modifikasi di masa mendatang, itu bukan masalah. Tetapi membuat pengontrol tampilan terpisah akan menyelesaikan banyak masalah untuk Anda. Dan karena dalam kasus Anda ini hanya indikator aktivitas, Anda dapat menggunakan objek UIViewController (seperti yang disebutkan dalam jawaban) dengan mudah. Saya akan memperbarui jawaban saya untuk melakukan ini sesuai.
Rakesh
1
Jadi saya bisa menggambar antarmuka di pembuat antarmuka daripada melakukannya secara terprogram. Tidak masalah melakukannya secara terprogram, saya hanya ingin tahu cara membuat subkelas UIView dan mencobanya! Ini seharusnya tidak sulit.
Adam Waite