App Store에서 무드등(Mood Lamp) 앱을 참고하여 Swift로 간단한 무드등 앱을 만들예정이에요.
추가적으로 전광판 앱까지 만들어볼게요.
아래는 간단하게 틀을 잡기 위해 AI를 통해 소스 코드를 생성해봤어요.
import UIKit
class MoodLampViewController: UIViewController {
// UI 요소 선언
private let lampView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 20
view.backgroundColor = .white
return view
}()
private let redSlider: UISlider = {
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 255
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()
private let greenSlider: UISlider = {
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 255
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()
private let blueSlider: UISlider = {
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 255
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()
private let brightnessSlider: UISlider = {
let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 1
slider.value = 1
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Mood Lamp"
setupUI()
setupActions()
updateLampColor()
}
// UI 설정
private func setupUI() {
view.addSubview(lampView)
view.addSubview(redSlider)
view.addSubview(greenSlider)
view.addSubview(blueSlider)
view.addSubview(brightnessSlider)
// 레이블 추가
let redLabel = UILabel()
redLabel.text = "Red"
redLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redLabel)
let greenLabel = UILabel()
greenLabel.text = "Green"
greenLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(greenLabel)
let blueLabel = UILabel()
blueLabel.text = "Blue"
blueLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blueLabel)
let brightnessLabel = UILabel()
brightnessLabel.text = "Brightness"
brightnessLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(brightnessLabel)
// 제약 조건 설정
NSLayoutConstraint.activate([
lampView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
lampView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
lampView.widthAnchor.constraint(equalToConstant: 200),
lampView.heightAnchor.constraint(equalToConstant: 200),
redLabel.topAnchor.constraint(equalTo: lampView.bottomAnchor, constant: 20),
redLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
redSlider.centerYAnchor.constraint(equalTo: redLabel.centerYAnchor),
redSlider.leadingAnchor.constraint(equalTo: redLabel.trailingAnchor, constant: 10),
redSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
greenLabel.topAnchor.constraint(equalTo: redLabel.bottomAnchor, constant: 20),
greenLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
greenSlider.centerYAnchor.constraint(equalTo: greenLabel.centerYAnchor),
greenSlider.leadingAnchor.constraint(equalTo: greenLabel.trailingAnchor, constant: 10),
greenSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
blueLabel.topAnchor.constraint(equalTo: greenLabel.bottomAnchor, constant: 20),
blueLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
blueSlider.centerYAnchor.constraint(equalTo: blueLabel.centerYAnchor),
blueSlider.leadingAnchor.constraint(equalTo: blueLabel.trailingAnchor, constant: 10),
blueSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
brightnessLabel.topAnchor.constraint(equalTo: blueLabel.bottomAnchor, constant: 20),
brightnessLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
brightnessSlider.centerYAnchor.constraint(equalTo: brightnessLabel.centerYAnchor),
brightnessSlider.leadingAnchor.constraint(equalTo: brightnessLabel.trailingAnchor, constant: 10),
brightnessSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
])
}
// 슬라이더 액션 설정
private func setupActions() {
redSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
greenSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
blueSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
brightnessSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
}
// 무드등 색상 및 밝기 업데이트
@objc private func updateLampColor() {
let red = CGFloat(redSlider.value) / 255.0
let green = CGFloat(greenSlider.value) / 255.0
let blue = CGFloat(blueSlider.value) / 255.0
let brightness = CGFloat(brightnessSlider.value)
lampView.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: brightness)
}
}
// AppDelegate 또는 SceneDelegate에서 실행 설정
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UINavigationController(rootViewController: MoodLampViewController())
window.makeKeyAndVisible()
self.window = window
return true
}
var window: UIWindow?
}
UI 구성:
lampView: 무드등을 시각적으로 표현하는 UIView.redSlider, greenSlider, blueSlider: RGB 색상을 조절하는 슬라이더.brightnessSlider: 밝기를 조절하는 슬라이더.기능:
updateLampColor 메서드가 호출되어 lampView의 색상과 투명도(밝기)를 실시간으로 변경합니다.App Store 참고:
소스코드만 보면 어려울 수 있기 때문에 핵심적인 기능만 간추려서 볼 수 있는 수도 코드로 다시 작성해달라고 했어요.
// 클래스: MoodLampController
클래스 MoodLampController:
// UI 요소 정의
lampView = 사각형 뷰 (색상 표시용)
redSlider = 슬라이더 (0~255, 빨간색 조절)
greenSlider = 슬라이더 (0~255, 초록색 조절)
blueSlider = 슬라이더 (0~255, 파란색 조절)
brightnessSlider = 슬라이더 (0~1, 밝기 조절)
// 초기화 시 실행
함수 viewDidLoad():
배경색 = 시스템 기본 배경
제목 = "Mood Lamp"
setupUI 호출
setupActions 호출
updateLampColor 호출
// UI 배치
함수 setupUI():
lampView를 화면 중앙 상단에 배치 (크기: 200x200)
redSlider와 "Red" 레이블을 lampView 아래에 배치
greenSlider와 "Green" 레이블을 redSlider 아래에 배치
blueSlider와 "Blue" 레이블을 greenSlider 아래에 배치
brightnessSlider와 "Brightness" 레이블을 blueSlider 아래에 배치
// 슬라이더 동작 설정
함수 setupActions():
redSlider 변경 시 updateLampColor 호출
greenSlider 변경 시 updateLampColor 호출
blueSlider 변경 시 updateLampColor 호출
brightnessSlider 변경 시 updateLampColor 호출
// 색상 및 밝기 업데이트
함수 updateLampColor():
red = redSlider 값 / 255
green = greenSlider 값 / 255
blue = blueSlider 값 / 255
brightness = brightnessSlider 값
lampView 색상 = RGB(red, green, blue, 밝기)
// 앱 실행 설정
클래스 AppDelegate:
함수 applicationDidFinishLaunching():
window 생성
rootViewController = MoodLampController
window 표시
lampView에 반영.이제 실제로 만들기 위한 계획안을 작성해달라고 했어요.
| 단계 | 세부 내용 | 기간 | 비고 |
|---|---|---|---|
| 요구사항 분석 | - 기능 정의 - UI 스케치 작성 | 4월 1일 ~ 4월 5일 (5일) | - 앱스토어 앱 벤치마킹 |
| 설계 | - UI 설계 - 아키텍처 설계 | 4월 6일 ~ 4월 10일 (5일) | - Auto Layout 계획 수립 |
| 구현 | - UI 구현 - 색상/밝기 조절 로직 | 4월 11일 ~ 4월 22일 (12일) | - 기본 기능 우선 구현 |
| 테스트 | - 기능 테스트 - 버그 수정 | 4월 23일 ~ 4월 28일 (6일) | - 시뮬레이터 및 실제 기기 테스트 |
| 최종 검토 및 배포 | - 코드 리뷰 - 앱스토어 제출 준비 | 4월 29일 ~ 4월 30일 (2일) | - 배포 준비 완료 |
lampView, 슬라이더, 레이블 UI 구성.updateLampColor 함수 구현.| 단계 | 세부 내용 | 기간 | 담당자 | 비고 |
|---|---|---|---|---|
| 요구사항 분석 | - 기능 정의 - 역할 분담 확정 | 4월 1일 ~ 4월 3일 (3일) | A, B, C (공동) | - 초기 회의 및 벤치마킹 |
| 설계 | - UI 설계 - 로직 구조화 | 4월 4일 ~ 4월 6일 (3일) | A (UI), B (로직) | - 수도 코드 기반 설계 |
| 구현 | - UI 구현 - 로직 개발 - 통합 준비 | 4월 7일 ~ 4월 14일 (8일) | A (UI), B (로직), C (통합) | - 병렬 작업 진행 |
| 테스트 | - 단위 테스트 - 통합 테스트 | 4월 15일 ~ 4월 18일 (4일) | C (주도), A, B 지원 | - 버그 수정 포함 |
| 통합 및 배포 | - 최종 통합 - 앱스토어 제출 준비 | 4월 19일 ~ 4월 20일 (2일) | C (주도), A, B 지원 | - 배포용 빌드 완료 |
// UI 구성 (MoodLampViewController 내)
함수 setupUI():
lampView 생성 및 배치 (200x200, 중앙 상단)
redSlider, greenSlider, blueSlider, brightnessSlider 생성
각 슬라이더 옆에 레이블 추가 ("Red", "Green", "Blue", "Brightness")
Auto Layout으로 제약 조건 설정
// 색상 및 밝기 로직 (MoodLampViewController 내)
함수 setupActions():
각 슬라이더에 valueChanged 이벤트 연결 (updateLampColor 호출)
함수 updateLampColor():
red = redSlider 값 / 255
green = greenSlider 값 / 255
blue = blueSlider 값 / 255
brightness = brightnessSlider 값
lampView 색상 = RGB(red, green, blue, brightness)
// 앱 실행 설정 (AppDelegate)
클래스 AppDelegate:
함수 applicationDidFinishLaunching():
window 생성
rootViewController = MoodLampViewController
window 표시
// 테스트 계획
- 시뮬레이터: iOS 15, 16, 17에서 UI 및 로직 동작 확인
- 실제 기기: iPhone 12 이상에서 테스트
- 버그 수정 및 최적화
import UIKit
class MoodLampViewController: UIViewController {
// Storyboard에서 연결할 IBOutlet
@IBOutlet weak var lampView: UIView!
@IBOutlet weak var redSlider: UISlider!
@IBOutlet weak var greenSlider: UISlider!
@IBOutlet weak var blueSlider: UISlider!
@IBOutlet weak var brightnessSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Mood Lamp"
// 슬라이더 초기 설정
redSlider.minimumValue = 0
redSlider.maximumValue = 255
greenSlider.minimumValue = 0
greenSlider.maximumValue = 255
blueSlider.minimumValue = 0
blueSlider.maximumValue = 255
brightnessSlider.minimumValue = 0
brightnessSlider.maximumValue = 1
brightnessSlider.value = 1
// lampView 초기 스타일
lampView.layer.cornerRadius = 20
// 슬라이더 액션 연결
setupActions()
// 초기 색상 업데이트
updateLampColor()
}
// 슬라이더 액션 설정
private func setupActions() {
redSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
greenSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
blueSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
brightnessSlider.addTarget(self, action: #selector(updateLampColor), for: .valueChanged)
}
// 무드등 색상 및 밝기 업데이트
@objc private func updateLampColor() {
let red = CGFloat(redSlider.value) / 255.0
let green = CGFloat(greenSlider.value) / 255.0
let blue = CGFloat(blueSlider.value) / 255.0
let brightness = CGFloat(brightnessSlider.value)
lampView.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: brightness)
}
}
Storyboard를 사용하므로 AppDelegate에서 window를 직접 설정할 필요가 없습니다. 기본 설정을 유지하거나, SceneDelegate를 사용할 경우 아래와 같이 수정합니다.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
// SceneDelegate 사용 시
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
Xcode 15 이상 프로젝트라면 SceneDelegate가 기본으로 포함됩니다. storyboard를 사용하도록 설정합니다.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()
}
}
UI 코드 제거:
setupUI() 함수와 Auto Layout 코드를 제거하고, storyboard에서 UI를 구성하도록 변경.@IBOutlet을 사용하여 storyboard의 UI 요소와 연결.IBOutlet 추가:
lampView, redSlider, greenSlider, blueSlider, brightnessSlider를 storyboard에서 연결 가능한 속성으로 선언.초기 설정:
viewDidLoad()에서 슬라이더의 최소/최대 값과 초기값을 설정.lampView의 초기 스타일(모서리 둥글기 등) 설정.Storyboard 기반 실행:
Xcode에서 프로젝트 생성:
UI 요소 배치:
연결:
@IBOutlet 변수에 드래그하여 연결.실행:
Minimum Deployments
Deployment Info




앱은 하나 이상의 뷰로 구성되며, 각각의 뷰들은 라이프 사이클을 가지고 있어요.
따라서 뷰의 라이프 사이클을 고려해서 로직을 넣고, 구성해야 해요.
ViewDidLoadViewWillAppearViewDidAppearViewWillDisappearViewDidDisappear
앱은 실행 모드와 상태를 가짐
Not Running
Foreground Mode
Background Mode
Not Running -> Active
Active -> Inactive -> Running
Active -> Inactive -> Suspend
Running -> Active

-앱 UI의 배경(액자)에 해당



AutoLayout은 각 기기별로 해상도와 방향을 모두 고려하여, 대부분의 환경(기기)에서 비슷한 위치를 가질 수 있도록 하는 기능이에요.Auto Layout은 제약 기반 레이아웃 시스템으로, 뷰(View)들의 위치와 크기를 수학적인 관계식으로 정의합니다. 이 시스템은 UI 요소 간의 관계를 수학적 제약 조건(Constraint Equations) 형태로 모델링합니다.
제약 조건은 UI 요소들 간의 관계를 수학적 식으로 표현합니다. 기본적으로 Auto Layout은 선형 방정식(Linear Equations) 을 사용합니다. 제약 조건의 기본 형태는 다음과 같습니다:
leading, trailing, top, bottom, width, height, centerX, centerY)UI 요소는 자신만의 내재된 크기를 가질 수 있습니다. 이는 주로 콘텐츠 기반 뷰(예: UILabel, UIButton, UIImageView)에서 사용되며, 요소가 자신의 콘텐츠를 기준으로 크기를 결정하는 속성입니다.
내재된 크기는 다음의 두 가지 속성을 가집니다:
intrinsicContentSize.widthintrinsicContentSize.heightAuto Layout은 기본적으로 내재된 크기를 고려하여 레이아웃을 계산하지만, 필요에 따라 명시적인 제약 조건으로 오버라이드할 수 있습니다.
제약 조건의 우선순위(Priority) 는 Auto Layout 시스템이 충돌 상황에서 어떤 제약을 우선적으로 적용할지를 결정하는 데 사용됩니다. 우선순위 값은 1부터 1000까지 설정할 수 있으며:
Auto Layout에서의 모호성은 뷰의 위치 또는 크기가 충분히 정의되지 않아 발생합니다. 충돌은 서로 상충하는 제약 조건이 동시에 적용될 때 발생합니다. 이러한 상황은 주로 다음과 같은 경우에 발생합니다:
Xcode는 Interface Builder나 런타임에서 모호성이나 충돌이 있을 때 경고를 표시합니다. 디버깅 명령어를 사용하여 문제를 추적할 수 있습니다:
view.hasAmbiguousLayout
view.debugDescription
iOS 11부터 도입된 Safe Area는 UI 요소들이 시스템 UI 요소(노치, 상태 바, 홈 인디케이터 등)에 가려지지 않도록 보장하는 영역입니다. Auto Layout을 사용할 때 safeAreaLayoutGuide를 참조하여 UI 요소를 배치하는 것이 좋습니다.
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16)
])
Auto Layout에서는 뷰의 크기를 결정할 때 두 가지 추가 속성을 고려합니다:
| 방법 | 장점 | 단점 |
|---|---|---|
| 스토리보드 (Interface Builder) | - 시각적 구성: UI를 직관적으로 구성할 수 있어 시각적으로 빠르게 디자인 가능. - 빠른 프로토타이핑: 초기 디자인과 레이아웃을 빠르게 구성. - 자동 완성 및 오류 감지: Xcode가 제약 조건 오류를 실시간으로 감지하고 수정 가능. - 간편한 Stack View 사용: Stack View를 쉽게 추가하고 구성 가능. - Auto Layout Guides 사용: Safe Area, Margins 등을 쉽게 적용 가능. | - 코드와의 분리: UI와 로직이 분리되어 있기 때문에 코드를 통해 UI를 동적으로 변경하기 어려움. - 버전 관리 어려움: 스토리보드는 XML 형식이기 때문에 Git과 같은 버전 관리에서 충돌 발생 가능. - 복잡한 레이아웃 관리 어려움: 복잡한 UI에서는 제약 조건이 많아지며 관리가 어려움. - 자동 생성 코드의 제한: Xcode가 생성하는 코드는 직접 수정이 어려움. |
| 코드 (Programmatic Layout) | - 유연성: UI를 동적으로 구성할 수 있어 코드 로직에 따라 UI를 조정하기 쉬움. - 버전 관리 용이: Git 등 버전 관리 도구에서 충돌이 적고 코드의 변경 이력을 쉽게 추적. - 복잡한 UI 관리: 복잡한 레이아웃이나 조건부 UI를 명확하게 정의 가능. - 조건 기반 UI 구성: 사용자의 입력이나 앱 상태에 따라 UI를 동적으로 변경 가능. | - 개발 속도 저하: 처음에는 코드로 UI를 작성하는 데 시간이 더 걸릴 수 있음. - 학습 곡선: Auto Layout 제약 조건 API를 숙지하는 데 시간이 필요. - 시각적 확인 불가: UI를 코드로만 구성하기 때문에 즉각적인 시각적 확인이 어려움. - 코드 가독성 저하: UI 관련 코드가 많아지면 가독성이 떨어질 수 있음. |
| 스토리보드 + 코드 (혼합 방식) | - 균형 잡힌 접근: 주요 UI 요소는 스토리보드로 빠르게 구성하고, 세부적인 제약 조건은 코드로 세밀하게 조정. - 빠른 프로토타이핑 + 유연성: 초기 UI는 스토리보드로, 필요 시 코드로 제약 조건을 조정. - UI 유지보수 용이: 스토리보드로 기본 레이아웃 유지, 코드로 동적 레이아웃 관리. | - 코드와 스토리보드의 복잡도 증가: 두 방식을 함께 사용하면 프로젝트 구조가 복잡해질 수 있음. - 버전 관리 이슈: 코드와 스토리보드를 동시에 관리하는 데 충돌이 발생할 수 있음. - 디버깅 어려움: UI 오류가 스토리보드와 코드 중 어디에서 발생했는지 추적이 어려움. |
textField와 label, Button 을 이용해 간단하게 전광판 앱을 만들어봤어요.
