-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat/#70] 목표설정시간 알림창 구현 #73
base: develop
Are you sure you want to change the base?
Changes from all commits
74a2d06
43596f4
684eff9
3942180
126f467
61a8e91
bc237d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// | ||
// SelectGoalWalkTimeView.swift | ||
// Footprint-iOSTests | ||
// | ||
// Created by 김영인 on 2023/02/05. | ||
// Copyright © 2023 Footprint-iOS. All rights reserved. | ||
// | ||
|
||
import UIKit | ||
|
||
import RxSwift | ||
import RxRelay | ||
|
||
class SelectGoalWalkTimeView: BaseView { | ||
|
||
enum Time: Int { | ||
case hour | ||
case minute | ||
} | ||
|
||
// MARK: - Properties | ||
|
||
private var hours: [String] = [] | ||
private var minutes: [String] = [] | ||
|
||
var selectedHour = BehaviorRelay<String>(value: "0시간") | ||
var selectedMinute = BehaviorRelay<String>(value: "10분") | ||
|
||
// MARK: - UI Components | ||
|
||
let pickerView = UIPickerView() | ||
|
||
// MARK: - Methods | ||
|
||
override func setupProperty() { | ||
super.setupProperty() | ||
|
||
setupPickerView() | ||
} | ||
|
||
override func setupDelegate() { | ||
pickerView.delegate = self | ||
pickerView.dataSource = self | ||
} | ||
|
||
override func setupHierarchy() { | ||
addSubview(pickerView) | ||
} | ||
|
||
override func setupLayout() { | ||
pickerView.snp.makeConstraints() { | ||
$0.top.bottom.equalToSuperview() | ||
$0.leading.trailing.equalToSuperview().inset(20) | ||
} | ||
} | ||
|
||
override func layoutSubviews() { | ||
super.layoutSubviews() | ||
|
||
pickerView.selectRow(1, inComponent: 1, animated: true) | ||
} | ||
|
||
private func setupPickerView() { | ||
for hour in Array(0...4) { | ||
hours.append("\(hour)시간") | ||
} | ||
|
||
for minute in Array(0...5) { | ||
minutes.append("\(minute * 10)분") | ||
} | ||
} | ||
} | ||
|
||
extension SelectGoalWalkTimeView: UIPickerViewDataSource { | ||
func numberOfComponents(in pickerView: UIPickerView) -> Int { | ||
return 2 | ||
} | ||
|
||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { | ||
switch component { | ||
case Time.hour.rawValue: | ||
return hours.count | ||
case Time.minute.rawValue: | ||
return minutes.count | ||
default: | ||
return 0 | ||
} | ||
} | ||
} | ||
|
||
extension SelectGoalWalkTimeView: UIPickerViewDelegate { | ||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { | ||
|
||
switch component { | ||
case Time.hour.rawValue: | ||
selectedHour.accept(hours[row]) | ||
case Time.minute.rawValue: | ||
selectedMinute.accept(minutes[row]) | ||
default: | ||
break | ||
} | ||
|
||
if selectedHour.value == hours[0] { | ||
pickerView.selectRow(1, inComponent: 1, animated: true) | ||
} | ||
if selectedHour.value == hours[4] { | ||
pickerView.selectRow(0, inComponent: 1, animated: true) | ||
} | ||
} | ||
|
||
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { | ||
return 40 | ||
} | ||
|
||
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { | ||
return 90 | ||
} | ||
|
||
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { | ||
|
||
let label = UILabel() | ||
label.font = .systemFont(ofSize: 18) | ||
label.textAlignment = .center | ||
|
||
switch component { | ||
case Time.hour.rawValue: | ||
label.text = hours[row] | ||
case Time.minute.rawValue: | ||
label.text = minutes[row] | ||
default: | ||
break | ||
} | ||
|
||
return label | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
import UIKit | ||
|
||
import ReactorKit | ||
import RxSwift | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p4; 음 .. AlertViewController에 로직이 추가되다보니 복잡해지네요, BottomSheet 구현한 것 처럼 Alert도 부모를 상속받아서 구현하는 구조로 작성하는 건 어떨까요 .. ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 되면 고칠거는 조금 많아져보이네요.. 애초에 Alert 뷰컨 코드 고민할때 타입이 몇개 인지 정해놓고 작성을 시작했었네여, 아무튼 리펙토링을 한다면, 상속으로 작성하면 좋아보여요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 알림창 내에 로직이 필요한 경우만 따로 빼도 좋을 것 같아 보입니다. |
||
import Then | ||
|
||
enum AlertType { | ||
|
@@ -22,7 +23,11 @@ enum AlertType { | |
case withdrawal | ||
case deleteAll(Int) | ||
case badge(String) | ||
case custom | ||
case custom(value: Custom) | ||
|
||
enum Custom { | ||
case selectGoalWalkTime | ||
} | ||
|
||
var title: String { | ||
switch self { | ||
|
@@ -46,7 +51,7 @@ enum AlertType { | |
return "\(i)번째 산책'을 삭제하시겠어요?" | ||
case let .badge(badgeName): | ||
return "\(badgeName)\n뱃지를 획득했어요!" | ||
case .custom: | ||
case .custom(value: .selectGoalWalkTime): | ||
return "목표산책시간 직접설정" | ||
} | ||
} | ||
|
@@ -89,24 +94,29 @@ class AlertViewController: NavigationBarViewController, View { | |
// MARK: - Properties | ||
|
||
private let type: AlertType | ||
private let customViewType: AlertType.Custom? | ||
typealias Reactor = AlertReactor | ||
var alertAction: (() -> Void)? | ||
var selectTimeAction: ((String) -> Void)? | ||
|
||
private lazy var selectedTime: String = "" | ||
|
||
// MARK: - UI Components | ||
|
||
private lazy var oneButtonAlertView = OneButtonAlertView.init(type: self.type) | ||
private lazy var twoButtonAlertView = TwoButtonAlertView.init(type: self.type) | ||
private lazy var badgeAlertView = BadgeAlertView.init(type: self.type) | ||
private lazy var customAlertView = CustomAlertView.init(type: self.type) | ||
private lazy var customAlertView = CustomAlertView.init(type: self.type, customViewType: self.customViewType) | ||
|
||
// MARK: - Initializer | ||
|
||
init(type: AlertType, reator: Reactor) { | ||
init(type: AlertType, customViewType: AlertType.Custom? = nil, reactor: Reactor) { | ||
self.type = type | ||
self.customViewType = customViewType | ||
|
||
super.init(nibName: nil, bundle: nil) | ||
|
||
self.reactor = reator | ||
self.reactor = reactor | ||
} | ||
|
||
@available(*, unavailable) | ||
|
@@ -190,15 +200,38 @@ class AlertViewController: NavigationBarViewController, View { | |
}) | ||
.disposed(by: disposeBag) | ||
|
||
if customViewType == .selectGoalWalkTime { | ||
Observable.combineLatest( | ||
customAlertView.selectGoalWalkTimeView.selectedHour, | ||
customAlertView.selectGoalWalkTimeView.selectedMinute | ||
).bind { [weak self] (hour, minute) in | ||
if hour == "4시간" { | ||
self?.selectedTime = "4시간" | ||
return | ||
} | ||
|
||
let time = hour.contains("0") ? "\(minute)" : | ||
(minute == "0분") ? "\(hour)" : "\(hour) \(minute)" | ||
|
||
self?.selectedTime = time | ||
if time == "0분" { | ||
self?.selectedTime = "10분" | ||
} | ||
} | ||
.disposed(by: disposeBag) | ||
} | ||
|
||
customAlertView.rightButton.rx.tap | ||
.withUnretained(self) | ||
.asDriver(onErrorDriveWith: .empty()) | ||
.drive(onNext: { owner, _ in | ||
owner.alertAction?() | ||
if owner.customViewType == .selectGoalWalkTime { | ||
owner.selectTimeAction?(owner.selectedTime) | ||
} | ||
owner.dismiss(animated: true) | ||
}) | ||
.disposed(by: disposeBag) | ||
|
||
reactor.state | ||
.map(\.isDismiss) | ||
.bind { [weak self] _ in | ||
|
@@ -209,11 +242,15 @@ class AlertViewController: NavigationBarViewController, View { | |
} | ||
|
||
extension UIViewController { | ||
func makeAlert(type: AlertType, alertAction: (() -> Void)? = nil) { | ||
let alertVC = AlertViewController(type: type, reator: .init()) | ||
func makeAlert(type: AlertType, | ||
customViewType: AlertType.Custom? = nil, | ||
alertAction: (() -> Void)? = nil, | ||
selectTimeAction: ((String) -> Void)? = nil) { | ||
let alertVC = AlertViewController(type: type, customViewType: customViewType, reactor: .init()) | ||
alertVC.modalTransitionStyle = .crossDissolve | ||
alertVC.modalPresentationStyle = .overCurrentContext | ||
alertVC.alertAction = alertAction | ||
alertVC.selectTimeAction = selectTimeAction | ||
self.present(alertVC, animated: true) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4; 이부분에서 저는 의존성이 생겼다고 생각해요, 이렇게 하면 기존에 reactor로 부모 자식간 단방향으로 통신하는 구조가 아니라고 생각합니다.
사실 이 두개 변수 정도가 끝이긴 하겠지만,, 밑에서 말한것처럼 alertviewcontroller를 컨테이너 삼고, 자식에서 reactor를 주입시키고, 해당 reactor에 infoService를 주입시켜서 infoService로 통신하는 방법은 어떨까요 .. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 View에 Reactor를 생성해 구현하라는 뜻일까요 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네 맞습니다 !