ILWAT / GrowingTalk

Service Level Project
2 stars 0 forks source link

GrowingTalk

๐ŸŽ™๏ธํ˜‘์—…์„ ํ†ตํ•œ ์„ฑ์žฅ ๊ณต๊ฐ„, GrowingTalk

๋” ์ž์„ธํ•œ ์Šคํฌ๋ฆฐ ์ƒท ๋ณด๊ธฐ
๐Ÿ”—์˜จ๋ณด๋”ฉ ํ™”๋ฉด
๐Ÿ”—ํšŒ์›๊ฐ€์ž…
๐Ÿ”—๋กœ๊ทธ์ธ
๐Ÿ”—ํ™ˆํ™”๋ฉด(์›Œํฌ์ŠคํŽ˜์ด์Šค)
๐Ÿ”—์‚ฌ์ด๋“œ ๋ฐ”
๐Ÿ”—์ฑ„ํŒ…์ฐฝ ๋ฐ ์•Œ๋ฆผ ๊ธฐ๋Šฅ
๐Ÿ”—PG๊ฒฐ์ œ

---------- **๐Ÿ“‹ํ•ต์‹ฌ ๊ธฐ์ˆ ** - [`RxSwift`](https://github.com/ReactiveX/RxSwift) ๊ธฐ๋ฐ˜ MVVM ํŒจํ„ด ์ ์šฉ์„ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ ๋ฐ ๋ฐ˜์‘ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ตฌํ˜„ - [`Realm`](https://github.com/realm/realm-swift.git) DataBase์— ๊ธฐ์กด ์ฑ„ํŒ… ๋ฐ์ดํ„ฐ๋ฅผ ์“ฐ๊ธฐ/์ฝ๊ธฐํ•˜์—ฌ ๋„คํŠธ์›Œํฌ Reqeust ์ตœ์†Œํ™” - [`SocketIO`](https://github.com/socketio/socket.io-client-swift.git)๋ฅผ ํ™œ์šฉํ•œ Socket ํ†ต์‹  ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ตฌํ˜„ - Payment Gateway(PG)๋ฅผ ํ†ตํ•œ ์‹ ์šฉ ์นด๋“œ ๊ฒฐ์ œ ์ง€์› - `Modern CollectionView`ย +ย `DiffableDataSource`๋ฅผ ํ™œ์šฉํ•œ Expandable List ๊ตฌํ˜„ - `Async/Await`๋ฅผ ๋„์ž…ํ•œย Realm Transaction ๋น„๋™๊ธฐ ์‹คํ–‰ ๊ตฌํ˜„ - `Rx operator`๋ฅผ ํ™œ์šฉํ•œ `JWT๊ธฐ๋ฐ˜ AccessToken, Refresh Token ๊ฐฑ์‹  ๋กœ์ง` ๊ตฌํ˜„ - `UIGraphicsImageRenderer`๋ฅผ ํ†ตํ•œย ์ด๋ฏธ์ง€ Rendering resizing - ๊ณตํ†ต UI Component ๋ถ„๋ฆฌ ๋ฐ ์บก์Šํ™” ๊ตฌํ˜„์„ ํ†ตํ•œ ์žฌ ์‚ฌ์šฉ์„ฑ ํ–ฅ์ƒ ## ๐Ÿ› ๏ธ๊ฐœ๋ฐœ ***๐ŸŒŽ๊ฐœ๋ฐœ ํ™˜๊ฒฝ*** > ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„: 2024.01.02. ~ 03.01. > ๊ฐœ๋ฐœ ์ธ์›: 1์ธ > ๊ฐœ๋ฐœ ์–ธ์–ด: Swift > Minimum Deployment: iOS 16.0+: `UISheetPresentationController.Detent.custom` --------- ***โš™๏ธ๊ธฐ์ˆ  ์Šคํƒ*** - **BaseSDK**: `UIKit` - **Pattern**: `MVVM`, `Singleton`, `Input-Output Pattern` - **Reactive Programming**: `RxSwift` - **Package Management**: `SPM`, `CocoaPods` - **CodeBaseUI**: `PHPickerViewController`, `SnapKit`, `Then`, `Toast` - **Database**: `RealmSwift` - **Network**: `Moya`, `SocketIO`, `Kingfisher` - **Management**: `FireBase Cloud Messaging` ## ๐Ÿ”ฅ๊ฐœ๋ฐœ Point ### ์‚ฌ์ด๋“œ ๋ฐ” ๊ตฌํ˜„ - ์‚ฌ์ด๋“œ ๋ฐ”๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ๋ณดํŽธ์ ์ธ ์‚ฌ์ด๋“œ ๋ฐ” ๊ตฌํ˜„ **๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ง€์›์ด ๋Š๊ธด์ง€ ์˜ค๋ž˜ ๋˜์—ˆ์Œ**์— ๋”ฐ๋ผ ์‚ฌ์ด๋“œ ๋ฐ” **์ง์ ‘ ๊ตฌํ˜„**์„ ์„ ํƒ. - `UIView.animate()`๋ฅผ ํ†ตํ•ด ViewWillAppear ์‹œ์ ๊ณผ viewWillDisappear ์‹œ์ ์—์„œ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„. - `UIPanGestureRecognizer`๋ฅผ ํ†ตํ•ด ๋ทฐ์˜ `Animate`๋ฅผ ์ ์šฉํ•˜๊ณ  **View์˜ dismiss๋ฅผ ๊ฒฐ์ •**ํ•  ์ˆ˜ ์žˆ๋‹ค. ### ์ฑ„ํŒ… ๋กœ์ง - ์„œ๋ฒ„์—์„œ ์ฑ„ํŒ… ๋‚ด์—ญ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ๋•Œ, ๋ชจ๋“  ์ฑ„ํŒ… ๋‚ด์—ญ์„ ๋ฐ›๊ฒŒ๋˜๋ฉด ์ฑ„ํŒ…์„ ํ•˜๋ฉด ํ• ์ˆ˜๋ก ์„œ๋ฒ„ ๋ฐ ํ†ต์‹ ์— ๋Œ€ํ•ด์„œ ๋น„์šฉ์ด ๋„ˆ๋ฌด ์ปค์ง€๊ฒŒ ๋œ๋‹ค. - ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ ๋ฐ›์€ ์ฑ„ํŒ… ๋‚ด์—ญ์— ๋Œ€ํ•ด์„œ๋Š” ๋กœ์ปฌ์— ์ €์žฅํ•˜์—ฌ CRDํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•œ ๋’ค, ๋กœ์ปฌ์—์„œ์˜ ๋งˆ์ง€๋ง‰ ์ฑ„ํŒ…์„ ๊ธฐ์ค€์œผ๋กœ ๊ทธ ์ดํ›„ ์ฑ„ํŒ… ๋‚ด์—ญ์„ ๋ฐ›๋Š” ๊ฒƒ์œผ๋กœ ๋น„์šฉ์„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. - ๋กœ์ปฌ DB์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ฑ„ํŒ…๋‚ด์—ญ, ์„œ๋ฒ„ ํ†ต์‹ ์„ ํ†ตํ•ด ์ฑ„ํŒ… ๋‚ด์—ญ์„ ๋ฐ›์•„์˜ค๊ณ  ๋‚˜๋ฉด `Socket`์„ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ฑ„ํŒ…์„ ๊ตฌํ˜„ํ•œ๋‹ค. ## โš Trouble Shooting ### ์‚ฌ์ด๋“œ๋ฐ”์˜ constraints + animate ๋ฌธ์ œ: (`Main event loop`์˜ ์ดํ•ด) |๋ฌธ์ œ ์ƒํ™ฉ|์ •์ƒ| |:--:|:--:| | || #### ๋ฌธ์ œ์  - ์‚ฌ์ด๋“œ ๋ฐ”์˜ ๋“ฑ์žฅ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์ด๋“œ ๋ฐ”์˜ View ์ดˆ๊ธฐ ์œ„์น˜๋ฅผ ๋„ˆ๋น„๋งŒํผ ํ˜„์žฌ View๋กœ๋ถ€ํ„ฐ ์Œ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ Constraints๋ฅผ viewDidLoad์‹œ์ ์— ์„ค์ •ํ•œ ๋‹ค์Œ, ViewWillAppear ์‹œ์ ์— Constraints๋ฅผ ํ˜„์žฌ View๋กœ ๋งž์ถฐ์ฃผ์–ด UIView.animate() ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ–ˆ์œผ๋‚˜, ๋ทฐ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด **X ์ขŒํ‘œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ Y์ขŒํ‘œ๋„ ๊ฐ™์ด Animation์ด ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ์ **์ด ๋ฐœ์ƒ ```Swift private func sideBarAppearAnimation() { self.sideBarView.snp.updateConstraints { make in make.leading.equalTo(self.view) } UIView.animate(withDuration: 0.5, delay: 0) { self.view.layoutIfNeeded() } } ``` #### ์›์ธ ๋ฐ ํ•ด๊ฒฐ - ๋””๋ฒ„๊น…์„ ์ง„ํ–‰ํ–ˆ์„ ๋•Œ, viewWillAppear์‹œ์  ์ „๊นŒ์ง€ ์‚ฌ์ด๋“œ๋ฐ” View์˜ ์ดˆ๊ธฐ ํฌ๊ธฐ ๋ฐ ์œ„์น˜๊ฐ€ ๋ชจ๋‘ ์ •ํ•ด์ง€์ง€ ์•Š๋Š” ์ƒํƒœ์ž„์„ ํ™•์ธ - `Main event loop`์˜ ๊ฐœ๋…์ด ํ•„์š”ํ•จ. - ๋ฌด์ž‘์ • Constraints๋ฅผ ์„ค์ •ํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ๋ฐ”๋กœ View์— Constraints๊ฐ€ ์ ์šฉ๋˜์–ด ๋ทฐ์˜ ์œ„์น˜์™€ ํฌ๊ธฐ๊ฐ€ ๊ฒฐ์ •๋˜๋Š” ๊ฒƒ์ด ์•„๋‹˜. - `Main run loop`์˜ ์‹œ์ ์ด ๋™์ž‘๋˜์–ด์•ผ ๋น„๋กœ์†Œ ์‹ค์งˆ์  Constraints๊ฐ€ ์ ์šฉ๋˜์–ด ๋ทฐ์˜ ์œ„์น˜์™€ ํฌ๊ธฐ๊ฐ€ ๊ฒฐ์ •๋จ. - UIView.animate()๋Š” Scope๋‚ด์—์„œ์˜ View ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ทธ ์ด์ „๊ณผ ๋น„๊ตํ•˜์—ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋™์ž‘ํ•จ. - ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ViewDidLoad() ์‹คํ–‰ ์‹œ์ ๊ณผ ViewWillAppear()๊ฐ€ ์‹คํ–‰๋˜๋Š” ์‹œ์ ์˜ ์ฐจ์ด๊ฐ€ ๊ต‰์žฅํžˆ ์งง์€ ๊ฒฝ์šฐ, ์‹ค์งˆ์ ์ธ ์ดˆ๊ธฐ Constraints๊ฐ€ ์ ์šฉ๋˜๊ธฐ ์ „์— Constratints๊ฐ€ ๋ฎ์–ด์“ฐ๊ธฐ ๋˜์–ด ์ขŒํ‘œ(0, 0)๊ณผ Frame(0, 0)์— ์ƒํƒœ์—์„œ ์ตœ์ข… ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด๊ธฐ์— ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ. - Constraints๊ฐ€ ๋ฎ์–ด์“ฐ๊ธฐ ๋˜๊ธฐ ์ „์— `Main run loop`๋ฅผ ์ž„์˜๋กœ ๋™์ž‘์‹œ์ผœ ์ดˆ๊ธฐ ๋ทฐ๋ฅผ ์„ค์ •ํ•ด ์ค€ ๋‹ค์Œ, Constraints๋ฅผ ๋ฐ”๊พธ์–ด animate๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋จ. ```Swift private func sideBarAppearAnimation() { self.view.layoutIfNeeded() //AutoLayout์„ ํ†ตํ•ด ๋ทฐ์˜ ์ดˆ๊ธฐ ์œ„์น˜์™€ ํฌ๊ธฐ๋ฅผ ์žก์•˜๊ธฐ์— ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ํ•ด๋‹น ๋ฉ”์„œ๋“œ ์‹คํ–‰ -> ๋ทฐ๊ฐ€ ์‹ค์ œ๋กœ ๋ณด์—ฌ์ง€๊ธฐ ์ „๊นŒ์ง€ ์ดˆ๊ธฐ AutoLayout์€ ์‹คํ–‰๋˜์ง€ ์•Š์Œ. sideBarView.snp.updateConstraints { make in make.leading.equalTo(self.view) } UIView.animate(withDuration: 0.5, delay: 0) { self.view.layoutIfNeeded() } } ``` ### ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”์˜ UIBarButtonItem์˜ ํฌ๊ธฐ๊ฐ€ ์กฐ์ ˆ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ |๋ฌธ์ œ ์ƒํ™ฉ|์ •์ƒ| |:--:|:--:| |แ„‚แ…ฆแ„‡แ…ตแ„€แ…ฆแ„‹แ…ตแ„‰แ…งแ†ซ แ„‡แ…ก แ„‹แ…ฉแ„…แ…ฒ|แ„‚แ…ฆแ„‡แ…ตแ„€แ…ฆแ„‹แ…ตแ„‰แ…งแ†ซ แ„‡แ…ก แ„Œแ…ฅแ†ผแ„‰แ…กแ†ผ| - Left Bar Button Item์„ ๊ธฐํš ๋ฐ ๋””์ž์ธ์— ๋งž์ถ”์–ด ๋ฒ„ํŠผ ํฌ๊ธฐ์˜ ์„ค์ •์ด ํ•„์š”ํ•จ. ```Swift let workSpaceImageButton = UIButton().then { view in view.frame = CGRect(origin: .zero, size: CGSize(width: 30, height: 30)) let defaultImage = UIImage(named: "WorkSpace") view.setBackgroundImage(defaultImage, for: .normal) view.backgroundColor = .clear view.layer.cornerRadius = 8 view.clipsToBounds = true view.contentMode = .scaleAspectFit } lazy var workSpaceImageBarButton = UIBarButtonItem(customView: workSpaceImageButton) ... navigationItem.setLeftBarButton(workSpaceImageBarButton, animated: true) - ์ด ์ƒํ™ฉ์—์„œ ๋ฒ„ํŠผ์˜ ์‚ฌ์ด์ฆˆ๋ฅผ **Constraints ํ˜น์€ Frame์œผ๋กœ ํฌ๊ธฐ๋ฅผ ์„ค์ •ํ•ด ์ฃผ์–ด๋„ ์ง€์ •ํ•œ ์‚ฌ์ด์ฆˆ๋Œ€๋กœ ๊ตฌํ˜„๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ** ๋ฐœ์ƒ. - `UIButton`์•ˆ์˜ image๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ, **์„ค์ •ํ•œ image์˜ ํฌ๊ธฐ์— ๋”ฐ๋ผ button๋‚ด ImageView์˜ ํฌ๊ธฐ๊ฐ€ ๊ฒฐ์ •๋˜๊ณ  button์€ ํ•ด๋‹น imageView์˜ ํฌ๊ธฐ๋ณด๋‹ค ์ž‘๊ฒŒ ์„ค์ •๋  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธ.** - button ๋‚ด image๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‹ถ์€ button์˜ ์‚ฌ์ด์ฆˆ๋ณด๋‹ค ์ž‘๊ฒŒ resizingํ•˜์—ฌ button์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์ด์ฆˆ ์กฐ์ ˆ์ด ๊ฐ€๋Šฅ. ```Swift let workSpaceImageButton = UIButton().then { view in view.frame = CGRect(origin: .zero, size: CGSize(width: 30, height: 30)) let defaultImage = UIImage(named: "WorkSpace")?.resizingByRenderer(size: CGSize(width: 30, height: 30), tintColor: .BackgroundColor.backgroundPrimaryColor) view.setBackgroundImage(defaultImage, for: .normal) view.backgroundColor = .clear view.layer.cornerRadius = 8 view.clipsToBounds = true view.contentMode = .scaleAspectFit } lazy var workSpaceImageBarButton = UIBarButtonItem(customView: workSpaceImageButton) ... navigationItem.setLeftBarButton(workSpaceImageBarButton, animated: true) ## ๐Ÿ“”ํšŒ๊ณ  - ์ตœ์ดˆ๋กœ PG์‚ฌ์˜ SDK๋ฅผ ํ†ตํ•ด ๊ฒฐ์ œ๋ฅผ ๋‹ฌ ์ˆ˜ ์žˆ์–ด, **๊ฒฐ์ œ ์‹œ์Šคํ…œ ๊ตฌํ˜„์— ๋Œ€ํ•œ ๋‘๋ ค์›€์ด ํ•ด์†Œ**๋˜์—ˆ๋‹ค. - ์—ด๊ฑฐํ˜•์„ RawValue๋กœ ์ดˆ๊ธฐํ™” ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์ถ”์ƒํ™”๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ๋งŽ์€ ๊ณ ๋ฏผ์„ ๋์— `RawValue Protocol`์„ ์•Œ๊ฒŒ๋˜์—ˆ๊ณ  ์ด๋ฅผ ํ†ตํ•ด NetworkError์— ๊ด€ํ•ด์„œ ์ถ”์ƒํ™”ํ•˜์—ฌ Generic์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜์—ฌ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. - Moya์˜ TargetType(Router Pattern)์„ **DI๋ฅผ ํ†ตํ•ด ๋ถ„๋ฆฌ**๋ฅผ ํ–ˆ๋‹ค๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ์ข‹๊ณ  ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์ง€๋งŒ ์‹ค์ œ๋กœ ์ ์šฉํ•˜์ง€ ๋ชปํ•ด ์•„์‰ฝ๋‹ค.