[Swift] 무드등, 전광판 앱 만들어보기

승민·2025년 3월 19일

Swift

목록 보기
3/10
post-thumbnail

앱 기획

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?
}

코드 설명

  1. UI 구성:

    • lampView: 무드등을 시각적으로 표현하는 UIView.
    • redSlider, greenSlider, blueSlider: RGB 색상을 조절하는 슬라이더.
    • brightnessSlider: 밝기를 조절하는 슬라이더.
  2. 기능:

    • 슬라이더를 움직일 때마다 updateLampColor 메서드가 호출되어 lampView의 색상과 투명도(밝기)를 실시간으로 변경합니다.
    • RGB 값은 0~255 범위로 설정되고, 밝기는 0~1 사이로 조절됩니다.
  3. App Store 참고:

    • App Store의 무드등 앱(예: "Mood Lamp", "Color Light")을 참고하면, 대부분 간단한 색상 선택과 밝기 조절 기능을 제공합니다. 이 코드는 그 기본 기능을 반영한 것입니다.

수도 코드

소스코드만 보면 어려울 수 있기 때문에 핵심적인 기능만 간추려서 볼 수 있는 수도 코드로 다시 작성해달라고 했어요.

// 클래스: 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 표시

수도 코드 설명

  • 구조: UI 요소 정의 → 초기화 → UI 배치 → 동작 설정 → 색상 업데이트 로직으로 나뉨.
  • 핵심 로직: 슬라이더로 RGB 값과 밝기를 조절하고, 이를 실시간으로 lampView에 반영.
  • 간결화: 세부 제약 조건이나 세밀한 Swift 문법은 생략하고, 동작 흐름만 표현.

프로젝트 개요

이제 실제로 만들기 위한 계획안을 작성해달라고 했어요.

목적

  • 사용자가 RGB 색상과 밝기를 조절하여 개인화된 분위기를 연출할 수 있는 iOS 기반 무드등 앱 개발.
  • App Store의 기존 무드등 앱을 참고하여 직관적이고 간단한 사용자 경험 제공.

배경

  • 현대인의 스트레스 해소 및 공간 연출 수요 증가.
  • 모바일 디바이스를 활용한 간편한 무드 조절 도구 필요성 대두.

주요 기능

핵심 기능

  • 색상 조절: RGB 슬라이더를 통해 색상 커스터마이징.
  • 밝기 조절: 밝기 슬라이더로 투명도(0~1) 설정.
  • 실시간 반영: 슬라이더 조작 시 무드등 색상 즉시 업데이트.

UI 구성

  • 무드등 뷰: 화면 상단에 위치한 200x200 크기의 색상 표시 영역.
  • 슬라이더: Red, Green, Blue, Brightness 슬라이더 및 레이블.

개발 환경

  • 언어: Swift
  • 프레임워크: UIKit
  • 개발 도구: Xcode
  • 대상 플랫폼: iOS 15.0 이상

개발 계획 기간

  • 총 기간: 2025년 4월 1일 ~ 2025년 4월 30일 (30일)
  • 단계별 일정: 요구사항 분석, 설계, 구현, 테스트로 구성.

개발 일정표

단계세부 내용기간비고
요구사항 분석- 기능 정의
- 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일)- 배포 준비 완료

일정 상세

  • 요구사항 분석 (5일): App Store의 유사 앱 분석 후 핵심 기능 확정.
  • 설계 (5일): UI 레이아웃 설계 및 수도 코드 기반 로직 구조화.
  • 구현 (12일): UI와 로직을 단계적으로 코딩. (UI: 5일, 로직: 7일)
  • 테스트 (6일): 다양한 iOS 버전과 디바이스에서 안정성 확인.
  • 최종 검토 (2일): 코드 최적화 및 배포 준비.

예상 산출물

  • 소스 코드: Xcode 프로젝트 파일.
  • 앱 실행 파일: .ipa 형식의 배포 가능 파일.
  • 문서: 개발 보고서 및 사용자 매뉴얼.

리스크 및 대응 방안

  • 리스크: UI 반응성 저하 (슬라이더 조작 시 지연).
    • 대응: 색상 업데이트 로직 최적화 및 백그라운드 처리 검토.
  • 리스크: 개발 지연.
    • 대응: 우선순위 기능(기본 색상/밝기 조절) 먼저 완성 후 추가 기능 확장.

무드등 앱 개발 기획안 및 보고서 (3인 협업 버전)

프로젝트 개요

  • 프로젝트명: 무드등 (Mood Lamp)
  • 목적: RGB 색상과 밝기 조절이 가능한 iOS 무드등 앱 개발.
  • 개발 인원: 3명 (개발자 A, B, C)

역할 분담 및 작업 내용

개발자 A: UI/UX 담당

  • 책임:
    • UI 설계 및 구현 (레이아웃, 슬라이더, 레이블 등).
    • Auto Layout을 사용한 반응형 디자인.
  • 세부 작업:
    • lampView, 슬라이더, 레이블 UI 구성.
    • 화면 레이아웃 설계 및 제약 조건 설정.
    • 사용자 인터페이스 시각적 개선 (필요 시 애니메이션 추가).

개발자 B: 로직 및 기능 담당

  • 책임:
    • 색상 및 밝기 조절 로직 구현.
    • 슬라이더 이벤트 처리 및 실시간 업데이트.
  • 세부 작업:
    • updateLampColor 함수 구현.
    • RGB 및 밝기 값 계산 및 적용 로직.
    • 슬라이더와 뷰 간 데이터 연동.

개발자 C: 통합 및 테스트 담당

  • 책임:
    • 프로젝트 통합, 앱 실행 설정, 테스트 및 배포 준비.
  • 세부 작업:
    • AppDelegate 설정 및 앱 실행 환경 구성.
    • 기능 테스트 (시뮬레이터 및 실제 기기).
    • 버그 수정 및 코드 최적화.

개발 환경

  • 언어: Swift
  • 프레임워크: UIKit
  • 도구: Xcode
  • 협업 도구: Git (소스 코드 관리 및 병합)

개발 계획 기간

  • 총 기간: 2025년 4월 1일 ~ 2025년 4월 20일 (20일)
  • 단계: 요구사항 분석, 설계, 구현, 테스트, 통합 및 배포.

개발 일정표 (3인 협업)

단계세부 내용기간담당자비고
요구사항 분석- 기능 정의
- 역할 분담 확정
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 지원- 배포용 빌드 완료

일정 상세

  • 요구사항 분석 (3일): 3명 모두 참여하여 기능 정의 및 역할 분담 확정.
  • 설계 (3일):
    • A: UI 설계 (스케치 및 Auto Layout 계획).
    • B: 로직 설계 (수도 코드 기반 함수 구조).
  • 구현 (8일):
    • A: UI 코드 작성 및 레이아웃 완성.
    • B: 색상/밝기 로직 및 이벤트 처리 구현.
    • C: 초기 프로젝트 설정 및 통합 준비.
  • 테스트 (4일):
    • C가 주도하여 전체 테스트 진행, A와 B는 필요 시 지원.
  • 통합 및 배포 (2일):
    • C가 주도하여 최종 빌드 및 배포 준비, A와 B는 코드 리뷰 참여.

작업 분리 세부 내용

개발자 A (UI/UX)

// UI 구성 (MoodLampViewController 내)
함수 setupUI():
    lampView 생성 및 배치 (200x200, 중앙 상단)
    redSlider, greenSlider, blueSlider, brightnessSlider 생성
    각 슬라이더 옆에 레이블 추가 ("Red", "Green", "Blue", "Brightness")
    Auto Layout으로 제약 조건 설정

개발자 B (로직 및 기능)

// 색상 및 밝기 로직 (MoodLampViewController 내)
함수 setupActions():
    각 슬라이더에 valueChanged 이벤트 연결 (updateLampColor 호출)

함수 updateLampColor():
    red = redSlider 값 / 255
    green = greenSlider 값 / 255
    blue = blueSlider 값 / 255
    brightness = brightnessSlider 값
    lampView 색상 = RGB(red, green, blue, brightness)

개발자 C (통합 및 테스트)

// 앱 실행 설정 (AppDelegate)
클래스 AppDelegate:
    함수 applicationDidFinishLaunching():
        window 생성
        rootViewController = MoodLampViewController
        window 표시

// 테스트 계획
- 시뮬레이터: iOS 15, 16, 17에서 UI 및 로직 동작 확인
- 실제 기기: iPhone 12 이상에서 테스트
- 버그 수정 및 최적화

협업 방안

  • 소스 관리: GitHub 사용, 각자 브랜치 생성 후 PR로 병합.
  • 커뮤니케이션: Slack/Discord로 매일 진행 상황 공유.
  • 중간 점검: 설계 완료 후 (4월 6일), 구현 중간 (4월 11일)에 회의.

리스크 및 대응

  • 리스크: UI와 로직 간 연동 오류.
    • 대응: 구현 단계에서 C가 조기 통합 점검.
  • 리스크: 일정 지연.
    • 대응: 우선순위 기능(UI, 기본 로직) 먼저 완료.

결론

  • 3인 협업으로 20일 내에 무드등 앱 개발 완료 목표.
  • 역할 분담을 통해 효율성 극대화 및 병렬 작업으로 일정 단축.

수정된 소스 코드

Main.storyboard 설정 가정

  • Main.storyboard에 다음 요소를 추가했다고 가정:
    • UIView (무드등 뷰, identifier: "lampView")
    • 4개의 UISlider (Red, Green, Blue, Brightness, identifier: "redSlider", "greenSlider", "blueSlider", "brightnessSlider")
    • 4개의 UILabel (각 슬라이더 옆에 "Red", "Green", "Blue", "Brightness" 텍스트)
  • Navigation Controller를 추가하고, MoodLampViewController를 루트 뷰 컨트롤러로 설정.

MoodLampViewController.swift

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)
    }
}

AppDelegate.swift

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)
    }
}

SceneDelegate.swift (필요 시)

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()
    }
}

수정된 코드의 주요 변경점

  1. UI 코드 제거:

    • setupUI() 함수와 Auto Layout 코드를 제거하고, storyboard에서 UI를 구성하도록 변경.
    • @IBOutlet을 사용하여 storyboard의 UI 요소와 연결.
  2. IBOutlet 추가:

    • lampView, redSlider, greenSlider, blueSlider, brightnessSlider를 storyboard에서 연결 가능한 속성으로 선언.
  3. 초기 설정:

    • viewDidLoad()에서 슬라이더의 최소/최대 값과 초기값을 설정.
    • lampView의 초기 스타일(모서리 둥글기 등) 설정.
  4. Storyboard 기반 실행:

    • AppDelegate와 SceneDelegate에서 storyboard를 로드하도록 설정.

Storyboard 설정 방법

  1. Xcode에서 프로젝트 생성:

    • "App" 템플릿 선택 시 "Storyboard" 인터페이스를 기본으로 선택.
    • Main.storyboard 파일이 자동 생성됨.
  2. UI 요소 배치:

    • Main.storyboard 열기.
    • Navigation Controller 추가 후 MoodLampViewController를 루트로 설정.
    • UIView (lampView)와 4개의 UISlider, 4개의 UILabel을 배치.
    • 각 요소에 Identifier 설정 (예: "lampView", "redSlider" 등).
  3. 연결:

    • MoodLampViewController.swift 파일을 열고, Assistant Editor로 storyboard와 코드 창을 나란히 띄움.
    • 각 UI 요소를 @IBOutlet 변수에 드래그하여 연결.
  4. 실행:

    • 시뮬레이터에서 실행하여 UI와 기능이 제대로 동작하는지 확인.

앱 만들기

  • Minimum Deployments

    • 앱이 동작할 수 있는 최소한의 버전을 뜻해요.
  • Deployment Info

    • Upside Down
      • 앱을 사용할 때 회전 가능한 방향을 뜻해요. 스마트폰의 경우 통화 시 방향이 정해져 있기 때문에 설정하는 것을 권장하지 않아요.

라이프사이클

  • 소스코드를 보면 따로 viewDidLoad() 함수가 정의만 되고 따로 호출되지 않았지만, 앱 실행 시에 Console에 print함수가 실행된 결과가 출력되었어요.
    • 이러한 앱의 특정 시작 시점 또는 자동 호출 지점을 특정하는 것을 라이프사이클이라고 해요.

View Life Cycle

앱은 하나 이상의 뷰로 구성되며, 각각의 뷰들은 라이프 사이클을 가지고 있어요.
따라서 뷰의 라이프 사이클을 고려해서 로직을 넣고, 구성해야 해요.

  • ViewDidLoad
    • 뷰 컨트롤러 클래스가 생성될 때, 가장 먼저 실행. 딱 한 번 실행되기 때문에 초기화 할 때 사용
  • ViewWillAppear
    • 뷰가 생성되기 직전에 매번 실행이 되기 때문에 뷰가 나타나기 전에 실행해야 하는 작업들을 여기서 함
  • ViewDidAppear
    • 뷰가 생성되고 난 뒤에 실행. 데이터를 받아서 화면에 뿌려주거나 애니메이션 등의 작업을 하는 로직을 작성
    • ViewWillAppear에서 로직을 넣었다가 뷰에 반영이 안되는 경우가 있기 때문
  • ViewWillDisappear
    • 뷰가 사라지기 직전에 실행
  • ViewDidDisappear
    • 뷰가 사라지고 난 뒤에 실행
  • 순환적으로 발생하기 때문에 화면 전환에 따라 발생해야 하는 로직을 적절한 곳에서 실행시켜야 해요.

App Life Cycle

  • 앱은 실행 모드와 상태를 가짐

  • Not Running

    • 앱이 아직 실행되지 않았거나, 시스템에 의해 종료된 상태
  • Foreground Mode

    • Active : 앱이 전경에 있고 이벤트를 받고 있음. 일반적으로 사용자와 상호작용하는 동안 앱은 이 상태에 있음
    • Inactive : 앱이 전경에 있지만 이벤트를 받고 있지 않음
  • Background Mode

    • Running : 앱이 백그라운드에 있지만 여전히 코드를 실행. 사용자가 다른 앱으로 전환하거나 홈 화면으로 돌아갔을 때 앱은 일시적으로 이 상태에 머물게 됨
    • Suspend : 앱이 백그라운드에 있지만 코드를 실행하지 않음. 시스템은 앱을 이 상태로 자동으로 전환하며 필요에 따라 메모리를 회수하기 위해 앱을 종료시킬 수 있음.
  • Not Running -> Active

    • 앱을 터치해서 실행이 되는 상태
  • Active -> Inactive -> Running

    • 앱을 활성화에서 비활성화 상태로 만든 뒤, 백그라운드에서도 계속 실행중인 상태
  • Active -> Inactive -> Suspend

    • 앱을 활성화에서 비활성화 상태로 만든 뒤, 백그라운드에서도 정지되어 있는 상태
  • Running -> Active

    • 백그라운드에서 실행 중인 앱이 다시 포어그라운드에서 활성화되는 상태

View

  • UI의 기본 구성 요소
  • 모든 뷰는 UIKit의 UIView 클래스의 자식클래스
  • 뷰는 앱의 UI를 구성하는 방법을 제공하며, 사용자의 터치 이벤트를 처리하고, 뷰의 내용을 그리는 방법을 제어하는 등의 작업을 수행
  • 뷰 계층(View Hierarchy)
    • 하나의 뷰는 여러 개의 하위 뷰를 가질 수 있고, 각 하위 뷰는 부모 뷰에 추가됨
    • 뷰 계층을 통해 뷰 간의 위치 관계와 그리기 순서를 결정
    • 보통 부모 뷰에 추가된 뷰는 부모 뷰의 영역 내에서만 그려짐
  • 레이블(UILabel),이미지 뷰(UIImageView), 버튼(UIButton), 텍스트 필드(UITextField) 등

UIWindow 클래스

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

  • 모든 앱은 하나 이상의 윈도우를 가지고 뷰 계층의 가장 상위에 위치하며 뷰 컨트롤러의 뷰와 그 하위 뷰들을 포함
    • 각 scene마다 자체 UIWindow 인스턴스를 가질 수 있음
  • UIWindow는 일반적으로 앱의 시작 시점에 생성되며, 앱의 메인 화면을 그리는 데 사용되는 뷰 컨트롤러를 root view controller로 설정
    • root view controller는 앱의 주요 내비게이션 논리를 관리하는 컨트롤러
  • UIWindow의 주요 역할
    • 뷰 컨트롤러의 뷰와 그 하위 뷰들을 포함하고, 그것들을 화면에 렌더링
    • 사용자로부터의 이벤트를 받아서 적절한 뷰나 뷰 컨트롤러로 전달

뷰 계층(view hierarchy) 구조

  • UI는 계층을 가지며 부모/자식 (superview/subview) 관계
  • 서브뷰들은 언제나 부모 뷰의 틀 안에서 보여짐
  • 버튼은 부모 뷰의 위에서 표시됨
  • 서브뷰의 크기 변화는(디바이스가 회전하여 뷰의 크기가 변화하는 경우) 부모 뷰와의 관계에 의해 정의됨
  • 수퍼뷰는 서브뷰의 위치 및 크기를 바꿀 수 있음

컨테이너 뷰(Container View)

  • 여러 개의 뷰 컨트롤러를 포함하고, 그 사이를 전환하는 역할
  • UINavigationController
    • 뷰 컨트롤러 간의 계층적 네비게이션을 관리
    • 리스트에서 항목을 선택하면 상세 화면으로 이동하거나, 여러 단계를 거쳐 정보를 입력하는 등의 작업에 사용
  • UITabBarController
    • 뷰 컨트롤러 간의 평행 네비게이션을 관리
    • 앱의 주요 기능을 각각의 탭으로 나누고 사용자가 원하는 탭을 선택하여 해당 기능을 이용할 수 있게 하는 데 사용

AlertView와 ActionSheet

  • 사용자에게 긴급하거나 중요한 정보를 표시해주고 어떠한 조치를 할 수 있는 버튼도 보여줄 수 있음
  • UIAlertView는 팝업 상자를 화면에 표시
  • UIActionSheet는 화면의 아래에서 위로 밀어 올리는 화면을 표시

AutoLayout

  • 요소를 정중앙으로 정렬하고 싶은경우 아래의 Container 의 값을 각각 0으로 지정하면 돼요.
  • Horizontally와 Vertically를 모두 체크하고 하단에 Add Constraints를 클릭해야 적용돼요.

  • AutoLayout은 각 기기별로 해상도와 방향을 모두 고려하여, 대부분의 환경(기기)에서 비슷한 위치를 가질 수 있도록 하는 기능이에요.

Auto Layout의 주요 개념

Auto Layout은 제약 기반 레이아웃 시스템으로, 뷰(View)들의 위치와 크기를 수학적인 관계식으로 정의합니다. 이 시스템은 UI 요소 간의 관계를 수학적 제약 조건(Constraint Equations) 형태로 모델링합니다.

1. Constraints (제약 조건)

제약 조건은 UI 요소들 간의 관계를 수학적 식으로 표현합니다. 기본적으로 Auto Layout은 선형 방정식(Linear Equations) 을 사용합니다. 제약 조건의 기본 형태는 다음과 같습니다:

view1.attribute=view2.attribute×multiplier+constantview1.attribute = view2.attribute \times multiplier + constant
  • attribute: 뷰의 특정 속성 (예: leading, trailing, top, bottom, width, height, centerX, centerY)
  • multiplier: 관계의 비율 (기본값은 1.0)
  • constant: 추가적인 오프셋 값

2. Intrinsic Content Size (내재된 크기)

UI 요소는 자신만의 내재된 크기를 가질 수 있습니다. 이는 주로 콘텐츠 기반 뷰(예: UILabel, UIButton, UIImageView)에서 사용되며, 요소가 자신의 콘텐츠를 기준으로 크기를 결정하는 속성입니다.

내재된 크기는 다음의 두 가지 속성을 가집니다:

  • intrinsicContentSize.width
  • intrinsicContentSize.height

Auto Layout은 기본적으로 내재된 크기를 고려하여 레이아웃을 계산하지만, 필요에 따라 명시적인 제약 조건으로 오버라이드할 수 있습니다.

3. Priority (우선순위)

제약 조건의 우선순위(Priority) 는 Auto Layout 시스템이 충돌 상황에서 어떤 제약을 우선적으로 적용할지를 결정하는 데 사용됩니다. 우선순위 값은 1부터 1000까지 설정할 수 있으며:

  • 1000 (Required): 반드시 충족해야 하는 제약 조건
  • < 1000 (Optional): 가능하면 적용하되, 필요시 무시 가능

4. Ambiguity (모호성) & Conflicts (충돌)

Auto Layout에서의 모호성은 뷰의 위치 또는 크기가 충분히 정의되지 않아 발생합니다. 충돌은 서로 상충하는 제약 조건이 동시에 적용될 때 발생합니다. 이러한 상황은 주로 다음과 같은 경우에 발생합니다:

  • 필수 제약 조건이 충돌할 때
  • 우선순위가 동일한 상충 조건이 있을 때
  • 크기나 위치를 명확히 정의하지 않은 경우

Xcode는 Interface Builder나 런타임에서 모호성이나 충돌이 있을 때 경고를 표시합니다. 디버깅 명령어를 사용하여 문제를 추적할 수 있습니다:

view.hasAmbiguousLayout
view.debugDescription

5. Safe Area (안전 영역)

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)
])

6. Content Hugging & Compression Resistance

Auto Layout에서는 뷰의 크기를 결정할 때 두 가지 추가 속성을 고려합니다:

  1. Content Hugging Priority (CHP): 뷰가 자신의 콘텐츠에 최대한 밀착하고자 하는 우선순위입니다. 값이 클수록 뷰가 작아지기를 거부합니다.
  2. Compression Resistance Priority (CRP): 뷰가 축소되어 콘텐츠가 잘리지 않도록 거부하는 우선순위입니다. 값이 클수록 크기가 줄어들지 않으려 합니다.

각 방법별 장단점

방법장점단점
스토리보드 (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 을 이용해 간단하게 전광판 앱을 만들어봤어요.

0개의 댓글