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

<p>я относительно новичок в мире xcode/ios; я сделал несколько приложений на основе раскадровок приличного размера, но я никогда не порезал себе зубы в целом. я хочу использовать те же инструменты для сцен, чтобы спроектировать/разметить многократно используемый вид/элемент управления. поэтому я создал свой первый xib для подкласса view и нарисовал его:</p><p><img src="https://i.stack.imgur.com/uyzhq.png" alt="enter image description here"></p><p>у меня подключены розетки и настроены ограничения, как я привык делать в раскадровке. я установил класс моего <code>file owner</code> на класс моего подкласса <code>uiview</code>. поэтому я предполагаю, что могу создать экземпляр этого подкласса с помощью некоторого api, и он будет настроен/подключен, как показано.</p><p>теперь вернувшись в мою раскадровку, я хочу внедрить/повторно использовать это. я делаю это в ячейке прототипа табличного представления:</p><p><img src="https://i.stack.imgur.com/zjpf0.png" alt="enter image description here"></p><p>у меня есть вид. я установил класс этого для моего подкласса. я создал выход для него, чтобы я мог им манипулировать.</p><p>вопрос за 64 $ - где/как я могу указать, что недостаточно просто поместить туда пустой/ненастроенный экземпляр моего подкласса представления, но использовать .xib, который я создал, чтобы настроить/создать его экземпляр? было бы здорово, если бы в xcode6 я мог просто ввести xib-файл для использования с данным uiview, но я не вижу поля для этого, поэтому я предполагаю, что где-то нужно что-то делать в коде.</p><p><em> (я вижу другие вопросы, подобные этому, на so, но не нашел ни одного вопроса, требующего только эту часть головоломки или не имеющую актуальности с xcode6/2015) </em></p><h2>обновление</h2><p>я могу добиться этого, реализовав <code>awakefromnib</code> в ячейке таблицы следующим образом:</p><pre><code>- (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; } </code></pre><p>это так легко, как это получается тогда? или я слишком усердно работаю для этого?</p>

<p>ты почти там. вам нужно переопределить initwithcoder в вашем пользовательском классе, которому вы назначили представление.</p><pre><code>- (id)initwithcoder:(nscoder *)adecoder { if ((self = [super initwithcoder:adecoder])) { [self addsubview:[[[nsbundle mainbundle] loadnibnamed:@"viewyoucreated" owner:self options:nil] objectatindex:0]]; } return self; } </code></pre><p>как только это будет сделано, storyboard будет знать, как загрузить xib внутри этого uiview.</p><p>вот более подробное объяснение:</p><p>вот как ваша <code>uiviewcontroller</code> выглядит на вашей доске рассказов: <img src="https://i.stack.imgur.com/aayfj.png" alt="enter image description here"></p><p>синее пространство - это, по сути, uiview, который будет «держать» ваш xib. </p><p>это твой хиб: </p><p><img src="https://i.stack.imgur.com/5ohlo.png" alt="enter image description here"></p><p>там есть действие, связанное с кнопкой, которая напечатает текст. </p><p>и это конечный результат: </p><p><img src="https://i.stack.imgur.com/uxo7l.png" alt="enter image description here"></p><p>разница между первым clickme и вторым заключается в том, что первый был добавлен в <code>uiviewcontroller</code> с помощью <code>storyboard</code>. второй был добавлен с использованием кода.</p>

<p>вам необходимо реализовать <code>awakeafterusingcoder:</code> в своем подклассе <code>uiview</code>. этот метод позволяет вам заменять декодированный объект (из раскадровки) другим объектом (из вашей многоразовой xib), например так:</p><pre><code>- (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; } </code></pre><p>здесь довольно хорошая рецензия <a href="http://cocoanuts.mobi/2014/03/26/reusable/">здесь</a> обсуждается этот метод и несколько альтернатив.</p><p>кроме того, если вы добавите <code>ib\_designable</code> в ваше объявление @interface и предоставите метод <code>initwithframe:</code>, вы можете получить предварительный просмотр во время разработки для работы в ib (требуется xcode 6!):</p><pre><code>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; } </code></pre>

<p>довольно крутой и многократно используемый способ создания этого interface builder и swift 4:</p><ol> <li><p>создайте новый класс примерно так:</p> <pre><code>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) } } </code></pre></li> <li><p>в свою раскадровку добавьте uiview, который будет действовать как контейнер для xib. дайте ему имя класса <code>xibview</code>:</p> <p><img src="https://i.stack.imgur.com/bylhz.png" width="260"></p></li> <li><p>в инспекторе свойств этой новой <code>xibview</code> задайте имя вашего .xib (без расширения файла) в поле ibinspectable:</p> <p><img src="https://i.stack.imgur.com/jvph4.png" width="260"></p></li> <li><p>добавьте новое представление xib в свой проект и в инспекторе свойств установите для «владельца файла» в xib значение <code>xibview</code> (убедитесь, что вы установили «владелец файла» только для своего пользовательского класса, не делайте подклассы представления содержимого или его произойдет сбой) и снова установите поле ibinspectable:</p> <p><img src="https://i.stack.imgur.com/jvph4.png" width="260"></p></li> </ol><p><strong>стоит заметить:</strong> предполагается, что вы сопоставляете фрейм .xib с его контейнером. если у вас нет или вам нужно изменить размер, вам нужно добавить некоторые программные ограничения или изменить рамку подпредставления так, чтобы она подходила. я использую <a href="/questions/tagged/snapkit">snapkit</a> , чтобы упростить задачу:</p><pre><code>xibview.snp\_makeconstraints(closure: { (make) -> void in make.edges.equalto(self) }) </code></pre><p><strong>бонусные очки</strong></p><p>предположительно, вы можете использовать <code>prepareforinterfacebuilder()</code>, чтобы сделать эти повторно используемые представления видимыми в интерфейсном разработчике, но мне не повезло. <a href="https://medium.com/zenchef-tech-and-product/how-to-visualize-reusable-xibs-in-storyboards-using-ibdesignable-c0488c7f525d">этот блог</a> предлагает добавить свойство <code>contentview</code> и вызвать следующее:</p><pre><code>override func prepareforinterfacebuilder() { super.prepareforinterfacebuilder() xibsetup() contentview?.prepareforinterfacebuilder() } </code></pre>

<ul> <li>создать xib-файл <code>file > new > new file > ios > user interface > view</code></li> <li>создайте пользовательский класс uiview <code>file > new > new file > ios > source > cocoatouch</code></li> <li>присвойте идентификатор файла xib пользовательскому классу представления</li> <li>в viewdidload контроллера представления инициализируйте xib и связанный с ней файл, используя <code>loadnibnamed:</code> для <code>nsbundle.mainbundle</code>, и первое возвращенное представление может быть добавлено как подпредставление self.view.</li> <li><p>пользовательский вид, загруженный из пера, можно сохранить в свойстве для установки фрейма в <code>viewdidlayoutsubviews</code>. просто установите фрейм в кадр <code>self.view</code>, если вам не нужно сделать его меньше, чем <code>self.view</code>.</p> <pre><code>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) { } } </code></pre></li> <li><p>кроме того, если вы хотите вернуться из xib к своему экземпляру <code>uiviewcontroller</code>, тогда создайте слабое свойство в классе пользовательского представления.</p> <pre><code>class myview: uiview { var buttonhandler:((sender:uibutton)->())! @ibaction func buttontapped(sender: uibutton) { buttonhandler(sender:sender) } } </code></pre></li> </ul><p>вот проект на <a href="https://github.com/philosopherdog/add\_xib\_to\_storyboard.git">github</a> </p>

<p>вам просто нужно перетащить <code>uiview</code> в свой ib, вывести его и установить</p><pre><code>youruiviewclass *yourview = [[[nsbundle mainbundle] loadnibnamed:@"youruiviewclass" owner:self options:nil] firstobject]; [self.view addsubview:yourview] </code></pre><p><img src="https://i.stack.imgur.com/kqv1i.png" alt="enter image description here"></p><h1>шаг</h1><ol> <li>добавить новый файл => пользовательский интерфейс => uiview </li> <li>установить пользовательский класс - youruiviewclass</li> <li>установите идентификатор восстановления - youruiviewclass</li> <li><code>youruiviewclass *yourview = [[[nsbundle mainbundle] loadnibnamed:@"youruiviewclass" owner:self options:nil] firstobject]; [self.view addsubview:yourview]</code></li> </ol><p>теперь вы можете настроить вид, как вы хотите.</p>

<p>чуть более быстрая версия идеи @brandonscript с ранним возвратом:</p><pre><code>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]) } } </code></pre>

<p>я использовал этот фрагмент кода в течение многих лет. если вы планируете иметь пользовательские представления классов в вашей xib, просто поместите это в файл .m вашего пользовательского класса.</p><p>в качестве побочного эффекта это приводит к вызову awakefromnib, так что вы можете оставить там весь свой код init/setup.</p><pre><code>- (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; } </code></pre>

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

<p>это было какое-то время, и я видел множество ответов. я недавно пересмотрел его, потому что я только что использовал встраивание uiviewcontroller. это работает до тех пор, пока вы не захотите поместить что-либо в «элемент, повторяющийся во время выполнения» (например, uicollectionviewcell или uitableviewcell). ссылка, предоставленная @tomswift, привела меня к тому, что</p><p>a) вместо того чтобы сделать родительский класс представления пользовательским типом класса, сделайте fileowner целевым классом (в моем примере, cyclecontrolsbar)</p><p>б) любая ссылка на выход/действие вложенных виджетов переходит к этому</p><p>c) реализуйте этот простой метод на cyclecontrolsbar:</p><pre><code>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 } } </code></pre><p>работает как шарм и намного проще, чем другие подходы.</p>