Skip to content

Files

Latest commit

199b6a0 ยท Jan 15, 2021

History

History
345 lines (259 loc) ยท 17.3 KB

README.md

File metadata and controls

345 lines (259 loc) ยท 17.3 KB

BeMeiOS

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

  • SOPT 27th ์žฅ๊ธฐ ํ•ด์ปคํ†ค APPJAM
  • ๊ธฐ๊ฐ„: 2020.12.26 ~ 2021.1.16

๋งค์ผ ์งˆ๋ฌธ์— ๋‹ต์„ ํ•˜๋ฉฐ ๋‚˜๋ฅผ ์•Œ์•„๊ฐ€๋Š” ์งˆ๋ฌธ๋‹ค์ด์–ด๋ฆฌ BeMe ์ž…๋‹ˆ๋‹ค.

24์‹œ๊ฐ„๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์งˆ๋ฌธ ์ œ๊ณต, ์ƒˆ๋กœ์šด ์งˆ๋ฌธ ๋ฐ›๊ธฐ๋ฅผ ํ†ตํ•ด ์งˆ๋ฌธ์— ๋‹ต์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์‹ ์˜ ๊ธ€์„ ์ „์ฒด ๊ณต๊ฐœํ•˜์—ฌ ์‚ฌ๋žŒ๋“ค๊ณผ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๋‚ด๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ๋‹ตํ•œ ์งˆ๋ฌธ์„ ์‰ฝ๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‚ด ์ƒ๊ฐ์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ–ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

Swift 4 Xcode swift iOS COCOAPODS

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Library) ๋ชฉ์ (Purpose) ๋ฒ„์ „(VersionA
Alamofire ์„œ๋ฒ„ ํ†ต์‹  Alamofire
Kingfisher ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ Kingfisher
SnapKit ์˜คํ† ๋ ˆ์ด์•„์›ƒ Kingfisher
Then ์งง์€ ์ฝ”๋“œ ์ฒ˜๋ฆฌ Kingfisher
lottie-ios ๋กœํ‹ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฒ˜๋ฆฌ lottie-ios
Firebase/Messaging Firebase Cloud Messaging Kingfisher
SwiftLint ๊น”๋”ํ•œ ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜ Kingfisher

AutoLayout ์ ์šฉ ์—ฌ๋ถ€

1. iPhone 12 Pro ์ ์šฉ

2. iPhone 12 mini ์ ์šฉ

3. iPhone SE2 ์ ์šฉ

๐Ÿ“Œ ์„œ๋น„์Šค workflow

๐Ÿ“Œ ํ˜‘์—… ๋ฐฉ์‹

๐Ÿ“Œ ๊ธฐ๋Šฅ๋ณ„ ๊ฐœ๋ฐœ ์—ฌ๋ถ€

ํ™”๋ฉด ๊ธฐ๋Šฅ ์ƒ์„ธ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ
์Šคํ”Œ๋ž˜์‹œ ์œค์žฌ๐Ÿš€
์˜จ๋ณด๋”ฉ ์œค์žฌ๐Ÿš€
ํšŒ์›๊ฐ€์ž… ์œค์žฌ๐Ÿš€
๋กœ๊ทธ์ธ ๋กœ๊ทธ์ธ ์œค์žฌ๐Ÿš€
์ž๋™๋กœ๊ทธ์ธ ์œค์žฌ๐Ÿš€
ํ™ˆ ์˜ค๋Š˜์˜ ์งˆ๋ฌธ ๋ฐ ๋‹ต๋ณ€ ์—ด๋žŒ ์œค์žฌ๐Ÿš€
๊ณผ๊ฑฐ์˜ ์งˆ๋ฌธ ๋ฐ ๋‹ต๋ณ€ ์—ด๋žŒ ์œค์žฌ๐Ÿš€
์ถ”๊ฐ€ ์งˆ๋ฌธ ์—ด๋žŒ ์œค์žฌ๐Ÿš€
๋‹ต๋ณ€ ๊ณต๊ฐœ ๋ฒ”์œ„ ์„ค์ • ์œค์žฌ๐Ÿš€
๋‹ต๋ณ€ ์ˆ˜์ • ๋ฐ ์‚ญ์ œ ์œค์žฌ๐Ÿš€
ํƒ์ƒ‰ ๋‚˜์™€ ๋‹ค๋ฅธ ๋‹ต๋ณ€๋“ค ์—ด๋žŒ ์žฌ์šฉ๐Ÿถ
๋‹ค๋ฅธ ๋‹ต๋ณ€๋“ค ๋‘˜๋Ÿฌ๋ณด๊ธฐ ์ตœ์‹ , ํฅ๋ฏธ ํƒญ ๋ฐ ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ ๊ฒ€์ƒ‰ ์žฌ์šฉ๐Ÿถ
์Šคํฌ๋žฉ/ ์–ธ์Šคํฌ๋žฉ ์žฌ์šฉ๐Ÿถ
์‹ ๊ณ ํ•˜๊ธฐ ์žฌ์šฉ๐Ÿถ
ํŒ”๋กœ์ž‰ ํŒ”๋กœ์ž‰/ ํŒ”๋กœ์›Œ์˜ ๋‹ต๋ณ€ ์—ด๋žŒ ์œค์žฌ๐Ÿš€
ํŒ”๋กœ์ž‰/ ํŒ”๋กœ์›Œ ์—ด๋žŒ ์œค์žฌ๐Ÿš€
ํŒ”๋กœ์ž‰/ ํŒ”๋กœ์›Œ ๊ฒ€์ƒ‰ ์‚ฌ์šฉ์ž ๊ฒ€์ƒ‰ ์œค์žฌ๐Ÿš€
ํŒ”๋กœ์šฐ/ ์–ธํŒ”๋กœ์šฐ ์œค์žฌ๐Ÿš€
ํŒ”๋กœ์›Œ ์‚ญ์ œ ์œค์žฌ๐Ÿš€
์‹ ๊ณ ํ•˜๊ธฐ ์œค์žฌ๐Ÿš€
๋งˆ์ดํŽ˜์ด์ง€ ํ”„๋กœํ•„ ํŽธ์ง‘ ์œค์žฌ๐Ÿš€
๋‚ด ๊ธ€, ์Šคํฌ๋žฉ ์—ด๋žŒ ์„ธ๋ž€๐ŸŒฟ
ํ‚ค์›Œ๋“œ ๋ฐ ํ•„ํ„ฐ ๊ฒ€์ƒ‰ ์„ธ๋ž€๐ŸŒฟ
๋‹ต๋ณ€ ๊ณต๊ฐœ ๋ฒ”์œ„ ์„ค์ • ์„ธ๋ž€๐ŸŒฟ
์Šคํฌ๋žฉ/ ์–ธ์Šคํฌ๋žฉ ์„ธ๋ž€๐ŸŒฟ
๊ธ€ ์ƒ์„ธ ๋‹ต๋ณ€ ๋‹ต๋ณ€ ์ƒ์„ธ ์—ด๋žŒ ์žฌ์šฉ๐Ÿถ
๋‹ต๋ณ€ ์ˆ˜์ • ๋ฐ ์‚ญ์ œ ์žฌ์šฉ๐Ÿถ
์Šคํฌ๋žฉ/์–ธ์Šคํฌ๋žฉ ์žฌ์šฉ๐Ÿถ
์‹ ๊ณ ํ•˜๊ธฐ ์žฌ์šฉ๐Ÿถ
๋Œ“๊ธ€ ๋Œ“๊ธ€ ์ž‘์„ฑ ์žฌ์šฉ๐Ÿถ
๋Œ“๊ธ€์˜ ๋‹ต๊ธ€ ์ž‘์„ฑ ์žฌ์šฉ๐Ÿถ
๋Œ“๊ธ€ ๊ณต๊ฐœ๋ฒ”์œ„ ์„ค์ • ์žฌ์šฉ๐Ÿถ
๋Œ“๊ธ€ ์ˆ˜์ • ๋ฐ ์‚ญ์ œ ์žฌ์šฉ๐Ÿถ
๊ธ€ ์“ฐ๊ธฐ ๊ณต๊ฐœ๋ฒ”์œ„ ๋ฐ ๋Œ“๊ธ€ ๊ธฐ๋Šฅ ์„ค์ • ์œค์žฌ๐Ÿš€, ์„ธ๋ž€๐ŸŒฟ
์ž„์‹œ์ €์žฅ ์„ธ๋ž€๐ŸŒฟ
๋‹ต๋ณ€ํ•˜๊ธฐ ์„ธ๋ž€๐ŸŒฟ
ํƒ€์ธํŽ˜์ด์ง€ ํŒ”๋กœ์šฐ/ ์–ธํŒ”๋กœ์šฐ ์„ธ๋ž€๐ŸŒฟ
์Šคํฌ๋žฉ/ ์–ธ์Šคํฌ๋žฉ ์„ธ๋ž€๐ŸŒฟ
์‹ ๊ณ ํ•˜๊ธฐ ์„ธ๋ž€๐ŸŒฟ
์ตœ๊ทผ ํ™œ๋™ ๋Œ“๊ธ€ ๋ฐ ๋‹ต๊ธ€ ์•Œ๋ฆผ
ํŒ”๋กœ์›Œ ์•Œ๋ฆผ
์žฅ๊ธฐ ํ‘ธ์‰ฌ ์•Œ๋ฆผ ์˜ค๋Š˜์˜ ์งˆ๋ฌธ ๋ฐ›๊ธฐ ์œค์žฌ๐Ÿš€

๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • ๊ธ€์“ฐ๊ธฐ

answer data๋ฅผ ๋„ฃ์–ด์„œ ๊ธ€์„ ๋“ฑ๋กํ•˜๋Š” ์ฝ”๋“œ

AnswerRegistService.shared.regist(answerID: answerData!.id!, content: answerData!.answer!, commentBlocked: commentSwitch.isOn, isPublic: answerSwitch.isOn) {(networkResult) -> (Void) in
    switch networkResult{
    case .success(let data) :
        print(self.isFromFollowingTab)
        if self.isFromFollowingTab {
            self.followScrapButtonDelegate?.fromAnswerVC(indexInVC: self.indexInFollowingVC!)
        }
    case .requestErr(let msg):
        if let message = msg as? String {
            print(message)
        }
    case .pathErr :
        print("pathErr")
    case .serverErr :
        print("serverErr")
    case .networkFail:
        print("networkFail")
        
    }
}
  • ํƒ€์ธ์ด ์“ด ๊ธ€ ๋ณด๊ธฐ

๋‹ค๋ฅธ ํŽ˜์ด์ง€๋ฅผ ๋‹ค๋…€์™€์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ๊ธ€์— ๋ณ€๋™์ด ์ƒ๊ธธ ๊ฒฝ์šฐ ์ƒˆ๋กœ ํ†ต์‹ ์„ ํ•ด์„œ Reloadํ•ด์ค˜์•ผํ•จ ํŽ˜์ด์ง• ํ•ด์„œ ์ •๋ณด๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— 1ํŽ˜์ด์ง€๋ถ€ํ„ฐ ์›๋ž˜์˜ ํŽ˜์ด์ง€๊นŒ์ง€ ๋‹ค์‹œ ๋ฐ›์•„์™€์•ผ ํ•จ ์ด ๊ณผ์ •์—์„œ ์ฒ˜์Œ์—๋Š” for๋ฌธ์„ ์ด์šฉํ•ด์„œ ์–ด๋ ค์›€์„ ๊ฒช์—ˆ์œผ๋‚˜, ํ†ต์‹ ์—์„œ์˜ ์žฌ๊ท€ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ํ†ตํ•ด ํ•ด๊ฒฐ

func getUpdateAnswers(){
    let loadingFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
    LoadingHUD.show(loadingFrame: loadingFrame,color: .white)
    curPage = 0
    answers = []
    updateDataOnePage()
}

func updateDataOnePage(){
    
    curPage += 1
    let loadingFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
    FollowingGetAnswersService.shared.getAnswerData(page: curPage){(networkResult) -> (Void) in
        switch networkResult{
        case .success(let data) :
            if let answerDatas = data as? FollowingAnswersData {
                self.pageLen = answerDatas.pageLen
                for answerData in answerDatas.answers{
                    self.answers.append(answerData)
                }
                
            }
            if self.curPage < self.answerPage {
                self.updateDataOnePage()
            }
            else{
                DispatchQueue.global(qos: .background).sync {
                    self.wholeCollectionView.reloadData()
                    self.wholeCollectionView.setContentOffset(CGPoint(x: 0, y: self.lastY), animated: false)
                }
                
                LoadingHUD.hide()
                if self.answers.count == 0{
                    self.customEmptyView.setItems(text: "์•„์ด์ฟ ..! ์•„์ง ํŒ”๋กœ์šฐํ•˜๋Š” ์‚ฌ๋žŒ์ด ์—†๊ตฐ์š”\nํŒ”๋กœ์ž‰์„ ํ•˜๊ณ  ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ๊ธ€์„ ๋‘˜๋Ÿฌ๋ณด์„ธ์š”")
                    self.wholeCollectionView.addSubview(self.customEmptyView)
                    print(self.customEmptyView.superview?.bounds.minX)
                    self.customEmptyView.snp.makeConstraints{
                        $0.centerX.equalToSuperview()
                        $0.top.equalToSuperview().offset(362)
                        $0.height.equalTo(80)
                    }
                   
    
                }
                else{
                    self.customEmptyView.removeFromSuperview()
                }
    
              
            }

            print("success")

        case .requestErr(let msg):
            if let message = msg as? String {
                print(message)
            }
        case .pathErr :
            print("pathErr")
        case .serverErr :
            print("serverErr")
        case .networkFail:
            print("networkFail")
            
        }
        
        
    }
    
    
}

๐Ÿ“Œ Extension์„ ํ†ตํ•œ ๋ฉ”์†Œ๋“œ ์„ค๋ช…

  • UIView Extension

    extension UIView {
        // Set Rounded View
        func makeRounded(cornerRadius : CGFloat?){
            
            // UIView ์˜ ๋ชจ์„œ๋ฆฌ๊ฐ€ ๋‘ฅ๊ทผ ์ •๋„๋ฅผ ์„ค์ •
            if let cornerRadius_ = cornerRadius {
                self.layer.cornerRadius = cornerRadius_
            }  else {
                // cornerRadius ๊ฐ€ nil ์ผ ๊ฒฝ์šฐ์˜ default
                self.layer.cornerRadius = self.layer.frame.height / 2
            }
            
            self.layer.masksToBounds = true
        }
        
        // Set UIView's Shadow
        func dropShadow(color: UIColor, offSet: CGSize, opacity: Float, radius: CGFloat) {
            
            // ๊ทธ๋ฆผ์ž ์ƒ‰์ƒ ์„ค์ •
            layer.shadowColor = color.cgColor
            // ๊ทธ๋ฆผ์ž ํฌ๊ธฐ ์„ค์ •
            layer.shadowOffset = offSet
            // ๊ทธ๋ฆผ์ž ํˆฌ๋ช…๋„ ์„ค์ •
            layer.shadowOpacity = opacity
            // ๊ทธ๋ฆผ์ž์˜ blur ์„ค์ •
            layer.shadowRadius = radius
            // ๊ตฌ๊ธ€๋ง ํ•ด๋ณด์„ธ์š”!
            layer.masksToBounds = false
            
        }
        
        // Set UIView's Border
        func setBorder(borderColor : UIColor?, borderWidth : CGFloat?) {
            
            // UIView ์˜ ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ ์„ค์ •
            if let borderColor_ = borderColor {
                self.layer.borderColor = borderColor_.cgColor
            } else {
                // borderColor ๋ณ€์ˆ˜๊ฐ€ nil ์ผ ๊ฒฝ์šฐ์˜ default
                self.layer.borderColor = UIColor(red: 205/255, green: 209/255, blue: 208/255, alpha: 1.0).cgColor
            }
            
            // UIView ์˜ ํ…Œ๋‘๋ฆฌ ๋‘๊ป˜ ์„ค์ •
            if let borderWidth_ = borderWidth {
                self.layer.borderWidth = borderWidth_
            } else {
                // borderWidth ๋ณ€์ˆ˜๊ฐ€ nil ์ผ ๊ฒฝ์šฐ์˜ default
                self.layer.borderWidth = 1.0
            }
        }
    }
  • UIViewController

    extension UIViewController {
        // ํ† ์ŠคํŠธ ๋ฉ”์„ธ์ง€
        func showToast(text: String, completion: @escaping ()->()) {
            let toast = ToastView(frame: CGRect(x: 0, y: 0, width: 343, height: 84))
            toast.setLabel(text: text)
            toast.alpha = 0
            self.view.addSubview(toast)
            toast.snp.makeConstraints {
                $0.leading.equalToSuperview().offset(16)
                $0.trailing.equalToSuperview().offset(-16)
                $0.bottom.equalToSuperview().offset(-101)
            }
            UIView.animate(withDuration: 0.3, animations: {
                toast.alpha = 1
                
            },completion: { finish in
                UIView.animate(withDuration: 0.3, delay: 0.7, animations: {
                    toast.alpha = 0
    
                }, completion: { finish in
                    if finish {
                        toast.removeFromSuperview()
                        completion()
                    }
                })
            })
        }
    }
  • UIImageView

    // Kingfisher๋ฅผ ์ด์šฉํ•˜์—ฌ url๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” extension
    extension UIImageView {
        
        public func imageFromUrl(_ urlString: String?) {
            if let url = urlString {
                self.kf.setImage(with: URL(string: url), options: [.transition(ImageTransition.fade(0.5))])
            }
        }
    }

๐Ÿ“Œ ํŒ€์› ์†Œ๊ฐœ

์œค์žฌ ์„ธ๋ž€ ์žฌ์šฉ
๋„ค์ด์Šค

๋ชฉ์ฐจ