swift_combination を Swift 3.0 に対応させました
Xcode 8.0 がリリースされていたので早速インストールして Swift 3.0 を試してみた。
拙作のライブラリ swift_combination を開いてみると既存のコードを Swift 3.0 のシンタックスにコンバートするか聞かれたので、とりあえずつっこんでみる。
下のコードは配列の長さを指定して 0
で埋めるという処理だが、微妙に API の名前が変わってたりした。
var indices = [Int](count: length, repeatedValue: 0) // before var indices = [Int](repeating: 0, count: length) // after
もう一箇所は for in
を使ってる箇所で enumerate
が enumerated
に変更されている。
for (i, slct) in unselected.enumerate() { // before ... } for (i, slct) in unselected.enumerated() { // after ... }
細かい API の変更は全部これで直してくれるっぽい。
手動で直した箇所
ビルドしてみると以下の2点で警告が出た。
第一引数をラベル無し引数で渡す場合は明示的に指定する必要がある
Swift 3.0 では基本的に引数のラベルは省略しない事になったらしい。
let combos = combination([0, 1, 2], length: 2) // before let combos = combination(arr: [0, 1, 2], length: 2) // after
ライブラリはこれに合わせて全部ラベル付きで呼び出すように修正したけど、ラベルを省略したコードをそのまま動かしたい場合は、関数定義の引数ラベルの前に _
を付ければ一応オッケーらしい。
public func combination<T>(arr:[T], length:Int? = nil) -> [[T]] { // before ... } public func combination<T>(_ arr:[T], length:Int? = nil) -> [[T]] { // after ... }
関数の戻り値を使用しない場合は明示的に指定する必要がある
関数定義の方で戻り値を指定している場合、それを使わないと警告が出る。
_ = myFunc()
のように _
への代入を書くか、関数定義の上に @discardableResult
を付けることで警告を消すことができた。
_combination(arr: arr, length: _len){ ret.append($0) } // 警告が出る _ = _combination(arr: arr, length: _len){ ret.append($0) } // 警告が消える @discardableResult // これがあれば戻り値を破棄出来る internal func _combination<T>(arr:[T], length:Int, process:([T]) -> ()) -> [T] { ... }
とりあえず @discardableResult
を関数定義に付ける方向で対応することにした。
flex-grow, flex-shrink, flex-basis について
flex-grow CSS プロパティは、flex アイテムの flex grow factor を指定します。これは、アイテムが flex コンテナ内のスペースをどれだけ占有するかを指定します。
MDN: flex-grow
MDN の説明がシンプルすぎてよく分からなかったので、もう少し詳しく調べた内容をまとめておきます。
説明中に出てくるスクショの動作サンプルは下の記事にあります。
この記事は上の動作サンプル記事が長くなっため分離した補足記事みたいな感じです。
flexbox
の基本
flexbox の基本的な動作は以下のページが個人的には分かりやすかったのでオススメです。
- CSS3 Flexbox の各プロパティの使い方をヴィジュアルで詳しく解説
flexbox の仕様全般に関する説明が分かりやすい - Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法
flex-grow に関する説明が分かりやすい
flex-grow
ある flex アイテムが、 flex コンテナ内の他の flex アイテムと比較して、どのくらい大きくなろうとするかを整数値で指定する。
W3C の flex grow
の説明にはこうある。
This
component sets flex-grow longhand and specifies the flex grow factor, which determines how much the flex item will grow relative to the rest of the flex items in the flex container when positive free space is distributed. When omitted, it is set to 1.
W3C: flex-grow
意味としては、
この数値は、ポジティブなフリースペースが分配された時に、 flex アイテムが flex コンテナ内の他の flex アイテムと比較してどれくらい増大されるかを決定します。
ということだと思う。
flex-grow
はあくまでも余ったスペースを各アイテムで分け合う。
分配されるのは領域全体ではなく、余ったスペース(以下ポジティヴなフリースペース)。
ある領域を1:2:3
の比率で分割したいとして、単純に flex-grow
をそれぞれ1
,2
,3
と指定しても上手くいかない。
上は width
を 60px
, 120px
, 180px
に指定した display: block
の要素。
下は flex-grow
を 1
, 2
, 3
に指定した display: flex
の要素。
上と下を見比べると、下の比率が1:2:3
になっていないことが分かる。
下の図について細く見ていくと、余ったスペースが各アイテムに対してどのように分配されているのかが分かる。
flex コンテナの width
は360px
。
flex アイテムの width
は auto
、 flex アイテム内のコンテンツは40px
の span
要素なので、 flex アイテムの幅は40px
となる。
flex-grow
が分け合うポジティヴなフリースペースとは、 flex コンテナから flex アイテムの幅を引いた値。360px - (40px + 40px + 40px) = 240px
のことを指す。
この240px
を flex-grow
の値1:2:3
の比率で分配し、 width
に足した合わせた値が最終的な幅となる。
(40 + 1/6 * 240) + (40 + 2/6 * 240) + (40 + 3/6 * 240)
80 + 120 + 160
この例では比率が2:3:4
になる。
flex-shrink
ある flex アイテムが、 flex コンテナ内の他の flex アイテムと比較して、どのくらい小さくなろうとするかを整数値で指定する。
W3C の flex-shrink
の説明にはこうある。
This
component sets flex-shrink longhand and specifies the flex shrink factor, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when negative free space is distributed.
W3C: flex-shrink
意味としては、
この数値は、ネガティブなフリースペースが分配された時に、 flex アイテムが flex コンテナ内の他の flex アイテムと比較してどれくらい縮小されるかを決定します。
ということだと思う。
ネガティヴなフリースペースというが分かりにくい。
flex コンテナの幅と flex アイテムの幅の差がポジティヴかネガティヴかというふうに考えると分かりやすいかもしれない。
flex-grow
の時と逆の状況を考える。
flex アイテムの width
を 160px
にして、 flex コンテナからはみ出すようにしてみる。
- flex アイテムを全て足した幅が flex コンテナより小さい
360px - (40px + 40px + 40px) = 240px
= ポジティブなフリースペース。 - flex アイテムを全て足した幅が flex コンテナより大きい
360px - (160px + 160px + 160px) = -120px
= ネガティヴなフリースペース
flex-shrink
はこのネガティヴなフリースペースをどう分配するか、ということを指定する。
下のスクショは、
上が display: block
+ float: left
、 width: 120px
の要素。
下が flex-shrink
を 1
,2
,3
に指定した display: flex
の要素となっている。
ネガティヴなフリースペース -120px
を、 flex-shrink
の値1:2:3
の比率で分配すると、
(160 + 1/6 * -120) + (160 + 2/6 * -120) + (160 + 3/6 * -120)
140 + 120 + 100
flex アイテムはそれぞれ140px
、120px
、100px
となり、比率は7:6:5
となる。
flex-basis
flex-basis
についてはまだ仕様が不安定な印象です。
とりあえず MDN の説明を引用。
flex-basis CSS プロパティは、flex アイテムの初期 main size である flex basis を指定します。box-sizing を使用して別に指定されていない限り、このプロパティが content-box の寸法を定義します。
MDN: flex-basis
要はベースとなる width
か height
を指定するということだと思う。
box-sizing
の影響を受けるので、 border
、 padding
を含めた値を指定したい場合は box-sizing
を border-box
にする。
(ただし、IEでは box-sizing
の指定に関するバグ有り。 Flexbugs: 7. flex-basis doesn't account for box-sizing:border-box)
指定出来る値は width
、 height
に設定出来る値であれば何でも良い。
あるいは、 content
を指定する。
content
結論から書くと、動作がブラウザによってバラバラなので、現段階では使わないほうが良さそうです(2016年9月4日)。
コンテンツサイズに応じてよしなにやってくれる的な設定なんじゃないかーぐらいのことしか分かりませんでした。
W3C の説明も
Indicates automatic sizing, based on the flex item’s content.
と一行だけです。
実際の動作サンプルについては別記事に追記があります。
メモを揉め: Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
ブラウザ間の差異の原因になっている(2016年9月4日現在)
flex-basis
は Chrome 、 Firefox 、 Edge グループと Safari 、 IE11 グループとで動作が異なる。
2017年8月17日現在は IE11 のみ動作が異なる。
Chrome 、 Firefox 、 Edge はコンテンツのサイズを維持。
Safari 、 IE11 はコンテンツサイズを無視する。
Chrome 、 Firefox 、 Safari 、 Edge はコンテンツのサイズを維持。
IE11 はコンテンツサイズを無視する。
こちらも詳しくは別記事に記述があります。
メモを揉め: Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
その他の考慮すべきこと
ここまでの flex-grow
、 flex-shrink
の動作は、余った、あるいは足りないスペースを flex-grow
、 flex-shrink
に基いて分配するという、ある意味シンプルなルールです。
しかし、場合によっては様々な要因によって結果が予想しにくくなることがあります。
再計算が必要になる場合
フリースペースの分配が行われた後、何らかの原因によって再びネガティブスペースを作ってしまった場合、再計算が行われます。
0以下になる場合
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 400px
、
min-wditn
が 0
に設定されていた場合、下のスクショの様な結果になる。
順を追って見ていくと、
flex
コンテナからflex
アイテム3つの合計を引く
360 - (400 + 400 + 400) = -840
- ネガティブなフリースペース
-840px
をflex-shrink
に基いて分配する
(400 - 1/6 * 840) + (400 - 2/6 * 840) + (400 - 3/6 * 840)
- 3つ目の
flex
アイテムが0
を下回ってしまう
(260) + (120) + (-20)
- 再び生まれたネガティブなフリースペース
-20px
の再分配が行われる
(260 - 1/3 * 20) + (120 - 2/3 * 20) + (0)
- 最終的なサイズが決定する
253.3333... + 106.6666... + 0
コンテンツサイズを下回る場合
Chrome 、 Firefox 、 Edge の flex-basis
の挙動により起こる現象。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-grow
が 1
、 5
、 6
、
flex-basis
が 0
に設定されていた場合、下のスクショの様な結果になる。
順を追って見ていくと、
flex
コンテナからflex
アイテム3つの合計を引く
360 - (0 + 0 + 0) = 360
- ポジティブなフリースペース
360px
をflex-grow
に基いて分配する
(0 + 1/12 * 360) + (0 + 5/12 * 360) + (0 + 6/12 * 360)
- 1つ目の
flex
アイテムがコンテンツサイズの40px
を下回ってしまう
(30) + (150) + (180)
- 1つ目の
40px
を除いた320px
が再びポジティブなフリースペースとして再分配される(40) + (0 + 5/11 * 320) + (180 + 6/11 * 320)
- 最終的なサイズが決定する
40 + 145.4545... + 174.5454...
margin, paddingを下回る場合
margin
、 padding
は flex-grow
、 flex-shrink
によって増大も縮小もしないため、フリースペースの分配結果がこれらを下回る場合は再分配が行われる。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 160px
、
左右の padding
が 40px
、
min-width
が 0
に設定されていた場合、下のスクショの様な結果になる。
flex
コンテナからflex
アイテム3つの合計を引く
360 - (240 + 240 + 240) = -360
- ポジティブなフリースペース
360px
をflex-grow
に基いて分配する
(240 - 1/6 * 360) + (240 - 2/6 * 360) + (240 - 3/6 * 360)
- 3つ目の
flex
アイテムが左右のpadding
の合計80px
を下回ってしまう
(180) + (120) + (60)
- 再び生まれたネガティブなフリースペース
-20px
の再分配が行われる
(180 - 1/3 * 20) + (120 - 2/3 * 20) + (80)
- 最終的なサイズが決定する
173.3333... + 106.6666... + 80
それでも解決しない場合
再分配しても以下の様に、
0
を下回るmin-width
を下回る- margin, paddingを下回る
- コンテンツサイズを下回る
に当てはまる場合は、サイズが下回らない様にコンテナからはみ出し、分配はそこで終わりとなる。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 0px
、
左右の padding
が 70px
に設定されていた場合、下のスクショの様な結果になる。
左右の padding
の合計 140px
より縮小することが出来ないため、 flex コンテナからはみ出る。
追記(2016年9月6日)Firefoxの表記について
Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
flexboxのプロパティの組み合わせを比較できるカタログが欲しいなーと思ったので作ってみました。
各プロパティーの値がどのように解釈されるか、ブラウザ間での違いなどが分かります。
あれもこれもと入れていたら無駄に長くなりました。
2017年8月17日追記
Safari の flex-basis
の挙動が Chrome、Firefox、Edge と同じなっていたので、記事とサンプルをアップデートしました。
See the Pen Case Studies of Flexbox by Keita Okamoto (@all-user) on CodePen.
縦のストライプの幅が10px
、flexコンテナの幅が360px
となっていて、flexアイテムとそのコンテンツの大きさはそれぞれのラベルに表示されます。
コンテナの上には当てているスタイルの要約と最終的なサイズを割り出す計算式があります。
全ブラウザで結果が同じ場合は緑、ブラウザ間で結果に差異がある場合は赤の文字になっています。
一応仕様的にはこうなるべきだろうという計算式を書いているつもりですが、解釈が間違っているかも知れません。
なんか違うなと思ったら教えていただけると助かります。
ベストプラクティス(暫定)
flexboxの各アイテムが最終的にどのサイズで描画されるのかは領域の分配ルールに様々な要素が絡んでいます。
そのため、適当にプロパティを組み合わせていくと、なかなか思い通りのサイズになりません。
スコープを絞るために話をflex-direction: row
に限定するとして、サイズの計算に影響を与えるプロパティはこれだけあります。
flex-shrink
flex-grow
flex-basis
width
min-width
max-width
margin
padding
box-sizing
さらにブラウザ間の誤差やflexboxの抱える様々なバグを考慮して全てをコントロールするのはかなり難しいと思われます。(この実験に関して言えば、実際にはブラウザの誤差はあるひとつの挙動に起因していたため、そこまで辛くなかった。後述)
色々試した結果、暫定的なベストプラクティスとして以下のルールを守るのが良さそうだという結論になりました。
flex-basis
はauto
(初期値)width
を0
に設定する(ただしflex-direction: column
+height
の時は要注意。下に追記あり)
width
を0
以外にしたい場合はmin-width
をそれより小さい値で設定する
min-width
を設定すればflex-basis
でもブラウザ間の差異は無くなりますが、あえてflex-basis
を使うメリットも特に無さそうなのでwidth
を使うのが良いかなと思ってます。
- flex-basisとmin-widthを組み合わせた例
あくまで厳密にサイズをコントロールしたい時のベストプラクティスです。(flexboxでテーブルみたいに縦を揃えたい、とか)
コンテンツのサイズが予測できたり、サイズに応じてよしなにやって欲しい時はwidth
、flex-basis
ともにauto
(必要に応じてmax-width
、min-width
)でも良いと思います。
幅の計算をするときに気をつけること
padding
、margin
の扱い
padding
、margin
はflex-shrink
、flex-grow
の影響を受けないようです。
常に設定された値になる必要があるので、スペースを分配した結果が設定した値を下回る場合はスペースを再分配する必要があります。
- 左右の
padding
80px
を下回るため再分配が行われる
box-sizing
の影響
padding
がwidth
に含まれるかどうかはbox-sizing
の値によって決まります。
初期値であるcontent-box
の場合は以下のようになります。
width: auto
のためコンテンツサイズ分の40px * 3
を除く240px
がflex-grow
の値に基いて分配される
コンテンツサイズ分の
40px * 3
に加え、左右のpadding
40px * 3
を除く120px
がflex-grow
の値に基いて分配される
flexアイテムの
width
160px * 3
とコンテナサイズ360px
の差-120px
がflex-shrink
に基いて分配される
flexアイテムの
width
160px * 3
に左右のpadding
80px * 3
を加えた720px
とコンテナサイズ360px
の差-360px
がflex-shrink
に基いて分配される
min-width
の効果
width
の設定を0
以上に設定していて、その値よりコンテンツサイズが小さい場合、flex-shrink
はコンテンツサイズを下回って縮小することができなくなります。(flex-basis
の場合は0
に設定してもコンテンツサイズを下回るサイズにはなれないことがある。後述)
min-width
を設定することで、その値まではコンテンツサイズを下回るサイズに縮小することが出来ます。
コンテンツサイズの
160px
を下回って縮小することが出来ない(Safari、IE11は縮小する)min-width: 0
によりコンテンツサイズの160px
を下回って縮小することが出来る
ブラウザ間の差異(2016年8月28日現在)
この実験に絞って言えばブラウザ間で結果に差異が出る原因は、flex-basis
がコンテンツサイズを保証するかどうか、ということに集約できました。
Chrome、Firefox、Edgeはコンテンツのサイズを維持。
Safari、IE11はコンテンツサイズを無視する。
コンテンツサイズを保証するというのはどういう事かというと、flex-shrink
やflex-grow
を適用した後に、もしflexアイテムのサイズがコンテンツサイズを下回っていた場合に、そのflexアイテム以外のflexアイテムの間でネガティブなスペースを再分配し、必ずコンテンツサイズを下回らない様にします。(それでも下回る場合はそれ以上縮小しない)
- 再分配してもコンテンツサイズを下回るのでそれ以上縮小しない例
どちらが仕様的に正しいのかはわかりませんが、IEを除くとコンテンツサイズを無視するのはSafariだけなのでChrome、Firefox、Edgeの動作がより最新の仕様に沿っているのかもしれません。
現在は Safari も Chrome、Firefox、Edge と同じ動作です。
追記(2016年8月31日)height
の0
指定について
flex-direction: column
時にheight
に0
を指定すると、たとえflexアイテムがflex-grow
によってコンテナいっぱいに広がっていたとしても、flexアイテム内の要素からはheight
が0
に見えてしまうらしく、%
指定が出来なくなるというデメリットがあることが分かりました。(Safariのみ)
height
の代わりにmin-height
を0
にすれば大丈夫でした。
- Safariのスクショ。左:
height: 0
、右:height: 100%
+min-height: 0
枠線付きがflexコンテナ、濃いグレーがflexアイテム、薄いグレーがflexアイテム内のブロック要素
http://codepen.io/all-user/pen/RGbKRd
追記(2016年9月5日)flex-basis
の content
について
flex-content
に指定できる値 content
についての調査を追加しました。
結論から書くとブラウザ間の挙動がバラバラ。
今はまだ使わないほうが良さそうです。
以下、実験で分かったこと。
ショートハンドが効かない(Chrome, Firefox, Safari, IE11)
ショートハンドで指定した flex-grow
、 flex-shrink
がなぜか無効になります。(Edge は有効)
flex-grow
は 0
(Chrome, Firefox, Safari, IE11)、 flex-shrink
は 1
(Chrome, Firefox, Safari)と解釈されているように見えます。
ロングハンドの指定は有効でした。
flex-grow
flex-basis
にcontent
flex-grow
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
、content
、auto
flex-grow
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
flex-grow
に1
、2
、3
をロングハンドで指定
flex-shrink
flex-basis
にcontent
min-width
に0
flex-shrink
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
、content
、auto
min-width
に0
flex-shrink
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
min-width
に0
flex-shrink
に1
、2
、3
をロングハンドで指定