ラベル Autolayout の投稿を表示しています。 すべての投稿を表示
ラベル Autolayout の投稿を表示しています。 すべての投稿を表示

[Autolayout] サンプルケース - 親ビューの下半分に子ビューを配置 (2) アニメーション

2015年6月12日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

前回のやつにアニメーションを加えてみる。子ビューが下からシュッと出てくるやつ。

これは制約を使えば簡単。子ビューの上のラインを画面下端から、最終位置(この場合親ビューのCenterY)まで引き上げてやれば良い。
まず子ビューの初期位置を最終下端で作る。
let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal,
     toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: parentView.frame.size.height)

一旦 parentView.layoutIfNeeded()で初期描画させた後、アニメーションのコードを走らせる。
v1c.constant = 0.0
        UIView.animateWithDuration(0.5) {
            parentView.layoutIfNeeded()
        }

こうすると子ビューの上の制約の値(constraint)が下端(parentView.frame.size.height)から 0.0へアニメーションしながら変化する。

なおこのままだと制約のワーニングが出る。
2015-06-11 08:10:18.938 LKMenu_Example[89282:6908924] Unable to simultaneously satisfy constraints.
 Probably at least one of the constraints in the following list is one you don't want.
 Try this: (1) look at each constraint and try to figure out which you don't expect; 
(2) find the code that added the unwanted constraint or constraints and fix it.
 (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer
 to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "",
    "",
    ""
)
  :

これは子ビューの上の制約(v1c)と下の制約(v2c)の値が初期状態では等しくなる(逆転する?)ため。こんな時は優先順位(priority)をつけてやればいい。
let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal,
      toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        v2c.priority = 750   // 追加
        parentView.addConstraint(v2c)
親ビューに制約を追加する前につけるのを忘れずに。これでワーニングが消える。priorityは大きい方が優先順位が高い。IBでプロパティ欄を見ると 1000(Required), 750(High), 250(Low)となっている。
デフォルト値は 1000(最大値も 1000)。


最終的なコード。
parentView.addSubview(tableView)

        tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
        
        let h1c = NSLayoutConstraint(item: tableView, attribute: .Left, relatedBy: .Equal,
            toItem: parentView, attribute: .Left, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h1c)
        let h2c = NSLayoutConstraint(item: tableView, attribute: .Right, relatedBy: .Equal,
            toItem: parentView, attribute: .Right, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h2c)
        
        let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal,
            toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: parentView.frame.size.height)
        parentView.addConstraint(v1c)
        let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal,
            toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        v2c.priority = 750
        parentView.addConstraint(v2c)
        parentView.layoutIfNeeded()
        
        v1c.constant = 0.0
        UIView.animateWithDuration(0.5) {
            parentView.layoutIfNeeded()
        }

Autolayoutはコツがわかってくると楽しい。


(おまけ)animateWithDuration:usingSpringWithDamping: を使えばアニメーションにバネ効果(バウンス効果っぽい)をつけられる。
UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.0, options: UIViewAnimationOptions.CurveEaseInOut,
            animations: { () -> Void in
            parentView.layoutIfNeeded()
        }) { (Bool) -> Void in
        }

[Autolayout] サンプルケース - 親ビューの下半分に子ビューを配置

2015年6月11日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

メニュー作成中。Autolayoutでこんな感じのビュー配置をしたい。
ランドスケープで。

親ビューが parentView、子ビューが tableViewとするとこんな感じ。
        parentView.addSubview(tableView)

        tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
        
        let h1c = NSLayoutConstraint(item: tableView, attribute: .Left, relatedBy: .Equal, toItem: parentView, attribute: .Left, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h1c)
        let h2c = NSLayoutConstraint(item: tableView, attribute: .Right, relatedBy: .Equal, toItem: parentView, attribute: .Right, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h2c)
        
        let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal, toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(v1c)
        let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal, toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(v2c)

        parentView.layoutIfNeeded()

ポイントは v1c のところで、親のCenterYに 子ビューのTOPを紐付けているところ。ここの constantを変更すれば高さを調整することもできる。


[Autolayout] アニメーション中に一時的に高さ0になると怒られる場合

2015年1月7日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

アニメーションなどで一時的に高さ0にすると下記のように怒られる。これは制約で高さ指定をしているから。
2015-01-07 08:14:06.763 EverGear[474:96170] Unable to simultaneously satisfy constraints.
 Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
    "<NSLayoutConstraint:0x1740836b0 V:[UIView:0x17419d330(44)]>",
    "<NSLayoutConstraint:0x174083b60 V:[UIView:0x17419af70(0)]>",
    "<NSLayoutConstraint:0x1740834d0 V:|-(0)-[UIView:0x17419d260]   (Names: '|':UIView:0x17419af70 )>",
    "<NSLayoutConstraint:0x174083430 V:[UIView:0x17419d260]-(0)-[UITableView:0x15084b000]>",
    "<NSLayoutConstraint:0x174086630 V:[UIView:0x17419d330]-(0)-|   (Names: '|':UIView:0x17419af70 )>",
    "<NSLayoutConstraint:0x1740869a0 V:[UITableView:0x15084b000]-(0)-[UIView:0x17419d330]>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x1740836b0 V:[UIView:0x17419d330(44)]>

この場合は制約の Priorityを下げてやればいい。

デフォルトの Required(1000) を Hight(750)に変更すると出なくなった。

- - - -
Autolayoutに徐々に慣れてきた。便利、というかマルチスクリーン対応では不可欠。

Autolayoutでビューを等間隔に並べる

2014年1月7日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

こんなやつを作りたい。

Autolayoutで等間隔にビューを並べる方法はガイドに記載がある。
Auto Layout ガイド - ビューを等間隔で並べる (P.31)

ビューの等間隔配置の機能は Autolayoutの自体には無くて、それぞれのビューの間にスペーサとなるビューを入れて間隔を調整する方法を取るらしい。

なるほど。スペーサ幅を可変にして、かつ全スペーサの幅が同じになるような制約が作れればいいわけだ。

実際やってみよう。
こんな感じでビューを配置して制約を作る。
スペーサ(黄色とオレンジ色のやつ)はデバッグしやすいように色をつけて縦位置も意図的にずらしてある。
制約を作るにはコントロールキーを押しながらスペーサをクリックして、そのままドラッグしてビュー(緑色)へ接続する。
すると作成可能な制約の一覧が出てくるので Horizontal Spacing を選ぶ。
これだけ。
この操作を全てのスペーサについて行う。

また全てのスペーサが同じ幅(width)を持つように設定しておく。これは上記のようなドラッグ操作を最初のスペーサと他のスペーサ間で行い Equal Widths を選べばいい(最初のスペーサを基準にしておく)。

その他、縦位置はお好みで。例ではビューが画面縦の中央に配置するようにしてある。


これで終わり。コーディングは無し。

実行してみよう。

iPhone縦

iPhone横

iPad縦

iPad横


いい感じ。
やってみた感じではドキュメントにあるような Relation=Less than or Equal や Priority=999の設定は不要だった(むしろ設定するとうまく行かなかった。やり方がまずかったのかも)。

サンプルのソース
iOS7-Sample/AutolayoutSample2

- - - -
Autolayoutは強力だがプリミティブすぎて少しでも凝ったことをやろうとすると今のままではちょっと面倒だし難解。今後は等間隔配置のような1段上のレベルのレイアウトを支援するような機能が望まれる。例えばInterfaceBuilder上でビューを複数選んでボタンをポチると配置が完了するとか、等間隔を管理するクラスを導入するとか(これはもしかするとサードパーティから出てるかもしれない)。




Autolayoutで可変高さのビューをアニメーション

2014年1月5日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

制約の追加・削除でアニメーションを制御するTips

例えば下図のような緑色のビュー(以下、緑ビュー)があるとする。

最初は赤いビュー(以下、赤ビュー)は隠れていてOpenボタンを押すと下から上へせり出てくるようなアニメーションをやってみる。



固定高さの場合は簡単で赤ビューの上の制約のconstant値を変えてやればいい。ただ今回のように出現位置(緑ビューの上辺)が可変の場合は少し工夫がいる。試行錯誤してみたところあらかじめ2つの制約(上辺と高さ)を用意しておき、それを操作することで実現できることがわかった。

順を追ってみてみる。

1. 赤ビューの制約を設定
まず赤ビューを緑ビューの上へ配置する。

次に制約を追加する。上下左右の制約をconstatn=0とした上で同時に Height=0とする。上下制約で高さを可変にしつつ、隠す時には高さを0にしておくという意図でこうする。
ただXcodeは警告を出す。

frame.size.heightが非0なのに(目で見て分かるように赤ビューは高さがある)制約height=0にしているので制約とframe設定が不一致という警告。ここでframe.height=0とすると今度は上制約(constatn=0)と矛盾して別の警告が出る。これは2つの矛盾する制約を課しているからこれは当たり前。
制約1:上辺は superviewとのオフセット0(プライオリティ 1,000)
制約2:高さは0(プライオリティ 1,000)
フレーム:高さは(ここでは282)

矛盾する制約がある場合はプライオリティに差をつけてやれば良い。今回は高さの制約のプライオリティを下げてやる。ここでは 500にしてみた。

すると警告が消えて代わりに高さの制約が点線表示になる。制約1の方が優先されるので赤ビューは見えたまま。

2. 制約の制御
次に赤ビューをアニメーションさせるコードを書く。
まず赤ビューの上の制約のアウトレット topConstraint を作っておく。

後での操作の為に ARCはweakではなくstrongにしておく。

初期表示時には赤ビューは隠しておきたい。そこで初期処理で制約1(上辺の制約)を削除してしまう。
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.view removeConstraint:self.topConstraint];
}
こうすると制約2(高さ0)の制約が残るので最初は何も表示されない。

次に赤ビューを表示させる。openボタンのアクションで取っておいた上の制約を加えてやる。
- (IBAction)open:(id)sender {
    [self.view addConstraint:self.topConstraint];
    [UIView animateWithDuration:0.5
                     animations:^{
                         [self.view layoutIfNeeded];
                     }];
}
こうすると制約1(上辺の制約)が復活し、かつ制約2(高さ0)よりもプライオリティが高いので優先され、画面上部まで上辺が移動する。
赤ビューが上へせり出してきて
アニメーション完了。制約1(上辺の制約=0)が適用された状態で終わる。

なおアニメーションの layoutIfNeededは制約ベースのアニメーションの定石(これをやらないとアニメーションしない)。

逆に閉じたい時は制約1(上辺の制約)を削除してやればいい。
- (IBAction)close:(id)sender {
    [self.view removeConstraint:self.topConstraint];
    [UIView animateWithDuration:0.5
                     animations:^{
                         [self.view layoutIfNeeded];
                     }];
}

Autolayoutを活用しているのでもちろん4インチディスプレイでも意図通りに動く。


なおアウトレットを作る代わりにコードで制約のインスタンスを作る方法もある。ただ今回のようにInterfaceBuilderを使った方が見た目わかりやすいのと制約をIB側でまとめられるメリットがあると思う。

- - - -
Autolayout以前のアニメーションは frameに対する操作が基本だったが、
Autolayoutでアニメーションを行う時の基本は
『制約に対して操作する』
これを理解すると難しくない(...いや凝ったことは難しいが)。ちょっとしたパズルみたい。

今回の方法がベストかは良くわからないがうまくいった。Autolayoutはとっつきにくいが慣れてくるとかなり強力。


サンプルのソースコードはこちら
AutolayoutSample1


[100min]

人気の投稿(過去 30日間)