Build Instagram App: Part 6 (Swift 5) - 2020 - Xcode 11 - iOS Development
private func bind() {
let output = viewModel.transform(input: input.eraseToAnyPublisher())
output
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
switch result {
case .showEditProfileView:
self?.showEditProfileView()
case .isLogoutDidSucceed(result: let isSucceeded):
self?.handleLogout(result: isSucceeded)
case .openURL(url: let url):
self?.handleURL(with: url)
}
}
.store(in: &cancellables)
}
sink
를 통해 내려받은 아웃풋의 종류에 따라 어떤 인터렉션을 보여줄지 결정private func handleURL(with url: URL) {
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
private func handleURLString(with urlString: String) {
guard let url = URL(string: urlString) else { return }
output.send(.openURL(url: url))
}
import UIKit
import Combine
import SafariServices
class SettingsViewController: UIViewController {
private let tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
return tableView
}()
private let input: PassthroughSubject<SettingsViewModel.Input, Never> = .init()
private let viewModel = SettingsViewModel()
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setUI()
bind()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
private func setUI() {
view.backgroundColor = .systemBackground
view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
}
private func bind() {
let output = viewModel.transform(input: input.eraseToAnyPublisher())
output
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
switch result {
case .showEditProfileView:
self?.showEditProfileView()
case .isLogoutDidSucceed(result: let isSucceeded):
self?.handleLogout(result: isSucceeded)
case .openURL(url: let url):
self?.handleURL(with: url)
}
}
.store(in: &cancellables)
}
private func showEditProfileView() {
let vc = EditProfileViewController()
let navVC = UINavigationController(rootViewController: vc)
present(navVC, animated: true)
}
private func handleURL(with url: URL) {
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
private func handleLogout(result: Bool) {
if result {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: true) {
self.navigationController?.popToRootViewController(animated: false)
self.tabBarController?.selectedIndex = 0
}
}
}
private func logoutButtonDidTap() {
let actionSheet = UIAlertController(title: "Log Out", message: "Are you sure you want to log out?", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
actionSheet.addAction(UIAlertAction(title: "Log Out", style: .destructive, handler: { [weak self] _ in
self?.input.send(.logoutDidTap)
}))
actionSheet.popoverPresentationController?.sourceView = tableView
actionSheet.popoverPresentationController?.sourceRect = tableView.bounds
present(actionSheet, animated: true)
}
}
extension SettingsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let model = viewModel.settingsModel.value[indexPath.section][indexPath.row]
switch model {
case "Log Out": logoutButtonDidTap()
case "Edit Profile": input.send(.editProfileDidTap)
case "Invite Friends": input.send(.inviteFriendDidTap)
case "Save Original Posts": input.send(.saveOriginalPostsDidTap)
case "Terms of Service": input.send(.termsOfServiceDidTap)
case "Privacy Policy": input.send(.privacyPolicyDidTap)
case "Help / Feedback": input.send(.helpAndFeedbackDidTap)
default: break
}
}
}
extension SettingsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
let model = viewModel.settingsModel.value[indexPath.section][indexPath.row]
cell.textLabel?.text = model
cell.accessoryType = .disclosureIndicator
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.settingsModel.value.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.settingsModel.value[section].count
}
}
import Foundation
import Combine
class SettingsViewModel {
enum Input {
case logoutDidTap
case editProfileDidTap
case inviteFriendDidTap
case saveOriginalPostsDidTap
case termsOfServiceDidTap
case privacyPolicyDidTap
case helpAndFeedbackDidTap
}
enum Output {
case openURL(url: URL)
case isLogoutDidSucceed(result: Bool)
case showEditProfileView
}
private let output: PassthroughSubject<Output, Never> = .init()
let settingsModel: CurrentValueSubject<[[String]], Never> = .init([])
private var cancellabels = Set<AnyCancellable>()
private let authManager = AuthManager.shared
init() {
addSubscription()
}
private func addSubscription() {
settingsModel.send([["Edit Profile", "Invite Friends", "Save Original Posts"], ["Terms of Service", "Privacy Policy", "Help / Feedback"], ["Log Out"]])
}
func transform(input: AnyPublisher<Input, Never>) -> AnyPublisher<Output, Never> {
input
.receive(on: DispatchQueue.global(qos: .default))
.sink { [weak self] result in
switch result {
case .logoutDidTap: self?.handleLogout()
case .editProfileDidTap:
self?.output.send(.showEditProfileView)
case .inviteFriendDidTap:
break
case .saveOriginalPostsDidTap:
break
case .termsOfServiceDidTap:
self?.handleURLString(with: "https://help.instagram.com/581066165581870")
case .privacyPolicyDidTap:
self?.handleURLString(with: "https://help.instagram.com/155833707900388")
case .helpAndFeedbackDidTap:
self?.handleURLString(with: "https://help.instagram.com")
}
}
.store(in: &cancellabels)
return output.eraseToAnyPublisher()
}
private func handleURLString(with urlString: String) {
guard let url = URL(string: urlString) else { return }
output.send(.openURL(url: url))
}
private func handleLogout() {
var logoutSubscription: AnyCancellable?
logoutSubscription = authManager
.logout()
.sink { [weak self] completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
self?.output.send(.isLogoutDidSucceed(result: false))
logoutSubscription?.cancel()
case .finished: break
}
} receiveValue: { [weak self] isSucceeded in
self?.output.send(.isLogoutDidSucceed(result: isSucceeded))
logoutSubscription?.cancel()
}
}
}