본문 바로가기

iOS

iOS_3grade - 3주차


이 코드는 단순히 함수 3개가 아니라 “View의 생명주기(Lifecycle)”를 보여주는 코드야.


🔄 View의 생명주기란?

👉 한 화면(ViewController)이
생성 → 나타남 → 사라짐 → 다시 나타남
이 과정을 반복하는 흐름


📱 이 코드가 의미하는 것

네 코드:

viewDidLoad
viewWillAppear
viewDidAppear

👉 이건 그냥 함수가 아니라

“이 화면이 언제 어떤 상태인지 알려주는 이벤트들”


🎬 쉽게 비유

🎭 무대 비유

  • viewDidLoad
    👉 무대 준비 (조명, 세트 설치)
  • viewWillAppear
    👉 커튼 올라가기 직전
  • viewDidAppear
    👉 관객이 실제로 보고 있는 상태

🔥 핵심 흐름

1. viewDidLoad      → 처음 생성됨
2. viewWillAppear   → 곧 보일 예정
3. viewDidAppear    → 이미 보이고 있음

🧠 중요한 개념 (진짜 핵심)

👉 “View는 계속 살아있지 않는다”

  • 처음 들어올 때 생성됨
  • 사라졌다가 다시 나타남
  • 그때마다 특정 함수들이 호출됨

📌 왜 이게 중요하냐

앱 기능은 이 타이밍에 따라 나뉨:

예시 (Night Light 앱 기준)

  • viewDidLoad
    👉 기본 색 세팅
  • viewWillAppear
    👉 저장된 밝기 다시 적용
  • viewDidAppear
    👉 타이머 시작 / 애니메이션 시작

⚠️ 이거 모르면 생기는 문제

초보들이 흔히 하는 실수:

👉 모든 코드를 viewDidLoad에 넣음

결과:

  • 화면 다시 들어올 때 값 안 바뀜
  • 버그처럼 보임

🔁 더 큰 그림 (전체 생명주기)

사실 더 있음:

viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear

👉 나타날 때 / 사라질 때 둘 다 있음


🎯 한 줄 핵심 정리

이 코드는 “화면이 언제 생성되고, 언제 보여지고 있는지 알려주는 타이밍 시스템”


 

 

 

 

자식들은 자신이 속해있는 부모 뷰를 통해 처리됨

//임의로 배경색 변경
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        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)
    }
}

 

//1초마다 랜덤 배경색
import UIKit

class ViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 처음 색 설정
        changeBackgroundColor()
        
        // 1초마다 실행
        timer = Timer.scheduledTimer(timeInterval: 1.0,
                                     target: self,
                                     selector: #selector(changeBackgroundColor),
                                     userInfo: nil,
                                     repeats: true)
    }
    
    @objc func changeBackgroundColor() {
        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)
    }
}

 

import UIKit
import AVFoundation

class ViewController: UIViewController {
  
    @IBOutlet weak var musicSwitch: UISwitch!
    var colorChangeTimer: Timer?
  var audioPlayer: AVAudioPlayer!

    @IBAction func switchDidChange(_ sender: UISwitch) {
        if sender.isOn { startTimerAndMusic()
        }else { stopTimerAndMusic()
        }
    }
    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() {
    startTimerAndMusic()
  }
  
  @objc func handleTapGesture() {
    stopTimerAndMusic()
    
    let alert = UIAlertController(title: "알림", message: "앱을 종료해주세요.", preferredStyle: .alert)
    let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
    alert.addAction(okAction)
    present(alert, animated: true, completion: nil)
  }
  
  func startTimerAndMusic() {
    // 배경 음악 재생 설정
    if let soundURL = Bundle.main.url(forResource: "bgm", withExtension: "mp3") {
      do {
        audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
        audioPlayer.play()
      } catch {
        print("음악 파일 로드 또는 재생 중 에러 발생: \(error)")
      }
    }
    
    // 1초마다 반복되는 타이머 생성
    colorChangeTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
      // 랜덤한 빨간색, 초록색, 파란색 값을 생성
      let red = CGFloat(arc4random_uniform(256)) / 255.0
      let green = CGFloat(arc4random_uniform(256)) / 255.0
      let blue = CGFloat(arc4random_uniform(256)) / 255.0
      
      // 생성된 랜덤 색상을 사용하여 ViewController의 배경색 설정
      self.view.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    }
  }
  
  func stopTimerAndMusic() {
    // 타이머와 음악 재생 중지
    colorChangeTimer?.invalidate()
    colorChangeTimer = nil
    audioPlayer.stop()
  }
}

- 6번 코드

import UIKit
import AVFoundation

class ViewController: UIViewController {
  
    @IBOutlet weak var musicSwitch: UISwitch!
    
    var colorChangeTimer: Timer?
    var fadeTimer: Timer?
    var audioPlayer: AVAudioPlayer!
    
    // 현재 밝기
    var currentAlpha: CGFloat = 1.0
    
    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)
    }
    
    @IBAction func switchDidChange(_ sender: UISwitch) {
        if sender.isOn {
            startTimerAndMusic()
        } else {
            stopTimerAndMusic()
        }
    }
    
    @objc func applicationWillEnterForeground() {
        if musicSwitch.isOn {
            startTimerAndMusic()
        }
    }
    
    @objc func handleTapGesture() {
        stopTimerAndMusic()
        
        let alert = UIAlertController(title: "알림",
                                      message: "앱을 종료해주세요.",
                                      preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default)
        alert.addAction(okAction)
        present(alert, animated: true)
    }
    
    func startTimerAndMusic() {
        // 🔴 중복 실행 방지
        if colorChangeTimer != nil { return }
        
        // 🎵 음악
        if let soundURL = Bundle.main.url(forResource: "bgm", withExtension: "mp3") {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
                audioPlayer.numberOfLoops = -1 // 무한 반복
                audioPlayer.volume = 1.0
                audioPlayer.play()
            } catch {
                print("음악 로드 실패: \(error)")
            }
        }
        
        // 🎨 색상 애니메이션 + 그라데이션
        colorChangeTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            // 랜덤 색상 2개를 선택
            let color1 = UIColor(red: CGFloat.random(in: 0...1),
                                 green: CGFloat.random(in: 0...1),
                                 blue: CGFloat.random(in: 0...1),
                                 alpha: 1.0)
            
            let color2 = UIColor(red: CGFloat.random(in: 0...1),
                                 green: CGFloat.random(in: 0...1),
                                 blue: CGFloat.random(in: 0...1),
                                 alpha: 1.0)
            
            // 그라데이션 레이어 생성
            let gradientLayer = CAGradientLayer()
            gradientLayer.frame = self.view.bounds
            gradientLayer.colors = [color1.cgColor, color2.cgColor]
            
            // 애니메이션 적용
            CATransaction.begin()
            CATransaction.setAnimationDuration(1.0)
            self.view.layer.sublayers?.forEach { $0.removeFromSuperlayer() } // 기존 제거
            self.view.layer.insertSublayer(gradientLayer, at: 0)
            CATransaction.commit()
        }
        
        // 🌙 Fade Out (밝기 점점 낮추기)
        currentAlpha = 1.0
        fadeTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
            if self.currentAlpha > 0.3 {
                self.currentAlpha -= 0.005
                self.view.alpha = self.currentAlpha
            }
        }
    }
    
    func stopTimerAndMusic() {
        // 타이머 종료
        colorChangeTimer?.invalidate()
        colorChangeTimer = nil
        
        fadeTimer?.invalidate()
        fadeTimer = nil
        
        // 음악 볼륨 천천히 줄이고 정지
        if let player = audioPlayer {
            player.setVolume(0, fadeDuration: 2.0) // 2초 페이드 아웃
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.1) {
                player.stop()
            }
        }
    }
}

- 기능을 추가한 코드

 

 

label의 텍스트 크기를 자동으로 조절하는 기능

 

//
//  ViewController.swift
//  Display
//
//  Created by 1 on 2026/03/17.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var label: UILabel!
    
    @IBAction func displayText(_ sender: UIButton) {
        label.text = textField.text
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        startScrolling()
    }
    func startScrolling() {
    // 시작 위치: 오른쪽 바깥
    label.center.x = view.bounds.width + label.bounds.width / 2
    UIView.animate(withDuration: 10, delay: 0, options: [.curveLinear, .repeat], animations: {
    // 끝 위치: 왼쪽 바깥
    self.label.center.x = -self.label.bounds.width / 2 }, completion: nil)
    }

}

 

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var label: UILabel!
    
    var scrollTimer: Timer?
    var blinkTimer: Timer?
    var ledColors: [UIColor] = [.red, .yellow, .white, .orange]
    
    var textToDisplay: String = ""
    var displayedText: String = ""
    
    var scrollInterval: TimeInterval = 0.2
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 화면 탭 시 LED 색상 랜덤 변경
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
        view.addGestureRecognizer(tapGesture)
    }
    
    @IBAction func displayText(_ sender: UIButton) {
        guard let input = textField.text, !input.isEmpty else { return }
        
        // 1️⃣ 대문자 변환
        textToDisplay = input.uppercased()
        
        // 2️⃣ 글자 수에 따른 스크롤 속도 자동 조절
        scrollInterval = max(0.05, 5.0 / Double(textToDisplay.count))
        
        // 3️⃣ 스크롤 시작
        startScrolling()
        
        // 4️⃣ 20초 후 자동 초기화
        DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
            if self.textToDisplay == input.uppercased() {
                self.textToDisplay = ""
                self.displayedText = ""
                self.label.text = ""
                self.stopTimers()
            }
        }
    }
    
    // MARK: - 스크롤 + LED 점등
    func startScrolling() {
        stopTimers()
        displayedText = textToDisplay + "     "
        
        scrollTimer = Timer.scheduledTimer(withTimeInterval: scrollInterval, repeats: true) { _ in
            guard !self.displayedText.isEmpty else { return }
            
            // 한 글자씩 이동
            let firstChar = self.displayedText.first!
            let rest = self.displayedText.dropFirst()
            self.displayedText = rest + String(firstChar)
            
            // 글자별 LED 점등
            let coloredText = NSMutableAttributedString(string: String(self.displayedText))
            for i in 0..<coloredText.length {
                let color = self.ledColors.randomElement()!
                coloredText.addAttribute(.foregroundColor, value: color, range: NSRange(location: i, length: 1))
            }
            
            self.label.attributedText = coloredText
        }
        
        // 깜빡임 효과
        blinkTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
            guard let text = self.label.attributedText else { return }
            let mutable = NSMutableAttributedString(attributedString: text)
            let length = mutable.length
            if length > 0 {
                let randomIndex = Int.random(in: 0..<length)
                mutable.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: randomIndex, length: 1))
                self.label.attributedText = mutable
            }
        }
    }
    
    func stopTimers() {
        scrollTimer?.invalidate()
        scrollTimer = nil
        blinkTimer?.invalidate()
        blinkTimer = nil
    }
    
    @objc func handleTapGesture() {
        ledColors.shuffle()
    }
}

기능 추가한 전광판 코드

 

 Swift에서 @IBOutlet weak var label: UILabel!처럼 암시적 언래핑(Implicitly Unwrapped Optional, !)을 왜 쓰는지에 대한 이유


1️⃣ 기본 개념

@IBOutlet weak var label: UILabel!
  • UILabel! → 암시적 언래핑 옵셔널
  • 옵셔널(UILabel?)과 비슷하지만, 컴파일러가 “사용할 때 자동으로 언래핑” 해준다는 의미
  • 즉, label.text = "Hello" 처럼 바로 접근 가능, label?.text = "Hello"처럼 안전하게 optional 체크할 필요 없음

2️⃣ 왜 암시적 언래핑을 사용하는가?

2-1. IBOutlet과 스토리보드 연결

  • 스토리보드에서 Label, Button, TextField 등 UI 요소를 IBOutlet으로 연결할 때
    • 뷰 컨트롤러가 초기화될 때는 label 객체가 아직 생성되지 않음
    • 하지만 뷰가 로드(viewDidLoad())되면 스토리보드가 label에 실제 객체를 연결해줌
  • 따라서 초기화 시점에는 nil일 수 있으므로 일반 Non-Optional로 선언할 수 없음
  • 하지만 뷰가 로드된 후에는 절대 nil이 아니므로 바로 사용 가능 → 안전하게 암시적 언래핑 사용 가능

2-2. Optional(?) 대신 ! 사용 이유

  • Optional(UILabel?)으로 선언하면, 접근할 때마다 아래처럼 써야 함:
label?.text = "Hello"
  • 하지만 암시적 언래핑(UILabel!)이면, 뷰가 로드된 후 바로 사용 가능:
label.text = "Hello"
  • 코드가 더 깔끔하고, 스토리보드와 IBOutlet 구조에서 안전하게 사용 가능

3️⃣ 주의 사항

  • 뷰가 아직 로드되기 전에 접근하면 런타임 에러 발생
    • 예: label.text = "Hello"를 init()나 viewDidLoad 이전에 접근
  • 일반적으로는 viewDidLoad() 이후에는 안전하므로 암시적 언래핑이 권장됨

4️⃣ 요약

구분특징사용 이유

UILabel! 암시적 언래핑 옵셔널 뷰 로드 이후에 절대 nil이 아니므로, 코드 간결하게 사용 가능
UILabel? 옵셔널 안전하지만 접근할 때 매번 ? 또는 ! 필요
UILabel 일반 Non-Optional 초기화 시점에서 반드시 값이 있어야 하므로 스토리보드 연결에서는 사용 불가

💡 한 줄 요약:
IBOutlet은 뷰가 로드되기 전에는 nil일 수 있으므로 Optional이어야 하고, 뷰가 로드된 후에는 항상 존재하므로 !로 암시적 언래핑해서 바로 사용하게 만든 것


UIButton 은 자료형처럼 쓰이는 클래스임

 

func add(x: Int, y: Int) -> Int { return(x+y)
}
print(add(x:10, y:20))

func add(xx x: Int,yy y: Int) -> Int { return(x+y)
}
print(add(xx:20, yy:40))
func add( _ x: Int,yy y: Int) -> Int { return(x+y)
}
print(add(10,yy: 20))

세번째 방식을 가장 많이 씀

 

Swift 함수의 매개변수 이름(Argument Label)과 내부 이름(Parameter Name) 구분입니다.


1️⃣ 기본 문법

func add(x: Int, y: Int) -> Int {
    return x + y
}
print(add(x: 10, y: 20)) // 30
  • 여기서 x와 y는 외부 이름 = 내부 이름
  • 호출할 때도 add(x:10, y:20)처럼 레이블과 함께 전달해야 함

2️⃣ 외부 이름과 내부 이름 분리

func add(xx x: Int, yy y: Int) -> Int {
    return x + y
}
print(add(xx: 20, yy: 40)) // 60
  • xx x → 외부 이름 = xx, 내부 이름 = x
  • 함수 내부에서는 x로 사용 가능, 호출할 때는 xx: 레이블 사용
  • 마찬가지로 yy y → 외부 이름 yy, 내부 이름 y

즉 func add(외부이름 내부이름: 타입) 형태


3️⃣ 외부 이름 생략(_)

func add(_ x: Int, yy y: Int) -> Int {
    return x + y
}
print(add(10, yy: 20)) // 30
  • _ x → 외부 이름 생략, 호출 시 레이블 없이 바로 값 전달 가능
  • yy y → 외부 이름 yy, 호출 시 yy: 사용
  • 그래서 호출할 때: add(10, yy: 20)

🔹 요약

선언호출 방식설명

func add(x: Int, y: Int) add(x:10, y:20) 외부 이름 = 내부 이름
func add(xx x: Int, yy y: Int) add(xx:20, yy:40) 외부 이름 xx/yy, 내부 이름 x/y
func add(_ x: Int, yy y: Int) add(10, yy:20) 첫 번째 인자 외부 이름 생략, 두 번째는 yy 사용

💡 핵심 포인트

  • Swift에서는 함수 인자의 **외부 이름(argument label)**과 **내부 이름(parameter name)**을 구분 가능
  • 외부 이름은 호출할 때 쓰이고, 내부 이름은 함수 내부에서 쓰임
  • _를 쓰면 외부 이름을 생략 가능

 

'iOS' 카테고리의 다른 글

iOS_grade3 - 5주차  (0) 2026.03.31
iOS_3grade - 4주차  (0) 2026.03.24
iOS_3grade - 2주차  (0) 2026.03.10
iOS_3grade - 1주차  (1) 2026.03.03
iOS_프로젝트  (1) 2025.12.08