포스트

Final (21)

내용이 많아 최대한 심플하게 작성을 한다…

커뮤니티 게스트 제한 기능 추가

앱 심사를 했으나 Reject이 되어버렸다.

사유는 다음과 같다.

  • 앱은 사용자가 계정 기반이 아닌 기능에 엑세스 하기 위해 등록하거나 로그인해야 합니다.
  • 앱은 앱의 핵심 기능과 직접 관련이 있거나 법에 의해 요구되는 경우를 제외하고는 사용자가 기능하기 위해 개인 정보를 입력할 것을 요구하지 않을 수 있습니다.

처음에 우리는 무조건 로그인을 해야 앱을 사용할수있도록 했는데, 그것이 Reject 사유가 된것이다.

그래서 게스트 로그인을 추가하기로 했다.

모델링

1
2
3
4
5
6
7
8
9
10
11
struct CustomUser {
    let uid: String
    let email: String?
    let isGuest: Bool
    
    init(guestUID: String) {
        self.uid = guestUID
        self.email = nil
        self.isGuest = true
    }
}

Firebase와 비슷하게 하기위해 uid, email을 하고 guest인지여부를 하기위해 isGuest를 넣어주었다.

init 추가

그리고 Message로 가서

새로만든 customUser에 대한 init도 구현해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
init(customUser: CustomUser, content: String, displayName: String) {
    sender = Sender(senderId: customUser.uid, displayName: displayName)
    self.content = content
    sentDate = Date()
    id = nil
}

init(customUser: CustomUser, image: UIImage, displayName: String) {
    sender = Sender(senderId: customUser.uid, displayName: displayName)
    self.image = image
    sentDate = Date()
    content = ""
    id = nil
}

Message 모델에 CustomUser` 기반의 이니셜라이저를 추가함으로써, Firebase 인증 유저가 아닌 게스트 유저도 채팅 메시지를 뷰에서 표시할 수 있도록 지원하게 했다.

SceneDelegate 수정

그리고 SceneDelegate에서 현재 유져가 어떤지에 따라 다르게 하기위해

var currentUser: User? 현재 유저에 대한 property를 만들어 주었다.

그러면서 기존의 guard let 부분을 지웠다.

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
// before
func switchToMainTabBarController() {
    guard let user = Auth.auth().currentUser else { return }
    let tabbarController = UITabBarController()

    // ... greetingVC 구성 생략

    let mapVC = UINavigationController(rootViewController: MapViewController())
    let recommendVC = UINavigationController(rootViewController: RecommendViewController())
    let communityVC = UINavigationController(
        rootViewController: ChannelVC(currentUser: Auth.auth().currentUser!)
    )

    // ... mypageVC 및 tabBarItem 구성 생략

    tabbarController.viewControllers = [recommendVC, mapVC, communityVC, mypageVC]

    window?.rootViewController = tabbarController
    tabbarController.tabBar.backgroundColor = .white
    tabbarController.tabBar.barTintColor = .white
    tabbarController.tabBar.tintColor = ThemeColor.mainOrange
}

// after
func switchToMainTabBarController() {
    let user: User? = Auth.auth().currentUser
    let customUser: CustomUser?

    if let user = user {
        currentUser = user
        customUser = nil
    } else {
        customUser = CustomUser(guestUID: "guest")
    }

    let tabbarController = UITabBarController()

    // ... greetingVC 구성 생략

    let mapVC = UINavigationController(rootViewController: MapViewController())
    let recommendVC = UINavigationController(rootViewController: RecommendViewController())

    let communityVC: UINavigationController
    if let user = currentUser {
        communityVC = UINavigationController(
            rootViewController: ChannelVC(currentUser: user)
        )
    } else if let guestUser = customUser {
        communityVC = UINavigationController(
            rootViewController: ChannelVC(customUser: guestUser)
        )
    } else {
        fatalError("No valid user found.")
    }

    // ... mypageVC 및 tabBarItem 구성 생략

    tabbarController.viewControllers = [recommendVC, mapVC, communityVC, mypageVC]

    window?.rootViewController = tabbarController
    tabbarController.tabBar.backgroundColor = .white
    tabbarController.tabBar.barTintColor = .white
    tabbarController.tabBar.tintColor = ThemeColor.mainOrange
}

기존에는 Auth.auth().currentUser가 없으면 메인 화면으로 진입할 수 없었다. 수정 후에는 User가 없을 경우 CustomUser(게스트)로 대체해 커뮤니티 등 주요 탭 접근이 가능하게 했다.

ChatVC.swift 변경사항 요약

1. 프로퍼티 변경

게스트(CustomUser) 유저를 지원하기 위해 기존 User만 있던 구조에 CustomUser 프로퍼티를 추가하였다.

1
2
private let user: User?  
private let customUser: CustomUser?

2. 이니셜라이저 추가

게스트 전용 초기화를 위해 CustomUser를 받는 새로운 이니셜라이저를 정의해준다.

1
2
3
4
5
6
7
init(customUser: CustomUser, channel: Channel) {
    self.user = nil
    self.customUser = customUser
    self.channel = channel
    super.init(nibName: nil, bundle: nil)
    title = channel.name
}

3. viewWillAppear 내 inputBar 업데이트 분리

viewDidLoad 대신 viewWillAppear에서 inputBar 상태를 최신 상태로 반영하도록 변경해준다.

1
2
3
4
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupMessageInputBar()
}

4. 로그인 상태에 따른 입력창 표시 구분

로그인 여부에 따라 채팅 입력창의 스타일과 메시지를 다르게 표시하도록 if를 통해 나눠 주었다.

1
2
3
4
5
6
7
8
9
10
11
private func setupMessageInputBar() {
    if let user = user {
        messageInputBar.inputTextView.tintColor = ThemeColor.mainOrange
        messageInputBar.sendButton.setTitleColor(ThemeColor.mainOrange, for: .normal)
        messageInputBar.inputTextView.placeholder = "채팅을 입력해주세요!"
    } else if customUser != nil {
        messageInputBar.inputTextView.tintColor = .systemGray
        messageInputBar.sendButton.setTitleColor(.systemGray, for: .normal)
        messageInputBar.inputTextView.placeholder = "채팅 입력을 위해 로그인해주세요!"
    }
}

5. 로그인 유저/게스트에 따른 발신자 정보 설정

게스트 유저도 currentSender를 갖도록 분기 처리하여 메시지 표시가 가능하도록 한다.

1
2
3
4
5
6
7
8
9
var currentSender: SenderType {
    if let user = user {
        return Sender(senderId: user.uid, displayName: currentDisplayName)
    } else if let customUser = customUser {
        return Sender(senderId: customUser.uid, displayName: currentDisplayName)
    } else {
        fatalError("No valid user found.")
    }
}

6. fetchDisplayName 수정

게스트의 경우 사용자 이름을 “Guest”로 고정하여 반환하도록 수정해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
private func fetchDisplayName(completion: @escaping (String?) -> Void) {
    let userManager = UserManager()
    if let user = user {
        userManager.fetchUserData(uid: user.uid) { error, snapshot in
            ...
        }
    } else if let customUser = customUser {
        completion(customUser.isGuest ? "Guest" : "Unknown")
    } else {
        completion(nil)
    }
}

7. 메시지 전송 시 분기 처리

User 또는 CustomUser에 따라 메시지 생성자를 다르게 사용하도록 구분하였다.

1
2
3
4
5
6
7
8
if let user = self.user {
    message = Message(user: user, content: text, displayName: displayName)
} else if let customUser = self.customUser {
    message = Message(customUser: customUser, content: text, displayName: displayName)
} else {
    print("No valid user found")
    return
}

8. 전송 불가 시 얼럿 표시 추가

게스트가 메시지를 보내려고 할 경우 안내 메시지를 띄우고 동작을 차단하게 하였다.

1
2
3
4
guard let displayName = displayName, let self = self else {
    self?.showMessage(title: "로그인이 필요한 기능입니다.", message: "게스트는 메세지를 보낼 수 없습니다.")
    return
}

9. 이미지 전송 로직 분기 처리 동일 적용

텍스트 메시지 전송과 동일하게 이미지 전송 시에도 UserCustomUser를 나누어 처리한다.

1
2
3
4
5
6
7
8
9
var message: Message
if let user = self.user {
    message = Message(user: user, image: image, displayName: displayName)
} else if let customUser = self.customUser {
    message = Message(customUser: customUser, image: image, displayName: displayName)
} else {
    print("No valid user found")
    return
}

ChannelVC.swift 변경사항 요약

1. 사용자 타입을 User/CustomUser로 분리

1
2
private var currentUser: User?
private var customUser: CustomUser?

기존 User 단일 타입에서 게스트 사용자인 CustomUser도 처리할 수 있도록 분리해준다.


2. 초기화 메서드 오버로드

1
2
init(currentUser: User)
init(customUser: CustomUser)

currentUser CustomUser를 각각 받아 채널 목록 화면을 구성할 수 있도록 이니셜라이저를 오버로드함.


3. uid 프로퍼티 확정

1
let uid: String

Auth.auth().currentUser?.uid에서 nil 가능성을 제거하고 명확한 uid 값을 갖도록 초기화 시점에 설정함.


4. 닉네임 체크는 로그인 유저에 한정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private func checkNickname() {Add commentMore actions
    // 유저 모드일 때만 닉네임 확인
    if let user = currentUser {
        userManager.fetchUserData(uid: user.uid) { [self] error, snapshot in
            if let error = error {
                print(error)
            }
            guard let dictionary = snapshot?.value as? [String: Any] else { return }
            currentName = (dictionary[db_nickName] as? String) ?? "Unknown"

            if currentName == ""  {
                showNameAlert(uid: user.uid)
            }
        }
    }
}

게스트(CustomUser)는 닉네임이 없기 때문에 로그인 유저일 때만 닉네임 입력을 요구함.


5. ChatVC로 화면 전환 시 사용자 유형 구분

1
2
3
4
5
if let user = currentUser {
    viewController = ChatVC(user: user, channel: channel)
} else if let customUser = customUser {
    viewController = ChatVC(customUser: customUser, channel: channel)
}

설채팅방 입장 시 User 또는 CustomUser에 따라 각각 다른 이니셜라이저를 사용해 ChatVC를 구성한다.

추천페이지 생명주기 수정

기존 방식 ViewWillApper에 Fetch를 하였다 하지만 해당 탭바를 다시 클릭할때마다 빈화면에서 로드가 되는 문제가 발생하였다.

그래서

viewWillAppear에 있던 fetch를 ViewDidLoad에 옮긴다.

1
2
3
4
5
6
7
8
9
public override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    setupCardSwiper()
    Task { // new
        await viewModel.fetchData()
    }
    bind()
}

그리고 화면이 사라질때 미리 fetch를 하도록 하기위해 ViewDidDisappear에서 작업을 수행한다. 그러면 추천페이지가 비활성화 되어있는 동안 미리 Fetch가 되기에 효율이 좋아진다.

1
2
3
4
5
6
7
8
public override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    cancellables.removeAll()
    Task {
        await viewModel.fetchData()
    }
    bind()
}

커뮤니티 UI 수정

chatVC의 ui를 설정하는 함수를 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private func configureColor() {
    view.backgroundColor = .white
    messagesCollectionView.backgroundColor = .white
    messageInputBar.backgroundColor = .white
    messageInputBar.backgroundView.backgroundColor = .white
    messageInputBar.inputTextView.backgroundColor = .white
    messageInputBar.inputTextView.textColor = .black
    navigationController?.navigationBar.tintColor = ThemeColor.mainOrange
    navigationController?.navigationBar.barTintColor = .white
    navigationController?.navigationBar.titleTextAttributes = [
        NSAttributedString.Key.foregroundColor: ThemeColor.mainOrange
        ]
    title = channel.name
    navigationController?.navigationBar.prefersLargeTitles = false
}

그리고 이 함수를 viewDidLoad에 추가해주었다.

또한 VC 생명주기에 맞춰서 탭바 표시의 유무를 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupMessageInputBar()
    tabBarController?.tabBar.isHidden = true
    navigationController?.setToolbarHidden(true, animated: false)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    tabBarController?.tabBar.isHidden = false
    navigationController?.setToolbarHidden(false, animated: false)
}

ChannelVC도 해준다.

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
override func viewDidLoad() {Add commentMore actions
        super.viewDidLoad()
        view.backgroundColor = .white // new
        channelTableView.backgroundColor = .white // new
        
        checkNickname()
        configureViews()
        addToolBarItems()
        setupListener()
    }

override func viewWillAppear(_ animated: Bool) { // new
    super.viewWillAppear(animated)
    navigationController?.navigationBar.tintColor = ThemeColor.mainOrange
    navigationController?.navigationBar.barTintColor = .white
    navigationController?.navigationBar.titleTextAttributes = [
        NSAttributedString.Key.foregroundColor: ThemeColor.mainOrange
        ]
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {Add commentMore actions
    let cell = tableView.dequeueReusableCell(withIdentifier: ChannelTableViewCell.className, for: indexPath) as! ChannelTableViewCell
    cell.backgroundColor = .white // new
    cell.selectionStyle = .none // new
    cell.chatRoomLabel.text = channels[indexPath.row].name
    return cell
}

이건 굳이 언급을 안해도 될 것 같다.

게스트 예외처리

1
2
private var user: User?
private var customUser: CustomUser?

let에서 var로 바꿔준다.

그리고 deinit시 nil로 값을 초기화한다.

1
2
3
4
5
6
deinit {
        chatFirestoreStream.removeListener()
        navigationController?.navigationBar.prefersLargeTitles = true
        user = nil // new
        customUser = nil // new
    }

ViewDidLoad에 제스처를 추가 (화면을 탭했을때 더이상 채팅이 입력되지 않게)

1
2
3
4
5
6
7
8
9
10
11
override func viewDidLoad() {
    removeOutgoingMessageAvatars()
    addCameraBarButtonToMessageInputBar()
    listenToMessages()
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        messagesCollectionView.addGestureRecognizer(tapGesture)
}

@objc private func handleTap() {
    view.endEditing(true)
}

viewWillAppear에 추가, 현재 유져에 따라 각각 다른 값을 부여하도록 좀더 세분화 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
override func viewWillAppear(_ animated: Bool) {Add commentMore actions
        super.viewWillAppear(animated)
        
        if let currentUser = Auth.auth().currentUser { // new
            self.user = currentUser
            self.customUser = nil
        } else {
            self.user = nil
            self.customUser = CustomUser(guestUID: "guest")
        }
        // 생략
}

유져가 nil이 아닐때, 즉 현재 게스트가 아닌 유져가 있을때로 바꿔준다.

1
2
3
4
5
6
7
8
private func setupMessageInputBar() {
    if user != nil {
            messageInputBar.inputTextView.tintColor = ThemeColor.mainOrange
            messageInputBar.sendButton.setTitleColor(ThemeColor.mainOrange, for: .normal)
            messageInputBar.inputTextView.placeholder = "채팅을 입력해주세요!"
            present(picker, animated: true)
    }
}

그리고 ChannelVC에도

1
2
3
4
5
deinit {
        channelStream.removeListener()
        currentUser = nil
        customUser = nil
    }

deinit시 초기화 해준다.

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