본문 바로가기
실무 (iOS)

iOS 프로그래밍 실무 11주차 [ Open API 기반 앱 개발 2]

by DeathStar183 2025. 5. 17.

이번 주차에 보고 넘어갈것들

 

- 수집한 데이터 정리

- 수집한 데이터중 원하는 데이터만 가져오기

- 원하는 데이터를 화면에 출력하기

- 스택 뷰 관련

- 화면 제작

 

- 전 주차에서 추가 설명없이 이어서 하기에 전주차를 보고 오시는게 좋습니다.

- 이번 주차역시 사진이 많아 데이터로 볼경우 스압주의!

 

 

 

 

1. 파싱용 구조체 제작 이후 뽑을 데이터만 넣기

 

 

 

1-1. 파일 불러오기

 

 

 

1-2.  JsonData를 MovieData로 디코딩 했지만 에러

 

 

 

1-3. 에러 핸들링

 

 

 

 

1-4. 에러 핸들링 후 1등 영화 이름과 관람객 수 뽑아보기

 

 

 

1-4. 프로퍼티에 decoderData 저장

 

 

클로저는 독립적인 코드 블록이다보니, 클로저 내에서 프로퍼티 접근시에는 self를 붙여야 오류가 안뜹니다.

 

 

 

1-5-1. 실행했는데 화면이 왜이러지?

 

몇번 대충 스크롤질 하다보면 다시 원래대로 나옵니다.

 

 

 

1-5-2. 문제 파악후 수정

 

 

 

1-6-1. 수정했더니 생전 처음보는 오류와 함께 크래시

좀전에도 말했듯 클로저는 독립적인 코드의 집합입니다.

하지만 독립 적인 코드 블록, 즉 메인 프로그램에 영향을 줄수 있는 코드를 메인 쓰레드가 아닌,

클로저 내에서 (메인 쓰레드 바깥에서) 처리해 생기는 문제입니다.

 

UITableView.reloadData() must be used from main thread only.

번역 : UITableView.reloadData()는 반드시 메인 쓰레드 내에서만 실행(사용)되어야 합니다.

 

AI도 말하는게 조금 달라서 그렇지 대략적인 설명은 같습니다.

 

 

 

 

크래시가 나면 첫번째 처럼 보라색 경고가 뜬뒤, 조금 있다가 두번째 사진으로 자동으로 넘어갑니다.

 

 

 

1-6-2. UI관련은 메인 쓰레드에서만

 

관련 오류 해결후 이번엔 관람객 수만 뽑아낸 결과

 

 

 

1-7. 날짜가 자동으로 갱신되게 만들기

 

 

가끔 다 수정하고도 간혹 왼쪽 사진처럼 뜰때가 있는데, 무시하고 스크롤하면 됩니다.

 

 

 

2. 뷰 만들고 수정하기

 

 

 

2-1. 스택 뷰

 

 

 

2-2. 스택 뷰의 4가지 제약조건

 

 

 

2-3. 스택 뷰 설정값

 

 

 

2-4. 제약조건이 걸린 디자인을 수정시에는 제약조건을 반드시 삭제

 

 

2-5. 제약조건 보다 더 좋은 스택뷰로 대신하기

 

 

 

2-6. 스택 뷰 활용

 

 

 

 

2-7. 텍스트 수정

 

스택뷰로 강력하게 잡혀져 있기 때문에 화면을 돌리더라도, 비율이 자동적으로 맞춰져서 수정되어 표시됩니다.

 

 

 

3. 하위 클래스 수정하기

 

 

 

3-1. 이름에 맞게 수정

간혹, movieData에 왜 ?를 붙일까 할수도 있지만, 연결이 잘안되었거나,

데이터를 못가져왔거나 등의 문제를 막아주기 위해서 존재합니다.

(못가져오더라도 일단은 실행하고, 추후 못불러왔다는 내용의 추가를 하면 되기 때문)

 

 

 

3-2. 등수 표시와 세세한 표시

(마지막 전체 본문 코드에 기록되어 있습니다.)

 

 

 

3-3. 글 넣기

 

 

 

3-4. 제목이 너무 길어서 ...로 표시될때

2가 아닌 0인 경우 줄이 끝없이 (제한없이) 늘어나서 통일감이 깨지기에 비추천합니다.

 

 

 

- - - 모든것이 작성된 전체 본문 코드 - - -

import UIKit

let name = ["1등","2등","3등","4등","5등"]

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView!
    
    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
    }
    
    var movieData : MovieData?
    
    var movieURL = "자신의 키를 넣으세요"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        table.delegate = self
        table.dataSource = self
        
        movieURL += makeYesterdayString()
        getData()
    }
    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 getData() {
        guard let url = URL(string: movieURL) else { return }
        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)
            let decoder = JSONDecoder()
            do {
            let decodedData =
            try decoder.decode(MovieData.self, from: JSONdata)
            self.movieData = decodedData
            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
            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 SSI"
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    { print(indexPath.description) }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
}

댓글