Загрузить представление из внешнего xib-файла в раскадровке
Я хочу использовать представление в нескольких viewcontrollers в раскадровке. Таким образом, я думал о разработке представления во внешнем xib, поэтому изменения отражаются в каждом viewcontroller. Но как можно загрузить представление из внешнего xib в раскадровке и возможно ли это вообще? Если это не так, то какие другие альтернативы доступны, чтобы удовлетворить ситуацию об этом?
8 ответов:
мой полный пример здесь, но я приведу резюме ниже.
планировка
добавить a .быстрый и. xib файл каждый с тем же именем для вашего проекта. Этот.xib-файл содержит ваш пользовательский макет представления (предпочтительно с использованием ограничений auto layout).
сделайте swift-файл владельцем xib-файла.
добавьте следующий код .быстрый файл и подключение выходы и действия со стороны .файл xib.
import UIKit 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) 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 } }
использовать
используйте свой пользовательский вид в любом месте раскадровки. Просто добавьте
UIView
и установите имя класса для вашего пользовательского имени класса.
предполагая, что вы создали xib, который вы хотите использовать:
1) Создайте пользовательский подкласс UIView (вы можете перейти в File -> New -> File... - >Какао Сенсорный Класс. Убедитесь, что" подкласс: "- это"UIView").
2) Добавьте представление, основанное на xib в качестве подвида к этому представлению при инициализации.
В Obj-C
-(id)initWithCoder:(NSCoder *)aDecoder{ if (self = [super initWithCoder:aDecoder]) { UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename" owner:self options:nil] objectAtIndex:0]; xibView.frame = self.bounds; xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview: xibView]; } return self; }
В Swift 2
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView xibView.frame = self.bounds xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] self.addSubview(xibView) }
В Swift 3
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView xibView.frame = self.bounds xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.addSubview(xibView) }
3) где вы хотите использовать его в своей раскадровка, добавьте UIView, как обычно, выберите недавно добавленное представление, перейдите к инспектору идентификации (третий значок в правом верхнем углу, который выглядит как прямоугольник с линиями в нем) и введите имя вашего подкласса в качестве "класса" в разделе "пользовательский класс".
пока подход Кристофера Свейси был лучший подход, который я нашел. Я попросила двух старших разработчиков в моей команде об этом и один из них был идеальным решением! Он удовлетворяет все проблемы, которые Кристофер Свейси так красноречиво рассматривал, и он не требует шаблонного кода подкласса(моя главная забота о его подходе). Есть один попался, но кроме этого он довольно интуитивно понятен и прост в реализации.
- создайте пользовательский класс UIView в a .swift-файл для управления вашим xib. то есть
MyCustomClass.swift
- создать .xib файл и стиль его, как вы хотите. то есть
MyCustomClass.xib
- установить
File's Owner
часть .xib файл будет вашим пользовательским классом (MyCustomClass
)- попался: оставить
class
значение (подidentity Inspector
) для представления в .xib файл пустой. таким образом, ваш пользовательский вид не будет иметь определенного класса, но он будет иметь указан владелец файла.- подключите свои розетки, как вы обычно используете
Assistant Editor
.
- Примечание: Если вы посмотрите на
Connections Inspector
вы заметите, что ваши ссылки на СМИ не ссылаться на ваш пользовательский класс (т. е.MyCustomClass
), а ссылкаFile's Owner
. Так какFile's Owner
определяется как ваш пользовательский класс, розетки будут подключаться и работать должным образом.- убедитесь, что ваш пользовательский класс имеет @IBDesignable перед классом заявление.
- сделайте свой пользовательский класс соответствующим
NibLoadable
протокол, указанный ниже.
- Примечание: Если ваш пользовательский класс
.swift
имя файла отличается от вашего.xib
имя файла, а затем выберитеnibName
свойство должно быть именем вашего .- реализовать
required init?(coder aDecoder: NSCoder)
иoverride init(frame: CGRect)
называтьsetupFromNib()
как в примере ниже.- добавить UIView к нужной раскадровке и установить класс, чтобы быть вашим обычаем имя класса (т. е.
MyCustomClass
).- смотреть IBDesignable в действии, как он рисует ваш .xib в раскадровке со всем этим, страх и удивление.
вот протокол, на который вы хотите ссылаться:
public protocol NibLoadable { static var nibName: String { get } } public extension NibLoadable where Self: UIView { public static var nibName: String { return String(describing: Self.self) // defaults to the name of the class implementing this protocol. } public static var nib: UINib { let bundle = Bundle(for: Self.self) return UINib(nibName: Self.nibName, bundle: bundle) } func setupFromNib() { guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") } addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true } }
а вот пример
MyCustomClass
, который реализует протокол (с указанием .xib файл с именемMyCustomClass.xib
):@IBDesignable class MyCustomClass: UIView, NibLoadable { @IBOutlet weak var myLabel: UILabel! required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupFromNib() } override init(frame: CGRect) { super.init(frame: frame) setupFromNib() } }
Примечание: Если вы пропустите Gotcha и установите
class
значение внутри вашего .xib-файл будет вашим пользовательским классом, затем он не будет рисовать в раскадровке, и вы получитеEXC_BAD_ACCESS
ошибка при запуске приложения, потому что он застревает в бесконечном цикле пытается инициализировать класс из пера с помощьюinit?(coder aDecoder: NSCoder)
метод, который затем называетSelf.nib.instantiate
и называетinit
снова.
Я всегда находил решение "добавить его в качестве подвида" неудовлетворительным, видя, как он завинчивается с (1) autolayout, (2)
@IBInspectable
и (3) розетки. Вместо этого, позвольте мне познакомить вас с магиейawakeAfter:
, anNSObject
метод.
awakeAfter
позволяет поменять объект на самом деле проснулся от пера/раскадровки с другим объектом полностью. это объект после этого положен через процесс оводнения, имеетawakeFromNib
вызывается на нем, добавляется в виде представления и т. д.мы можем использовать это в подклассе "картонный вырез" нашего представления, единственной целью которого будет загрузка представления из пера и возврат его для использования в раскадровке. Встраиваемый подкласс затем указывается в инспекторе идентификации представления раскадровки, а не в исходном классе. На самом деле это не обязательно должен быть подкласс, чтобы это работало, но сделать его подклассом-это то, что позволяет IB видеть любые свойства IBInspectable/IBOutlet.
Примечание: класс, установленный в представлении в файле NIB, остается прежним. Вложимый подкласс-это только используется в раскадровке. Подкласс не может быть использован для создания экземпляра представления в коде,поэтому он не должен иметь никакой дополнительной логики. Он должен только содержат
awakeAfter
крюк.class MyCustomEmbeddableView: MyCustomView { override func awakeAfter(using aDecoder: NSCoder) -> Any? { return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any } }
⚠️ один существенный недостаток здесь заключается в том, что если вы определяете ограничения ширины, высоты или соотношения сторон в раскадровке, которые не относятся к другому виду, то они имеют для копирования вручную. Ограничения, которые связывают два представления, устанавливаются на ближайшем общем предке, а представления пробуждаются из раскадровки изнутри, поэтому к тому времени, когда эти ограничения гидратируются на супервизоре, своп уже произошел. Ограничения, которые связаны только с рассматриваемым представлением, устанавливаются непосредственно на этом представлении и, таким образом, отбрасываются при выполнении подкачки, если они не скопированы.
обратите внимание, что то, что происходит здесь установлены ограничения на вид в раскадровке копируются в новый экземпляр view, который уже может иметь собственные ограничения, определенные в его файле nib. На них это никак не влияет.
class MyCustomEmbeddableView: MyCustomView { override func awakeAfter(using aDecoder: NSCoder) -> Any? { let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! for constraint in constraints { if constraint.secondItem != nil { newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)) } else { newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant)) } } return newView as Any } }
instantiateViewFromNib
является типобезопасным расширением дляUIView
. Все, что он делает, это цикл через объекты пера, пока он не найдет тот, который соответствует типу. Обратите внимание, что общий тип-это возвращение значение, поэтому тип должен быть указан при вызове сайт.extension UIView { public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? { if let objects = bundle.loadNibNamed(nibName, owner: nil) { for object in objects { if let object = object as? T { return object } } } return nil } }
лучшее решение в настоящее время-просто использовать пользовательский контроллер представления с его представлением, определенным в xib, и просто удалить свойство "view", которое xcode создает внутри раскадровки при добавлении к нему vc ( не забудьте установить имя пользовательского класса).
Это заставит среду выполнения автоматически искать xib и загружать его. Вы можете использовать этот трюк для любого вида представлений контейнера или представления содержимого.
Я думаю о
alternative
использоватьXIB views
использоватьView Controller
в отдельной раскадровки.затем в основной раскадровке вместо пользовательского вида используйте
container view
СEmbed Segue
иStoryboardReference
этой настраиваемое представление-контроллер какой вид должен быть помещен внутри другого вида в основной раскадровке.тут можно настройка делегирования и связь между этим встроенным ViewController и контроллером основного вида через подготовка к segue. Этот подход разные затем отображение UIView, но гораздо проще и эффективнее (с точки зрения программирования) может быть использовано для достижения той же цели, т. е. иметь многоразовый пользовательский вид, который виден в основной раскадровке
дополнительным преимуществом является то, что вы можете реализовать свою логику в классе CustomViewController и там настроить все делегирование и просмотр подготовки без создания отдельных (сложнее найти в проекте) классы контроллера, и без размещения шаблонного кода в главном UIViewController с помощью компонента. Я думаю, что это хорошо для многоразовых компонентов ex. Компонент музыкального проигрывателя (виджет, подобный), который можно встраивать в другие представления.
Это решение может быть использовано, даже если ваш класс не имеет того же имени, что и XIB. Например, если у вас есть базовый класс контроллера вида controllerA, который имеет имя xib controllerA.xib и вы подкласс этого с controllerB и хотите создать экземпляр controllerB в раскадровке, то вы можете:
- создать представление-контроллер в раскадровке
- установите класс контроллера в controllerB
- удалить вид controllerB в раскадровке
- переопределить вид нагрузки в controllerA:
*
- (void) loadView { //according to the documentation, if a nibName was passed in initWithNibName or //this controller was created from a storyboard (and the controller has a view), then nibname will be set //else it will be nil if (self.nibName) { //a nib was specified, respect that [super loadView]; } else { //if no nib name, first try a nib which would have the same name as the class //if that fails, force to load from the base class nib //this is convenient for including a subclass of this controller //in a storyboard NSString *className = NSStringFromClass([self class]); NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"]; UINib *nib ; if (pathToNIB) { nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]]; } else { //force to load from nib so that all subclass will have the correct xib //this is convenient for including a subclass //in a storyboard nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]]; } self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0]; } }
решение для Objective-C в соответствии с шагами, описанными в ответ Бена патча.
использовать расширение для UIView:
@implementation UIView (NibLoadable) - (UIView*)loadFromNib { UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject]; xibView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:xibView]; [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES; [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES; [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES; [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES; return xibView; } @end
создать файлы
MyView.h
,MyView.m
иMyView.xib
.сначала подготовить свой
MyView.xib
как ответ Бена патча так говорит set classMyView
для владельца файла вместо основного вида внутри этого XIB.
MyView.h
:#import <UIKit/UIKit.h> IB_DESIGNABLE @interface MyView : UIView @property (nonatomic, weak) IBOutlet UIView* someSubview; @end
MyView.m
:#import "MyView.h" #import "UIView+NibLoadable.h" @implementation MyView #pragma mark - Initializers - (id)init { self = [super init]; if (self) { [self loadFromNib]; [self internalInit]; } return self; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self loadFromNib]; [self internalInit]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self loadFromNib]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self internalInit]; } - (void)internalInit { // Custom initialization. } @end
а позже просто создайте свой вид программно:
MyView* view = [[MyView alloc] init];
предупреждение! предварительный просмотр этого представления не будет отображаться в раскадровке, если вы используете расширение WatchKit из-за этой ошибки в Xcode >= 9.2: https://forums.developer.apple.com/thread/95616