web-dev-qa-db-ja.com

ストーリーボードシーンにカスタムビューxibを埋め込む方法は?

私はXCode/iOSの世界では比較的新しいです。ストーリーボードベースのアプリをある程度作成しましたが、nib/xibの全体に歯を磨いたことはありませんでした。シーンにも同じツールを使用して、再利用可能なビュー/コントロールを設計/レイアウトしたいと思います。そこで、ビューサブクラス用に史上初のxibを作成し、ペイントしました。

enter image description here

ストーリーボードで行うのと同じように、コンセントを接続して制約を設定します。 File OwnerのクラスをカスタムUIViewサブクラスのクラスに設定します。したがって、このビューサブクラスを何らかのAPIでインスタンス化できると仮定すると、示されているように構成/接続されます。

ストーリーボードに戻って、これを埋め込み/再利用します。私はテーブルビューのプロトタイプセルでそうしています:

enter image description here

私はビューを持っています。そのクラスをサブクラスに設定しました。操作できるように、アウトレットを作成しました。

$ 64の質問は、ビューサブクラスの空の/未構成のインスタンスをそこに置くだけでは不十分であり、作成した.xibを使用して構成/インスタンス化するだけでは不十分であることをどこで/どのように示すのですか? XCode6で、与えられたUIViewに使用するXIBファイルを入力することができればとてもクールですが、それを行うためのフィールドが表示されないので、コードのどこかで何かをしなければならないと思います。

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

これは簡単ですか?それとも私はこれのために一生懸命働いていますか?

42
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はそのUIView内にxibをロードすることを認識します。

詳細な説明は次のとおりです。

これは、ストーリーボードでUIViewControllerがどのように見えるかです。 enter image description here

青いスペースは、基本的にxibを「保持」するUIViewです。

これはあなたのxibです。

enter image description here

いくつかのテキストを印刷するボタンに接続されたアクションがあります。

これが最終結果です:

enter image description here

最初のclickMeと2番目のclickMeの違いは、最初のclickMeがUIViewControllerを使用してStoryBoardに追加されたことです。 2番目はコードを使用して追加されました。

27
Segev

カスタムUIViewサブクラスにawakeAfterUsingCoder:を実装する必要があります。このメソッドを使用すると、次のように、デコードされたオブジェクト(ストーリーボードから)を別のオブジェクト(再利用可能な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;
}

かなり良い記事があります here このテクニックといくつかの代替案について議論しています。

さらに、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. ストーリーボードで、Xibのコンテナーとして機能するUIViewを追加します。 XibViewというクラス名を付けます:

  3. この新しいXibViewのプロパティインスペクターで、IBInspectableフィールドに.xib(ファイル拡張子なし)の名前を設定します。

  4. 新しいXibビューをプロジェクトに追加し、プロパティインスペクターで、Xibの「ファイルの所有者」をXibViewに設定します(カスタムクラスに「ファイルの所有者」のみを設定したことを確認し、コンテンツビュー、またはクラッシュします)、再度、IBInspectableフィールドを設定します。

注意すべき点が1つあります:これは、.xibフレームをコンテナに一致させることを前提としています。サイズを変更できない場合、またはサイズ変更が必要な場合は、プログラムによる制約を追加するか、サブビューのフレームを変更して合わせる必要があります。 snapkit を使用して、物事を簡単にします。

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

ボーナスポイント

伝えられるところでは、prepareForInterfaceBuilder()を使用してこれらの再利用可能なビューをInterface Builderで表示できますが、私はあまり運がありません。 このブログ は、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ファイルのIDをカスタムビュークラスに割り当てます
  • View ControllerのviewDidLoadでは、loadNibNamed: on NSBundle.mainBundleを使用してxibとその関連ファイルを初期化し、返される最初のビューを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

IBにUIViewをドラッグアンドドロップし、アウトレットして設定するだけです。

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

enter image description here

ステップ

  1. 新しいファイルの追加=>ユーザーインターフェイス=> UIView
  2. カスタムクラスの設定-yourUIViewClass
  3. 復元IDの設定-yourUIViewClass
  4. yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

これで、必要に応じてビューをカスタマイズできます。

2
Yuyutsu

私はこのコードスニペットを何年も使用しています。 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;
}
2
alexgophermix

@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

下に行くパスはお勧めしませんが、ビューを表示したい場所に「埋め込みView Controllerビュー」を配置することで実行できます。

単一のビュー(再利用したいビュー)を含むView Controllerを埋め込みます。

0
Dustin

これについてはしばらく経ちましたが、私は多くの答えが通り過ぎるのを見てきました。 UIViewController埋め込みを使用していたので、最近再訪しました。これは、「実行時に繰り返される要素」(UICollectionViewCellまたはUITableViewCellなど)に何かを配置するまで機能します。 @TomSwiftが提供するリンクは、次のパターンに従うように導きました

A)親ビュークラスをカスタムクラスタイプにするのではなく、FileOwnerをターゲットクラス(この例では、CycleControlsBar)にします

B)ネストされたウィジェットのアウトレット/アクションのリンクはそこに行きます

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

ここにあなたがずっと望んでいた答えがあります。 CustomViewクラスを作成して、すべてのサブビューとアウトレットを持つxibにそのマスターインスタンスを保持することができます。その後、そのクラスをストーリーボードまたは他のxibのインスタンスに適用できます。

File's Ownerをいじったり、アウトレットをプロキシに接続したり、特有の方法でxibを変更したり、カスタムビューのインスタンスをそれ自体のサブビューとして追加したりする必要はありません。

これを行うだけです:

  1. BFWControlsフレームワークをインポートする
  2. スーパークラスをUIViewからNibViewに変更します(またはUITableViewCellからNibTableViewCellに変更します)

それでおしまい!

IBDesignableと連携して、ストーリーボードの設計時にカスタムビュー(xibのサブビューを含む)をレンダリングすることもできます。

詳しくはこちらをご覧ください: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

そして、ここからオープンソースのBFWControlsフレームワークを入手できます。 https://github.com/BareFeetWare/BFWControls

好奇心が強い場合に備えて、これを駆動するコードの簡単な抜粋を示します。 https://Gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

トム????

0
barefeettom