root  view controller(시험)

OS 앱의 첫 번째 화면을 관리하는 뷰 컨트롤러를 의미

보통 앱이 실행되면, root view controller가 화면에 처음 표시됨

이 뷰 컨트롤러는 다른 뷰 컨트롤러들을 관리하거나 전환하는 역할을 함

 

 

 

Action Segue vs Manual Segue 비교

        특성                                      Action Segue                                                             Manual Segue

연결 방법 코드에서 특정 이벤트(버튼 클릭 등)로 트리거 스토리보드에서 화면 간 직접 연결
트리거 방식 코드 내에서 performSegue로 호출 스토리보드에서 직접 설정된 트리거
사용 예시 버튼을 눌러 다른 화면으로 이동할 때 버튼이나 특정 UI 요소를 드래그하여 연결
유연성 코드에서 조건에 맞게 세밀한 제어 가능 트리거가 정해져 있어 유연성이 적음
구현 위치 @IBAction 함수 내에서 사용 스토리보드에서 연결 설정
사용 상황 코드로 뷰 컨트롤러 간의 전환을 다룰 때 화면 전환을 스토리보드에서 시각적으로 설정할 때

(시험에 나오진 않음)

 

-> 사라졌을때 Is Initial View Controller체크하기

 

 

idedentity inspector시험나옴

 


func prepare(for segue: UIStoryboardSegue, sender: Any?)는 화면 전환(segue)이 일어나기 직전에 호출되는 메소드

주로 다음 화면으로 데이터를 넘겨줄 때 사용


언제 호출되나요?

  • 스토리보드에서 segue로 두 화면(ViewController)을 연결했을 때
  • 화면 전환이 일어나기 직전에 자동으로 호출
  • 수동이든 자동이든 performSegue()를 호출하면 이 메소드가 실행됨
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // segue.identifier로 어떤 segue인지 확인 가능
    // segue.destination으로 이동할 화면 접근
}


자료형?


DetailViewController.swift


import UIKit      // 사용자 인터페이스 구성 요소를 사용하기 위한 프레임워크
import WebKit     // 웹 페이지를 보여주기 위해 WKWebView를 사용

// 영화 상세 정보를 보여주는 화면(ViewController)
class DetailViewController: UIViewController {
    
    // 영화 제목을 표시할 UILabel (스토리보드에서 연결됨)
    @IBOutlet weak var nameLabel: UILabel!
    
    // 웹 페이지를 표시할 WKWebView (스토리보드에서 연결됨)
    @IBOutlet weak var webView: WKWebView!
    
    // 이전 화면에서 전달받을 영화 제목 변수 (기본값: "미션임파서블")
    var movieName = "미션임파서블"
    
    // 뷰가 메모리에 올라왔을 때 호출되는 생명주기 메서드
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // UILabel과 네비게이션 바 타이틀에 영화 제목 표시
        nameLabel.text = movieName
        navigationItem.title = movieName
        
        // 영화 이름을 포함한 네이버 검색 URL 생성
        let urlKorString =
            "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=" + movieName
        
        // URL에 한글이 포함되어 있을 수 있으므로 퍼센트 인코딩 처리
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        
        // 문자열을 URL 타입으로 변환, 실패 시 함수 종료
        guard let url = URL(string: urlString) else { return }
        
        // URL 요청 객체 생성
        let request = URLRequest(url: url)
        
        // WKWebView를 통해 웹 페이지 로드
        webView.load(request)
    }
    
    // MARK: - Table view data source
    // 현재 이 클래스는 UITableView를 사용하지 않으므로 비어 있음
}

 

ViewController.swift

//
//  ViewController.swift
//  Moviekdh
//
//  Created by 소프트웨어컴퓨터 on 2025/05/07.
//

import UIKit  // 사용자 인터페이스 요소를 사용하기 위한 프레임워크
let movie = ["야당","야당1","야당","야당3","야당4"]  // 영화 이름 배열 (현재 사용되지 않음)

// 영화 데이터를 디코딩하기 위한 구조체 정의
struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

// 박스오피스 결과를 담는 구조체
struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// 개별 영화의 박스오피스 정보를 담는 구조체
struct DailyBoxOfficeList: Codable {
    let movieNm: String  // 영화 제목
    let audiCnt: String  // 어제의 관객 수
    let audiAcc: String  // 누적 관객 수
    let rank: String     // 영화 순위
}

// ViewController 클래스 정의 (박스오피스 데이터를 보여주는 화면)
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView!  // 영화 정보를 보여줄 테이블 뷰
    var movieData: MovieData?  // 영화 데이터를 저장할 변수
    var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt="  // 영화 데이터 API URL
    
    // 화면이 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 테이블 뷰의 델리게이트와 데이터 소스를 설정
        table.delegate = self
        table.dataSource = self
        
        // 어제 날짜를 URL에 추가
        movieURL = movieURL + makeYesterdayString()
        
        // 영화 데이터를 가져오는 메서드 호출
        getDate()
    }

    // 어제 날짜를 "yyyyMMdd" 형식으로 반환하는 함수
    func makeYesterdayString() -> String {
        let y = Calendar.current.date(byAdding:.day, value:-1, to: Date())!  // 하루 전 날짜 계산
        let dateF = DateFormatter()
        dateF.dateFormat = "yyyyMMdd"  // 날짜 포맷 설정
        let day = dateF.string(from: y)  // 날짜를 문자열로 변환
        return day
    }
    
    // 영화 데이터를 API에서 가져오는 함수
    func getDate() {
        guard let url = URL(string: movieURL) else { return }  // URL 변환 실패 시 종료
        
        let session = URLSession(configuration: .default)  // URLSession을 이용해 데이터 요청
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil {
                print(error!)  // 에러 발생 시 출력
                return

MyTableViewCell.swift

//
//  MyTableViewCell.swift
//  Moviekdh
//
//  Created by 소프트웨어컴퓨터 on 2025/05/07.
//

import UIKit  // 사용자 인터페이스 관련 클래스들을 사용할 수 있게 해주는 프레임워크

// UITableViewCell을 상속받은 커스텀 셀 클래스
class MyTableViewCell: UITableViewCell {
   
    // 영화 제목, 어제의 관객 수, 누적 관객 수를 표시할 UILabel들
    @IBOutlet weak var audiAccumulate: UILabel!  // 누적 관객 수
    @IBOutlet weak var movieName: UILabel!       // 영화 제목
    @IBOutlet weak var audiCount: UILabel!       // 어제의 관객 수

    // 셀이 메모리로 로드될 때 호출되는 초기화 메서드
    override func awakeFromNib() {
        super.awakeFromNib()
        // 셀의 초기 설정 코드가 들어갈 수 있지만, 현재는 기본적인 초기화만 수행됨
    }

    // 셀이 선택된 상태일 때 호출되는 메서드
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // 선택된 상태에서 셀의 스타일을 설정할 수 있음
        // 예를 들어 선택되었을 때 셀 배경색을 바꾸는 등의 작업을 할 수 있음
    }
}
  1. ViewController.swift:
    • 영화 목록을 테이블 뷰에 표시합니다.
    • API에서 데이터를 가져와 MovieData 형태로 파싱한 후, 이를 **MyTableViewCell**에 표시합니다.
    • 사용자가 특정 영화를 선택하면, 해당 영화의 제목을 **DetailViewController**에 전달하여 세부 정보를 보여주기 위한 세그웨이를 트리거합니다.
  2. MyTableViewCell.swift:
    • 영화 정보를 보여주는 커스텀 셀입니다.
    • 각 셀은 영화 제목, 어제의 관객 수, 누적 관객 수 등을 표시하는 **UILabel**들로 구성됩니다.
    • ViewController에서 데이터를 받아와 이 셀에 표시합니다.
  3. DetailViewController.swift:
    • ViewController에서 선택된 영화의 상세 정보를 보여주는 화면입니다.
    • 영화 제목을 movieName 변수로 받아서 웹뷰에 관련된 정보를 표시하거나, 추가적인 영화 정보를 제공할 수 있습니다.

관계 요약:

  • ViewController.swift는 영화 목록테이블 뷰로 표시하고, 선택된 영화를 **DetailViewController**로 전달합니다.
  • **MyTableViewCell**은 ViewController에서 받은 영화 정보를 화면에 표시하는 역할을 합니다.
  • **DetailViewController**는 선택된 영화의 상세 정보를 표시합니다.

🎯 목표: MVC 구조로 리팩토링

MVC (Model-View-Controller)는 앱 구조를 깔끔하게 나눠주는 디자인 패턴입니다.

  • Model: 데이터 구조와 로직 (ex: MovieData, 네트워크 통신)
  • View: UI 요소 (ex: 스토리보드, UILabel, UITableViewCell 등)
  • Controller: 뷰와 모델을 연결하는 중간 관리자 (ex: ViewController, DetailViewController)

✅ 1. Model 분리

📁 Model/Movie.swift

 
import Foundation

struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

struct DailyBoxOfficeList: Codable {
    let movieNm: String
    let audiCnt: String
    let audiAcc: String
    let rank: String
}

✅ 2. Service(네트워크) 분리

📁 Service/MovieService.swift

import Foundation

class MovieService {
    static let shared = MovieService()
    
    private init() {}
    
    func fetchMovies(for date: String, completion: @escaping (MovieData?) -> Void) {
        let urlString = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt=\(date)"
        
        guard let url = URL(string: urlString) else {
            completion(nil)
            return
        }

        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else {
                completion(nil)
                return
            }

            do {
                let decoded = try JSONDecoder().decode(MovieData.self, from: data)
                completion(decoded)
            } catch {
                print("JSON 디코딩 오류:", error)
                completion(nil)
            }
        }
        task.resume()
    }
}

✅ 3. Controller 간결화

📁 Controller/ViewController.swift

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var table: UITableView!
    
    var movieData: MovieData?

    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        fetchMovies()
    }
    
    func fetchMovies() {
        let dateString = makeYesterdayString()
        MovieService.shared.fetchMovies(for: dateString) { [weak self] data in
            guard let self = self else { return }
            DispatchQueue.main.async {
                self.movieData = data
                self.table.reloadData()
            }
        }
    }
    
    func makeYesterdayString() -> String {
        let y = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyyMMdd"
        return dateFormatter.string(from: y)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let dest = segue.destination as? DetailViewController,
           let indexPath = table.indexPathForSelectedRow {
            dest.movieName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm ?? ""
        }
    }
}

'Swift' 카테고리의 다른 글

iOS 프로그래밍 실무 11주차  (1) 2025.05.14
iOS프로그래밍 실무 10주차  (1) 2025.05.07
iOS프로그래밍 실무 9주차  (0) 2025.05.06
iOS프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 6주차  (0) 2025.04.09

identity inspector 중요

// 전체 JSON 응답의 최상위 구조체
// "boxOfficeResult" 키를 통해 BoxOfficeResult 구조체를 포함
struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

// 박스오피스 결과를 담는 구조체
// "dailyBoxOfficeList" 키를 통해 하루치 박스오피스 목록을 배열로 포함
struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// 하루 동안의 박스오피스 데이터 하나를 표현하는 구조체
struct DailyBoxOfficeList: Codable {
    let movieNm: String    // 영화 제목 (예: "범죄도시4")
    let audiCnt: String    // 해당 일자의 관객 수 (문자열로 제공됨, 예: "12345")
    let audiAcc: String    // 누적 관객 수 (문자열로 제공됨, 예: "567890")
    let rank: String       // 박스오피스 순위 (예: "1")
}

// 제네릭 함수 'decode'를 정의합니다.
// 이 함수는 제네릭 타입 'T'를 사용하며, 'T'는 어떤 타입이든 될 수 있습니다.
// 보통 'T'는 Decodable(또는 Codable)을 따르는 타입입니다.
func decode<T>(
    _ type: T.Type,     // 파라미터 1: 디코딩할 대상 타입입니다.
                        // 예를 들어 MovieData.self 같은 타입 정보를 넘깁니다.
                        // '_ type'은 외부 인자 이름 생략을 의미합니다.

    from data: Data     // 파라미터 2: 디코딩할 JSON 데이터입니다.
                        // Foundation의 'Data' 타입이며, 보통 API에서 받은 JSON 바이트입니다.
) throws -> T           // 반환값: T 타입의 값을 반환합니다.
                        // 이 함수는 오류가 발생할 수 있으므로 'throws'가 붙어 있습니다.
                        // 예: JSON 형식이 잘못됐거나 타입이 일치하지 않을 경우 에러가 발생합니다.

위 코드 중요


예외처리 해야한다는 에러

do ~try~cstch


self.를 써야한다는 에러


iOS 앱에서 UI 관련 작업을 잘못된 쓰레드(백그라운드 쓰레드)에서 실행했을 때 발생하는 에러

 DispatchQueue.main.async {
                    self.table.reloadData()
                }
                
 //수정전엔
 self.table.reloadData() //이것만 있었음

이렇게 수정해야됨

 



import UIKit
let movie = ["야당","야당1","야당","야당3","야당4"]
struct MovieData : Codable {
    let boxOfficeResult : BoxOfficeResult
}
struct BoxOfficeResult : Codable {
    let dailyBoxOfficeList : [DailyBoxOfficeList]
}
struct DailyBoxOfficeList : Codable {
    let movieNm : String
    let audiCnt : String
    let audiAcc : String
    let rank : String
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    
    
    @IBOutlet weak var table: UITableView!
    var movieData : MovieData?
    let movieURL =
    "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt=20250513"
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        getDate()
    }
    func getDate(){
        guard let url = URL(string: movieURL) else { return } //옵셔널을 푸는 방법 if let {}하면 됨}
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil {
                print(error!)
                return
            }
            guard let JSONdata = data else { return }
            let dataString = String(data: JSONdata, encoding: .utf8)
            //print(dataString!)
            let decoder = JSONDecoder()
            do{
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                self.movieData = decodedData
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
                
            }catch{
                print(error)
            }
            }
        task.resume()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =
        tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)as! MyTableViewCell
        cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
        return cell
        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    
    
    
    
    
}
//
//  ViewController.swift
//  Moviekdh
//
//  Created by 소프트웨어컴퓨터 on 2025/05/07.
//

import UIKit
let movie = ["야당","야당1","야당","야당3","야당4"]
struct MovieData : Codable {
    let boxOfficeResult : BoxOfficeResult
}
struct BoxOfficeResult : Codable {
    let dailyBoxOfficeList : [DailyBoxOfficeList]
}
struct DailyBoxOfficeList : Codable {
    let movieNm : String
    let audiCnt : String
    let audiAcc : String
    let rank : String
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    
    
    @IBOutlet weak var table: UITableView!
    var movieData : MovieData?
    var movieURL =
    "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt="
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        movieURL = movieURL + makeYesterdayString()
        getDate()
    }
 
    func makeYesterdayString() -> String {
        let y = Calendar.current.date(byAdding:.day, value :-1, to : Date())!
        let dateF = DateFormatter()
        dateF.dateFormat = "yyyyMMdd"
        let day = dateF.string(from: y)
        return day
    }
    
    func getDate(){
        guard let url = URL(string: movieURL) else { return } //옵셔널을 푸는 방법 if let {}하면 됨}
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil {
                print(error!)
                return
            }
            guard let JSONdata = data else { return }
            let dataString = String(data: JSONdata, encoding: .utf8)
            //print(dataString!)
            let decoder = JSONDecoder()
            do{
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                self.movieData = decodedData
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
                
            }catch{
                print(error)
            }
            }
        task.resume()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =
        tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)as! MyTableViewCell
        cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
        return cell
        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    
    
    
    
    
}

레이블 2개를 새로 만들어 Stack View에 넣기

 

(레이블과 스택뷰 )Stack View안에 Stack View가 있는 구조

 

 

MyTableViewCell

import UIKit

class MyTableViewCell: UITableViewCell {
   
    @IBOutlet weak var audiAccumulate: UILabel!
    @IBOutlet weak var movieName: UILabel!
    @IBOutlet weak var audiCount: UILabel!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

 

 

ViewController


import UIKit
let movie = ["야당","야당1","야당","야당3","야당4"]
struct MovieData : Codable {
    let boxOfficeResult : BoxOfficeResult
}
struct BoxOfficeResult : Codable {
    let dailyBoxOfficeList : [DailyBoxOfficeList]
}
struct DailyBoxOfficeList : Codable {
    let movieNm : String
    let audiCnt : String
    let audiAcc : String
    let rank : String
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    
    
    @IBOutlet weak var table: UITableView!
    var movieData : MovieData?
    var movieURL =
    "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt="
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        movieURL = movieURL + makeYesterdayString()
        getDate()
    }
 
    func makeYesterdayString() -> String {
        let y = Calendar.current.date(byAdding:.day, value :-1, to : Date())!
        let dateF = DateFormatter()
        dateF.dateFormat = "yyyyMMdd"
        let day = dateF.string(from: y)
        return day
    }
    
    func getDate(){
        guard let url = URL(string: movieURL) else { return } //옵셔널을 푸는 방법 if let {}하면 됨}
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil {
                print(error!)
                return
            }
            guard let JSONdata = data else { return }
            let dataString = String(data: JSONdata, encoding: .utf8)
            //print(dataString!)
            let decoder = JSONDecoder()
            do{
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                self.movieData = decodedData
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
                
            }catch{
                print(error)
            }
            }
        task.resume()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    //func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   //     let cell =
   //     tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)as! MyTableViewCell
   //     cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
   //     cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc
   //     cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt
   //     return cell
        
   // }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        guard let mRank = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].rank else{return UITableViewCell()}
        guard let mName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm else{return UITableViewCell()}
        cell.movieName.text = "[\(mRank)위] \(mName)"
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aCount = Int(aCnt)!
            let result = numF.string(for: aCount)!+"명"
            cell.audiCount.text = "어제: \(result)"
        }
        if let aAcc = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aAcc1 = Int(aAcc)!
            let result = numF.string(for: aAcc1)!+"명"
            cell.audiAccumulate.text = "누적: \(result)"
        }
        return cell
    }
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "박스오피스(영화진응위원회제공:" + makeYesterdayString() + ")"
    }
    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "made by dh"
    }


    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    
    
    
    
    
}

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무12주차  (1) 2025.05.21
iOS프로그래밍 실무 10주차  (1) 2025.05.07
iOS프로그래밍 실무 9주차  (0) 2025.05.06
iOS프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 6주차  (0) 2025.04.09

hared State – 공유되는 state

• 하나의 데이터를 여러 개의 컴포넌트에서 활용해야 하는 경우

 

./conponents/SharedState.jsx

import React, { useState } from 'react';
// 자식 A: 2배 값 표시
function DoubleDisplay({ value }) {
    return (
        <div>
            <span>자식 A(x2) : {value * 2}
            </span>
        </div>
    );
}
// 자식 B: 3배 값 표시
function TripleDisplay({ value }) {
    return (
        <div>
            <span>자식 B(x3) : {value * 3}
            </span>
        </div>
    );
}
export default function SharedState() {
    const [value, setValue] = useState(0);
    const handleChange = (e) => {
        setValue(Number(e.target.value));
    };
    return (
        <div>
            부모 컴포넌트
            <input
                type="number"
                value={value}
                onChange={handleChange}
            />
            <DoubleDisplay value={value} />
            <TripleDisplay value={value} />
        </div>
    );
}

 

 

실습.))

추가 기능 구현

• 온도에 따라 조건부 메시지 출력

• 100도(섭씨 기준) 이상인 경우“물이 끓습니다.” 메시지 출력

 • 100도 (섭씨 기준) 미만인 경우“물이 끓지 않습니다.”메시지 출력

import React, { useState } from 'react';

// 자식 : 화씨->섭씨
function ComponentC({ temp, changeTemp }) {
  return (
    <div>
      <span>단위(섭씨) : </span>
      <input
        type="number"
        value={temp}
        onChange={(e) => changeTemp(e.target.value)}
      />
    </div>
  );
}

// 자식 : 섭씨->화씨
function ComponentF({ temp, changeTemp }) {
  return (
    <div>
      <span>단위(화씨) : </span>
      <input
        type="number"
        value={temp}
        onChange={(e) => changeTemp(e.target.value)}
      />
    </div>
  );
}

// 부모 컴포넌트
export default function SharedStateApp() {
  const [temperature, setTemperature] = useState(0);
  const [type, setType] = useState('c');

  // 섭씨->화씨 변환 함수
  const toFahrenheit = (celsius) => {
    return ((celsius * 9) / 5 + 32).toFixed(1);
  };

  // 화씨->섭씨 변환 함수
  const toCelsius = (fahrenheit) => {
    return (((fahrenheit - 32) * 5) / 9).toFixed(1);
  };

  // 섭씨 입력 변경 핸들러
  const handleCelsiusChange = (temp) => {
    setTemperature(temp);
    setType('c');
  };

  // 화씨 입력 변경 핸들러
  const handleFahrenheitChange = (temp) => {
    setTemperature(temp);
    setType('f');
  };

  // 현재 온도 상태와 단위를 기준으로 섭씨와 화씨 값을 계산
  const celsius = type === "c" ?
    temperature : toCelsius(temperature);

  const fahrenheit = type === "f" ?
    temperature : toFahrenheit(temperature);

  return (
    <div>
      <h2>온도 변환</h2>
      <ComponentC
        temp={celsius}
        changeTemp={handleCelsiusChange}
      />
      <ComponentF
        temp={fahrenheit}
        changeTemp={handleFahrenheitChange}
      />
      {/* 이 부분 오타 수정 */}
      <p>{celsius >= 100 ? "물이 끓습니다" : "물이 끓지 않습니다"} </p>
    </div>
  );
}

 

컨텍스트 개념

• 컴포넌트 간 데이터 전달 방식

• 리액트 컴포넌트 사이에서 props를 통해 데이터를 전달하는 방식

 Context

• React 컴포넌트 트리 안에서 전역적으로 데이터를 공유할 수 있도록 고안된 방법

• 컴포넌트 트리를 통해 곧바로 컴포넌트에 데이터를 전달하는 방식

 

Context API

• React.createContext

• Context 객체를 생성하기 위한 함수

 

 Context.Provider

• Context 객체에 포함된 React 컴포넌트로 데이터 제공자 역할

 

Context.Consumer

• context의 데이터를 구독하는 React 컴포넌트

 

Context.Consumer

• function as a child - 컴포넌트의 자식으로 함수를 사용하는 방법

 

Context.displayName

• 크롬의 리액트 개발자 도구에서 context를 표시할 때 이 displayName을 표시

 

 

실습2))

context를 사용하여 테마 변경 기능 만들기

 

 ./components/ThemeContext.jsx

import React from "react";

const ThemeContext = React.createContext();

ThemeContext.displayName = 'Theme-Context';

export default ThemeContext;

 ./components/MainContent.jsx

import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function MainContent(props) {
    const { theme, toggleTheme } = useContext(ThemeContext);

    return (
        <div
            style={{
                width: '100vw',
                height: '100vh',
                padding: '1.5rem',
                // 'bavkgroundColor' -> 'backgroundColor' 로 수정
                backgroundColor: theme === 'light' ? 'white' : 'black',
                color: theme === 'light' ? 'black' : 'white',
            }}
        >
            <p>테마 변경이 가능한 웹사이트입니다.</p>
            {/* 'onclick' -> 'onClick' 로 수정 */}
            <button onClick={toggleTheme}>테마 변경</button>
        </div>
    );
}

// 'MainCOntent' -> 'MainContent' 로 수정
export default MainContent;

./components/DarkOrLight.jsx

import { useState, useCallback } from 'react';
import ThemeContext from './ThemeContext';
import MainContent from './MainContent';

function DarkOrLight(props) {
    const [theme, setTheme] = useState('light');

    const toggleTheme = useCallback(() => {
        if (theme === 'light') {
            setTheme('dark');
        } else if (theme === 'dark') {
            setTheme('light');
        }
    }, [theme]);

    return (
        // 'Value' -> 'value' 로 수정
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {/* 'MainCOntent' -> 'MainContent' 로 수정 */}
            <MainContent />
        </ThemeContext.Provider>
    );
}

export default DarkOrLight;

 

'React' 카테고리의 다른 글

리스트(List), 키(Key), 폼(Forms)  (1) 2025.05.11
훅(Hook)  (2) 2025.04.15
컴포넌트와 Props  (1) 2025.04.02
리액트JSX  (0) 2025.03.25
소프트웨어 설계 3주차  (3) 2025.03.18

리스트(List)

:같은 아이템을 순서대로 모아놓은 것

 

키(Key)

:각 객체나 아이템을 구분할 수 있는 고유한 값(두 리스트 사이에선 키가 같아도 상관없음)

 

Array.map() 활용하여 여러개의 컴포넌트 랜더링

: 배열에 모든 요소에 대해 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환

const numbers = [1,2,3,4,5];
const listItems = numbers.map(number =>
	<li>{number}</li>
    );

 

 

리스트 출력하는 컴포넌트

./components/NumberList.jsx

import React from "react";
export default function NumberList(props) {
const { numbers } = props;
const listItems = numbers.map( number =>
<li>{number}</li>
);
return (
<ul> {listItems} </ul>
);
}

 

./index.js

import NumberList from './components/NumberList'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<NumberList numbers={ [1, 2, 3, 4, 5] } />
</React.StrictMode>
);

 

 

*실행시 경고문이 출력되는데 , 현재 각 아이템에 키가 없기 때문임

 

키 값으로 숫자의 값을 사용

const numbers = [1,2,3,4,5];//배열 요소 중복 가능서 있음
const listItems = numbers.map(number =>
<li> key = {numbber.toString()}>{number}</li>
);

 

키 값으로 id를 사용하는 방식(아이템의 고유한 id가 있을 경우 사용)

const todos = [
{id:1, text: '첫 번째 할일'},
{id:2, text: '두 번쨰 할일'}
]
const todoItems = todos.map(todo =>
	<li key = {todo.id}>{todo.text}</li>
    );

 

 

키 값으로 인덱스를 사용하는 방법 - 아이템의 고유한 id가 없을 경우 사용

map() 메서드 안에 있는 엘리먼트는 key가 필수

const todos = [
{ text: '첫 번째 할일'},
{ text: '두 번쨰 할일'}
]
const todoItems = todos.map(todo, index) =>
	<li key = {index}>{todo.text}</li>
    );

 

 

실습) 출석부 만들기

import React from "react";

const students = [
   {
		id:1,
    	name: "Inje",
    },
    {	id:2,
    	name: "Steve",
    },
      {	id:3,
    	name: "Bill",
    },
    
];

export default function AttenanceBook(props){
	return(
		<ul>
			{students.map((student, index) => {
            	return<li key = {student.id}>
                	{student.name}</li>
             })}
             </ul>
         );
   }

 

 

폼(Forms)

:사용자로부터 입력을 받기 위해 사용하는 양식

 

리액트는 컴포넌트 내부에서 state를 통해 데이터를 관리

 

제어 컴포넌트(Controlled Component)

React에 의해서 값이 제어되는 컴포넌트 (input form element)

제어 컴포넌트를 사용하면 입력값이 리액트 컴포넌트의 state를 통해 관리할 수 있음

 사용자의 입력을 직접적으로 제어할 수 있음

 

function NameForm(props) {
	const[value, setValue] = useState('');
    
    const handleChange = (event) => {
    	setValue(event.target.value); //event객체->event.target: 이벤트가 발생한 타겟
        }

	const handleSubmit = (event_ => {
    	alert(`입력한 이름 : ${vlaue}`);
        event.preventDefault();
        }//event.preventDefault();->새로운 페이지로 연결되는 기본 동작 중단시킴
        
        return(
        <form onSubmit = {handleSubmit}>
        <label>
        이름:
        <input type = "text" value = {value} onChange = {handleChange} />
        </label>
        <button type = "submit" >제출 </button>
        </form>
        )
        }

 

 

textarea 태그

• 여러 줄의 텍스트를 입력 받기 위한 HTML 태그

• 리액트 – textarea 태그의 value 라는 속성을 사용하여 텍스트를 표시

<label>
요청사항:
<textarea value = {value} onChange = {handleChange}/>
</label>

 

 

select 태그

• drop-down 목록을 보여주기 위한 HTML 태그

리액트 – selected 대신 value 속성을 사용하여 선택 값을 표시

 

// FruitSelect라는 이름의 React 함수 컴포넌트를 정의합니다.
// 이 컴포넌트는 props라는 매개변수를 받지만, 현재 코드에서는 사용하지 않습니다.
function FruitSelect(props) {
	// useState 훅을 사용하여 'value'라는 상태 변수를 선언하고,
	// 초기값을 'grape' (포도)로 설정합니다.
	// value는 현재 선택된 과일의 값을 저장합니다.
	// setValue는 value 상태를 업데이트하는 함수입니다.
	const [value, setValue] = React.useState('grape'); // React.useState로 명시적으로 사용

	// select 박스의 값이 변경될 때 호출되는 함수입니다.
	const handleChange = (event) => {
    	// 이벤트 객체(event)를 통해 현재 선택된 select option의 value를 가져와서
    	// setValue 함수를 사용하여 value 상태를 업데이트합니다.
        // 오타가 있어서 event.targegt.value -> event.target.value 로 수정했습니다.
    	setValue(event.target.value);
    }

    // 폼 제출(submit) 이벤트가 발생했을 때 호출되는 함수입니다.
    // 오타가 있어서 handleSubit -> handleSubmit 로 수정했습니다.
    const handleSubmit = (event) => {
        // 현재 value 상태에 저장된 과일 값을 사용하여 경고창을 띄웁니다.
		alert(`선택한 과일: ${value}`);
        // 폼의 기본 제출 동작(페이지 새로고침 등)을 막습니다.
        event.preventDefault();
     }

    // 컴포넌트가 렌더링할 JSX(JavaScript XML)를 반환합니다.
    return(
    // 폼(form) 엘리먼트를 생성하고, 제출 이벤트(onSubmit)가 발생하면
    // 위에서 정의한 handleSubmit 함수를 호출하도록 설정합니다.
    <form onSubmit = {handleSubmit}>
    	{/* 레이블 엘리먼트를 생성합니다. */}
    	<label>
        과일을 선택하세요:
        	{/* select 엘리먼트를 생성합니다. */}
            {/* value 속성은 현재 select 박스에 표시될 값을 value 상태 변수와 연결합니다. */}
            {/* onChange 속성은 select 박스의 값이 변경될 때 handleChange 함수를 호출하도록 설정합니다. */}
        	<select value = {value} onChange = {handleChange}>
            	{/* 옵션 엘리먼트들입니다. 각각 과일의 값과 표시될 텍스트를 정의합니다. */}
            	<option value = "apple"> 사과</option>
                <option value = "banana"> 바나나</option>
                <option value = "grape"> 포도</option>
            </select> {/* select 엘리먼트 닫는 태그가 빠져있어서 추가했습니다. */}
        </label>
        {/* 제출(submit) 버튼을 생성합니다. */}
        <button type = "submit"> 제출</button>
    </form>
    )
}

// 이 컴포넌트를 다른 파일에서 사용할 수 있도록 내보냅니다. (예: export default FruitSelect;)
// 실제 사용 시에는 이 컴포넌트를 렌더링하는 코드가 필요합니다.
// 예: ReactDOM.render(<FruitSelect />, document.getElementById('root'));

 

• multiple inputs

• 여러 개의 state를 선언하여 각각의 입력에 대해 사용

 

Input Null Value

• 제어 컴포넌트에서 value 값을 지정하되 입력 값을 자유롭게 변경하고자 할 때

• 제어 컴포넌트에 value 값이 정해지면 입력값을 바꿀 수 없음

• value에 undefined 또는 null을 넣어 주면 입력이 가능한 상태로 변경할 수 있음

 

 

실습)) 회원가입 폼 만들기

./components/SignUpORg.jsx

import React, { useState } from "react";

// 컴포넌트 정의: 회원가입 폼
export default function SignUp() {
  // 각 입력 필드의 상태(state) 정의
  const [name, setName] = useState("");             // 이름
  const [gender, setGender] = useState("남자");     // 성별 (기본값: 남자)
  const [email, setEmail] = useState("");           // 이메일
  const [password, setPassword] = useState("");     // 비밀번호
  const [birthdate, setBirthdate] = useState("");   // 생년월일
  const [agree, setAgree] = useState(false);        // 약관 동의 체크 여부
  const [error, setError] = useState("");           // 에러 메시지 상태
  const [emailValid, setEmailValid] = useState(true); // 이메일 유효성 여부

  // 이메일 형식을 정규표현식으로 검사하는 함수
  const validateEmail = (email) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email); // 정규표현식에 맞으면 true, 아니면 false 반환
  };

  // 이메일 입력 시마다 호출되는 함수 (실시간 유효성 검사)
  const handleEmailChange = (e) => {
    const value = e.target.value;
    setEmail(value); // 입력된 이메일 값을 상태에 저장
    setEmailValid(validateEmail(value)); // 이메일 유효성 검사 결과 저장
  };

  // 폼 제출 시 실행되는 함수
  const handleSubmit = (event) => {
    event.preventDefault(); // 폼의 기본 제출 동작을 막음

    // 필수 입력 항목 확인
    if (!name) {
      alert("이름을 입력해주세요");
      return;
    }

    if (!validateEmail(email)) {
      alert("이메일 주소가 유효하지 않습니다");
      return;
    }

    if (!agree) {
      alert("약관에 동의해주세요");
      return;
    }

    // 모든 조건이 만족되었을 때 실행
    setError(""); // 에러 메시지 초기화
    alert(`이름: ${name}\n이메일: ${email}\n성별: ${gender}\n생년월일: ${birthdate}`);
  };

  // 실제로 사용자에게 보여지는 폼 구성
  return (
    <form onSubmit={handleSubmit}>
      <h2>회원가입</h2>

      {error && <p style={{ color: "red" }}>{error}</p>} {/* 에러 메시지 표시 */}

      <label>
        이름:
        <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      </label>
      <br />

      <label>
        성별:
        <select value={gender} onChange={(e) => setGender(e.target.value)}>
          <option value="남자">남자</option>
          <option value="여자">여자</option>
        </select>
      </label>
      <br />

      <label>
        이메일:
        <input type="email" value={email} onChange={handleEmailChange} />
        {!emailValid && (
          <p style={{ color: "red", marginTop: 0 }}>
            유효한 이메일 주소를 입력해주세요.
          </p>
        )}
      </label>
      <br />

      <label>
        비밀번호:
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      </label>
      <br />

      <label>
        생년월일:
        <input type="date" value={birthdate} onChange={(e) => setBirthdate(e.target.value)} />
      </label>
      <br />

      <label>
        <input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} />
        약관에 동의합니다.
      </label>
      <br />

      <button type="submit">가입</button>
    </form>
  );
}

 

이메일 유효성 검사 정규 표현식

const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

 

@ 앞뒤로 문자 존재 여부

도메인에 . 포함 여부 검사

공백 또는 잘못된 기호는 허용하지 않음

 

 

코드 흐름 요약

  1. 사용자가 입력 폼에 정보를 입력
  2. useState 훅을 통해 각 항목의 값이 상태로 저장됨
  3. 이메일 입력 시 handleEmailChange 함수로 유효성 실시간 검사
  4. 가입 버튼 클릭 시 handleSubmit 함수 실행
    • 이름/이메일/약관 동의 항목에 대해 유효성 검사
    • 문제 없으면 alert()으로 정보 출력

'React' 카테고리의 다른 글

Shared State  (0) 2025.05.13
훅(Hook)  (2) 2025.04.15
컴포넌트와 Props  (1) 2025.04.02
리액트JSX  (0) 2025.03.25
소프트웨어 설계 3주차  (3) 2025.03.18

constraints가득 채우기 : 0으로 맞추고 constrain to margins 체크 해제

import UIKit // UIKit 프레임워크를 임포트 (iOS UI 구성 요소들을 사용하기 위해 필요)

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // ViewController 클래스는 UIViewController를 상속받고,
    // UITableViewDelegate와 UITableViewDataSource 프로토콜을 채택하여
    // 테이블 뷰의 동작과 데이터를 처리함

    @IBOutlet weak var table: UITableView!
    // 인터페이스 빌더에서 연결된 UITableView 아웃렛 (스토리보드에 있는 테이블 뷰와 연결됨)

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // 각 섹션에 몇 개의 셀(행)이 들어갈지를 정의
        return 5 // 각 섹션에 5개의 셀을 표시
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 테이블 뷰에서 셀을 생성하고 반환하는 메서드
        return UITableViewCell() // 기본 스타일의 셀 객체를 반환 (아무 내용이 없는 셀)
        // ()는 생성자 호출로, 새 객체(여기서는 UITableViewCell)를 생성함
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        // 테이블 뷰의 섹션 수를 설정
        return 2 // 테이블 뷰에 2개의 섹션을 표시
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // 뷰가 메모리에 로드되었을 때 호출되는 메서드

        table.delegate = self // 테이블 뷰의 델리게이트를 현재 ViewController로 설정
        table.dataSource = self // 테이블 뷰의 데이터 소스를 현재 ViewController로 설정
    }
}

label의 constraints를 5로 설정

3번째 단계 어려움

import UIKit
let movie = ["야당","야당1","야당","야당3","야당4"]
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
   
    

    @IBOutlet weak var table: UITableView!
    let movieURL =
    "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt=20250506"
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        getDate()
    }
    func getDate(){
        guard let url = URL(string: movieURL) else { return } //옵셔널을 푸는 방법 if let {}하면 됨}
        print(url)
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let cell =
        tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)as! MyTableViewCell
        cell.movieName.text = movie[indexPath.row]
        return cell
        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 5
    }
    
 
    
   


}

옵셔널 푸는 법 if let {}을 쓰거나 guard let ~else {거짓일떄 실행할 내용을 씀} 를 씀

 

함수 안에서 옵셔널을 풀때는 guard let이 가독성이 좋다!!

 

url session은 class임

이렇게 후행클로저 스타일로 바꾸는 법 : tab키누른 후 enter치면 됨

//  ViewController.swift

import UIKit // UIKit 프레임워크를 임포트, iOS의 UI 구성 요소 사용을 위해 필요

// 영화 제목 배열
let movie = ["야당", "야당1", "야당", "야당3", "야당4"]

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // ViewController 클래스가 UIViewController를 상속받고, 
    // UITableViewDelegate와 UITableViewDataSource 프로토콜을 채택하여 테이블 뷰의 동작을 처리합니다.
    
    @IBOutlet weak var table: UITableView! // 스토리보드에서 연결된 테이블 뷰 아울렛

    // 영화 데이터를 가져올 API URL
    let movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=2bc95506177a89ebc75d86551353ab57&targetDt=20250506"

    // viewDidLoad는 뷰가 메모리에 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 테이블 뷰의 델리게이트와 데이터 소스를 현재 ViewController로 설정
        table.delegate = self
        table.dataSource = self
        
        // 영화 데이터를 가져오는 함수 호출
        getDate()
    }

    // 영화 데이터를 API에서 가져오는 함수
    func getDate() {
        // URL(string:)으로 movieURL을 URL 객체로 변환, 실패하면 함수 종료
        guard let url = URL(string: movieURL) else { return }
        
        // URLSession을 기본 설정으로 생성
        let session = URLSession(configuration: .default)
        
        // 데이터를 비동기적으로 가져오는 task 생성
        let task = session.dataTask(with: url) { data, response, error in
            // 네트워크 요청 중 오류가 발생하면 오류를 출력하고 함수 종료
            if error != nil {
                print(error!) // 오류 메시지 출력
                return
            }
            
            // 받은 데이터가 없으면 함수 종료
            guard let JSONdata = data else { return }
            
            // 받은 데이터를 UTF-8 문자열로 변환하여 출력
            let dataString = String(data: JSONdata, encoding: .utf8)
            print(dataString) // 변환된 데이터를 출력
        }
        
        // 비동기적으로 네트워크 작업을 시작
        task.resume()
    }

    // 테이블 뷰의 행(row) 개수를 설정하는 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // 배열 'movie'의 길이만큼 행을 반환
        return movie.count
    }
    
    // 테이블 뷰의 셀을 설정하는 메서드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 재사용 가능한 셀을 가져오고, 그 셀을 'MyTableViewCell'로 강제 변환
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        
        // 셀의 movieName 레이블에 'movie' 배열에서 해당 인덱스의 영화 제목을 설정
        cell.movieName.text = movie[indexPath.row]
        
        // 설정된 셀 반환
        return cell
    }

    // 테이블 뷰에서 셀을 선택했을 때 호출되는 메서드 (선택된 셀에 대해 추가 작업을 처리할 수 있음)
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 선택된 셀에 대한 작업을 추가할 수 있음 (현재는 비어 있음)
    }

    // 테이블 뷰의 섹션 개수를 설정하는 메서드
    func numberOfSections(in tableView: UITableView) -> Int {
        // 섹션 수를 1로 설정 (기본적으로 하나의 섹션만 사용)
        return 1
    }
}

 

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

import Foundation

// MARK: - Welcome
struct Welcome {
    let boxOfficeResult: BoxOfficeResult
}

// MARK: - BoxOfficeResult
struct BoxOfficeResult {
    let boxofficeType, showRange: String
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList {
    let rnum, rank, rankInten: String
    let rankOldAndNew: RankOldAndNew
    let movieCD, movieNm, openDt, salesAmt: String
    let salesShare, salesInten, salesChange, salesAcc: String
    let audiCnt, audiInten, audiChange, audiAcc: String
    let scrnCnt, showCnt: String
}

enum RankOldAndNew: String {
    case old
}

 

 

perplexity

import Foundation

struct MovieData: Codable {
    let rnum: String
    let rank: String
    let rankInten: String
    let rankOldAndNew: String
    let movieCd: String
    let movieNm: String
    let openDt: String
    let salesAmt: String
    let salesShare: String
    let salesInten: String
    let salesChange: String
    let salesAcc: String
    let audiCnt: String
    let audiInten: String
    let audiChange: String
    let audiAcc: String
    let scrnCnt: String
    let showCnt: String
}

struct BoxOfficeResult: Codable {
    let boxofficeType: String
    let showRange: String
    let dailyBoxOfficeList: [MovieData]
}

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무12주차  (1) 2025.05.21
iOS 프로그래밍 실무 11주차  (1) 2025.05.14
iOS프로그래밍 실무 9주차  (0) 2025.05.06
iOS프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 6주차  (0) 2025.04.09

 

REST & RESTful

  • REST: HTTP 기반 소프트웨어 아키텍처 스타일 (로이 필딩의 논문에서 정의)
  • RESTful: REST 원칙을 잘 지킨 웹 서비스

 REST 구성 요소

  • 리소스(Resource): 웹에서 다루는 정보 (예: /users/1)
  • URI: 리소스의 주소 (예: https://site.com/users/1)
  • HTTP 메서드:
    • GET: 조회
    • POST: 생성
    • PUT: 수정
    • DELETE: 삭제

RESTful하지 않은 예

  • URI에 동작이 들어간 경우
    예: POST /users/getUser, GET /users/deleteUser

RESTful API vs SOAP

  • REST: URL과 HTTP 메서드 사용, 응답은 보통 JSON 또는 XML
  • SOAP: XML 기반, 고정된 구조(WSDL 필요)

데이터 형식

XML

  • 태그 기반 (<tag>내용</tag>)
  • 플랫폼 독립적, 데이터량 많음

JSON

  • 경량 데이터 교환 형식
  • 사람이 읽기 쉬움, {}는 객체, []는 배열
  • 예:
  • json
    { "이름": "김컴소", "나이": 20, "성별": "남" }

API

  • API (Application Programming Interface):
    • 프로그램 간 소통을 위한 인터페이스
    • 컴퓨터와 사람을 잇는 UI, 컴퓨터끼리는 API
    • 예: Open API, printf, Windows API 등

 

 

guard let중요

함수 : 일급 객체 

func up(num: Int) -> Int {
return num + 1
}
func down(num: Int) -> Int {
return num - 1
}
let toUp = up
print(up(num:10))
print(toUp(10))
let toDown = down
func upDown(Fun: (Int) -> Int, value: Int) {
let result = Fun(value)
print("결과 = \(result)")
}
upDown(Fun:toUp, value: 10) //toUp(10)
upDown(Fun:toDown, value: 10) //toDown(10)
func decideFun(x: Bool) -> (Int) -> Int {
//매개변수형 리턴형이 함수형
if x {
return toUp
} else {
return toDown
}
}
let r = decideFun(x:true) // let r = toUp
print(type(of:r)) //(Int) -> Int
print(r(10)) // toUp(10)

후행 클로저

func mul(a: Int, b: Int) -> Int {
return a * b
}
let multiply = {(a: Int, b: Int) -> Int in
return a * b
}
print(mul(a:10, b:20))
print(multiply(10, 20))
let add = {(a: Int, b: Int) -> Int in
return a + b
}
print(add(10, 20))
func math(x: Int, y: Int, cal: (Int, Int) -> Int) -> Int {
return cal(x, y)
}
var result = math(x: 10, y: 20, cal: add) //return add(10,20)
print(result)
result = math(x: 10, y: 20, cal: multiply)//return multiply(10,20)
print(result)
result = math(x: 10, y: 20, cal: {(a: Int, b: Int) -> Int in
return a + b
}) //클로저 소스를 매개변수에 직접 작성
print(result)
result = math(x: 10, y: 20) {(a: Int, b: Int) -> Int in
return a + b
}//trailing closure
print(result)

클로저의 축약 표현들

// 기본 클로저 정의 및 사용
let multiply = { (a: Int, b: Int) -> Int in
    return a * b
}
var result = multiply(10, 20)
print(result) // 200

let add = { (a: Int, b: Int) -> Int in
    return a + b
}
result = add(10, 20)
print(result) // 30

// 함수를 통한 클로저 전달
func math(x: Int, y: Int, cal: (Int, Int) -> Int) -> Int {
    return cal(x, y)
}

result = math(x: 10, y: 20, cal: add)
print(result) // 30

result = math(x: 10, y: 20, cal: multiply)
print(result) // 200

// 다양한 클로저 표현 방법
result = math(x: 10, y: 20, cal: { (val1: Int, val2: Int) -> Int in
    return val1 + val2
})
print(result) // 30

result = math(x: 10, y: 20) { (val1: Int, val2: Int) -> Int in
    return val1 + val2
}
print(result) // 30

result = math(x: 10, y: 20, cal: { (val1, val2) in
    return val1 + val2
})
print(result) // 30

result = math(x: 10, y: 20) { (val1, val2) in
    return val1 + val2
}
print(result) // 30

result = math(x: 10, y: 20, cal: {
    return $0 + $1
})
print(result) // 30

result = math(x: 10, y: 20) {
    return $0 + $1
}
print(result) // 30

result = math(x: 10, y: 20, cal: {
    $0 + $1
})
print(result) // 30

result = math(x: 10, y: 20) { $0 + $1 }
print(result) // 30

guard~let

func printName(firstName: String, lastName: String?) {
    // 옵셔널 바인딩: lastName이 nil일 경우 함수 조기 종료
    guard let lName = lastName else {
        print("성이 없네요!")
        return
    }
    
    print(lName, firstName)
}

// 호출 예시
printName(firstName: "길동", lastName: "홍")  // 출력: 홍 길동
printName(firstName: "길동", lastName: nil)  // 출력: 성이 없네요!

failable initializer(실패 가능한 생성자: init? init!)

class Man{
var age : Int
var weight : Double
func display(){
print("나이=\(age), 몸무게=\(weight)")
}
init?(age: Int, weight : Double){
if age <= 0 {
return nil
}
else {
self.age = age
}
self.weight = weight
} // failable initialize
}
var kim : Man = Man(age:10, weight:20.5)
kim.display()
var lee : Man = Man(age:0, weight:3.5)
lee.display()

'Swift' 카테고리의 다른 글

iOS 프로그래밍 실무 11주차  (1) 2025.05.14
iOS프로그래밍 실무 10주차  (1) 2025.05.07
iOS프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 6주차  (0) 2025.04.09
iOS프로그래밍 실무 5주차  (0) 2025.04.02

열거형 정의

// 열거형(Enumeration)을 정의하는 코드
enum Compass { 
    // `enum` 키워드로 열거형을 정의하며, 이 열거형의 이름은 `Compass`입니다.
    // 열거형 내에는 네 가지 방향을 나타내는 값들이 있습니다.
    
    // 열거형 값들 (각각의 경우는 특정 방향을 나타냄)
    case North // 북쪽을 나타내는 값
    case South // 남쪽을 나타내는 값
    case East  // 동쪽을 나타내는 값
    case West  // 서쪽을 나타내는 값
}

// `var x : Compass`는 `Compass`라는 열거형 타입을 가진 변수 `x`를 선언하는 코드입니다.
// 그러나 여기에 주석이 달린 대로 실제로 선언은 아래와 같이 되어 있습니다.
var x = Compass.West // `Compass` 열거형의 `West` 값을 `x`에 할당
// 위 코드에서는 `Compass`를 명시하지 않고 `West`만 사용해도 자동으로 `Compass` 타입임을 인식합니다.

// `print(Compass.North)`는 `Compass` 열거형의 `North` 값을 출력하는 코드입니다.
// 열거형의 각 값을 참조할 때는 `.`, 즉 점(.)을 사용하여 접근합니다.
print(Compass.North) // 출력 결과: North

// `var x = Compass.West`로 `x`라는 변수를 `Compass.West`로 초기화한 후,
// `type(of: x)`를 사용하여 변수 `x`의 타입을 확인할 수 있습니다.
print(type(of: x)) // 출력 결과: Compass

// `x = .East`는 `x` 변수에 `Compass` 열거형의 `East` 값을 할당하는 코드입니다.
// 이때 `Compass`를 생략할 수 있습니다. 왜냐하면 `x`가 이미 `Compass` 타입이기 때문입니다.
x = .East // `Compass`를 생략해도 `x`는 여전히 `Compass` 타입으로 인식됨

// `print(x)`는 `x` 변수에 할당된 값을 출력하는 코드입니다.
// 현재 `x`는 `Compass.East` 값을 가지고 있기 때문에, "East"가 출력됩니다.
print(x) // 출력 결과: East
 enum Compass { 
 //열거형은 enum 그리고 이름(자료형)을 씀 열거형은 처음에만 이름.을 쓰고 그 후에는 생략 가능 
case North,South,East,West //이렇게 한줄에 써도 됨
}
 //var x : Compass // Compass형 인스턴스 x
 print(Compass.North) // North /case를 접근할떄는 .으로 접근
var x = Compass.West
print(type(of:x)) // Compass
x = .East//Compass를 생략가능 위에서 var x = Compass.West이렇게 x에 compass를 알기 때문 
print(x) // East

 

 

열거형 멤버별 기능 정의

// 열거형(Enumeration) 정의
enum Compass {
    case North  // 북쪽을 나타내는 값
    case South  // 남쪽을 나타내는 값
    case East   // 동쪽을 나타내는 값
    case West   // 서쪽을 나타내는 값
}

// `direction`이라는 변수 선언. 타입은 `Compass` 열거형.
var direction: Compass

// `direction` 변수에 `Compass.South` 값을 할당
direction = .South

// `switch` 문을 사용하여 `direction` 값을 확인
switch direction { 
case .North:
    // 만약 `direction`이 `.North`이면 "북"을 출력
    print("북")
case .South:
    // 만약 `direction`이 `.South`이면 "남"을 출력
    print("남")
case .East:
    // 만약 `direction`이 `.East`이면 "동"을 출력
    print("동")
case .West:
    // 만약 `direction`이 `.West`이면 "서"를 출력
    print("서")
}
// 출력 결과: "남" (왜냐하면 direction이 .South로 설정되었기 때문)

 

 

열거형 멤버에는 메서드도 가능

주말 출력

// 열거형(Enumeration) 정의
enum Week {
    // 일주일의 각 요일을 나타내는 값들
    case Mon, Tue, Wed, Thur, Fri, Sat, Sun
    
    // 열거형에 메서드 추가
    func printWeek() {
        // self는 메서드가 호출된 인스턴스(여기서는 특정 요일)를 의미
        switch self {
        case .Mon, .Tue, .Wed, .Thur, .Fri:
            // 주중인 경우 (월요일부터 금요일까지)
            print("주중")
        case .Sat, .Sun:
            // 주말인 경우 (토요일과 일요일)
            print("주말")
        }
    }
}

// `Week.Sun` 열거형 값에 대해 `printWeek()` 메서드를 호출
Week.Sun.printWeek()  // 이 코드가 실행됨
//주말

주중 출력

// 열거형(Enumeration) 정의
enum Week {
    // 일주일의 각 요일을 나타내는 값들
    case Mon, Tue, Wed, Thur, Fri, Sat, Sun
    
    // 열거형에 메서드 추가
    func printWeek() {
        // self는 메서드가 호출된 인스턴스(여기서는 특정 요일)를 의미
        switch self {
        case .Mon, .Tue, .Wed, .Thur, .Fri:
            // 주중인 경우 (월요일부터 금요일까지)
            print("주중")
        case .Sat, .Sun:
            // 주말인 경우 (토요일과 일요일)
            print("주말")
        }
    }
}

// `Week.Tue` (화요일)을 주중으로 설정하여 `printWeek()` 메서드를 호출
Week.Tue.printWeek()  // 주중이 출력됩니다.

열거형의 rawValue

enum Color: Int { //원시값(rawValue) 지정/ 열거형들은 rowvalue를 지정할 수 있다
case red
case green = 2
case blue
}
print(Color.red) //red
print(Color.blue)//blue
print(Color.red.rawValue) //0
print(Color.blue.rawValue)//3

rowvalue를 string형으로 지정한 예시

// 열거형(Enumeration) 정의
// Week 열거형은 주의 요일을 나타내며, 각 요일은 문자열(String) 값과 연결됩니다.
enum Week: String {
    // 각 요일에 대해 문자열 값을 직접 할당
    case Monday = "월"    // 월요일은 "월"로 값이 할당됨
    case Tuesday = "화"   // 화요일은 "화"로 값이 할당됨
    case Wednesday = "수" // 수요일은 "수"로 값이 할당됨
    case Thursday = "목"  // 목요일은 "목"으로 값이 할당됨
    case Friday = "금"    // 금요일은 "금"으로 값이 할당됨

    // Saturday와 Sunday는 값을 명시하지 않으면, case 이름이 자동으로 값으로 할당됨
    case Saturday         // "Saturday"라는 문자열 값이 자동으로 할당됨
    case Sunday           // "Sunday"라는 문자열 값이 자동으로 할당됨
}

// `Week.Monday`는 열거형 값 `Monday`를 나타냄
// `print(Week.Monday)`는 열거형 자체의 이름을 출력
print(Week.Monday)  // 출력 결과: Monday

// `.rawValue`는 열거형 값에 연결된 실제 값을 반환
// `Week.Monday.rawValue`는 "월"이라는 값을 출력
print(Week.Monday.rawValue)  // 출력 결과: 월

// `Week.Sunday`는 열거형 값 `Sunday`를 나타냄
// 이 경우, raw value는 자동으로 "Sunday"라는 문자열로 할당됨
print(Week.Sunday)  // 출력 결과: Sunday

// `Week.Sunday.rawValue`는 `Sunday`에 해당하는 raw value인 "Sunday"를 출력
// 하지만 위에서 `rawValue`가 자동으로 "Sunday"로 설정되었기 때문에 "Sunday"가 출력됨
print(Week.Sunday.rawValue)  // 출력 결과: Sunday

연관 값(associated value)을 갖는 enum

enum Date {
case intDate(Int,Int,Int) //(int,Int,Int)형 연관값을 갖는 intDate
case stringDate(String) //String형 연관값을 값는 stringDate
}
var todayDate = Date.intDate(2025,4,30)
todayDate = Date.stringDate("2025년 5월 20일") //Date생략 가능/주석처리하면?
switch todayDate {
case .intDate(let year, let month, let day)://위 주석처리하면 case문 print됨
print("\(year)년 \(month)월 \(day)일")
case .stringDate(let date):
print(date)
}
// `age`라는 변수는 Optional 타입(Int?)로 선언되었습니다.
// Optional은 값이 있을 수도 있고, 없을 수도 있는 상태를 나타냅니다.
// 여기서는 `age`가 `30`이라는 값을 가지고 있습니다.
let age: Int? = 30 // Optional(30)

// Optional은 내부적으로 두 가지 상태인 `some`과 `none`으로 구분됩니다.
// `some`은 값이 있을 때, `none`은 값이 없을 때 (즉, `nil`일 때) 사용됩니다.

switch age {  // `age`가 Optional이므로, 이를 `switch` 문으로 처리합니다.
case .none:  // 값이 없는 경우, 즉 `age`가 `nil`일 경우
    print("나이 정보가 없습니다.")  // "나이 정보가 없습니다." 출력
case .some(let a) where a < 20:  // 값이 있을 경우, `a`로 해당 값을 받으며, 20살 미만인 경우
    print("\(a)살 미성년자입니다")  // 예: 18살이면 "18살 미성년자입니다" 출력
case .some(let a) where a < 71:  // 값이 있을 경우, `a`로 해당 값을 받으며, 71살 미만인 경우
    print("\(a)살 성인입니다")  // 예: 30살이면 "30살 성인입니다" 출력
default:  // `a`가 71 이상인 경우
    print("경로우대입니다")  // "경로우대입니다" 출력
}

(셤x(?))

구조체 Memberwise Initializer가 자동으로 만들어지는게 장점

 

// struct Resolution { //구조체 정의 , 첫글자 대문자로 지정
// var width = 1024 //프로퍼티, 초기값 필요
// var height = 768
// }
// let myComputer = Resolution() //인스턴스 생성, 괄호 열고 닫으면 인스턴스가 됨
// print(myComputer.width) //프로퍼티 접근

 

// struct Resolution { //구조체 정의
// var width : Int //프로퍼티 초기값이 없어요!!
// var height : Int

// init(width: Int, height :Int){
//     self.width = width,
//     self.height = height
// }
// let myComputer = Resolution(width:1920,height:1080) //Memberwise Initializer /위에 init를 적어야 initializer을 호출 가능 
// print(myComputer.width) 
// // 과제 struct을 class로 변경하면 오류. init만들어 오류 제거하기


struct Resolution { //구조체 정의
var width : Int //프로퍼티 초기값이 없어요!!
var height : Int

} //init()메서드 없어요, 그런데!
let myComputer = Resolution(width:1920,height:1080) //Memberwise Initializer 구조체로 만들면 Memberwise Initializer 자동 생성 가능(init없어도 됨)
print(myComputer.width) 
// 과제 struct을 class로 변경하면 오류. init만들어 오류 제거하기

 

구조체는 init을 명시적으로 작성하지 않아도, 자동으로 "memberwise initializer"가 제공됨

 

 

클래스 내에 구조체

struct Resolution {//중첩된 class와 struct
var width = 1024
var height = 768
}
class VideoMode {
var resolution = Resolution()
var frameRate = 0.0
}
let myVideo = VideoMode()
print(myVideo.resolution.width) //024 최종적으로 접근하려면 점으로 접근

 

시험X /차이점이 나옴

 

 

차이점

class가 struct보다 더 갖는 특징에서 중요한 것은 상속이 가능 

swift에선 상속이 가능하기 때문에 부모와 자식간에는 형 변환 가능

+구조체는 값 타입 클래스는 참조타입

var x = 1
var y = x
print(x,y)//1 1
x = 2
print(x,y)//2 1
y = 3
print(x,y)//2 3
//값 타입은 복사할 때 새로운 데이터가 하나 더 생김

 

구조체struct일때 (value type)

//구조체는 별개임 둘 중에 하나만 바뀌면 그것만 바뀜

struct Human {
var age : Int = 1
}
var kim = Human()
var lee = kim //값 타입 한번 초기값을 복사해주고 lee와 kim은 별개임
print(kim.age, lee.age)//1 1
lee.age = 20
print(kim.age, lee.age)//1 20
kim.age = 30
print(kim.age, lee.age)//30 20

 

구조체를 calss로 바꿨을때

//값 복사가 아닌 주소 복사임

//구조체(struct)를 class로 바꿈
class Human {
var age : Int = 1
}
var kim = Human()
var lee = kim //참조reference 타입 //참조 타입은 복사할 때 주소를 복사해서
//한 데이터의 reference가 2개 생김
//주소를 복사해주기 때문에 하나가 바뀌면 동시에 바뀜
print(kim.age, lee.age)//1 1
lee.age = 20
print(kim.age, lee.age)//20 20
kim.age = 30
print(kim.age, lee.age)//30 30

 


struct Resolution {//구조체는 별개임 둘 중에 하나만 바뀌면 그것만 바뀜
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var frameRate = 0
var name: String?
}
var hd = Resolution(width: 1920, height: 1080)
//자동 Memberwise Initializer
var highDef = hd
//구조체는 값타입(value type)
print(hd.width, highDef.width)//1920 1920
hd.width = 1024
print(hd.width, highDef.width)//1024 1920

**간단한 자료들을 하나의 묶음으로 관리하고 싶을때는 구조체

덩치자 커지거나 기능이 많은 걸 만들때는 class사용함

 

 

타입이름 /대문자로 시작: Upper Camel case

클래스/구조체 안의 프로퍼치나 메서드 /소문자 시작: lower Camel Case

 

 

4주차 강의자료 p38

extension

:클래스를 건들이지 않고 기능을 추가할 때 사용

 extension Double { //double이라는 구조체의 기능을 확장하겠다
var squared : Double {//Double으로 나오게 해줘
return self * self 
}
}
 let myValue: Double = 3.0
 print(myValue.squared) //과제//9.0
 print(4.0.squared) //Double형 값에도 .으로 바로 사용 가능//16.0
 print(myValue.isZero) //instance property, 결과?//false

 

 

 

 

 

 

 

 

 

 

 

 

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무 10주차  (1) 2025.05.07
iOS프로그래밍 실무 9주차  (0) 2025.05.06
iOS프로그래밍 실무 6주차  (0) 2025.04.09
iOS프로그래밍 실무 5주차  (0) 2025.04.02
iOS실무 프로그래밍 4주차  (0) 2025.03.26

Hooks

:함수 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수

 

-리액트의 state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만드는 것

=>이때 실행되는 함수가 훅

-훅의 이름은 모두 use로 시작

 

useState( ) 기반 state 사용하기

• 함수 컴포넌트에서는 기본적으로 state를 제공하지 않기 때문에 state를 사용하려면 useState() 훅을 사용해야 함

• React 라이브러리를 로드할 때 useState도 함께 로드

 

import React, { useState } from "react";

 

사용법

const [변수명, set함수명] = useState(초기값);

useState()를 호출할 때는 state의 초기값을 파라미터로 전달

• 리턴 값 - 배열 1) state로 선언된 변수 2) state의 set 함수

• state 로 선언된 변수는 반드시 set 함수만을 사용해서 변경해야 함

• state 변수가 변경되면 이를 참조하는 UI는 자동으로 재렌더링 작업이 수행됨.

 

 

예시) 카운터 (useState( ) 훅 기반) • 변수 각각에 대해 set함수를 개별 정의하여 사용함.

import React, { useState } from "react";
function Counter(porps) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={ () => {
setCount(count+1);
 console.log(count);
}}>클릭</button>
</div>
);
}
export default Counter

**setCount는 바로 반영이 안됨, 컴포넌트가 재렌더링이 되어야 반영됨, 그렇기 때문에 화면 출력결과와 콘솔에서 출력한 count값이 다름 (즉시 반영이 아님, 렌더링된 시점에 state변수가 반영이 됨)

 

 

 

예시) 카운터 (useState( ) 훅 기반) – 함수 업데이트 방식 • 이전 상태의 값을 사용해야 할 경우 “함수형 업데이트“ 방식으로 처리

import React, { useState } from "react";
function Counter(porps) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={ () => {
setCount(prev => prev+1);
setCount(prev => prev+1);
 console.log(count);
}}>클릭</button>
</div>
);
}
export default Counter

재렌더링이 되기 전에 seycount함수 누적해서 수정이 됨

여러번 했을떄 횟수만큼 반영이 됨

기존의 최신값을 이루어지도록 사용?

 

 

useEffect() - Side effect를 수행하기 위한 훅

 

- 다른 컴포넌트에 영향을 미칠 수 있으며 렌더링 중에는 작업이 완료될 수 없음

- 즉, 렌더링이 끝난 이후에 실행되어야 하는 작업

 

클래스 컴포넌트에서 제공하는 생명주기 함수 componentDidMount(), componentDidUpdate(), ComponentWillMount()와 동일한 기능을 하나로 통합해서 제공

• useEffect() 훅만으로 위 생명주기 함수와 동일한 기능을 수행할 수 있음

 

useEffect(이펙트 함수, 의존성 배열);

 

의존성 배열 - – 이 이펙트가 의존하고 있는 배열. 배열안에 있는 변수 중 하나라도 값이 변경되면 이펙트 함수가 실행됨

이펙트 함수는 처음 컴포넌트가 렌더링 된 이후, 업데이트로 재렌더링 이후에 실행됨.

 

의존성 배열이 빈 값 – Effect 함수는 mount, unmount시에 단 한번씩 실행됨

useEffect(이펙트 함수, []);

 

의존성 배열 생략 – 컴포넌트가 업데이트 될 때마다 호출

useEffect(이펙트 함수);

 


 

import React, {useState, useEffect} from "react";

 

 

title메시지에 바로 반영됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;//title메시지에 바로 반영됨
   }, [count]);

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

 

title메시지에 바로 반영 안됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
   // document.title = `${count}번 클릭`;
   }, [count]);

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
                document.title = `${count}번 클릭`;//title메시지에 바로 반영 안됨
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

빈 배열이면 title에 반영 안됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;
   //}, [count]);
}, []); //이렇게 빈 배열이면 title에 반영안됨
    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

 

배열 생략 컴포넌트 업데이트 될때마다 title도 동일하게 변경됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;
    });//배열 생략 컴포넌트 업데이트 될떄마다 title도 동일하게 변경됨

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

실습. useState()와 useEffect()를 이용한 자동 증감 카운터

import React, { useState, useEffect } from "react";

function Counter(props) {
    // count: 현재 카운트 숫자 (초기값 0)
    // setCount: count를 변경하는 함수
    const [count, setCount] = useState(0);

    // isRunning: 카운터 작동 여부를 저장하는 상태 (초기값 false = 정지 상태)
    // setIsRunning: isRunning을 변경하는 함수
    const [isRunning, setIsRunning] = useState(false);

    // useEffect: 컴포넌트가 렌더링되거나 의존값이 바뀔 때 실행됨
    useEffect(() => {
        if (!isRunning) return; 
        // isRunning이 false면 아무 것도 하지 않음 → 카운터 멈춤

        // 1초(1000ms) 뒤에 count를 1 증가시킴
        const timer = setTimeout(() => {
            setCount(count + 1); 
            // 주의: count는 이전 상태값이므로, 배열에 count를 넣어줘야 최신 값 반영 가능
        }, 1000);

        // 클린업 함수: setTimeout이 쌓이지 않도록 정리
        return () => clearTimeout(timer);
    }, [count, isRunning]); 
    // 의존성 배열: count 또는 isRunning이 바뀔 때마다 useEffect 실행됨

    return (
        <div>
            <p>{count}</p> {/* 현재 count 값 화면에 표시 */}
            <button onClick={() => {
                setIsRunning(!isRunning);
                // 버튼 클릭 시 isRunning 값을 반전시킴 (START ↔ STOP)
            }}>
                {isRunning ? 'STOP' : 'START'}
                {/* 현재 상태에 따라 버튼 텍스트 변경 */}
            </button>
        </div>
    );
}

export default Counter;

 

 

useMemo란?

"비싼 계산 결과를 기억(Memoization)해서 불필요한 연산을 줄여주는 훅"

 
// 오래 걸리는 계산을 수행하는 함수
function compute(x, y) {
  // 이 부분은 계산량이 많은 작업이라고 가정
  // 예: 복잡한 수학 계산, 반복문, 대용량 데이터 처리 등
}

// React 컴포넌트
function MyComponent({ x, y }) {
  // 컴포넌트가 렌더링될 때마다 compute(x, y)가 실행됨
  // 즉, x나 y가 바뀌지 않아도 매번 동일한 계산을 다시 함
  // 계산량이 많은 작업일 경우 렌더링 속도 저하 발생 가능
  const value = compute(x, y);

  // 계산된 값을 화면에 출력함
  return <div>{value}</div>;
}

 동작 방식

  • x 또는 y가 바뀔 때만 computeExpensiveValue() 함수를 실행함.
  • 그 외에는 **이전에 계산해 둔 값(memoized value)**을 재사용함.
  • 즉, 불필요한 계산을 줄여서 성능 향상에 도움!

Memoization(메모이제이션)이란?

같은 입력 → 같은 출력이면,
결과값을 저장해서 재사용하는 기법

 왜 필요할까?

  • 반복해서 동일한 계산을 할 필요 없음
  • CPU 리소스 아낌
  • UI 렌더링 속도 향상

 예시

 
function computeExpensiveValue(a, b) { console.log(" 계산 중..."); // 시간이 오래 걸리는 작업이라고 가정 return a + b; }

이 함수가 매번 호출된다면 → 불필요한 낭비!
👉 useMemo()로 감싸면, 값이 변하지 않는 한 딱 한 번만 계산됨!

상황useMemo 필요 여부

단순한 값 계산 ❌ 필요 없음
반복적으로 실행되는 무거운 계산 ✅ 사용하는 것이 좋음
렌더링 성능 저하가 체감되는 경우 ✅ 추천

 

사용법

const memoizedValue = useMemo(
	() => {
		//연산량이 높은 작업을 수행하여 결과 반환
        return computeExpensiveValue(의존성변수1, 의존성 변수2);
        },
        [의존성 변수1, 의존성 변수2]
 );

• 의존성 배열의 요소 값이 업데이트될 때만 콜백 함수를 다시 호출

- momoization된 값을 업데이트 후 다시 memoization 해줌

• useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행됨

• 렌더링이 일어나는 동안 실행되서는 안되는 작업은 useMemo()에 사용하면 안됨

- useEffect()에서 실행돼야 할 이펙트 작업

 

 

 

실습)) usememo() 느리게 작동하는 숫자 제곱 함수

import React, { useState, useMemo } from 'react';

function BigSquare() {
  // 📌 number: 입력 숫자 (제곱 계산의 대상)
  // 📌 setNumber: number 값을 변경하는 함수
  const [number, setNumber] = useState(0);

  // 📌 count: 단순히 +1씩 증가하는 카운터 값
  // 📌 setCount: count 값을 변경하는 함수
  const [count, setCount] = useState(0);

  // 🐢 느리게 계산되는 함수 (일부러 느리게 만들었음)
  const slowSquare = (num) => {
    console.log('계산 중...'); // 매번 계산될 때마다 콘솔 출력
    let result = 0;

    // 이 반복문은 실제로 제곱 계산이 느리게 되도록 만든 예제용
    for (let i = 0; i < 2500000000; i++) {
      result = num * num;
    }

    return result;
  };

  // 🚀 useMemo: number 값이 바뀔 때만 slowSquare(number) 호출
  // 그렇지 않으면 이전에 계산된 제곱 값을 재사용함 (성능 최적화!)
  const squared = useMemo(() => slowSquare(number), [number]);

  return (
    <div>
      <h2>느린 제곱 계산기</h2>

      {/* 사용자 입력 필드: 입력한 숫자가 number에 저장됨 */}
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(Number(e.target.value))}
      />

      {/* 계산된 제곱 결과 출력 */}
      <p>제곱 결과: {squared}</p>

      {/* count 값 출력 */}
      <p>{count}</p>

      {/* 클릭할 때마다 count 값을 1씩 증가시킴 */}
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default BigSquare;

 

*setCount 함수 사용시 useMemo 덕분에 number 값이 바뀌지 않으면 계산 작업이 생략됨. 즉 불필요한 재렌더링 작업이 발생하지 않음.

 

 

useCallback()

: 함수를 메모이제이션 하기 위한 훅

-첫번째 인자로 넘어온 함수를 의존성 배열 내의 값이 변경될 때까지 저장하여 재사용

 

const memoizedCallback = useCallback(
()=>{
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);

  useCallback() 훅을 사용하지 않고 컴포넌트 내에 함수를 정의할 경우, 렌더링이 일어날 때마다 함수가 새로 정의됨

• useCallback() 훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의

 

실습. useCallback( )

import React, { useState, useEffect, useCallback } from "react";

// FetchComponent: 검색어에 따라 데이터를 가져오는 예제 컴포넌트
export default function FetchComponent() {
  // 📌 keyword: 사용자가 입력한 검색어 상태
  const [keyword, setKeyword] = useState("");

  // 📌 data: 검색 결과를 저장하는 상태
  const [data, setData] = useState("");

  // 📌 fetchData 함수: keyword를 기반으로 가짜 검색 결과를 가져옴
  // useCallback 사용 이유 → 의존성 배열([keyword])에 따라 함수 재생성 방지
  const fetchData = useCallback(() => {
    console.log("데이터 불러오는 중...");

    // 1초 후에 검색 결과를 보여주는 타이머 (API 대체용)
    setTimeout(() => {
      setData(`"${keyword}"에 대한 검색 결과`);
    }, 1000);
  }, [keyword]); // keyword가 바뀔 때만 fetchData 함수가 새로 생성됨

  // 📌 useEffect: keyword가 변경될 때마다 자동으로 fetchData 실행
  useEffect(() => {
    console.log("useEffect()");
    if (keyword) fetchData(); // 검색어가 비어있지 않으면 fetch 실행
  }, [fetchData]); // fetchData가 바뀔 때 실행되므로, keyword 변화 감지 가능

  return (
    <div>
      <h2>검색 예제 (단일 컴포넌트)</h2>

      {/* 사용자 입력: keyword 상태 업데이트 */}
      <input
        type="text"
        placeholder="검색어 입력"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
      />

      {/* 결과 출력 */}
      <p>결과: {data}</p>

      {/* 버튼 클릭 시 수동으로 fetchData 호출 가능 */}
      <button onClick={fetchData}>수동 검색</button>
    </div>
  );
}

 

*차이점

usememo는 데이터임 데이터가 저장되는, usecallback은 함수 함수로 호출

 

 

useRef( )

• useRef() – Reference를 사용하기 위한 훅

• 리액트 레퍼런스 – 특정 컴포넌트에 접근할 수 있는 객체

• useRef()는 레퍼런스 객체를 반환 •

레퍼런스 객체의 .current 속성 – 현재 참조하고 있는 엘리먼트

 

const refContainer = useRef(초기값);

 

• 초기값으로 초기화된 레퍼런스 반환

• 초기값이 null이면 .current 값이 null인 객체 반환 - { current: null }

 

실습. useRef( ) • 예시) input 태그의 포커싱 되도록 수행

import React, { useState, useRef } from "react";

// 🔹 TextInputWithFocusButton 컴포넌트
// 사용자가 버튼을 누르면 input에 자동으로 포커스를 주고, 값을 채워줌
export default function TextInputWithFocusButton(props) {
  // 📌 useRef 훅: DOM 요소(input)를 직접 참조할 수 있게 해줌
  // inputElem.current로 해당 input DOM에 접근 가능
  const inputElem = useRef(null);

  // 🔹 버튼 클릭 시 실행되는 함수
  const onButtonClick = () => {
    // 📌 focus(): 해당 input 박스로 커서를 자동으로 이동시킴
    inputElem.current.focus();

    // 📌 value 설정: input에 텍스트 값을 직접 넣음
    inputElem.current.value = "test1234";
  };

  return (
    <div>
      {/* 📌 ref 속성을 통해 input 요소를 inputElem에 연결 */}
      <input ref={inputElem} />

      {/* 🔘 버튼 클릭 시 onButtonClick 함수 실행 */}
      <button onClick={onButtonClick}>
        Focus the input
      </button>
    </div>
  );
}

 

* 버튼 클릭시 input 태그가 포커싱 된다.

개념설명

useRef() DOM 요소나 값을 기억할 수 있는 React 훅
ref={inputElem} 해당 input 요소를 inputElem.current로 직접 제어 가능하게 연결
focus() input에 커서(포커스)를 자동으로 이동시키는 메서드
value 직접 설정 input 값을 프로그래밍적으로 변경 가능

 

훅(Hook)의 규칙 정리

 규칙 1. 훅은 무조건 컴포넌트의 최상위 레벨에서만 호출해야 함

  • 반복문, 조건문, 중첩된 함수 내부에서 호출 ❌
  • 컴포넌트가 렌더링될 때마다 항상 같은 순서로 훅이 실행되어야 하기 때문

 규칙 2. 훅은 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출 가능

  • 일반 자바스크립트 함수에서 훅 사용 ❌
  • 훅은 오직 함수형 컴포넌트 또는 커스텀 훅 내부에서만 사용 가능

 커스텀 훅(Custom Hook) 개념 정리

  • 리액트에서 기본 제공 훅 외에 직접 만든 훅 함수
  • 여러 컴포넌트에서 반복되는 로직을 재사용하기 위해 사용
  • 이름은 반드시 use로 시작
  • 내부에서 useState, useEffect 등 다른 훅 사용 가능
  • 공통 로직을 분리하여 코드 가독성과 유지보수성 향상

커스텀 훅(Custom Hook) 정리

 커스텀 훅이란?

  • 중복된 로직을 함수로 분리한 것
  • 일반 함수처럼 파라미터와 리턴 값을 자유롭게 정할 수 있음
  • 하지만 기본 훅과 동일한 규칙을 따라야 함

 커스텀 훅의 규칙

  1. 이름은 반드시 use로 시작해야 함
  2. 컴포넌트의 최상위에서만 호출해야 함
  3. React 함수형 컴포넌트 또는 다른 훅 안에서만 사용 가능

 이름을 use로 시작하지 않으면?

  • React가 훅인지 인식 못 해서 규칙 위반 검사를 못함
  • 예기치 않은 오류 발생 가능

상태(state) 공유 여부

  • 여러 컴포넌트에서 같은 커스텀 훅을 사용해도
    상태는 각각 따로 관리됨 (독립적임)
  • 즉, 호출된 만큼 각각 개별 상태 생성

'React' 카테고리의 다른 글

Shared State  (0) 2025.05.13
리스트(List), 키(Key), 폼(Forms)  (1) 2025.05.11
컴포넌트와 Props  (1) 2025.04.02
리액트JSX  (0) 2025.03.25
소프트웨어 설계 3주차  (3) 2025.03.18

+ Recent posts