Skip to content
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

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions FootprintIOS/FootprintIOS/Sources/Service/InfoService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum InfoEvent {
case updateBirth(content: String)
case updateWalk(content: String)
case updateGoalWalk(content: String)
case showGoalWalkAlertView

case getThisMonthGoal(GoalResponseDTO)
case getNextMonthGoal(GoalResponseDTO)
Expand All @@ -28,6 +29,7 @@ protocol InfoServiceType {
func updateBirth(to birth: String) -> Observable<String>
func updateWalk(to walk: String) -> Observable<String>
func updateGoalWalk(to goalWalk: String) -> Observable<String>
func showGoalWalkAlertView() -> Observable<Void>
}

class InfoService: NetworkService, InfoServiceType {
Expand Down Expand Up @@ -108,6 +110,11 @@ class InfoService: NetworkService, InfoServiceType {
return .just(walk)
}

func showGoalWalkAlertView() -> Observable<Void> {
event.onNext(.showGoalWalkAlertView)
return .just(Void())
}

func updateGoalWalk(to goalWalk: String) -> Observable<String> {
event.onNext(.updateGoalWalk(content: goalWalk))
return .just(goalWalk)
Expand Down
29 changes: 17 additions & 12 deletions FootprintIOS/FootprintIOS/Sources/View/Alert/CustomAlertView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class CustomAlertView: BaseView {
// MARK: - Properties

let type: AlertType
let customViewType: AlertType.Custom?

// MARK: - UI Components

Expand All @@ -26,10 +27,7 @@ class CustomAlertView: BaseView {
$0.textColor = FootprintIOSAsset.Colors.blackM.color
}

// FIXME: 나중에 재사용 가능성이 있다면 customView도 enum으로 관리
private let customView = UIView().then {
$0.backgroundColor = .systemYellow
}
lazy var selectGoalWalkTimeView = SelectGoalWalkTimeView.init()

private let lineView = UIView().then {
$0.backgroundColor = FootprintIOSAsset.Colors.white3.color
Expand All @@ -55,8 +53,9 @@ class CustomAlertView: BaseView {

// MARK: - Initializer

init(type: AlertType) {
init(type: AlertType, customViewType: AlertType.Custom?) {
self.type = type
self.customViewType = customViewType

super.init(frame: .zero)
}
Expand All @@ -79,15 +78,19 @@ class CustomAlertView: BaseView {
super.setupHierarchy()

addSubview(backgroundView)
backgroundView.addSubviews([titleLabel, customView, lineView, buttonStackView])
backgroundView.addSubviews([titleLabel, lineView, buttonStackView])

if customViewType == .selectGoalWalkTime {
backgroundView.addSubview(selectGoalWalkTimeView)
}
}

override func setupLayout() {
super.setupLayout()

backgroundView.snp.makeConstraints {
$0.width.equalTo(326)
$0.height.equalTo(259)
$0.width.equalTo(327)
$0.height.equalTo(230)
$0.center.equalToSuperview()
}

Expand All @@ -96,10 +99,12 @@ class CustomAlertView: BaseView {
$0.centerX.equalTo(backgroundView)
}

customView.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(10)
$0.leading.trailing.equalTo(backgroundView)
$0.height.equalTo(150)
if customViewType == .selectGoalWalkTime {
selectGoalWalkTimeView.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(10)
$0.leading.trailing.equalTo(backgroundView)
$0.height.equalTo(130)
}
}

buttonStackView.snp.makeConstraints {
Expand Down
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분")
Comment on lines +26 to +27
Copy link
Contributor

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로 통신하는 방법은 어떨까요 .. ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 View에 Reactor를 생성해 구현하라는 뜻일까요 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다 !


// 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
Expand Up @@ -10,10 +10,6 @@ import UIKit

class GoalView: BaseView {

// MARK: - Properties

var walkTimes: [Int] = [15, 30, 60, 90, 0]

// MARK: - UI Components

lazy var dayButtons: [UIButton] = []
Expand Down Expand Up @@ -120,11 +116,25 @@ class GoalView: BaseView {
func getWalkIndex(type: UserInfoSelectBarType) -> Int {
switch type {
case .goalTime:
for (index, walkType) in InfoTexts.goalWalkTexts.enumerated() {
if walkType == goalWalkSelectView.selectLabel.text {
return walkTimes[safe: index] ?? 0
}
let time = goalWalkSelectView.selectLabel.text ?? ""
var goalTime: Int

if !time.contains("시간") {
goalTime = time.split(separator: "분").map { Int($0) ?? 0 }.first ?? 0
} else if !time.contains("분") {
goalTime = (time.split(separator: "시간").map { Int($0) ?? 0 }.first ?? 0) * 60
} else {
let hourMinute = time.split(separator: " ")
let hour = (hourMinute[0].split(separator: "시간").map { Int($0) ?? 0 }.first ?? 0) * 60
let minute = hourMinute[1].split(separator: "분").map { Int($0) ?? 0 }.first ?? 0
goalTime = hour + minute
}

if time == "0분" {
goalTime = 10
}

return goalTime
case .time:
for (index, walkType) in InfoTexts.walkTexts.enumerated() {
if walkType == walkSelectView.selectLabel.text {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import UIKit

import ReactorKit
import RxSwift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4; 음 .. AlertViewController에 로직이 추가되다보니 복잡해지네요, BottomSheet 구현한 것 처럼 Alert도 부모를 상속받아서 구현하는 구조로 작성하는 건 어떨까요 .. ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 되면 고칠거는 조금 많아져보이네요.. 애초에 Alert 뷰컨 코드 고민할때 타입이 몇개 인지 정해놓고 작성을 시작했었네여, 아무튼 리펙토링을 한다면, 상속으로 작성하면 좋아보여요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알림창 내에 로직이 필요한 경우만 따로 빼도 좋을 것 같아 보입니다.
이건 추후에 다시 고민해보겠습니다 ~!

import Then

enum AlertType {
Expand All @@ -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 {
Expand All @@ -46,7 +51,7 @@ enum AlertType {
return "\(i)번째 산책'을 삭제하시겠어요?"
case let .badge(badgeName):
return "\(badgeName)\n뱃지를 획득했어요!"
case .custom:
case .custom(value: .selectGoalWalkTime):
return "목표산책시간 직접설정"
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}
Loading