본문 바로가기

iOS-Study

iOS Study : 9주차 - 오토레이아웃 정리(1)

오토 레이아웃

  • 자동으로 레이아웃을 계산하여 그려주는 것
  • 해당 뷰의 x,y의 위치(가로,세로 위치)와 가로,세로의 크기(사이즈)를 앵커를 걸면 자동으로 뷰를 그려줌 
  • 아이폰의 기기마다 크기가 다르기 때문에 앵커를 걸면 기기가 바뀌어도 자동으로 위치가 지정됨

Main에서 뷰를 클릭한 후 Option이나 Alt키를 누르면 아래처럼 사이즈가 뜨게 된다.

 

앵커 거는 법

위에서 앵커를 걸면 아래처럼 선이 생기면서 왼쪽에 앵커를 걸게 되는 것이다.

크기도 위에서 크기 부분에 앵커를 걸면 된다.

 

위에서 Clear Constraints를 클릭해서 앵커를 전부 없앨 수 있고, Add Missing Constrainsts는 Xcode에서 자동을
빼먹은 앵커를 걸어준다.

 

위에처럼 가로와 세로의 정중앙에 위치시키는 것도 가능하다. 이 경우 세로의 크기 대신 위나 아래의 위치의 앵커를 걸어줘도
자동으로 오토레이아웃이 된다.

 

미싱 컨스트레이트 잡아주기

  • 아래 양쪽에서 앵커를 걸었는데 크기를 고정시키게 되면 빨간색이 뜨게 되지만 실행은 정상적으로 된다.
    이는 크기보다는 앵커를 더 우선적으로 보기 때문에 양쪽에서 잡아당기게 되면 크기는 무시하게 되는 것이다.
  • 정상적으로 실행이 되더라도 오류이기 때문에 이를 해결해야 한다. 해결하기 위해서는 View Controller Scene 옆에 빨간색 화살표를 눌러주면 어디서 오류가 났는지 해결하는 부분이 나온다. 이를 통해 해결하면 된다.

뷰 복사 방법

alt 혹은 Option키를 누른 상태로 뷰를 드래그하면 뷰가 복사가 된다.

 

뷰끼리 컨스트레인트 걸기

아래처럼 blue 뷰의 왼쪽 앵커의 기준을 green으로 하고 싶으면 해당 앵커를 클릭 후 inspector창에서 바꾸면 된다.

first item은 뷰 자신이고 seconde item은 기준이 되기 때문에 green.Leading으로 바꾸면 blue의 왼쪽을 green의 왼쪽으로
바꾸겠다는 의미가 된다.

또 다른 방법

  • 다른 방법으로는control키를 누른 후 드래그를 해서 옵션을 설정하는 방법도 있다.
  • 아래처럼 orange에서 blue로 드래그를 하게 되면 orange를 blue에 맞춘다는 의미이고 옵션을 선택할 수 있다.
  • 여기서 leading을 누르면 blue의 왼쪽에 맞춰서 정렬이 되지만 Option키를 누른 상태에서 클릭 하면 현재 상태에 따라
    떨어진 위치에 정렬이 되고 연결이 된다.
  • 연결 전에 orange의 크기를 고정시켜 주어야 한다.

 

셀프 사이징

라벨에 따른 뷰 크기 변경

  • 라벨의 경우 크기는 정해져 있기 때문에 앵커만 걸어주면 고정이 된다.
  • Lines를 0으로 하면 글씨가 많아져도 고정된 크기 이상 넘어가지 않고 다음 줄로 넘어가게 된다.

  • 뷰 안에 라벨을 넣을 경우 뷰의 앵커를 세로의 위치가 세로 크기를 고정하지 않아도 안에 있는 라벨을 고정시키게 되면
    라벨의 크기가 커지면 뷰도 같이 커지게 된다.

  • 만약 뷰의 크기에 제한을 두고 싶으면 height를 걸고 인스펙터 창에서 equal이 아닌 less than or equal로 바꾸게 되면
    라벨의 크기가 지정한 heigth의 크기를 넘어가지 않는다.

다이나믹 테이블뷰

컨텐츠 내용에 따라 셀의 크기를 자동으로 설정하도록 하는 것

  • Main에서 table View를 추가한 뒤 사방에 0으로 앵커를 걸고 bottom 앵커의 경우 first item을 Superview로 바꾸고
    constant를 0으로 설정 해 준다.

  1. cmd + N을 눌러서 empty 파일을 생성한 뒤 image View를 추가해 준뒤 크기와 위, 왼쪽 앵커를 고정시켜 준다.
  2. Vertical Stack View를 추가해 준 뒤 control 드래그를 imageView에 하고 Equal Height와 Top을 눌러서
    y축 오토레이아웃을 맞춘다. 그리고 왼쪽 앵커를 고정한다.
  3. Stack View에 label을 2개 추가해 준 뒤, StackView에서 Fill Equally로 라벨들의 크기를 정렬한다.
  4. 각 라벨을 꾸며준다.

  1. label을 하나 더 추가해서 image View 밑에 놓고 드래그로 Leading을 클릭한다.
  2. 위와 오른쪽에 앵커를 걸어 주고 오른쪽 앵커는 Greater Than or Equal로 바꿔준다.
  3. horizantal stack view를 라벨 아래에 추가한 후 위 앵커를 걸고 위 라벨에 드래그하여 Leading을 맞춰준뒤 안에 버튼을 추가한다.
  4. 추가한 버튼의 크기를 고정시키고 복사해서 3개를 꾸며준 뒤 stack view 아래에 앵커를 걸어준다.
  5. 내용 부분의 라벨에 긴 글을 쓰면 Table View Cell의 크기에 맞에 늘어나게 된다.

  • 아래처럼 ViewController를 열어서 Main에 있는 TableView를 ViewController에 연결해 주고 코드를 작성해 준다.

//ViewController.swift파일
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var myTableView: UITableView!
    
    
    let contentArray = [
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sollicitudin cursus mi, a cursus leo consectetur vel. Sed maximus sapien est, ac hendrerit dolor suscipit vel. Mauris consectetur turpis faucibus metus efficitur pharetra. Donec ullamcorper neque sit amet lacinia suscipit. Cras facilisis pulvinar dignissim. Morbi in vulputate dolor, vitae laoreet nisi. Fusce tincidunt urna vel efficitur auctor. Proin suscipit sodales tincidunt. Nam sed mattis turpis, quis gravida quam. Proin in consequat magna. Pellentesque mollis velit nec purus dapibus viverra. Curabitur ac tempus ex, ac volutpat felis. In ornare velit sit amet mi fermentum aliquam. Donec fermentum libero eros, et gravida dui sollicitudin at.",
        
        "Mauris non tincidunt nulla. Sed dictum semper elit eget molestie. Morbi ac purus non velit efficitur rutrum vel nec diam. Phasellus auctor erat vel tempor accumsan. Sed dapibus magna sed urna aliquam auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed volutpat erat posuere porta ultricies.",
        
        "Quisque non nibh sed mi suscipit hendrerit at eget tellus. Cras lectus erat, vehicula semper sagittis id, dictum commodo est. Morbi eu urna sollicitudin, volutpat mi nec, feugiat nunc. Aliquam blandit dapibus nulla, at tristique arcu interdum vel. Cras aliquam elit quam, sit amet auctor metus faucibus laoreet. Nam eget mauris sit amet ante euismod condimentum. Donec purus neque, ullamcorper eu fermentum a, blandit venenatis nisl.",
        
        "Sed nec sem auctor est euismod fringilla. Ut viverra orci et dictum lacinia. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer malesuada dapibus lacinia. Nulla rutrum consectetur purus, id pretium justo elementum sed. Sed bibendum erat libero, non dapibus purus interdum sed. Sed cursus nisi nec commodo semper. Duis eleifend semper diam. Curabitur ut pulvinar diam."
    ]
    

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        //셀 리소스 파일 가져오기
        let myTableViewCellNib = UINib(nibName: String(describing: MyTableViewCell.self), bundle: nil)
        
        //셀에 리소스 등록
        self.myTableView.register(myTableViewCellNib, forCellReuseIdentifier: "myTableViewCell")
        
        self.myTableView.rowHeight = UITableView.automaticDimension
        self.myTableView.estimatedRowHeight = 120
        
        //아주 중요
        self.myTableView.delegate = self
        self.myTableView.dataSource = self
        
        print("contentArray.count : \(contentArray.count)")
    }


}

extension ViewController: UITableViewDelegate {
    
}

extension ViewController: UITableViewDataSource {
    
    //테이블 뷰 셀의 개수
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.contentArray.count
    }
    
    //각각 셀에 대한 설정
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = myTableView.dequeueReusableCell(withIdentifier: "myTableViewCell", for: indexPath) as! MyTableViewCell
        
        cell.userContentLabel.text = contentArray[indexPath.row]
        
        return cell
    }
    
    
}
  • MyTableViewCell.swift 파일을 만들어서 MyTableViewCell.xib에 있는 이미지와 내용을 드래그래서 연결시켜주고
    코드를 작성해 준다.

//MyTableViewCell.swift 파일

import Foundation
import UIKit

class MyTableViewCell: UITableViewCell {
    
    @IBOutlet weak var userProfileimg: UIImageView!
    
    @IBOutlet weak var userContentLabel: UILabel!
    
    //셀이 렌더링(그릴때) 될 때
    override func awakeFromNib() {
        super.awakeFromNib()
        
        print("MyTableViewCell - awakeFromNib() called")
        userProfileimg.layer.cornerRadius = userProfileimg.frame.width / 2
    }
}

 

  • 실행을 해보면 아래와 같은 결과가 나온다.

 

스크롤 뷰

  • 스크롤뷰의 크기와 위치를 잡는다. -> UIView로 컨테이너 뷰를 잡으면 편하다.
  • 스크롤 뷰의 Content layout이 스크롤 가능한 영역을 나타낸다.
  • 스크롤 뷰 안에 들어가는 뷰의 앵커와 Content layout의 앵커를 일치시킨다.
  • 스크롤이 가능한 영역을 조절하는 frame layout guide와 컨테이너 뷰를 맞춘다.

control을 누른 상태에서 view에서 Content layout으로 드래그 한뒤 shift를 누른 상태에서 위 4개를 다 체크해줘서 크기를 맞춘다.

그리고 view의 Constraints에서 constant를 다 0으로 바꿔준다.

이번에는 view에서 Frame Layout Guide로 드래그 해서 Equal Widths를 선택한다.

이제 안에 앞에서 했던 것처럼 라벨을 넣어서 고정시켜 주고 내용을 넣으면 스크롤 뷰가 완성이 된다.

 

컨스트레인트 우선 순위

  • 앵커가 서로 충돌될 때 우선순위를 지정해 줄 수 있고 우선순위가 낮은거는 무시한다.
  • 우선순위 지정은 앵커 클릭 후 Inspector 창에서 지정 가능하며 1000이 가장 높다.

 

Hugging

  • 안으로 끌어 당기기
  • 공간이 남아서 당겨지는 상황

위 사진에서 Content Hugging Priority 부분의 우선순위에 따라 우선 순위가 높은 쪽을 기준으로 빈 공간은 당겨지게 된다.

 

Compression Reistance

  • 밖으로 밀어내기
  • 공간이 없어서 쪼그라드는 상황

Hugging처럼 Content Compression Resistance Priority 우선순위에 따라 우선순위가 더 높은 쪽을 기준으로 앵커 크기 이상으로

넘어갈 경우 밀려나게 된다.

 

스택뷰

  • 자동으로 크기를 조정하고 균등하게 배치하기 때문에 빠른 작업이 가능하다
  • 스택뷰 안에 넣으려는 것들은 크기가 제공되어 있어야 한다.
  • 스택뷰에서 Distribution을 Fill이 아닌 Fill Equaly로 하게 되면 처음 넣은 뷰의 크기에 맞춰져서 들어가게 된다.
    Fill Proportionally로 하면 내용물에 맞게끔 크기가 조정된다.

색이 있는 뷰를 넣어서 크기를 고정시킨 뒤 새로운 뷰를 넣었을 때 자동으로 크기가 맞춰져서 들어가게 된다.

  • 스택 뷰의 Spacing은 스택뷰에 들어가 있는 뷰 사이의 간격을 나타낸다.
  • Alignment는 스택뷰 안에 있는 뷰들의 위치를 적용시킨다.

Spacing 20으로 라벨들 사이의 간격이 늘어났고, Alignment를 Top으로 해서 라벨들이 맨 위에 위치해 있다.

 

배운 것을 이용하여 만들기

  • 휴대폰의 전화번호 처럼 버튼을 클릭하면 숫자가 쳐지고 통화버튼을 누르면 지워지는 화면을 만든다.
  • 스택뷰를 활용하여 버튼들을 만들고 버튼을 동그랗게 만들기 위한 코드를 만들어 적용시킨다.

CircleButton.swift 파일을 만들어서 cornerRadius를 Insepctor창에서 조절이 가능하도록 한 뒤 버튼을 구성한다.

//CircleButton.swift
import Foundation
import UIKit

//IBDesignable = 인터페이스 빌더에서 디자인으로 확인 가능하게끔
@IBDesignable
class CircleButton: UIButton {
    //IBInspectable = 인스펙터 패널에서 사용될 수 있도록 설정
    @IBInspectable var cornerRadius: CGFloat = 0 {
        didSet {
            layer.cornerRadius = cornerRadius
            layer.masksToBounds = cornerRadius > 0
        }
    }
}

코드를 적용시키면 Insepctor 창에서 cornerRadius를 조절하는 부분이 생기고 실시간으로 변하는 것이 확인 가능하다.

  • 번호를 입력할 라벨을 추가해서 스택뷰와 Leading과 Trailling을 맞추고 아래 앵커를 주고 폰트를 조정한다.
  • 라벨과 버튼들을 ViewController에 연결을 해준다. 이때 버튼의 경우 너무 많아지기 때문에 버튼 하나를 Outlet Collection으로
    연결하고 버튼들을 연결해준다.

Outlet Collection부분에 +를 드래그하여 버튼에 연결하면 된다. 마우스를 +부분에 올리면 연결되어 있는 버튼들이 표시된다.

  • 동작에 필요한 기능들을 위한 코드를 작성해 주고 실행을 한다.
//ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var phoneNumberLabel: UILabel!
    
    @IBOutlet var phoneNumberBtns: [CircleButton]!
    
    @IBOutlet weak var phoneCallButton: CircleButton!
    
    var phoneNumberString = "" {
        didSet {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.phoneNumberLabel.textColor = .black
                self.phoneNumberLabel.text = self.phoneNumberString
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        
        for btnItem in phoneNumberBtns {
            btnItem.addTarget(self, action: #selector(onNumberBtnClicked(sender: )), for: .touchUpInside)
        }
        
        phoneCallButton.addTarget(self, action: #selector(onphoneBtnClicked(_:)), for: .touchUpInside)
    }
    
    @objc fileprivate func onNumberBtnClicked(sender: UIButton) {
        guard let inputString =  sender.titleLabel?.text else { return }
        phoneNumberString.append(inputString)
    }
    
    @objc fileprivate func onphoneBtnClicked(_ sender: UIButton) {
        phoneNumberString.removeAll()
    }


}