趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
###いくつかレイアウトの実装方法について書いてます
##グループ化
UIの部品をUIViewによりグループ化し、中身のコンテンツの大きさにより高さと幅が下辺となるviewを作る。
IB上ではUILayoutGuideが使用できないのでUIViewで空間を作る。
ただし、空間の色を透明にすると描画処理が重くなってしまうので、出来れば色を指定した方がいい。
下記の例のviewは、x座標と上からの距離のみ指定されているが制約エラーは出ていない。
これは中身のviewにより高さと幅が決まっているためである。
まず高さのみ制約を見ると、コンテンツの高さ+制約で高さが求められている。
横幅も同じようになっている。
中身の部品はグループ化に使っているviewまたは互いに対して制約を指定しているので、画面を横にしても位置関係が変わることはない。
しかし今のレイアウトではコンテンツが入らない時に画像が隠れてしまう。
はみ出した時に画像だけは画面に入るように制約を加えてみる。
親view.width >= view.widthとなるように制約を加える。
関係を >= とすることでコンテンツが小さい時は親viewのwidthに依存しないようにした。
ラベルのTrailing Spaceの制約の優先度を下げて表示を行う。
幅を等しくするオブジェクトを全て選択して、EqualWidthを選択。
####オブジェクト間の幅を等しくしたい場合
まずボタン同士の幅を等しくする。
viewを2つ定義してAとBの間BとCの間に設置し、それぞれボタンとくっつける制約を定義する。
(ついでに高さをボタンと揃える)
最後にview同士の幅を等しくして終わり。
##トルツメパターン
トルツメ : 不用なインターフェースを削除してその間のスペースを詰めること。
UITextViewを取り除きたい。
###トルツメ失敗パターン
####viewから消した場合
textView.removeFromSuperview()
####非表示にした場合
textView.isHidden = true
###viewから取り除ける場合のトルツメ
imageViewと優先度の低い制約をつける
viewから消す場合はトルツメできた。
非表示にする場合はできない。
###viewから取り除けない場合のトルツメ
再利用を前提としていてオブジェクトを取り除きたくない場合、Content Sizeに高さが依存しているオブジェクトであれば、コンテンツをnilにすることでトルツメできるようになる。
####labelを取り除く場合
label.text = nil
####imageの場合
imaageView.image = nil
####StackViewを使った時のトルツメ
isHiddenにするとトルツメできる。
imageView.isHidden = true
##UIStackViewを使ったレイアウト
下記画像のような配置のStackViewを作りたいとする。
####ダメな例
まとめて選択してStackViewにしようとする。
StackViewは部品を一方向に揃えるのでレイアウトか崩れた。
####小さな組み合わせからまとめる
縦方向横方向に並べる部品を細分化して小さなものからStackにしていく。
####それぞれのStackViewの設定
####一番大きなStack
・AlignmentをFillではなくcenterにすることで各要素の幅を別々に設定できるようにした。
・DiscributuonをFillにすることでどんな画面でも最大の幅で表示するようにした。
・Spacingで各要素の間隔を取っている。
####画像をstackをまとめているstack
・AlignmentをTopにすることで文章が長くなっても画像が一番上に来るようにした。
・画像とstackの幅の比率を1:2に設定した。
・DiscributuonをFill Prportionallyにすることで画像の比率を崩さないようにした。
####星の画像をまとめているstack
・DiscributuonをEqual Spacingにしたが、他のどの設定でも良かった。
・Spacingで間隔を空けている。
・AlignmentをFillとして全ての星の画像の比率を1:1にしているが、今回はwidthを等しくする制約をつけているので、高さの制約が必要となった。
widthを等しくする制約を消してstackViewの設定だけで実装しようとした時のレイアウト。
####犬ラベルと説明textViewをまとめているstack
・Alignment、Discributuon共にFillにして横幅いっぱいに表示するようにした。
・textViewのscrolling Enabledをチェックマークを外して全文表示するようにしている。
##コンテンツの高さに合わせてスクロールするView
画面を横にした時にコンテンツの高さだけスクロールできるViewを作る。
Scroll View の中に View を設置し中に部品を置く。
####Viewの制約を追加する。
FrameLayoutGuideに対して、幅を等しくする制約を与える。
ContentLayoutGuideの上下左右と等しくする制約を設定して完成。
現在の仕様ではキーボードを表示した時に一番下のtextFieldが隠れてしまう。
キーボードを表示した時に上にスクロールしてずらす処理を実装する。
import UIKit
class ViewController2: UIViewController, UITextFieldDelegate {
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var textField1: UITextField!
@IBOutlet weak var textField2: UITextField!
@IBOutlet weak var textField3: UITextField!
@IBOutlet weak var textField4: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField1.delegate = self
textField2.delegate = self
textField3.delegate = self
textField4.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc func handleKeyboardWillShow(_ notification: Notification) {
guard let userInfo = notification.userInfo as? [String: Any] else { return }
guard let keyboardInfo = userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue else { return }
guard let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
scrollIndicatorInsets(height: keyboardInfo.cgRectValue.size.height, duration: duration)
}
func scrollIndicatorInsets(height: CGFloat, duration: Double) {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: height, right: 0)
UIView.animate(withDuration: duration){
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.layoutIfNeeded()
}
}
@objc func handleKeyboardWillHide(_ notification: Notification) {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// キーボードを閉じる
textField.resignFirstResponder()
return true
}
}
##コンテンツの大きさに合わせたTableView
TableViewCellクラスを作成してxibファイルと紐づける。
import UIKit
class TableViewCell: UITableViewCell {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var textView: UITextView!
func setCell(label: String, text: String) {
self.label.text = label
self.textView.text = text
}
}
tableViewを作る。先ほど作成したカスタムセルを呼び出す。
####UIViewControllerをStroryBoardで定義している場合
import UIKit
class ViewController2: UIViewController, UITableViewDelegate, UITableViewDataSource{
// 表示するデータ
let displayList = [["1", "hoge\nhoge\nhoge"],["2", "fuga\nfuga"],["3", "piyo"],["4", "a\naa\naaa"]]
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register (UINib(nibName: "TableViewCell", bundle: nil),forCellReuseIdentifier:"TableViewCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// セルの個数を指定するデリゲートメソッド(必須)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayList.count
}
// セルに値を設定するデータソースメソッド(必須)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// セルを取得する
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.setCell(label: displayList[indexPath.row][0], text: displayList[indexPath.row][1])
return cell
}
}
####UIViewControllerをXibで定義している場合
viewDidLoadで下記の2行を追加
tableView.delegate = self
tableView.dataSource = self