스파르타 코딩 클럽 - iOS 스타터 6기/본 캠프

61. 스파르타 코딩 클럽 - 최종 팀 프로젝트 #3

seongpil Heo 2025. 6. 10. 23:36

  🎯  Trouble Shooting

[ 1. 보여주는 이미지를 3개에서 1개로 변경 ]

 

이유 : 가게 정보에 관한 이미지를 보여주기 위해서는 Google Places API 통신을 해야 한다.

그러나 우리가 필요한 사진의 수는 한 가게당 최소 3개 이상

Google Places는 한 번의 API 통신에 한 개의 이미지만 제공한다.

따라서 가게 정보와 이미지 3개를 받아오기 위해서는 총 4번의 API 통신이 필요하다.

한정된 지원금 안에서 유료 API를 사용하다 보니 부득이하게 이미지를 1개만 사용하는 방향으로 수정하게 되었다.

 

[ 해당 issue 관련하여 API 문서 ]

 

장소 사진 (신규)  |  Places SDK for iOS  |  Google for Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 의견 보내기 장소 사진 (신규) 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 플랫폼 선택: Android iOS

developers.google.com

 

 

[ 2. 이미지의 비율 정하기 ]

단방향 AspectFill
단방향 AspectFill
정방향 AspectFit
정방향 AspectFit

 

여러 번의 테스트를 해본 결과 우리 팀은 1:1 비율의 AspectFill 방법을 사용하기로 했다.

[ 우리 팀의 결정 ]

 

 

[ 3. PopUp Button의 사춘기 ]

좌 : 피그마 디자인 상 원하는 위치 / 우 : 피그마 디자인대로 구현 시 지정된 위치

 

iPhone 13 mini 기준

전체 높이 812px

SafeArea (Top) 44px

SafeArea (Bottom) 34px

812 - 44 - 34 = 734 / 2 = 367

812 / 2 = 406 - 44 = 362

 

SafeArea 기준으로 346 정도 offset하니 PopUp Button이 아래로 위치하게 되었다.


👨🏻‍💻  오늘의 작업 - UI 구현

[ 1. TableView 만들기 ]

[ 코드 ]

private let tableView: UITableView = {
        let tableView = UITableView()
        tableView.register(DetailTableViewCell.self, forCellReuseIdentifier: DetailTableViewCell.identifier)
        return tableView
    }()
    
    
 private func configureUI() {
    view.backgroundColor = .white

    tableView.delegate = self
    tableView.dataSource = self
    
    [storeCountLabel, sortButton, tableView].forEach {
        view.addSubview($0)
    }
        
    tableView.snp.makeConstraints { make in
        make.top.equalTo(storeCountLabel.snp.bottom).offset(8)
        make.leading.trailing.equalToSuperview()
        make.bottom.equalToSuperview()
    }

}

extension DetailViewController: UITableViewDataSource, UITableViewDelegate {
    // 테이블 뷰 섹션 수 지정
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    // 테이블 뷰 셀들의 갯수
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return testArray.count
    }
    
    // 테이블 뷰 셀 지정
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: DetailTableViewCell.identifier, for: indexPath) as? DetailTableViewCell else { return UITableViewCell() }
        
        cell.configureView()
        return cell
    }
    
    // 테이블 뷰의 높이 지정
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 145
    }
    
    
}

 

[ 2. TableView Cell 만들기 ]

[ 코드 ]

import UIKit
import SnapKit

class DetailTableViewCell: UITableViewCell {

    static let identifier = "CustomTableViewCell"
    
    private let storeNameLabel: UILabel = {
        let label = UILabel()
        label.text = "식당명"
        label.textColor = .black
        label.font = .customFontForHeader(weight: .w800)
        return label
    }()
    
    private let rateImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(named: "Star")
        return imageView
    }()
    
    private let rateLabel: UILabel = {
        let label = UILabel()
        label.text = "평점"
        label.textColor = .black
        label.font = .customFontForBody(weight: .w600)
        return label
    }()
    
    private let userRateCountLabel: UILabel = {
        let label = UILabel()
        label.text = "(리뷰수)"
        label.textColor = .black
        label.font = .customFontForBody(weight: .w600)
        return label
    }()
    
    private let storeTypeLabel: UILabel = {
        let label = UILabel()
        label.text = "식당 종류"
        label.textColor = .black
        label.font = .customFontForBody(weight: .w500)
        return label
    }()
    
    private let addressLabel: UILabel = {
        let label = UILabel()
        label.text = "주소"
        label.textColor = .black
        label.font = .customFontForBody(weight: .w500)
        return label
    }()
    
    private let timeImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(named: "Time")
        return imageView
    }()
    
    private let openNowLabel: UILabel = {
        let label = UILabel()
        label.text = "영업전/후 • 시간"
        label.textColor = .black
        label.font = .customFontForBody(weight: .w500)
        return label
    }()
    
    private let storeImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(named: "Rectangle")
        return imageView
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureView() {
        
        [storeNameLabel, rateImageView, rateLabel, userRateCountLabel, storeTypeLabel, addressLabel, timeImageView, openNowLabel, storeImageView].forEach {
            contentView.addSubview($0)
        }
        
        storeNameLabel.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(20)
            make.top.equalToSuperview().offset(26)
            make.width.equalTo(242)
            make.height.equalTo(25)
        }
        
        rateImageView.snp.makeConstraints { make in
            make.width.height.equalTo(14)
            make.leading.equalToSuperview().offset(20)
            make.top.equalTo(storeNameLabel.snp.bottom).offset(7)
        }
        
        rateLabel.snp.makeConstraints { make in
            make.height.equalTo(20)
            make.width.equalTo(25)
            make.leading.equalTo(rateImageView.snp.trailing).offset(4)
            make.centerY.equalTo(rateImageView)
        }
        
        userRateCountLabel.snp.makeConstraints { make in
            make.leading.equalTo(rateLabel.snp.trailing).offset(4)
            make.centerY.equalTo(rateImageView)
            make.height.equalTo(20)
            make.width.equalTo(48)
        }
        
        storeTypeLabel.snp.makeConstraints { make in
            make.leading.equalTo(userRateCountLabel.snp.trailing).offset(4)
            make.centerY.equalTo(rateImageView)
            make.height.equalTo(18)
            make.width.equalTo(143)
        }
        
        addressLabel.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(20)
            make.top.equalTo(rateLabel.snp.bottom).offset(4)
            make.height.equalTo(18)
            make.width.equalTo(242)
        }
        
        timeImageView.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(20)
            make.width.height.equalTo(16)
            make.top.equalTo(addressLabel.snp.bottom).offset(5)
        }
        
        openNowLabel.snp.makeConstraints { make in
            make.width.equalTo(222)
            make.height.equalTo(18)
            make.leading.equalTo(timeImageView.snp.trailing).offset(4)
            make.centerY.equalTo(timeImageView)
        }
        
        storeImageView.snp.makeConstraints { make in
            make.height.width.equalTo(93)
            make.leading.equalTo(storeNameLabel.snp.trailing)
            make.top.equalToSuperview().offset(26)
        }
    }
}

 

[ 3. keynote Data Flow 작성 ]

[ 4. PopUp Button UI 구현 ]

[ 코드 ]

private let sortButton: UIButton = {
    var config = UIButton.Configuration.plain()

    let font = UIFont.customFontForSubtitle(weight: .w600)
    let title = "별점순"
    let attributedTitle = AttributedString(title, attributes: AttributeContainer([.font: font]))
    config.attributedTitle = attributedTitle

    config.image = UIImage(named: "ChevronDown")
    config.imagePadding = 2     // 텍스트와 이미지 사이 간격
    config.imagePlacement = .trailing // 텍스트 오른쪽에 이미지

    let button = UIButton(configuration: config, primaryAction: nil)
        button.tintColor = .black

    let updateTitle: (String) -> Void = { newTitle in
            var updatedConfig = button.configuration
            let attributedTitle = AttributedString(newTitle, attributes: AttributeContainer([.font: font]))
            updatedConfig?.attributedTitle = attributedTitle
            button.configuration = updatedConfig
        }

    let menuItems = [
            UIAction(title: "별점순", handler: { _ in updateTitle("별점순") }),
            UIAction(title: "거리순", handler: { _ in updateTitle("거리순") }),
            UIAction(title: "리뷰순", handler: { _ in updateTitle("리뷰순") }),
            UIAction(title: "가격순", handler: { _ in updateTitle("가격순") })
        ]

    button.menu = UIMenu(title: "", options: .displayInline , children: menuItems)
    button.showsMenuAsPrimaryAction = true
    button.contentHorizontalAlignment = .leading
    return button
}()