인스타그램 만들기 v0.3

인스타그램 핵심 기능을 구현해보자. Home 컨트롤러에서는 유저의 프로필과 포스트들이 전시된다.

기능 Spec

MyHomeHeader

internal class MyHomeHeader : UICollectionViewCell {

    internal var delegate: MyHomeHeaderDelegate?

    internal var user: Onstagram.User? { get set }

    internal var headerBtnType: HeaderBtnType

    lazy internal var editProfileFollowButton: UIButton { get set }

    @objc internal func handleEditProfileOrFollow()

    internal var profileImageView: UIImageView

    lazy internal var gridButton: UIButton { get set }

    @objc internal func handleChangeToGridView()

    lazy internal var listButton: UIButton { get set }

    @objc internal func handleChangeToListView()

    internal let bookmarkButton: UIButton

    internal let usernameLabel: UILabel

    internal let postsLabel: UILabel

    internal let followersLabel: UILabel

    internal let followingLabel: UILabel

    override internal init(frame: CGRect)

    required internal init?(coder aDecoder: NSCoder)
}
var user: User? {
        didSet {
            usernameLabel.text = user?.email
            setupHeaderBtn()
            guard let profileImageUrl = user?.profileImageUrl else { return }
            profileImageView.loadImage(URLstring: profileImageUrl)
        }
    }

MyHomeHeaderDelegate

internal protocol MyHomeHeaderDelegate {
    internal func didChangeToListView()
    internal func didChangeToGridView()
    internal func didTapEditProfileBtn()
}

HeaderBtnType

enum HeaderBtnType: String {
    case follow = "Follow"
    case unfollow = "Unfollow"
    case editProfile = "Edit Profile"
    case loading = "Now Loading..."
}

var headerBtnType: HeaderBtnType = .loading
lazy var editProfileFollowButton: UIButton = {
    let button = UIButton(type: .system)
    button.setTitle(headerBtnType.rawValue, for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
    button.layer.borderColor = UIColor.lightGray.cgColor
    button.layer.borderWidth = 1
    button.layer.cornerRadius = 3
    button.addTarget(self, action: #selector(handleEditProfileOrFollow), for: .touchUpInside)
    return button
}()
    
fileprivate func setupHeaderBtn() {
    guard let currentLoggedInUserId = Auth.auth().currentUser?.uid else { return }
    guard let userId = user?.uid else { return }
    if currentLoggedInUserId == userId {
        // edit profile
        self.headerBtnType = .editProfile
        self.editProfileFollowButton.setTitle(headerBtnType.rawValue, for: .normal)
    } else {
        // check if following
        Database.database().reference().child("following")
            .child(currentLoggedInUserId)
            .child(userId)
            .observeSingleEvent(of: .value, with: { (snapshot) in
                if let isFollowing = snapshot.value as? Int, isFollowing == 1 {
                    self.updateUnFollowBtn()
                } else {
                    self.updateFollowBtn()
                }
            }, withCancel: { (err) in
                print("Failed to check if following:", err)
            })
    }
}
    
@objc func handleEditProfileOrFollow() {
    guard let currentLoggedInUserId = Auth.auth().currentUser?.uid else { return }
    guard let userId = user?.uid else { return }
    switch headerBtnType {
    case .editProfile:
        delegate?.didTapEditProfileBtn()
    case .follow:
        updateUnFollowBtn()
        let ref = Database.database().reference().child("following").child(currentLoggedInUserId)
        let values = [userId: 1]
        ref.updateChildValues(values) { (err, ref) in
            if let err = err {
                print("Failed to follow user:", err)
                return
            }
        }
    case .unfollow:
        updateFollowBtn()
        Database.database().reference().child("following")
            .child(currentLoggedInUserId)
            .child(userId)
            .removeValue(completionBlock: { (err, ref) in
                if let err = err {
                    print("Failed to unfollow user:", err)
                }
            })
    case .loading:
        break
    }
}
    
fileprivate func updateFollowBtn() {
    self.headerBtnType = .follow
    self.editProfileFollowButton.setTitle(self.headerBtnType.rawValue, for: .normal)
    self.editProfileFollowButton.backgroundColor = .blue
    self.editProfileFollowButton.setTitleColor(.white, for: .normal)
    self.editProfileFollowButton.layer.borderColor = UIColor(white: 0, alpha: 0.2).cgColor
}
    
fileprivate func updateUnFollowBtn() {
    self.headerBtnType = .unfollow
    self.editProfileFollowButton.setTitle(self.headerBtnType.rawValue, for: .normal)
    self.editProfileFollowButton.backgroundColor = .white
    self.editProfileFollowButton.setTitleColor(.black, for: .normal)
}
    

MyHomeController

extension MyHomeController: MyHomeHeaderDelegate {
   
// MARK: - MyHomeHeaderDelegate
func didChangeToGridView() {
    isGridView = true
    collectionView?.reloadData()
}
    
func didChangeToListView() {
    isGridView = false
    collectionView?.reloadData()
}
    
func didTapEditProfileBtn() {
    let profileImagePicker = PhotoSelectorController()
    profileImagePicker.pickerType = .ProfileImagePicker
    profileImagePicker.delegate = self
    let pickerNavi = UINavigationController(rootViewController: profileImagePicker)
    self.present(pickerNavi, animated: true, completion: nil)
}
    
}

MyHomeController

NotificationCenter.default.addObserver(self,
                                               selector: #selector(fetchPosts),
                                               name: Notification.Name.newPost,
                                               object: nil)
        
NotificationCenter.default.addObserver(self,
                                       selector: #selector(fetchUser),
                                       name: NSNotification.Name.userLogined,
                                       object: nil)
    
NotificationCenter.default.addObserver(self,
                                       selector: #selector(needUpdateHeader),
                                       name: NSNotification.Name.userUpdatedInfo,
                                       object: nil)
                                               
@objc fileprivate func fetchPosts() {
        guard  let user = self.currentUser else { return }
        self.posts.removeAll()
        App.api.fetchPosts(uid: user.uid) { (posts) in
            self.posts = posts
            DispatchQueue.main.async {
                self.collectionView?.reloadData()
            }
        }
    }
    
@objc fileprivate func fetchUser() {
    self.currentUser = GlobalState.instance.user
    self.fetchPosts()
}
    
@objc fileprivate func needUpdateHeader() {
    self.currentUser = GlobalState.instance.user
    collectionView?.reloadData()
}