Как вставить пользовательский вид 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;
}
Это так же просто, как потом? Или я слишком много работаю для этого?
Ответы
Ответ 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;
}