Nasıl özel bir iOS görünüm sınıfı oluşturabilirim ve birden çok kopyasını (IB'de) örnekleyebilirim?


114

Şu anda, temelde hepsi aynı olan birden fazla zamanlayıcıya sahip bir uygulama yapıyorum.

Zamanlayıcılar ve düzen / animasyonlar için tüm kodu kullanan özel bir sınıf oluşturmak istiyorum, böylece birbirinden bağımsız olarak çalışan 5 aynı zamanlayıcıya sahip olabilirim.

Düzeni IB (xcode 4.2) kullanarak oluşturdum ve zamanlayıcılar için tüm kod şu anda sadece viewcontroller sınıfında.

Beynimi, her şeyi özel bir sınıfa nasıl yerleştireceğime ve sonra bunu görüntü denetleyicisine nasıl ekleyeceğime sarmakta güçlük çekiyorum, herhangi bir yardım çok takdir edilecektir.


Google'da burayı ziyaret eden herkes için ... iOS'a gelen kapsayıcı görünümlerinden beş yıl sonra ! Kapsayıcı görünümleri, şimdi iOS'ta her şeyi nasıl yaptığınızla ilgili temel ve temel fikirdir . Kullanımları inanılmaz derecede basit ve etrafta konteyner görünümleriyle ilgili çok sayıda mükemmel (beş yaşında!) Öğretici var, örneğin , örneğin
Fattie

2
@JoeBlow Bir kap görünümünü bir UITableViewCellveya içinde kullanamazsınız UICollectionViewCell. Benim durumumda, denetleyicilerde ve koleksiyon görünümlerinde birçok kez kullanabileceğim küçük ama oldukça karmaşık bir görünüme ihtiyacım var. Ve tasarımcılar onu yeniden çalışmaya devam ediyor, bu yüzden düzenin tanımlandığı tek bir yer istiyorum. Dolayısıyla bir uç.
Echelon

Yanıtlar:


142

Kavramsal olarak cevaplamak gerekirse, zamanlayıcınız büyük olasılıkla UIViewyerine bir alt sınıf olmalıdır NSObject.

Zamanlayıcınızın bir örneğini IB'de somutlaştırmak için, UIViewonu görüntü denetleyicinizin görünümüne sürükleyin ve sınıfını zamanlayıcınızın sınıf adına ayarlayın.

görüntü açıklamasını buraya girin

#importGörünüm denetleyicinizde zamanlayıcı sınıfınızı unutmayın .

Düzenleme: IB tasarımı için (kod somutlaştırması için revizyon geçmişine bakın)

Film şeridine pek aşina değilim, ancak IB'de arayüzünüzü .xibfilm şeridi sürümüyle neredeyse aynı olan bir dosya kullanarak oluşturabileceğinizi biliyorum ; Hatta görünümlerinizi bir bütün olarak mevcut arayüzünüzden .xibdosyaya kopyalayıp yapıştırabilmelisiniz .

Bunu test etmek için .xib"MyCustomTimerView.xib" adlı yeni bir boş yarattım . Sonra bir görünüm ekledim ve buna bir etiket ve iki düğme ekledim. Şöyle:

görüntü açıklamasını buraya girin

UIView"MyCustomTimer" adında yeni bir hedef-C sınıfı alt sınıf oluşturdum . Benim dosyamın Owner sınıfını MyCustomTimer olarak.xib ayarladım . Artık diğer herhangi bir görünüm / denetleyici gibi eylemleri ve çıkışları bağlamakta özgürüm. Ortaya çıkan dosya şuna benzer:.h

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

Atlamak için geriye kalan tek engel .xib, bunu UIViewalt sınıfıma almak. Bir kullanmak .xib, gerekli kurulumu önemli ölçüde azaltır. Ve zamanlayıcıları yüklemek için hikaye tahtaları kullandığınızdan -(id)initWithCoder:, çağrılacak tek başlatıcı olduğunu biliyoruz . İşte uygulama dosyası şuna benziyor:

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self addSubview:
         [[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" 
                                        owner:self 
                                      options:nil] objectAtIndex:0]];
    }
    return self;
}
- (IBAction)startButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

Adlı yöntem loadNibNamed:owner:options:, tam olarak göründüğü gibi yapar. Ucu yükler ve "File's Owner" özelliğini self olarak ayarlar. Dizideki ilk nesneyi çıkarıyoruz ve bu Nib'in kök görünümüdür. Görünümü bir alt görünüm olarak ekliyoruz ve Voila ekrana geliyor.

Açıkçası bu, düğmelere basıldığında etiketin arka plan rengini değiştirir, ancak bu örnek sizi yolunuza alacaktır.

Yorumlara dayalı notlar:

Sonsuz özyineleme problemleri yaşıyorsanız, muhtemelen bu çözümün ince hilesini kaçırmışsınızdır. Yaptığını düşündüğün şeyi yapmıyor. Film şeridine yerleştirilen görünüm görülmez, bunun yerine başka bir görünümü alt görünüm olarak yükler. Yüklediği görünüm, uçta tanımlanan görünümdür. Uçtaki "dosyanın sahibi" görünmeyen görünümdür. İşin güzel yanı, bu görünmeyen görünümün, uçtan getirdiği görünüm için bir çeşit görünüm denetleyicisi olarak kullanılabilen bir Objective-C sınıfı olmasıdır. Örneğin IBAction, MyCustomTimersınıftaki yöntemler, bir görünümden çok bir görünüm denetleyicisinde beklediğiniz bir şeydir.

Bir yan not olarak, bazıları bunun kırıldığını iddia edebilir MVCve bir şekilde katılıyorum. Benim bakış açıma göre, bir gelenekle daha yakından ilişkili UITableViewCell, ki bu da bazen parça kontrolörü olmak zorunda.

Bu cevabın çok özel bir çözüm sağlamak olduğunu da belirtmekte fayda var; bir film şeridinde düzenlendiği gibi aynı görünümde birden çok kez örneklenebilen bir uç oluşturun . Örneğin, bu zamanlayıcılardan altısının aynı anda bir iPad ekranında olduğunu kolayca hayal edebilirsiniz. Yalnızca uygulamanızda birden çok kez kullanılacak bir görünüm denetleyicisi için bir görünüm belirtmeniz gerekiyorsa, jyavenard tarafından bu soruya sağlanan çözüm kesinlikle sizin için daha iyi bir çözümdür.


Bu aslında ayrı bir soru gibi görünüyor. Ama hızlı bir şekilde cevaplanabileceğini düşünüyorum, işte burada. Yeni bir bildirim oluşturduğunuz her seferinde bu yeni bir bildirimdir, ancak aynı ada sahip olmaları gerekiyorsa, onları farklılaştıracak bir şeyler eklemek istiyorsanız userInfo, bildirimdeki özelliği kullanın@property(nonatomic,copy) NSDictionary *userInfo;
NJones

28
İnitWithCoder'dan sonsuz bir özyineleme alıyorum. Herhangi bir fikir?
kayboldu.

6
Eski bir iş parçacığını canlandırdığım için üzgünüm File's Owner, sınıf adınızın ayarlandığından emin olmak sonsuz özyineleme sorunumu çözdü.
themanatuf

20
Sonsuz özyinelemenin bir başka nedeni de xib'deki kök görünümü özel sınıfınıza ayarlamaktır. Bunu yapmak istemezseniz, varsayılan "UIView" olarak bırakın
XY

11
Bu yaklaşımla ilgili dikkatimi çeken tek bir şey var ... 2 Görüntünün aynı sınıfta olması kimseyi rahatsız etmiyor mu? UIView'den devralan Orijinal Dosya .h / .m birincisi ve [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; İkinci UIView Ekleme yaparak ... Bu benim için tuhaf görünüyor, başka kimse aynı şekilde hissetmiyor mu? Eğer öyleyse bu, tasarım tarafından yapılan bir elma mı? Yoksa birisinin bunu yaparak bulduğu çözüm mü?
SH

137

Swift örneği

Xcode 10 ve Swift 4 için güncellendi

İşte temel bir yürüyüş. Aslında bu Youtube video dizisini izleyerek çok şey öğrendim . Daha sonra cevabımı bu makaleye göre güncelledim .

Özel görünüm dosyaları ekleyin

Aşağıdaki iki dosya özel görünümünüzü oluşturacaktır:

  • düzeni içeren .xib dosyası
  • UIViewalt sınıf olarak .swift dosyası

Bunları eklemek için ayrıntılar aşağıdadır.

Xib dosya

Projenize bir .xib dosyası ekleyin (Dosya> Yeni> Dosya ...> Kullanıcı Arayüzü> Görünüm) . Benimkini arıyorum ReusableCustomView.xib.

Özel görünümünüzün sahip olmasını istediğiniz düzeni oluşturun. Örnek olarak, a UILabelve a ile bir düzen yapacağım UIButton. Otomatik mizanpajı kullanmak iyi bir fikirdir, böylece daha sonra hangi boyuta ayarlarsanız ayarlayın her şey otomatik olarak yeniden boyutlandırılır. ( Öznitelikler denetçisindeki xib boyutu için Serbest Biçimi kullandım , böylece simüle edilen ölçümleri ayarlayabilirdim, ancak bu gerekli değil.)

görüntü açıklamasını buraya girin

Swift dosyası

Projenize bir .swift dosyası ekleyin (Dosya> Yeni> Dosya ...> Kaynak> Swift Dosyası) . Bu bir alt sınıfı UIViewve ben benimkini arıyorum ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Swift dosyasını sahibi yapın

.Xib dosyanıza geri dönün ve Belge Ana Hatlarında "Dosyanın Sahibi" ni tıklayın. Identity Inspector'da .swift dosyanızın adını özel sınıf adı olarak yazın.

görüntü açıklamasını buraya girin

Özel Görünüm Kodu Ekleyin

Değiştir ReusableCustomView.swiftaşağıdaki kodla dosyanın içeriğini:

import UIKit

@IBDesignable
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)
        commonInit()
    }

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

    func commonInit() {
        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
    }
}

.Xib dosyanızın adı için doğru yazımı aldığınızdan emin olun.

Çıkışları ve Eylemleri Bağlayın

Xib düzenindeki etiket ve düğmeden hızlı özel görünüm koduna sürüklemeyi kontrol ederek çıkışları ve eylemleri bağlayın.

Size özel görünümü kullanın

Özel görünümünüz artık bitti. Tek yapmanız gereken UIView, ana film şeridinizde istediğiniz yere bir eklemektir . ReusableCustomViewIdentity Inspector'da görünümün sınıf adını olarak ayarlayın .

görüntü açıklamasını buraya girin


31
Xib görünüm sınıfını ResuableCustomView olarak değil, bu Xib'in Sahibi olarak ayarlamak önemlidir. Aksi takdirde hata alırsınız.
RealNmae

5
Evet, güzel hatırlatma. Sınıf adına Xib'in görünümünü (Dosya Sahibi yerine) ayarlamanın neden olduğu sonsuz özyineleme problemlerini çözmeye çalışırken ne kadar zaman harcadığımı bilmiyorum.
Suragch

2
@TomCalmon, bu projeyi Xcode 8'de Swift 3 ile yeniden yaptım ve Auto Layout benim için iyi çalışıyordu.
Suragch

6
Başkasının IB görünümü render ile ilgili bir sorun varsa, ben değişen bulundu Bundle.mainetmek Bundle(for: ...)hile yok. Görünüşe göre, Bundle.maintasarım zamanında kullanılamıyor.
crizzis

4
Bunun @ IBDesignable ile çalışmamasının nedeni, uygulamamış olmanızdır init(frame:). Bunu eklediğinizde (ve tüm başlatma kodunu bir commonInit()işleve çektiğinizde ), iyi çalışır ve IB'de gösterilir. Daha fazla bilgiyi burada bulabilirsiniz: stackoverflow.com/questions/31265906/…
Dimitris

39

Görünüm denetleyicileri için yanıt, görünümler için değil :

Bir film şeridinden bir xib yüklemenin daha kolay bir yolu var. Denetleyicinizin MyClassController türünde olduğunu varsayalım UIViewController.

UIViewControllerHikaye panonuza IB kullanarak bir eklersiniz ; sınıf türünü MyClassController olacak şekilde değiştirin. Film şeridine otomatik olarak eklenen görünümü silin.

Çağrılmasını istediğiniz XIB'nin MyClassController.xib olarak adlandırıldığından emin olun.

Film şeridi yüklenmesi sırasında sınıfın somutlaştırılması durumunda, xib otomatik olarak yüklenecektir. Bunun nedeni UIViewController, sınıf adıyla adlandırılan XIB'yi çağıran varsayılan uygulamadan kaynaklanmaktadır .


1
Özel görünümleri her zaman şişiriyorum, ancak bu küçük numarayı film şeridine yüklemek için asla bilmiyordum. ÇOK takdir edildi!
MrTristan

38
Soru, görünüm denetleyicileriyle ilgili değil, görünümlerle ilgilidir.
Ricardo

Açıklama için başlık eklendi, "Görünüm denetleyicileri için
yanıt

1
İOS 9.1'de çalışıyor gibi görünmüyor. Otomatik oluşturulan (varsayılan) görünümü kaldırırsam, VC'nin viewDidLoad'dan döndükten sonra çöküyor. XIB'deki görünümün varsayılan görünümün yerine bağlanacağını sanmıyorum.
Jeff

1
Aldığım istisna şu 'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''. Whacko nibName'e dikkat edin. Uç için doğru sınıf adını kullanıyorum.
Jeff

16

Bu gerçekten bir cevap değil, ancak bu yaklaşımı paylaşmanın yararlı olacağını düşünüyorum.

Objective-C

  1. CustomViewWithXib.h ve CustomViewWithXib.m'yi projenize aktarın
  2. Aynı adla (.h / .m / .xib) özel görünüm dosyaları oluşturun
  3. CustomViewWithXib'den özel sınıfınızı devralın

hızlı

  1. CustomViewWithXib.swift'i projenize aktarın
  2. Aynı adla (.swift ve .xib) özel görünüm dosyaları oluşturun
  3. CustomViewWithXib'den özel sınıfınızı devralın

İsteğe bağlı :

  1. Xib dosyanıza gidin, bazı öğeleri bağlamanız gerekiyorsa, sahibini özel sınıf adınızla ayarlayın (daha fazla ayrıntı için Swift dosyasını @Suragch yanıtının sahibi yapma bölümüne bakın)

Hepsi bu, şimdi özel görünümünüzü film şeridinize ekleyebilirsiniz ve gösterilecektir :)

CustomViewWithXib.h :

 #import <UIKit/UIKit.h>

/**
 *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
 MyCustomView.h
 MyCustomView.m
 MyCustomView.xib
 */

// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView

@end

CustomViewWithXib.m :

#import "CustomViewWithXib.h"

@implementation CustomViewWithXib

#pragma mark - init methods

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

#pragma mark - setup view

- (UIView *)loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    //  An exception will be thrown if the xib file with this class name not found,
    UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
    return view;
}

- (void)commonSetup {
    UIView *nibView = [self loadViewFromNib];
    nibView.frame = self.bounds;
    // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
    nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // Adding nibView on the top of our view
    [self addSubview:nibView];
}

@end

CustomViewWithXib.swift :

import UIKit

@IBDesignable
class CustomViewWithXib: UIView {

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

        commonSetup()
    }

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

        commonSetup()
    }

    // MARK: setup view 

    private func loadViewFromNib() -> UIView {
        let viewBundle = NSBundle(forClass: self.dynamicType)
        //  An exception will be thrown if the xib file with this class name not found,
        let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
        return view as! UIView
    }

    private func commonSetup() {
        let nibView = loadViewFromNib()
        nibView.frame = bounds
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        // Adding nibView on the top of our view
        addSubview(nibView)
    }
}

Bazı örnekler bulabilirsin Burada bulabilirsiniz .

Umarım yardımcı olur.


uicontrols görünümü için iboutlet'i nasıl ekleyebiliriz
Amr Angry

1
Hey @AmrAngry, görünümleri bağlamak için 4 adımı yapmanız gereken: xib dosyanıza gidin, "Dosyanın Sahibi" bölümünde sınıfınızı ayarlayın ve "ctrl" düğmesine dokunarak görünümlerinizi arayüze sürükleyip bırakın, Bu örnekle kod kaynağı, herhangi bir sorunuz varsa tereddüt etmeyin, umarım yardımcı olur
HamzaGhazouani

tekrarınız için çok teşekkürler, bunu zaten yapıyorum ama sanırım sorunum bu bölümde değil. Benim Sorunum, bir UIDatePIcker ekleyip ViewController'dan bu kontrol için bir hedef eylem eklemeye çalışıyorum ve uiControleer olayının değeri değiştirildi, ancak yöntem asla çağrılmıyor / fire
Amr Angry

1
@AmrAngry Seçici görünümünü ekleyerek örneği güncelliyorum, size yardımcı olabilir mi :) github.com/HamzaGhazouani/Stackoverflow/tree/master/…
HamzaGhazouani

1
Teşekkür ederim bunu uzun zamandır arıyordum.
MH175
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.