🗜️
段階を示すUIの画面をAutoLayoutで
以前こんなツイートをしました。
将棋など甲乙に分かれて行うゲームの段階を示す画面では、こういうデザインにして出来るだけ縮めてスペースを有効に使いたいですね。これを(SwiftUIではなく)AutoLayoutでやります。
完成品
現在の状態がどれなのか示すUIはこの記事から省きます。
登場人物
各工程毎に
- 点(赤を塗る、UIViewのサブクラス)
- 文章(UILabel)
必要な制約
- ペアになっている点と文はCenterYが揃っている
隣あう上下の工程(AとBとする)は
- Aの点の下端よりもBの文章の上端は下
- Aの文章の下端よりもBの点の上端は下
です。
最後に
- できるだけ縦方向を詰める
があります。
コード
pp
点(UIViewのサブクラス)の配列
dd
文章(UILabel)の配列
ペアになっている点と文はCenterYを揃えるところが
for i in 0..<4 {
pp[i].centerYAnchor.constraint(equalTo: dd[i].centerYAnchor)
.isActive = true
}
です。
上下関係のこれ以上踏み込んではいけない制約が
for i in 0..<3 {
pp[i].bottomAnchor.constraint(lessThanOrEqualTo: dd[i + 1].topAnchor)
.isActive = true
dd[i].bottomAnchor.constraint(lessThanOrEqualTo: pp[i + 1].topAnchor)
.isActive = true
}
です。
最後に、手元の確認ではあってもなくても変わりませんでしたが、理論上は広がってしまうことがありえる状態なので、念の為に縮める制約も付けます。
let shrink = dd[3].bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
shrink.priority = UILayoutPriority(700.0)
shrink.isActive = true
ちなみに700は、文章が潰されるのに抵抗する力が750なのでそれに負ける値というのが根拠です。
ここに760を設定すると、文章が潰されて
となります。点のサイズは1000の力で設定してあるので生き残ります。
ソース全体
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var pp: [PointView] = []
var dd: [UILabel] = []
for _ in 0..<4 {
pp.append(PointView(frame: .zero))
dd.append({
let l = UILabel()
l.numberOfLines = 0
return l
}())
}
dd[0].text = "春はあけぼの。やうやう白くなりゆく山ぎは、すこしあかりて、紫だちたる 雲のほそくたなびきたる。"
dd[1].text = "夏は夜。月のころはさらなり。やみもなほ、蛍の多く飛びちがひたる。また、 ただ一つ二つなど、ほのかにうち光りて行くもをかし。雨など降るもをかし。"
dd[2].text = "秋は夕暮れ。夕日のさして山の端いと近うなりたるに、烏の寝どころへ行く とて、三つ四つ、二つ三つなど、飛びいそぐさへあはれなり。まいて雁などの つらねたるが、いと小さく見ゆるはいとをかし。日入りはてて、風の音、虫の 音など、はたいふべきにあらず。"
dd[3].text = "冬はつとめて。雪の降りたるはいふべきにもあらず、霜のいと白きも、また さらでもいと寒きに、火など急ぎおこして、炭もて渡るもいとつきづきし。 昼になりて、ぬるくゆるびもていけば、火桶の火も白き灰がちになりてわろし"
for p in pp {
view.addSubview(p)
p.translatesAutoresizingMaskIntoConstraints = false
}
for d in dd {
view.addSubview(d)
d.translatesAutoresizingMaskIntoConstraints = false
}
for i in 0..<4 {
pp[i].centerYAnchor.constraint(equalTo: dd[i].centerYAnchor).isActive = true
}
for i in 0..<4 {
pp[i].widthAnchor.constraint(equalToConstant: 24).isActive = true
pp[i].heightAnchor.constraint(equalToConstant: 24).isActive = true
}
for i in 0..<3 {
pp[i].centerXAnchor.constraint(equalTo: pp[i + 1].centerXAnchor).isActive = true
}
for i in 0..<3 {
dd[i].widthAnchor.constraint(equalTo: dd[i + 1].widthAnchor).isActive = true
}
for i in 0..<2 {
dd[i * 2].leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor).isActive = true
dd[i * 2].trailingAnchor.constraint(equalTo: pp[i * 2].leadingAnchor).isActive = true
dd[i * 2 + 1].leadingAnchor.constraint(equalTo: pp[i * 2 + 1].trailingAnchor).isActive = true
dd[i * 2 + 1].trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor).isActive = true
}
for i in 0..<3 {
pp[i].bottomAnchor.constraint(lessThanOrEqualTo: dd[i + 1].topAnchor).isActive = true
dd[i].bottomAnchor.constraint(lessThanOrEqualTo: pp[i + 1].topAnchor).isActive = true
}
dd[0].topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
let shrink = dd[3].bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
shrink.priority = UILayoutPriority(700.0)
shrink.isActive = true
}
}
class PointView: UIView {
let padding: CGFloat = 6.0
let radius: CGFloat = 4.0
override init(frame: CGRect) {
super.init(frame: frame)
let inner = UIView()
inner.layer.backgroundColor = UIColor.red.cgColor
inner.layer.cornerRadius = radius
addSubview(inner)
inner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
inner.topAnchor.constraint(equalTo: topAnchor, constant: padding),
inner.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding),
inner.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding),
inner.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Discussion