Як створити користувацький клас перегляду iOS і створити екземпляр декількох копій (у IB)?


114

Зараз я створюю додаток, у якому буде кілька таймерів, які в основному все однакові.

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

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

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


Для будь-кого, хто гугла сюди ... ось уже п’ять років з перегляду контейнерів, що надходять на iOS! Перегляди контейнерів - це основна, центральна ідея того, як ви робите все в iOS зараз. Вони неймовірно прості у використанні, і існує чимало відмінних (п'ятирічних!) Навчальних посібників на видах контейнерів навколо, наприклад , приклад
Fattie

2
@JoeBlow За винятком того, що ви не можете використовувати перегляд контейнера в UITableViewCellабо UICollectionViewCell. У моєму випадку мені потрібен невеликий, але досить складний вигляд, який я можу використовувати багато разів для перегляду контролерів та колекцій. І дизайнери продовжують її переробляти, тому я хочу, щоб єдине місце було визначено компонуванням. Значить, куля.
Ешелон

Відповіді:


142

Що ж, щоб відповісти концептуально, ваш таймер, швидше за все, повинен бути підкласом UIViewзамість NSObject.

Щоб створити екземпляр свого таймера в IB, просто перетягніть UIViewйого на подання контролера перегляду та встановіть його клас на ім'я класу вашого таймера.

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

Запам’ятайте #importсвій клас таймера в контролері перегляду.

Редагування: для дизайну ІБ (для створення коду див. Історію редагування)

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

Щоб перевірити це, я створив нову порожню .xibназву "MyCustomTimerView.xib". Потім я додав перегляд, а до цього додав мітку та дві кнопки. Як так:

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

Я створив новий підклас класу об’єктивного класу 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 і встановлює властивість "Власник файлу" для себе. Ми витягуємо перший об’єкт з масиву, і це кореневий вигляд Nib. Ми додаємо подання як підвід, і Voila це на екрані.

Очевидно, що це просто змінює колір фону ярлика при натисканні кнопок, але цей приклад повинен добре допомогти вам.

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

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

Як зауваження, деякі можуть стверджувати, що це порушується, MVCі я дещо згоден. З моєї точки зору, це більш тісно пов'язане зі звичаєм UITableViewCell, який також іноді має бути частиною контролера.

Варто також зазначити, що ця відповідь мала забезпечити дуже конкретне рішення; створіть одну підказку, яку можна кілька разів інстанціювати на одному і тому ж представленні, як викладене на дошці розкадрувань. Наприклад, ви могли легко уявити собі шість таких таймерів, усі на екрані iPad за один раз. Якщо вам потрібно лише вказати подання для контролера перегляду, який повинен використовуватися кілька разів у вашій програмі, тоді рішення, яке надає jyavenard для цього питання , майже напевно є кращим рішенням для вас.


Це насправді здається окремим питанням. Але я думаю, що на нього можна швидко відповісти, тож ось що. Кожен раз, коли ви створюєте нове сповіщення, це нове сповіщення, але якщо вам потрібно мати однакове ім'я, хочете додати щось для їх розмежування, тоді використовуйте userInfoвластивість у сповіщенні@property(nonatomic,copy) NSDictionary *userInfo;
NJones

28
Я отримую нескінченну рекурсію від initWithCoder. Будь-які ідеї?
зникли21

6
Вибачте за відродження старої нитки, переконайтесь File's Owner, що встановлене назви вашого класу вирішило мою нескінченну проблему рекурсії.
themanatuf

20
Ще одна причина нескінченної рекурсії - це встановлення кореневого виду в xib до вашого користувацького класу. Ви не хочете цього робити, залиште це як "UIView" за замовчуванням
XY

11
Є лише одна річ, яка привертає мою увагу щодо такого підходу ... Чи не турбує когось, що 2 Погляди зараз в одному класі? оригінальний файл .h / .m, який успадковує UIView як перший, і [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; додаючи другий UIView ... Це щось виглядає дивно для мене, ніхто більше не відчуває те саме? Якщо так, це щось яблучне, зроблене дизайном? Або рішення хтось знайшов, роблячи це?
SH

137

Швидкий приклад

Оновлено для Xcode 10 та Swift 4

Ось основна прогулянка. Я спочатку багато чого з цього дізнався, переглядаючи цю відеосерію Youtube . Пізніше я оновив свою відповідь на основі цієї статті .

Додайте власні файли перегляду

Наступні два файли формуватимуть ваш власний вид:

  • .xib файл, щоб містити макет
  • .swift файл як UIViewпідклас

Деталі щодо їх додавання наведені нижче.

Файл Xib

Додайте файл .xib до свого проекту (Файл> Створити> Файл ...> Інтерфейс користувача> Переглянути) . Я дзвоню своїм ReusableCustomView.xib.

Створіть макет, який ви хочете мати у своєму власному представленні. Як приклад, я зроблю макет з a UILabelі a UIButton. Добре використовувати автоматичний макет, щоб змінити розмір автоматично, незалежно від розміру, який ви встановите на нього пізніше. (Я використовував Freeform для розміру xib в інспекторі атрибутів, щоб я міг коригувати імітовані показники, але це не потрібно.)

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

Швидкий файл

Додайте у свій проект файл .swift (Файл> Новий> Файл ...> Джерело> Файл Swift) . Це підклас UIViewі я називаю свій ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Зробіть файл Swift власником

Поверніться до свого .xib-файлу та натисніть "Власник файлу" в "Контур документа". В інспектор ідентичності запишіть ім'я вашого файлу .swift як ім'я користувацького класу.

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

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

Замініть 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в Інспекторі ідентичності.

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


31
Важливо не встановлювати клас перегляду Xib як ResuableCustomView, а власника цього Xib. Інакше ви отримаєте помилку.
RealNmae

5
Так, гарне нагадування. Я не знаю, скільки часу я витратив на те, щоб вирішити нескінченні проблеми рекурсії, викликані встановленням виду Xib (а не власника файлу) на ім'я класу.
Сурагч

2
@TomCalmon, я переробив цей проект у Xcode 8 за допомогою Swift 3 і Auto Layout працював нормально для мене.
Сурагч

6
Якщо у кого-небудь ще є проблеми з візуалізацією подання в ІБ, я виявив, що перехід Bundle.mainна Bundle(for: ...)трюк. Мабуть, Bundle.mainнедоступний на час проектування.
crizzis

4
Причиною, чому це не працює з @IBDesignable, є те, що ви не реалізували init(frame:). Після того, як ви додасте це (і втягнете весь код ініціалізації в якусь commonInit()функцію), він працює добре і відображається в ІБ. Детальніше дивіться тут: stackoverflow.com/questions/31265906/…
Димитріс

39

Відповідь для контролерів перегляду, а не переглядів :

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

Ви додаєте UIViewController ІБ у вашій дошці повідомлень; змінити тип класу, щоб бути MyClassController. Видаліть представлення даних, яке було автоматично додано в дошку розкадрувань.

Переконайтесь, що назва XIB, яку ви хочете, називається MyClassController.xib.

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


1
Я весь час надував власні погляди, але ніколи не знав цієї маленької хитрості, щоб завантажити його на раскадровку. Цінується!
MrTristan

38
Питання стосується переглядів, а не контролерів перегляду.
Рікардо

Додано заголовок для уточнення: "Відповідь для контролерів перегляду, а не переглядів:"
nacho4d

1
Не працює в iOS 9.1. Якщо я видаляю створений автоматично (за замовчуванням) перегляд, він виходить з ладу після повернення з виду перегляду VDDidLoad. Я не думаю, що представлення у XIB підключається замість подання за замовчуванням.
Джефф

1
Виняток, який я отримую, це 'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''. Зверніть увагу на ім'я whackoName. Я використовую правильну назву класу для вказівки.
Джефф

16

Це насправді не відповідь, але я думаю, що корисно поділитися таким підходом.

Ціль-С

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

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

Сподіваюся, що це допомагає.


так як ми можемо додати iboutlet для перегляду uicontrols
Amr Angry

1
Привіт @AmrAngry, для підключення поглядів слід виконати 4 крок: Перейдіть до свого файлу xib, у "Власник файлу" встановіть свій клас та перетягніть свої погляди на інтерфейс, торкнувшись дотику "ctrl", я оновлюю оновлення джерело коду з цим прикладом, не соромтеся, якщо у вас виникли питання, сподівайтеся, що це допоможе
HamzaGhazouani

велике спасибі за ваше повторення, я вже це роблю, але я думаю, що моя проблема не в цій частині. моя проблема полягає в тому, що я додаю UIDatePIcker і намагаюся додати цільову дію для цього елемента управління з ViewController для встановлення і події uiControleer змінено значення, але метод ніколи не викликається / запускається
Amr Angry

1
@AmrAngry Я оновлюю приклад, додавши подання вибору, можливо, це допоможе вам :) github.com/HamzaGhazouani/Stackoverflow/tree/master/…
HamzaGhazouani

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