2015年5月30日土曜日

layoutSubviews で座標変更した UIView がスクロールすると元の座標に戻る

タイトルの通りになりますが、ScrollView 上に配置した UIView(ボタンとかラベルとか)を、layoutSubviews 内で座標を変更後、表示をすると座標が変更されて正常にみえますが、スクロールをすると、変更前の座標に戻ってしまいます。

この現象は、AutoLayout を使用している場合に発生します。
ViewController 毎に AutoLayout の設定を解除できるようなプロパティを設定したのですが意味がなかった(!?)。

ここで、AutoLayout と書いていますが、これに気づくまでに相当悩みました。

処理の流れをみると、座標の変更を行う layoutSubviews は、表示の時の 1 度だけ通過して、スクロールの時には処理を通らない。。。
安易な回避策として、同じ UIView を 2 つ用意し、座標前と変更後用の UIView の hidden を切り替えようかと考えましたが、内部の処理が冗長になるのでできれば同じものを使いまわしたいと思っていました。

UIScrollview, move, position... などと少ないボキャブラリーの中からキーワードを試行錯誤して検索しました見つかりませんでした。

今一度、起こっている現象と状況を辿って行くことにしました。

通常、AutoLayout を使用する場合、座標の変更を行わないのであれば制約を付けないか、位置とサイズの制約を使用するようにします。
と言うことは、座標をずらす場合には、layoutSubviews で、ずらした部分の制約をつけていく必要があるのでは?と思ったわけです。

つまり、スクロールした時点で AutoLayout が動き制約を実施する行うが、座標変更後の制約の設定がないため元の座標のまま放置されると言った動きになっているのではないかと。

どうやら、これが当たりのようでした。
たぶん、storyboard で画面レイアウト等をされない方は、すぐに現象の理解ができたのかもしれません。。。

同じ現象で悩んでいる人(が居るのか?)の為に、今回とった方法を記載します。

1. storyboard で、変更前の座標に配置し制約を付ける。


2. 制約をアウトレットで接続する。


3. layoutSubViews で、条件を判定して制約を付け直す。
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    self._button.translatesAutoresizingMaskIntoConstraints = NO;

    // 制約を解除
    [self._scrollView removeConstraints:@[self.widthConstraint]];

    // 条件によって X 座標を変更する制約を付ける
    if (self.isFlag) {
        self.widthConstraint
        = [NSLayoutConstraint constraintWithItem:self._button
                                       attribute:NSLayoutAttributeLeft
                                       relatedBy:NSLayoutRelationEqual
                                          toItem:self._scrollView
                                       attribute:NSLayoutAttributeLeft
                                      multiplier:1
                                        constant:20];
        
    } else {
        self.widthConstraint
        = [NSLayoutConstraint constraintWithItem:self._button
                                       attribute:NSLayoutAttributeLeft
                                       relatedBy:NSLayoutRelationEqual
                                          toItem:self._scrollView
                                       attribute:NSLayoutAttributeLeft
                                      multiplier:1
                                        constant:120];
        
    }
    // 制約を再付与
    [self._scrollView addConstraints:@[self.widthConstraint]];
}
 
ポイントは、制約を接続してコード上で操作できるようにしてから、制約を付け直すという部分になります。
手順で書くとあっという間なのに相当な労力が、、、こんな現象で困っている人がいるのかわかりませんが参考になれば。

0 件のコメント:

コメントを投稿