Muat tampilan dari file xib eksternal di storyboard

131

Saya ingin menggunakan tampilan di seluruh banyak viewcontrollers di storyboard. Jadi, saya berpikir tentang mendesain tampilan dalam xib eksternal sehingga perubahan tercermin di setiap viewcontroller. Tetapi bagaimana kita bisa memuat tampilan dari xib eksternal dalam storyboard dan apakah itu mungkin? Jika bukan itu masalahnya, apa alternatif lain yang tersedia untuk menyesuaikan situasi ini?

Sebastian Hoffmann
sumber

Jawaban:

132

Contoh lengkap saya ada di sini , tetapi saya akan memberikan ringkasan di bawah ini.

Tata letak

Tambahkan file .swift dan .xib masing-masing dengan nama yang sama ke proyek Anda. File .xib berisi tata letak tampilan khusus Anda (lebih disukai menggunakan batasan tata letak otomatis).

Jadikan file cepat sebagai pemilik file xib.

masukkan deskripsi gambar di sini Kode

Tambahkan kode berikut ke file .swift dan kaitkan outlet dan tindakan dari file .xib.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Gunakan

Gunakan tampilan khusus Anda di mana saja di storyboard Anda. Cukup tambahkan UIViewdan atur nama kelas ke nama kelas khusus Anda.

masukkan deskripsi gambar di sini

Suragch
sumber
3
Bukankah itu panggilan loadNibNamed init (coder :)? Saya mengalami gangguan saat mencoba menyesuaikan pendekatan Anda.
Fishman
@Fishman, jika Anda mencoba memuat tampilan secara terprogram (bukan dari storyboard), itu akan macet karena saat ini tidak memiliki init(frame:). Lihat tutorial ini untuk lebih jelasnya.
Suragch
7
Penyebab umum kerusakan lainnya adalah pengaturan tampilan kustom untuk pemilik file . Lihat lingkaran merah di jawaban saya.
Suragch
5
Ya saya telah mengatur kelas tampilan root alih-alih pemilik file dan itu menyebabkan loop tak terbatas.
devios1
2
Setelah menambahkan view.autoresizingMask = [.flexibleWidth, .flexibleHeight] baris sebelum self.addSubview (view) berfungsi dengan baik.
Pramod Tapaniya
69

Untuk sementara pendekatan Christopher Swasey adalah pendekatan terbaik yang saya temukan. Saya bertanya kepada beberapa devs senior di tim saya tentang hal itu dan salah satu dari mereka memiliki solusi yang sempurna ! Ini memenuhi semua kekhawatiran yang ditangani oleh Christopher Swasey dengan fasih dan tidak memerlukan kode subkelas boilerplate (perhatian utama saya dengan pendekatannya). Ada satu gotcha , tetapi selain itu cukup intuitif dan mudah diterapkan.

  1. Buat kelas UIView khusus dalam file .swift untuk mengontrol xib Anda. yaituMyCustomClass.swift
  2. Buat file .xib dan gaya sesuai keinginan Anda. yaituMyCustomClass.xib
  3. Setel File's Ownerfile .xib menjadi kelas khusus Anda ( MyCustomClass)
  4. GOTCHA: biarkan classnilai (di bawah identity Inspector) untuk tampilan khusus Anda dalam file .xib kosong. Jadi tampilan khusus Anda tidak akan memiliki kelas yang ditentukan, tetapi akan memiliki Pemilik File yang ditentukan.
  5. Sambungkan outlet Anda seperti biasa menggunakan Assistant Editor .
    • CATATAN: Jika Anda melihat Connections InspectorAnda akan melihat bahwa Outlet Referensi Anda tidak mereferensikan kelas kustom Anda (yaitu MyCustomClass), melainkan referensi File's Owner. Karena File's Ownerditentukan sebagai kelas khusus Anda, outlet akan terhubung dan berfungsi sebagaimana mestinya.
  6. Pastikan kelas khusus Anda memiliki @IBDesignable sebelum pernyataan kelas.
  7. Buat kelas khusus Anda sesuai dengan NibLoadableprotokol yang dirujuk di bawah ini.
    • CATATAN: Jika .swiftnama file kelas kustom Anda berbeda dari .xibnama file Anda , maka atur nibNameproperti menjadi nama .xibfile Anda .
  8. Terapkan required init?(coder aDecoder: NSCoder)dan override init(frame: CGRect)panggilsetupFromNib() seperti contoh di bawah ini.
  9. Tambahkan UIView ke storyboard yang Anda inginkan dan atur kelas menjadi nama kelas kustom Anda (mis MyCustomClass ).
  10. Tonton IBDesignable dalam aksi saat menarik .xib Anda di storyboard dengan semua kagum dan takjub.

Berikut adalah protokol yang ingin Anda rujuk:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

Dan di sini adalah contoh dari MyCustomClassyang mengimplementasikan protokol (dengan nama file .xib MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupFromNib()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupFromNib()
    }

}

CATATAN: Jika Anda melewatkan Gotcha dan menetapkan classnilai di dalam file .xib Anda menjadi kelas kustom Anda, maka itu tidak akan menggambar di storyboard dan Anda akan mendapatkan EXC_BAD_ACCESSkesalahan ketika Anda menjalankan aplikasi karena macet dalam loop tak terbatas dari mencoba menginisialisasi kelas dari nib menggunakan init?(coder aDecoder: NSCoder)metode yang kemudian memanggil Self.nib.instantiatedan memanggil initlagi.

Ben Patch
sumber
2
Berikut ini adalah pendekatan hebat lainnya, tapi saya rasa yang di atas masih lebih baik: medium.com/zenchef-tech-and-product/…
Ben Patch
4
Pendekatan yang Anda sebutkan di atas berfungsi dengan baik dan menerapkan pratinjau langsung tepat di storyboard. Ini sangat berguna dan luar biasa, besar!
Igor Leonovich
1
FYI: solusi ini, menggunakan definisi kendala dalam setupFromNib(), tampaknya memperbaiki masalah tata letak otomatis tertentu yang aneh dengan sel tampilan tabel ukuran otomatis yang berisi tampilan yang dibuat-XIB.
Gary
1
Sejauh ini solusi terbaik! Saya suka @IBDesignablekompatibilitas. Tidak dapat percaya mengapa Xcode atau UIKit tidak menyediakan sesuatu seperti ini sebagai default saat menambahkan file UIView.
fl034
1
sepertinya tidak bisa membuat yang ini berfungsi .. Setiap kali saya mengatur File Owner di dalam file .xib saya, itu juga menetapkan kelas khusus.
drewster
32

Dengan asumsi bahwa Anda telah membuat xib yang ingin Anda gunakan:

1) Buat subkelas khusus dari UIView (Anda dapat membuka File -> New -> File ... -> Cocoa Touch Class. Pastikan "Subkelas dari:" adalah "UIView").

2) Tambahkan tampilan yang didasarkan pada xib sebagai subview untuk tampilan ini saat inisialisasi.

Dalam Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

Dalam Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

Dalam Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Di mana pun Anda ingin menggunakannya di storyboard Anda, tambahkan UIView seperti biasanya, pilih tampilan yang baru ditambahkan, buka Identity Inspector (ikon ketiga di kanan atas yang terlihat seperti persegi panjang dengan garis-garis di dalamnya), dan masukkan nama subclass Anda sebagai "Kelas" di bawah "Kelas Kustom".

pengguna1021430
sumber
xibView.frame = self.frame;seharusnya xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);, jika tidak xibView akan memiliki offset ketika tampilan ditambahkan ke storyboard.
BabyPanda
terlambat ke pesta tetapi sepertinya dia mengubahnya ke xibView.frame = self.bounds, yang merupakan bingkai tanpa offset
Heavy_Bullets
20
Hasil dalam kecelakaan karena rekursi tak terbatas. Memuat nib menciptakan instance lain dari subclass.
David
2
Kelas tampilan xib tidak boleh sama dengan subkelas baru ini. Jika xib adalah MyClass, Anda dapat membuat kelas MyClass baru ini.
user1021430
solusi di atas akan crash ke rekursi tak terbatas.
JBarros35
27

Saya selalu menemukan solusi "tambahkan sebagai subview" tidak memuaskan, karena sekrup dengan (1) autolayout, (2) @IBInspectable, dan (3) outlet. Sebagai gantinya, izinkan saya memperkenalkan Anda pada keajaiban awakeAfter:, NSObjectmetode.

awakeAftermemungkinkan Anda menukar objek yang sebenarnya dibangunkan dari NIB / Storyboard dengan objek yang sama sekali berbeda. Itu objek kemudian dimasukkan melalui proses hidrasi, memilikiawakeFromNib disebut di atasnya, ditambahkan sebagai pandangan, dll

Kita bisa menggunakan ini dalam subkelas "kardus cut-out" dari pandangan kita, satu-satunya tujuan yang akan memuat pandangan dari NIB dan mengembalikannya untuk digunakan dalam Storyboard. Subclass yang dapat disematkan kemudian ditentukan dalam inspektur identitas tampilan Storyboard, daripada kelas aslinya. Sebenarnya tidak harus menjadi subclass agar ini berfungsi, tetapi menjadikannya subclass adalah apa yang memungkinkan IB untuk melihat properti IBInspectable / IBOutlet.

Pelat ekstra ini mungkin tampak suboptimal — dan dalam arti tertentu, karena idealnya UIStoryboardakan menangani hal ini dengan mulus — tetapi memiliki keuntungan meninggalkan NIB asli danUIView subkelas sepenuhnya tidak dimodifikasi. Peran yang dimainkannya pada dasarnya adalah kelas adaptor atau jembatan, dan sangat valid, desain-bijaksana, sebagai kelas tambahan, bahkan jika itu disesalkan. Di sisi lain, jika Anda lebih suka pelit dengan kelas Anda, solusi @ BenPatch bekerja dengan mengimplementasikan protokol dengan beberapa perubahan kecil lainnya. Pertanyaan tentang solusi mana yang lebih baik bermuara pada masalah gaya programmer: apakah orang lebih suka komposisi objek atau multiple inheritance.

Catatan: kelas yang diatur pada tampilan di file NIB tetap sama. Subclass yang dapat disematkan hanya digunakan di storyboard. Subkelas tidak dapat digunakan untuk membuat tampilan dalam kode, jadi seharusnya tidak memiliki logika tambahan. Seharusnya hanya berisi awakeAfterkait.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ Satu kelemahan signifikan di sini adalah jika Anda menentukan batasan rasio lebar, tinggi, atau aspek dalam storyboard yang tidak berhubungan dengan tampilan lain, maka harus disalin secara manual. Kendala yang berhubungan dengan dua pandangan dipasang pada leluhur bersama terdekat, dan pandangan terbangun dari storyboard dari dalam-luar, sehingga pada saat kendala terhidrasi pada superview pertukaran telah terjadi. Kendala yang hanya melibatkan tampilan yang dipermasalahkan dipasang langsung pada tampilan itu, dan dengan demikian dilemparkan ketika swap terjadi kecuali mereka disalin.

Perhatikan bahwa yang terjadi di sini adalah kendala yang dipasang pada tampilan di storyboard disalin ke tampilan yang baru dipakai , yang mungkin sudah memiliki kendala sendiri, yang didefinisikan dalam file nib-nya. Mereka tidak terpengaruh.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibadalah ekstensi aman untuk UIView. Yang dilakukannya hanyalah mengulang-ulang objek NIB hingga menemukan yang cocok dengan tipenya. Perhatikan bahwa tipe generik adalah nilai balik , jadi tipe tersebut harus ditentukan di situs panggilan.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
Christopher Swasey
sumber
Itu membingungkan. Jika saya tidak salah, ini hanya bekerja "di storyboard" - jika Anda mencoba membuat kelas dalam kode saat runtime, saya tidak berpikir itu berfungsi. Aku percaya.
Fattie
Subkelas harus bekerja dalam kode sama baiknya dengan kelas asli untuk semua maksud dan tujuan. Jika Anda ingin memuat tampilan dari nib dalam kode Anda hanya akan instantiate langsung menggunakan teknik yang sama. Semua subkelas lakukan adalah mengambil kode untuk membuat tampilan dari sebuah nib dan meletakkannya di kait untuk storyboard untuk digunakan.
Christopher Swasey
Sebenarnya saya salah — itu akan bekerja dengan baik jika Anda bisa membuat instance, tetapi Anda tidak bisa, karena tampilan di NIB akan memiliki superclass sebagai tipenya sehingga instantiateViewFromNibtidak akan mengembalikan apa pun. Bukan masalah besar bagaimanapun IMO, subclass hanyalah alat untuk menghubungkan ke storyboard, semua kode harus pada kelas asli.
Christopher Swasey
1
Bagus! Satu hal yang membuat saya tersandung karena saya memiliki sedikit pengalaman dengan xibs (saya hanya pernah bekerja dengan storyboard & pendekatan programatik), meninggalkan ini di sini jika itu membantu seseorang: dalam file .xib, Anda perlu memilih tampilan tingkat atas, dan mengatur tipe kelasnya untuk MyCustomView. Di xib saya bilah sisi dalam sebelah kiri tidak ada secara default; untuk menyalakannya, ada tombol di sebelah kontrol ciri "View as: iPhone 7" dekat sisi bawah / kiri.
xaphod
5
Ini mengerem kendala ketika digantikan oleh objek lain. :(
invoodoo
6

Solusi terbaik saat ini adalah dengan hanya menggunakan pengontrol tampilan kustom dengan tampilan yang ditentukan dalam xib, dan cukup hapus properti "view" yang dibuat Xcode di dalam storyboard saat menambahkan pengontrol tampilan ke dalamnya (jangan lupa untuk mengatur nama kelas kustom).

Ini akan membuat runtime secara otomatis mencari xib dan memuatnya. Anda dapat menggunakan trik ini untuk segala jenis tampilan kontainer, atau tampilan konten.

Ben G
sumber
5

Saya berpikir alternativeuntuk menggunakan XIB viewsuntuk digunakan View Controller dalam storyboard terpisah .

Kemudian di storyboard utama di tempat tampilan kustom digunakan container viewdengan Embed Seguedan harus controller tampilan kustomStoryboardReference ini yang tampilan harus ditempatkan di dalam tampilan lain di storyboard utama.

Kemudian kita dapat mengatur delegasi dan komunikasi antara ViewController embed ini dan pengendali tampilan utama melalui persiapan untuk segue . Pendekatan ini berbeda daripada menampilkan UIView, tetapi jauh lebih sederhana dan lebih efisien (dari perspektif pemrograman) dapat digunakan untuk mencapai tujuan yang sama, yaitu memiliki tampilan kustom yang dapat digunakan kembali yang terlihat di storyboard utama

Keuntungan tambahan adalah Anda dapat menerapkan logika Anda di kelas CustomViewController dan mengatur semua delegasi dan melihat persiapan tanpa membuat kelas pengontrol yang terpisah (sulit ditemukan di proyek), dan tanpa menempatkan kode boilerplate di UIViewController utama menggunakan Komponen. Saya pikir ini bagus untuk komponen bekas yang dapat digunakan kembali. Komponen Music Player (seperti widget) yang dapat disematkan dalam tampilan lain.

Michał Ziobro
sumber
Memang solusi yang jauh lebih sederhana daripada menggunakan tampilan kustom xib :(
Wilson
1
setelah berputar-putar dengan rantai penciptaan siklus hidup xib apel di UIView kustom ini adalah cara saya akhirnya pergi.
LightningStryk
Jika Anda ingin menambahkan tampilan kustom secara dinamis, kami masih harus menggunakan pengontrol tampilan terpisah (paling disukai XIB)
Satyam
5

Meskipun jawaban paling populer bekerja dengan baik, secara konsep mereka salah. Mereka semua digunakan File's ownersebagai koneksi antara outlet kelas dan komponen UI. File's ownerseharusnya digunakan hanya untuk objek tingkat atas bukan UIViews. Lihat dokumen pengembang Apple . Memiliki UIView sebagai File's ownermengarah pada konsekuensi yang tidak diinginkan ini.

  1. Anda dipaksa untuk menggunakan contentViewtempat yang seharusnya Anda gunakan self. Ini tidak hanya jelek, tetapi juga salah secara struktural karena tampilan perantara membuat struktur data tidak menyampaikan struktur UI-nya. Ini seperti kebalikan dari UI deklaratif.
  2. Anda hanya dapat memiliki satu UIView per Xib. Xib seharusnya memiliki beberapa UIViews.

Ada cara yang elegan untuk melakukannya tanpa menggunakan File's owner. Silakan periksa posting blog ini . Ini menjelaskan cara melakukannya dengan cara yang benar.

Ingun 전인 건
sumber
tidak masalah sama sekali Anda tidak menjelaskan mengapa itu buruk. Saya tidak mengerti mengapa. tidak ada efek samping.
JBarros35
1

Inilah jawaban yang Anda inginkan selama ini. Anda bisa saja membuat CustomViewkelas Anda , memiliki instance master dalam xib dengan semua subview dan outlet. Kemudian Anda bisa menerapkan kelas itu ke instance apa pun di storyboard Anda atau xib lainnya.

Tidak perlu mengutak-atik Pemilik File, atau menghubungkan outlet ke proxy atau memodifikasi xib dengan cara yang aneh, atau menambahkan contoh tampilan kustom Anda sebagai subview dari itu sendiri.

Lakukan saja ini:

  1. Impor kerangka kerja BFWControls
  2. Ubah superclass Anda dari UIViewke NibView(atau dari UITableViewCellke NibTableViewCell)

Itu dia!

Ia bahkan bekerja dengan IBDesignable untuk merujuk tampilan kustom Anda (termasuk subview dari xib) pada waktu desain di storyboard.

Anda dapat membaca lebih lanjut tentang ini di sini: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

Dan Anda bisa mendapatkan kerangka BFWControls open source di sini: https://github.com/BareFeetWare/BFWControls

Dan inilah ekstrak sederhana dari NibReplaceablekode yang mendorongnya, jika Anda penasaran:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom 👣

tanpa alas kaki
sumber
Solusi bagus, terima kasih!
avee
Saya tidak ingin menginstal kerangka kerja ketika harus disediakan secara asli.
JBarros35
0

Solusi ini dapat digunakan bahkan jika kelas Anda tidak memiliki nama yang sama dengan XIB. Misalnya, jika Anda memiliki pengontrol kelas tampilan dasar controllerA yang memiliki XIB nama controllerA.xib dan Anda subclass ini dengan controllerB dan ingin membuat turunan controllerB di storyboard, maka Anda dapat:

  • buat view controller di storyboard
  • atur kelas controller ke controllerB
  • hapus tampilan controllerB di storyboard
  • timpa tampilan beban di controllerA ke:

*

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}
otusweb
sumber
0

Solusi untuk Objective-C sesuai dengan langkah-langkah yang dijelaskan dalam respons Ben Patch .

Gunakan ekstensi untuk UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Buat file MyView.h, MyView.mdan MyView.xib.

Pertama, siapkan tanggapan Anda MyView.xibseperti yang dikatakan Ben Patch, jadi atur kelas MyViewuntuk pemilik File alih-alih tampilan utama di dalam XIB ini.

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

Dan nanti buat saja tampilan Anda secara terprogram:

MyView* view = [[MyView alloc] init];

Peringatan! Pratinjau tampilan ini tidak akan ditampilkan di Storyboard jika Anda menggunakan Ekstensi WatchKit karena bug ini di Xcode> = 9.2: https://forums.developer.apple.com/thread/95616

Ariel Bogdziewicz
sumber
Seharusnya menyediakan versi Swift. Sangat sedikit yang mungkin menggunakan ObjC saat ini
Satyam