Build Instagram App: Part 11 (Swift 5) - 2020 - Xcode 11 - iOS Development
extension ListViewController {
private func bind(in output: AnyPublisher<UserFollowTableViewCell.Output, Never>) {
output
.sink { [weak self] result in
switch result {
case .followButtonDidTap(model: let model):
print("\(model.name) is currently \(model.type == .following ? "following" : "unfollowing")")
}
}
.store(in: &cancellables)
}
}
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
profileImageView.image = nil
userNameLabel.text = nil
followButton.setTitle(nil, for: .normal)
followButton.backgroundColor = nil
followButton.layer.borderWidth = 0
output.send(completion: .finished)
}
finished
를 통해 해당 퍼블리셔 해제 func configure(with model: UserRelationshipModel) {
output = .init()
userModel = model
nameLabel.text = model.name
userNameLabel.text = model.userName
followingStatePublisher.send(model.type)
}
private func bind() {
followingStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] type in
self?.toggleButton(type: type)
}
.store(in: &cancellables)
followButton
.tapPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let currentUserModel = self?.userModel else { return }
if let currentState = self?.followingStatePublisher.value {
if currentState == .following {
self?.followingStatePublisher.send(.not_following)
let newUserModel = UserRelationshipModel(userName: currentUserModel.userName, name: currentUserModel.name, type: .not_following)
self?.output.send(.followButtonDidTap(model: newUserModel))
} else {
self?.followingStatePublisher.send(.following)
let newUserModel = UserRelationshipModel(userName: currentUserModel.userName, name: currentUserModel.name, type: .following)
self?.output.send(.followButtonDidTap(model: newUserModel))
}
}
}
.store(in: &cancellables)
}
import UIKit
import Combine
class ListViewController: UIViewController {
private let viewModel: ListViewModel
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(UserFollowTableViewCell.self, forCellReuseIdentifier: UserFollowTableViewCell.identifier)
return tableView
}()
private var cancellables = Set<AnyCancellable>()
init(data: [UserRelationshipModel]) {
self.viewModel = ListViewModel(listData: data)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
private func setUI() {
view.backgroundColor = .systemBackground
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
}
extension ListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.listData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: UserFollowTableViewCell.identifier, for: indexPath) as? UserFollowTableViewCell else { return UITableViewCell() }
let model = viewModel.listData[indexPath.row]
cell.configure(with: model)
let output = cell.transform()
bind(in: output)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75
}
}
extension ListViewController {
private func bind(in output: AnyPublisher<UserFollowTableViewCell.Output, Never>) {
output
.sink { [weak self] result in
switch result {
case .followButtonDidTap(model: let model):
print("\(model.name) is currently \(model.type == .following ? "following" : "unfollowing")")
}
}
.store(in: &cancellables)
}
}
extension ListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
import UIKit
import Combine
class UserFollowTableViewCell: UITableViewCell {
enum Output {
case followButtonDidTap(model: UserRelationshipModel)
}
static let identifier = "UserFollowTableViewCell"
private var cancellables = Set<AnyCancellable>()
private let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.layer.masksToBounds = true
imageView.backgroundColor = .secondarySystemBackground
imageView.contentMode = .scaleAspectFill
return imageView
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.font = .systemFont(ofSize: 17, weight: .semibold)
return label
}()
private let userNameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.font = .systemFont(ofSize: 16, weight: .regular)
label.textColor = .secondaryLabel
return label
}()
private let followButton: UIButton = {
let button = UIButton()
button.backgroundColor = .link
return button
}()
private var output: PassthroughSubject<Output, Never> = .init()
private var userModel: UserRelationshipModel?
private let followingStatePublisher: CurrentValueSubject<FollowState, Never> = .init(.following)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUI()
bind()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
profileImageView.image = nil
userNameLabel.text = nil
followButton.setTitle(nil, for: .normal)
followButton.backgroundColor = nil
followButton.layer.borderWidth = 0
output.send(completion: .finished)
}
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.frame = CGRect(x: 3, y: 3, width: contentView.height-6, height: contentView.height-6)
profileImageView.layer.cornerRadius = profileImageView.height / 2.0
let labelHeight = contentView.height / 2
let buttonWidth = contentView.width > 500 ? 220 : contentView.width / 3
nameLabel.frame = CGRect(x: profileImageView.right + 5, y: 0, width: contentView.width - 8 - profileImageView.width - buttonWidth, height: labelHeight)
userNameLabel.frame = CGRect(x: profileImageView.right + 5, y: nameLabel.bottom, width: contentView.width - 8 - profileImageView.width - buttonWidth, height: labelHeight)
followButton.frame = CGRect(x: contentView.width - 5 - buttonWidth, y: (contentView.height - 40) / 2, width: buttonWidth, height: 40)
}
private func setUI() {
contentView.addSubview(nameLabel)
contentView.addSubview(profileImageView)
contentView.addSubview(followButton)
contentView.addSubview(userNameLabel)
contentView.clipsToBounds = true
}
func configure(with model: UserRelationshipModel) {
output = .init()
userModel = model
nameLabel.text = model.name
userNameLabel.text = model.userName
followingStatePublisher.send(model.type)
}
private func toggleButton(type: FollowState) {
if type == .following {
followButton.setTitle("Unfollow", for: .normal)
followButton.setTitleColor(.label, for: .normal)
followButton.backgroundColor = .systemBackground
followButton.layer.borderWidth = 1
followButton.layer.borderColor = UIColor.label.cgColor
} else {
followButton.setTitle("Follow", for: .normal)
followButton.setTitleColor(.label, for: .normal)
followButton.backgroundColor = .systemBlue
followButton.layer.borderWidth = 0
}
}
private func bind() {
followingStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] type in
self?.toggleButton(type: type)
}
.store(in: &cancellables)
followButton
.tapPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let currentUserModel = self?.userModel else { return }
if let currentState = self?.followingStatePublisher.value {
if currentState == .following {
self?.followingStatePublisher.send(.not_following)
let newUserModel = UserRelationshipModel(userName: currentUserModel.userName, name: currentUserModel.name, type: .not_following)
self?.output.send(.followButtonDidTap(model: newUserModel))
} else {
self?.followingStatePublisher.send(.following)
let newUserModel = UserRelationshipModel(userName: currentUserModel.userName, name: currentUserModel.name, type: .following)
self?.output.send(.followButtonDidTap(model: newUserModel))
}
}
}
.store(in: &cancellables)
}
func transform() -> AnyPublisher<Output, Never> {
return output.eraseToAnyPublisher()
}
}
finish
이후 새롭게 인스턴스를 줌으로써 생성 가능import UIKit
class NotificationViewController: UIViewController {
private let tableView: UITableView = {
let tableView = UITableView()
tableView.isHidden = true
tableView.register(NotificationLikeTableViewCell.self, forCellReuseIdentifier: NotificationLikeTableViewCell.identifier)
tableView.register(NotificationFollowEventTableViewCell.self, forCellReuseIdentifier: NotificationFollowEventTableViewCell.identifier)
return tableView
}()
private lazy var noNotificationView = NoNotificationView()
private let spinner: UIActivityIndicatorView = {
let spinner = UIActivityIndicatorView(style: .large)
spinner.hidesWhenStopped = true
spinner.tintColor = .label
return spinner
}()
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
noNotificationView.frame = CGRect(x: 0, y: 0, width: view.width / 2, height: view.width / 4)
noNotificationView.center = view.center
spinner.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
spinner.center = view.center
}
private func setUI() {
view.addSubview(spinner)
spinner.startAnimating()
view.backgroundColor = .systemBackground
view.addSubview(tableView)
view.addSubview(noNotificationView)
noNotificationView.isHidden = true
navigationItem.title = "Notifications"
tableView.delegate = self
tableView.dataSource = self
}
}
extension NotificationViewController: UITableViewDelegate {
}
extension NotificationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: NotificationFollowEventTableViewCell.identifier, for: indexPath) as? NotificationFollowEventTableViewCell {
return cell
} else if let cell = tableView.dequeueReusableCell(withIdentifier: NotificationLikeTableViewCell.identifier, for: indexPath) as? NotificationLikeTableViewCell {
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
UIActivityIndicaterView
)를 통해 뷰 로딩 중을 표시 가능퍼블리셔 구독이 끝난 뒤 새로 생성하는 방법을 배웠다! Reference