Skip to content

Commit

Permalink
[MERGE] LoginViewController MVVM-C로 변경 (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
ffalswo2 authored Nov 14, 2023
2 parents 438dd5b + a72551e commit 8b19e3a
Show file tree
Hide file tree
Showing 22 changed files with 533 additions and 309 deletions.
12 changes: 12 additions & 0 deletions LionHeart-iOS/LionHeart-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
B520EA6C2A670C010027356E /* progressbar_2m.json in Resources */ = {isa = PBXBuildFile; fileRef = B520EA632A670C010027356E /* progressbar_2m.json */; };
B520EA6D2A670C010027356E /* progressbar_3m.json in Resources */ = {isa = PBXBuildFile; fileRef = B520EA642A670C010027356E /* progressbar_3m.json */; };
B520EA6E2A670C010027356E /* progressbar_5m.json in Resources */ = {isa = PBXBuildFile; fileRef = B520EA652A670C010027356E /* progressbar_5m.json */; };
B52C6BB42AF8AE1D008E3B99 /* Combine+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52C6BB32AF8AE1D008E3B99 /* Combine+.swift */; };
B532E8322A5525C600F0DB19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B532E8312A5525C600F0DB19 /* AppDelegate.swift */; };
B532E8342A5525C600F0DB19 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B532E8332A5525C600F0DB19 /* SceneDelegate.swift */; };
B532E83B2A5525C700F0DB19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B532E83A2A5525C700F0DB19 /* Assets.xcassets */; };
Expand Down Expand Up @@ -68,6 +69,8 @@
B57BEB6C2A6149AD00D1727C /* NetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57BEB6B2A6149AD00D1727C /* NetworkRequest.swift */; };
B57BEB6E2A6275D600D1727C /* Serviceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57BEB6D2A6275D600D1727C /* Serviceable.swift */; };
B57BEB702A6275F500D1727C /* ViewControllerServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57BEB6F2A6275F500D1727C /* ViewControllerServiceable.swift */; };
B58095022AF354C700FFCE1B /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58095012AF354C700FFCE1B /* LoginViewModel.swift */; };
B58095042AF354EB00FFCE1B /* LoginViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58095032AF354EB00FFCE1B /* LoginViewModelImpl.swift */; };
B598926F2A56C21800CE1FEB /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = B598926E2A56C21800CE1FEB /* FirebaseAnalytics */; };
B59892712A56C21800CE1FEB /* FirebaseAnalyticsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B59892702A56C21800CE1FEB /* FirebaseAnalyticsSwift */; };
B59892732A56C21800CE1FEB /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = B59892722A56C21800CE1FEB /* FirebaseCrashlytics */; };
Expand Down Expand Up @@ -368,6 +371,7 @@
B520EA632A670C010027356E /* progressbar_2m.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = progressbar_2m.json; sourceTree = "<group>"; };
B520EA642A670C010027356E /* progressbar_3m.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = progressbar_3m.json; sourceTree = "<group>"; };
B520EA652A670C010027356E /* progressbar_5m.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = progressbar_5m.json; sourceTree = "<group>"; };
B52C6BB32AF8AE1D008E3B99 /* Combine+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Combine+.swift"; sourceTree = "<group>"; };
B532E82E2A5525C600F0DB19 /* LionHeart-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LionHeart-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
B532E8312A5525C600F0DB19 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B532E8332A5525C600F0DB19 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -401,6 +405,8 @@
B57BEB6B2A6149AD00D1727C /* NetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequest.swift; sourceTree = "<group>"; };
B57BEB6D2A6275D600D1727C /* Serviceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Serviceable.swift; sourceTree = "<group>"; };
B57BEB6F2A6275F500D1727C /* ViewControllerServiceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerServiceable.swift; sourceTree = "<group>"; };
B58095012AF354C700FFCE1B /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
B58095032AF354EB00FFCE1B /* LoginViewModelImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelImpl.swift; sourceTree = "<group>"; };
B58E8CBD2A6AE0F200FF5202 /* LionHeart-iOSRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "LionHeart-iOSRelease.entitlements"; sourceTree = "<group>"; };
B59892802A56C3F900CE1FEB /* LionHeart-iOSDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "LionHeart-iOSDebug.entitlements"; sourceTree = "<group>"; };
B59892822A56C93400CE1FEB /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -907,6 +913,7 @@
4A2050142A5DCD1900C7AF3C /* UICollectionView+.swift */,
D34280762A66B90C00DA1499 /* UILabelPadding.swift */,
4A81C2902ACC7DC80056E815 /* UICollectionViewCell+.swift */,
B52C6BB32AF8AE1D008E3B99 /* Combine+.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -996,6 +1003,8 @@
isa = PBXGroup;
children = (
C0DF037C2A5A9CFD0037F740 /* LoginViewController.swift */,
B58095012AF354C700FFCE1B /* LoginViewModel.swift */,
B58095032AF354EB00FFCE1B /* LoginViewModelImpl.swift */,
);
path = Login;
sourceTree = "<group>";
Expand Down Expand Up @@ -2157,12 +2166,15 @@
4A860AD62A6265B2002BA428 /* BookmarkModel.swift in Sources */,
B5C6A2BE2A5DE6590021BE5E /* GeneralTitleTableViewCell.swift in Sources */,
D3AB54C12A62CE3F0017BF53 /* ChallengeDayCheckCollectionViewCollectionViewCell.swift in Sources */,
B58095042AF354EB00FFCE1B /* LoginViewModelImpl.swift in Sources */,
B58095022AF354C700FFCE1B /* LoginViewModel.swift in Sources */,
B5C6A2B22A5DB0B10021BE5E /* ArticleDetailTableView.swift in Sources */,
C003CC4F2ADA4F8D00AFFAAC /* OnboardingNavigation.swift in Sources */,
C034EE202ADE450600AD6FF3 /* ArticleCategoryCoordinator.swift in Sources */,
B59892E12A5AF39300CE1FEB /* LHNavigationBarView.swift in Sources */,
C0F029E62A5FB9E000E0D185 /* ContainerView.swift in Sources */,
C0856B712ABFC0FB0026D9F8 /* ChallengeManagerImpl.swift in Sources */,
B52C6BB42AF8AE1D008E3B99 /* Combine+.swift in Sources */,
B53F4EE72ADBC29F001C5752 /* CurriculumFactoryImpl.swift in Sources */,
C0DF03672A5A9C680037F740 /* ArticleDetailViewController.swift in Sources */,
C003CC392ADA4D9600AFFAAC /* PopNavigation.swift in Sources */,
Expand Down
129 changes: 129 additions & 0 deletions LionHeart-iOS/LionHeart-iOS/Global/Extensions/Combine+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// Combine+.swift
// LionHeart-iOS
//
// Created by 김민재 on 11/6/23.
//

import Combine
import UIKit


extension UIControl {
func controlPublisher(for event: UIControl.Event) -> UIControl.EventPublisher {
return UIControl.EventPublisher(control: self, event: event)
}

// Publisher
struct EventPublisher: Publisher {
typealias Output = UIControl
typealias Failure = Never

let control: UIControl
let event: UIControl.Event

func receive<S>(subscriber: S)
where S: Subscriber, Never == S.Failure, UIControl == S.Input {
let subscription = EventSubscription(
control: control,
subscriber: subscriber,
event: event
)
subscriber.receive(subscription: subscription)
}
}

// Subscription
fileprivate class EventSubscription<EventSubscriber: Subscriber>: Subscription
where EventSubscriber.Input == UIControl, EventSubscriber.Failure == Never {

let control: UIControl
let event: UIControl.Event
var subscriber: EventSubscriber?

init(control: UIControl, subscriber: EventSubscriber, event: UIControl.Event) {
self.control = control
self.subscriber = subscriber
self.event = event

control.addTarget(self, action: #selector(eventDidOccur), for: event)
}

func request(_ demand: Subscribers.Demand) {}

func cancel() {
subscriber = nil
control.removeTarget(self, action: #selector(eventDidOccur), for: event)
}

@objc func eventDidOccur() {
_ = subscriber?.receive(control)
}
}
}

extension UITextField {
var textPublisher: AnyPublisher<String, Never> {
controlPublisher(for: .editingChanged)
.map { $0 as! UITextField }
.map { $0.text! }
.eraseToAnyPublisher()
}
}

extension UIButton {
var tapPublisher: AnyPublisher<Void, Never> {
controlPublisher(for: .touchUpInside)
.map { _ in }
.eraseToAnyPublisher()
}
}


extension Publisher {

func tryAwaitMap<T>(_ transform: @escaping (Self.Output) async throws -> T) -> Publishers.FlatMap<Future<T, NetworkError>, Self> {
flatMap { value in
Future { promise in
Task {
do {
let result = try await transform(value)
promise(.success(result))
}
catch {
promise(.failure(error as! NetworkError))
}
}
}
}
}

func task<T>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) async -> T)
-> Publishers.FlatMap<Deferred<Future<T, Never>>, Self> {
flatMap(maxPublishers: maxPublishers) { value in
Deferred {
Future { promise in
Task {
let output = await transform(value)
promise(.success(output))
}
}
}
}
}

func errorTask<T>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) async throws -> T)
-> Publishers.FlatMap<Deferred<Future<T, Never>>, Self> {
flatMap(maxPublishers: maxPublishers) { value in
Deferred {
Future { promise in
Task {
let output = try await transform(value)
promise(.success(output))
}
}
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import UIKit


/// 네트워크 요청을 하는 ViewController가 채택하는 프로토콜
protocol ViewControllerServiceable where Self: UIViewController {
protocol ViewControllerServiceable {

/// 네트워크 통신을 do-catch문으로 감싸고 catch문에서 호출하는 메서드
/// - Parameter error: 네트워크 통신 중 throw된 에러
Expand All @@ -18,25 +18,31 @@ protocol ViewControllerServiceable where Self: UIViewController {
func hideLoading()
}

extension ViewControllerServiceable {
extension UIViewController: ViewControllerServiceable {}

extension ViewControllerServiceable where Self: UIViewController {

func handleError(_ error: NetworkError) {
print(error)
}

func showLoading() {
if let loadingView = getLoadingView() {
loadingView.startAnimating()
return
}
let loadingView = LHLoadingView()
loadingView.startAnimating()

view.addSubview(loadingView)
loadingView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
// if let loadingView = getLoadingView() {
// loadingView.startAnimating()
// return
// }
// let loadingView = LHLoadingView()
// loadingView.startAnimating()
//
// view.addSubview(loadingView)
// loadingView.snp.makeConstraints { make in
// make.edges.equalToSuperview()
// }
}

func hideLoading() {
getLoadingView()?.stopAnimating()
getLoadingView()?.removeFromSuperview()
// getLoadingView()?.stopAnimating()
// getLoadingView()?.removeFromSuperview()
}

private func getLoadingView() -> LHLoadingView? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class LHToast {
window.subviews
.filter { $0 is LHToastView }
.forEach { $0.removeFromSuperview() }
window.addSubview(toastView)
// window.addSubview(toastView)

toastView.snp.makeConstraints { make in
if isTabBar {
Expand Down
56 changes: 28 additions & 28 deletions LionHeart-iOS/LionHeart-iOS/Global/Utils/LoadingIndicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@
import UIKit


final class LoadingIndicator {
static func showLoading() {
DispatchQueue.main.async {
guard let window = UIApplication.keyWindow else { return }

let loadingIndicatorView = UIActivityIndicatorView(style: .large)
loadingIndicatorView.color = .designSystem(.lionRed)
loadingIndicatorView.backgroundColor = .designSystem(.black)

loadingIndicatorView.frame = window.frame
window.addSubview(loadingIndicatorView)

loadingIndicatorView.startAnimating()
}
}

static func hideLoading() {
DispatchQueue.main.async {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
guard let window = windowScene?.windows.last else { return }
window.subviews
.filter { $0 is UIActivityIndicatorView }
.forEach { $0.removeFromSuperview() }
}

}
}
//final class LoadingIndicator {
// static func showLoading() {
// DispatchQueue.main.async {
// guard let window = UIApplication.keyWindow else { return }
//
// let loadingIndicatorView = UIActivityIndicatorView(style: .large)
// loadingIndicatorView.color = .designSystem(.lionRed)
// loadingIndicatorView.backgroundColor = .designSystem(.black)
//
// loadingIndicatorView.frame = window.frame
// window.addSubview(loadingIndicatorView)
//
// loadingIndicatorView.startAnimating()
// }
// }
//
// static func hideLoading() {
// DispatchQueue.main.async {
// let scenes = UIApplication.shared.connectedScenes
// let windowScene = scenes.first as? UIWindowScene
// guard let window = windowScene?.windows.last else { return }
// window.subviews
// .filter { $0 is UIActivityIndicatorView }
// .forEach { $0.removeFromSuperview() }
// }
//
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,18 @@ extension ArticleDetailViewController {
}
}

extension ArticleDetailViewController: ViewControllerServiceable {
func handleError(_ error: NetworkError) {
switch error {
case .unAuthorizedError:
adaptor.checkTokenIsExpired()
case .clientError(_, let message):
LHToast.show(message: "\(message)")
default:
LHToast.show(message: error.description)
}
}
}
//extension ArticleDetailViewController: ViewControllerServiceable {
// func handleError(_ error: NetworkError) {
// switch error {
// case .unAuthorizedError:
// adaptor.checkTokenIsExpired()
// case .clientError(_, let message):
// LHToast.show(message: "\(message)")
// default:
// LHToast.show(message: error.description)
// }
// }
//}

// MARK: - UI & Layout

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,26 +100,26 @@ private extension ArticleListByCategoryViewController {
}
}

extension ArticleListByCategoryViewController: ViewControllerServiceable {
func handleError(_ error: NetworkError) {
switch error {
case .urlEncodingError:
LHToast.show(message: "URL Error")
case .jsonDecodingError:
LHToast.show(message: "Decoding Error")
case .badCasting:
LHToast.show(message: "Bad Casting")
case .fetchImageError:
LHToast.show(message: "Image Error")
case .unAuthorizedError:
navigator.checkTokenIsExpired()
case .clientError(_, let message):
LHToast.show(message: message)
case .serverError:
LHToast.show(message: error.description)
}
}
}
//extension ArticleListByCategoryViewController: ViewControllerServiceable {
// func handleError(_ error: NetworkError) {
// switch error {
// case .urlEncodingError:
// LHToast.show(message: "URL Error")
// case .jsonDecodingError:
// LHToast.show(message: "Decoding Error")
// case .badCasting:
// LHToast.show(message: "Bad Casting")
// case .fetchImageError:
// LHToast.show(message: "Image Error")
// case .unAuthorizedError:
// navigator.checkTokenIsExpired()
// case .clientError(_, let message):
// LHToast.show(message: message)
// case .serverError:
// LHToast.show(message: error.description)
// }
// }
//}

extension ArticleListByCategoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
Expand Down
Loading

0 comments on commit 8b19e3a

Please sign in to comment.