Build Instagram App: Part 5 (Swift 5) - 2020 - Xcode 11 - iOS Development
private func setNavigationBar() {
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "gear")?.withTintColor(UIColor.black, renderingMode: .alwaysOriginal), style: .done, target: self, action: #selector(settingButtonDidTap))
}
@objc private func settingButtonDidTap() {
let vc = SettingsViewController()
vc.title = "Settings"
navigationController?.pushViewController(vc, animated: true)
}
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)
}
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()
}
}
one-shot subscription
을 통해 뷰 모델에서 가지고 있는 인증 매니저의 로그아웃 함수 호출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
}
}
}
result
에 따라 풀 모달 형태로 기존의 로그인 뷰를 올릴지 결정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()
}
}
.store(in: &cancellabels)
return output.eraseToAnyPublisher()
}
import Foundation
import Combine
class SettingsViewModel {
enum Input {
case logoutDidTap
}
enum Output {
case isLogoutDidSucceed(result: Bool)
}
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([["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()
}
}
.store(in: &cancellabels)
return output.eraseToAnyPublisher()
}
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()
}
}
}
import UIKit
import Combine
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 .isLogoutDidSucceed(result: let isSucceeded):
self?.handleLogout(result: isSucceeded)
}
}
.store(in: &cancellables)
}
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]
if model == "Log Out" {
logoutButtonDidTap()
}
}
}
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
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
}
}
Diffable Data Source
를 통해 구현하지 않아도 된다는 판단