web-dev-qa-db-ja.com

動的な高さのサブビューを持つ自動レイアウトUIScrollView

自動レイアウト制約を使用してUIScrollViewで問題が発生しています。次のビュー階層があり、IBを介して制約が設定されています。

- ScrollView (leading, trailing, bottom and top spaces to superview)
-- ContainerView (leading, trailing, bottom and top spaces to superview)
--- ViewA (full width, top of superview)
--- ViewB (full width, below ViewA)
--- Button (full width, below ViewB)

ViewAとViewBの初期高さは200ポイントですが、クリックすることで垂直方向に400ポイントの高さに拡張できます。 ViewAとViewBは、高さの制約を更新することで拡張されます(200から400)。対応するスニペットは次のとおりです。

if(self.contentVisible) {
    heightConstraint.constant -= ContentHeight;
    // + additional View's internal constraints update to hide additional content 
    self.contentVisible = NO;
} else {
    heightConstraint.constant += ContentHeight;
    // + additional View's internal constraints update to show additional content
    self.contentVisible = YES;
}

[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:.25f animations:^{
    [self.view layoutIfNeeded];
}];

私の問題は、両方のビューが展開されている場合、コンテンツ全体を表示するにはスクロールできる必要があり、現在スクロールが機能していないことです。 ViewAおよびViewBの高さの変更を反映する制約を使用してスクロールビューを更新するにはどうすればよいですか?

私がこれまで考えられる唯一の解決策は、アニメーションの後にContainerViewの高さを手動で設定することです。これは、ViewA + ViewB + Buttonの高さの合計になります。しかし、私はより良い解決策があると信じていますか?

ありがとう

40
lukas

私は次のような純粋な構造を使用します

-view
  -scrollView
    -view A
    -view B
    -Button

Button(THE LAST view)に制約(スクロールビューであるスーパービューまでの下部から垂直方向の間隔)があることを確認してくださいビューAとビューBの変更内容は、それに応じてscrollViewの高さが変更されます。

この素晴らしいオンライン book site を参照します。

「スクロールビューの作成」セクションを読むだけで、アイデアが得られるはずです。

詳細ビューを作成していて、自動レイアウトでInterface Builderを使用することはタスクに非常に適しているという同様の問題がありました!

がんばろう!

(追加リソース:

スクロールビューの自動レイアウト に関するスタックオーバーフローの説明。

iOS 6には、 リリースノート UIScrollViewの自動レイアウトサポートについての説明があります。

無料のオンライン iOSブックの説明 スクロールビューについて。これは実際に私を大いに助けてくれました!

58
lorixx

次のような階層があるとします(Label1はContentViewのサブビュー、ContentViewはScrollViewのサブビュー、ScrollViewはViewControllerのビューのサブビューです)。

ViewController's View ScrollView ContentView Label1 Label2 Label3

ScrollViewは、viewcontrollerのビューに対する通常の方法で自動レイアウトで制約されます。

ContentViewはscrollviewの上部/左/右/下部に固定されています。つまり、ContentViewのトップ/ボトム/リーディング/トレーリングエッジをScrollViewの同じエッジに等しくなるように制約する制約があります。ここにキーがあります:これらの制約は、ViewControllerのビューに表示されるフレームサイズではなく、ScrollViewのcontentSizeに対するものです。したがって、表示されたScrollViewフレームと同じフレームサイズになるようにContentViewに通知するのではなく、ScrollViewにContentViewがコンテンツであることを通知するので、scrollView.contentSizeを大きく設定するように、contentviewがScrollViewフレームよりも大きい場合はスクロールしますscrollView.frameはコンテンツをスクロール可能にします。

もう1つ重要な点があります。ContentView、Label1-3、およびContentViewのScrollview以外に、これらの制約から幅と高さを把握できる十分な制約が必要です。

たとえば、垂直方向にスクロールするラベルのセットが必要な場合は、ContentViewの幅をViewControllerビューの幅に等しくするように制約を設定し、幅を処理します。高さを管理するには、Label1の上部をContentViewの上部に、Label2の上部をLabel1の下部に、Label3の上部をLabel2の下部に、最後に(そして重要なこととして)Label3の下部をContentViewの下部に固定します。これで、ContentViewの高さを計算するのに十分な情報が得られました。

上記の投稿を読んで、まだContentViewの幅と高さの制約を適切に設定する方法が分からなかったので、これが誰かに手がかりを与えることを願っています。私が行方不明になったのは、Label3の下部をContentViewの下部に固定することでした。

25
Doug Voss

これは、コンテナビューで純粋な自動レイアウトUIScrollViewをレイアウトする方法の例です。より明確にするためにコメントしました:

コンテナは標準のUIViewであり、本体はUITextViewです

- (void)viewDidLoad
{
    [super viewDidLoad];

    //add scrollview
    [self.view addSubview:self.scrollView];

    //add container view
    [self.scrollView addSubview:self.container];

    //body as subview of container (body size is undetermined)
    [self.container addSubview:self.body];

    NSDictionary *views = @{@"scrollView" : self.scrollView, @"container" : self.container, @"body" : self.body};
    NSDictionary *metrics = @{@"margin" : @(100)};

    //constrain scrollview to superview, pin all edges
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];

    //pin all edges of the container view to the scrollview (i've given it a horizonal margin as well for my purposes)
    [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[container]|" options:kNilOptions metrics:metrics views:views]];
    [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[container]-margin-|" options:kNilOptions metrics:metrics views:views]];

    //the container view must have a defined width OR height, here i am constraining it to the frame size of the scrollview, not its bounds
    //the calculation for constant is so that it's the width of the scrollview minus the margin * 2
    [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:-([metrics[@"margin"] floatValue] * 2)]];

    //now as the body grows vertically it will force the container to grow because it's trailing Edge is pinned to the container's bottom Edge
    //it won't grow the width because the container's width is constrained to the scrollview's frame width
    [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[body]|" options:kNilOptions metrics:metrics views:views]];
    [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[body]|" options:kNilOptions metrics:metrics views:views]];
}

私の例では、「body」はUITextViewですが、他のものでもかまいません。 UITextViewを使用している場合は、UITextViewを垂直に拡張するには、viewDidLayoutSubviewsで設定される高さの制約が必要であることに注意してください。したがって、viewDidLoadに次の制約を追加し、それへの参照を保持します。

self.bodyHeightConstraint = [NSLayoutConstraint constraintWithItem:self.body attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0f constant:100.0f];
[self.container addConstraint:self.bodyHeightConstraint];

次に、viewDidLayoutSubviewsで高さを計算し、制約の定数を更新します。

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    [self.bodyHeightConstraint setConstant:[self.body sizeThatFits:CGSizeMake(self.container.width, CGFLOAT_MAX)].height];

    [self.view layoutIfNeeded];
}

UITextViewのサイズを変更するには、2番目のレイアウトパスが必要です。

7
jwswart

このコードを使用してください。 ScrollView setContentSizeは、メインスレッドで非同期と呼ばれるべきです。

迅速:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        var contentRect = CGRect.zero

        for view in self.scrollView.subviews {
           contentRect = contentRect.union(view.frame)
        }

        self.scrollView.contentSize = contentRect.size
    }
}

目標C:

 - (void)viewDidLayoutSubviews {
     [super viewDidLayoutSubviews];

     dispatch_async(dispatch_get_main_queue(), ^ {
                        CGRect contentRect = CGRectZero;

                        for(UIView *view in scrollView.subviews)
                           contentRect = CGRectUnion(contentRect,view.frame);

                           scrollView.contentSize = contentRect.size;
                       });
}
1

Dynamicを使用したスクロールビューの私のバリアント高さ:

1)scroll viewUIViewに追加します。すべての(上、下、リード、トレイル)制約を固定します。

2)UIViewScroll Viewに追加します。すべての(上、下、リード、トレイル)制約を固定します。あなたのContent viewになります。名前を変更することもできます。

3)Content viewからScroll viewへの制御ドラッグ-Equal width

4)UIViewにコンテンツを追加します。必要な制約を設定します。そして!下部の項目に下部の制約を追加します[〜#〜] not [〜#〜]Greater or equal>=)(ほとんどの人と同じように)[〜# 〜] but [〜#〜]Equal!たとえば、20に設定します。

私の状況では、コンテンツにUIImageViewがあります。 heightをコードに接続しました。そして、1000のように変更すると、スクロールが表示されます。そして、すべて動作します。

私にとって魅力的な作品です。ご質問-コメントへようこそ。

0
Vlad Pulichev

スクロールビューは、コンテンツのサイズを常に把握している必要があります。コンテンツサイズは、スクロールビューのサブビューから推測されます。コントローラーのプロパティを、サブビューの高さを記述するxibファイルの制約にマップすると非常に便利です。次に、コード(アニメーションブロック)で、これらの制約プロパティの定数を変更するだけです。制約全体を変更する必要がある場合は、親コンテナで後で更新できるように、制約への参照を保持します。

0