web-dev-qa-db-ja.com

方法:iOSでStackViewビューを使用してXIBからカスタムビューをサイズ変更する

私は最近、Swiftを使用してiOS開発に飛び込み始めており、カスタムUIControlを調べています。レイアウトと柔軟性のために、このカスタムビューはStackViewを使用して構築されています。

達成したいこと...

...基本的には、StackViewをストーリーボードに直接ドラッグしたときのStackViewと同じ機能を単純に持つことです-問題なく適合するようにサイズ変更されます。これは、本質的にセルフサイジングテーブルビューセルと同じです。

ショーケース

CustomViewを単純な例に減らすことができます。それは、3つのラベルが含まれているStackViewで、AlignmentCenterDistributionEqual Centeringに設定されています。 StackViewは、左、右、上部のレイアウトガイド(またはトレーリング、リーディング、トップ)に固定されます。これは、CustomViewで簡単に再作成でき、XIBからロードして、同じパラメーターでCustomViewに変換します-両方をスクリーンショットとして添付しました。

シンプルなStackViewのスクリーンショット

CustomViewのスクリーンショット

XIBファイルをロードして設定します。

import UIKit

@IBDesignable
class CustomView: UIView {

    // loaded from NIB
    private weak var view: UIView!

    convenience init() {
        self.init(frame: CGRect())
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.loadNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.loadNib()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // we need to adjust the frame of the subview to no longer match the size used
        // in the XIB file BUT the actual frame we got assinged from the superview
        self.view.frame = bounds
    }

    private func loadNib ()  {
        self.view = Bundle (for: type(of: self)).loadNibNamed(
            "CustomView", owner: self, options: nil)! [0] as! UIView
        self.addSubview(self.view)
    }
}

ここで確認できるのは、XIBファイルをロードし、レイアウトのサブビューをオーバーライドして、フレームを実際のビューに適合させるだけです。

ご質問

だから私の質問は、これが通常どのようにアプローチされるかに関連しています。 StackViewのラベルには固有のコンテンツサイズがあります。つまり、通常、StackViewは、ストーリーボードでデザインした場合、文句なしにラベルの高さに単純に適応します。ただし、すべてをXIBに配置し、レイアウトをそのままにしておくと、ビューはスクリーンショットのように3つの方法(上部、先頭、末尾)で固定されていると、不明な高さについて不平を言うでしょう。

自動レイアウトガイドを読み、WWDCのビデオ「自動レイアウトの謎」を視聴しましたが、このトピックはまだまったく新しいものであり、適切なアプローチがまだ得られていないと思います。これは私が助けを必要とするところです

(1)制約を設定してtranslatesAutoresizingMaskIntoConstraintsを無効にする必要がありますか?

最初にできることとすべきことは、自動生成された制約を取得せず、独自の制約を定義しないようにtranslatesAutoresizingMaskIntoConstraintsfalseを設定することです。だから私はloadNibをこれに変更することができます:

private func loadNib ()  {
    self.view = Bundle (for: type(of: self)).loadNibNamed(
        "CustomView", owner: self, options: nil)! [0] as! UIView
    self.view.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(self.view)

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .bottom
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .bottom
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .top
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .top
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .leading
        , relatedBy: .equal
        , toItem: self
        , attribute: .leading
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .trailing
        , relatedBy: .equal
        , toItem: self
        , attribute: .trailing
        , multiplier: 1.0
        , constant: 0))
}

基本的に、ストーリーボードのUIViewの全幅に合うようにビューを引き伸ばし、StackViewの上下を上下に制限しました。

これを使用すると、XCode 8がクラッシュするという大量の問題があったので、どうしたらいいのかよくわかりません。

(2)またはintrinsicContentSizeをオーバーライドする必要がありますか?

すべてのintrinsicContentSizeの高さをチェックしてarrangedSubviewsをオーバーライドし、最も高いものを取得して、ビューの高さをそれに設定することもできます。

override var intrinsicContentSize: CGSize {
    var size = CGSize.zero

    for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {
        let subViewHeight = aSubView.intrinsicContentSize.height
        if  subViewHeight > size.height {
            size.height = aSubView.intrinsicContentSize.height
        }
    }

    // add some padding
    size.height += 0
    size.width = UIViewNoIntrinsicMetric
    return size
}

これにより確かに柔軟性が高まる一方で、固有のコンテンツサイズを無効にする必要がありますが、動的タイプと、避けたい他の多くの要素を確認してください。

(3)「提案された」方法でXIBをロードしていますか?

または、より良い方法があります。 UINib.instantiate?それを行うためのベストプラクティスの方法を見つけることができませんでした。

(4)そもそもなぜこれをしなければならないのですか?

ほんとに?なんで? StackViewをCustomViewに移動しただけです。では、なぜ今StackViewはそれ自体の適応を停止するのですか、なぜ上部と下部の制約を設定する必要があるのですか?

これまで最も役立っていたのは、 https://stackoverflow.com/a/24441368 のキーフレーズです。

「通常、自動レイアウトはトップダウン方式で実行されます。つまり、最初に親ビューのレイアウトが実行され、次に子ビューのレイアウトが実行されます。」

..しかし、私はそれについて本当によくわかりません。

(5)コードに制約を追加する必要があるのはなぜですか?

translatesAutoresizingMaskIntoConstraintsを無効にすると、IBのテーブルビューセルのように制約を設定できませんか?常にlabel.top = topのようなものに変換されるため、StackViewがサイズ/高さを継承する代わりに、ラベルが引き伸ばされます。また、StackViewをXIBファイルのIBのコンテナービューに固定することもできません。

誰かが私を助けてくれることを願っています。乾杯!

26
martin

順序は異なりますが、質問に対する回答をいくつか示します。

(5)コードに制約を追加する必要があるのはなぜですか?

TranslatesAutoresizingMaskIntoConstraintsを無効にすると、IBのテーブルビューセルのように制約を設定できませんか?常にlabel.top = topのようなものに変換されるため、StackViewがサイズ/高さを継承する代わりに、ラベルが引き伸ばされます。また、StackViewをXIBファイルのIBのコンテナービューに固定することもできません。

この例では、nibから任意のビューをロードし、それをコード化されたCustomViewに追加するため、コードに制約を追加する必要があります。 nibのビューには、どのコンテキストまたは階層に配置されるかについての既存の知識がないため、未知の将来のスーパービューに制約する方法を事前に定義することはできません。ストーリーボードまたはプロトタイプセル内のStackViewの場合、そのコンテキストは親ビューと同様に認識されます。

制約を手動で記述したくない場合、このケースで検討できる1つの素晴らしい代替策はIViewの代わりにCustomViewサブクラスをUIStackViewにするです。 UIStackViewは適切な制約を自動的に生成するので、手動でコーディングする必要がなくなります。いくつかのサンプルコードについては、次の回答を参照してください。

(3)「提案された」方法でXIBをロードしていますか?

または、より良い方法があります。 UINib.instantiateを使用して?それを行うためのベストプラクティスの方法を見つけることができませんでした。

カスタムビューへのnibのロードを処理する「標準」または「正しい」方法を知りません。私はすべてが機能するさまざまなアプローチを見てきました。ただし、サンプルコードで必要以上の作業を行っていることに注意します。以下は、同じ結果を達成するためのより最小限の方法ですが、UIViewサブクラスの代わりにUIStackViewサブクラスを使用します(上記のとおり)。

class CustomView: UIStackView {
    required init(coder: NSCoder) {
        super.init(coder: coder)
        distribution = .fill
        alignment = .fill
        if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {
            addArrangedSubview(nibView)
        }
    }
}

CustomViewはUIStackViewのサブクラスになっているため、サブビューに必要な制約が自動的に作成されることに注意してください。この場合、distributionalignmentはどちらも.fillに設定されているため、必要に応じてサブビューのエッジを独自のエッジに固定します。

(4)そもそもなぜこれをしなければならないのですか?

ほんとに?なんで? StackViewをCustomViewに移動しただけです。では、なぜ今StackViewはそれ自体の適応を停止するのですか、なぜ上部と下部の制約を設定する必要があるのですか?

実際、ここでの重要な問題は、StackViewのalignmentプロパティ.centerです。つまり、StackViewのラベルは垂直方向の中央に配置されますただし、StackViewの上部または下部に制約されません。ビュー内のサブビューにcenterY制約を追加するのと同じです。外側のビューは任意の高さに設定でき、サブビューは中央に配置されますが、中央の制約は上端と下端ではなく中心のみを制約するため、外側のビューの上下を「押し出す」ことはありません。

StackViewのストーリーボードバージョンでは、StackViewの上のビュー階層がわかっており、マスクのサイズ変更から制約を自動的に生成するオプションもあります。どちらの方法でも、最終的なStackViewの高さはUIKitに認識され、ラベルはその高さ内で垂直方向に中央揃えされますが、実際には影響しません。 StackViewの配置を.fillに設定するか、自動レイアウトでStackViewの高さを決定するために、ラベルの上部と下部からStackViewの上部と下部に追加の制約を追加する必要がありますする必要があります。

(2)あるいは、intelligentContentSizeをオーバーライドする必要がありますか?

サブビューのエッジも親ビューのエッジに制約されていない限り、固有のコンテンツサイズ自体は親ビューのエッジを押し出さないため、これはあまり役に立ちません。いずれにしても、上記の.center配置の問題に対処する限り、この場合に必要なすべての正しい固有のコンテンツサイズ動作が自動的に得られます。

(1)制約を設定して、translatesAutoresizingMaskIntoConstraintsを無効にする必要がありますか?

これは通常、有効なアプローチです。ただし、上記で説明したようにUIStackViewをサブクラス化する場合は、どちらも行う必要はありません。

要約すれば

もし、あんたが:

  1. 上記のサンプルコードのように、UIViewのサブクラスとしてCustomViewを作成します。

  2. カスタムクラスCustomViewに設定したビューをストーリーボードに作成します。 CustomViewのサイズを正しく調整する場合は、サイズを変更するマスクから自動生成された制約を使用せずに、制約を指定する必要があります。

  3. NibのStackViewを変更して、alignment.fillに設定するか、少なくとも1つのラベルの上部と下部(中央の大きなラベル)とスタックビューの上部と下部

次に、自動レイアウトが適切に機能し、StackViewを使用したカスタムビューが期待どおりにレイアウトされます。

14
Daniel Hall