지금까지의 토이 프로젝트는 개인 사용자를 위해 만들어졌다면, 이번 프로젝트는 로그인 및 실제 DB를 활용하여 다중 사용자를 대상으로 한 앱이다.
사카마카를 개발하게 된 이유
평소에 물건을 살지 말지 결정할 때, 종종 우유부단하게 여러 옵션을 고민하다가 결정을 내리지 못하는 경우가 있었다. 그래서 이런 상황에서 도움이 될 수 있는 투표 기능을 갖춘 앱을 만들고자 했다.
앱 소개
📆 개발 기간
24.05.18 ~ 24.06.11
⚽️ 목표
1. 로컬 DB가 아닌 실제 DB 사용해보기
➡️ 로컬DB(CoreData, Realm)이 아닌 실제 DB를 사용하면서 통신에 익숙해지기.
2. Splash Screen 구현해보기
➡️ 앱 실행 시 Splash Screen를 화면에 표시해보기. (조금 더 완성도 있는 앱 만들기)
3. 여러 라이브러리 사용해보기
➡️ 자주 사용하는 라이브러리(SnapKit, Kingfisher, alamofire)말고 다른 라이브러리도 사용하면서 익숙해지기.
4. 탭바 커스텀 해보기
➡️ 탭바의 모양이나 레이아웃 등을 커스텀해서 사용해보기.
5. 애플 로그인 구현해보기
➡️ 카카오, 구글 로그인은 경험이 있으니 애플 소셜 로그인 구현해보면서 익숙해지기.
✅ 적용 기술
RxSwift, RxCocoa, MVVM Pattern, Delegate Pattern, Singleton Pattern, Snapkit, Kingfisher, Then, Firebase, Lottie
⚙️ 기능
1. 사고 싶은 물건을 투표로 만들어 피드 형식으로 제공
2. 투표 기능 제공
3. 각 투표마다 의견을 나눌 수 있는 댓글, 대댓글 기능 제공
4. 내 투표 모아보기 기능 제공
✅ 깃 링크 : https://github.com/YeoSeongil/Project_SakaMaka
🎥 시연 영상
기술적 성장
1. withUnretained (withUnretained에 대해 알아보자.)
이번 프로젝트에서는 weak self 대신 withUnretained를 사용했다. 이를 통해 코드의 가독성이 향상되었다. 또, withUnretained를 사용함으로써 코드 블록 내에서 self의 존재 여부를 명시적으로 확인할 필요가 없어서 개발 과정에서의 실수를 줄일 수 있었다. 이렇게 withUnretained의 장점을 경험하며, 코드의 가독성과 안정성을 높일 수 있었다.

2. 앱 내에서 웹 표시 (앱 내에서 웹을 보여주는 방법에 대해 알아보자. , 앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자.)
상품의 링크를 클릭했을 때 웹 페이지를 보여주는 기능을 구현하기 위해 SFSafariViewController를 사용했다. 이를 통해 앱 내에서 내장된 웹 브라우저를 통해 사용자에게 빠르고 편리한 웹 경험을 제공할 수 있었다. SFSafariViewController는 기본적으로 사용자 인터페이스 스타일과 기능이 Safari 브라우저와 동일하여, 사용자들이 익숙한 환경에서 웹 페이지를 확인할 수 있다. 또한, SFSafariViewController를 사용함으로써 사용자 경험을 향상시키고 앱 내에서의 일관성을 유지하는 것을 경험할 수 있었다.

3. 커스텀 탭바
커스텀 탭바를 구현하면서 UIKit-TabBar의 기능과 속성을 활용하는 과정에서 많은 것을 배웠다. 먼저, UITabBar의 속성을 활용하여 탭바의 색상, 아이콘, 그림자 등을 설정하는 방법을 익혔다. 이를 통해 앱의 UI를 보다 매력적으로 꾸밀 수 있었다. 또한 UITabBarController의 viewControllers 속성을 활용하여 탭바에 표시될 화면들을 관리하고 설정하는 방법도 익힐 수 있었다. 탭바의 외관을 커스터마이징하여 앱의 전반적인 디자인과 일관성을 높일 수 있음을 경험할 수 있었다.

✅ 팩토리 메서드 패턴

setNavigationController 메소드는 UINavigationController의 인스턴스를 생성하고 구성하는 팩토리 메소드이다. 이렇게 팩토리 메소드 디자인 패턴을 직접 구현하고 사용함으로써 코드를 모듈화하고 재사용성을 높이는 방법에 대해 다시 생각해보게 됐다.
setNavigationController 메소드를 사용하면 각 UINavigationController의 생성과 설정을 일관되게 유지하는데, 동일한 메소드를 통해 네비게이션 컨트롤러를 생성하므로, 모든 네비게이션 컨트롤러가 동일한 방식으로 초기화되고 설정된다. 직접 사용해보니 코드의 일관성을 유지할 수 있었고, 모듈화와 재사용성, 일관성을 다시 한번 생각해보게 되었다.
4. 네비게이션에 대한 다른 접근 (UINavigation에 대해 다르게 접근해보자.)
UINavigationController나 navigationItem에 직접 접근하여 네비게이션을 커스텀하는 것이 아니라, HeaderView를 만들어 사용하는 방법으로 네비게이션을 구현했다. 기존의 UINavigationController나 navigationItem에 직접 접근하여 네비게이션을 커스터마이징 하는 방식은 제한적이었고, 복잡한 UI를 구현하기에 적합하지 않았다. 반면에, 커스텀 헤더 뷰를 사용함으로써 더 자유롭게 UI를 설계하고 커스터마이징 할 수 있었다. 이 접근 방식은 기존의 방식과는 여러 부분에서 차별화 되었으며, 이를 통해 UI를 구성하는 방법에 대해 다시 한번 생각해보게 되었다.
5. 커스텀 Alert
이번 프로젝트에서는 기본 시스템 Alert(UIAlertController)을 사용하는 대신, 커스텀 Alert를 구현해서 사용했다. 이 커스텀 Alert창을 구현하면서 사용자 경험을 더 개선할 수 있는 방법을 생각하고, UI를 보다 직관적이고 유연하게 구현하는 데 있어서 좋은 경험을 했다.

커스텀 Alert은 다양한 상황에 맞는 Alert창을 쉽게 생성할 수 있도록 구현했다. 버튼의 텍스트를 동적으로 변경할 수 있고, Alert창 외부를 터치했을 때 닫히는 기능을 추가하여 사용자 편의성을 높였다. 이를 통해 사용자에게 더 나은 피드백 경험을 제공할 수 있음을 경험했다.
✅ Enum을 통한 Alert 유형 설정
enum OnlyYesAlertType {
case defaultType
case customType
}
먼저, OnlyYesAlertType이라는 enum을 정의하여 Alert의 타입을 설정할 수 있게 했다.
final class OnlyYesAlertViewController: UIViewController {
...
// MARK: - Properties
var yesButtonTapAction: (() -> Void)?
var onlyAlertType: OnlyYesAlertType = .defaultType
...
}
onlyAertType은 기본적으로 defaultType으로 설정되어 있으며, 이는 Alert가 기본 동작을 따르도록 한다. yesButtonTapAction은 사용자가 버튼을 눌렀을 때 실행될 커스텀 액션을 정의한다.
extension OnlyYesAlertViewController {
private func setAddTarget() {
self.yesButton.addTarget(self, action: #selector(yesButtonTapped), for: .touchUpInside)
}
@discardableResult
func setButtonTitle(_ yesButtonTitle: String) -> Self {
self.yesButton.setTitle(yesButtonTitle, for: .normal)
return self
}
}
버튼의 타이틀을 설정하고, 버튼에 액션을 추가하는 부분이다. 이 메서드들은 버튼의 사용자 정의 타이틀을 설정하고, 버튼이 눌렸을 때 yesButtonTapped 메소드를 호출하게 한다.
// MARK: - @objc
extension OnlyYesAlertViewController {
@objc private func yesButtonTapped() {
onlyAlertType == .defaultType ? dismiss(animated: false) : self.yesButtonTapAction?()
}
}
마지막으로, 버튼이 눌렸을 때의 동작을 정의하는 부분이다. 이 메소드는 Alert의 Type이 defaultType일 경우 단순하게 Alert창을 닫는다. 반면, customType일 경우 사용자가 정의한 yesButtonTapAction 클로저를 실행한다.

결과적으로, 사용자 경험을 고려한 맞춤형 알림창을 구현함으로써 프로젝트의 완성도를 높였으며, 앞으로도 사용자 인터페이스와 사용자 경험을 최적화하는 데 있어서 더 많은 커스텀 요소를 적용해볼 계획이다.
6. UISheetPresentaionController를 사용해보다.

iOS 15에서 새롭게 도입된 UISheetPresentationController를 활용해보았다. 다양한 속성을 공부하면서 프로젝트에 적용해보았다.
1. Custom Detents
if let sheet = modalViewController.sheetPresentationController {
let fixedDetent = UISheetPresentationController.Detent.custom { context in
return 70
}
...
}
기본 제공되는 UISheetPresentationController.Detent 타입 외에도, custom 클로저를 통해 동적으로 높이를 설정할 수 있다. 이번 프로젝트에서는 높이를 70으로 고정하는 커스텀 detent를 사용했다.
2. 모달의 다양한 속성 설정
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
sheet.prefersGrabberVisible = true
모달이 화면 끝까지 스크롤되지 않도록 설정하는 prefersScrollingExpandsWhenScrolledToEdge,
컴팩트 높이에서 화면 가장자리에 붙도록 설정하는 prefersEdgeAttachedInCompactHeight,
모달이 화면 가장자리에 붙을 때, 모달의 너비가 preferredContentSize를 따르도록 하는 widthFollowsPreferredContentSizeWhenEdgeAttached,
모달의 상단에 작은 막대를 표시할지 여부를 결정하는 prefersGrabberVisible 속성을 설정해보면서
사용자 경험을 향상시키기 위해 노력했다.
7. NotificationCenter를 이용한 키보드 이벤트 감지 (키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자.)

사용자 입력을 받는 화면을 개발하던 중, 키보드가 나타날 때마다 UI가 적절히 대응할 수 있도록 하는 작업을 진행하게 됐다. 이 과정에서 NotificationCenter와 RxSwift를 활용하여 키보드 이벤트를 처리하고, SnapKit을 사용해 레이아웃을 업데이트하는 방법을 구현해 보았다.
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { [weak self] notification in
self?.handleKeyboardWillShow(notification)
})
.disposed(by: disposeBag)
✅ NotificationCenter의 알림을 RxSwift를 통해 구독하여 키보드가 나타나는 이벤트를 감지하고, 이를 처리하였다.
private func handleKeyboardWillShow(_ notification: Notification) {
// 알림에서 키보드의 정보 가져오기
guard let userInfo = notification.userInfo,
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return }
let keyboardHeight = keyboardFrame.height
// 애니메이션을 사용하여 footerView 위치 업데이트
UIView.animate(withDuration: animationDuration) {
self.footerView.snp.updateConstraints {
$0.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(keyboardHeight)
}
self.view.layoutIfNeeded()
}
}
✅ SnapKit을 사용하여 키보드 이벤트에 대응하는 UI 업데이트를 손쉽게 구현할 수 있었다. 키보드가 나타날 때 footerView의 위치를 업데이트하기 위해 snp.updateConstraints를 활용했다. 이를 통해 코드가 더 간결해지고, Auto Layout을 직관적으로 다루는 법을 경험했다.
✅ 키보드가 나타날 때의 애니메이션을 자연스럽게 처리하기 위해 UIView.animate(withDureation:)을 사용했다. 사용자 경험을 향상시키기 위해 애니메이션의 지속시간과 커브를 알림에서 제공하는 값으로 설정했다. 이는 사용자 인터페이스가 보다 부드럽고 직관적으로 느껴지게 하기 위함이다.
아쉬운 점
1. 파이어베이스 경험 부족
이번 프로젝트는 RxSwift 기반으로 진행되었기 때문에, 처음에는 RxFireBase를 사용하고자 했다. 그러나 RxFireBase가 더 이상 업데이트되지 않아서 사용하지 못하게 되었다. 파이어베이스 + Rx 경험이 없었기 때문에 구현하는 데 어려움이 있었다. FireBase와의 통신을 억지로 Rx로 처리하려고 했기 때문에 기존의 비동기 콜백 방식과 Rx 패러다임을 결합하는 데 어려움을 겪었다.

특히, FireBase와의 통신에서 발생하는 콜백을 RxSwift의 스트림으로 변환하는 과정에서 많은 도전이 있었다. 비동기 작업을 Observable로 래핑하고, 이를 통해 데이터 스트림을 처리하는 방법을 찾고 개발하는 데 상당한 시간을 투자해야 했다. 이러한 과정은 기술적으로 매우 도전적이었다.
2. RxSwift 패러다임에 대한 강박
개인적으로, Rx 패러다임에 대한 강박이 오히려 코드의 복잡성을 증가시킬 수 있다고 생각한다. 모든 것을 Rx로 처리하려는 시도가 항상 최선을 아닐 수 있으며, 상황에 따라 다른 접근 방식을 고려하는 것도 중요하다고 느꼈다. 특히, 위 처럼 FireBase와의 통신에서 RxSwift를 과도하게 적용하려다 보니, 개발하는 데 어려움이 있었다. 다양한 상황에 맞는 적절한 도구와 방법을 선택하는 것이 얼마나 중요한지 다시 한번 느끼게 되었다.
이 경험을 통해, 비동기 프로그래밍에서는 RxSwift가 제공하는 장점을 최대한 활용하되, 필요에 따라 더 간단하고 직관적인 콜백 방식이나 다른 비동기 처리 기법을 사용하는 것이 좋을 수 있다는 것을 배웠다. 또한, 여러 가지 접근 방식을 시도해보고, 각각의 장단점을 비교해보는 것이 기술적으로 성장하는 데 큰 도움이 된다는 것을 깨달았다.
앞으로는 특정 기술에 집착하기보다는, 문제의 성격과 요구사항에 맞는 최적의 해결책을 찾는 데 집중하여 개발할 것이다. 다양한 방법을 배우고 적용해보면서, 상황에 맞는 유연한 접근 방식을 생각하면서 개발해 나갈 계획이다.
3. 새로운 기능을 구현하는 것에 대한 모험 (TableView의 Section을 이용해서 대댓글 기능을 구현해보자.)
댓글과 대댓글 기능을 처음 구현해보았다. 어떻게 구현해야할 지 갈피를 잡지 못해 웹 검색을 했지만, 쉽게 찾을 수 없었고, 정형화 된 방법이 없어서 어려움을 겪었다. 특히 댓글과 대댓글을 만들 때, 댓글 테이블 뷰와 대댓글 테이블 뷰 총 2개의 테이블 뷰를 사용하는지, 아니면 하나의 테이블 뷰에 댓글 셀과 대댓글 셀을 사용하는지 궁금했다. (결국 하나의 테이블 뷰에 댓글 셀과 대댓글 셀을 사용했다.)
확실히 실전 경험이 부족함을 느꼈다. 아쉬운 점도 있었지만, 새로운 도전에 직면할 때, 공식적인 문서나 튜토리얼, 블로그 글 외에도 스스로 생각하고 개발하는 것이 중요하다는 것을 배웠다.
4. 뭔가 아쉽고 부족한 느낌?
앱을 완성하고 처음으로 전체적인 흐름을 테스트할 때, 몇 가지 세부적인 부분에서 부족함을 느꼈다. 예를 들어, 투표를 게시할 때 로딩 인디케이터가 없어 사용자는 현재 어떤 작업이 진행 중인지 알 수 없었다. 또한, 앱이 정상적으로 동작하고 있다는 피드백이 부족해 사용자에게 신뢰감을 주지 못할 것 같다는 생각이 들었다.
마무리
이번 프로젝트는 단순한 기능 구현을 넘어, 사용자 경험의 중요성을 깊이 깨닫는 계기가 되었다. 그 과정을 돌아보며 느낀 점과 배운점, 앞으로의 개선 방향을 총평으로 마무리하겠다.
✅ RxSwift를 더 많이 이해하게 됨.
이번 프로젝트에서 RxSwift를 본격적으로 사용하게 되면서, 반응형 프로그래밍의 강력함과 편리함을 몸소 느낄 수 있었다. 특히 NotificationCenter와의 결합을 통해 키보드 이벤트를 처리하고 UI를 업데이트하는 작업에서 RxSwift의 장점을 극대화할 수 있었다.
✅ 서버와의 통신의 어려움
서버와의 통신은 여전히 많은 도전과제들을 안겨주었다. 특히, 안정적인 데이터 전송과 오류 처리, 로딩 상태 관리 등에서 미숙함을 느꼈고, 많은 학습이 필요했다. 이는 앞으로 더 많은 경험과 연습을 통해 극복해야 할 부분임을 다시금 깨달았다.
✅ 다양한 UI 구성 요소 사용
사용자 만족감을 높이기 위해 다양한 UI 구성 요소들을 활용했다. 커스텀 모달, 커스텀 탭바, 헤더뷰를 사용한 네비게이션 바 등은 사용자가 앱을 더 직관적으로 편리하게 사용할 수 있도록 돕는 중요한 요소들이었다.
기능적으로는 만족스러운 프로젝트였지만, 세부적인 디테일에서 부족함을 느꼈다. 특히, 로딩 인디케이터의 부재와 적절한 사용자 피드백의 부족은 앱의 신뢰성을 저하시킬 수 있는 중요한 요소였다. 이러한 디테일의 부족은 사용자 경험을 완벽하게 만드는 데 있어 중요한 부분임을 깨닫게 되었다.
✅ 앞으로의 계획
사용자의 입장에서 생각하며 UI/UX를 설계하고, 사용자가 앱을 사용하는 동안 느낄 수 있는 모든 불편함을 최소화하기 위해 최선을 다할 것이다. 기술적으로 성장한 만큼, 앞으로도 끊임없이 배우고 개선해나가며, 사용자에게 더 나은 경험을 제공하기 위해 모든 디테일에 집중하여 개발할 것이다. 이러한 노력은 단순히 기능 구현에 그치지 않고, 사용자가 나의 앱을 통해 최고의 만족감을 느낄 수 있도록 하는 데 목표를 두고 있다. 앞으로도 지속적인 학습과 개선을 통해 더 나은 사용자 경험을 제공하는 개발자가 되기위해 노력할 것이다.
UINavigation에 대해 다르게 접근해보자. | https://yeoseongil.tistory.com/129 |
CollectionViewCell의 버튼 이벤트 문제를 해결해보자. | https://yeoseongil.tistory.com/130 |
Lottie로 애니메이션을 사용해보자. | https://yeoseongil.tistory.com/131 |
앱 내에서 웹을 보여주는 방버에 대해 알아보자. | https://yeoseongil.tistory.com/132 |
앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자. | https://yeoseongil.tistory.com/133 |
키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자. | https://yeoseongil.tistory.com/134 |
TableView의 Section을 이용해서 대댓글 기능을 구현해보자. | https://yeoseongil.tistory.com/135 |
'ToyProject - 사카마카 (살까말까 고민 될 때는 사카마카)' 카테고리의 다른 글
[사카마카] TableView의 Section을 이용해서 대댓글 기능을 구현해보자. (0) | 2024.06.03 |
---|---|
[사카마카] 키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자. (0) | 2024.06.01 |
[사카마카/문제해결] 앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자. (0) | 2024.05.30 |
[사카마카] 앱 내에서 웹을 보여주는 방법에 대해 알아보자. (0) | 2024.05.30 |
[사카마카] Lottie로 애니메이션을 사용해보자. (0) | 2024.05.29 |
지금까지의 토이 프로젝트는 개인 사용자를 위해 만들어졌다면, 이번 프로젝트는 로그인 및 실제 DB를 활용하여 다중 사용자를 대상으로 한 앱이다.
사카마카를 개발하게 된 이유
평소에 물건을 살지 말지 결정할 때, 종종 우유부단하게 여러 옵션을 고민하다가 결정을 내리지 못하는 경우가 있었다. 그래서 이런 상황에서 도움이 될 수 있는 투표 기능을 갖춘 앱을 만들고자 했다.
앱 소개
📆 개발 기간
24.05.18 ~ 24.06.11
⚽️ 목표
1. 로컬 DB가 아닌 실제 DB 사용해보기
➡️ 로컬DB(CoreData, Realm)이 아닌 실제 DB를 사용하면서 통신에 익숙해지기.
2. Splash Screen 구현해보기
➡️ 앱 실행 시 Splash Screen를 화면에 표시해보기. (조금 더 완성도 있는 앱 만들기)
3. 여러 라이브러리 사용해보기
➡️ 자주 사용하는 라이브러리(SnapKit, Kingfisher, alamofire)말고 다른 라이브러리도 사용하면서 익숙해지기.
4. 탭바 커스텀 해보기
➡️ 탭바의 모양이나 레이아웃 등을 커스텀해서 사용해보기.
5. 애플 로그인 구현해보기
➡️ 카카오, 구글 로그인은 경험이 있으니 애플 소셜 로그인 구현해보면서 익숙해지기.
✅ 적용 기술
RxSwift, RxCocoa, MVVM Pattern, Delegate Pattern, Singleton Pattern, Snapkit, Kingfisher, Then, Firebase, Lottie
⚙️ 기능
1. 사고 싶은 물건을 투표로 만들어 피드 형식으로 제공
2. 투표 기능 제공
3. 각 투표마다 의견을 나눌 수 있는 댓글, 대댓글 기능 제공
4. 내 투표 모아보기 기능 제공
✅ 깃 링크 : https://github.com/YeoSeongil/Project_SakaMaka
🎥 시연 영상
기술적 성장
1. withUnretained (withUnretained에 대해 알아보자.)
이번 프로젝트에서는 weak self 대신 withUnretained를 사용했다. 이를 통해 코드의 가독성이 향상되었다. 또, withUnretained를 사용함으로써 코드 블록 내에서 self의 존재 여부를 명시적으로 확인할 필요가 없어서 개발 과정에서의 실수를 줄일 수 있었다. 이렇게 withUnretained의 장점을 경험하며, 코드의 가독성과 안정성을 높일 수 있었다.

2. 앱 내에서 웹 표시 (앱 내에서 웹을 보여주는 방법에 대해 알아보자. , 앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자.)
상품의 링크를 클릭했을 때 웹 페이지를 보여주는 기능을 구현하기 위해 SFSafariViewController를 사용했다. 이를 통해 앱 내에서 내장된 웹 브라우저를 통해 사용자에게 빠르고 편리한 웹 경험을 제공할 수 있었다. SFSafariViewController는 기본적으로 사용자 인터페이스 스타일과 기능이 Safari 브라우저와 동일하여, 사용자들이 익숙한 환경에서 웹 페이지를 확인할 수 있다. 또한, SFSafariViewController를 사용함으로써 사용자 경험을 향상시키고 앱 내에서의 일관성을 유지하는 것을 경험할 수 있었다.

3. 커스텀 탭바
커스텀 탭바를 구현하면서 UIKit-TabBar의 기능과 속성을 활용하는 과정에서 많은 것을 배웠다. 먼저, UITabBar의 속성을 활용하여 탭바의 색상, 아이콘, 그림자 등을 설정하는 방법을 익혔다. 이를 통해 앱의 UI를 보다 매력적으로 꾸밀 수 있었다. 또한 UITabBarController의 viewControllers 속성을 활용하여 탭바에 표시될 화면들을 관리하고 설정하는 방법도 익힐 수 있었다. 탭바의 외관을 커스터마이징하여 앱의 전반적인 디자인과 일관성을 높일 수 있음을 경험할 수 있었다.

✅ 팩토리 메서드 패턴

setNavigationController 메소드는 UINavigationController의 인스턴스를 생성하고 구성하는 팩토리 메소드이다. 이렇게 팩토리 메소드 디자인 패턴을 직접 구현하고 사용함으로써 코드를 모듈화하고 재사용성을 높이는 방법에 대해 다시 생각해보게 됐다.
setNavigationController 메소드를 사용하면 각 UINavigationController의 생성과 설정을 일관되게 유지하는데, 동일한 메소드를 통해 네비게이션 컨트롤러를 생성하므로, 모든 네비게이션 컨트롤러가 동일한 방식으로 초기화되고 설정된다. 직접 사용해보니 코드의 일관성을 유지할 수 있었고, 모듈화와 재사용성, 일관성을 다시 한번 생각해보게 되었다.
4. 네비게이션에 대한 다른 접근 (UINavigation에 대해 다르게 접근해보자.)
UINavigationController나 navigationItem에 직접 접근하여 네비게이션을 커스텀하는 것이 아니라, HeaderView를 만들어 사용하는 방법으로 네비게이션을 구현했다. 기존의 UINavigationController나 navigationItem에 직접 접근하여 네비게이션을 커스터마이징 하는 방식은 제한적이었고, 복잡한 UI를 구현하기에 적합하지 않았다. 반면에, 커스텀 헤더 뷰를 사용함으로써 더 자유롭게 UI를 설계하고 커스터마이징 할 수 있었다. 이 접근 방식은 기존의 방식과는 여러 부분에서 차별화 되었으며, 이를 통해 UI를 구성하는 방법에 대해 다시 한번 생각해보게 되었다.
5. 커스텀 Alert
이번 프로젝트에서는 기본 시스템 Alert(UIAlertController)을 사용하는 대신, 커스텀 Alert를 구현해서 사용했다. 이 커스텀 Alert창을 구현하면서 사용자 경험을 더 개선할 수 있는 방법을 생각하고, UI를 보다 직관적이고 유연하게 구현하는 데 있어서 좋은 경험을 했다.

커스텀 Alert은 다양한 상황에 맞는 Alert창을 쉽게 생성할 수 있도록 구현했다. 버튼의 텍스트를 동적으로 변경할 수 있고, Alert창 외부를 터치했을 때 닫히는 기능을 추가하여 사용자 편의성을 높였다. 이를 통해 사용자에게 더 나은 피드백 경험을 제공할 수 있음을 경험했다.
✅ Enum을 통한 Alert 유형 설정
enum OnlyYesAlertType {
case defaultType
case customType
}
먼저, OnlyYesAlertType이라는 enum을 정의하여 Alert의 타입을 설정할 수 있게 했다.
final class OnlyYesAlertViewController: UIViewController {
...
// MARK: - Properties
var yesButtonTapAction: (() -> Void)?
var onlyAlertType: OnlyYesAlertType = .defaultType
...
}
onlyAertType은 기본적으로 defaultType으로 설정되어 있으며, 이는 Alert가 기본 동작을 따르도록 한다. yesButtonTapAction은 사용자가 버튼을 눌렀을 때 실행될 커스텀 액션을 정의한다.
extension OnlyYesAlertViewController {
private func setAddTarget() {
self.yesButton.addTarget(self, action: #selector(yesButtonTapped), for: .touchUpInside)
}
@discardableResult
func setButtonTitle(_ yesButtonTitle: String) -> Self {
self.yesButton.setTitle(yesButtonTitle, for: .normal)
return self
}
}
버튼의 타이틀을 설정하고, 버튼에 액션을 추가하는 부분이다. 이 메서드들은 버튼의 사용자 정의 타이틀을 설정하고, 버튼이 눌렸을 때 yesButtonTapped 메소드를 호출하게 한다.
// MARK: - @objc
extension OnlyYesAlertViewController {
@objc private func yesButtonTapped() {
onlyAlertType == .defaultType ? dismiss(animated: false) : self.yesButtonTapAction?()
}
}
마지막으로, 버튼이 눌렸을 때의 동작을 정의하는 부분이다. 이 메소드는 Alert의 Type이 defaultType일 경우 단순하게 Alert창을 닫는다. 반면, customType일 경우 사용자가 정의한 yesButtonTapAction 클로저를 실행한다.

결과적으로, 사용자 경험을 고려한 맞춤형 알림창을 구현함으로써 프로젝트의 완성도를 높였으며, 앞으로도 사용자 인터페이스와 사용자 경험을 최적화하는 데 있어서 더 많은 커스텀 요소를 적용해볼 계획이다.
6. UISheetPresentaionController를 사용해보다.

iOS 15에서 새롭게 도입된 UISheetPresentationController를 활용해보았다. 다양한 속성을 공부하면서 프로젝트에 적용해보았다.
1. Custom Detents
if let sheet = modalViewController.sheetPresentationController {
let fixedDetent = UISheetPresentationController.Detent.custom { context in
return 70
}
...
}
기본 제공되는 UISheetPresentationController.Detent 타입 외에도, custom 클로저를 통해 동적으로 높이를 설정할 수 있다. 이번 프로젝트에서는 높이를 70으로 고정하는 커스텀 detent를 사용했다.
2. 모달의 다양한 속성 설정
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
sheet.prefersGrabberVisible = true
모달이 화면 끝까지 스크롤되지 않도록 설정하는 prefersScrollingExpandsWhenScrolledToEdge,
컴팩트 높이에서 화면 가장자리에 붙도록 설정하는 prefersEdgeAttachedInCompactHeight,
모달이 화면 가장자리에 붙을 때, 모달의 너비가 preferredContentSize를 따르도록 하는 widthFollowsPreferredContentSizeWhenEdgeAttached,
모달의 상단에 작은 막대를 표시할지 여부를 결정하는 prefersGrabberVisible 속성을 설정해보면서
사용자 경험을 향상시키기 위해 노력했다.
7. NotificationCenter를 이용한 키보드 이벤트 감지 (키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자.)

사용자 입력을 받는 화면을 개발하던 중, 키보드가 나타날 때마다 UI가 적절히 대응할 수 있도록 하는 작업을 진행하게 됐다. 이 과정에서 NotificationCenter와 RxSwift를 활용하여 키보드 이벤트를 처리하고, SnapKit을 사용해 레이아웃을 업데이트하는 방법을 구현해 보았다.
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { [weak self] notification in
self?.handleKeyboardWillShow(notification)
})
.disposed(by: disposeBag)
✅ NotificationCenter의 알림을 RxSwift를 통해 구독하여 키보드가 나타나는 이벤트를 감지하고, 이를 처리하였다.
private func handleKeyboardWillShow(_ notification: Notification) {
// 알림에서 키보드의 정보 가져오기
guard let userInfo = notification.userInfo,
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return }
let keyboardHeight = keyboardFrame.height
// 애니메이션을 사용하여 footerView 위치 업데이트
UIView.animate(withDuration: animationDuration) {
self.footerView.snp.updateConstraints {
$0.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(keyboardHeight)
}
self.view.layoutIfNeeded()
}
}
✅ SnapKit을 사용하여 키보드 이벤트에 대응하는 UI 업데이트를 손쉽게 구현할 수 있었다. 키보드가 나타날 때 footerView의 위치를 업데이트하기 위해 snp.updateConstraints를 활용했다. 이를 통해 코드가 더 간결해지고, Auto Layout을 직관적으로 다루는 법을 경험했다.
✅ 키보드가 나타날 때의 애니메이션을 자연스럽게 처리하기 위해 UIView.animate(withDureation:)을 사용했다. 사용자 경험을 향상시키기 위해 애니메이션의 지속시간과 커브를 알림에서 제공하는 값으로 설정했다. 이는 사용자 인터페이스가 보다 부드럽고 직관적으로 느껴지게 하기 위함이다.
아쉬운 점
1. 파이어베이스 경험 부족
이번 프로젝트는 RxSwift 기반으로 진행되었기 때문에, 처음에는 RxFireBase를 사용하고자 했다. 그러나 RxFireBase가 더 이상 업데이트되지 않아서 사용하지 못하게 되었다. 파이어베이스 + Rx 경험이 없었기 때문에 구현하는 데 어려움이 있었다. FireBase와의 통신을 억지로 Rx로 처리하려고 했기 때문에 기존의 비동기 콜백 방식과 Rx 패러다임을 결합하는 데 어려움을 겪었다.

특히, FireBase와의 통신에서 발생하는 콜백을 RxSwift의 스트림으로 변환하는 과정에서 많은 도전이 있었다. 비동기 작업을 Observable로 래핑하고, 이를 통해 데이터 스트림을 처리하는 방법을 찾고 개발하는 데 상당한 시간을 투자해야 했다. 이러한 과정은 기술적으로 매우 도전적이었다.
2. RxSwift 패러다임에 대한 강박
개인적으로, Rx 패러다임에 대한 강박이 오히려 코드의 복잡성을 증가시킬 수 있다고 생각한다. 모든 것을 Rx로 처리하려는 시도가 항상 최선을 아닐 수 있으며, 상황에 따라 다른 접근 방식을 고려하는 것도 중요하다고 느꼈다. 특히, 위 처럼 FireBase와의 통신에서 RxSwift를 과도하게 적용하려다 보니, 개발하는 데 어려움이 있었다. 다양한 상황에 맞는 적절한 도구와 방법을 선택하는 것이 얼마나 중요한지 다시 한번 느끼게 되었다.
이 경험을 통해, 비동기 프로그래밍에서는 RxSwift가 제공하는 장점을 최대한 활용하되, 필요에 따라 더 간단하고 직관적인 콜백 방식이나 다른 비동기 처리 기법을 사용하는 것이 좋을 수 있다는 것을 배웠다. 또한, 여러 가지 접근 방식을 시도해보고, 각각의 장단점을 비교해보는 것이 기술적으로 성장하는 데 큰 도움이 된다는 것을 깨달았다.
앞으로는 특정 기술에 집착하기보다는, 문제의 성격과 요구사항에 맞는 최적의 해결책을 찾는 데 집중하여 개발할 것이다. 다양한 방법을 배우고 적용해보면서, 상황에 맞는 유연한 접근 방식을 생각하면서 개발해 나갈 계획이다.
3. 새로운 기능을 구현하는 것에 대한 모험 (TableView의 Section을 이용해서 대댓글 기능을 구현해보자.)
댓글과 대댓글 기능을 처음 구현해보았다. 어떻게 구현해야할 지 갈피를 잡지 못해 웹 검색을 했지만, 쉽게 찾을 수 없었고, 정형화 된 방법이 없어서 어려움을 겪었다. 특히 댓글과 대댓글을 만들 때, 댓글 테이블 뷰와 대댓글 테이블 뷰 총 2개의 테이블 뷰를 사용하는지, 아니면 하나의 테이블 뷰에 댓글 셀과 대댓글 셀을 사용하는지 궁금했다. (결국 하나의 테이블 뷰에 댓글 셀과 대댓글 셀을 사용했다.)
확실히 실전 경험이 부족함을 느꼈다. 아쉬운 점도 있었지만, 새로운 도전에 직면할 때, 공식적인 문서나 튜토리얼, 블로그 글 외에도 스스로 생각하고 개발하는 것이 중요하다는 것을 배웠다.
4. 뭔가 아쉽고 부족한 느낌?
앱을 완성하고 처음으로 전체적인 흐름을 테스트할 때, 몇 가지 세부적인 부분에서 부족함을 느꼈다. 예를 들어, 투표를 게시할 때 로딩 인디케이터가 없어 사용자는 현재 어떤 작업이 진행 중인지 알 수 없었다. 또한, 앱이 정상적으로 동작하고 있다는 피드백이 부족해 사용자에게 신뢰감을 주지 못할 것 같다는 생각이 들었다.
마무리
이번 프로젝트는 단순한 기능 구현을 넘어, 사용자 경험의 중요성을 깊이 깨닫는 계기가 되었다. 그 과정을 돌아보며 느낀 점과 배운점, 앞으로의 개선 방향을 총평으로 마무리하겠다.
✅ RxSwift를 더 많이 이해하게 됨.
이번 프로젝트에서 RxSwift를 본격적으로 사용하게 되면서, 반응형 프로그래밍의 강력함과 편리함을 몸소 느낄 수 있었다. 특히 NotificationCenter와의 결합을 통해 키보드 이벤트를 처리하고 UI를 업데이트하는 작업에서 RxSwift의 장점을 극대화할 수 있었다.
✅ 서버와의 통신의 어려움
서버와의 통신은 여전히 많은 도전과제들을 안겨주었다. 특히, 안정적인 데이터 전송과 오류 처리, 로딩 상태 관리 등에서 미숙함을 느꼈고, 많은 학습이 필요했다. 이는 앞으로 더 많은 경험과 연습을 통해 극복해야 할 부분임을 다시금 깨달았다.
✅ 다양한 UI 구성 요소 사용
사용자 만족감을 높이기 위해 다양한 UI 구성 요소들을 활용했다. 커스텀 모달, 커스텀 탭바, 헤더뷰를 사용한 네비게이션 바 등은 사용자가 앱을 더 직관적으로 편리하게 사용할 수 있도록 돕는 중요한 요소들이었다.
기능적으로는 만족스러운 프로젝트였지만, 세부적인 디테일에서 부족함을 느꼈다. 특히, 로딩 인디케이터의 부재와 적절한 사용자 피드백의 부족은 앱의 신뢰성을 저하시킬 수 있는 중요한 요소였다. 이러한 디테일의 부족은 사용자 경험을 완벽하게 만드는 데 있어 중요한 부분임을 깨닫게 되었다.
✅ 앞으로의 계획
사용자의 입장에서 생각하며 UI/UX를 설계하고, 사용자가 앱을 사용하는 동안 느낄 수 있는 모든 불편함을 최소화하기 위해 최선을 다할 것이다. 기술적으로 성장한 만큼, 앞으로도 끊임없이 배우고 개선해나가며, 사용자에게 더 나은 경험을 제공하기 위해 모든 디테일에 집중하여 개발할 것이다. 이러한 노력은 단순히 기능 구현에 그치지 않고, 사용자가 나의 앱을 통해 최고의 만족감을 느낄 수 있도록 하는 데 목표를 두고 있다. 앞으로도 지속적인 학습과 개선을 통해 더 나은 사용자 경험을 제공하는 개발자가 되기위해 노력할 것이다.
UINavigation에 대해 다르게 접근해보자. | https://yeoseongil.tistory.com/129 |
CollectionViewCell의 버튼 이벤트 문제를 해결해보자. | https://yeoseongil.tistory.com/130 |
Lottie로 애니메이션을 사용해보자. | https://yeoseongil.tistory.com/131 |
앱 내에서 웹을 보여주는 방버에 대해 알아보자. | https://yeoseongil.tistory.com/132 |
앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자. | https://yeoseongil.tistory.com/133 |
키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자. | https://yeoseongil.tistory.com/134 |
TableView의 Section을 이용해서 대댓글 기능을 구현해보자. | https://yeoseongil.tistory.com/135 |
'ToyProject - 사카마카 (살까말까 고민 될 때는 사카마카)' 카테고리의 다른 글
[사카마카] TableView의 Section을 이용해서 대댓글 기능을 구현해보자. (0) | 2024.06.03 |
---|---|
[사카마카] 키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자. (0) | 2024.06.01 |
[사카마카/문제해결] 앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자. (0) | 2024.05.30 |
[사카마카] 앱 내에서 웹을 보여주는 방법에 대해 알아보자. (0) | 2024.05.30 |
[사카마카] Lottie로 애니메이션을 사용해보자. (0) | 2024.05.29 |