Build Instagram App: Part 14 (Swift 5) - 2020 - Xcode 11 - iOS Development
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let x = indexPath.section
let model: HomeFeedRenderViewModel
if x == 0 {
model = viewModel.feedRenderModels[0]
} else {
let position = x % 4 == 0 ? x / 4 : ((x - (x % 4)) / 4)
model = viewModel.feedRenderModels[position]
}
let subSection = x % 4
switch subSection {
case 0:
//header
let headerModel = model.header
switch headerModel.renderType {
case .header(provider: let user):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostHeaderTableViewCell.identifier, for: indexPath) as? IGFeedPostHeaderTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
case 1:
// post
let postModel = model.post
switch postModel.renderType {
case .primaryContent(providier: let post):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostTableViewCell.identifier, for: indexPath) as? IGFeedPostTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
case 2:
let actionModel = model.actions
switch actionModel.renderType {
case .actions(provider: let action):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostActionsTableViewCell.identifier, for: indexPath) as? IGFeedPostActionsTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
// actions
case 3:
let commentModel = model.comments
// comments
switch commentModel.renderType {
case .comments(comments: let comments):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostGeneralTableViewCell.identifier, for: indexPath) as? IGFeedPostGeneralTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
default: return UITableViewCell()
}
}
import UIKit
import Combine
class HomeViewController: UIViewController {
private let input: PassthroughSubject<HomeViewModel.Input, Never> = .init()
private let viewModel = HomeViewModel()
private var cancellables = Set<AnyCancellable>()
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(IGFeedPostTableViewCell.self, forCellReuseIdentifier: IGFeedPostTableViewCell.identifier)
tableView.register(IGFeedPostHeaderTableViewCell.self, forCellReuseIdentifier: IGFeedPostHeaderTableViewCell.identifier)
tableView.register(IGFeedPostGeneralTableViewCell.self, forCellReuseIdentifier: IGFeedPostGeneralTableViewCell.identifier)
tableView.register(IGFeedPostActionsTableViewCell.self, forCellReuseIdentifier: IGFeedPostActionsTableViewCell.identifier)
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
setUI()
bind()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
input.send(.isAlreadyLogin)
}
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 .loginOutput(result: let result):
if !result {
self?.handleNotLogined()
}
}
}
.store(in: &cancellables)
}
private func handleNotLogined() {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: true)
}
}
extension HomeViewController: UITableViewDelegate {
}
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let x = section
let model: HomeFeedRenderViewModel
if x == 0 {
model = viewModel.feedRenderModels[0]
} else {
let position = x % 4 == 0 ? x / 4 : ((x - (x % 4)) / 4)
model = viewModel.feedRenderModels[position]
}
let subSection = x % 4
switch subSection {
case 0:
//header
return 1
case 1:
// post
return 1
case 2:
return 1
// actions
case 3:
let commentsModel = model.comments
switch commentsModel.renderType {
case .comments(comments: let comments):
return comments.count > 2 ? 2 : comments.count
default: return 0
}
// comments
default: return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let x = indexPath.section
let model: HomeFeedRenderViewModel
if x == 0 {
model = viewModel.feedRenderModels[0]
} else {
let position = x % 4 == 0 ? x / 4 : ((x - (x % 4)) / 4)
model = viewModel.feedRenderModels[position]
}
let subSection = x % 4
switch subSection {
case 0:
//header
let headerModel = model.header
switch headerModel.renderType {
case .header(provider: let user):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostHeaderTableViewCell.identifier, for: indexPath) as? IGFeedPostHeaderTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
case 1:
// post
let postModel = model.post
switch postModel.renderType {
case .primaryContent(providier: let post):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostTableViewCell.identifier, for: indexPath) as? IGFeedPostTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
case 2:
let actionModel = model.actions
switch actionModel.renderType {
case .actions(provider: let action):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostActionsTableViewCell.identifier, for: indexPath) as? IGFeedPostActionsTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
// actions
case 3:
let commentModel = model.comments
// comments
switch commentModel.renderType {
case .comments(comments: let comments):
guard let cell = tableView.dequeueReusableCell(withIdentifier: IGFeedPostGeneralTableViewCell.identifier, for: indexPath) as? IGFeedPostGeneralTableViewCell else { return UITableViewCell() }
return cell
default: return UITableViewCell()
}
default: return UITableViewCell()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.feedRenderModels.count * 4
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let subSection = indexPath.section % 4
switch subSection {
case 0: return 70
case 1: return tableView.width
case 2: return 60
case 3: return 50
default: return .zero
}
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let subSection = section % 4
return subSection == 3 ? 70 : 0
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
}
import Foundation
import Combine
struct HomeFeedRenderViewModel {
let header: PostRenderViewModel
let post: PostRenderViewModel
let actions: PostRenderViewModel
let comments: PostRenderViewModel
}
class HomeViewModel {
enum Input {
case isAlreadyLogin
}
enum Output {
case loginOutput(result: Bool)
}
var feedRenderModels: [HomeFeedRenderViewModel]
private let authManager = AuthManager.shared
private let output: PassthroughSubject<Output, Never> = .init()
private var cancellables = Set<AnyCancellable>()
init() {
let counts = UserCountModel(follwers: 100, following: 100, posts: 100)
let photoURL = URL(string: "https://i.stack.imgur.com/GsDIl.jpg")!
var mockModel = [HomeFeedRenderViewModel]()
for x in 0..<5 {
let user = UserModel(email: "email" + "\(x)", bio: "bio", firstName: "firstName", lastName: "lastName", birthDate: Date(), counts: counts, gender: .male, joinDate: Date(), profilePhoto: photoURL)
let post = UserPostModel(identifier: "post \(x)", postType: .photo, postURL: photoURL, thumnailImage: photoURL, caption: "Caption", comments: [], likeCount: [], createdDate: Date(), taggedUers: [user], owner: user)
let comment = PostCommentModel(userName: user.firstName + user.lastName, text: "Comment_\(x)", createdDate: Date(), identifier: user.email + Date().formatted())
let viewModel = HomeFeedRenderViewModel(header: PostRenderViewModel(renderType: .header(provider: user)), post: PostRenderViewModel(renderType: .primaryContent(providier: post)), actions: PostRenderViewModel(renderType: .actions(provider: "action_\(x)")), comments: PostRenderViewModel(renderType: .comments(comments: [comment])))
mockModel.append(viewModel)
}
self.feedRenderModels = mockModel
}
func transform(input: AnyPublisher<Input, Never>) -> AnyPublisher<Output, Never> {
input
.receive(on: DispatchQueue.global(qos: .default))
.sink { [weak self] result in
switch result {
case .isAlreadyLogin: self?.isAlreadyLogin()
}
}
.store(in: &cancellables)
return output.eraseToAnyPublisher()
}
private func isAlreadyLogin() {
if authManager.isSignedIn() {
output.send(.loginOutput(result: true))
} else {
output.send(.loginOutput(result: false))
}
}
}