Cara memuat UIView menggunakan file nib yang dibuat dengan Interface Builder

241

Saya mencoba melakukan sesuatu yang sedikit rumit, tetapi sesuatu yang seharusnya mungkin. Jadi di sini adalah tantangan untuk semua ahli di luar sana (forum ini adalah paket dari kalian semua :)).

Saya sedang membuat "komponen" Kuisioner, yang ingin saya muat di NavigationContoller(saya QuestionManagerViewController). "Komponen" adalah "kosong" UIViewController, yang dapat memuat pandangan yang berbeda tergantung pada pertanyaan yang perlu dijawab.

Cara saya melakukannya adalah:

  1. Buat objek Question1View sebagai UIViewsubclass, tentukan beberapa IBOutlets.
  2. Buat (menggunakan Interface Builder) the Question1View.xib (DI SINI ADALAH MASALAH SAYA MASALAH ADALAH ). Saya mengatur kedua UIViewControllerdan UIViewmenjadi dari Question1View kelas.
  3. Saya menautkan outlet dengan komponen view (menggunakan IB).
  4. Saya menimpa initWithNibsaya QuestionManagerViewControlleruntuk terlihat seperti ini:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

Ketika saya menjalankan kode, saya mendapatkan kesalahan ini:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Mengakhiri aplikasi karena pengecualian yang tidak tertangkap ' NSInternalInconsistencyException', alasan: ' -[UIViewController _loadViewFromNibNamed:bundle:]memuat nib "Question1View" tetapi tampilan outlet tidak disetel.'

Saya yakin ada cara untuk memuat tampilan menggunakan file nib, tanpa perlu membuat kelas viewController.

Alexander Farber
sumber

Jawaban:

224

Ada juga cara yang lebih mudah untuk mengakses tampilan daripada berurusan dengan nib sebagai array.

1 ) Buat subkelas Tampilan khusus dengan outlet apa pun yang ingin Anda akses nanti. --Pandangan ku

2 ) di UIViewController yang Anda ingin memuat dan menangani nib, buat properti IBOutlet yang akan menampung tampilan nib yang dimuat, misalnya

di MyViewController (subkelas UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(jangan lupa untuk mensintesis dan melepaskannya di file .m Anda)

3) buka nib Anda (kami akan menyebutnya 'myViewNib.xib') di IB, membuat Anda Pemilik file ke MyViewController

4) sekarang hubungkan myViewFromNib Outlet pemilik file Anda ke tampilan utama di nib.

5) Sekarang di MyViewController, tulis baris berikut:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Sekarang segera setelah Anda melakukannya, memanggil properti Anda "self.myViewFromNib" akan memberi Anda akses ke tampilan dari nib Anda!

averydev
sumber
4
Apakah ini akan berfungsi untuk tampilan pengontrol utama (self.view)? Untuk beberapa alasan saya perlu memuat tampilan utama dari nib setelah metode init pengontrol standar. Alasan - struktur subklasifikasi kompleks
Lukasz
@Lukasz Seberapa banyak Anda berhasil melakukannya? Bahkan saya mencari sesuatu seperti Anda.
Ajay Sharma
Maaf saya mungkin tebal, tapi saya bingung pada poin pertama. Bagaimana cara membuat outlet di subkelas tampilan kustom? Saya pikir outlet hanya untuk UIViewControllers?
jowie
1
Bagaimana dengan kasus ketika tampilan ini muncul pada beberapa tampilan induk? Jadi, apakah Anda mengusulkan untuk mengedit xib untuk masing-masing secara manual? Ini terlalu buruk!!!
user2083364
2
Ini hanya berfungsi jika Anda ingin menggunakan nib khusus dalam pengontrol tampilan tunggal. Dalam kasus di mana Anda ingin menggunakannya pada pengontrol tampilan yang berbeda, masing-masing membuat instance dari custom nib secara dinamis, ini tidak akan berfungsi.
thgc
120

Terima kasih semua. Saya memang menemukan cara untuk melakukan apa yang saya inginkan.

  1. Buat Anda UIViewdengan yang IBOutletAnda butuhkan.
  2. Buat xib di IB, desain itu sesuai dengan keinginan Anda dan tautkan seperti ini: Pemilik File adalah kelas UIViewController(Tidak ada subkelas kustom, tetapi yang "asli"). Tampilan Pemilik File terhubung ke tampilan utama dan kelasnya dinyatakan sebagai yang dari langkah 1).
  3. Hubungkan kontrol Anda dengan IBOutlets.
  4. The DynamicViewControllerdapat menjalankan logika untuk memutuskan apa melihat / xib ke beban. Setelah membuat keputusan, dalam loadViewmetode ini masukkan sesuatu seperti ini:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

Itu dia!

Metode bundel utama loadNibNamedakan mengatur inisialisasi tampilan dan membuat koneksi.

Sekarang ViewController dapat menampilkan tampilan atau yang lain tergantung pada data dalam memori, dan layar "induk" tidak perlu repot dengan logika ini.

geedubb
sumber
2
Saya memiliki masalah yang sangat mirip - Saya hanya ingin memuat UIView dari file xib. Langkah-langkah ini tidak berhasil untuk saya - aplikasi mogok dengan "akses buruk" segera setelah menambahkan tampilan yang dimuat sebagai subview.
Justicle
2
Untuk iPhone 3.0, gunakan objectAtIndex: 0 untuk mendapatkan elemen pertama. Ini crash untuk saya persis seperti yang dijelaskan oleh Justicle. Ada yang tahu kenapa?
tba
1
+1 itu berfungsi dengan baik untuk saya. Saya ingin menambahkan banyak tampilan yang memiliki pengontrol satu tampilan (saya menggunakan skenario tampilan terbalik di mana bagian tampilan berputar) Saya harus membuat indeks pada array 0 (iPhone 3.0)
Aran Mulholland
42
Ini adalah jawaban yang benar, namun, kode tersebut harus dimodifikasi sehingga melewati loop nibViews dan melakukan pemeriksaan kelas pada setiap objek, sehingga Anda tentu mendapatkan objek yang benar. objectAtIndex: 1 adalah asumsi berbahaya.
M. Ryan
2
Jasconius benar. Anda harus memutari array nibViews seperti ini: gist.github.com/769539
leviathan
71

Saya tidak yakin apa yang dibicarakan beberapa jawaban, tetapi saya harus meletakkan jawaban ini di sini ketika saya mencari di Google lain kali. Kata kunci: "Cara memuat UIView dari nib" atau "Cara memuat UIView dari NSBundle."

Berikut kode hampir 100% langsung dari buku Apress Beginning iPhone 3 (halaman 247, "Menggunakan Sel Table View Baru"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Ini mengandaikan Anda memiliki UIViewsubclass yang dipanggil Blah, sebuah nib yang disebut Blahyang mengandung UIViewyang kelasnya diatur ke Blah.


Kategori: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Ekstensi cepat

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

Dan contoh yang digunakan:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Dan Rosenstark
sumber
2
Jika Anda menggunakan isKindOfAnda dilindungi dari perubahan struktur Bundle.
Dan Rosenstark
1
Sebuah assertakan mungkin solusi yang lebih baik. Dengan cara ini Anda hanya akan menyembunyikan masalahnya.
Sulthan
1
@Sulthan sebuah pernyataan secara inheren berbeda. Saya ingin objek bertipe Blah di mana pun ia berada di Nib . Saya tidak peduli apakah itu dipromosikan atau diturunkan peringkatnya. Namun, sebagai tanggapan atas komentar Anda, saya telah menambahkan sebuah penegasan, tetapi tidak ada pada tempatnya for. Itu melengkapi itu.
Dan Rosenstark
Anda dapat melakukan loadFromNib tanpa parameter: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([kelas diri]) pemilik: self options: nil]; for (objek id dalam bundel) {if ([objek isKindOfClass: [self class]]) {return object; }} return nil; }
JVC
22

Untuk semua yang perlu mengelola lebih dari satu contoh tampilan khusus, yaitu Koleksi Outlet , saya menggabungkan dan mengkustomisasi jawaban @Gonso, @AVeryDev, dan @Olie dengan cara ini:

  1. Buat custom MyView : UIViewdan atur sebagai " Custom Class " dari root UIViewdi XIB yang diinginkan ; kelas khusus

  2. Buat semua outlet yang Anda butuhkan MyView(lakukan sekarang karena setelah poin 3, IB akan menyarankan Anda untuk menghubungkan outlet ke UIViewControllerdan bukan ke tampilan kustom seperti yang kita inginkan); outlet kelas khusus

  3. Tetapkan Anda UIViewControllersebagai " Pemilik File " dari tampilan kustom XIB ; masukkan deskripsi gambar di sini

  4. Di UIViewControllertambah yang baru UIViewsuntuk setiap instance yang MyViewAnda inginkan, dan hubungkan mereka untuk UIViewControllermembuat Koleksi Outlet : tampilan ini akan bertindak sebagai tampilan "pembungkus" untuk mesin virtual tampilan kustom; masukkan deskripsi gambar di sini

  5. Akhirnya, di viewDidLoadAnda UIViewControllertambahkan baris berikut:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
Francesco Vadicamo
sumber
Hai ... kamu tahu bagaimana melakukan ini semua secara terprogram yaitu. tidak dengan menggunakan file xib sama sekali?
The X-Coder
19

Saya akan menggunakan UINib untuk membuat UIView khusus untuk digunakan kembali

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

File yang dibutuhkan dalam kasus ini adalah MyCustomView.xib , MyCustomViewClass.h dan MyCustomViewClass.m Catatan yang [UINib instantiateWithOwner]mengembalikan array, jadi Anda harus menggunakan elemen yang mencerminkan UIView yang ingin Anda gunakan kembali. Dalam hal ini elemen pertama.

terima kasih
sumber
1
Bukankah maksud Anda "customNib" di baris 2 alih-alih "rankingNib"
Danny
11

Anda seharusnya tidak mengatur kelas pengontrol tampilan Anda menjadi subkelas dari UIViewdalam Interface Builder. Itu pasti paling tidak bagian dari masalah Anda. Biarkan itu sebagai baik UIViewController, beberapa subkelasnya, atau beberapa kelas khusus yang Anda miliki.

Adapun memuat hanya tampilan dari xib, saya berada di bawah asumsi bahwa Anda harus memiliki semacam pengontrol tampilan (bahkan jika itu tidak meluas UIViewController, yang mungkin terlalu berat untuk kebutuhan Anda) ditetapkan sebagai Pemilik File di Antarmuka Builder jika Anda ingin menggunakannya untuk mendefinisikan antarmuka Anda. Saya melakukan sedikit riset untuk mengkonfirmasi ini juga. Ini karena jika tidak, tidak akan ada cara untuk mengakses elemen antarmuka di UIViewdalamnya, juga tidak akan ada cara untuk memiliki metode Anda sendiri dalam kode yang dipicu oleh peristiwa.

Jika Anda menggunakan UIViewControllersebagai Pemilik File untuk tampilan Anda, Anda bisa menggunakannya initWithNibName:bundle:untuk memuatnya dan mendapatkan objek view controller kembali. Di IB, pastikan Anda mengatur viewoutlet ke tampilan dengan antarmuka Anda di xib. Jika Anda menggunakan beberapa jenis lainnya objek sebagai file Anda pemilik, Anda harus menggunakan NSBundle's loadNibNamed:owner:options:metode untuk memuat pena, lewat sebuah contoh dari berkas ini pemilik ke metode. Semua propertinya akan diatur dengan benar sesuai dengan outlet yang Anda tentukan di IB.

Marc W
sumber
Saya membaca buku Apress, "Memulai Pengembangan iPhone: Menjelajahi SDK iPhone" dan mereka membahas metode ini di bab 9: Pengontrol Navigasi dan Tampilan Tabel. Tampaknya tidak terlalu rumit.
Lyndsey Ferguson
Saya ingin tahu bagaimana cara mereka mengatakan itu dilakukan. Saya tidak memiliki salinan buku itu saat ini.
Marc W
10

Anda juga dapat menggunakan initWithNibName UIViewController alih-alih loadNibNamed. Lebih sederhana, saya menemukan.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Sekarang Anda hanya perlu membuat MySubView.xib dan MySubView.h / m. Di MySubView.xib setel kelas Pemilik File ke UIViewController dan lihat kelas ke MySubView.

Anda dapat menentukan posisi dan ukuran subviewfile induk xib yang digunakan.

Ying
sumber
8

Ini adalah pertanyaan yang bagus (+1) dan jawabannya hampir membantu;) Maaf teman-teman, tapi saya punya banyak waktu menghabiskan ini, meskipun Gonso & AVeryDev memberikan petunjuk yang baik. Semoga jawaban ini akan membantu orang lain.

MyVC adalah view controller yang memegang semua hal ini.

MySubview adalah pandangan yang ingin kita muat dari xib

  • Di MyVC.xib, buat tampilan tipe MySubViewyang ukuran & bentuk yang tepat & diposisikan di tempat yang Anda inginkan.
  • Di MyVC.h, miliki

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • Di MyVC.m, @synthesize mySubView;dan jangan lupa untuk melepaskannya di dealloc.

  • Di MySubview.h, miliki outlet / properti untuk UIView *view(mungkin tidak perlu, tetapi bekerja untuk saya.) Mensintesis & lepaskan di .m
  • Di MySubview.xib
    • atur jenis pemilik file ke MySubview, dan tautkan properti tampilan ke tampilan Anda.
    • Letakkan semua bit & sambungkan ke yang IBOutletdiinginkan
  • Kembali di MyVC.m, miliki

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

Agak sulit bagi saya adalah: petunjuk di jawaban lain memuat pandangan saya dari xib, tetapi TIDAK mengganti tampilan di MyVC (ya!) - Saya harus menukar itu sendiri.

Juga, untuk mendapatkan akses ke mySubviewmetode, viewproperti dalam file .xib harus disetel ke MySubview. Kalau tidak, ia kembali seperti biasa UIView.

Jika ada cara untuk memuat mySubviewlangsung dari xib sendiri, itu akan rock, tapi ini membuat saya di mana saya harus berada.

Olie
sumber
1
Saya menggunakan metode ini, terima kasih. Satu perubahan yang saya buat untuk memungkinkannya mendukung tampilan bersarang adalah dengan mengubah [ self.view insertSubview: msView aboveSubview: mySubview]; untuk [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Jason
8

Ini adalah sesuatu yang seharusnya lebih mudah. Saya akhirnya memperluas UIViewControllerdan menambahkan loadNib:inPlaceholder:pemilih. Sekarang bisa saya katakan

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Berikut kode untuk kategorinya (ia menggunakan rigamarole yang sama seperti yang dijelaskan oleh Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end
skot
sumber
7

Dengan cepat

Sebenarnya resolusi saya untuk masalah ini adalah, untuk memuat tampilan dalam viewDidLoad di CustonViewController saya di mana saya ingin menggunakan tampilan seperti itu:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Jangan memuat tampilan dalam suatu loadView()metode! Metode loadView berfungsi untuk memuat tampilan untuk ViewController kustom Anda.

kalafun
sumber
6

Saya juga ingin melakukan hal serupa, inilah yang saya temukan: (SDK 3.1.3)

Saya memiliki view controller A (itu sendiri dimiliki oleh Nav controller) yang memuat VC B pada tombol tekan:

Dalam AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Sekarang VC B memiliki antarmuka dari Bnib, tetapi ketika tombol ditekan, saya ingin pergi ke 'mode edit' yang memiliki UI terpisah dari nib yang berbeda, tetapi saya tidak ingin VC baru untuk mode edit, Saya ingin pena baru dikaitkan dengan B VC saya yang ada.

Jadi, dalam BViewController.m (metode tekan tombol)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Kemudian pada tombol lain tekan (untuk keluar dari mode edit):

[editView removeFromSuperview];

dan saya kembali ke Bnib asli saya.

Ini berfungsi dengan baik, tetapi perhatikan EditMode.nib saya hanya memiliki 1 obj tingkat atas di dalamnya, sebuah obj UIView. Tidak masalah apakah Pemilik File di nib ini ditetapkan sebagai BViewController atau NSObject default, TAPI pastikan Outlet Outlet di Pemilik File TIDAK diatur ke apa pun. Jika ya, maka saya mendapatkan crash exc_bad_access dan hasil xcode untuk memuat 6677 stack frame yang menunjukkan metode UIView internal yang berulang kali disebut ... sehingga terlihat seperti infinite loop. (View Outlet IS diatur dalam Bnib asli saya)

Semoga ini membantu.

Brynjar
sumber
Membaca yang tersirat, ini membantu saya juga.
petert
Menurut informasi di tautan Anda tidak boleh memanipulasi pengontrol tampilan dengan cara ini.
T. Markle
6

Saya membuat kategori yang saya sukai:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Lalu, panggil seperti ini:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Gunakan nama nib jika nib Anda bernama sesuatu selain nama kelas Anda.

Untuk menimpanya dalam subclass Anda untuk perilaku tambahan, itu bisa terlihat seperti ini:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
Logan
sumber
5

Untuk pengguna Swift dengan opsi yang dirancang:

  1. Buat UIViewsubkelas khusus dan file xib , yang akan kami beri nama setelah nama kelas kami sendiri: dalam kasus kami MemeView. Di dalam kelas Mim View, ingat untuk mendefinisikannya sebagai dapat dirancang dengan @IBDesignableatribut sebelum deklarasi kelas
  2. Pilih untuk mengatur Pemilik File di xib dengan UIViewsubkelas kustom kami di panel Inspektur Indetity

    masukkan deskripsi gambar di sini

  3. Dalam file xib sekarang kita dapat membangun antarmuka kita, membuat batasan, membuat outlet, tindakan, dll. masukkan deskripsi gambar di sini

  4. Kita perlu mengimplementasikan beberapa metode ke kelas kustom kita untuk membuka xib setelah diinisialisasi

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. Di kelas khusus kami, kami juga dapat mendefinisikan beberapa properti yang dapat diinspirasikan untuk memiliki kontrol penuh atas mereka dari pembangun antarmuka

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Beberapa contoh:
masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Jika kita perlu menambahkan lebih banyak informasi view saat ditampilkan di dalam storyboard atau xib lainnya, untuk melakukan itu kita bisa mengimplementasikan prepareForInterfaceBuilder(), metode ini hanya akan dieksekusi ketika membuka file di interface builder. Jika Anda melakukan semua yang saya tulis tetapi tidak ada yang berfungsi, itu adalah cara untuk men-debug tampilan sigle dengan menambahkan breakpoints dalam implementasinya. Berikut adalah hierarki tampilan. masukkan deskripsi gambar di sini
Lihat hiearchy

Semoga ini dapat membantu contoh lengkap dapat diunduh di sini

Andrea
sumber
Artikel lengkap dapat dilihat dari blog saya cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah tapi saya sudah memposting bagian yang relevan.
Andrea
let nib = loadNib(nibName)ini adalah instance XibbedView, jika Anda memanggil `` `addSubview (nib)` `` maka Anda menambahkan satu instance XibbedView ke instance XibbedView lainnya?
William Hu
Saya setuju tentang hal ini. Ketika Anda mencoba melihatnya dari "Debug View Hierarchy" tampaknya XibbedView berisi XibbedView. Kamu bisa melihatnya.
William Hu
@ WilliamHu jika Anda tidak memuat contoh yang diberikan, Anda dapat melihat bahwa MemeView.xib hanyalah sebuah instance UIView dengan sekelompok subview, pemegang file adalah MemeView yang merupakan subclass dari XibbedView. Jadi, satu-satunya contoh XibbedView hanya MimView yang memuat file xib di mana tampilan utama hanya tampilan
Andrea
Oh oke kalau begitu. Saya akan mengujinya. Terima kasih!
William Hu
4

Saya menemukan posting blog ini oleh Aaron Hillegass (penulis, instruktur, Cocoa ninja) sangat mencerahkan. Bahkan jika Anda tidak mengadopsi pendekatannya yang dimodifikasi untuk memuat file NIB melalui penginisialisasi yang ditunjuk Anda mungkin setidaknya akan mendapatkan pemahaman yang lebih baik dari proses yang terjadi. Saya telah menggunakan metode ini akhir-akhir ini untuk sukses besar!

Meltemi
sumber
Tautan sudah mati. Saya percaya itu sekarang terletak di bignerdranch.com/blog/…
joelliusp
3

Jawaban sebelumnya tidak memperhitungkan perubahan dalam struktur NIB (XIB) yang terjadi antara 2.0 dan 2.1 pada iPhone SDK. Konten pengguna sekarang mulai dari indeks 0 bukannya 1.

Anda dapat menggunakan makro 2.1 yang valid untuk semua versi 2.1 dan di atasnya (dua garis bawah sebelum IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Kami menggunakan teknik yang mirip dengan ini untuk sebagian besar aplikasi kami.

Barney

Barney Mattox
sumber
2
Barney: "Jawaban sebelumnya" tidak ada artinya di Stack Overflow karena jawaban menggeser posisi berdasarkan pemungutan suara. Lebih baik merujuk pada "jawaban Fred" atau "jawaban Barney" atau "jawaban olie."
Olie
3

Saya punya alasan untuk melakukan hal yang sama (secara program memuat tampilan dari file XIB), tetapi saya perlu melakukan ini sepenuhnya dari konteks subkelas dari subkelas a UIView(yaitu tanpa melibatkan pengontrol tampilan dengan cara apa pun). Untuk melakukan ini, saya membuat metode utilitas ini:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Lalu saya menyebutnya dari initWithFramemetode subclass saya seperti:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Diposting untuk kepentingan umum; jika ada yang melihat masalah tanpa melakukannya dengan cara ini, beri tahu saya.

MusiGenesis
sumber
Saya sudah menerapkan kode Anda, tetapi kode ini tetap keluar dari ruang lingkup init. Saya membuat metode statis Anda di kelas Utilities. Saya membuat file h / m bernama MyView (subkelas dari UIView) dan xib bernama sama. Di IB saya mengatur pemilik file xib DAN tampilan ke "MyView." Dalam debugger itu di luar jangkauan. Apakah saya kehilangan sesuatu di IB?
TigerCoding
Silakan lihat pertanyaan SO baru saya di sini: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Utilitas initWithNibName: @ "XIB1" withSelf: self]; Anda melewati diri sendiri ketika belum diatur. Bukankah kode itu sama dengan ini: self = [Utilities initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt
@ Javy: contoh kode saya di sini mungkin tidak berfungsi dengan ARC - Saya belum mengujinya dengan mengaktifkan ARC.
MusiGenesis
3

Berikut cara untuk melakukannya di Swift (saat ini sedang menulis Swift 2.0 di XCode 7 beta 5).

Dari UIViewsubkelas Anda yang Anda tetapkan sebagai "Kelas Kustom" di Interface Builder buat metode seperti ini (subkelas saya disebut RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Maka Anda bisa menyebutnya seperti ini:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
sumber
2

Tidak ada jawaban yang menjelaskan cara membuat XIB yang berdiri sendiri yang merupakan akar dari pertanyaan ini. Tidak ada Xcode 4opsi untuk "Buat File XIB Baru".

Untuk melakukan ini

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Ini mungkin tampak sederhana tetapi dapat menghemat waktu Anda beberapa menit untuk itu karena kata "XIB" tidak muncul di mana pun.

Whitneyland
sumber
1

@AVeryDev

6) Untuk melampirkan tampilan yang dimuat ke tampilan pengontrol tampilan Anda:

[self.view addSubview:myViewFromNib];

Agaknya, perlu untuk menghapusnya dari tampilan untuk menghindari kebocoran memori.

Untuk memperjelas: pengontrol tampilan memiliki beberapa IBOutlets, beberapa di antaranya terhubung ke item dalam file nib asli (seperti biasa), dan beberapa terhubung ke item dalam nib yang dimuat. Kedua nib memiliki kelas pemilik yang sama. Tampilan yang dimuat menutupi yang asli.

Petunjuk: atur opacity dari tampilan utama di nib yang dimuat ke nol, maka itu tidak akan mengaburkan item dari nib asli.

Mat
sumber
hal yang baik untuk diperhatikan, tetapi menambahkan tampilan sebagai subview secara otomatis menghapusnya dari induk sebelumnya sehingga tidak boleh bocor.
averydev
1

Setelah menghabiskan banyak waktu, saya mencari solusi berikut. Ikuti langkah-langkah ini untuk membuat custom UIView.

1) Buat kelas myCustomView diwarisi dari UIView .
masukkan deskripsi gambar di sini

2) Buat .xib dengan nama myCustomView .
masukkan deskripsi gambar di sini

3) Ubah Kelas UIView di dalam file .xib Anda, tetapkan Kelas myCustomView di sana.
masukkan deskripsi gambar di sini

4) Buat IBOutlets
masukkan deskripsi gambar di sini

5) Muat .xib dalam myCustomView * customView . Gunakan kode contoh berikut.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Catatan: Bagi yang masih menghadapi masalah dapat berkomentar, saya akan memberi mereka tautan proyek sampel dengan myCustomView

Aamir
sumber
1

Versi terpendek:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Denis Kutlubaev
sumber
0

Saya memiliki konvensi penamaan xibs dengan view di dalamnya sama dengan view. Sama seperti yang akan dilakukan pengontrol tampilan. Kemudian, saya tidak perlu menuliskan nama kelas dalam kode. Saya memuat UIView dari file nib dengan nama yang sama.

Contoh untuk kelas yang disebut MyView.

  • Buat file nib bernama MyView.xib di Interface Builder
  • Di Interface Builder, tambahkan UIView. Atur kelasnya ke MyView. Sesuaikan dengan isi hati Anda, sambungkan variabel instan MyView ke subview yang mungkin ingin Anda akses nanti.
  • Dalam kode Anda, buat MyView baru seperti ini:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

Inilah kategori untuk ini:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Saya kemudian akan memasang tombol dengan tindakan dari controller yang saya gunakan, dan mengatur hal-hal pada label menggunakan outlet di subkelas tampilan kustom saya.

n13
sumber
0

Untuk memuat tampilan dari nib / xib di Swift 4 secara terprogram:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}
Eng Yew
sumber
-1

Saya akhirnya menambahkan kategori ke UIView untuk ini:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

penjelasan di sini: viewcontroller kurang memuat view di ios & mac

gngrwzrd
sumber
Hrm Ada apa dengan tag itu?
n13
untuk mengetahui objek tingkat atas mana yang harus dipertahankan. pada saat itu diperlukan, tetapi Anda mungkin dapat mengambil sisa untuk mematuhi ARC sekarang.
gngrwzrd