iPhone: создайте повторно используемый компонент (элемент управления), который содержит несколько компонентов Interface Builder и некоторый код - PullRequest
40 голосов
/ 27 октября 2010

Я хочу создать повторно используемый компонент (пользовательский элемент управления) для iPhone. Он состоит из нескольких стандартных элементов управления, предварительно настроенных на View, и затем некоторого связанного кода. Мои цели:

  1. Я хочу иметь возможность использовать Interface Builder для размещения подвидов в моем пользовательском элементе управления;
  2. Я хочу каким-то образом упаковать все это, чтобы потом можно было довольно легко перетаскивать полученный пользовательский компонент в другие представления без необходимости вручную перепрограммировать несколько выходов и так далее. (Небольшая ручная перемотка - это хорошо, я просто не хочу делать это тоннами.)

Позвольте мне быть более конкретным и конкретно сказать вам, что должен делать мой контроль. В моем приложении мне иногда приходится обращаться к веб-службе для проверки данных, введенных пользователем. В ожидании ответа от веб-службы я хочу отобразить счетчик (индикатор активности). Если веб-службы отвечают кодом успеха, я хочу отобразить галочку «успех». Если веб-служба отвечает кодом ошибки, я хочу отобразить значок ошибки и сообщение об ошибке.

Одноразовый способ сделать это довольно легко: я просто создаю UIView, который содержит UIActivityIndicatorView, два UIImage (одно для значка успеха и одно для значка ошибки) и UILabel для сообщения об ошибке. Вот скриншот с соответствующими частями, отмеченными красным:

alt text

Затем я подключаю части к розеткам и помещаю некоторый код в мой контроллер.

Но как мне упаковать эти фрагменты - код и небольшую коллекцию представлений - чтобы я мог их повторно использовать? Вот несколько вещей, которые я нашел для меня, но они не так хороши:

  • Я могу перетащить коллекцию видов и элементов управления в раздел «Пользовательские объекты» библиотеки; потом, позже, я могу перетащить их обратно на другие виды. Но (а) он забывает, какие изображения были связаны с двумя изображениями UIImage, (б) существует множество ручных переподключений четырех или пяти розеток, и (в), что наиболее важно, это не приводит к коду. (Возможно, есть простой способ подключить код?)
  • Я думаю, я мог бы создать IBPlugin; не уверен, поможет ли это, и это кажется большой работой, а также мне не совсем понятно, работают ли IBPlugins для разработки iPhone.
  • Я подумал: «Хм, с этим связан код, который пахнет контроллером», поэтому я попытался создать собственный контроллер (например, WebServiceValidatorController) со связанным файлом XIB. Это на самом деле кажется многообещающим, но тогда я не могу понять, как в Интерфейсном Разработчике перетащить этот компонент в другие представления. WebServiceValidatorController - это контроллер, а не представление, поэтому я могу перетащить его в окно документа, но не в представление.

У меня такое чувство, что я упускаю что-то очевидное ...

Ответы [ 2 ]

21 голосов
/ 31 января 2011

Вот как я решаю похожую проблему: я хотел:

  • создание многократно используемых автономных «классов модулей виджетов», реализующих сложное представление, построенное из нескольких компонентов UIKit (и других вложенных классов модулей виджетов!). Классы клиентов более высокого уровня этих классов виджетов не заботятся о том, что такое UILabel, что такое UIImageView внутри виджета, а классы клиентов заботятся только о таких понятиях, как «отображение счета» или «показ логотипа команды».

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

  • для пользователей виджетов более высокого уровня, я хотел бы иметь возможность размещать рамку виджета в поле зрения клиента в конструкторе интерфейсов без необходимости разработки пользовательских плагинов для IB и т. Д.

Эти виджеты не являются контроллерами верхнего уровня: поэтому для них нет смысла быть подклассами UIViewController. Также есть совет от Apple, чтобы на одном экране не было более одного ВК. Кроме того, есть много советов и мнений, таких как «MVC! MVC! Вы должны разделить свой View и свой контроль! MVC!» что людям так настоятельно не рекомендуется создавать подклассы UIView или размещать логику приложения в подклассе UIView.

Но я все равно решил это сделать (подкласс UIView). Я бывал в этом районе несколько раз (но все еще новичок в iPhone SDK / UIKit), и я очень чувствителен к уродливому дизайну, который может вызвать проблемы, и, честно говоря, не вижу проблемы с подклассами UIView и добавлением торговых точек и действий. На самом деле есть много преимуществ сделать ваш повторно используемый виджет подклассом UIView, а не на основе UIViewController:

  • Вы можете поместить прямой экземпляр класса виджета в компоновщик построителя интерфейса представления клиента, и фрейм UIView представления виджета будет правильно инициализирован при загрузке класса клиента из xib. Для меня это намного чище, чем поместить «представление заполнителя» в клиента, затем создать экземпляр модуля виджета программно, установить рамку виджета в соответствии с заполнителем, а затем поменять представление заполнителя для представления виджета.

  • Вы можете создать чистый и простой xib-файл для размещения компонентов виджета в конструкторе интерфейса. Владелец файла установлен на ваш класс виджетов. Файл пера содержит корневой (vanilla) UIView, в котором расположен весь графический интерфейс, а затем в методе виджета awakeFromNib: этот вид пера добавляется в сам виджет как подпредставление. Любые корректировки размера кадра могут быть обработаны здесь, полностью в коде виджета, если таковые необходимы. Вот так:

- (void) awakeFromNib
{
    [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil];
    [self addSubview:self.view];
}
  • Виджет самостоятельно инициализирует свой пользовательский интерфейс, используя метод loadNibNamed: owner: options NSBundle в своем собственном методе awakeFromNib, поэтому интеграция виджета в классы клиента является чистой: просто поместите UIView в представление клиента в IB, измените Класс UIView для MyWidgetView, а в init или viewDidLoad клиента установите любые значения по умолчанию на дисплее виджета, используя API виджета, который вы пишете сами (setScore: setTeamImage: и т. Д.)

Мне кажется, что в результирующем коде, по сути, нет разницы между созданием подкласса UIView таким образом для создания многократно используемого "виджета" и использованием UIViewController. В обоих случаях файлы .h содержат членов класса виджетов, выходы, объявления действий и специальные объявления API. В обоих случаях файл .m содержит реализации действий и специальные реализации API. И представление отделено от элемента управления - представление находится в xib-файле!

Итак, вот что я делаю для упаковки сложных многократно используемых виджетов:

  • Сделать класс виджета подклассом UIView
  • Создавайте виджет где угодно, в других представлениях в вашем приложении в IB напрямую, перетаскивая UIView из библиотеки инструментов и изменяя класс на свой «MyWidgetClass».
  • Создайте пользовательский интерфейс вашего виджета в IBв кончике внутри корневого ванильного UIView.
  • в классе awakeFromNib: метод класса виджета, загрузите пользовательский интерфейс виджета из xib и выполните [self addSubview:self.theXibRootView]

  • Codeвиджет, как и UIViewController: розетки, действия, специальные API

Мне было бы интересно услышать о других структурных системах для создания многократно используемых виджетов и о преимуществах этогоподход.

Если виджет должен сообщать о событиях в класс клиента, просто используйте протокол делегата, в котором клиент устанавливает для делегата виджета значение self, как в UIViewController.

12 голосов
/ 28 октября 2010

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

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

Я создаю все IBOutlets в своем классе пользовательского представления и подключаю их там.В этом упражнении я полностью игнорирую «владельца файла».

Теперь, когда мне нужно создать представление (обычно в контроллере как часть цикла while / for, чтобы создать столько их, сколько необходимо), яиспользуйте функциональность NSBundle следующим образом:

- (void)viewDidLoad
{
    CGRect fooBarViewFrame = CGRectMake(0, 0, self.view.bounds.size.width, FOOBARVIEW_HEIGHT);
    for (MBSomeData *data in self.dataArray) {
        FooBarView *fooBarView = [self loadFooBarViewForData:data];
        fooBarView.frame = fooBarViewFrame;
        [self.view addSubview:fooBarView];

        fooBarViewFrame = CGRectOffset(fooBarViewFrame, 0, FOOBARVIEW_HEIGHT);
    }
}

- (FooBarView*)loadFooBarViewForData:(MBSomeData*)data
{
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"FooBarView" owner:self options:nil];
    FooBarView *fooBarView = [topLevelObjects objectAtIndex:0];
    fooBarView.barView.amountInEuro = data.amountInEuro;
    fooBarView.barView.gradientStartColor = data.color1;
    fooBarView.barView.gradientMidColor = data.color2;
    fooBarView.titleLabel.text = data.name;
    fooBarView.titleLabel.textColor = data.nameColor;
    return fooBarView;
}

Обратите внимание, как я установил для владельца nib self - нет никакого вреда, поскольку я ни к чему не подключил «Владельца файла».Единственный ценный результат загрузки этого пера - это его первый элемент - мой взгляд.

Если вы хотите адаптировать этот подход для создания представлений в IB, это довольно просто.Реализуйте загрузку подпредставления в основном пользовательском представлении.Установите рамку подпредставления для границ основного вида, чтобы они были одинакового размера.Основное представление станет контейнером для вашего реального пользовательского представления, а также интерфейсом для внешнего кода - вы открываете только необходимые свойства его подпредставлений, остальное инкапсулируется.Затем вы просто отбрасываете пользовательское представление в IB, настраиваете его как свой класс и используете как обычно.

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