Skip to content

Commit

Permalink
Refactoring Controller's logic transform each ViewModel in ProfileCon…
Browse files Browse the repository at this point in the history
…troller, ProfileHeader, ProfileHeaderViewModel, ProfhieViewModel
  • Loading branch information
SHcommit committed Dec 4, 2022
1 parent 2aded75 commit fe69635
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 85 deletions.
26 changes: 16 additions & 10 deletions Clone App/Instagram/Instagram/Controller/ProfileController.swift
Expand Up @@ -47,20 +47,25 @@ extension ProfileController {
}

func setupBindings() {
vm.$user.sink { _ in
vm.$user
.receive(on: RunLoop.main)
.sink { _ in
self.collectionView.reloadData()
}.store(in:&subscriptions)
}.store(in:&subscriptions)

vm.$userStats.sink{ _ in
self.collectionView.reloadData()
}.store(in: &subscriptions)
vm.$userStats
.receive(on: RunLoop.main)
.sink{ _ in
self.collectionView.reloadData()
}.store(in: &subscriptions)

vm.$profileImage.sink{ _ in
vm.$profileImage
.receive(on: RunLoop.main)
.sink{ _ in
self.collectionView.reloadData()
}.store(in: &subscriptions)

}

}

//MARK: - UICollectionViewDataSource
Expand All @@ -77,13 +82,14 @@ extension ProfileController {
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: COLLECTIONHEADERREUSEABLEID, for: indexPath) as? ProfileHeader else { fatalError() }
guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: COLLECTIONHEADERREUSEABLEID, for: indexPath) as? ProfileHeader else { fatalError() }
headerView.delegate = self
headerView.userVM = ProfileHeaderViewModel(user: self.vm.user, profileImage: self.vm.profileImage, userStats: vm.userStats )
headerView.vm = ProfileHeaderViewModel(user: vm.user)
headerView.vm?.profileImage = vm.profileImage
headerView.vm?.userStats = vm.userStats
return headerView
}


}

//MARK: - UICollectionViewDelegateFlowLayout
Expand Down
61 changes: 36 additions & 25 deletions Clone App/Instagram/Instagram/View/Profile/ProfileHeader.swift
Expand Up @@ -6,6 +6,7 @@
//

import UIKit
import Combine
import Firebase
import FirebaseFirestore
import FirebaseFirestoreSwift
Expand All @@ -29,50 +30,60 @@ class ProfileHeader: UICollectionReusableView {
private lazy var gridBtn: UIButton = initialGridBtn()
private lazy var listBtn: UIButton = initialListBtn()
private lazy var bookMarkBtn: UIButton = initialBookMarkBtn()

weak var delegate: ProfileHeaderDelegate?
var userVM: ProfileHeaderViewModel? {
var vm: ProfileHeaderViewModel? {
didSet {
configure()
setupBindings()
}
}
var subscriptions = Set<AnyCancellable>()

//MARK: - LifeCycle
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setupSubview()

}

required init?(coder: NSCoder) {
fatalError("init(coder:) not implement")
}


}

//MARK: - Helpers
extension ProfileHeader {

func configure() {
guard let userVM = userVM else { return }
nameLabel.text = userVM.username()
editProfileFollowButton.setTitle(userVM.followButtonText(), for: .normal)
editProfileFollowButton.setTitleColor(userVM.followButtonTextColor(), for: .normal)
editProfileFollowButton.backgroundColor = userVM.followButtonBackgroundColor()
postLabel.attributedText = userVM.numberOfPosts()
followersLabel.attributedText = userVM.numberOfFollowers()
followingLabel.attributedText = userVM.numberOfFollowing()
if userVM.image() == nil {
Task() {
await userVM.fetchImage()
DispatchQueue.main.async {
self.profileIV.image = userVM.image()
}
}
}else {
profileIV.image = userVM.image()
}
func setupBindings() {
vm?.$user
.receive(on: RunLoop.main)
.sink { [unowned self] _ in
configureUI()
}.store(in: &subscriptions)
vm?.$profileImage
.receive(on: RunLoop.main)
.sink { [unowned self] image in
profileIV.image = image
}.store(in: &subscriptions)
vm?.$userStats
.receive(on: RunLoop.main)
.sink{ [unowned self] stats in
followersLabel.attributedText = vm?.numberOfFollowers()
followingLabel.attributedText = vm?.numberOfFollowing()
}.store(in: &subscriptions)
}

func configureUI() {
guard let vm = vm else { return }
nameLabel.text = vm.username()
editProfileFollowButton.setTitle(vm.followButtonText(), for: .normal)
editProfileFollowButton.setTitleColor(vm.followButtonTextColor(), for: .normal)
editProfileFollowButton.backgroundColor = vm.followButtonBackgroundColor()
postLabel.attributedText = vm.numberOfPosts()
followersLabel.attributedText = vm.numberOfFollowers()
followingLabel.attributedText = vm.numberOfFollowing()
}

}
Expand All @@ -81,8 +92,8 @@ extension ProfileHeader {
extension ProfileHeader {

@objc func didTapEditProfileFollow(_ sender: Any) {
guard let userVM = userVM else { return }
delegate?.header(self, didTapActionButtonFor: userVM.getUserInfo())
guard let vm = vm else { return }
delegate?.header(self, didTapActionButtonFor: vm.getUserInfo())
}

@objc func didTapGridBtn(_ sender: Any) {
Expand Down
Expand Up @@ -11,22 +11,15 @@ import FirebaseFirestore
class ProfileHeaderViewModel {

//MARK: - Properties
private var user: UserInfoModel
private var profileImage : UIImage?
private var userStats: Userstats?
@Published var user: UserInfoModel
@Published var profileImage : UIImage?
@Published var userStats: Userstats?

//MARK: - LifeCycle
init(user: UserInfoModel, profileImage image: UIImage? = nil, userStats: Userstats? = nil) {
self.user = user
self.userStats = userStats
if image == nil {
Task() {
await fetchImage()
}
}else {
profileImage = image
}

self.profileImage = profileImage
}

func initProfileImage(image: UIImage?) {
Expand Down Expand Up @@ -95,32 +88,3 @@ extension ProfileHeaderViewModel {
}

}

//MARK: - API
extension ProfileHeaderViewModel {
func fetchImage() async {
do {
try await fetchProfileFromImageService()
} catch {
fetchImageErrorHandling(error: error)
}
}

func fetchProfileFromImageService() async throws {
let image = try await UserProfileImageService.fetchUserProfile(userProfile: profileURL())
DispatchQueue.main.async {
self.profileImage = image
}
}

func fetchImageErrorHandling(error: Error) {
switch error {
case FetchUserError.invalidUserProfileImage :
print("DEBUG: Failure invalid user profile image instance")
default:
print("DEBUG: Unexpected error occured :\(error.localizedDescription)")
}

}

}
26 changes: 16 additions & 10 deletions Clone App/Instagram/Instagram/ViewModel/ProfileViewModel.swift
Expand Up @@ -7,11 +7,19 @@

import UIKit
import Combine
/**
일단 profileImage경우 메인 홈 컨트롤러에서 비동기적으로 업데이트했는데 VM이 하도록 했다.
그리고 Controller에선 setupBindings를 통해 profileImage값이 바뀌면 sink로 현재 label등이 바뀌도록할것이다.
searchViewController에서 tableView(didSelectedRowAt)요고더 profileVC랑 연관있음.
*/

protocol ProfileViewModelType {
func fetchData()
}

protocol ProfileViewModelAPIType {
func fetchToCheckIfUserIsFollowed()
func fetchUserStats()
func fetchImage(profileUrl url: String) async
func fetchImageFromUserProfileImageService(url: String) async throws
func fetchImageErrorHandling(withError error: Error)
}

class ProfileViewModel {
//MARK: - Properties
@Published var user: UserInfoModel
Expand All @@ -26,7 +34,7 @@ class ProfileViewModel {
}

//MARK: - Helpers
extension ProfileViewModel {
extension ProfileViewModel: ProfileViewModelType {
func fetchData() {
fetchToCheckIfUserIsFollowed()
fetchUserStats()
Expand All @@ -37,7 +45,7 @@ extension ProfileViewModel {
}

//MARK: - API
extension ProfileViewModel {
extension ProfileViewModel: ProfileViewModelAPIType {
func fetchToCheckIfUserIsFollowed() {
UserService.checkIfUserIsFollowd(uid: user.uid) { [unowned self] isFollowed in
user.isFollowed = isFollowed
Expand All @@ -60,9 +68,7 @@ extension ProfileViewModel {

func fetchImageFromUserProfileImageService(url: String) async throws {
let image = try await UserProfileImageService.fetchUserProfile(userProfile: url)
DispatchQueue.main.async {
self.profileImage = image
}
profileImage = image
}

func fetchImageErrorHandling(withError error: Error) {
Expand Down

0 comments on commit fe69635

Please sign in to comment.