오토레이아웃을 더 공부하며 알게 된 점 정리해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
공부하면서 더 추가해나갈 예정입니다. 틀린 부분 혹은 잘못된 부분이 있다면 알려주세요! :)


AutoLayout

view의 절대적인 좌표가 아닌 상대적인 좌표를 요할 때 사용한다.
오토레이아웃이 없으면 view의 크기는 고유 크기를 지니고 있다. (width, height)

multiplier

  • 연결하는 방식에 따라 비율이 달라진다.
  • 1>2, 2>1로 연결하냐에 따라 First Item, Second Item 이 정해지고 비율이 설정된다.

alignmebt + multiplier

  1. 부모뷰를 기준으로 이동
  2. 하나만 선택했을때 가로, 세로 정렬을 선택할 수 있음
    • Horizontally in Container, Vertically in Container 설정
  3. Align CenterX: 0.1 > 왼쪽으로 이동, 1.5 > 오른쪽으로 이동
  4. Align CenterY: 0.1 > 위로 이동, 1.5 > 아래로 이동

View를 겹치게 하기

2개 이상의 겹쳐진 뷰를 만들 때 단순히 자식뷰의 multiplier 값을 변경하는 것으로 한다면 부모 view가 어떤 모양을 가지거나 라운드 처리를 해야하는 등의 view라면
inspector에서 clip to bounds 속성을 체크하게 된다. 이러면 부모뷰에서 겹쳐지지 않은 영역은 잘리게 된다. > 동등하지 않은 레벨의 정렬

view를 겹쳐지게 하기 위해서 자식 뷰를 부모 뷰에서 꺼내고 두 뷰를 선택한 뒤 Top Edges와 Horizontal Centers를 체크한다. > 동등한 레벨의 정렬

그리고 겹쳐질 뷰의 Align Center Y의 Second Item을 Center Y 로 변경한다.

Priority

priority는 우선 Required(1000), High(750), Low(250)이 기본이며 원하는 만큼의 값을 직접 입력할 수 있다.
일단 기본으로 1000priority가 주어지는데 이것은 강한 우선순위로 다른 우선순위에 영향을 준다.

  • Hugging: 내 컨텐츠에 대한 본연의 사이즈 유지 우선순위
  • Compression Resistance: 배치한 방향에 대한 우선순위 (작지않게 버티겠다)
    • 우선순위가 크면 내 크기를 유지하고
    • 우선순위가 작으면 내 크기를 작게한다
    • 숫자가 높을수록 우선순위가 높고
    • 낮은 우선 순위는 늘어나게 된다.

Portrait(세로), Landscape(가로)

  • constraints는 세로모드일때와 같이 동일하게 지정
  • Vary for Traits 메뉴를 통해 width/height 변경에 따라 적용 가능한 디바이스 확인 가능
  • Vary for Traits에서 가로/세로 모드일때 적용하고 싶은 대로 뷰를 다시 정의함

stretching

  • x,y,w,h에 대한 설정 가능
  • 값이 0일때는 시작점붜 늘어나게 하는 것(제약없이 이미지가 늘어남, 단순 이미지 확대용)
    • width를 0.5로 한 경우 시작점부터 50% 영역까지 늘어남
    • y를 0.5로 한 경우 50%영역 이상 늘어남

SnapKit에 대해 아는만큼 정리해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
공부하면서 더 추가해나갈 예정입니다. 틀린 부분 혹은 잘못된 부분이 있다면 알려주세요! :)


SnapKit

SnapKit을 사용할 때는 해석 방향이 중요

make.top.equalTo(viewProgress.snp.bottom).offset(30)

순차적으로 해석 (만들다 > 상단을 > 똑같게 > viewProgress의 하단과 > offset은 30

offset? inset?

  • offset: element와의 간격에 사용
  • inset: superView와의 간격에 사용

Q. childView가 superView로부터 top, bottom, left, right 50 spacing 각각 주려면 어떻게?

1. offset: childView의 constraint = superView의 constraint + offset

self.box.make { (make) in 
  make.top.equalToSuperView().offset(50)
  make.left.equalToSuperView().offset(50)
  make.right.equalToSuperView().offset(-50)
  make.bottom.equalToSuperView().offset(-50)
}

2. inset

self.box.make { (make) in
  make.top.left.bottom.right.equalToSuperView().inset(50)
}

위 코드는 아래와 같게 사용할 수도 있다.

self.box.make { (make) in
  make.edges.equalTo(UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50))
}

그치만 각각의 spacing이 같다면 위 방법이 더 낫다.

left, right / leading, trailing

기본적으로 leading, trailing을 사용하는게 localized 때문인데, 이와 같이 설정하면 right-to-left 순서로 읽는 지역에서는 화면이 거꾸로(flip)되어서 표시된다고 한다. 반면 left, right를 사용하면 안그럼!

  • left, right는 모든 지역에서 같은 방향으로 나오지만 leading, trailing은 오른쪽에서 왼쪽으로 읽는 지역에서는 flip된 ui가 나오게 된다.
  • text를 보여줄때에는 leading, trailing을 사용하는게 나을 것 같으며 그림, 지도와 같이 이미지가 보이는 view를 보여줄 때에는 left, right를 쓰는게 나을 것 같다

전체적인 예시를 한번 보자!

class mainVC: UIViewController {

  // 원하는 객채를 이름붙여 만들어줌 
  let nameLbl = UILabel()
  let nameTextField = UITextField()
  let changeBtn = UIButton()

  // 이름만 만들어준 객체를 실제로 뷰에 띄움 
  override func viewDidLoad() {
    super.viewDidLoad()
    self.view.addSubView(self.nameLbl)
    self.view.addSubView(self.nameTextField)
    self.view.addSubView(self.changeBtn)

    // 아래부터 띄워놓은 객체들에게 각각의 위치를 부여해줌
    self.nameLbl.snp.makeConstraints {
      // self.nameLbl은 superView로부터 center에 위치함 
      $0.center.equalToSuperView()
    }

    self.nameTextField.snp.makeConstraints {
      // self.nameTextField의 top은 superView로 부터 80 떨어짐
      // leading, trailing은 각각 24만큼 떨어짐 
      $0.top.equalToSuperView().offset(80)
      $0.leading.equalToSuperView().offset(24)
      $0.trailing.equalToSuperView().offset(-24)
  }

    self.changeBtn.snp.makeConstraints {
      // changeBtn의 top은 nameLabl의 bottom으로부터 24 떨어짐
      // leading, trailing은 nameLbl과 같게
      $0.top.equalTo(self.nameLbl.snp.bottom).offset(24)
      $0.leading.trailing.equalTo(self.nameLbl)
    }
  }
}

translatesAutoresizingMaskIntoConstraints = false

코드로 constraints를 잡으려면 translatesAutoresizingMaskIntoConstraints = false를 명시해줘야하지만
snapKit은 translatesAutoresizingMaskIntoConstraints = false를 내부에서 알아서 해준다.

따라서 굳이 우리가 이를 명시해주지 않아도 되며, 아래는 snapKit의 LayoutConstraintItem 파일에 적혀있는 코드이다.

extension LayoutConstraintItem {
  internal func prepare() {
    if let view = self as? ConstraintView {
      view.translatesAutoresizingMaskIntoConstraints = false
    }
  }
}

Anchor

1. 모든 앵커와 제약 조건 자체를 함께 연결하는 것이 가능

child.snp.makeContraints { make in 
  make.leading.top.trailing.bottom.equealToSuperView()
}

2. edges를 이용해 더욱 간편하게 사용 가능

child.snp.makeConstraints { make in 
  make.edges.equalToSuperView()
}

3. view에 inset 값을 주고싶다면 inser() 가능

child.snp.makeConstraints { make in 
  make.edges.equealToSuperView().inset(15)
}

Constraints

  • multipliedBy()
make.width.equalToSuperView().multipliedBy(0.45)

superView와 width를 같게 만들면서 0.45를 곱한 상태

암시

constraints 기준이 되는 view 값을 최대한 짧게 작성한다.
아래 코드는 다 같은 의미를 지닌다.

view.snp.makeConstraints { make in 
  make.width.equalTo(otherView.snp.width
  make.centerX.equalTo(otherView.snp.centerX)
}

view.snp.makeConstraints { make in 
  make.width.equalTo(otherView)
  make.centerX.equalTo(otherView)
}

view.snp.makeConstraints { make in 
  make.width.centerX.equalTo(otherView)
}

UpdateConstraints

constraints 기준이 될 기존에 지정한 view가 바뀌는게 아닌 constant value(상수값)만 업데이트

extension QuizVC {
  override func willTransition (
    to newCollection: UITraitCollection,
    with coordinator: UIViewControllerTransitionCOordinator
  ) {
    super.willTransition(to: newCollection, with: coordinator)

    // 현재 방향을 설정(bool)
    let isPortrait = UIDevice.current.orientation.isPortrait

    // 세로일 경우 45, 가로일 경우 65로 높이를 바꿔줌
    lblTimer.snp.updateConstraints { make in 
        make.height.equalTo(isPortrait ? 45 : 65)
    }

    // 글꼴 크기를 변경해줌 
    lblTimer.font = UIFont.systerFont(ofize: isPortrait ? 20 : 32, weight: .light)
  }
} 

RemakeConstraints

constant value만 변경되는 것이 아닌 기준이 될 view도 업데이트
기존에 존재하던 constraints는 삭제된다.

func updateConstraints {
  pView.snp.remakeConstraints { make in 
    make.top.equalTo(view.safeAreaLayoutGuide)
    make.width.equalToSuperView().multipliedby(0.1)
    make.height.equalTo(32)
    make.leading.equalToSuperView()
  }
}

lessThanOrEqualTo, priority > 아직 공부 더 필요

override func updateConstaints() {
  self.prowingBtn.snp.updateConstraints { make in
    make.center.equalTo(self);
    make.width.height.equalTo(buttonSize).priority(250)
    make.width.height.lessThanOrEqualTo(self)
  }
  super.updateConstraints()
}

UIRefresh controller 사용해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


UIRefresh Controller

리스트 형식의 테이블 뷰를 사용할때 새로고침 버튼을 두는것보다는 테이블 뷰를 아래로 당김으로써 새로고침을 진행하는 것이 좋다고 한다.
실제로 우리가 많은 앱을 사용하다 보면 앱의 최 상단을 위에서 아래로 잡아당겼을 경우 새로고침이 되는 경우가 많음을 쉽게 떠올릴 수 있을 것이다.

@available(iOS 6.0, *)
open class UIRefreshControl : UIControl {


    /* The designated initializer
     * This initializes a UIRefreshControl with a default height and width.
     * Once assigned to a UITableViewController, the frame of the control is managed automatically.
     * When a user has pulled-to-refresh, the UIRefreshControl fires its UIControlEventValueChanged event.
     *
    */
    public init()


    open var isRefreshing: Bool { get }


    open var tintColor: UIColor!

    open var attributedTitle: NSAttributedString?


    // May be used to indicate to the refreshControl that an external event has initiated the refresh action
    @available(iOS 6.0, *)
    open func beginRefreshing()

    // Must be explicitly called when the refreshing has completed
    @available(iOS 6.0, *)
    open func endRefreshing()
}

UIRefreshControl 내부를 살펴보면 iOS6.0 버전 이상부터 지원이 되고있으며 UIControl를 상속받고 있음을 볼 수 있다.
isRefreshing 은 현재 리프레쉬가 진행중인지를 나타나며 tintColor 를 통해 색의 변화도 줄 수 있다.
attributedTitle 를 통해 새로고침 indicator 아래에 다른 글귀를 줄 수도 있다

  • beginRefreshing: 새로고침이 시작됐음을 알려준다
  • endRefreshing: 새로고침이 끝났음을 알려준다

실제 구현해보기

테이블뷰가 구현되어있는 뷰 컨트롤러로 가봅시다.

class HomeVC: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        initUI()
        initRefresh()
    }

    func initUI() {
        self.tableView.delegate = self
        self.tableView.dataSource = self
    }

    func initRefresh() {
        let refresh = UIRefreshControl() // UIRefreshControl 인스턴스화
        refresh.addTarget(self, action: #selector(updateTableView(refresh:)), for: .valueChanged)  // target 설정
        if #available(iOS 10.0, *) {
            tableView.refreshControl = refresh
        } else {
            tableView.addSubview(refresh)
        }
    }

    @objc func updateTableView(refresh: UIRefreshControl) {
        refresh.endRefreshing()
        tableView.reloadData()
    }
}

구체적인 테이블 뷰에 들어가는 데이터는 생략하였습니다.

UIRefreshControl를 인스턴스화 하여 target 설정을 해주면 스와이프 액션이 취해졌을 때, 연결해놓은 updateTableView 함수를 호출합니다.
updateTableView함수가 호출되면 리프레쉬를 종료하는 endRefreshing 함수를 호출함으로써
initRefresh 함수는 종료하며 tableView는 reloadData를 하게 됩니다.

masksToBounds와 clipsToBounds 차이점?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


예시 상황

어떤 네모난 textView 안에 “안녕하세요”라는 텍스트가 들어있다고 생각해보자.
이때 이 네모난 textView를 동그랗게 즉, radius를 줬다고 하자. 그러면 textView의 텍스트는 어떻게 될까?

아마도 네모난 부분에서 동그랗게 변해진 부분에 존재하는 텍스트는 동그래진 만큼 잘려서 보이지 않게 될 것이다.

때에 따라서는 이 텍스트가 잘리면 안되는 상황도 분명 존재할텐데!
그때 바로 사용하는 것이 clipsToBounds, masksToBounds이다.

우선 이 둘이 하는 기능 자체는 똑같은데, 불러오는 곳이 다르다.

textView.layer.masksToBounds
textView.clipsToBounds

즉, masksToBounds는 layer의 프로퍼티이고 clipsToBounds는 그냥 textView의 프로퍼티이다.
이때 알아두면 좋을 건 textView 뿐만 아니라, 버튼, 라벨에서도 불러올 수 있는 프로퍼티라는 것!

masksToBounds

open var masksToBounds: Bool

masksToBounds의 기본값은 false이다. 그렇다면 이때 true, false가 의미하는것은 무엇일까?

  • true: textView의 테두리가 기준으로, 위에 설명처럼 텍스트는 잘리게 된다.
    • textView라는 큰 뷰가 있고 그 안에 “안녕하세요”라는 텍스트가 있는것으로 큰 뷰를 기준으로 바깥에 있는것은 잘리게 된다.
  • false: textView 안의 내용 또한 잘리지 않고 “안녕하세요” 모두 보이게 된다.

clipsToBounds

open var clipsToBounds: Bool  // When Yes, content and subviews are clipped to the bounds of the view. Default is No

clipsToBounds 또한 masksToBounds와 같이 Bool 타입이다.
주석 내용또한 살펴보면 true일때 내용들과 서브뷰들은 뷰의 테두리를 기준으로 잘리게 되며 default는 false이다.

masksToBounds와 같이 false를 주지 않으면 안의 내용은 잘리게 된다.

tintColor란 무엇이며 image renderingMode 살펴보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


tintColor

tintColor란 시각적으로 화면 상의 어떤 요소가 현재 활성화되었는지를 보여주는 요소

예로들어 NavigationBar의 아이템의 refresh 버튼이나 back 버튼을 누르면 단순히 눌리고 끝나는 것이 아니라 눌려졌을때 흰색으로 살짝 변했다가 다시 원래의 색으로 돌아오는 것이 그 예시이다. 이런 효과를 가능하게 하는것이 바로 tintColor이다. 기본적으로 tintColor는 UIView의 프로퍼티로 존재하기에 UIView를 상속받는뷰들은 모두 tintColor를 적용시킬 수 있다!

따라서 view에 tintColor를 사용하기 위해서는 약간의 방법이 필요한데
그 방법은 바로 image의 renderingMode를 .alwaysTemplate으로 바꿔야 하는 것이다.

renderingMode

UIImage의 renderingMode코드를 살펴보면 아래와 같이 세가지 옵션이 있다.

@available(iOS 7.0, *)
    public enum RenderingMode : Int {


        case automatic = 0 // Use the default rendering mode for the context where the image is used


        case alwaysOriginal = 1 // Always draw the original image, without treating it as a template

        case alwaysTemplate = 2 // Always draw the image as a template image, ignoring its color information
    }

1. automatic

이미지 렌더링 모드 중에서 automatic은 default 값이다.
뷰 상에서는 제대로 나오지만 탭바에서 이미지가 불투명한 부분을 틴트컬러로 보이게 한다.

let image = UIImage(named: "")?.withRenderingMode(.automatic)

2. alwaysOriginal

원본 이미지에서 컬러정보가 모두 보이는 것으로 말 그대로 원본의 색상 정보 그대로 이미지에 보여진다.

let image = UIImage(named: "")?.withRenderingMode(.alwaysOriginal)

3. alwaysTemplate

원본 이미지에서 컬러 정보가 모두 제거되고 불투명한 부분을 틴트컬러로 보이게 한다.
즉, 원본 이미지가 가지고 있는 컬러정보는 사라지고 내가 지정한 tintColor로 이미지의 색상이 보여지는 것을 의미한다.

let button = UIButton()
button.setImage(UIImage(named: "")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = UIColor.red

이런식으로 코드가 짜여진다면 버튼에 들어가는 이미지의 원본 색상은 사라지고 내가 지정한 tintColor인 red 색상이 버튼 이미지의 색상이 될 것이다.