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 규칙 변경
규칙을 다시 설정후 시도.
출력이 되는걸 확인.
회원 가입 코드 구현
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()
}
}
굿.
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")
}
}
}
로그인한 유져의 이름이 나오는걸 확인.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.