Repository: SOPT-all/36-COLLABORATION-iOS-UBER
Pull request #16: [Feat] 예약 정보 화면 UI 구현
Author: login: kwon-seokki
Labels: name: 🧃석기🧃
name: feat
Created: 2025-05-15T07:18:05Z | Merged: 2025-05-15T15:59:59Z

## 📌 𝗜𝘀𝘀𝘂𝗲𝘀
- closed #12 

## 🚖 What’s this PR about?
<!-- 이번 PR에서 구현한 기능의 목적, 주요 흐름을 간략히 설명해주세요. -->
- 예약 정보 화면 UI 구현

## 🧾 Summary of Changes
<!-- 작업 내용을 간결한 리스트로 정리해주세요. 커밋 메시지 요약처럼 써도 좋아요. -->
- 예약정보 화면 UI 구현
- 예약 화면 공통 컴포넌트 SectionView Component 추가했습니다
- 문자열 관련 extension을 추가했습니다.
    - appendImage: 문자 뒤에 이미지 추가
    - preprentImage: 문자앞에 이미지 추가
    - replaceFont(pattern: String, replaceFont: UIFont): 특정 pattern 문자 font 변경
- UILabel extension 
    - setTextWithLineHeight: UILabel에 LineHeight 적용

## 🤔 Considerations
<!-- 구현하면서 고려한 점, 고민한 부분, 해결한 이슈 등을 작성해주세요. -->
### 반복되는 섹션을 컴포넌트로 분리
아래처럼 Title, SubTitle, Content 가 반복됩니다.
<img width="318" alt="스크린샷 2025-05-15 오후 3 24 45" src="https://github.com/user-attachments/assets/4b8287fc-9fdd-4104-9cf3-5163c87fd730" />
전역으로 사용하는 컴포넌트는 아니지만 예약 정보, 차량 선택 화면에서 생각보다 많이 반복되고 일일히 작성하기에는 코드가 길어질듯하여 컴포넌트로 분리하기로 했습니다. 

**Properties**
초기화할때 필요한 파라미터 입니다. convenience init을 활용해서 이니셜라이져를 상황별로 나누어 봤어요
```swift
 init(title: String, subtitle: NSMutableAttributedString?, content: UIView, contentEdge: UIEdgeInsets) {
        super.init(frame: .zero)
        setupView(title: title, subtitle: subtitle, content: content, contentEdge: contentEdge)
    }
    
    convenience init(title: String, subtitle: NSMutableAttributedString?, content: UIView) {
        self.init(title: title, subtitle: subtitle, content: content, contentEdge: UIEdgeInsets())
    }
    
    convenience init(title: String, content: UIView) {
        self.init(title: title, subtitle: nil, content: content, contentEdge: UIEdgeInsets())
    }
    
    convenience init(title: String, content: UIView, contentEdge: UIEdgeInsets) {
        self.init(title: title, subtitle: nil, content: content, contentEdge: contentEdge)
    }
```
title(required): 섹션에 나타나는 제목
subtitle(optional): 제목 아래에 나타나는 부제목
content(required): 섹션에 표시할 뷰
contentEdge(optional): 컨텐츠에 적용할 padding

**SectionView Component**

<img width="886" alt="스크린샷 2025-05-15 오후 3 45 32" src="https://github.com/user-attachments/assets/343dbbda-aa8c-4fe8-85bb-09a5484870c8" />

많은 부분을 스택뷰로 구성해서 넘겨주는 View의 레이아웃만 잘 잡혀있다면 스택뷰가 알아서 계산해서 레이아웃을 잡아줍니다. 반대로 레이아웃이 잘 잡혀있지 않다면 스택뷰가 오토레이아웃으로 계산하지 못해서 레이아웃이 깨질 수 있습니다.

ContentView: 전체를 감싸는 컨테이너 뷰
TopContainerView: Title, SubTitle이 들어가는 뷰
BottomContainer: 컨텐츠를 넘겨주면 BottomContiner에 추가해서 보여줌

**TopContainer를 StackView로 구성한 이유**
아래와같이 가로정렬이 필요한 경우를 대비해서 StackView로 구성했습니다.

<img width="562" alt="스크린샷 2025-05-15 오후 3 57 52" src="https://github.com/user-attachments/assets/fff624e5-08e9-47ec-a501-ea7173b4de2c" />

사용법
```swift
 private lazy var startAndArriveSection = SectionView(
        title: "출발/도착",
        content: imageView,
        contentEdge: .init(top: 10, left: 15, bottom: 10, right: 15)
    )
```

### LineHieght 설정
피그마에서 Label의 높이가 100%로 되어있지 않아서 별도로 LineHeight 처리가 필요했습니다. 섹션이 여러개 쌓이다보니 차이가 더 명확하게 보여서 작업하는김에 extension으로 만들어 두었습니다.

UILabel을 확장해서 사용합니다
```swift
 func setTextWithLineHeight(text: String?, lineHeight: CGFloat) {
        if let text = text {
            let style = NSMutableParagraphStyle()
            style.maximumLineHeight = lineHeight
            style.minimumLineHeight = lineHeight
            
            let range = NSRange(location: 0, length: text.count)
            
            let attributes: [NSAttributedString.Key: Any] = [
                .paragraphStyle: style,
                .baselineOffset: (lineHeight - font.lineHeight) / 2
            ]          
            
            var mutableAttrString = NSMutableAttributedString(string: text)
            
            // 기존에 AttributedString이 존재한다면 덮어쓰기
            if let attrString = self.attributedText {
                mutableAttrString = NSMutableAttributedString(attributedString: attrString)
            }
            
            mutableAttrString.addAttributes(attributes, range: range)

            self.attributedText = mutableAttrString
        }
    }
```

사용법
```swift
 $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
```
## 📱 Screenshot (or Demo)
| 화면 종류 | iPhone SE | iPhone 16 Pro |
|----------|-----------|----------------|
| GIF | <img src="https://github.com/user-attachments/assets/a72864df-4315-4697-aada-866cb39abb37" width="250"> | <img src="https://github.com/user-attachments/assets/31615b40-7831-4e7c-9cd2-5aa77b4cac84" width="250"> |




## 👀 To Reviewers
<!-- 코드 리뷰어에게 특별히 봐줬으면 하는 부분, 질문하고 싶은 점 등을 적어주세요. -->
- 우선 pr을 적절하게 나누지 못한점 죄송합니다. 여러가지 처음으로 시도해본 부분도 있어서 조금 정신없이 한 것 같아요 다음부터는 태스크를 잘 나눠보겠습니다.
- 이외에 Text를 다루는 extension도 많이 추가가 되었는데 다 설명하기에는 길어질듯해서 뺏습니다 개인적으로 궁금하시다면 코멘트 달아주세요
- 개인적으로 시도해본 부분이 많아서 미숙할 수 있습니다 기탄없는 피드백 부탁드리겠습니다!


Linked issue #12: [Feat] 예약 정보 UI 작업
## 📌𝗧𝗮𝘀𝗸
- [ ] 예약 정보 UI 작업(Reference 참고)

## 💡𝗥𝗲𝗳𝗲𝗿𝗲𝗻𝗰𝗲
<img width="357" alt="Image" src="https://github.com/user-attachments/assets/e1f4c198-07d9-4e2a-9560-a1daf759e747" />

Diff:
diff --git a/Uber-iOS/Uber-iOS.xcodeproj/project.pbxproj b/Uber-iOS/Uber-iOS.xcodeproj/project.pbxproj
index 5e53baf..5c7ffd5 100644
--- a/Uber-iOS/Uber-iOS.xcodeproj/project.pbxproj
+++ b/Uber-iOS/Uber-iOS.xcodeproj/project.pbxproj
@@ -21,7 +21,10 @@
 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
 			membershipExceptions = (
 				Base/BaseViewController.swift,
+				Config/Secret.swift,
+				"Extension/NSMutableAttributedString+.swift",
 				"Extension/NSObject+.swift",
+				"Extension/String+.swift",
 				"Extension/UIButton+.swift",
 				"Extension/UILabel+.swift",
 				"Extension/UIStackView+.swift",
@@ -191,7 +194,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = 6B47X24XTW;
+				DEVELOPMENT_TEAM = X58MVPBY9R;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = "Uber-iOS/Global/Setting/Info.plist";
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -223,7 +226,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = 6B47X24XTW;
+				DEVELOPMENT_TEAM = X58MVPBY9R;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = "Uber-iOS/Global/Setting/Info.plist";
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
diff --git a/Uber-iOS/Uber-iOS/Global/Extension/NSMutableAttributedString+.swift b/Uber-iOS/Uber-iOS/Global/Extension/NSMutableAttributedString+.swift
new file mode 100644
index 0000000..41629a0
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Extension/NSMutableAttributedString+.swift
@@ -0,0 +1,36 @@
+//
+//  NSMutableAttributedString+.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/15/25.
+//
+
+import UIKit
+
+extension NSMutableAttributedString {
+    func appendImage(image: UIImage) -> NSMutableAttributedString {
+        let imageAttachment = NSTextAttachment()
+        imageAttachment.image = image
+        self.append(NSAttributedString(attachment: imageAttachment))
+        return self
+    }
+    
+    func prependImage(image: UIImage, imageSize: CGSize = CGSize(width: 16, height: 16)) -> NSMutableAttributedString {
+        let imageAttachment = NSTextAttachment()
+        imageAttachment.image = image
+        imageAttachment.bounds = CGRect(
+            x: 0,
+            y: -2,
+            width: imageSize.width,
+            height: imageSize.height
+        )
+        
+        let imageString = NSAttributedString(attachment: imageAttachment)
+        let result = NSMutableAttributedString(attributedString: imageString)
+        
+        result.append(NSAttributedString(string: " "))
+        result.append(self)
+        
+        return result
+    }
+}
diff --git a/Uber-iOS/Uber-iOS/Global/Extension/String+.swift b/Uber-iOS/Uber-iOS/Global/Extension/String+.swift
new file mode 100644
index 0000000..e3ca1c7
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Extension/String+.swift
@@ -0,0 +1,29 @@
+//
+//  String+.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+extension String {
+    func replaceFont(pattern: String, replaceFont: UIFont) -> NSMutableAttributedString {
+        let attributes: [NSAttributedString.Key: Any] = [.font: replaceFont, .foregroundColor: UIColor.primary]
+        let attributedString = NSMutableAttributedString(string: self)
+        do {
+            let regex = try NSRegularExpression(pattern: pattern)
+            let length = self.count
+            let range = NSRange(location: 0, length: length)
+            let matches = (regex.matches(in: self, options: [], range: range))
+            matches.forEach {
+                attributedString.addAttributes(attributes, range: $0.range)
+            }
+        } catch {
+            
+        }
+        return attributedString
+    }
+}
+
+
diff --git a/Uber-iOS/Uber-iOS/Global/Extension/UILabel+.swift b/Uber-iOS/Uber-iOS/Global/Extension/UILabel+.swift
index 67d8629..666232d 100644
--- a/Uber-iOS/Uber-iOS/Global/Extension/UILabel+.swift
+++ b/Uber-iOS/Uber-iOS/Global/Extension/UILabel+.swift
@@ -8,14 +8,14 @@
 import UIKit
 
 extension UILabel {
-
+    
     func setLabel(
         text: String? = "",
         alignment: NSTextAlignment = .center,
         numberOfLines: Int = 0,
         textColor: UIColor,
         font: UIFont,
-        backgroundColor: UIColor? = .clear,
+        backgroundColor: UIColor? = .clear
     ) {
         self.text = text
         self.textAlignment = alignment
@@ -24,5 +24,30 @@ extension UILabel {
         self.font = font
         self.backgroundColor = backgroundColor
     }
+    
+    func setTextWithLineHeight(text: String?, lineHeight: CGFloat) {
+        if let text = text {
+            let style = NSMutableParagraphStyle()
+            style.maximumLineHeight = lineHeight
+            style.minimumLineHeight = lineHeight
+            
+            let range = NSRange(location: 0, length: text.count)
+            
+            let attributes: [NSAttributedString.Key: Any] = [
+                .paragraphStyle: style,
+                .baselineOffset: (lineHeight - font.lineHeight) / 2
+            ]          
+            
+            var mutableAttrString = NSMutableAttributedString(string: text)
+            
+            // 기존에 AttributedString이 존재한다면 덮어쓰기
+            if let attrString = self.attributedText {
+                mutableAttrString = NSMutableAttributedString(attributedString: attrString)
+            }
+            
+            mutableAttrString.addAttributes(attributes, range: range)
 
+            self.attributedText = mutableAttrString
+        }
+    }
 }
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/Contents.json b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/Contents.json
new file mode 100644
index 0000000..7c58196
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "image 1295.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "image 1295@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "image 1295@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295.png
new file mode 100644
index 0000000..0a863d7
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@2x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@2x.png
new file mode 100644
index 0000000..015fe81
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@2x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@3x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@3x.png
new file mode 100644
index 0000000..dbb4dbb
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/cardImage.imageset/image 1295@3x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/Contents.json b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/Contents.json
new file mode 100644
index 0000000..01a4310
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "promotionImage.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_18px@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "icon_18px@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@2x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@2x.png
new file mode 100644
index 0000000..69465b3
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@2x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@3x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@3x.png
new file mode 100644
index 0000000..8811bd9
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/icon_18px@3x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/promotionImage.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/promotionImage.png
new file mode 100644
index 0000000..dd04e94
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/promotionImage.imageset/promotionImage.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/Contents.json b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/Contents.json
new file mode 100644
index 0000000..5da85ee
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "rightIcon.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "ic24@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "ic24@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@2x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@2x.png
new file mode 100644
index 0000000..7657f3b
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@2x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@3x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@3x.png
new file mode 100644
index 0000000..3dc5dba
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/ic24@3x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/rightIcon.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/rightIcon.png
new file mode 100644
index 0000000..8f09a05
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/rightIcon.imageset/rightIcon.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/Contents.json b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/Contents.json
new file mode 100644
index 0000000..6985262
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "routeImage.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "routeImage@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "routeImage@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage.png
new file mode 100644
index 0000000..d1d38ec
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@2x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@2x.png
new file mode 100644
index 0000000..3298ab3
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@2x.png differ
diff --git a/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@3x.png b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@3x.png
new file mode 100644
index 0000000..e5fcac7
Binary files /dev/null and b/Uber-iOS/Uber-iOS/Global/Resources/Resources/Assets.xcassets/routeImage.imageset/routeImage@3x.png differ
diff --git a/Uber-iOS/Uber-iOS/Presentation/Reservation/ReservationInfoViewController.swift b/Uber-iOS/Uber-iOS/Presentation/Reservation/ReservationInfoViewController.swift
new file mode 100644
index 0000000..6a05894
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Presentation/Reservation/ReservationInfoViewController.swift
@@ -0,0 +1,229 @@
+//
+//  ReservationInfoViewController.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+import SnapKit
+
+final class ReservationInfoViewController: UIViewController {
+    
+    // MARK: - Properties
+    
+    // Content
+    
+    private let imageView = UIImageView().then {
+        $0.image = UIImage(resource: .route)
+    }
+    
+    private let pickupTimeLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "05월 05일 (월) / 오전 20:16".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "오전 20:31".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveDetailLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.text = "예상 운행 시간 약 25분 소요 예상"
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 18)
+    }
+    
+    private lazy var expectedArriveStack = UIStackView().then {
+        $0.axis = .vertical
+        $0.alignment = .leading
+        $0.addArrangedSubviews(expectedArriveLabel, expectedArriveDetailLabel)
+    }
+    
+    private lazy var vehicleSelectionButton = UIButton().then {
+        $0.setTitle("차량 선택하기", for: .normal)
+        $0.setTitleColor(.primary, for: .normal)
+        $0.titleLabel?.font = .body2_sb16
+        $0.layer.borderWidth = 1
+        $0.layer.cornerRadius = 12
+        $0.layer.borderColor = UIColor.black.cgColor
+        $0.snp.makeConstraints {
+            $0.height.equalTo(56)
+        }
+        $0.addTarget(self, action: #selector(vehicleSelectionButtonTapped), for: .touchUpInside)
+    }
+    
+    private let expectedPaymentLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "₩ 15,000 - 19,000".replaceFont(pattern: "[0-9]", replaceFont: .body2_sb16)
+    }
+    
+    private lazy var directPaymentView = UIStackView().then {
+        $0.axis = .horizontal
+        $0.alignment = .center
+        $0.distribution = .fill
+        $0.spacing = 4
+        
+        let cardImageView = UIImageView(image: UIImage(resource: .card)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.equalTo(36) }
+        }
+        
+        let paymentLabel = UILabel().then {
+            $0.text = "직접 결제"
+            $0.font = .body2_eb16
+            $0.textColor = .primary
+            $0.setContentHuggingPriority(.defaultLow, for: .horizontal)
+            $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+        }
+        
+        let rightIconImageView = UIImageView(image: UIImage(resource: .rightIcon)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.height.equalTo(24) }
+        }
+        
+        $0.addArrangedSubviews(cardImageView, paymentLabel, rightIconImageView)
+        let tapGesture = UITapGestureRecognizer()
+        tapGesture.addTarget(self, action: #selector(directPaymentButtonTapped))
+        $0.addGestureRecognizer(tapGesture)
+    }
+    
+    private let buttonContainer = UIView().then {
+        $0.backgroundColor = .white
+    }
+    
+    private let goTovehicleReservButton = UIButton().then {
+        $0.setTitle("차량 서비스 예약", for: .normal)
+        $0.applyUberStyle()
+    }
+    
+    // ScrollView
+    
+    private let scrollView = UIScrollView().then {
+        $0.showsVerticalScrollIndicator = false
+        $0.contentInset = .init(top: 0, left: 0, bottom: 92, right: 0)
+    }
+    
+    // Sections containing content
+    
+    private lazy var sections: [SectionView] = [
+        .init(
+            title: "출발/도착",
+            content: imageView,
+            contentEdge: .init(top: 10, left: 15, bottom: 10, right: 15)
+        ),
+        .init(
+            title: "픽업 시간",
+            content: pickupTimeLabel,
+            contentEdge: .init(top: 0, left: 25, bottom: 0, right: 25)
+        ),
+        .init(
+            title: "예정 도착 시간",
+            content: expectedArriveStack,
+            contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+        ),
+        .init(
+            title: "차량 선택",
+            subtitle: .init(string: "상황에 최적화 된 차량과 기사님을 만나보세요\n가장 훌륭한 탑승 경험을 누릴 수 있어요"),
+            content: vehicleSelectionButton,
+            contentEdge: .init(top: 16, left: 17.5, bottom: 6, right: 17.5)
+        ),
+        .init(
+            title: "예상 결제 금액",
+            subtitle: .init(string: "적용 가능한 할인 혜택이 없습니다.")
+                .prependImage(image: UIImage(resource: .promotion), imageSize: .init(width: 18, height: 18)),
+            content: expectedPaymentLabel,
+            contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+        ).then { $0.headerAxis = .horizontal },
+        .init(
+            title: "",
+            content: directPaymentView,
+            contentEdge: .init(top: 10, left: 18, bottom: 10, right: 18)
+        )
+    ]
+    
+    // Container containing sections
+    
+    private lazy var contentStackView = UIStackView().then {
+        let stackView = $0
+        stackView.backgroundColor = .bgGray
+        stackView.axis = .vertical
+        stackView.spacing = 8
+        sections.forEach { section in
+            stackView.addArrangedSubview(section)
+        }
+        stackView.setCustomSpacing(0, after: sections[1])
+    }
+}
+
+// MARK: - LifeCycle
+
+extension ReservationInfoViewController {
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        view.backgroundColor = .white
+        addSubViews()
+        setLayout()
+    }
+}
+
+// MARK: - Layout
+
+extension ReservationInfoViewController {
+    private func addSubViews() {
+        scrollView.addSubview(contentStackView)
+        buttonContainer.addSubview(goTovehicleReservButton)
+        [scrollView, buttonContainer].forEach {
+            view.addSubview($0)
+        }
+    }
+    
+    private func setLayout() {
+        scrollView.snp.makeConstraints {
+            $0.leading.trailing.bottom.equalToSuperview()
+            $0.top.equalTo(view.safeAreaLayoutGuide)
+        }
+        
+        contentStackView.snp.makeConstraints {
+            $0.edges.equalTo(scrollView.contentLayoutGuide)
+            $0.width.equalTo(scrollView.frameLayoutGuide)
+        }
+        
+        buttonContainer.snp.makeConstraints {
+            $0.horizontalEdges.bottom.equalToSuperview()
+        }
+        
+        goTovehicleReservButton.snp.makeConstraints {
+            $0.horizontalEdges.equalToSuperview().inset(10)
+            $0.top.equalTo(buttonContainer.safeAreaLayoutGuide).inset(6)
+            $0.bottom.equalTo(buttonContainer.safeAreaLayoutGuide).inset(6)
+            $0.height.equalTo(56)
+        }
+    }
+}
+
+// MARK: - UI Action
+
+extension ReservationInfoViewController {
+    @objc private func vehicleSelectionButtonTapped() {
+        
+    }
+    
+    @objc private func directPaymentButtonTapped() {
+        
+    }
+}
+
+#Preview {
+    ReservationInfoViewController()
+}
+
diff --git a/Uber-iOS/Uber-iOS/Presentation/Reservation/SectionView.swift b/Uber-iOS/Uber-iOS/Presentation/Reservation/SectionView.swift
new file mode 100644
index 0000000..c7c428f
--- /dev/null
+++ b/Uber-iOS/Uber-iOS/Presentation/Reservation/SectionView.swift
@@ -0,0 +1,103 @@
+//
+//  SectionView.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+import SnapKit
+
+final class SectionView: UIView {
+    
+    var headerAxis: NSLayoutConstraint.Axis {
+        get {
+            topContainerView.axis
+        } set {
+            topContainerView.axis = newValue
+            titleLabel.setContentHuggingPriority(.required, for: .horizontal)
+        }
+    }
+    
+    private let titleLabel = UILabel().then {
+        $0.setLabel(alignment: .left, textColor: .primary, font: .body1_eb18)
+    }
+    
+    private let subtitleLabel = UILabel().then {
+        $0.setLabel(alignment: .left, textColor: .sub2, font: .caption_m12)
+    }
+    
+    private let topContainerView = UIStackView().then {
+        $0.axis = .vertical
+        $0.spacing = 4.5
+    }
+    
+    private let bottomContainer = UIView()
+    
+    private let contentView = UIStackView().then {
+        $0.axis = .vertical
+    }
+    
+    init(title: String, subtitle: NSMutableAttributedString?, content: UIView, contentEdge: UIEdgeInsets) {
+        super.init(frame: .zero)
+        setupView(title: title, subtitle: subtitle, content: content, contentEdge: contentEdge)
+    }
+    
+    convenience init(title: String, subtitle: NSMutableAttributedString?, content: UIView) {
+        self.init(title: title, subtitle: subtitle, content: content, contentEdge: UIEdgeInsets())
+    }
+    
+    convenience init(title: String, content: UIView) {
+        self.init(title: title, subtitle: nil, content: content, contentEdge: UIEdgeInsets())
+    }
+    
+    convenience init(title: String, content: UIView, contentEdge: UIEdgeInsets) {
+        self.init(title: title, subtitle: nil, content: content, contentEdge: contentEdge)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    private func setupView(title: String, subtitle: NSMutableAttributedString?, content: UIView, contentEdge: UIEdgeInsets) {
+        backgroundColor = .white
+        titleLabel.text = title
+        subtitleLabel.attributedText = subtitle        
+        titleLabel.setTextWithLineHeight(text: title, lineHeight: 36)
+        
+        // Set topContainer
+        
+        if subtitle != nil {
+            topContainerView.addArrangedSubviews(titleLabel, subtitleLabel)
+        } else {
+            topContainerView.addArrangedSubview(titleLabel)
+        }
+        
+        topContainerView.isLayoutMarginsRelativeArrangement = true
+        topContainerView.layoutMargins = UIEdgeInsets(top: 10, left: 25, bottom: 0, right: 25)
+        topContainerView.layoutMargins.top += 10
+        
+        // Set bottomContainer
+        
+        bottomContainer.addSubview(content)
+        
+        content.snp.makeConstraints {
+            $0.leading.equalToSuperview().inset(contentEdge.left)
+            $0.top.equalToSuperview().inset(contentEdge.top)
+            $0.bottom.equalToSuperview().inset(contentEdge.bottom + 10)
+            $0.trailing.equalToSuperview().inset(contentEdge.right)
+        }
+        
+        // Set Container
+        
+        addSubview(contentView)
+        contentView.addArrangedSubviews(topContainerView, bottomContainer)
+        topContainerView.snp.makeConstraints {
+            $0.horizontalEdges.equalToSuperview()
+        }
+        contentView.snp.makeConstraints {
+            $0.edges.equalToSuperview()
+        }
+    }
+}


Reviews:
user: login: hwidung
body: 너무너무 잘하신담 🫨
빠르게 구현 완료 하신 점 대단합니담! 수고하셨습니다
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T09:50:45Z
user: login: hwidung
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T09:58:08Z
user: login: kwon-seokki
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T10:50:51Z

Review comments:
diff_hunk: @@ -0,0 +1,103 @@
+//
+//  SectionView.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+import SnapKit
+
+final class SectionView: UIView {
+    
+    var headerAxis: NSLayoutConstraint.Axis {
+        get {
+            topContainerView.axis
+        } set {
+            topContainerView.axis = newValue
+            titleLabel.setContentHuggingPriority(.required, for: .horizontal)
+        }
+    }
+    
+    private let titleLabel = UILabel().then {
+        $0.setLabel(alignment: .left, textColor: .primary, font: .body1_eb18)
+    }
+    
+    private let subtitleLabel = UILabel().then {
+        $0.setLabel(alignment: .left, textColor: .sub2, font: .caption_m12)
+    }
+    
+    private let topContainerView = UIStackView().then {
+        $0.axis = .vertical
+        $0.spacing = 4.5
+    }
+    
+    private let bottomContainer = UIView()
+    
+    private let contentView = UIStackView().then {
+        $0.axis = .vertical
+    }
+    
+    init(title: String, subtitle: NSMutableAttributedString?, content: UIView, contentEdge: UIEdgeInsets) {
+        super.init(frame: .zero)
+        setupView(title: title, subtitle: subtitle, content: content, contentEdge: contentEdge)
+    }
+    
+    convenience init(title: String, subtitle: NSMutableAttributedString?, content: UIView) {
path: Uber-iOS/Uber-iOS/Presentation/Reservation/SectionView.swift
user: login: hwidung
body: p3: 
다양한 경우에 쓸 수 있도록 잘 정리됐네요. 
상황에 따라 아래 subtitle이 있는 뷰도, 아닌 뷰도 있는데 모든 부분 잘 고려해서 만드신 첨 최고에용
created_at: 2025-05-15T09:47:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,236 @@
+//
+//  ReservationInfoViewController.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+import SnapKit
+
+// MARK: - Properties
+
+final class ReservationInfoViewController: UIViewController {
+    
+    // Content
+    
+    private let imageView = UIImageView().then {
+        $0.image = UIImage(resource: .route)
+    }
+    
+    private let pickupTimeLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "05월 05일 (월) / 오전 20:16".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "오전 20:31".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveDetailLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.text = "예상 운행 시간 약 25분 소요 예상"
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 18)
+    }
+    
+    private lazy var expectedArriveStack = UIStackView().then {
+        $0.axis = .vertical
+        $0.alignment = .leading
+        $0.addArrangedSubviews(expectedArriveLabel, expectedArriveDetailLabel)
+    }
+    
+    private lazy var vehicleSelectionButton = UIButton().then {
+        $0.setTitle("차량 선택하기", for: .normal)
+        $0.setTitleColor(.primary, for: .normal)
+        $0.titleLabel?.font = .body2_sb16
+        $0.layer.borderWidth = 1
+        $0.layer.cornerRadius = 12
+        $0.layer.borderColor = UIColor.black.cgColor
+        $0.snp.makeConstraints {
+            $0.height.equalTo(56)
+        }
+        $0.addTarget(self, action: #selector(vehicleSelectionButtonTapped), for: .touchUpInside)
+    }
+    
+    private let expectedPaymentLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "₩ 15,000 - 19,000".replaceFont(pattern: "[0-9]", replaceFont: .body2_sb16)
+    }
+    
+    private lazy var directPaymentView = UIStackView().then {
+        $0.axis = .horizontal
+        $0.alignment = .center
+        $0.distribution = .fill
+        $0.spacing = 4
+        
+        let cardImageView = UIImageView(image: UIImage(resource: .card)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.equalTo(36) }
+        }
+        
+        let paymentLabel = UILabel().then {
+            $0.text = "직접 결제"
+            $0.font = .body2_eb16
+            $0.textColor = .primary
+            $0.setContentHuggingPriority(.defaultLow, for: .horizontal)
+            $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+        }
+        
+        let rightIconImageView = UIImageView(image: UIImage(resource: .rightIcon)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.height.equalTo(24) }
+        }
+        
+        $0.addArrangedSubviews(cardImageView, paymentLabel, rightIconImageView)
+        let tapGesture = UITapGestureRecognizer()
+        tapGesture.addTarget(self, action: #selector(directPaymentButtonTapped))
+        $0.addGestureRecognizer(tapGesture)
+    }
+    
+    private let buttonContainer = UIView().then {
+        $0.backgroundColor = .white
+    }
+    
+    private let goTovehicleReservButton = UIButton().then {
+        $0.setTitle("차량 서비스 예약", for: .normal)
+        $0.applyUberStyle()
+    }
+    
+    // ScrollView
+    
+    private let scrollView = UIScrollView().then {
+        $0.showsVerticalScrollIndicator = false
+        $0.contentInset = .init(top: 0, left: 0, bottom: 92, right: 0)
+    }
+    
+    // Sections containing content
+    
+    private lazy var startAndArriveSection = SectionView(
+        title: "출발/도착",
+        content: imageView,
+        contentEdge: .init(top: 10, left: 15, bottom: 10, right: 15)
+    )
+    
+    private lazy var pickupTimeSection = SectionView(
+        title: "픽업 시간",
+        content: pickupTimeLabel,
+        contentEdge: .init(top: 0, left: 25, bottom: 0, right: 25)
+    )
+    
+    private lazy var expectedArriveSection = SectionView(
+        title: "예정 도착 시간",
+        content: expectedArriveStack,
+        contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+    )
+    
+    private lazy var vehicleSelectionSection = SectionView(
+        title: "차량 선택",
+        subtitle: .init(string: "상황에 최적화 된 차량과 기사님을 만나보세요\n가장 훌륭한 탑승 경험을 누릴 수 있어요"),
+        content: vehicleSelectionButton,
+        contentEdge: .init(top: 16, left: 17.5, bottom: 6, right: 17.5)
+    )
+    
+    private lazy var expectedPaymentAccountSection = SectionView(
+        title: "예상 결제 금액",
+        subtitle: .init(string: "적용 가능한 할인 혜택이 없습니다.")
+            .prependImage(image: UIImage(resource: .promotion), imageSize: .init(width: 18, height: 18)),
+        content: expectedPaymentLabel,
+        contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+    )
+    
+    private lazy var directPaymentSection = SectionView(
+        title: "",
+        content: directPaymentView,
+        contentEdge: .init(top: 10, left: 18, bottom: 10, right: 18)
+    )
+    
+    // Container containing sections
+    
+    private lazy var contentStackView = UIStackView().then {
+        $0.backgroundColor = .bgGray
+        $0.axis = .vertical
+        $0.spacing = 8
+        expectedPaymentAccountSection.headerAxis = .horizontal
path: Uber-iOS/Uber-iOS/Presentation/Reservation/ReservationInfoViewController.swift
user: login: hwidung
body: p3:
SectionView 안에서 headerAxis 을 조정하면 어땠을까 싶어용
`SectionView(title:..., content:..., headerAxis: .horizontal)` 처럼요!
created_at: 2025-05-15T09:58:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,236 @@
+//
+//  ReservationInfoViewController.swift
+//  Uber-iOS
+//
+//  Created by 권석기 on 5/14/25.
+//
+
+import UIKit
+
+import SnapKit
+
+// MARK: - Properties
+
+final class ReservationInfoViewController: UIViewController {
+    
+    // Content
+    
+    private let imageView = UIImageView().then {
+        $0.image = UIImage(resource: .route)
+    }
+    
+    private let pickupTimeLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "05월 05일 (월) / 오전 20:16".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "오전 20:31".replaceFont(pattern: "[0-9]|\\([ㄱ-ㅣ가-힣]\\)", replaceFont: .body2_sb16)
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 24)
+    }
+    
+    private let expectedArriveDetailLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.text = "예상 운행 시간 약 25분 소요 예상"
+        $0.setTextWithLineHeight(text: $0.text, lineHeight: 18)
+    }
+    
+    private lazy var expectedArriveStack = UIStackView().then {
+        $0.axis = .vertical
+        $0.alignment = .leading
+        $0.addArrangedSubviews(expectedArriveLabel, expectedArriveDetailLabel)
+    }
+    
+    private lazy var vehicleSelectionButton = UIButton().then {
+        $0.setTitle("차량 선택하기", for: .normal)
+        $0.setTitleColor(.primary, for: .normal)
+        $0.titleLabel?.font = .body2_sb16
+        $0.layer.borderWidth = 1
+        $0.layer.cornerRadius = 12
+        $0.layer.borderColor = UIColor.black.cgColor
+        $0.snp.makeConstraints {
+            $0.height.equalTo(56)
+        }
+        $0.addTarget(self, action: #selector(vehicleSelectionButtonTapped), for: .touchUpInside)
+    }
+    
+    private let expectedPaymentLabel = UILabel().then {
+        $0.font = .caption_m12
+        $0.textColor = .sub1
+        $0.attributedText = "₩ 15,000 - 19,000".replaceFont(pattern: "[0-9]", replaceFont: .body2_sb16)
+    }
+    
+    private lazy var directPaymentView = UIStackView().then {
+        $0.axis = .horizontal
+        $0.alignment = .center
+        $0.distribution = .fill
+        $0.spacing = 4
+        
+        let cardImageView = UIImageView(image: UIImage(resource: .card)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.equalTo(36) }
+        }
+        
+        let paymentLabel = UILabel().then {
+            $0.text = "직접 결제"
+            $0.font = .body2_eb16
+            $0.textColor = .primary
+            $0.setContentHuggingPriority(.defaultLow, for: .horizontal)
+            $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+        }
+        
+        let rightIconImageView = UIImageView(image: UIImage(resource: .rightIcon)).then {
+            $0.contentMode = .scaleAspectFit
+            $0.setContentHuggingPriority(.required, for: .horizontal)
+            $0.snp.makeConstraints { $0.width.height.equalTo(24) }
+        }
+        
+        $0.addArrangedSubviews(cardImageView, paymentLabel, rightIconImageView)
+        let tapGesture = UITapGestureRecognizer()
+        tapGesture.addTarget(self, action: #selector(directPaymentButtonTapped))
+        $0.addGestureRecognizer(tapGesture)
+    }
+    
+    private let buttonContainer = UIView().then {
+        $0.backgroundColor = .white
+    }
+    
+    private let goTovehicleReservButton = UIButton().then {
+        $0.setTitle("차량 서비스 예약", for: .normal)
+        $0.applyUberStyle()
+    }
+    
+    // ScrollView
+    
+    private let scrollView = UIScrollView().then {
+        $0.showsVerticalScrollIndicator = false
+        $0.contentInset = .init(top: 0, left: 0, bottom: 92, right: 0)
+    }
+    
+    // Sections containing content
+    
+    private lazy var startAndArriveSection = SectionView(
+        title: "출발/도착",
+        content: imageView,
+        contentEdge: .init(top: 10, left: 15, bottom: 10, right: 15)
+    )
+    
+    private lazy var pickupTimeSection = SectionView(
+        title: "픽업 시간",
+        content: pickupTimeLabel,
+        contentEdge: .init(top: 0, left: 25, bottom: 0, right: 25)
+    )
+    
+    private lazy var expectedArriveSection = SectionView(
+        title: "예정 도착 시간",
+        content: expectedArriveStack,
+        contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+    )
+    
+    private lazy var vehicleSelectionSection = SectionView(
+        title: "차량 선택",
+        subtitle: .init(string: "상황에 최적화 된 차량과 기사님을 만나보세요\n가장 훌륭한 탑승 경험을 누릴 수 있어요"),
+        content: vehicleSelectionButton,
+        contentEdge: .init(top: 16, left: 17.5, bottom: 6, right: 17.5)
+    )
+    
+    private lazy var expectedPaymentAccountSection = SectionView(
+        title: "예상 결제 금액",
+        subtitle: .init(string: "적용 가능한 할인 혜택이 없습니다.")
+            .prependImage(image: UIImage(resource: .promotion), imageSize: .init(width: 18, height: 18)),
+        content: expectedPaymentLabel,
+        contentEdge: .init(top: 0, left: 25, bottom: 10, right: 25)
+    )
+    
+    private lazy var directPaymentSection = SectionView(
+        title: "",
+        content: directPaymentView,
+        contentEdge: .init(top: 10, left: 18, bottom: 10, right: 18)
+    )
+    
+    // Container containing sections
+    
+    private lazy var contentStackView = UIStackView().then {
+        $0.backgroundColor = .bgGray
+        $0.axis = .vertical
+        $0.spacing = 8
+        expectedPaymentAccountSection.headerAxis = .horizontal
path: Uber-iOS/Uber-iOS/Presentation/Reservation/ReservationInfoViewController.swift
user: login: kwon-seokki
body: 이부분도 잠깐 고민을 하다가
initializer가 옆으로 넒어지는 문제와 그에따라 convenience init 등의 명세가 달라지는점을 피하고자
계산프로퍼티로 변경이 가능하게 해두었습니다!

휘원님 말씀대로 지금처럼 2번을 설정하게되는 부분은 생각을 해보고 수정이 필요할 것 같습니다!

created_at: 2025-05-15T10:50:51Z
author_association: COLLABORATOR

PR comments:
user: login: dudwntjs
created_at: 2025-05-15T12:26:14Z
body: p3: 전체 SectionView를 배열로 묶어서 관리하면 반복 코드가 줄고, StackView에 한 번에 추가할 수 있어서 가독성과 유지보수성이 좋아질 것 같아요!
특히 section 순서를 바꾸거나 조건부로 표시할 일이 생긴다면 배열 방식이 더 유연하게 대응할 수 있을 것 같아서 제안드려봅니다🔥

지금 꼭 바꾸라는 건 절대 아니고요! 저도 개인적으로 한번 적용해보고 싶었던 방식이라 공유만 드립니다🫨

``` Swift
private lazy var sections: [SectionView] = [
    SectionView(title: "출발/도착", content: imageView),
    SectionView(title: "픽업 시간", content: pickupTimeLabel),
    // ...
]

private lazy var contentStackView = UIStackView().then {
    $0.axis = .vertical
    $0.spacing = 8
    $0.addArrangedSubviews(sections)
}
```
이러코롬.... 말로 설명하기 어려워서 코드로 치자면 이렇습니다

필요에 따라 특정 section만 배열 밖으로 빼서 관리하는 hybrid 방식도 있다고 하더라구요~?!!!!! 저도 예전에 다른 분 코리하면서 배웠어요ㅎㅎ정보 공유 코리입니더
author_association: COLLABORATOR
user: login: kwon-seokki
created_at: 2025-05-15T15:25:19Z
body: > p3: 전체 SectionView를 배열로 묶어서 관리하면 반복 코드가 줄고, StackView에 한 번에 추가할 수 있어서 가독성과 유지보수성이 좋아질 것 같아요! 특히 section 순서를 바꾸거나 조건부로 표시할 일이 생긴다면 배열 방식이 더 유연하게 대응할 수 있을 것 같아서 제안드려봅니다🔥
> 
> 지금 꼭 바꾸라는 건 절대 아니고요! 저도 개인적으로 한번 적용해보고 싶었던 방식이라 공유만 드립니다🫨
> 
> ```swift
> private lazy var sections: [SectionView] = [
>     SectionView(title: "출발/도착", content: imageView),
>     SectionView(title: "픽업 시간", content: pickupTimeLabel),
>     // ...
> ]
> 
> private lazy var contentStackView = UIStackView().then {
>     $0.axis = .vertical
>     $0.spacing = 8
>     $0.addArrangedSubviews(sections)
> }
> ```
> 
> 이러코롬.... 말로 설명하기 어려워서 코드로 치자면 이렇습니다
> 
> 필요에 따라 특정 section만 배열 밖으로 빼서 관리하는 hybrid 방식도 있다고 하더라구요~?!!!!! 저도 예전에 다른 분 코리하면서 배웠어요ㅎㅎ정보 공유 코리입니더

맞아요 현재 화면에는 section 순서를 바꿀일이 없긴하지만 많은 경우에서 바꾸거나 조건부로 표시하는 경우가 있다고 생각합니다! 
현재 상태에서도 각각 선언된 프로퍼티를 배열로 묶는것도 가독성 측면에서 의미가 있다고 생각해서 바로 반영하도록 할게요!

 꼼꼼한 리뷰 감사합니다 🙇‍♂️
author_association: COLLABORATOR