Skip to content

Commit 300bad5

Browse files
authored
[NL-69] : Toast 구현 (#46)
* [NL-69] : profile 수정 * [NL-69] : 정보 수정 API 연결 * [NL-69] : PopUpComponent 생성 * [NL-69] : Popup 적용 * [NL-69] : 파일 이름 수정 * [NL-69] : Toas 구현 - Layout 문제 해결
1 parent 04eee2d commit 300bad5

File tree

16 files changed

+270
-28
lines changed

16 files changed

+270
-28
lines changed

Common/Base/Sources/Model/UserDTO.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ public struct UserDTO: Decodable {
2020
public let birthDate: String?
2121
public let birthTime: [String]?
2222
public let gender: GenderDTO
23+
24+
public init(id: String, name: String, birthDate: String?, birthTime: [String]?, gender: GenderDTO) {
25+
self.id = id
26+
self.name = name
27+
self.birthDate = birthDate
28+
self.birthTime = birthTime
29+
self.gender = gender
30+
}
2331

2432
public init(id: String, name: String, birthDate: String?, birthTime: [String]?, gender: GenderDTO)
2533
{

Common/Lib/Sources/Router/AppRouter.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ extension Routable {
6363
} else {
6464
topViewController()?.dismiss(animated: true, completion: nil)
6565
}
66+
case .pop :
67+
if let navigationController = topViewController()?.navigationController {
68+
navigationController.popViewController(animated: true)
69+
} else {
70+
topViewController()?.dismiss(animated: true, completion: nil)
71+
}
6672
}
6773
}
6874
}

Common/Lib/Sources/Router/Concreate/NavigateType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ public enum NavigateType {
1414
case overCurrentContext // modalPresentationStyle = .overCurrentContext
1515
case custom // custom 전환 (transitioningDelegate 필요)
1616
case clear //해당 router 관련 view 전부 지움
17+
case pop
1718
}

Common/Lib/Sources/Router/SettingRoute.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public enum SettingRoute {
1212
case timePicker
1313
case back
1414
case pop
15+
case popWithToast
1516
}
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "AlertComponent.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
}
12+
}

DesignSystem/DesignSystem/Sources/PopUp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public final class PopUp: UIView {
105105
outButton.snp.makeConstraints { make in
106106
make.height.equalTo(48)
107107
}
108-
108+
109109
actionButton.snp.makeConstraints { make in
110110
make.height.equalTo(48)
111111
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Toast.swift
3+
// DesignSystemLayer
4+
//
5+
// Created by 최재혁 on 8/18/25.
6+
//
7+
8+
import UIKit
9+
10+
import SnapKit
11+
import Then
12+
13+
public final class Toast : UIView {
14+
private lazy var contentStack = UIStackView().then {
15+
$0.axis = .horizontal
16+
$0.alignment = .leading
17+
$0.distribution = .fill
18+
$0.spacing = 8
19+
$0.backgroundColor = STColors.gray2.color
20+
$0.layer.cornerRadius = 10
21+
$0.layoutMargins = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
22+
$0.isLayoutMarginsRelativeArrangement = true
23+
}
24+
25+
private lazy var checkImageView = UIImageView().then {
26+
$0.image = STImages.alertComponent.image
27+
$0.contentMode = .scaleAspectFit
28+
}
29+
30+
private lazy var messageLabel = UILabel().then {
31+
$0.textColor = STColors.white.color
32+
$0.style = Typography.Body_14_M
33+
$0.textAlignment = .left
34+
}
35+
36+
public init() {
37+
super.init(frame: .zero)
38+
setupUI()
39+
}
40+
41+
required init?(coder: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
private func setupUI() {
46+
addSubview(contentStack)
47+
contentStack.addArrangedSubview(checkImageView)
48+
contentStack.addArrangedSubview(messageLabel)
49+
50+
contentStack.snp.makeConstraints { make in
51+
make.edges.equalToSuperview()
52+
}
53+
54+
checkImageView.snp.makeConstraints { make in
55+
make.width.height.equalTo(16)
56+
}
57+
}
58+
59+
public func update(message : String) {
60+
self.messageLabel.styledText = message
61+
}
62+
}
63+
64+
@available(iOS 17.0, *)
65+
#Preview {
66+
let stackView = UIStackView().then {
67+
$0.axis = .vertical
68+
$0.spacing = 16
69+
$0.alignment = .center
70+
}
71+
72+
let toast = Toast().then {
73+
$0.update(message: "정보 수정 성공했습니다.")
74+
}
75+
76+
stackView.addArrangedSubview(toast)
77+
78+
toast.snp.makeConstraints { make in
79+
make.width.equalTo(327)
80+
make.height.equalTo(44)
81+
}
82+
83+
return stackView
84+
}

Feature/Setting/Sources/EditPage/EditProfileViewController.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ import DIInjector
1111
import DesignSystem
1212
import Foundation
1313
import UIKit
14+
import DIInjector
15+
16+
public protocol EditProfileViewControllerProtocol {
17+
func showToast()
18+
}
1419

1520
final class EditProfileViewController: BaseViewController {
1621

1722
private var store: Set<AnyCancellable> = []
1823
private let viewModel: EditProfileViewModel
19-
@Injected private var router: SettingRouter
24+
25+
var delegate : EditProfileViewControllerProtocol?
2026

2127
private lazy var scrollView: UIScrollView = UIScrollView().then {
2228
$0.showsHorizontalScrollIndicator = false
@@ -31,6 +37,16 @@ final class EditProfileViewController: BaseViewController {
3137
$0.spacing = 28
3238
$0.alignment = .leading
3339
}
40+
41+
private lazy var backgourndView = UIView().then {
42+
$0.isHidden = true
43+
$0.backgroundColor = STColors.black.color.withAlphaComponent(0.5)
44+
}
45+
46+
private lazy var popup = PopUp().then {
47+
$0.isHidden = true
48+
$0.update(titile: "수정 중인 내용이 있소", description: "저장하지 않고 화면을 벗어나면\n감쪽같이 사라질 것이오", actionButtonTitle: "계속 수정하기", outButtonTitle: "나가기")
49+
}
3450

3551
private lazy var backgourndView = UIView().then {
3652
$0.isHidden = true
@@ -235,6 +251,16 @@ extension EditProfileViewController {
235251
make.top.bottom.equalToSuperview()
236252
make.leading.trailing.equalToSuperview().inset(24)
237253
}
254+
255+
backgourndView.snp.makeConstraints { make in
256+
make.edges.equalToSuperview()
257+
}
258+
259+
popup.snp.makeConstraints { make in
260+
make.center.equalToSuperview()
261+
make.width.equalTo(327)
262+
make.height.equalTo(206)
263+
}
238264

239265
backgourndView.snp.makeConstraints { make in
240266
make.edges.equalToSuperview()
@@ -363,6 +389,41 @@ extension EditProfileViewController {
363389
self.birthStack.layoutIfNeeded()
364390
}
365391
.store(in: &store)
392+
393+
viewModel.output.setTimePickerLabel
394+
.receive(on: RunLoop.main)
395+
.sink { [weak self] time in
396+
guard let self else { return }
397+
self.bornTimeSetButton.selectedItem = time
398+
}
399+
.store(in: &store)
400+
401+
viewModel.output.navigate
402+
.receive(on: RunLoop.main)
403+
.sink { [weak self ] route in
404+
guard let self else { return }
405+
guard let delegate else { return }
406+
if route == .popWithToast {
407+
delegate.showToast()
408+
}
409+
}
410+
.store(in: &store)
411+
412+
viewModel.output.updatePopupHiden
413+
.receive(on: RunLoop.main)
414+
.sink { [weak self] isHidden in
415+
guard let self else { return }
416+
self.popup.isHidden = isHidden
417+
self.backgourndView.isHidden = isHidden
418+
}
419+
.store(in: &store)
420+
421+
popup.outButton.tapPublisher
422+
.sink { [weak self] _ in
423+
guard let self else { return }
424+
self.navigationController?.popViewController(animated: true)
425+
}
426+
.store(in: &store)
366427

367428
viewModel.output.setTimePickerLabel
368429
.receive(on: RunLoop.main)
@@ -421,6 +482,7 @@ extension EditProfileViewController {
421482
dontKnowButton.gesturePublisher(gestureRecognizer: UITapGestureRecognizer())
422483
.sink { [weak self] _ in
423484
guard let self else { return }
485+
424486
self.viewModel.send(
425487
input: .bornTimeSelected(bornTime: .dontKnow(isSelected: self.dontKnowButton.isSelected)))
426488
bornTimeSetButton.isEnabled.toggle()

Feature/Setting/Sources/EditPage/EditProfileViewModel.swift

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Combine
1111
import DIInjector
1212
import Foundation
1313
import Lib
14+
import Auth
15+
import DIInjector
1416

1517
final class EditProfileViewModel {
1618
enum Input {
@@ -45,6 +47,9 @@ final class EditProfileViewModel {
4547
let output: Output = Output()
4648

4749
private var state: State = .init()
50+
51+
@Injected private var userDataManager : UserDataManager
52+
@Injected private var router: SettingRouter
4853

4954
@Injected private var userDataManager: UserDataManager
5055
@Injected private var router: SettingRouter
@@ -81,22 +86,22 @@ final class EditProfileViewModel {
8186
self.output.setTimePickerLabel.send(time)
8287
self._isDontKnowButtonSelected = false
8388
}
84-
89+
8590
case .checkBirthFormat(let birth):
8691
self._isBirthDateValid = checkBirthFormat(birth: birth)
8792
if self._isBirthDateValid { self.state.birthDate = birth }
8893
self.output.showBirthError.send(self._isBirthDateValid)
8994
self.output.isSaveButtonEnabled.send(_isNameValid && _isBirthDateValid && _isBornTimeValied)
90-
95+
9196
case .checkNameFormat(let name):
9297
self._isNameValid = checkNameFormat(name: name)
9398
if self._isNameValid { self.state.name = name }
9499
self.output.showNameError.send(self._isNameValid)
95100
self.output.isSaveButtonEnabled.send(_isNameValid && _isBirthDateValid && _isBornTimeValied)
96-
101+
97102
case .genderSelected(let isSelected):
98103
self.state.gender = isSelected
99-
104+
100105
case .timePickerTap:
101106
Task { @MainActor in
102107
self.router.navigate(
@@ -106,25 +111,27 @@ final class EditProfileViewModel {
106111
if let userDTO = generateUserDTO() {
107112
Task {
108113
do {
109-
let result = try await self.userDataManager.update(
110-
name: userDTO.name, birthDate: userDTO.birthDate!, birthTime: userDTO.birthTime,
111-
gender: userDTO.gender)
112-
self.state.originalUser = result
113-
self.state.name = nil
114-
self.state.bornTime = nil
115-
self.state.birthDate = nil
116-
self.state.gender = nil
114+
let _ = try await self.userDataManager.update(
115+
name: userDTO.name, birthDate: userDTO.birthDate!, birthTime: userDTO.birthTime, gender: userDTO.gender)
116+
await MainActor.run {
117+
self.output.navigate.send(.popWithToast)
118+
self.router.navigate(to: SettingRoute.myPage, how: .pop, with: [:])
119+
}
120+
117121
} catch {
118122
// TODO: API 호출 에러처리
119123
print(error)
120124
}
121125
}
122126
}
127+
123128
case .backButtonTapped:
124129
if hasChanges() {
125130
self.output.updatePopupHiden.send(false)
126131
} else {
127-
self.output.navigate.send(.pop)
132+
Task { @MainActor in
133+
self.router.navigate(to: SettingRoute.myPage, how: .pop, with: [:])
134+
}
128135
}
129136
}
130137
}
@@ -178,6 +185,7 @@ extension EditProfileViewModel {
178185
var updatedGender = originalUser.gender
179186
let updatedBirthDate = self.state.birthDate ?? originalUser.birthDate
180187
var updatedBornTime: [String]? = nil
188+
181189
if let bornTimeState = self.state.bornTime {
182190
updatedBornTime = bornTimeState
183191
} else if self.state.bornTime == nil && self._isDontKnowButtonSelected == false {
@@ -221,7 +229,6 @@ extension EditProfileViewModel {
221229
return true
222230
}
223231
}
224-
225232
return false
226233
}
227234
}

0 commit comments

Comments
 (0)