Как создать пользовательский класс представления iOS и создать несколько экземпляров его (в IB)?

В настоящее время я создаю приложение, которое будет иметь несколько таймеров, которые в основном все одинаковы.

Я хочу создать пользовательский класс, который использует весь код для таймеров, а также макет/анимацию, поэтому я могу иметь 5 одинаковых таймеров, которые работают независимо друг от друга.

Я создал макет с использованием IB (xcode 4.2), и весь код для таймеров в настоящее время находится только в классе viewcontroller.

У меня возникли трудности с переносом моего мозга вокруг того, как инкапсулировать все в пользовательский класс, а затем добавить его в viewcontroller, любая помощь будет высоко оценена.

Ответ 1

Чтобы ответить концептуально, ваш таймер должен скорее быть подклассом UIView вместо NSObject.

Чтобы создать экземпляр вашего таймера в IB, просто перетащите его UIView на его представление контроллера просмотра и установите его классом в имя своего таймера.

enter image description here

Помните #import свой класс таймера в вашем контроллере представления.

Изменить: для дизайна IB (для создания экземпляра кода см. историю изменений)

Я не очень хорошо разбираюсь в раскадровке, но я знаю, что вы можете построить свой интерфейс в IB, используя файл .xib, который почти идентичен использованию версии раскадровки; Вы даже сможете копировать и вставлять свои представления в целом из существующего интерфейса в файл .xib.

Чтобы проверить это, я создал новый пустой .xib с именем "MyCustomTimerView.xib". Затем я добавил представление, и к нему добавлена ​​метка и две кнопки. Как и так:

enter image description here

Я создал новый класс objective-C подкласса UIView с именем "MyCustomTimer". В моем .xib я установил класс File Owner MyCustomTimer. Теперь я могу подключать действия и выходы, как и любой другой вид/контроллер. Полученный файл .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

Единственное препятствие для перехода - получить этот .xib в моем подклассе UIView. Использование .xib резко сокращает требуемую настройку. И поскольку вы используете раскадровку для загрузки таймеров, мы знаем, что -(id)initWithCoder: - единственный инициализатор, который будет вызываться. Итак, вот как выглядит файл реализации:

#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

Метод с именем loadNibNamed:owner:options: делает именно то, что он звучит так, как будто он. Он загружает Nib и устанавливает свойство "File Owner" для себя. Мы извлекаем первый объект в массиве и являемся корневым представлением Nib. Мы добавляем представление как подвью и Voila на экран.

Очевидно, что это просто меняет цвет фона метки при нажатии кнопок, но этот пример должен хорошо вам помочь.

Примечания, основанные на комментариях:

Стоит отметить, что если вы получаете бесконечные проблемы с рекурсией, вы, вероятно, пропустили тонкий трюк этого решения. Это не делает то, что вы думаете, что это делает. Представление, которое помещается в раскадровку, не видно, но вместо этого загружает другое представление в качестве подвью. Это представление, которое он загружает, представляет собой представление, которое определено в nib. "Владелец файла" в банке - это невидимое представление. Классная часть состоит в том, что это невидимое представление все еще является классом objective-C, который может использоваться как контроллер вида для вида, который он вносит из ниба. Например, методы IBAction в классе MyCustomTimer - это то, чего вы ожидаете больше в контроллере представления, чем в представлении.

В качестве побочного примечания некоторые могут утверждать, что это нарушает MVC, и я согласен несколько. С моей точки зрения, он более тесно связан с пользовательским UITableViewCell, который также иногда должен быть контроллером части.

Также стоит отметить, что этот ответ должен был дать очень конкретное решение; создайте один наконечник, который может быть создан несколько раз на том же изображении, что и на раскадровке. Например, вы можете легко представить шесть из этих таймеров на экране iPad за один раз. Если вам нужно только указать представление для контроллера вида, который будет использоваться несколько раз в вашем приложении, тогда решение, предоставленное jyavenard для этого вопроса, почти наверняка будет лучше решение для вас.

Ответ 2

Пример Swift

Обновлено для Xcode 10 и Swift 4

Вот базовая прогулка. Я изначально узнал об этом, посмотрев эту серию видео Youtube. Позже я обновил свой ответ на основе этой статьи.

Добавить пользовательские файлы просмотра

Следующие два файла формируют ваш пользовательский вид:

  • .xib файл для размещения макета
  • .swift как подкласс UIView

Подробности для их добавления приведены ниже.

Файл Xib

Добавьте в проект файл.xib (Файл> Создать> Файл...> Пользовательский интерфейс> Вид). Я вызываю мой ReusableCustomView.xib.

Создайте макет, который требуется для вашего пользовательского представления. В качестве примера я сделаю макет с UILabel и UIButton. Рекомендуется использовать автоматическую компоновку, чтобы вещи автоматически изменялись независимо от того, какой размер вы задали позже. (Я использовал Freeform для размера xib в инспекторе атрибутов, чтобы я мог настраивать моделируемые показатели, но это необязательно.)

enter image description here

Быстрый файл

Добавьте в проект файл.swift (Файл> Создать> Файл...> Источник> Файл Swift). Это подкласс UIView и я UIView ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Сделать файл Swift владельцем

Вернитесь в ваш .xib файл и нажмите "Владелец файла" в документе. В Identity Inspector напишите имя вашего .swift файла как имя пользовательского класса.

enter image description here

Добавить пользовательский код просмотра

Замените содержимое файла ReusableCustomView.swift следующим кодом:

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 файла.

Подключить выходные и действия

Подключите выходы и действия, перетащив управление с метки и кнопки в маке xib на быстрый код пользовательского вида.

Использование пользовательского вида

Теперь ваш пользовательский вид завершен. Все, что вам нужно сделать, это добавить UIView везде, где вы хотите, в свою основную раскадровку. Задайте имя класса вида ReusableCustomView в Identity Inspector.

enter image description here

Ответ 3

Ответ для диспетчеров просмотров, не просмотры:

Существует более простой способ загрузить xib из раскадровки. Скажем, ваш контроллер имеет тип MyClassController, который наследует от UIViewController.

Вы добавляете UIViewController с помощью IB в свою раскадровку; измените тип класса на MyClassController. Удалите представление, которое было автоматически добавлено в раскадровку.

Убедитесь, что XIB, который вы хотите вызвать, называется MyClassController.xib.

Когда класс будет создан при загрузке раскадровки, xib будет автоматически загружен. Причина этого связана с реализацией по умолчанию UIViewController, которая вызывает XIB с именем класса.

Ответ 4

На самом деле это не ответ, но я думаю, что полезно использовать этот подход.

Objective-C

  • Импорт CustomViewWithXib.h и CustomViewWithXib.m на ваш проект
  • Создайте пользовательские файлы с тем же именем (.h/.m/ .xib)
  • Наследовать свой пользовательский класс из CustomViewWiспасибоib

Swift

  • Импортируйте CustomViewWithXib.swift в свой проект
  • Создайте пользовательские файлы с тем же именем (.swift и .xib)
  • Наследовать свой пользовательский класс из CustomViewWiспасибоib

Дополнительно:

  1. Перейдите к вашему xib файлу, задайте владельцу имя вашего класса, если вы необходимо подключить некоторые элементы (подробнее см. в разделе Make файл Swift - владелец ответа @Suragch)

Все, теперь вы можете добавить свой собственный вид в свою раскадровку, и это будет показано:)

CustomViewWiспасибоib.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

CustomViewWiспасибоib.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

CustomViewWiспасибоib.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)
    }
}

Здесь вы можете найти несколько примеров .

Надеюсь, что это поможет.