포스트

Chat app (2)

라이브러리 설치

강의에선 cocoapod이지만 spm으로 설치한다.

ApiService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct AuthCredential {
    
    let email: String
    let password: String
    let username: String
    let fullname: String
    let profileImage: UIImage
    
}

struct AuthServices {
    static func loginUser() {
        
    }
    
    static func registerUser(credential: AuthCredential) {
        
        
    }
}

이번에는 틀을 이렇게 잡는다.

확실히 강의 마다 다르다.

1
2
3
4
5
6
7
8
9
10
@objc func handleSignUpVC() {
        guard let email = emailTF.text else { return }
        guard let password = passwordTF.text else { return }
        guard let usernmae = usernameTF.text else { return }
        guard let fullname = fullnameTF.text else { return }
        guard let profileImage = profileImage else { return }
        
        let credential = AuthCredential(email: email, password: password, username: usernmae, fullname: fullname, profileImage: profileImage)
        
    }

가입정보는 이렇게 text로 받고 struct를 사용하여 담았다.

FileUploader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct FileUploader {
    
    static func uploadImage(image: UIImage, completion: @escaping(String) -> Void) {
        guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
        let uid = Auth.auth().currentUser?.uid ?? "/profileImages/"
        
        let filename = NSUUID().uuidString
        let ref = Storage.storage().reference(withPath: "/\(uid)/\(filename)")
        
        ref.putData(imageData, metadata: nil) { metaData, error in
            if let erorr = error {
                print (erorr.localizedDescription)
                return
            }
            
            ref.downloadURL { url, error in
                if let erorr = error {
                    print (erorr.localizedDescription)
                    return
                }
                
                guard let fileURL = url?.absoluteString else { return }
                completion(fileURL)
            }
            
        
        }
    }
    
}

이미지를 업로드 하는 녀석이며 현재 로그인된 유져의 uid를 가져오고 그렇지 않으면 profileImages라고 한다.

파일명은 중복을 막기위해 UUID를 사용.

그리고 FirebaseStorage의 경로는 위와 같이 설정한다.

그리고나서 해당 경로에 이미지를 업로드 하는 코드를 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
struct AuthServices {
    static func loginUser() {
        
    }
    
    static func registerUser(credential: AuthCredential) {
        FileUploader.uploadImage(image: credential.profileImage) { imageURL in
            print(imageURL)
        }
        
    }
}

가입페이지에서 이미지를 업로드하고 등록 버튼을 누르면 이미지url이 나와야한다.

하지만 업로드가 안된다.

Storage 규칙 변경

CleanShot 2024-06-06 at 00 50 22@2x

규칙을 다시 설정후 시도.

출력이 되는걸 확인.

회원 가입 코드 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static func registerUser(credential: AuthCredential, completion: @escaping(Error?) -> Void) {
        FileUploader.uploadImage(image: credential.profileImage) { imageURL in
            
            Auth.auth().createUser(withEmail: credential.email, password: credential.password) { result, error in
                if let error = error {
                    print("Error create account \(error.localizedDescription)")
                    return
                }
                
                guard let uid = result?.user.uid else { return }
                
                let data: [String: Any] = [
                    "email": credential.email,
                    "username": credential.username,
                    "fullname": credential.fullname,
                    "uid": uid,
                    "profileImageURL": imageURL
                ]
                
                collection_User.document(uid).setData(data, completion: completion)
            }
        }
        
    }

올라가는것 확인 완료.

1
2
3
4
5
static func loginUser(withEmail email: String, withPassword password: String, completion: (AuthDataResultCallback?)) {
        
        Auth.auth().signIn(withEmail: email, password: password, completion: completion)
        
    }

이렇게 유저 정보 로그인을 하던 도중

AuthDataResultCallback 에러가 발생하여

typealias AuthDataResultCallback = (AuthDataResult?, Error?) -> Void 이걸 사용하여 해결.

새로 올라온 강의에는

1
2
3
4
5
static func loginUser(withEmail email: String, withPassword password: String, completion: @escaping(AuthDataResult?, Error?) -> Void) {
        
        Auth.auth().signIn(withEmail: email, password: password, completion: completion)
        
    }

이걸로 바꿔줌.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @objc func handleLoginVC() {
        guard let email = emailTF.text?.lowercased() else { return }
        guard let password = passwordTF.text else { return }
        
        AuthServices.loginUser(withEmail: email, withPassword: password) { result, error in
            if let error = error {
                print ("error \(error)")
                return
            }
            
            print("success")
        }
    }
    

로그인 버튼을 다음과 같이 구현

로그인도 확인 완료

Delegate 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protocol RegisterVC_Delegate: AnyObject {
    func didSuccessCreateAccount(_ vc: RegisterViewController)
}

class RegisterViewController: UIViewController {

    
    weak var delegate: RegisterVC_Delegate?

@objc func handleSignUpVC() {
        guard let email = emailTF.text?.lowercased() else { return }
        guard let password = passwordTF.text else { return }
        guard let usernmae = usernameTF.text?.lowercased() else { return }
        guard let fullname = fullnameTF.text else { return }
        guard let profileImage = profileImage else { return }
        
        let credential = AuthCredential(email: email, password: password, username: usernmae, fullname: fullname, profileImage: profileImage)
        
        AuthServices.registerUser(credential: credential) { error in
            if let error = error {
                print("error \(error.localizedDescription)")
                return
            }

            delegate?.didSuccessCreateAccount(self) // added
        }
        
        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// LoginVC

@objc func handleSignUpButton() {
        let controller = RegisterViewController()
        controller.delegate = self // added
        navigationController?.pushViewController(controller, animated: true)
        
    }

extension LoginViewController: RegisterVC_Delegate {
    func didSuccessCreateAccount(_ vc: RegisterViewController) {
        vc.navigationController?.popViewController(animated: true)
    }
    
}

VC Extenstion 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import UIKit
import JGProgressHUD

extension UIViewController {
    static let hud = JGProgressHUD(style: .dark)
    
    func showLoader(_ show: Bool) {
        view.endEditing(true)
        
        if show {
            UIViewController.hud.show(in: view)
        } else {
            UIViewController.hud.dismiss()
        }
    }
    
    
    func showMessage(title: String, message: String, completion: (() -> Void)? = nil) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
            completion?()
        }))
        
        present(alert, animated: true)
    }
 }

 
@objc func handleSignUpVC() {
        guard let email = emailTF.text?.lowercased() else { return }
        guard let password = passwordTF.text else { return }
        guard let usernmae = usernameTF.text?.lowercased() else { return }
        guard let fullname = fullnameTF.text else { return }
        guard let profileImage = profileImage else { return }
        
        let credential = AuthCredential(email: email, password: password, username: usernmae, fullname: fullname, profileImage: profileImage)
        
        showLoader(true) // added
        AuthServices.registerUser(credential: credential) { error in
            self.showLoader(false) // added
            if let error = error {
                self.showMessage(title: "Error", message: error.localizedDescription) // modified
                return
            }
        }
        
        delegate?.didSuccessCreateAccount(self)
    }

 @objc func handleLoginVC() {
        guard let email = emailTF.text?.lowercased() else { return }
        guard let password = passwordTF.text else { return }
        
        showLoader(true) // added
        AuthServices.loginUser(withEmail: email, withPassword: password) { result, error in
            if let error = error {
                self.showMessage(title: "Error", message: error.localizedDescription) // modified
                return
            }
            self.showLoader(false) // added
            
            print("success")
            self.navToConversationVC()
        }
    }

simulator_screenshot_6F899ABE-BEB2-47AA-AB5C-AD0326EDAF4B

굿.

SDWebImage 사용

1
2
3
4
5
6
7
8
9
10
11
func getImage(withImageURL imageURL: URL, completion: @escaping(UIImage) -> Void) {
        SDWebImageManager.shared.loadImage(with: imageURL, options: .continueInBackground, progress: nil) { image, data, error, cashType, finished, url in
            if let error = error {
                self.showMessage(title: "Error", message: error.localizedDescription)
                return
            }
            
            guard let image = image else { return }
            completion(image)
        }
    }

새롭게 sturct도 만들어주고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct AuthCredentialEmail {
    
    let email: String
    let uid: String
    let username: String
    let fullname: String
    let profileImage: UIImage
    
}


static func registerWithGoogle(credential: AuthCredentialEmail, completion: @escaping(Error?) -> Void) {
        FileUploader.uploadImage(image: credential.profileImage) { imageURL in
            
            let data: [String: Any] = [
                "email": credential.email,
                "username": credential.username,
                "fullname": credential.fullname,
                "uid": credential.uid,
                "profileImageURL": imageURL
            ]
            
            collection_User.document(credential.uid).setData(data, completion: completion)
        }
    }

모델링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct User {
    let email: String
    let username: String
    let fullname: String
    let uid: String
    let profileImageURL: String
    
    init(dictionary: [String: Any]) {
        self.email = dictionary["email"] as? String ?? ""
        self.username = dictionary["username"] as? String ?? ""
        self.fullname = dictionary["fullname"] as? String ?? ""
        self.uid = dictionary["uid"] as? String ?? ""
        self.profileImageURL = dictionary["profileImageURL"] as? String ?? ""
    }
}


struct UserServices {
    static func fetchUser(uid: String, completion: @escaping (User) -> Void) {
        collection_User.document(uid).getDocument { (snapshot, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }
            
            guard let dictionary = snapshot?.data() else { return }
            let user = User(dictionary: dictionary)
            completion(user)
        }
    }
}

func navToConversationVC() {
        guard let uid = Auth.auth().currentUser?.uid else { return }
        UserServices.fetchUser(uid: uid) { user in
            print(user)
        }
        let controller = ConversationViewController()
        let nav = UINavigationController(rootViewController: controller)
        nav.modalPresentationStyle = .fullScreen
        self.present(nav, animated: true)
    }

실행하면 유저 졍보가 프린트됨

1
User(email: "a@n.com", username: "ttt", fullname: "Test", uid: "ItlrMBBVskOUuenmDxNwocCowzS2", profileImageURL: "https://firebasestorage.googleapis ....

CONVERSATION VC 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ConversationViewController: UIViewController {

    // MARK: - Properties
    private var user: User
    
    // MARK: - Lifecycle
    init(user: User) {
        self.user = user
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .red
       
    }
  
    // MARK: - Helpers

}

의존성 주입을 해준다.

1
2
3
4
5
6
7
8
9
10
11
func navToConversationVC() {
        guard let uid = Auth.auth().currentUser?.uid else { return }
        UserServices.fetchUser(uid: uid) { user in
            print(user)
            let controller = ConversationViewController(user: user)
            let nav = UINavigationController(rootViewController: controller) // modified
            nav.modalPresentationStyle = .fullScreen
            self.present(nav, animated: true)
        }
        
    }

해당 함수를 수정해준다. 위치도 안으로 넣어주었다.

Splash VC 생성

SplashVC는 현재 유져의 로그인 상태에 따라 다른 VC를 보여주는 역할을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class SplashViewController: UIViewController {

    // MARK: - Properties
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if Auth.auth().currentUser?.uid == nil {
            let controller = LoginViewController()
            let nav = UINavigationController(rootViewController: controller)
            nav.modalPresentationStyle = .fullScreen
            self.present(nav, animated: true)
        } else {
            guard let uid = Auth.auth().currentUser?.uid else { return }
            showLoader(true)
            UserServices.fetchUser(uid: uid) { [self] user in
                self.showLoader(false)
                let controller = ConversationViewController(user: user)
                let nav = UINavigationController(rootViewController: controller)
                nav.modalPresentationStyle = .fullScreen
                self.present(nav, animated: true)
            }
            
        }
    }

}


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        let nav = UINavigationController(rootViewController: SplashViewController()) // modified
        window?.rootViewController = nav
        window?.makeKeyAndVisible()
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class ConversationViewController: UIViewController {

    // MARK: - Properties
    private var user: User
    
    // MARK: - Lifecycle
    init(user: User) {
        self.user = user
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
       configureUI()
    }
  
    // MARK: - Helpers
    private func configureUI() {
        view.backgroundColor = .white
        title = user.fullname
        
        let logoutBarbutton = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))
        
        navigationItem.leftBarButtonItem = logoutBarbutton
    }
    
    @objc func handleLogout() {
        do {
            try Auth.auth().signOut()
            dismiss(animated: true)
        } catch {
            print("Error")
        }
    }
    
}


로그인한 유져의 이름이 나오는걸 확인.

Jun-06-2024 02-57-48

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.