Подтвердить что ты не робот

Как вставить пользовательский вид xib в сцену раскадровки?

Я относительно новый в мире XCode/iOS; Я сделал несколько приличных приложений на основе раскадровки, но я ни разу не перерезал себе зубы на всю вещь nib/xib. Я хочу использовать те же инструменты для сцен для проектирования/компоновки многоразового представления/управления. Поэтому я создал свой первый xib для моего подкласса view и нарисовал его:

enter image description here

У меня установлены мои подключения и ограничения, так же, как я привык делать в раскадровке. Я установил класс моего File Owner в класс моего подкласса UIView. Поэтому я предполагаю, что могу создать этот подкласс вида с некоторым API, и он будет настроен/подключен, как показано.

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

enter image description here

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

Вопрос $64: где/как я могу указать, что этого недостаточно, чтобы просто поместить пустой /unconfigured экземпляр моего подкласса вида, но использовать .xib, который я создал для его настройки/создания? Было бы здорово, если бы в XCode6 я мог просто ввести XIB файл для использования для данного UIView, но я не вижу поля для этого, поэтому я предполагаю, что мне нужно что-то сделать в коде где-то.

(Я вижу другие вопросы, подобные этому на SO, но не нашел ни одного запроса этой части головоломки или не обновился с XCode6/2015)

Update

Я могу получить эту работу, выполнив мою ячейку таблицы awakeFromNib следующим образом:

- (void)awakeFromNib
{
    // gather all of the constraints pointing to the uncofigured instance
    NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
        return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
    }]];
    // fetch the fleshed out variant
    ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
    // ape the current placeholder frame
    fromXIB.frame = self.progressControl.frame;
    // now swap them
    [UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
    // recreate all of the constraints, but for the new guy
    for (NSLayoutConstraint *each in progressConstraints) {
        id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
        id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
        [self.contentView addConstraint: constraint];
    }
    // update our outlet
    self.progressControl = fromXIB;
}

Это так же просто, как потом? Или я слишком много работаю для этого?

4b9b3361

Ответ 1

Ты почти там. Вы должны переопределить initWithCoder в своем пользовательском классе, которому вы присвоили представление.

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
    }
    return self; }

После этого StoryBoard будет знать, как загрузить xib внутри этого UIView.

Вот более подробное объяснение:

Вот как выглядит ваш UIViewController на вашей доске объявлений: enter image description here

Синее пространство - это в основном UIView, который "удерживает" ваш xib.

Это ваш xib:

enter image description here

Там есть действие, связанное с кнопкой, на которой будет напечатан некоторый текст.

и это конечный результат:

enter image description here

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

Ответ 2

Вам нужно реализовать awakeAfterUsingCoder: в вашем пользовательском подклассе UIView. Этот метод позволяет обменивать декодированный объект (из раскадровки) на другой объект (из вашего многократного xib), например:

- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
    // without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
    // set the view tag in the MyView xib to be -999 and anything else in the storyboard.
    if ( self.tag == -999 )
    {
        return self;
    }

    // make sure your custom view is the first object in the nib
    MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];

    // copy properties forward from the storyboard-decoded object (self)
    v.frame = self.frame;
    v.autoresizingMask = self.autoresizingMask;
    v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
    v.tag = self.tag;

    // copy any other attribtues you want to set in the storyboard

    // possibly copy any child constraints for width/height

    return v;
}

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

Кроме того, если вы добавите IB_DESIGNABLE в ваше объявление @interface и предоставите метод initWithFrame:, вы можете получить предварительный просмотр времени разработки для работы в IB (требуется Xcode 6!):

IB_DESIGNABLE @interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [[[UINib nibWithNibName: @"MyView"
                            bundle: [NSBundle bundleForClass: [MyView class]]]

             instantiateWithOwner: nil
             options: nil] firstObject];

    self.frame = frame;

    return self;
}

Ответ 3

Довольно крутой и многоразовый способ создания этого интерфейса Builder и Swift 3 (Swift 2 похож, но вам придется модифицировать его самостоятельно):

  • Создайте новый класс следующим образом:

    import Foundation
    import UIKit
    @IBDesignable
    
    class XibView: UIView {
    
        @IBInspectable var xibName: String?
    
        override func awakeFromNib() { 
            guard let name = self.xibName, 
                  let xib = Bundle.main.loadNibNamed(name, owner: self), 
                  let views = xib as? [UIView], views.count > 0 else { return }    
            self.addSubview(views[0] as! UIView)
        }
    }
    
  • В своем раскадровке создайте представление, в котором будет размещен файл .xib. Дайте ему имя класса XibView:

    BYLHz.png

  • В инспекторе свойств нового XibView укажите имя своего .xib(без расширения файла):

    jVph4.png

Следует отметить:. Предполагается, что вы сопоставляете фрейм .xib с контейнером. Если вы этого не сделаете или вам нужно изменить размер, вам нужно будет добавить некоторые программные ограничения или изменить рамки подкадров для соответствия. Я использую snapkit, чтобы упростить задачу:

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})

Ответ 4

  • Создать файл xib File > New > New File > iOS > User Interface > View
  • Создать пользовательский класс UIView File > New > New File > iOS > Source > CocoaTouch
  • Назначьте идентификатор файла xib классу пользовательского вида
  • В представлении ViewDidLoad контроллера представления инициализируется xib и связанный с ним файл с помощью loadNibNamed: on NSBundle.mainBundle, и первое возвращенное представление может быть добавлено как подзаголовок self.view.
  • Пользовательский вид, загруженный из nib, можно сохранить в свойство для установки фрейма в viewDidLayoutSubviews. Просто установите рамку в self.view, если вам не нужно сделать ее меньше self.view.

    class ViewController: UIViewController {
    
        weak var customView: MyView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
            self.view.addSubview(customView)
            addButtonHandlerForCustomView()
        }
    
        private func addButtonHandlerForCustomView() {
            customView.buttonHandler = {
                [weak self] (sender:UIButton) in
                guard let welf = self else {
                    return
                }
                welf.buttonTapped(sender)
            }
        }
    
        override func viewDidLayoutSubviews() {
            self.customView.frame = self.view.frame
        }
    
        private func buttonTapped(button:UIButton) {
    
        }
    }
    
  • Кроме того, если вы хотите поговорить с xib с вашим экземпляром UIViewController, создайте в классе пользовательского вида слабое свойство.

    class MyView: UIView {
    
        var buttonHandler:((sender:UIButton)->())!
    
        @IBAction func buttonTapped(sender: UIButton) {
            buttonHandler(sender:sender)
        }
    }
    

Здесь проект GitHub

Ответ 5

Вам просто нужно перетащить UIView в свой IB и вывести его и установить

yourUIViewClass  *yourView =   [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]

enter image description here

Шаг

  • Добавить новый файл = > Пользовательский интерфейс = > UIView
  • Установить пользовательский класс - yourUIViewClass
  • Установить идентификатор восстановления - yourUIViewClass
  • yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

Теперь вы можете настроить представление по своему усмотрению.

Ответ 6

"Правильный" ответ заключается в том, что вы не собираетесь делать повторно используемые снимки с соответствующими наконечниками. Если подкласс представления является ценным как объект многократного использования, ему редко понадобится наконечник, чтобы идти с ним. Возьмем, к примеру, каждый подкласс вида, предоставленный UIKit. Часть этого мышления является подклассом вида, который на самом деле ценен не будет реализован с использованием nib, который является общим видом в Apple.

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

Возможно, вы захотите использовать "copy paste" для воссоздания одинаковых или похожих представлений вместо создания отдельных наконечников. Я полагаю, что это соответствует тем же требованиям, и это будет держать вас более или менее в соответствии с тем, что делает Apple.

Ответ 7

Немного более осторожная версия идеи @brandonscript с ранним возвратом:

override func awakeFromNib() {

    guard let xibName = xibName,
          let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
          let views = xib as? [UIView] else {
        return
    }

    if views.count > 0 {
        self.addSubview(views[0])
    }

}

Ответ 8

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

В качестве побочного эффекта это приводит к вызову awakeFromNib, поэтому вы можете оставить там весь свой код инициализации/настройки.

- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
    if ([[self subviews] count] == 0) {
        UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
        view.frame = self.frame;
        view.autoresizingMask = self.autoresizingMask;
        view.alpha = self.alpha;
        view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
        return view;
    }
    return self;
}