5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

レイアウト実装パターン

Last updated at Posted at 2020-01-09

趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。

参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

###いくつかレイアウトの実装方法について書いてます

##グループ化
UIの部品をUIViewによりグループ化し、中身のコンテンツの大きさにより高さと幅が下辺となるviewを作る。
IB上ではUILayoutGuideが使用できないのでUIViewで空間を作る。
ただし、空間の色を透明にすると描画処理が重くなってしまうので、出来れば色を指定した方がいい。

下記の例のviewは、x座標と上からの距離のみ指定されているが制約エラーは出ていない。
これは中身のviewにより高さと幅が決まっているためである。

image.png

まず高さのみ制約を見ると、コンテンツの高さ+制約で高さが求められている。
image.png
横幅も同じようになっている。
image.png

その他の制約
image.png

中身の部品はグループ化に使っているviewまたは互いに対して制約を指定しているので、画面を横にしても位置関係が変わることはない。
image.png

コンテンツが大きくなるとviewも大きくなる。
image.png

しかし今のレイアウトではコンテンツが入らない時に画像が隠れてしまう。
image.png

はみ出した時に画像だけは画面に入るように制約を加えてみる。
親view.width >= view.widthとなるように制約を加える。
関係を >= とすることでコンテンツが小さい時は親viewのwidthに依存しないようにした。

image.png

ラベルのTrailing Spaceの制約の優先度を下げて表示を行う。
image.png

エラーがなくなって表示できるようになった。
image.png

最終的なviewと部品達の制約。特に重要なのが多い部分。
image.png

##同一幅のオブジェクト
image.png

幅を等しくするオブジェクトを全て選択して、EqualWidthを選択。
image.png

各々制約を設定して終わり。
image.png

####オブジェクト間の幅を等しくしたい場合
まずボタン同士の幅を等しくする。
image.png

viewを2つ定義してAとBの間BとCの間に設置し、それぞれボタンとくっつける制約を定義する。
(ついでに高さをボタンと揃える)
最後にview同士の幅を等しくして終わり。
image.png

##トルツメパターン
トルツメ : 不用なインターフェースを削除してその間のスペースを詰めること。
image.png
UITextViewを取り除きたい。

###トルツメ失敗パターン

####viewから消した場合

    textView.removeFromSuperview()

image.png

####非表示にした場合

    textView.isHidden = true

image.png

###viewから取り除ける場合のトルツメ
imageViewと優先度の低い制約をつける
image.png

viewから消す場合はトルツメできた。
非表示にする場合はできない。
image.png

###viewから取り除けない場合のトルツメ
再利用を前提としていてオブジェクトを取り除きたくない場合、Content Sizeに高さが依存しているオブジェクトであれば、コンテンツをnilにすることでトルツメできるようになる。

####labelを取り除く場合

    label.text = nil

image.png

####imageの場合

    imaageView.image = nil

####StackViewを使った時のトルツメ
isHiddenにするとトルツメできる。
image.png

    imageView.isHidden = true

実行時
image.png

##UIStackViewを使ったレイアウト
下記画像のような配置のStackViewを作りたいとする。
image.png

####ダメな例
まとめて選択してStackViewにしようとする。
StackViewは部品を一方向に揃えるのでレイアウトか崩れた。
image.png

####小さな組み合わせからまとめる
縦方向横方向に並べる部品を細分化して小さなものからStackにしていく。
image.png

このような見た目になるのでレイアウトを修正する。
image.png

修正後
image.png

####それぞれのStackViewの設定
####一番大きなStack
・AlignmentをFillではなくcenterにすることで各要素の幅を別々に設定できるようにした。
・DiscributuonをFillにすることでどんな画面でも最大の幅で表示するようにした。
・Spacingで各要素の間隔を取っている。
image.png

####画像をstackをまとめているstack
・AlignmentをTopにすることで文章が長くなっても画像が一番上に来るようにした。
・画像とstackの幅の比率を1:2に設定した。
・DiscributuonをFill Prportionallyにすることで画像の比率を崩さないようにした。
image.png

####星の画像をまとめているstack
・DiscributuonをEqual Spacingにしたが、他のどの設定でも良かった。

・Spacingで間隔を空けている。
・AlignmentをFillとして全ての星の画像の比率を1:1にしているが、今回はwidthを等しくする制約をつけているので、高さの制約が必要となった。

image.png

widthを等しくする制約を消してstackViewの設定だけで実装しようとした時のレイアウト。
image.png

####犬ラベルと説明textViewをまとめているstack
・Alignment、Discributuon共にFillにして横幅いっぱいに表示するようにした。
・textViewのscrolling Enabledをチェックマークを外して全文表示するようにしている。
image.png

##コンテンツの高さに合わせてスクロールするView
画面を横にした時にコンテンツの高さだけスクロールできるViewを作る。
image.png

Scroll View の中に View を設置し中に部品を置く。
image.png

部品だけでViewの高さを決定するように配置を行う。
image.png

####Viewの制約を追加する。
FrameLayoutGuideに対して、幅を等しくする制約を与える。
image.png

ContentLayoutGuideの上下左右と等しくする制約を設定して完成。
image.png

現在の仕様ではキーボードを表示した時に一番下のtextFieldが隠れてしまう。
image.png

キーボードを表示した時に上にスクロールしてずらす処理を実装する。
image.png

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

image.png

TableViewCellクラスを作成してxibファイルと紐づける。
image.png

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を作る。先ほど作成したカスタムセルを呼び出す。

image.png

####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
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?