Как создать пользовательский класс представления iOS и создать несколько его экземпляров (в IB)? - PullRequest
102 голосов
/ 12 февраля 2012

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

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

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

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

Ответы [ 4 ]

139 голосов
/ 12 февраля 2012

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

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

enter image description here

Не забудьте #import ваш класс таймера в вашем контроллере представления.

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

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

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

enter image description here

Я создал новый подкласс класса класса 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 для этого вопроса , почти наверняка будет лучшим решением для вас.

117 голосов
/ 30 декабря 2015

Пример Swift

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

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

Добавление файлов пользовательского представления

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

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

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

Xib-файл

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

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

enter image description here

Файл Swift

Добавление файла .swift в ваш проект (Файл> Создать> Файл ...> Исходный код> Файл Swift) .Это подкласс 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 в Инспекторе Идентификации.

enter image description here

39 голосов
/ 18 октября 2013

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

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

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

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

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

15 голосов
/ 12 февраля 2016

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

Objective-C

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

Swift

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

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

Перейдите к файлу 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)
    }
}

Примеры можно найти здесь .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...