私はXCode/iOSの世界では比較的新しいです。ストーリーボードベースのアプリをある程度作成しましたが、nib/xibの全体に歯を磨いたことはありませんでした。シーンにも同じツールを使用して、再利用可能なビュー/コントロールを設計/レイアウトしたいと思います。そこで、ビューサブクラス用に史上初のxibを作成し、ペイントしました。
ストーリーボードで行うのと同じように、コンセントを接続して制約を設定します。 File Owner
のクラスをカスタムUIView
サブクラスのクラスに設定します。したがって、このビューサブクラスを何らかのAPIでインスタンス化できると仮定すると、示されているように構成/接続されます。
ストーリーボードに戻って、これを埋め込み/再利用します。私はテーブルビューのプロトタイプセルでそうしています:
私はビューを持っています。そのクラスをサブクラスに設定しました。操作できるように、アウトレットを作成しました。
$ 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;
}
これは簡単ですか?それとも私はこれのために一生懸命働いていますか?
あなたはほとんどそこにいます。ビューを割り当てたカスタムクラスの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
がどのように見えるかです。
青いスペースは、基本的にxibを「保持」するUIViewです。
これはあなたのxibです。
いくつかのテキストを印刷するボタンに接続されたアクションがあります。
これが最終結果です:
最初のclickMeと2番目のclickMeの違いは、最初のclickMeがUIViewController
を使用してStoryBoard
に追加されたことです。 2番目はコードを使用して追加されました。
カスタム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;
}
このInterface BuilderとSwift 4:
次のような新しいクラスを作成します。
_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)
}
}
_
ストーリーボードで、Xibのコンテナーとして機能するUIViewを追加します。 XibView
というクラス名を付けます:
この新しいXibView
のプロパティインスペクターで、IBInspectableフィールドに.xib(ファイル拡張子なし)の名前を設定します。
新しい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()
}
_
File > New > New File > iOS > User Interface > View
File > New > New File > iOS > Source > CocoaTouch
を作成します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 のプロジェクトがあります
IBにUIView
をドラッグアンドドロップし、アウトレットして設定するだけです。
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]
これで、必要に応じてビューをカスタマイズできます。
私はこのコードスニペットを何年も使用しています。 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;
}
@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])
}
}
下に行くパスはお勧めしませんが、ビューを表示したい場所に「埋め込みView Controllerビュー」を配置することで実行できます。
単一のビュー(再利用したいビュー)を含むView Controllerを埋め込みます。
これについてはしばらく経ちましたが、私は多くの答えが通り過ぎるのを見てきました。 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
}
}
魅力のように機能し、他のアプローチよりもはるかに単純です。
ここにあなたがずっと望んでいた答えがあります。 CustomView
クラスを作成して、すべてのサブビューとアウトレットを持つxibにそのマスターインスタンスを保持することができます。その後、そのクラスをストーリーボードまたは他のxibのインスタンスに適用できます。
File's Ownerをいじったり、アウトレットをプロキシに接続したり、特有の方法でxibを変更したり、カスタムビューのインスタンスをそれ自体のサブビューとして追加したりする必要はありません。
これを行うだけです:
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
トム????