Правильный способ загрузки пера для подкласса UIView

98

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

Я хочу иметь многократно используемый UIViewподкласс во всем моем приложении. Я хочу описать интерфейс с помощью файла пера.

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

Как я могу сделать это с помощью пера? Сохранение размера, указанного в построителе интерфейса, без необходимости устанавливать фрейм.

Мне удалось это сделать вот так, но я уверен, что это неправильно (это просто представление с пикером в нем):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Но я перезаписываю себя, и когда я его создаю:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Мне нужно вручную установить рамку, а не забирать ее из IB.

РЕДАКТИРОВАТЬ: я хочу создать это настраиваемое представление в контроллере представления и иметь доступ для управления элементами представления. Мне не нужен новый контроллер представления.

Спасибо

РЕДАКТИРОВАТЬ: Я не знаю, является ли это лучшей практикой, я уверен, что это не так, но я сделал это так:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

Правильная практика все еще нужна

Должно ли это быть сделано с помощью контроллера представления и инициализации с именем пера? Должен ли я установить перо в каком-либо методе инициализации UIView в коде? Или то, что я сделал, нормально?

Адам Уэйт
источник
Но разве первая строка кода не такая же, как та, которую вы использовали в исходном вопросе initWithDataSource? В любом случае это будет работать, даже если вы укажете для владельца значение «Nil».
Rakesh
Стоит отметить, что в наши дни в 99,99999% случаев можно было бы просто использовать представления контейнера , которые теперь используются везде и всегда в iOS. статья с практическими
рекомендациями
обратите внимание, что вы можете заменить [NSString stringWithFormat: @ "% @", [FSASelectView class]] на NSStringFromClass ([FSASelectView class])
naomimichiko

Ответы:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

Я использую это для инициализации имеющихся у меня многоразовых пользовательских представлений.


Обратите внимание, что вы можете использовать "firstObject" в конце, это немного чище. firstObject - удобный метод для NSArray и NSMutableArray.

Вот типичный пример загрузки xib для использования в качестве заголовка таблицы. В вашем файле YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Обычно в TopArea.xibфайле вы нажимаете File Owner и устанавливаете владельцем файла YourClass . Тогда на самом деле в YourClass.h у вас будут свойства IBOutlet. В TopArea.xib, вы можете перетащить элементы управления к этим выходам.

Не забывайте, что TopArea.xibвам, возможно, придется щелкнуть по самому просмотру и перетащить его в какой-либо выход, чтобы вы могли контролировать его, если это необходимо. (Очень полезный совет: когда вы делаете это для строк ячеек таблицы, вы обязательно должны это сделать - вы должны подключить само представление к соответствующему свойству в вашем коде.)

Premsuraj
источник
нет метода initWithNibName: для UIView, он только для UIViewController
meronix
Это для контроллера представления, я хочу использовать UIView и иметь контроллер, который его создает, чтобы владеть им.
Адам Уэйт
Извините, я в спешке скопировал не тот код, я обновил ответ, посмотрите, пожалуйста.
Premsuraj
Я отмечу это правильно. Не знаю, лучше ли это, но работает.
Adam Waite
@Joe Blow Извините за удар, но зачем это нужно делать: "Не забывайте, что в TopArea.xib вам, возможно, придется щелкнуть сам View и перетащить его в какой-нибудь выход, чтобы вы могли его контролировать. , если необходимо." Не могли бы вы вместо этого просто использовать myViewObject для доступа к базовому представлению, поскольку это сам UIView? Зачем нужна недвижимость?
user1686342
39

Если вы хотите, чтобы вы CustomViewи он не xibзависели от него File's Owner, выполните следующие действия.

  • Оставьте File's Ownerполе пустым.
  • Нажмите на фактическое представление в xibвашем файле CustomViewи установите его Custom Classкак CustomView(имя вашего пользовательского класса представления)
  • Добавьте IBOutletв .hфайл вашего пользовательского представления.
  • В .xibфайле вашего пользовательского представления нажмите на просмотр и войдите Connection Inspector. Здесь вы найдете все свои IBOutlets, которые вы определяете в .hфайле
  • Свяжите их с их соответствующим видом.

в .mфайле вашего CustomViewкласса переопределите initметод следующим образом

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Теперь, когда вы хотите загрузить свой CustomView, используйте следующую строку кода [[CustomView alloc] init];

Ans
источник
1
Что касается iOS 9, это единственное решение, указанное на этой странице, которое действительно работает для меня (другие решения вызывают сбой).
lifjoy
Просто обратите внимание, что этот подход несовместим с загрузкой вашего пользовательского представления из существующего XIB, поскольку он не вызывает -init. Вместо этого он вызовет initWithFrame:и -awakeFromNib. Если вы напишете тот же код для загрузки вашего представления, что и в -initметоде, вы войдете в бесконечный цикл.
Dustt
25

Выполните следующие шаги

  1. Создайте класс MyView типа .h / .m UIView.
  2. Создайте одноименный xib MyView.xib.
  3. Теперь измените класс владельца файла на UIViewControllerfrom NSObjectв xib. См. Изображение ниже введите описание изображения здесь
  4. Подключите представление владельца файла к вашему представлению. См. Изображение ниже введите описание изображения здесь

  5. Измените класс вашего представления на MyView. То же, что и 3.

  6. Элементы управления размещением создают IBOutlets.

Вот код для загрузки представления:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

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

Уточнение :

UIViewControllerиспользуется для загрузки вашего xib и представления, которое на UIViewControllerсамом деле есть, MyViewкоторое вы назначили в MyView xib ..

Demo Я сделал демо - грейфер здесь

iphonic
источник
Почему люди голосуют против этого, неправильно? Я еще не пробовал, но похоже, что он не сделает то, что я хочу.
Adam Waite
Я проголосовал против, но это потому, что я неправильно прочитал ваш ответ (думал, что вы назначаете подкласс UIView в качестве владельца файла). Извини за это.
Rakesh
2
В вашем файле xib игнорируйте владельца файлов. Тогда вы можете избежать создания UIViewController, просто выполнив MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Да, конечно, но это просто еще один способ сделать это.
iphonic
1
Необходимо создать подкласс UIView многократного использования, а не использовать UIViewController для этой цели.
arielyz
11

Отвечая на мой собственный вопрос о 2 или около того годах здесь, но ...

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

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Адам Уэйт
источник
8

В Swift:

Например, имя вашего настраиваемого класса InfoView

Сначала вы создаете файлы InfoView.xibи InfoView.swiftвот так:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Затем установите File's Ownerна UIViewControllerэто нравится:

введите описание изображения здесь

Переименуйте Viewв InfoView:

введите описание изображения здесь

Щелкните правой кнопкой мыши File's Ownerи подключите свое viewполе к InfoView:

введите описание изображения здесь

Убедитесь, что имя класса InfoView:

введите описание изображения здесь

И после этого вы можете без проблем добавить действие к кнопке в своем пользовательском классе:

введите описание изображения здесь

И использование этого настраиваемого класса в вашем MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
вкалит
источник
2

Ну, вы можете либо инициализировать xib с помощью контроллера представления, либо использовать viewController.view. или сделайте так, как вы это сделали. Создание только UIViewподкласса в качестве контроллера UIView- плохая идея.

Если у вас нет выходов из вашего пользовательского представления, вы можете напрямую использовать UIViewControllerкласс для его инициализации.

Обновление: в вашем случае:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

В противном случае вам придется сделать заказ UIViewController(сделать его владельцем файла, чтобы розетки были правильно подключены).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Где бы вы ни хотели добавить представление, которое вы можете использовать.

[parentView addSubview:yCustCon.view];

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

РЕДАКТИРОВАТЬ: вы столкнетесь с этой проблемой, если вы настроили свой новый xib с владельцем файла в качестве того же основного UIViewControllerкласса и связали свойство представления с новым представлением xib.

т.е.

  • YourMainViewController - управляет - mainView
  • CustomView - необходимо загружать из xib по мере необходимости.

Приведенный ниже код вызовет путаницу позже, если вы напишете его внутри view, загрузившего YourMainViewController. Это потому, что self.viewс этого момента будет ссылаться на ваш customview

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Ракеш
источник
Хорошо, в общем, лучшая практика гласит, что каждый UIView должен иметь связанный контроллер?
Adam Waite
Не обязательно. Но при загрузке представления из отдельного xib это был бы беспроблемный способ. В документации Apple до IOS5 говорится, что один контроллер представления не должен использоваться для управления более чем одной сценой (xib) и наоборот.
Rakesh
Xib на самом деле имеет размер только для предупреждения
Адам Уэйт
Я лично считаю, что от сложности кода / xib зависит, нужно ли вам создавать новый контроллер или нет. Если вы можете успешно справиться с возникшими проблемами, и если вы не планируете вносить изменения в будущем, это не проблема. Но создание отдельного контроллера представления решит для вас множество проблем. И поскольку в вашем случае это всего лишь индикатор активности, вы можете легко использовать объект UIViewController (как указано в ответе). Я обновлю свой ответ, чтобы сделать это соответствующим образом.
Ракеш,
1
Поэтому я могу рисовать интерфейс в построителе интерфейсов, а не делать это программно. Это не проблема сделать это программно, я просто хочу знать, как создать подкласс UIView и дать ему перо! Это не должно быть сложно.
Адам Уэйт