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 (최소 기능 제품)

초기 버전은 이렇게 시작하는 게 좋습니다:

필수 기능만:

  1. 색상 선택 + 밝기 조절
  2. 수면 타이머 (페이드 아웃)
  3. 감정 선택 → 자동 색 추천
  4. 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개만 꼽으면

  1. UIView
  2. UIViewController
  3. UITableView

👉 이 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
        }
    }
}