Jak utworzyć niestandardową klasę widoku iOS i utworzyć instancję wielu jej kopii (w IB)?

Obecnie robię aplikację, która będzie miała wiele timerów, które są w zasadzie wszystkie takie same.

Chcę stworzyć własną klasę, która używa całego kodu dla timerów, jak również układu / animacji, więc mogę mieć 5 identycznych timerów, które działają niezależnie od siebie.

Stworzyłem układ używając IB (xcode 4.2) i cały kod timerów jest obecnie tylko w klasie viewcontroller.

Mam problemy z owijaniem mózgu wokół tego, jak zamknąć wszystko w niestandardowej klasie, a następnie dodać go do viewcontroller, każda pomoc będzie mile widziana.

Author: Michael Campsall, 2012-02-12

4 answers

Dobrze odpowiedzieć koncepcyjnie, Twój timer powinien być podklasą UIView zamiast NSObject.

Aby utworzyć instancję instancji timera w IB, po prostu przeciągnij UIView upuść ją w widoku kontrolera widoku i ustaw klasę na nazwę klasy timera.

Tutaj wpisz opis obrazka

Pamiętaj o klasie timera w kontrolerze widoku.

Edit: for IB design (dla instancji kodu patrz historia wersji)

Nie znam się zbyt dobrze na wszystko z storyboard, ale wiem, że możesz zbudować swój interfejs w IB za pomocą pliku .xib, który jest prawie identyczny z używaniem wersji storyboard; powinieneś nawet być w stanie skopiować i wkleić swoje widoki jako całość z istniejącego interfejsu do pliku .xib.

Aby to przetestować utworzyłem nowy pusty .xib o nazwie "MyCustomTimerView.xib". Następnie dodałem widok, A do tego dodałem etykietę i dwa przyciski. Tak:

Tutaj wpisz opis obrazka

Stworzyłem nową klasę objective-C podklasa UIView o nazwie "MyCustomTimer". W mojej .xib ustawiam klasę właściciela pliku na MyCustomTimer . Teraz mogę swobodnie łączyć akcje i wyjścia, tak jak każdy inny widok / kontroler. Plik wynikowy.h wygląda następująco:

@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

Jedyną przeszkodą do skoku jest zdobycie tego .xib na mojej podklasie UIView. Użycie .xib radykalnie zmniejsza wymaganą konfigurację. A ponieważ używasz storyboardów do ładowania timerów, o których wiemy, że -(id)initWithCoder: jest jedynym inicjalizatorem, który zostanie wezwany. Oto jak wygląda plik implementacji:

#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

Metoda o nazwie loadNibNamed:owner:options: robi dokładnie to, na co wygląda. Ładuje stalówkę i ustawia właściwość "File' s Owner " na self. Wyodrębniamy pierwszy obiekt w tablicy i jest to główny widok Stalówki. Dodajemy Widok jako subview i Voila jest na ekranie.

Oczywiście po prostu zmienia to kolor tła etykiety, gdy przyciski są wciśnięte, ale ten przykład powinien ci pomóc sposób.

Uwagi na podstawie komentarzy:

Warto zauważyć, że jeśli masz problemy z rekurencją nieskończoną, prawdopodobnie przegapiłeś subtelną sztuczkę tego rozwiązania. Nie robi tego, co myślisz. Widok umieszczony w storyboardzie nie jest widoczny, ale zamiast tego ładuje inny widok jako podgląd podrzędny. Widok, który ładuje, jest widokiem zdefiniowanym w stalówce. "Właściciel pliku" w stalówce jest tym niewidzialnym widokiem. Najfajniejsze jest to, że ten niewidoczny widok jest nadal Objective-Klasa C, która może być używana jako kontroler widoku dla widoku, który wnosi z końcówki. Na przykład metody IBAction w klasie MyCustomTimer są czymś, czego można oczekiwać bardziej w kontrolerze widoku niż w widoku.

na marginesie, niektórzy mogą twierdzić, że to łamie MVC i trochę się Zgadzam. Z mojego punktu widzenia jest to bardziej związane z niestandardowym UITableViewCell, który również czasami musi być kontrolerem części.

Warto również zauważyć, że ta odpowiedź miała zapewnić bardzo konkretne rozwiązanie; utworzyć jedną stalówkę, która może być instancjowana wiele razy na tym samym widoku, jak określono na storyboardzie. Na przykład możesz łatwo wyobrazić sobie sześć z tych liczników na ekranie iPada w jednym czasie. Jeśli potrzebujesz tylko określić widok kontrolera widoku, który ma być używany wiele razy w całej aplikacji, to rozwiązanie dostarczone przez jyavenard do tego pytania jest prawie na pewno lepszym rozwiązaniem dla Ciebie.

 138
Author: NJones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:39

Przykład Swift

Aktualizacja dla Xcode 9 i Swift 4

Oto podstawowe Przejście. Wiele się z tego nauczyłem oglądając tę serię filmów na Youtube . Później zaktualizowałem swoją odpowiedź na podstawie tego artykułu .

Dodaj własne pliki widoku

Następujące dwa pliki utworzą Twój własny widok:

  • .plik xib zawierający układ
  • .plik swift jako UIView podklasa

Szczegóły do ich dodania są poniżej.

Plik Xib

Dodaj a .plik xib do twojego projektu (Plik > Nowy > plik... > Interfejs Użytkownika > Widok) . Nazywam swoją ReusableCustomView.xib.

Utwórz układ, który chcesz mieć w widoku niestandardowym. Jako przykład zrobię układ z UILabel i UIButton. Dobrym pomysłem jest użycie układu automatycznego, aby rzeczy automatycznie zmieniały rozmiar bez względu na to, jaki rozmiar ustawisz później. (Użyłem Freeform dla rozmiaru xib w Inspektor atrybutów, żebym mógł dostosować symulowane metryki, ale nie jest to konieczne.)

Tutaj wpisz opis obrazka

Plik Swift

Dodaj a .plik swift do twojego projektu (Plik > Nowy > plik... > Source > Swift File) . Jest to podklasa UIView i nazywam swoją ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Uczyń plik Swift właścicielem

Wróć do swojego .plik xib i kliknij "właściciel pliku" w zarysie dokumentu. W tożsamości Inspektor napisz imię swojego .plik swift jako niestandardowa nazwa klasy.

Tutaj wpisz opis obrazka

Dodaj Własny Kod Widoku

Zastąp zawartość pliku ReusableCustomView.swift następującym kodem:

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
    }
}

Upewnij się, że pisownia Twojego imienia jest prawidłowa .plik xib.

Podłącz gniazdka i akcje

Podłącz gniazda i akcje poprzez sterowanie przeciąganiem z etykiety i przycisku w układzie xib do swift custom view kod.

Użyj własnego widoku

Twój widok niestandardowy jest już skończony. Wszystko, co musisz zrobić, to dodać UIView gdziekolwiek chcesz, w swoim głównym storyboardzie. Ustaw nazwę klasy widoku na ReusableCustomView w Inspektorze tożsamości.

Tutaj wpisz opis obrazka

W momencie pisania tego tekstu miałem problem z dodaniem @IBDesignable do kodu, więc widoki nie pojawiają się w IB, ale robią to podczas uruchamiania aplikacji. (Na powyższym obrazku dałem widoki IB kolor tła tak, że będą widoczne w czasie projektowania.)

Tutaj wpisz opis obrazka

 96
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-23 19:01:05

odpowiedź dla kontrolerów widoku, a nie views:

Istnieje łatwiejszy sposób załadowania xib ze storyboardu. Powiedzmy, że twój kontroler jest typu MyClassController, który dziedziczy od UIViewController.

Dodajesz UIViewController używając IB w storyboardzie; Zmień typ klasy na MyClassController. Usuń widok, który został automatycznie dodany w storyboardzie.

Upewnij się, że żądany XIB nazywa się MyClassController.xib.

Kiedy klasa będzie utworzone podczas ładowania storyboardu xib zostanie automatycznie załadowany. Powodem tego jest domyślna implementacja UIViewController, która wywołuje nazwę XIB z nazwą klasy.

 39
Author: jyavenard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-04 05:58:55

To nie jest naprawdę odpowiedź, ale myślę, że warto podzielić się tym podejściem.

Objective-C

  1. Import CustomViewWithXib.h i CustomViewWithXib.m do twojego projekt
  2. Utwórz własne pliki widoków o tej samej nazwie (.h / .na / .xib)
  3. Dziedzicz swoją klasę z CustomViewWithXib

Swift

  1. Import CustomViewWithXib.swift do twojego projektu
  2. Utwórz własne pliki widoków o tej samej nazwie (.swift i .xib)
  3. Dziedzicz swoją klasę z CustomViewWithXib

Opcjonalnie:

  1. przejdź do pliku xib, Ustaw właściciela z niestandardową nazwą klasy, jeśli potrzeba podłączenia niektórych elementów (więcej szczegółów w części Make plik Swift właściciela @ Suragch answer ' S)

To wszystko, teraz możesz dodać swój niestandardowy widok do storyboardu i zostanie wyświetlony :)

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)
    }
}

Kilka przykładów znajdziesz tutaj .

Mam nadzieję, że to pomoże.
 15
Author: Hamza Ghazouani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-24 14:08:33