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

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

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

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

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

4 ответа

Решение

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

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

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

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

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

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

Я создал новый подклассы класса Objective C UIView названный "MyCustomTimer". В моем .xib Я установил класс Владельца моего Файла как 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 и устанавливает для свойства "Владелец файла" значение self. Мы извлекаем первый объект в массиве, и это корневое представление Nib. Мы добавляем вид как подпредставление и вуаля это на экране.

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

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

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

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

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

Свифт пример

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

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

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

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

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

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

Xib файл

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

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

Свифт файл

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

import UIKit
class ResuableCustomView: UIView {

}

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

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

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

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

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

Убедитесь, что правильно написали имя вашего файла.xib.

Подключите Аутлеты и Акции

Соедините розетки и действия, управляя перетаскиванием от метки и кнопки в макете XIB к быстрому пользовательскому коду представления.

Используйте свой пользовательский вид

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

На момент написания этой статьи у меня были проблемы с добавлением @IBDesignable в код, поэтому представления не отображаются в IB, а появляются при запуске приложения. (На изображении выше я дал представлениям IB цвет фона, чтобы они были видны во время разработки.)

Ответ для просмотра контроллеров, а не просмотров:

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

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

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

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

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

Objective-C

  1. Импортируйте CustomViewWithXib.h и CustomViewWithXib.m в ваш проект
  2. Создайте файлы пользовательского представления с тем же именем (.h /.m /.xib)
  3. Унаследуйте свой пользовательский класс от CustomViewWithXib

стриж

  1. Импортируйте CustomViewWithXib.swift в ваш проект
  2. Создайте файлы пользовательского представления с одинаковыми именами (.swift и.xib)
  3. Унаследуйте свой пользовательский класс от CustomViewWithXib

Необязательный:

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

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

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

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

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

Другие вопросы по тегам