Правильний спосіб завантаження Nib для підкласу UIView


98

Мені відомо, що це питання було задано раніше, але відповіді суперечать, і я плутаюся, тому, будь ласка, не спалахуйте мене.

Я хочу мати UIViewпідклас багаторазового використання протягом усього додатка. Я хочу описати інтерфейс за допомогою файлу nib.

Тепер скажімо, що це перегляд індикатора завантаження з індикатором активності. Мені б хотілося, щоб на якійсь події створити цей погляд і анімувати погляд контролера. Я міг би описувати інтерфейс перегляду без проблем програмно, створюючи елементи програмно та встановлюючи їх кадр всередині методу init тощо.

Як я можу це зробити за допомогою ручки? Збереження розміру, заданого в конструкторі інтерфейсів, без встановлення кадру.

Мені вдалося зробити це так, але я впевнений, що це неправильно (це лише вид з вибирачем у ньому):

 - (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.

EDIT: Я хочу створити цей спеціальний вигляд у контролері перегляду та мати доступ для управління елементами подання. Я не хочу нового контролера перегляду.

Дякую

EDIT: Я не знаю, чи це найкраща практика, я впевнений, що це не так, але ось як я це зробив:

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) {
                        }];

Правильної практики все-таки хотілося

Чи слід було це зробити за допомогою контролера перегляду та init з ім'ям ручки? Чи повинен я встановити ручку в якомусь методі ініціалізації UIView у коді? Або це те, що я зробив нормально?


Але чи не перший рядок коду майже такий самий, як той, який ви використовували в оригінальному запитанні initWithDataSource? У будь-якому випадку це буде спрацьовувати, навіть якщо ви дасте власнику "Nil".
Ракеш

Варто зазначити, що в ці дні в 99,99999% випадків просто використовуються перегляди контейнерів , які зараз використовуються скрізь і завжди в iOS. стаття як про те
Fattie

збоку зауважте, що ви можете замінити [NSString stringWithFormat: @ "% @", [клас FSASelectView]] на NSStringFromClass ([клас FSASelectView])
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Власник файлу" ви б натискали на "Власник файлу" та встановлювали власника файлу на "Ваш клас" . Тоді фактично у YourClass.h ви мали б властивості IBOutlet. В TopArea.xib, ви можете перетягувати елементи управління в цих точках.

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


не існує initWithNibName: метод для UIView, це просто для UIViewController
meronix

Це для контролера перегляду, я хочу використовувати UIView і мати контролер, який створює його, щоб ним володіти.
Адам Уейт

Вибачте, я скопіював неправильний код у поспіху, я оновив відповідь, будь ласка, подивіться.
Премсурай

Я збираюся відзначити це правильно. Я не знаю, чи це найкраща практика, але це працює.
Адам Вейт

@Joe Blow Вибачте за нерівність, але навіщо це робити так: "Не забувайте, що в TopArea.xib вам, можливо, доведеться натиснути на сам вид і перетягнути його до якоїсь розетки, щоб ви мали контроль над цим , якщо необхідно." Чи можете ви замість цього просто використовувати myViewObject для доступу до базового перегляду, оскільки це сам UIView? Чому потрібна власність?
користувач1686342

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];


1
Що стосується iOS 9, це єдине рішення, перелічене на цій сторінці, яке фактично працює для мене (інші рішення спричиняють збій).
lifjoy

Зауважте лише, що цей підхід не сумісний із завантаженням вашого власного перегляду з існуючого XIB, оскільки він не вимагає -init. Замість цього він подзвонить initWithFrame:і -awakeFromNib. Якщо ви будете писати той самий код, щоб завантажити свій погляд, як у -initметоді, ви введете нескінченний цикл.
Даст

25

Виконайте наступні дії

  1. Створіть клас під назвою MyView .h / .m типу UIView.
  2. Створіть однойменний xib MyView.xib.
  3. Тепер змініть клас власника файлів на UIViewControllerз 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 Я зробив демо - грейфер тут


Чому люди голосують за це, неправильно? Я ще не пробував, але це виглядає так, що я не буду робити те, що я хочу.
Адам Уейт

Я проголосував, але тому я неправильно прочитав вашу відповідь (думав, що ви призначили підклас UIView як власника файлу). Вибач за це.
Ракеш

2
У файлі Xib ігноруйте власника файлів. Тоді ви можете уникнути необхідності створення UIViewController, просто виконавши MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
Блискавка

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

У Свіфті:

Наприклад, ім'я вашого користувацького класу - це 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це подобається:

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

Перейменуйте ViewTo 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 не є хорошою ідеєю, оскільки властивість перегляду контролера буде змінена, і коли ви захочете отримати доступ до вихідного перегляду, ви не отримаєте мати посилання на нього.

EDIT: Ви зіткнетеся з цією проблемою, якщо ви встановите свій новий xib з власником файлу як того самого основного UIViewControllerкласу та прив’язуєте властивість перегляду до нового виду xib.

тобто;

  • YourMainViewController - керує - mainView
  • CustomView - потрібно завантажувати з xib як і коли потрібно.

Нижче наведений код викличе плутанину пізніше, якщо ви напишете його всередині перегляду зробив завантаження YourMainViewController. Це тому, що self.viewз цього моменту буде посилатися на ваш власний перегляд

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}

Добре, так, в основному, найкраща практика говорить про те, що кожен UIView повинен мати пов'язаний контролер?
Адам Уейт

Не обов'язково. Але при завантаженні подання з окремого xib це було б безпроблемним способом. Документи Apple-до-IOS5 кажуть, що один контролер перегляду не повинен використовуватися для управління більш ніж однією сценою (xib), і навпаки.
Ракеш

Насправді xib - це лише розмір тривожного перегляду
Адам Уайт

Я особисто відчуваю, від складності коду / xib залежить, чи потрібно створити новий контролер чи ні. Якщо ви можете успішно впоратися з проблемами, викликаними, і якщо ви не дивитесь на майбутні модифікації, це не проблема. Але створення окремого контролера перегляду дозволить вирішити багато проблем для вас. А оскільки у вашому випадку це лише показник активності, ви можете легко використовувати об’єкт UIViewController (як зазначено у відповіді). Я оновлю свою відповідь, щоб зробити це відповідно.
Ракеш

1
Тож я можу малювати інтерфейс у конструкторі інтерфейсів, а не робити це програмно. Проблемно це робити непросто, я просто хочу знати, як підкласити UIView і дати йому підказку! Це не повинно бути важким.
Адам Уейт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.