iOS Class 3
좋아요. 단순한 “불 켜는 앱”은 이미 너무 많기 때문에, 감성 + 개인화 + 상황 인식을 잘 묶어야 경쟁력이 생깁니다. 아래는 바로 써먹을 수 있는 수준의 핵심 기능 + 차별화 중심 기획서입니다.
📱 Night Light (무드등) iOS 앱 기획서
1. 🎯 서비스 개요
서비스명 (가칭): Luma – 당신만의 감정형 무드등
한 줄 설명:
사용자의 감정, 시간, 환경에 맞춰 자동으로 빛을 연출하는 스마트 감성 무드등 앱
2. 👤 타겟 사용자
- 잠들기 전 휴대폰을 보는 사용자
- 감성적인 공간 연출을 좋아하는 20~30대
- 수면 질 개선에 관심 있는 사람
- ASMR, 백색소음 앱 사용자
3. 🔑 핵심 기능
3.1 🌈 무드 라이트 (기본 기능)
- 다양한 색상 & 그라데이션
- 밝기 / 색온도 조절
- 전체 화면 라이트 모드
- 깜빡임 없는 안정적인 광원
👉 단순하지만 UX는 매우 중요 (터치 몇 번으로 끝나야 함)
3.2 😴 수면 모드 (Sleep Mode)
- 타이머 기반 자동 꺼짐
- 서서히 어두워지는 “페이드 아웃”
- 수면 유도 색상 자동 추천 (따뜻한 톤)
👉 차별화 포인트:
“수면 단계에 맞춘 조명 변화”
3.3 🧠 감정 기반 추천 (핵심 차별화)
- 간단한 감정 선택 UI
- 😌 편안함 / 😔 우울 / 😴 졸림 / 😤 스트레스
- 감정에 맞는:
- 색상
- 밝기
- 움직임 패턴
👉 예:
- 스트레스 → 차분한 블루 + 느린 호흡 리듬
- 우울 → 따뜻한 오렌지 + 점진적 밝기 상승
3.4 🌊 움직이는 라이트 (Dynamic Lighting)
- 파도 / 숨쉬기 / 불빛 흔들림
- “호흡 유도 모드” (빛이 숨쉬듯 움직임)
👉 명상/힐링 기능과 연결 가능
3.5 🎧 사운드 연동 (선택 기능)
- 백색소음 / 빗소리 / 파도
- 라이트와 싱크
- 빛이 소리에 맞춰 변화
3.6 📅 자동 실행 (루틴 기능)
- 특정 시간에 자동 켜짐
- 예: 밤 11시 → 수면 모드 자동 실행
- iOS Focus / 수면 모드 연동
3.7 📷 카메라 기반 색상 추출 (차별화)
- 주변 공간 촬영 → 어울리는 무드등 생성
- “내 방에 맞는 조명”
👉 인스타 감성 + 개인화
3.8 🧩 위젯 & 잠금화면 제어
- iOS 위젯에서 바로 실행
- “한 번 터치 → 무드 ON”
4. 💡 핵심 차별화 전략
4.1 “감정 기반 UX”
기존 앱:
색상 선택 → 끝
우리 앱:
감정 선택 → 자동 연출
👉 사용자가 “생각하지 않아도 되는 UX”
4.2 “빛 = 인터랙션”
- 정적인 색이 아니라
- 살아있는 빛
- 숨쉬는 빛
- 흐르는 빛
4.3 “수면 최적화”
- 단순 타이머 X
- 생체 리듬 기반 조명 변화
4.4 “공간 맞춤형”
- 카메라 기반 색 추천
- 개인 방 분위기에 맞춤
5. 🧪 MVP (최소 기능 제품)
초기 버전은 이렇게 시작하는 게 좋습니다:
필수 기능만:
- 색상 선택 + 밝기 조절
- 수면 타이머 (페이드 아웃)
- 감정 선택 → 자동 색 추천
- 2~3가지 라이트 애니메이션
👉 이 정도면 앱스토어 출시 가능
6. 💰 수익 모델
Freemium 구조
무료:
- 기본 색상
- 기본 타이머
유료 (구독 or 일회성):
- 프리미엄 테마
- 감정 기반 추천 확장
- 사운드 팩
- 고급 애니메이션
👉 추천 가격:
- ₩3,900 ~ ₩6,900 / 월
7. 🎨 UI/UX 방향
- 다크모드 기반
- 최소 인터랙션
- 감성적인 그라데이션
- 애니메이션 중심 UI
👉 참고 키워드:
- Calm 앱 스타일
- Apple 기본 앱 감성
8. 🚀 확장 아이디어
- Apple Health 연동 (수면 데이터 기반 조명)
- HomeKit 연동 (실제 조명 제어)
- Apple Watch 연동 (심박수 기반 빛 변화)
- AI 추천 무드 생성
9. 🔥 핵심 성공 포인트 요약
이 앱의 본질은:
“빛을 켜는 앱”이 아니라
“감정을 조절하는 도구”


- Not Running
- 앱이 아직 실행되지 않았거나, 시스템에 의해 종료된 상태
- Foreground Mode
- Active : 앱이 전경에 있고 이벤트를 받고 있음. 일반적으로 사용자와 상호작용하는 동안 앱은 이 상태에 있음
- Inactive : 앱이 전경에 있지만 이벤트를 받고 있지 않음
- Background Mode
- Running : 앱이 백그라운드에 있지만 여전히 코드를 실행. 사용자가 다른 앱으로 전환하거나 홈 화면으로 돌아갔을 때 앱은 일시적으로 이 상태에 머물게 됨
- Suspend : 앱이 백그라운드에 있지만 코드를 실행하지 않음. 시스템은 앱을 이 상태로 자동으로 전환하며 필요에 따라 메모리를 회수하기 위해 앱을 종료시킬 수 있음 .
- ViewDidLoad : 뷰 컨트롤러 클래스가 생성될 때 , 가장 먼저 실행 . 딱 한 번 실행되기 때문에 초기화 할 때 사용
- ViewWillAppear : 뷰가 생성되기 직전에 매번 실행 이 되기 때문에 뷰가 나타나기 전에 실행해야 하는 작업들을 여기서 함
좋은 자료 가져왔네요 👍
이건 iOS UIKit 클래스 구조인데, 여기서 실무에서 진짜 많이 쓰는 것들만 골라서 핵심만 설명해줄게요.
🔥 가장 많이 쓰는 핵심 5개
1️⃣ UIView
👉 모든 UI의 부모
- 화면에 보이는 모든 것의 기본
- 버튼, 라벨, 이미지 다 이걸 상속
역할
let view = UIView()
view.backgroundColor = .red
👉 “그냥 사각형 하나 만든다” 느낌
2️⃣ UIViewController
👉 화면 하나를 관리하는 객체
- iOS 앱의 기본 단위
- 화면 전환, 데이터 연결 담당
역할
class MainViewController: UIViewController {
}
👉 “이 화면을 어떻게 동작시킬지” 담당
3️⃣ UIWindow
👉 앱의 가장 바깥 컨테이너
- 앱 화면의 루트
- 보통 1개만 존재
구조
UIWindow
└── UIViewController.view
└── UIView들
👉 “앱 전체를 담는 유리창”
4️⃣ UIControl
👉 사용자 입력을 받는 UI의 부모
- 버튼, 스위치, 슬라이더의 공통 부모
특징
- 터치 이벤트 처리 가능
button.addTarget(self, action: #selector(tap), for: .touchUpInside)
👉 “눌리고 반응하는 UI들의 공통 조상”
5️⃣ UIResponder
👉 이벤트 전달의 핵심
- 터치, 키보드 이벤트 처리
- 거의 모든 UI의 상위 클래스
👉 “터치가 어디로 전달될지 결정”
🎯 자주 쓰는 UI 컴포넌트들
🔘 UIButton
👉 버튼
button.setTitle("확인", for: .normal)
🏷 UILabel
👉 텍스트 표시
label.text = "안녕하세요"
🖼 UIImageView
👉 이미지 표시
imageView.image = UIImage(named: "cat")
⌨️ UITextField
👉 한 줄 입력
textField.placeholder = "입력하세요"
📜 UIScrollView
👉 스크롤 가능 영역
- 모든 스크롤의 기본
📋 UITableView
👉 리스트 (가장 많이 씀)
tableView.dataSource = self
👉 “인스타 피드 같은 구조”
🧭 UINavigationController
👉 화면 스택 관리
- push / pop
navigationController?.pushViewController(vc, animated: true)
👉 “뒤로가기 있는 구조”
📑 UITabBarController
👉 하단 탭 UI
- 여러 화면 전환
🧠 구조 한 번에 이해하기
UIWindow
└── UIViewController
└── UIView
├── UILabel
├── UIButton
├── UIImageView
└── UITextField
💡 무드등 앱 기준 매핑
UIViewController→ 메인 무드 화면UIView→ 색상 배경UIButton→ 모드 선택 버튼UISlider→ 밝기 조절UILabel→ 현재 상태 표시
⚠️ 핵심 감각
👉 iOS 개발 =
“UIView 위에 컴포넌트 올리고, UIViewController가 제어한다”
🔥 진짜 중요한 3개만 꼽으면
UIViewUIViewControllerUITableView
👉 이 3개 이해하면 iOS 70% 이해한 거임

- 컨트롤
- 컨트롤들은 정보를 표시하고 사용자에게 반응도 하는 뷰들을 포함
- UIControl 클래스(UIView의 서브뷰)의 자식으로 버튼, 슬라이더, TextField 등
- 디스플레이 뷰
- 디스플레이 뷰는 시각적인 반응을 제공한다는 점에서는 컨트롤과 비슷하지만 사용자 상호작용에 반응하지 않음
- UILabel, UIImageView


import UIKit // UI 관련 프레임워크 import
import AVFoundation // 오디오 재생을 위한 프레임워크 import
class ViewController: UIViewController { // 메인 화면 클래스
@IBOutlet weak var Switch: UISwitch! // 스토리보드에 연결된 스위치 (음악 ON/OFF)
var colorChangeTimer: Timer? // 배경색을 바꾸는 타이머
var stopTimer: Timer? // 20초 후 음악 종료를 위한 타이머
var audioPlayer: AVAudioPlayer? // 음악 재생 객체 (옵셔널로 안전하게 처리)
override func viewDidLoad() { // 화면이 처음 로드될 때 호출
super.viewDidLoad() // 부모 클래스 초기화
// 화면 터치 시 이벤트 처리용 제스처 추가
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
view.addGestureRecognizer(tapGesture) // 현재 뷰에 제스처 등록
// 앱이 백그라운드 → 포그라운드로 돌아올 때 알림 받기
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
@objc func applicationWillEnterForeground() { // 앱이 다시 활성화될 때 호출
if Switch.isOn { // 스위치가 ON 상태라면
startAll() // 음악 + 색상 타이머 다시 시작
}
}
@objc func handleTapGesture() { // 화면을 터치했을 때 실행
stopAll() // 모든 기능 중지
// 알림창 생성
let alert = UIAlertController(title: "알림", message: "앱을 종료해주세요.", preferredStyle: .alert)
// 확인 버튼 생성
let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
alert.addAction(okAction) // 알림창에 버튼 추가
present(alert, animated: true, completion: nil) // 알림창 화면에 표시
}
@IBAction func musicSwitch(_ sender: UISwitch) { // 스위치 상태 변경 시 호출
if sender.isOn { // 스위치 ON
startAll() // 전체 기능 시작
} else { // 스위치 OFF
stopAll() // 전체 기능 정지
}
}
func startAll() { // 음악 + 색상 변화 시작 함수
// ===== 음악 재생 =====
if let soundURL = Bundle.main.url(forResource: "bgm", withExtension: "mp3") { // mp3 파일 경로 가져오기
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL) // 오디오 플레이어 생성
audioPlayer?.volume = 1.0 // 볼륨 최대
audioPlayer?.play() // 음악 재생
} catch {
print("음악 재생 오류: \(error)") // 오류 발생 시 출력
}
}
// ===== 20초 후 음악 자동 종료 타이머 =====
stopTimer?.invalidate() // 기존 타이머 제거 (중복 방지)
stopTimer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { [weak self] _ in
self?.fadeOutAndStopMusic() // 20초 후 페이드 아웃 실행
}
// ===== 배경색 변경 타이머 =====
colorChangeTimer?.invalidate() // 기존 타이머 제거
colorChangeTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
// 랜덤 색상 생성 (0~255 → 0~1로 변환)
let red = CGFloat(arc4random_uniform(256)) / 255.0
let green = CGFloat(arc4random_uniform(256)) / 255.0
let blue = CGFloat(arc4random_uniform(256)) / 255.0
// 배경색 변경
self.view.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
func fadeOutAndStopMusic() { // 음악을 서서히 줄이면서 종료하는 함수
guard let player = audioPlayer else { return } // audioPlayer 없으면 종료
// 0.1초마다 반복되는 타이머
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if player.volume > 0.1 { // 볼륨이 충분히 크면
player.volume -= 0.1 // 볼륨을 점점 줄임
} else { // 거의 0에 도달하면
player.stop() // 음악 정지
player.volume = 1.0 // 다음 재생을 위해 초기화
timer.invalidate() // 타이머 종료
}
}
}
func stopAll() { // 모든 기능 정지 함수
colorChangeTimer?.invalidate() // 색상 변경 타이머 중지
colorChangeTimer = nil // 메모리 정리
stopTimer?.invalidate() // 20초 타이머 중지
stopTimer = nil // 메모리 정리
audioPlayer?.stop() // 음악 정지
}
}



import UIKit
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupLabel()
}
func setupLabel() {
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 40)
// 네온 느낌 그림자
label.layer.shadowColor = UIColor.red.cgColor
label.layer.shadowRadius = 10
label.layer.shadowOpacity = 1
label.layer.shadowOffset = .zero
}
@IBAction func displayText(_ sender: UIButton) {
label.text = textField.text
startScrolling()
startColorAnimation()
}
// 🏃♂️ 스크롤 애니메이션
func startScrolling() {
label.sizeToFit()
label.center.x = view.bounds.width + label.bounds.width / 2
UIView.animate(
withDuration: 8,
delay: 0,
options: [.curveLinear, .repeat],
animations: {
self.label.center.x = -self.label.bounds.width / 2
},
completion: nil
)
}
// 🌈 색상 변화 애니메이션
func startColorAnimation() {
let colors: [UIColor] = [.red, .green, .blue, .yellow, .cyan, .magenta]
var index = 0
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { timer in
UIView.transition(with: self.label, duration: 0.2, options: .transitionCrossDissolve, animations: {
self.label.textColor = colors[index]
})
index = (index + 1) % colors.count
}
}
}