it-swarm.com.ru

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

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

enter image description here

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

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

enter image description here

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

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

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

Обновление

Я могу добиться этого, реализовав 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's 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;
}

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

41
Travis Griggs

Ты почти там. Вам нужно переопределить 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. 

Это твой хиб: 

enter image description here

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

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

enter image description here

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

26
Segev

Вам необходимо реализовать 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;
}
16
TomSwift

Довольно крутой и многократно используемый способ создания этого Interface Builder и Swift 4:

  1. Создайте новый класс примерно так:

    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 view = xib.first as? UIView else { return }    
            self.addSubview(view)
        }
    
    }
    
  2. В свою раскадровку добавьте UIView, который будет действовать как контейнер для Xib. Дайте ему имя класса XibView:

  3. В инспекторе свойств этой новой XibView задайте имя вашего .xib (без расширения файла) в поле IBInspectable:

  4. Добавьте новое представление Xib в свой проект и в инспекторе свойств установите для «Владельца файла» в Xib значение XibView (убедитесь, что вы установили «Владелец файла» только для своего пользовательского класса, НЕ делайте подклассы представления содержимого или его произойдет сбой) и снова установите поле IBInspectable:

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

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

Бонусные очки

Предположительно, вы можете использовать prepareForInterfaceBuilder(), чтобы сделать эти повторно используемые представления видимыми в Интерфейсном Разработчике, но мне не повезло. Этот блог предлагает добавить свойство contentView и вызвать следующее:

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    xibSetup()
    contentView?.prepareForInterfaceBuilder()
}
15
brandonscript
  • Создать XIB-файл File > New > New File > iOS > User Interface > View
  • Создайте пользовательский класс UIView File > New > New File > iOS > Source > CocoaTouch
  • Присвойте идентификатор файла XIB пользовательскому классу представления
  • В viewDidLoad контроллера представления инициализируйте xib и связанный с ней файл, используя loadNibNamed: для NSBundle.mainBundle, и первое возвращенное представление может быть добавлено как подпредставление self.view.
  • Пользовательский вид, загруженный из пера, можно сохранить в свойстве для установки фрейма в 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

2
smileBot

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

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

enter image description here

Шаг

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

Теперь вы можете настроить вид, как вы хотите.

2
Yuyutsu

Чуть более быстрая версия идеи @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])
    }

}
1
Sasha Prokhorenko

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

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

- (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;
}
1
alexgophermix

Хотя я не рекомендую путь, по которому вы идете, это можно сделать, разместив «представление встроенного контроллера представления» там, где вы хотите, чтобы оно появилось.

Вставьте контроллер представления, который содержит одно представление - представление, которое вы хотите использовать повторно.

0
Dustin

Это было какое-то время, и я видел множество ответов. Я недавно пересмотрел его, потому что я только что использовал встраивание UIViewController. Это работает до тех пор, пока вы не захотите поместить что-либо в «элемент, повторяющийся во время выполнения» (например, UICollectionViewCell или UITableViewCell). Ссылка, предоставленная @TomSwift, привела меня к тому, что

A) Вместо того чтобы сделать родительский класс представления пользовательским типом класса, сделайте FileOwner целевым классом (в моем примере, CycleControlsBar)

Б) Любая ссылка на выход/действие вложенных виджетов переходит к этому

C) Реализуйте этот простой метод на CycleControlsBar:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
        self.addSubview(container)
        container.constrainToSuperview() // left as an excise for the student
    }
}

Работает как шарм и намного проще, чем другие подходы.

0
Travis Griggs