Skip to content

iyeahh/4cuts.zip-iOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

35 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ—“ ํฌ์ปท์ธ ์ง‘ (4cuts.zip)

โœŒ๐Ÿป ๋„ค์ปท ์‚ฌ์ง„๊ณผ ๊ด€๋ จ๋œ ํฌํ† ๋ถ€์Šค, ํฌ์ฆˆ ๋“ฑ์„ ํ•จ๊ป˜ ๊ณต์œ ํ•˜๋Š” SNS ์•ฑ

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„: 2024๋…„ 8์›” 25์ผ โ†’ 2024๋…„ 9์›” 3์ผ (10์ผ)
์ธ์›: ๊ธฐํš & ๋””์ž์ธ & ๊ฐœ๋ฐœ ์ด 1๋ช…
์ตœ์†Œ ๋ฒ„์ „: iOS 17+

๐Ÿง ํ•ต์‹ฌ ๊ธฐ๋Šฅ

  • ํฌํ† ๋ถ€์Šค, ๋ฐฐ๊ฒฝ ๋ฐ ํ•„ํ„ฐ, ํฌ์ฆˆ ๊ณต์œ 
  • ๋„ค์ปท ์‚ฌ์ง„ ํฌ์ŠคํŠธ
  • ๊ทผ์ฒ˜์— ์žˆ๋Š” ํฌํ† ๋ถ€์Šค ์œ„์น˜ ํ‘œ์‹œ
  • ๋„ค์ปท์‚ฌ์ง„๊ณผ ๊ด€๋ จ๋œ ์ƒํ’ˆ ๊ฒฐ์ œ

๐Ÿค“ ๊ธฐ์ˆ  ์Šคํƒ

  • UIKit, RxSwift, RxCocoa
  • MVVM - InOut, Router ํŒจํ„ด
  • CoreLocation

๐Ÿ“š ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • NaverMapSDK
  • Alamofire
  • iamport_ios
  • SnapKit
  • Kingfisher
  • Toast

๐Ÿ’ก ๋ฌธ์ œ ํ•ด๊ฒฐ

โ“ ํ† ํ”ฝ

ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” API์—์„œ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์œผ๋กœ ํ† ํฐ ๊ฐฑ์‹ , ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ๋Œ€์‘

โ• ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿ’ก ๋ฌธ์ œ์ƒํ™ฉ: ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์—, ์ค‘๊ฐ„์— ๊ฐ€๋กœ์ฑ„ ์ž‘์—…ํ•œ ๋’ค ๋‹ค์‹œ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” Interceptor๋ฅผ ํ™œ์šฉํ•˜์—ฌ retry ๋ฉ”์„œ๋“œ๋กœ ๊ฐฑ์‹ ํ•˜๋ ค ํ–ˆ์œผ๋‚˜, retry ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌดํ•œ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ โ†’ ์–ด๋–ป๊ฒŒ ๋ฌดํ•œ ์‹คํ–‰์„ ๋ฉˆ์ถ”๊ณ  ํ† ํฐ ๊ฐฑ์‹ ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

  • Router ํŒจํ„ด & TargetType ์ ์šฉ์œผ๋กœ Router๋ฅผ ํ†ตํ•ด ๋งŒ๋ฃŒ๋œ ํ† ํฐ์ด ํฌํ•จ๋œ Request๋ฅผ ์ ์šฉํ•˜๊ณ  ์žˆ์Œ
    • retry ๋ฉ”์„œ๋“œ์˜ request๋Š” ํ† ํฐ ๊ฐฑ์‹  ์ „์˜ request๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์—
    • adapt ๋ฉ”์„œ๋“œ์— request Header์— ๊ฐฑ์‹ ๋œ ํ† ํฐ์˜ ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  retry ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œ์ผœ์•ผ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•จ
  • ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๊นŒ์ง€ ๋งŒ๋ฃŒ๋˜๋ฉด ์ €์žฅ๋˜์–ด ์žˆ๋˜ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์‚ญ์ œํ•˜๊ณ , ๋กœ๊ทธ์ธ ํ™”๋ฉด์„ ๋„์›Œ
    • ์ •์ƒ์ ์ธ ๋กœ๊ทธ์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ํ† ํฐ๊ณผ, ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๋Œ€์‘
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-09-17 แ„‹แ…ฉแ„’แ…ฎ 1 25 50

โ“ ํ† ํ”ฝ

PG ๊ฒฐ์ œ ํ”Œ๋กœ์šฐ์™€ ์˜์ˆ˜์ฆ ๊ฒ€์ฆ

โ• ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿ’ก ๋ฌธ์ œ์ƒํ™ฉ: ๋„ค์ปท ์‚ฌ์ง„๊ณผ ๊ด€๋ จ๋œ ์ƒํ’ˆ์„ ํŒ๋งคํ•˜๊ณ  ์‹ถ์€๋ฐ ์–ด๋–ป๊ฒŒ ๊ฒฐ์ œ ์‹œ์Šคํ…œ์„ ์ ์šฉ์‹œํ‚ค๊ณ  ์‹ค์ œ ๊ฒฐ์ œ๊ฐ€ ์ผ์–ด๋‚ฌ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„๊นŒ?

  • ํ†ตํ•ฉ ๊ฒฐ์ œ API์ธ ํฌํŠธ์›์„ ํ™œ์šฉํ•˜์—ฌ ๊ฒฐ์ œ๋Œ€ํ–‰์‚ฌ(PG) ์—ฐ๊ฒฐ

    • ๊ฒฐ์ œ ๋ฒ„ํŠผ์„ ํƒญํ–ˆ์„ ๋•Œ ๊ฒฐ์ œ ์ •๋ณด ์ž…๋ ฅ
    input.payTap
            .subscribe(with: self) { owner, value in
                let payment = IamportPayment(
                    pg: PG.html5_inicis.makePgRawName(pgId: "INIpayTest"),
                    merchant_uid: "ios_\(APIKey.sesacKey)_\(Int(Date().timeIntervalSince1970))",
                    amount: "\(value.1)").then {
                        $0.pay_method = PayMethod.card.rawValue
                        $0.name = "4cut.zip"
                        $0.buyer_name = "์–‘๋ณด๋ผ"
                        $0.app_scheme = "sesac"
                    }
                outputPayment.onNext((payment, value.0))
            }
            .disposed(by: disposeBag)
  • ์‹ค์ œ ๊ฒฐ์ œ๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด

    • ๊ฒฐ์ œ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์˜์ˆ˜์ฆ ๊ฒ€์ฆ์„ ํ†ตํ•ด ์‹ค์ œ ๊ฒฐ์ œ ๋‚ด์—ญ์ด ์žˆ๋Š”์ง€ ํ™•์ธ ํ›„
    • ์œ ์ €์—๊ฒŒ ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ๋„์›Œ ์ฃผ๊ณ  ๋กœ์ง์„ ์ฒ˜๋ฆฌ
output.payment
            .subscribe(with: self) { owner, payment in
                Iamport.shared.payment(viewController: owner,
                                       userCode: "imp57573124",
                                       payment: payment.0) { paymentResult in
                    NetworkManager.shared.validPayment(impUid: paymentResult.imp_uid, postId: payment.1) { value in
                        switch value {
                        case .success(let success):
                            owner.makeToast(title: "๊ฒฐ์ œ", message: "๊ฒฐ์ œ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์–ด์š”!")
                        case .failure(let failure):
                            self.view.makeToast("๊ฒฐ์ œ๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค")
                        }
                    }
                }
            }
            .disposed(by: disposeBag)

โ“ ํ† ํ”ฝ

์‚ฌ์šฉ์ž์˜ ์•„์ดํฐ ์ž์ฒด์˜ ์œ„์น˜์„œ๋น„์Šค๊ฐ€ ๊บผ์ ธ์žˆ๊ฑฐ๋‚˜, ์•ฑ์˜ ์œ„์น˜์„œ๋น„์Šค๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ๋Œ€์‘

โ• ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿ’ก ๋ฌธ์ œ ์ƒํ™ฉ: CoreLocation์„ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ๋ฐ›์•„์™€ ์ฃผ๋ณ€์˜ ํฌํ† ๋ถ€์Šค๋“ค์„ ํ‘œ์‹œํ•ด ์ฃผ๊ณ  ์‹ถ์€๋ฐ ์œ„์น˜ ์„œ๋น„์Šค ์ƒํƒœ์— ๋”ฐ๋ผ ์•ฑ์ด ๊ทธ๋ƒฅ ๊บผ์ง€๊ธฐ๋„ ํ•˜์—ฌ ์ƒ์„ธํ•˜๊ณ  ์ ์ ˆํ•œ ๋Œ€์‘์ด ํ•„์š”ํ•˜๊ณ , ์•ฑ์„ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ์„ค์ •์˜ ๋ณ€๊ฒฝ๊นŒ์ง€ ํ™•์ธํ•˜์—ฌ ๋Œ€์‘ํ•ด์•ผ ํ•˜์ง€ ์•Š์„๊นŒ?

  • ์–ด๋– ํ•œ ์ด์œ ๋กœ ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋œจ์ง€ ์•Š๋Š”์ง€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ด์œ ๋ฅผ ํ† ์ŠคํŠธ ๋ฉ”์„ธ์ง€๋กœ ๋„์›Œ์ฃผ๊ณ 
  • ๋””ํดํŠธ ์ฃผ์†Œ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ง€๋„๋ฅผ ๋„์›Œ์คŒ
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-09-17 แ„‹แ…ฉแ„’แ…ฎ 1 25 58

โ“ ํ† ํ”ฝ

Create์™€ Update๋ฅผ ํ•œ ๋ฉ”์„œ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ

โ• ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿ’ก ๋ฌธ์ œ ์ƒํ™ฉ: ๊ธ€์„ ํฌ์ŠคํŠธ ํ•˜๊ฑฐ๋‚˜, ์ˆ˜์ •ํ•˜๋Š” ๋กœ์ง์„ ํ•œ ํ™”๋ฉด์—์„œ ๊ตฌํ˜„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—…๋กœ๋“œ ๋ฒ„ํŠผ์ด tap ๋˜์—ˆ์„ ๋•Œ multipartFormData ํƒ€์ž…์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ๊นŒ์ง€๋Š” ๋™์ผํ•˜๋‚˜ ๋„คํŠธ์›Œํ‚น ํ•˜๋Š” ๋ถ€๋ถ„๋ถ€ํ„ฐ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ๋ฐ ์–ด๋–ป๊ฒŒ ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ๋กœ ๋‘ ๊ฐ€์ง€ ๋กœ์ง์„ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

  • ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ๋„คํŠธ์›Œํ‚น์„ ์‹คํ–‰ํ•˜๊ณ  ์‹คํŒจํ–ˆ์„ ๋•Œ๋Š” ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹คํŒจ์— ๋Œ€ํ•œ ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์„ฑ๊ณตํ–ˆ์„ ๋•Œ๋Š”
    • Update์˜ ๊ฒฝ์šฐ์—๋Š” ์ด๋ฏธ PostId๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
    • PostId ์œ ๋ฌด๋กœ ๋ถ„๊ธฐ์ฒ˜๋ฆฌํ•˜์—ฌ ๊ฐ๊ฐ์˜ ๋ผ์šฐํ„ฐ์˜ request๋กœ ๋„คํŠธ์›Œํ‚น ์ฒ˜๋ฆฌ๋กœ
    • ํ•œ ๋ฉ”์„œ๋“œ๋กœ ๋‘ ๊ฐ€์ง€ ๋กœ์ง ๋Œ€์‘
input.uploadButtonTap
            .subscribe(with: self) { owner, contentValue in
                let imageContents = contentValue.0
                let stringContent = contentValue.1
                let postId = contentValue.2

                do {
                    AF.upload(multipartFormData: { multipartFormData in
                        imageContents.forEach { image in
                                if let convertedImage = image.jpegData(compressionQuality: 0.1) {
                                multipartFormData.append(convertedImage, withName: "files", fileName: "\(stringContent).png", mimeType: "image/png")
                            }
                        }
                    }, with: try Router.uploadPhoto.asURLRequest())
                    .responseDecodable(of: PhotoListModel.self) { [weak self] response in
                        guard let self else { return }
                        switch response.result {
                        case .success(let value):
                            Observable.just(value)
                                .flatMap { photo -> Single<Result<PostContent, NetworkError>> in
                                    if let id = postId {
                                        NetworkManager.shared.callRequestWithToken(router: .editPost(id: id, content: Content(content: stringContent, product_id: "4cut_photo", files: photo.files)))
                                    } else {
                                        NetworkManager.shared.callRequestWithToken(router: .postContent(content: Content(content: stringContent, product_id: "4cut_photo", files: photo.files)))
                                    }
                                }
                                .subscribe(with: self, onNext: { owner, value in
                                    switch value {
                                    case .success:
                                        owner.popNavi.onNext(true)
                                    case .failure:
                                        failUploadString.onNext(true)
                                    }
                                })
                                .disposed(by: self.disposeBag)
                        case .failure:
                            failUploadImage.onNext(true)
                        }
                    }
                } catch {
                    failNetworking.onNext(true)
                }

            }
            .disposed(by: disposeBag)

About

โœŒ๐Ÿป ๋„ค์ปท ์‚ฌ์ง„๊ณผ ๊ด€๋ จ๋œ ํฌํ† ๋ถ€์Šค, ํฌ์ฆˆ ๋“ฑ์„ ํ•จ๊ป˜ ๊ณต์œ ํ•˜๋Š” SNS ์•ฑ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages