
이제는 프로그래밍으로도 View를 짜는법을 해볼거
오토레이아웃을 위한 사각형 영역
10개의 앵커(Anchor)를 가지고 있다.
• 8개의 앵커는 좌표를 의미하며
• 2개(wdithAnchor, heightAnchor)는 크기를 의미함

LayoutGuide를 이용한 설정
– 각 Anchor는 constraint() 함수가 있어 이를 이용하여 아래와 같이 설정

// 올바른 접근법
let myButton = UIButton(type: .system)
view.addSubview(myButton) // 계층에 추가
myButton.translatesAutoresizingMaskIntoConstraints = false
// 이제 제약조건 설정 가능
// 잘못된 접근법
let wrongButton = UIButton(type: .system)
// 계층에 추가하지 않음 - 제약조건 적용 불가
wrongButton.translatesAutoresizingMaskIntoConstraints = false
A.translatesAutoresizingMaskIntoConstraints = false// 기본 레이아웃 설정 예제
NSLayoutConstraint.activate([
// 위치(x, y) 설정
myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
// 크기(w, h) 설정
myView.widthAnchor.constraint(equalToConstant: 100),
myView.heightAnchor.constraint(equalToConstant: 50)
])
참고사항
- Autoresizing: 초기 iOS에서는 다양한 Device 크기를 만족하는 App을 만들기 위해서 오토리사이징 개념 을 도입하였음. 그러나 곧 다양성을 지원하기에는 한계가 있음을 알고 autolayout를 추가함. 그래서 하 나의 View에 대한 constraint가 두 방식을 양립함
- Interface Builder: 인터페이스 빌더에서 컨트롤을 놓으면, 자동으로 Autoresizing은 리셋됨, 즉 autolayout만 적용됨
- Programming: autoresizing이 기본적으로 동작하도록 설정됨, 그래서 이를 해제하기 위해서 translatesAutoresizingMaskIntoConstraints를 false로 설정함
– “Hello, Autolayout” Label
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 위치와 크기를 지정하여 UILabel을 생성
let helloLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 30))
// 생성된 UILabel을 꾸민다.
helloLabel.text = "Hello, Autolayout" // 레이블에 표시할 텍스트 설정
helloLabel.backgroundColor = UIColor.green // 배경색을 녹색으로 설정
helloLabel.font = UIFont.systemFont(ofSize: 30, weight: .bold) // 폰트를 30pt 크기, 볼드체로 설정
helloLabel.textAlignment = .center // 텍스트 정렬을 가운데로 설정
// 계층적 뷰에 연결한다.
view.addSubview(helloLabel) // 생성한 레이블을 현재 뷰의 하위 뷰로 추가
}
}
참고로 클래스 연결해야함


2. 동적 방법: 고정 레이아웃과 동일한 효과이다
override func viewDidLoad() {
super.viewDidLoad()
//let helloLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 30))
let helloLabel = UILabel() // 위의 것을 대신하여 생성한다.
// 생성된 UILabel을 꾸민다.
helloLabel.text = "Hello, Autolayout"
helloLabel.backgroundColor = UIColor.green
helloLabel.font = UIFont.systemFont(ofSize: 30, weight: .bold)
helloLabel.textAlignment = .center
// 계층적 뷰에 연결한다.
view.addSubview(helloLabel)
// 오토리사이징을 해제한다.
helloLabel.translatesAutoresizingMaskIntoConstraints = false
// 좌, 상, 너비, 높이에 대한 제약 조건을 생성한다.
let leadingConstraint = helloLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100)
let topConstraint = helloLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
let widthConstraint = helloLabel.widthAnchor.constraint(equalToConstant: 200)
let heightConstraint = helloLabel.heightAnchor.constraint(equalToConstant: 30)
// 좌, 상, 너비, 높이에 대한 제약 조건을 활성화한다.
leadingConstraint.isActive = true
topConstraint.isActive = true
widthConstraint.isActive = true
heightConstraint.isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
//let helloLabel = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 30))
let helloLabel = UILabel() // 위의 것을 대신하여 생성한다.
// 생성된 UILabel을 꾸민다.
helloLabel.text = "Hello, Autolayout"
helloLabel.backgroundColor = UIColor.green
helloLabel.font = UIFont.systemFont(ofSize: 30, weight: .bold)
helloLabel.textAlignment = .center
// 계층적 뷰에 연결한다.
view.addSubview(helloLabel)
// 오토리사이징을 해제한다.
helloLabel.translatesAutoresizingMaskIntoConstraints = false
// 좌, 상, 너비, 높이에 대한 제약 조건을 생성한다.
let leadingConstraint = helloLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100)
let topConstraint = helloLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
let widthConstraint = helloLabel.widthAnchor.constraint(equalToConstant: 200)
let heightConstraint = helloLabel.heightAnchor.constraint(equalToConstant: 30)
// 좌, 상, 너비, 높이에 대한 제약 조건을 활성화한다.
leadingConstraint.isActive = true
topConstraint.isActive = true
widthConstraint.isActive = true
heightConstraint.isActive = true
}
NSLayoutConstraint.activate([
// view와 helloLabel 사이의 제약조건 (view에 속함)
helloLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100),
helloLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
// helloLabel 자체의 제약조건 (helloLabel에 속함)
helloLabel.widthAnchor.constraint(equalToConstant: 200),
helloLabel.heightAnchor.constraint(equalToConstant: 30)
])
// 1. 레이블 생성
let helloLabel = UILabel()
helloLabel.text = "Hello, Autolayout"
// 2. 뷰 계층에 추가
view.addSubview(helloLabel)
// 3. 오토리사이징 비활성화 (필수)
helloLabel.translatesAutoresizingMaskIntoConstraints = false
// 4. 제약조건 설정 및 활성화
NSLayoutConstraint.activate([
helloLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100),
helloLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
helloLabel.widthAnchor.constraint(equalToConstant: 200),
helloLabel.heightAnchor.constraint(equalToConstant: 30)
])
Todo
– 사진을 assets에 복사한다.
– UIImageView를 생성(imageView)한다.
– imageView.image = UIImage(named: “사진이름적으셈”)를 하여 이미지를 삽입한다.
– imageView를 view의 자식으로 등록한다.
– imageView의 translatesAutoresizingMaskIntoConstraints를 fasle한 다
– imageView의 상하좌우 여백을 0로 설정한다.

Asset에 사진 놓기
import UIKit
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 이미지 뷰를 위한 뷰를 생성한다
let imageView = UIImageView()
imageView.image = UIImage(named: "image") // assets에 있는 이미지를 객채화
view.addSubview(imageView) // 자식으로 등록한다
imageView.translatesAutoresizingMaskIntoConstraints = false
// 상하좌우에 대한 0으로 여백을 설정한다
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}


import UIKit
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 이미지 뷰를 위한 뷰를 생성한다
let imageView = UIImageView()
imageView.image = UIImage(named: "image") // assets에 있는 이미지를 객채화
view.addSubview(imageView) // 자식으로 등록한다
imageView.translatesAutoresizingMaskIntoConstraints = false
// 상하좌우에 대한 제약조건을 설정한다
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
// view -> safeAreaLayoutGuide로 변경
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
// view -> safeAreaLayoutGuide로 변경, constant 제거
imageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
// view -> safeAreaLayoutGuide로 변경
imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
// view -> safeAreaLayoutGuide로 변경
])
}
}
희미하게 간격 다른게 보일거임

그래도 모르겟으면

대충 이렇게됨
우선순위
- setContentHuggingPriority
설명: 뷰가 자신의 내용을 "꼭 감싸려는(hugging)" 우선순위를 설정합니다.
- 높은 우선순위 = 뷰가 콘텐츠 크기보다 커지지 않으려고 함
- 낮은 우선순위 = 뷰가 필요시 콘텐츠보다 쉽게 확장됨
UIView.setContentHuggingPriority(
priority: UILayoutPriority, // 높고/낮음 설정
for: NSLayoutConstraint.Axis // 수직/수평 설정
)
- setContentCompressionResistancePriority
설명: 뷰가 자신의 내용이 "압축되는 것을 저항하는" 우선순위를 설정합니다.
- 높은 우선순위 = 뷰가 콘텐츠 크기보다 작아지지 않으려고 함
- 낮은 우선순위 = 뷰가 필요시 내용이 잘리더라도 쉽게 축소됨
UIView.setContentCompressionResistancePriority(
priority: UILayoutPriority, // 높고/낮음 설정
for: NSLayoutConstraint.Axis // 수직/수평 설정
)
// 두 레이블이 나란히 있는 상황
let titleLabel = UILabel()
titleLabel.text = "제목"
let descriptionLabel = UILabel()
descriptionLabel.text = "이 설명은 매우 길어질 수 있습니다..."
// 제목은 크기를 유지하고, 설명은 필요시 늘어나도록 설정
titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
descriptionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
// 제목은 압축되지 않고, 설명은 필요시 압축될 수 있도록 설정
titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
descriptionLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

▪ 두개의 UIView에 대한 코딩
- nameLabel: UILabel, nameTextField: UITextField를 생성하라.
- nameLabel
• text, backgroundColor을 각각 “로그인 ID”, .green으로 하라 • 상(safeArea),좌 (safeArea),우(nameTextField) 여백을 100, 10, 10으로 설정하라- nameTextField
• placeholder, backgroundColor을 각각 “아이디 입력”, .cyan으로 하라 • 우(safeArea), 여백을 10으로 하라.- nameLabel, nameTextField의 centerYAnchor 을 같게 하라
// 레이블과 텍스트필드 생성
let nameLabel = UILabel()
nameLabel.text = "로그인 ID" // 레이블에 표시할 텍스트 설정
nameLabel.backgroundColor = .green // 레이블 배경색 초록색으로 설정
let nameTextField = UITextField()
nameTextField.placeholder = "아이디 입력" // 텍스트필드 placeholder 설정
nameTextField.backgroundColor = .cyan // 텍스트필드 배경색 청록색으로 설정
// 뷰 계층에 추가 (추가 순서는 중요하지 않음)
view.addSubview(nameTextField) // add하는 순서는 중요하지 않다
view.addSubview(nameLabel)
// 오토레이아웃 사용을 위해 오토리사이징 비활성화
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// 레이블 위치 설정
nameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100), // 상단에서 100pt 떨어진 위치
nameLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), // 왼쪽에서 10pt 떨어진 위치
// 레이블과 텍스트필드 관계 설정
nameLabel.trailingAnchor.constraint(equalTo: nameTextField.leadingAnchor, constant: -10), // 레이블의 오른쪽은 텍스트필드의 왼쪽에서 10pt 간격
nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10), // 텍스트필드의 오른쪽은 화면 오른쪽에서 10pt 간격
nameTextField.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor) // 텍스트필드와 레이블의 세로 중앙 정렬
])

nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) 다음 코드를 추가하여 nameLabel에 고무밴드를 더 감아보자

그러나 막 입력을 하면 nameLabel이 줄여든다.

– 다음과 같이 코드 추가
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
nameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal

import UIKit
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 레이블과 텍스트필드 생성
let nameLabel = UILabel()
nameLabel.text = "로그인 ID" // 레이블에 표시할 텍스트 설정
nameLabel.backgroundColor = .green // 레이블 배경색 초록색으로 설정
let nameTextField = UITextField()
nameTextField.placeholder = "아이디 입력" // 텍스트필드 placeholder 설정
nameTextField.backgroundColor = .cyan // 텍스트필드 배경색 청록색으로 설정
// 뷰 계층에 추가 (추가 순서는 중요하지 않음)
view.addSubview(nameTextField) // add하는 순서는 중요하지 않다
view.addSubview(nameLabel)
// 오토레이아웃 사용을 위해 오토리사이징 비활성화
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameTextField.translatesAutoresizingMaskIntoConstraints = false
// 우선순위 설정 - nameLabel이 크기를 유지하도록 함
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
// 압축 저항 우선순위도 높게 설정하여 내용이 짤리지 않도록 함
nameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
// 주의: 위 두 코드를 추가해도 적절한 제약조건이 없으면 효과가 없을 수 있음
NSLayoutConstraint.activate([
// 레이블 위치 설정
nameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100), // 상단에서 100pt 떨어진 위치
nameLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), // 왼쪽에서 10pt 떨어진 위치
// 레이블과 텍스트필드 관계 설정
nameLabel.trailingAnchor.constraint(equalTo: nameTextField.leadingAnchor, constant: -10), // 레이블의 오른쪽은 텍스트필드의 왼쪽에서 10pt 간격
nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10), // 텍스트필드의 오른쪽은 화면 오른쪽에서 10pt 간격
nameTextField.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor) // 텍스트필드와 레이블의 세로 중앙 정렬
])
}
}
문제확인하기
print() 갈기기

완성코드
import UIKit
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 레이블과 텍스트필드 생성
let nameLabel = UILabel()
nameLabel.text = "로그인 ID" // 레이블에 표시할 텍스트 설정
nameLabel.backgroundColor = .green // 레이블 배경색 초록색으로 설정
let nameTextField = UITextField()
nameTextField.placeholder = "아이디 입력" // 텍스트필드 placeholder 설정
nameTextField.backgroundColor = .cyan // 텍스트필드 배경색 청록색으로 설정
// 뷰 계층에 추가 (추가 순서는 중요하지 않음)
view.addSubview(nameTextField) // add하는 순서는 중요하지 않다
view.addSubview(nameLabel)
// 오토레이아웃 사용을 위해 오토리사이징 비활성화
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// 레이블 위치 설정
nameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100), // 상단에서 100pt 떨어진 위치
nameLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), // 왼쪽에서 10pt 떨어진 위치
// 레이블과 텍스트필드 관계 설정
nameLabel.trailingAnchor.constraint(equalTo: nameTextField.leadingAnchor, constant: -10), // 레이블의 오른쪽은 텍스트필드의 왼쪽에서 10pt 간격
nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10), // 텍스트필드의 오른쪽은 화면 오른쪽에서 10pt 간격
nameTextField.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor) // 텍스트필드와 레이블의 세로 중앙 정렬
])
// 우선순위 설정 - 레이아웃 제약조건 활성화 이후에 설정됨
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) // 레이블이 자신의 내용 크기를 유지하려는 성질 강화
nameTextField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) // 텍스트필드가 필요시 쉽게 압축될 수 있도록 설정
}
}

UIStackView의 장점
AddArrangedSubView
// 스택뷰 생성
let stackView = UIStackView()
// 스택뷰 속성 설정
stackView.axis = .horizontal // 가로 방향으로 요소들 배치
stackView.alignment = .center // 요소들을 중앙에 정렬
stackView.distribution = .fill // 가용 공간 채우기
stackView.spacing = 10 // 요소 사이 간격은 10포인트
// 요소 추가하기
stackView.addArrangedSubview(view1)
...
stackView.addArrangedSubview(viewN)
// 스택뷰를 메인 화면에 추가
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false // 자동 크기 조정 비활성화
// 이제 stackView에 대한 제약조건 설정 가능
// UIStackView 기본 예제 코드 (iOS)
// UIStackView는 여러 UI 요소들을 쉽게 배치할 수 있게 해주는 컨테이너입니다.
import UIKit
class ProgrammingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupStackView()
}
func setupStackView() {
// 1. UIStackView 객체 생성
let stackView = UIStackView()
// 2. 스택뷰 기본 설정
stackView.backgroundColor = UIColor(red: 0, green: 1, blue: 1, alpha: 1) // 청록색 배경
stackView.translatesAutoresizingMaskIntoConstraints = false // 자동 크기 조정 비활성화
// 3. 스택뷰 속성 설정
stackView.axis = .horizontal // 가로 방향으로 요소들을 배치 (수평축으로 설정)
stackView.alignment = .center // 요소들을 중앙에 정렬
stackView.distribution = .fill // 요소들이 공간을 채우도록 설정
stackView.spacing = 10 // 요소들 사이의 간격은 10포인트
// 4. UI 요소 생성 (라벨과 텍스트필드)
// 4-1. 라벨 생성 및 설정
let nameLabel = UILabel()
nameLabel.text = "Name"
nameLabel.backgroundColor = .green
nameLabel.translatesAutoresizingMaskIntoConstraints = false
// 4-2. 텍스트필드 생성 및 설정
let nameTextField = UITextField()
nameTextField.backgroundColor = .yellow
nameTextField.translatesAutoresizingMaskIntoConstraints = false
// 5. 허깅 우선순위 설정 (컨텐츠 크기 유지를 위한 설정)
// 허깅 우선순위가 높을수록 자신의 고유 크기를 유지하려는 성향이 강해짐
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
nameTextField.setContentHuggingPriority(.defaultLow, for: .horizontal)
// 라벨은 텍스트 크기만큼만 공간을 차지하고, 텍스트필드는 남은 공간을 모두 차지
// 6. stackView에 UI 요소 추가
// 주의: addSubview가 아니라 addArrangedSubview 사용해야 스택뷰 특성이 적용됨
stackView.addArrangedSubview(nameLabel)
stackView.addArrangedSubview(nameTextField)
// 7. 스택뷰를 메인 뷰에 추가
view.addSubview(stackView)
// 8. 스택뷰의 제약조건(Constraints) 설정
NSLayoutConstraint.activate([
// 왼쪽 여백: 스택뷰의 왼쪽을 안전영역 왼쪽에서 10포인트 떨어지게
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
// 오른쪽 여백: 스택뷰의 오른쪽을 안전영역 오른쪽에서 -10포인트 떨어지게 (안쪽으로 10포인트)
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
// 상단 여백: 스택뷰의 상단을 안전영역 상단에서 100포인트 떨어지게
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
// 높이: 스택뷰의 높이를 120포인트로 고정
stackView.heightAnchor.constraint(equalToConstant: 120)
])
// 참고: 여기서 제약조건을 설정하는 이유는 스택뷰 자체의 위치와 크기를 지정하기 위함
// 스택뷰 내부의 요소들은 스택뷰의 속성(axis, alignment, distribution, spacing)에 따라 자동 배치됨
}
}



IImageView 및 설정
– Images 폴더에서 bird.png를 프로젝트에 복사
• Navigator 판넬에서 asset폴더를 오픈하라
• bird.png를 그곳에 drag & drop하라

Object Library에서 UIImageView를 선택하여 ViewController에 드롭하라

UIImageView 설정
• 속성 Inspector의 Image에 bird를 선택하라.
• 왼쪽여백 0, 윗여백 0, 크기(40, 20)으로 설정하라.

• 이를 ViewController의 birdImageView로 Outlet하라
4. Constraints와 ViewController연결
• Main.storyboard와 ViewController.swift를 동시에 오픈하라
• Document Outline에서 왼쪽여백의 Constraint를 Control+Drag하여 ViewController.swift에서 drop하라. 이름을 birdLeadingConstraint로 하라
• Document Outline에서 윗여백의 Constraint를 Control+Drag하여 ViewController.swift에서 drop하라. 이름을 birdTopConstraint로 하라

//
// 날아가는 새 애니메이션 구현 코드 (iOS)
//
import UIKit
class ProgrammingViewController: UIViewController {
// MARK: - 속성
// 새 이미지를 표시할 이미지뷰
@IBOutlet weak var birdImageView: UIImageView!
// 새의 위치 제약조건 (왼쪽 여백)
@IBOutlet weak var birdLeadingConstraint: NSLayoutConstraint!
// 새의 위치 제약조건 (위쪽 여백)
@IBOutlet weak var birdTopConstraint: NSLayoutConstraint!
// 타이머 객체
var timer: Timer!
// 새의 이동 방향 (1: 오른쪽, -1: 왼쪽)
var direction = 1
// MARK: - 뷰 라이프사이클
override func viewDidLoad() {
super.viewDidLoad()
// 새 이미지뷰 초기 설정
setupBirdImageView()
// 타이머 생성 및 시작
startBirdAnimation()
}
// MARK: - 설정 및 초기화
func setupBirdImageView() {
// 이미지뷰에 bird.png 이미지 설정
birdImageView.image = UIImage(named: "bird")
// 초기 위치 및 크기 설정 (스토리보드에서 설정해도 됨)
// 왼쪽 여백: 0, 위쪽 여백: 0, 크기: 40x20
}
func startBirdAnimation() {
// 타이머 생성 - 0.1초마다 timerAction 함수 실행
timer = Timer.scheduledTimer(
timeInterval: 0.1, // 호출 주기 (0.1초)
target: self, // 호출 주체 (현재 클래스)
selector: #selector(timerAction), // 실행할 함수
userInfo: nil, // 사용자 정보 (없음)
repeats: true // 반복 실행 여부 (계속 반복)
)
}
// MARK: - 타이머 동작
// @objc 어노테이션: Objective-C 런타임에 노출시키기 위해 필요함
@objc func timerAction() {
// 1. 새의 위치 업데이트 (왼쪽 여백 변경)
birdLeadingConstraint.constant += CGFloat(direction * 10)
// 2. 화면 경계 체크 및 방향 전환
// 2-1. 오른쪽 경계 도달 확인
if birdLeadingConstraint.constant + birdImageView.frame.size.width >= view.frame.size.width {
// 오른쪽 끝에 도달하면 왼쪽으로 방향 전환
direction = -1
// 새의 이미지를 수평 방향으로 뒤집기 (왼쪽 방향)
birdImageView.transform = .init(scaleX: -1, y: 1)
}
// 2-2. 왼쪽 경계 도달 확인
else if birdLeadingConstraint.constant < 0 {
// 왼쪽 끝에 도달하면 오른쪽으로 방향 전환
direction = 1
// 새의 이미지를 원래대로 복원 (오른쪽 방향)
birdImageView.transform = .init(scaleX: 1, y: 1)
}
}
// MARK: - 리소스 정리
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 화면이 사라질 때 타이머 중지
if timer != nil {
timer.invalidate()
timer = nil
}
}
}

간단히 말해 '액션'은 "사용자가 화면의 무언가와 상호작용할 때 실행될 코드"를 말함. 이러한 연결 방식을 '타겟-액션 메커니즘'이라고 부름.
Interface Builder에서 Action에 대한 함수를 설정할 수 있듯이 프로그래밍에서도 Action에 대한 함수를 설정할 수 있음
• GestureRecognizer와 유사
// 버튼 생성
let button = UIButton(type: .system)
button.setTitle("클릭", for: .normal)
// 액션 연결하기
button.addTarget(self,
action: #selector(buttonClicked),
for: .touchUpInside)

- UIControl: 버튼, 슬라이더 등 정해진 컨트롤의 상호작용에 사용
- GestureRecognizer: 복잡한 제스처(핀치, 스와이프, 롱프레스 등)를 모든 뷰에 추가할 때 사용
// 제스처 인식기 사용 예시
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
imageView.addGestureRecognizer(tapGesture) // 이미지뷰에 탭 제스처 추가
@objc func handleTap() {
print("이미지가 탭되었습니다!")
}

@MainActor
func addTarget(
_ target: Any?,
action: Selector,
for controlEvents: UIControl.Event
)
왜 단순히 함수 이름만 쓰면 안 될까?
Swift와 Objective-C의 '호출 방식'이 다르기 때문
Swift: 컴파일 시점에 함수가 연결됨 (정적 디스패치)
UIKit(Objective-C 기반): 실행 중에 함수 이름으로 찾아서 호출함 (동적 디스패치)
@objc func 함수이름(…){ }
import UIKit
class ProgrammingViewController: UIViewController {
// MARK: - 뷰 라이프사이클
override func viewDidLoad() {
super.viewDidLoad()
// UIButton 객체 생성 및 설정
setupButton()
}
// MARK: - 버튼 설정
func setupButton() {
// 1. 버튼 객체 생성
let button = UIButton()
// 2. 버튼 기본 속성 설정
button.setTitle("Start", for: .normal) // 버튼의 텍스트 설정
button.backgroundColor = .blue // 배경색을 파란색으로 설정
button.translatesAutoresizingMaskIntoConstraints = false // 자동 크기 조정 비활성화
// 3. 버튼을 메인 뷰에 추가
view.addSubview(button)
// 4. Auto Layout 제약조건 설정
NSLayoutConstraint.activate([
// 버튼을 화면 중앙에 위치시키기 위한 X축 가운데 정렬
button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
// 버튼을 화면 하단에 붙이기
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
// 버튼의 너비를 화면 너비의 50%로 설정
button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
// 5. 버튼에 액션 연결하기 (GestureRecognizer와 유사한 방식)
// - 버튼이 눌렸다 떼어질 때(touchUpInside) buttonPressed 함수 호출
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
// MARK: - 버튼 액션 처리
// @objc: Objective-C 런타임에서 이 함수를 찾을 수 있도록 하는 키워드
// 파라미터로 sender(이벤트를 발생시킨 객체)를 받아 어떤 버튼이 눌렸는지 알 수 있음
@objc func buttonPressed(sender: UIButton) {
// 버튼의 현재 텍스트 확인
if let text = sender.titleLabel?.text {
// 삼항 연산자로 버튼 텍스트 토글: "Start"면 "Stop"으로, 아니면 "Start"로
sender.setTitle((text == "Start") ? "Stop" : "Start", for: .normal)
}
// 여기에 버튼이 눌렸을 때 실행할 추가 코드를 작성할 수 있음
// 예: 애니메이션 시작/중지, 데이터 처리 등
}
}
import UIKit
class ProgrammingViewController: UIViewController {
// MARK: - 속성
// 새 이미지를 표시할 이미지뷰
@IBOutlet weak var birdImageView: UIImageView!
// 새의 위치 제약조건 (왼쪽 여백)
@IBOutlet weak var birdLeadingConstraint: NSLayoutConstraint!
// 새의 위치 제약조건 (위쪽 여백)
@IBOutlet weak var birdTopConstraint: NSLayoutConstraint!
// 타이머 객체
var timer: Timer!
// 새의 이동 방향 (1: 오른쪽, -1: 왼쪽)
var direction = 1
// MARK: - 뷰 라이프사이클
override func viewDidLoad() {
super.viewDidLoad()
// 새 이미지뷰 초기 설정
setupBirdImageView()
// 버튼 생성 및 설정
setupButton()
// viewDidLoad에서는 타이머를 생성하지 않음 (버튼 클릭 시 생성)
}
// MARK: - 새 이미지뷰 설정
func setupBirdImageView() {
// 이미지뷰에 bird.png 이미지 설정
birdImageView.image = UIImage(named: "bird")
// 초기 위치 및 크기 설정 (스토리보드에서 설정해도 됨)
// 왼쪽 여백: 0, 위쪽 여백: 0, 크기: 40x20
birdImageView.translatesAutoresizingMaskIntoConstraints = false
}
// MARK: - 버튼 설정
func setupButton() {
// 1. 버튼 객체 생성
let button = UIButton()
// 2. 버튼 기본 속성 설정
button.setTitle("Start", for: .normal) // 버튼의 텍스트 설정
button.backgroundColor = .blue // 배경색을 파란색으로 설정
button.translatesAutoresizingMaskIntoConstraints = false // 자동 크기 조정 비활성화
// 3. 버튼을 메인 뷰에 추가
view.addSubview(button)
// 4. Auto Layout 제약조건 설정
NSLayoutConstraint.activate([
// 버튼을 화면 중앙에 위치시키기 위한 X축 가운데 정렬
button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
// 버튼을 화면 하단에 붙이기
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
// 버튼의 너비를 화면 너비의 50%로 설정
button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
// 5. 버튼에 액션 연결하기
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
// MARK: - 애니메이션 관련 메소드
// 타이머 액션 메소드: 0.1초마다 호출되어 새의 위치를 업데이트
@objc func timerAction() {
// 1. 새의 위치 업데이트 (왼쪽 여백 변경)
birdLeadingConstraint.constant += CGFloat(direction * 10)
// 2. 화면 경계 체크 및 방향 전환
// 2-1. 오른쪽 경계 도달 확인
if birdLeadingConstraint.constant + birdImageView.frame.size.width >= view.frame.size.width {
// 오른쪽 끝에 도달하면 왼쪽으로 방향 전환
direction = -1
// 새의 이미지를 수평 방향으로 뒤집기 (왼쪽 방향)
birdImageView.transform = .init(scaleX: -1, y: 1)
}
// 2-2. 왼쪽 경계 도달 확인
else if birdLeadingConstraint.constant < 0 {
// 왼쪽 끝에 도달하면 오른쪽으로 방향 전환
direction = 1
// 새의 이미지를 원래대로 복원 (오른쪽 방향)
birdImageView.transform = .init(scaleX: 1, y: 1)
}
}
// MARK: - 버튼 액션 처리
// 버튼이 눌렸을 때 호출되는 메소드
@objc func buttonPressed(_ sender: UIButton) {
// 버튼의 현재 텍스트 확인
if let text = sender.titleLabel?.text {
if text == "Start" {
// 1. "Start" 버튼이 눌렸을 때: 타이머 생성 및 애니메이션 시작
timer = Timer.scheduledTimer(
timeInterval: 0.1, // 호출 주기 (0.1초)
target: self, // 호출 주체 (현재 클래스)
selector: #selector(timerAction), // 실행할 함수
userInfo: nil, // 사용자 정보 (없음)
repeats: true // 반복 실행 여부 (계속 반복)
)
} else {
// 2. "Stop" 버튼이 눌렸을 때: 타이머 중지 및 애니메이션 정지
timer.invalidate()
// 참고: 타이머를 nil로 설정하지 않으면 메모리 누수 발생 가능
timer = nil
}
// 3. 버튼 텍스트 토글 (Start ↔ Stop)
sender.setTitle((text == "Start") ? "Stop" : "Start", for: .normal)
}
}
// MARK: - 리소스 정리
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 화면이 사라질 때 타이머 중지 (메모리 누수 방지)
if timer != nil {
timer.invalidate()
timer = nil
}
}
}

근데 진지하게 이거 알바에 SwiftUI 공부하는게 정신건강에 훨 좋을듯