iOS/Toy project

[iOS : Toy Project] Stock Rank

yevdev 2022. 5. 21. 11:53

📌 세번째 프로젝트

주식 리스트 앱을 만들어보자

 

 

1️⃣ CollectionView

CollectionView의 Contraint

 

 

2️⃣ Auto Layout

1. 회사 로고 이미지 상단에 회사 이름 Label 맞추기

- label을 image view와의 관계에서 top을 설정

- label을 image view와의 관계에서 horizontal spacing을 설정 : image view와 label 사이의 거리 설정

2. 회사이름과 주가 사이의 관계

- Vertical spacing : 수직 거리

- Leading : 왼쪽 맞추기

3. 등락폭과 주가 사이의 관계

- Horizontal Spacing : 수평 거리

- Center vertical : 같은 높이를 유지

4. 하트 이미지뷰와 Content View 사이의 관계

- Trailing Space to Container : 오른쪽에 trailing

- Center Vertical in Container : 컨테이너의 수직에서 가운데에 있게

- width랑 height도 정해주기

 

 

3️⃣ Data 확인

- 패스트캠퍼스에서 제공한 데이터를 이용했다.

- StockModel.swift

import Foundation

struct StockModel {
    let rank: Int
    let imageName: String
    let name: String
    let price: Int
    let diff: Double
}

extension StockModel {
    static let list: [StockModel] = [
        StockModel(rank: 1, imageName: "TSLA", name: "테슬라", price: 1_238_631, diff: 0.04),
        StockModel(rank: 2, imageName: "AAPL", name: "애플", price: 238_631, diff: 1.04),
        StockModel(rank: 3, imageName: "NFLX", name: "넷플릭스", price: 438_631, diff: -0.04),
        StockModel(rank: 4, imageName: "GOOG", name: "알파벳 A", price: 3_176_631, diff: 0.04),
        StockModel(rank: 5, imageName: "AMZN", name: "아마존", price: 3_538_631, diff: 0.04),
        StockModel(rank: 6, imageName: "NIKE", name: "나이키", price: 158_631, diff: 0.04),
        StockModel(rank: 7, imageName: "DIS", name: "디즈니", price: 138_631, diff: 0.04),
        StockModel(rank: 8, imageName: "TSLA", name: "테슬라", price: 1_238_631, diff: 0.04),
        StockModel(rank: 9, imageName: "AAPL", name: "애플", price: 238_631, diff: 1.04),
        StockModel(rank: 10, imageName: "NFLX", name: "넷플릭스", price: 438_631, diff: -0.04),
        StockModel(rank: 11, imageName: "GOOG", name: "알파벳 A", price: 3_176_631, diff: 0.04),
        StockModel(rank: 12, imageName: "AMZN", name: "아마존", price: 3_538_631, diff: 0.04),
        StockModel(rank: 13, imageName: "NIKE", name: "나이키", price: 158_631, diff: 0.04),
        StockModel(rank: 14, imageName: "DIS", name: "디즈니", price: 138_631, diff: 0.04),
    ]
}

- 그리고 여러 이미지 set!

패캠에서 제공한 데이터

 

4️⃣ UICollectionViewCell 만들기

CollectionViewCell 파일 생성
Class 설정
Identifier도 설정

➰ 이 cell 자체는 재사용이 될 수 있기 때문에 구분자인 Identifier가 필요하다. 

 

 

현재까지 작성한 코드

import UIKit

class StockRankViewController: UIViewController {
    
    // Data 가져오기
    let stockList: [StockModel] = StockModel.list


    @IBOutlet weak var collectionView: UICollectionView!
    
    // CollectionView를 사용하기 위해 필요한 3가지!
    // Data, Presentation, Layout
    // Data - 어떤 데이터? -> stockList
    // Presentation - 셀을 어떻게 표현?
    // Layout - 셀을 어떻게 배치?
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Protocol : dataSource, delegate
        // dataSource : viewController가 collectionView에게 데이터가 어떻게 될 것인지 알려줌
        collectionView.dataSource = self
        // delegate : 레이아웃이나 셀의 사이즈 등이 어떻게 될 것인지 알려줌 (어떻게 배치할 것인지?)
        collectionView.delegate = self
        // self : 내가 알려줄거야~
    
    }
}

extension StockRankViewController: UICollectionViewDataSource {
    
    // 몇개의 데이터를 가져올 것인지?
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return stockList.count
    }
    
    // 셀을 어떻게 표현할 것인지?
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        // dequeueReusableCell : 재사용이 가능한 cell을 가져오겠다!
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath)
        return cell
    }
    
    
}

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    
}

구현중인 화면

 

 

5️⃣ Cell을 어떻게 배치할것인가?

위에서 본 구현 중인 화면은 컬럼이 6개로 나와있는데, 단일 컬럼으로 cell이 배치되도록 해야한다!

UICollectionViewDelegateFlowLayout의 메소드를 이용하면 된다.

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width == collectionView
        // height == 80
        
        // collectionView의 width 가져오기 == collectionView.bounds.width
        return CGSize(width: collectionView.bounds.width, height: 80)
    }
    
}

단일컬럼으로 바뀜

 

 

6️⃣ Cell의 Data를 업데이트!

 

StockRankCollectionViewCell.swift

import UIKit

class StockRankCollectionViewCell: UICollectionViewCell {
    
    // UIcomponent 연결
    @IBOutlet weak var rankLabel: UILabel!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var companyIconImageView: UIImageView!
    @IBOutlet weak var diffLabel: UILabel!
    @IBOutlet weak var companyPriceLabel: UILabel!
    
    // UIcomponent에 데이터를 업데이트하는 코드를 넣기
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        companyPriceLabel.text = "\(stock.price) 원"
        diffLabel.text = "\(stock.diff)%"
    }
}

 

StockRankViewController.swift

extension StockRankViewController: UICollectionViewDataSource {
    
    // 몇개의 데이터를 가져올 것인지?
    // == CollectionView에게 몇개의 cell이 필요하냐
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return stockList.count
    }
    
    // 각 셀을 어떻게 표현할 것인지?
    // == indexPath번째에 해당하는 item을 UICollectionViewCell 형태로 어떻게 표현할 거냐
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        // dequeueReusableCell : 재사용이 가능한 cell을 가져오겠다!
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath) as? StockRankCollectionViewCell else {
            return UICollectionViewCell()
        }
        // cell을 StockRankCollectionViewCell로 커스팅을 시켜놓기
        
        let stock = stockList[indexPath.item]
        // indexPath에는 section에 대한 정보 즉, section 안에 들어가는 각 데이터(Row)에 대한 정보들이 있다.
        // [indexPath.item] == item에 대한 정보가 몇번째에 있는지를 알려준다.
        
        cell.configure(stock)
        return cell
    }
    
    
}

 

위의 코드 자세히 살펴보기

guard ?

guard <<꼭 참이어야하는 조건>> else {
    return 참이 아닐때 실행
}

참일때 실행
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath) as? StockRankCollectionViewCell else {
            return UICollectionViewCell()
        }

-> 해석 : collectionView cell에서 "StockRankCollectionViewCell" 이라는 ID를 가진 재사용 cell을 가져오는데, StockRankCollectionViewCell로 캐스팅을 한다. 

캐스팅이 안되면 UICollectionViewCell()을 return

 

지금까지 구현한 화면

 

 

 

7️⃣ 12312313원 -> 가독성있게 읽히도록 바꿔보자

//
//  StockRankCollectionViewCell.swift
//  Stock Rank
//
//  Created by 오예진 on 2022/05/20.
//

import UIKit

class StockRankCollectionViewCell: UICollectionViewCell {
    
    // UIcomponent 연결
    @IBOutlet weak var rankLabel: UILabel!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var companyIconImageView: UIImageView!
    @IBOutlet weak var diffLabel: UILabel!
    @IBOutlet weak var companyPriceLabel: UILabel!
    
    // UIcomponent에 데이터를 업데이트하는 코드를 넣기
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        
        // 아래에 만든 convertToCurrencyFormat 써먹기
        companyPriceLabel.text = "\(convertToCurrencyFormat(price : stock.price)) 원"
        
        diffLabel.text = "\(stock.diff)%"
    }
    
    // 가격을 세자리씩 끊기
    func convertToCurrencyFormat(price: Int) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 0
        
        // nil인 경우에는 "" 빈 string을 넘겨주자 -> 실제 많이 사용되는 방식
        let result = numberFormatter.string(from: NSNumber(value: price)) ?? ""
        
        return result
    }
}

-> NumberFormatter()숫자값과 문자적 표현 사이를 변환해주는 formatter =  숫자의 Formatting을 도와주는 객체

3자리씩 , 으로 띄어짐

 

 

8️⃣ 등락폭이 -값 일때는 파란색으로 표시하기

 

StockRankCollectionViewCell.swift

// UIcomponent에 데이터를 업데이트하는 코드를 넣기
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        
        // 아래에 만든 convertToCurrencyFormat 써먹기
        companyPriceLabel.text = "\(convertToCurrencyFormat(price : stock.price)) 원"
        
        // 등락폭에 따라 text color 바꿔주기
        var color: UIColor
        if stock.diff > 0 {
            color = UIColor.systemRed
        } else {
            color = UIColor.systemBlue
        }
        diffLabel.textColor = color
        diffLabel.text = "\(stock.diff)%"
    }

 

삼항연산자로 코드 줄이기

// 등락폭에 따라 text color 바꿔주기
diffLabel.textColor = stock.diff > 0 ? UIColor.systemRed : UIColor.systemBlue

diffLabel.text = "\(stock.diff)%"

 

🚀구현완료!

 

 

 


 

📌 정리,

CollectionView를 이용할 때, 

1. Data : 어떤 데이터를 가져올건지?

2. Presentation : 셀을 어떻게 표현할 건지?

3. Layout : 셀을 어떻게 배치할 건지?

를 생각해야한다.

 

이번 프로젝트에서 

Data는 패캠에서 제공해준 assets와 StockModel.swift 데이터를 사용,

PresentationUICollectionViewDataSource 프로토콜을 준수하는 객체를 만들어 표현

LayoutUICollectionViewDelegateFlowLayout의 메소드를 이용하여 단일 컬럼으로 셀이 배치되게 하였다.

 

 

그리고 StockRankCollectionViewCell 파일을 만들어 UIComponent를 연결과 UIComponent에 데이터를 업데이트하는 코드를 작성했다.

+ 등락폭에 따라 글자색상 변경

+ 가격을 가독성있게 변경 : NumberFormatter() 객체 사용

 

 

 

 


Reference

패스트캠퍼스 온라인 강의