영화 예매 앱 만들기 - 3

maxminseok·2024년 12월 18일
1

예매 결과 확인 창

특이하게도, 영화 예매가 랜덤인 앱이다.

금액과 좌석이 모두 랜덤이다. 일종의 가챠인 셈이다.
처음엔 시간대나 날짜까지 랜덤으로 하자는 아이디어가 나왔다가 축소된 것이다..😅

오늘은 그 가챠 결과 화면을 출력하도록 View와 Controller를 작성하였다.


PaymentResultViewController 추가

import UIKit

// 좌석 번호랑 할인 가격 랜덤으로 들어가게 하기
/*
 좌석 번호 A~Z + 1~49
 최종 금액 확률 (%)
 30,000원 5%
 25,000원 10%
 20,000원 30%
 15,000원 50%
 10,000원 10%
 5,000원 5%
 */
class PaymentResultViewController: UIViewController, gachaButtonDelegate {
    
    let paymentResultView = PaymentResultView()
    
    var ticketNumber: Int = 1 // 티켓 번호
    
    override func loadView() {
        self.view = paymentResultView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        paymentResultView.delegate = self
    }
    
    // 좌석 번호 생성
    private func generateSeatCode() -> String {
        // 랜덤한 a~z 문자 생성
        let randomLetter = Character(UnicodeScalar(Int.random(in: 65...90))!)
        // 랜덤한 1~49 숫자 생성
        let randomNumber = Int.random(in: 1...49)
        // 대문자 알파벳과 숫자 합쳐서 반환
        return "\(randomLetter)\(randomNumber)"
    }
    
    // 당첨 금액 생성
    private func genratePrizeAmount() -> String {
        let randomValue = Int.random(in: 0..<100)
        let amount: Int
        
        switch randomValue {
        case 0..<5:    // 5%
            amount = 5000
        case 5..<15:   // 10%
            amount = 10000
        case 15..<65:  // 50%
            amount = 15000
        case 65..<95:  // 30%
            amount = 20000
        case 95..<100: // 5%
            amount = 30000
        default:
            amount = 0
        }
        
        // NumberFormatter를 사용해 금액 포맷 적용
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal // 천 단위 구분 기호 추가
        return formatter.string(from: NSNumber(value: amount)) ?? "0"
    }
    
    // '티켓 뽑기' 버튼 누를때마다 당첨금과 좌석 랜덤하게 변경
    func didTapGachaButton() {
        // 애니메이션 적용
        UIView.animate(withDuration: 0.3, animations: { [weak self] in
            guard let self = self else { return }
            // 1. 뷰를 화면 왼쪽으로 이동
            self.paymentResultView.transform = CGAffineTransform(translationX: -self.view.bounds.width, y: 0)
        }) { [weak self] _ in
            guard let self = self else { return }
            // 2. 새로운 값 설정
            let seat = self.generateSeatCode()
            let prize = self.genratePrizeAmount()
            self.ticketNumber += 1
            self.paymentResultView.setUI(self.ticketNumber, prize, seat)
            
            // 3. 뷰를 화면 오른쪽으로 이동
            self.paymentResultView.transform = CGAffineTransform(translationX: self.view.bounds.width, y: 0)
            
            // 4. 뷰를 원래 위치로 애니메이션
            UIView.animate(withDuration: 0.3) {
                self.paymentResultView.transform = .identity
            }
        }
    }
    
}
  • generateSeatCode : 랜덤한 좌석 번호 생성하는 메서드
  • genratePrizeAmount : 랜덤한 당첨금 생성하는 메서드
  • didTapGachaButton : 뷰에 있는 뽑기 버튼을 누를 때마다 호출되는 메서드

PaymentResultView

import UIKit
import SnapKit

protocol gachaButtonDelegate: AnyObject {
    func didTapGachaButton()
}

class PaymentResultView: UIView {
    
    private let height =  UIScreen.main.bounds.height // 뷰의 세로 길이, 16pro 기준 874.0
    
    weak var delegate: gachaButtonDelegate?
    
    // 티켓 번호 레이블
    private let ticketNumberLabel: UILabel = {
        let label = UILabel()
        label.text = "티켓 1 당첨 결과"
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 22)
        return label
    }()
    
    // 당첨 금액 레이블
    private let amountLabel: UILabel = {
        let label = UILabel()
        label.text = "🎉 축하드립니다! 30,000원 당첨!"
        label.textAlignment = .center
        label.font = .boldSystemFont(ofSize: 24)
        return label
    }()
    
    // 좌석 번호 레이블
    private let seatLabel: UILabel = {
        let label = UILabel()
        label.text = "영등포 럭비관 1관 6층 (아이맥스) C9"
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 20)
        return label
    }()
    
    // 안내 문구 레이블
    private let commentLabel: UILabel = {
        let label = UILabel()
        label.text = "차액은 바로 환불되오니 즐거운 관람 되세요! 🍿✨"
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 20)
        label.numberOfLines = 0 // 레이블 줄 수 무제한으로 설정
        label.lineBreakMode = .byWordWrapping   // 줄바꿈 모드 설정
        return label
    }()
    
    // 뽑기 버튼
    private let gachaButton: UIButton = {
        let button = UIButton()
        button.setTitle("티켓 뽑기", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.setTitleColor(.gray, for: .highlighted)
        button.backgroundColor = .systemGreen
        button.layer.cornerRadius = 16
        button.addTarget(self, action: #selector(gachaButtonTapped), for: .touchUpInside)
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    // UI 셋업 메서드
    private func setupUI() {
        [
            ticketNumberLabel,
            amountLabel,
            seatLabel,
            commentLabel,
            gachaButton
        ].forEach { addSubview($0) }
        
        ticketNumberLabel.snp.makeConstraints{
            $0.top.equalToSuperview().offset(height / 3) // 전체 높이의 1/3 지점
            $0.centerX.equalToSuperview()
        }
        
        amountLabel.snp.makeConstraints{
            $0.top.equalTo(ticketNumberLabel.snp.bottom).offset(20)
            $0.leading.trailing.equalToSuperview().inset(32)
        }
        
        seatLabel.snp.makeConstraints{
            $0.top.equalTo(amountLabel.snp.bottom).offset(16)
            $0.leading.trailing.equalToSuperview().inset(32)
        }
        
        commentLabel.snp.makeConstraints{
            $0.top.equalTo(seatLabel.snp.bottom).offset(16)
            $0.leading.trailing.equalToSuperview().inset(32)
        }
        
        gachaButton.snp.makeConstraints{
            $0.bottom.equalTo(safeAreaLayoutGuide.snp.bottom)
            $0.leading.trailing.equalToSuperview().inset(32)
            $0.height.equalTo(50)
        }
    }
    
    // UI 값 접근 메서드
    func setUI(_ ticketNumer: Int, _ amount: String, _ seat: String) {
        ticketNumberLabel.text = "티켓 \(ticketNumer) 당첨 결과"
        amountLabel.text = "🎉 축하드립니다! \(amount)원 당첨!"
        seatLabel.text = "영등포 럭비관 1관 6층 (아이맥스) \(seat)"
    }
    
    @objc func gachaButtonTapped() {
        delegate?.didTapGachaButton()
    }
    
}
  • 버튼 동작 처리를 위해 gachaButtonDelegate 프로토콜을 생성
    • didTapGachaButton 메서드를 여기서 정의
  • 뽑기 버튼 gachaButton의 addTarget을 추가.
  • 버튼이 눌릴 때 호출되는 gachaButtonTapped 메서드를 작성

결과

이제 다른 팀원이 결제 화면에서 티켓 매수와 결제 동작에 대해 작성하고나면 이 결과 화면을 연결해 약간의 수정을 거쳐 활용하게 될 것이다.


느낀 점

issues라는 것도 처음 사용 해봤는데 각자 개인 feature 브랜치 만들어서 코딩하고 merge 하는 것 보다 더 역할 분담이 깔끔하고,
마치 Todo 리스트처럼 얼마나 진행되었는지 살펴보기도 편한 것 같다.

issues만 만들어서 작업하다가,

projects탭에 이런 칸반보드 같은 기능이 있다는 것도 알게 되었다.

단순히 issues만 만들어 작업할 때보다 한눈에 작업 상황을 확인하기 쉽고, 프로젝트에 참여하고 있지 않은 외부 사람들과 작업 상황에 대해 소통하기도 더 수월해졌다.

또, 팀원들이 각자 역할을 나눠 하나씩 구현해나가다 보니 어느새 구현이 완성되어 가는게 보여서 재밌다고 느꼈다.

저번 프로젝트 때는, '내가 맡은 기능을 구현하는 건 하는 건데 다른 팀원이 한 거랑 어떻게 합치지?' 라는 생각에 걱정이 많았고, 합치는 과정에서도 스트레스가 조금 있었는데, 이번에는 그 과정을 즐기게 된 것 같다.

0개의 댓글