9oHigh / usket.SeSAC

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

iOS App Programming with SeSAC ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป

-๏ธŽ ๋ฐฐ์šด๊ฒƒ์„ ํ•™์Šตํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ์—ฐ์Šต์žฅ ๐Ÿ”ฅ

ํ”„๋กœ์ ํŠธ ์š”์•ฝ

ํ”„๋กœ์ ํŠธ ์„ค๋ช…
๋ฌผ๋งˆ์‹œ๊ธฐ ํ”„๋กœ์ ํŠธ UserDefaults๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šต
ํ…Œ์ด๋ธ”๋ทฐ ํ”„๋กœ์ ํŠธ UITableViewController๋ฅผ ์ด์šฉํ•ด TableView์˜ ๊ธฐ์ดˆ์ ์ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ํ•™์Šต
ํ™˜์œจ ํ”„๋กœ์ ํŠธ ๊ฐ„๋‹จํ•œ ํ™˜์œจ ๊ณ„์‚ฐ๊ธฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, didSet / willSet ๊ทธ๋ฆฌ๊ณ  get / set์„ ํ•™์Šต
์‡ผํ•‘๋ฆฌ์ŠคํŠธ ํ”„๋กœ์ ํŠธ ์‡ผํ•‘๋ฆฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋ฐฑ์—… / ๋ณต๊ตฌ / ๊ณต์œ  ๊ธฐ๋Šฅ์„ ์œ„ํ•ด Zip๊ณผ ๋”๋ถˆ์–ด Realm, Custom Cell๋“ฑ์„ ํ•™์Šต
๋กœ๋˜ ํ”„๋กœ์ ํŠธ PickerView๋ฅผ ์ด์šฉํ•˜์—ฌ ์›ํ•˜๋Š” ๋‚ ์งœ์˜ ๋กœ๋˜๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ Alamofire ํ•™์Šต
ํŠธ๋ Œ๋“œ ๋ฏธ๋””์–ด ํ”„๋กœ์ ํŠธ TMDB API๋ฅผ ์ด์šฉํ•˜์—ฌ Trend Media์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๊ธฐ์กด์— ๋ฐฐ์šด๊ฒƒ๋“ค์— ๋”๋ถˆ์–ด TableViewCell์„ ์ปค์Šคํ…€, ์œ„์น˜ ๊ถŒํ•œ ์„ค์ •, WebView ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ ๋“ค์„ ์ง‘์ค‘ ํ•™์Šต
์˜คํ”ˆ์›จ๋” ํ”„๋กœ์ ํŠธ ์œ„์น˜๊ถŒํ•œ ์„ค์ •๊ณผ ๋”๋ถˆ์–ด Alamofire์™€ OpenWeather API๋ฅผ ์ด์šฉํ•ด ๋‚ ์”จ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
์˜ํ™”์ง„ํฅ์› ํ”„๋กœ์ ํŠธ ์˜ํ™”์ง„ํฅ์›์˜ API๋ฅผ ์ด์šฉ, Realm์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์— ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ํ•™์Šต
์นด์นด์˜ค OCR API ํ”„๋กœ์ ํŠธ Kakao API๋ฅผ ์ด์šฉํ•˜์—ฌ OCR ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ŠคํŠธ
Punk API SnapKit๊ณผ Codable์„ ์ด์šฉ, beer API๋ฅผ ํ™œ์šฉํ•ด ๋งฅ์ฃผ์™€ ํ•จ๊ป˜๋จน์œผ๋ฉด ์ข‹์€ ์Œ์‹ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

๋ฌผ๋งˆ์‹œ๊ธฐ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, UserDefaults

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

autolayout

  • ์œ ์ €์˜ ๋‹‰๋„ค์ž„, ํ‚ค, ๋ชธ๋ฌด๊ฒŒ๋ฅผ UserDefault๋ฅผ ์ด์šฉํ•ด ์ €์žฅํ•˜๊ณ  ํ™œ์šฉ
@IBAction func checkButtonAction(_ sender: UIBarButtonItem) {
    UserDefaults.standard.set(nickNameTextfield.text, forKey: "nickname")
    UserDefaults.standard.set(heightTextfield.text, forKey: "height")
    UserDefaults.standard.set(weightTextfield.text, forKey: "weight")
    UserDefaults.standard.set(intakeWater(),forKey: "intakewater")
    self.navigationController?.popViewController(animated: true)
}
  • ์ดˆ๊ธฐํ™” ๋ฐ ๋ฌผ ๋งˆ์‹œ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ์ด๋ฒคํŠธ ์„ค์ •
  
@IBAction func drinkWaterButtonAction(_ sender: UIButton) {
    howDrinkValue = howDrinkValue + Int(mlTextField.text!)!
    howDrinkLabel.text = String(howDrinkValue) + "ml"

    goalPercent = (howDrinkValue/10) / UserDefaults.standard.integer(forKey: "intakewater")
    goalLabel.text = "๋ชฉํ‘œ์˜ " + String(goalPercent) + "%"
    //์ด๋ฏธ์ง€ ์„ ํƒ
    pickImage(goalPercent)
    //ํ…์ŠคํŠธ ์ดˆ๊ธฐํ™”
    mlTextField.text = ""
}
func pickImage(_ goalPercent : Int){

    if 0 <= goalPercent && goalPercent < 10 { myImageIdx = 0}
    else if 10 <= goalPercent && goalPercent < 20 { myImageIdx = 1}
    else if 20 <= goalPercent && goalPercent < 30 { myImageIdx = 2}
    else if 30 <= goalPercent && goalPercent < 40 { myImageIdx = 3}
    else if 40 <= goalPercent && goalPercent < 50 { myImageIdx = 4}
    else if 50 <= goalPercent && goalPercent < 60 { myImageIdx = 5}
    else if 60 <= goalPercent && goalPercent < 70 { myImageIdx = 6}
    else if 70 <= goalPercent && goalPercent < 80 { myImageIdx = 7}
    else {myImageIdx = 8}

    myPlantImageView.image = imageArray[myImageIdx]
    UserDefaults.standard.set(myImageIdx, forKey: "imageNumber")
}
  
  • UITextField์—์„œ Bottom์— ๋ผ์ธ์„ ์ฃผ๊ธฐ์œ„ํ•ด ํ•จ์ˆ˜ ์ •์˜

    • CALayer๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ณ  ๋†’์ด๋ฅผ 3์œผ๋กœ ์กฐ์ •
    • ์›ํ•˜๋Š” ์ปฌ๋Ÿฌ๋ฅผ ์ง€์ •ํ•˜๊ณ  addSublayer ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ๊ธฐ์กด์˜ TextField์— ๋„ฃ์–ด์ฃผ๊ธฐ
    func addBottomBorder(){
        let bottomLine = CALayer()
        bottomLine.frame = CGRect(x: 0, y: self.frame.size.height - 3, width: self.frame.size.width, height: 3)
        bottomLine.backgroundColor = UIColor.white.cgColor
        borderStyle = .none
        layer.addSublayer(bottomLine)
    }
์˜์ƒ
แ„†แ…ฎแ†ฏแ„†แ…กแ„‰แ…ตแ„€แ…ตแ„‘แ…ณแ„…แ…ฉแ„Œแ…ฆแ†จแ„แ…ณ

ํ…Œ์ด๋ธ”๋ทฐ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout,UITableViewController

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

์ฒซ๋ฒˆ ์งธ ๋‘๋ฒˆ ์งธ
Simulator Screen Shot - iPhone 12 - 2022-02-08 at 13 05 55 Simulator Screen Shot - iPhone 12 - 2022-02-08 at 13 10 28
  • UITableViewController๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ์ƒ์„ฑ

  • ํ…Œ์ด๋ธ”๋ทฐ์— ๋„ฃ๊ธฐ ์œ„ํ•œ ํ”„๋กœํผํ‹ฐ๋“ค์„ ๋งŒ๋“ค๊ธฐ

var sectionTitle : [String] = ["์ „์ฒด ์„ค์ •","๊ฐœ์ธ ์„ค์ •","๊ธฐํƒ€"]
var myTableList : [[String]] = [["๊ณต์ง€์‚ฌํ•ญ","์‹คํ—˜์‹ค","๋ฒ„์ „ ์ •๋ณด"],["๊ฐœ์ธ/๋ณด์•ˆ","์•Œ๋ฆผ","์ฑ„ํŒ…","๋ฉ€ํ‹ฐํ”„๋กœํ•„"],["๊ณ ๊ฐ์„ผํ„ฐ/๋„์›€๋ง"]]
var myTableListCount : [Int] = [3,4,1]
  • Section ๋ฐ Row์˜ ๊ฐœ์ˆ˜ ํ•จ์ˆ˜๋กœ ์ •์˜ํ•˜๊ธฐ
 //Section ๊ฐœ์ˆ˜
  override func numberOfSections(in tableView: UITableView) -> Int {
      // #warning Incomplete implementation, return the number of sections
      return 3
  }
  //Section์˜ ์…€ ๊ฐœ์ˆ˜
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      
      return myTableListCount[section]
  } 
  • ๊ฐ ์…€๋“ค์˜ Configuration ๋ถ€์—ฌ
 //๊ฐ๊ฐ์˜ ์…€๋“ค
 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     guard let cell = tableView.dequeueReusableCell(withIdentifier: "memoCell") else {
         return UITableViewCell()
     }

     if indexPath.section == 0 {
         cell.textLabel?.text = myTableList[0][indexPath.row]
         cell.textLabel?.font = .systemFont(ofSize: 15)
     } else if indexPath.section == 1{
         cell.textLabel?.text = myTableList[1][indexPath.row]
         cell.textLabel?.font = .systemFont(ofSize: 15)
     } else if indexPath.section == 2{
         cell.textLabel?.text = myTableList[2][indexPath.row]
         cell.textLabel?.font = .systemFont(ofSize: 15)
     }

     return cell
 }

ํ™˜์œจ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout,Property Observer: willSet/didSet, Computed Property: get/set

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

๊ตฌ์„ฑ ๊ตฌ๋™ ์˜์ƒ
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-09 แ„‹แ…ฉแ„’แ…ฎ 2 24 04 แ„’แ…ชแ†ซแ„‹แ…ฒแ†ฏแ„‘แ…ณแ„…แ…ฉแ„Œแ…ฆแ†จแ„แ…ณ


* ํ”„๋กœํผํ‹ฐ ๊ฐ์‹œ์ž : willSet/didSet์„ ํ™œ์šฉํ•˜์—ฌ ์ž…๋ ฅ๋ฐ›์€ ๊ฐ’์— ์˜ํ•ด ๋ณ€ํ™”๋œ ๊ฐ’์„ ๊ฐ์ง€
var currencyRate : Double {
      willSet{
          outWillSetPrint = "ํ™˜์œจ์ด ๋ณ€๋™ ์˜ˆ์ • : \(currencyRate) -> \(newValue)"
      }
      didSet{
          outDidSetPrint = "ํ™˜์œจ์ด ๋ณ€๋™ ์™„๋ฃŒ : \(oldValue) -> \(currencyRate)"
      }
  }
  var USD: Double{
      willSet{
          outUSDWillSetPrint = "ํ™˜์ „๊ธˆ์•ก : \(newValue)๋‹ฌ๋Ÿฌ๋กœ ํ™˜์ „๋  ์˜ˆ์ •"
      }
      didSet{
          outUSDDidSetPrint = "KRW: \(KRW)์› -> \(USD)๋‹ฌ๋Ÿฌ๋กœ ํ™˜์ „๋  ์˜ˆ์ •"
      }
  }
  • ์—ฐ์‚ฐ์ž ํ”„๋กœํผํ‹ฐ : get/set์„ ํ™œ์šฉํ•˜์—ฌ ๋ณ€ํ™”๋œ ๊ฐ’์„ ๋‹ค๋ฅธ ํ”„๋กœํผํ‹ฐ์— ์ „๋‹ฌ
var WON : Double
  
var KRW : Double {
    get {
        return WON
    }
    set {
        WON = newValue
    }
}

์‡ผํ•‘๋ฆฌ์ŠคํŠธ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, AlertSheet, Zip, Realm, Custom Cell, ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ

  • ํ•ต์‹ฌ ๊ตฌํ˜„ ๊ธฐ๋Šฅ

    • ์ฆ๊ฒจ์ฐพ๊ธฐ์™€ ํ•  ์ผ ์™„๋ฃŒ ์—ฌ๋ถ€ ๋ฒ„ํŠผ
    • ์‡ผํ•‘๋ฆฌ์ŠคํŠธ ์Šค์™€์ดํ”„๋กœ ์ œ๊ฑฐ
    • ShoppingList ๋ฐ์ดํ„ฐ๋ฅผ Realm Database์— ์ €์žฅ
    • ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ ํ…Œ์ด๋ธ”๋ทฐ์— ๋ณด์—ฌ์ฃผ๊ธฐ
    • ActionSheet๋ฅผ ํ†ตํ•ด ํ•  ์ผ ๊ธฐ์ค€ ์ •๋ ฌ, ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ์ค€ ์ •๋ ฌ, ์ œ๋ชฉ(ํ•œ๊ธ€ ์ž์Œ์ˆœ) ๊ธฐ์ค€ ์ •๋ ฌ

  • Custom Cell : AutoLayout์„ ์ด์šฉํ•ด ์ฒดํฌ๋ฐ•์Šค, ๋ ˆ์ด๋ธ”, ์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ๊ตฌ์„ฑ

    • ์…€๊ฐ„์˜ ๊ฐ„๊ฒฉ์„ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์…€์˜ contentView.frame.inset์— top๊ณผ bottom์— ๊ฐ’์„ ์คŒ

      class ShoppingListTableViewCell: UITableViewCell {
      
       //๋ฒ„ํŠผ, ๋ผ๋ฒจ
       @IBOutlet var checkBoxButton: UIButton!
       @IBOutlet var shopListLabel: UILabel!
       @IBOutlet var starButton: UIButton!
      
       override func awakeFromNib() {
           super.awakeFromNib()
      
       }
       override func setSelected(_ selected: Bool, animated: Bool) {
           super.setSelected(selected, animated: animated)
      
       }
       //์˜ค๋ฒ„๋ผ์ด๋”ฉ + ๊ฐ„๊ฒฉ์ฃผ๊ธฐ
       override func layoutSubviews() {
           super.layoutSubviews()
           contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 2, left: 0, bottom: 2, right: 0))
           contentView.layer.cornerRadius = 15
        }
      }
  • Realm database์— ์ €์žฅํ•˜๊ณ  TableView์— ๋ณด์—ฌ์ฃผ๊ธฐ

    • Realm Table ์ž‘์„ฑ

      import RealmSwift
      
      class ShopList : Object{
          //์ฒดํฌ๋ฐ•์Šค Bool
          @Persisted var checkClicked : Bool
          //์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฐ•์Šค Bool
          @Persisted var starClicked : Bool
          //์ด๋ฆ„
          @Persisted var name : String
          //uuid
          @Persisted(primaryKey: true) var _id : ObjectId
          //์ดˆ๊ธฐํ™”
          convenience init(name : String) {
              self.init()
              self.name = name
              self.checkClicked = false
              self.starClicked = false
          }
      }
    • ๊ฐ’ ์ €์žฅ ๋ฐ ํ…Œ์ด๋ธ”๋ทฐ์— ๋ณด์—ฌ์ฃผ๊ธฐ

      let localRealm = try! Realm()
      var tasks : Results<ShopList>
      
      //์ค‘๊ฐ„์ƒ๋žต
      
      override func viewDidLoad() {
        super.viewDidLoad()
      
       tasks = localRealm.objects(ShopList.self)
      }
      
      //์ค‘๊ฐ„์ƒ๋žต
      
      //์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ
      @IBAction func addButtonClickedAction(_ sender: UIButton) {
          guard let text = addShopListTextField.text else {
              showAlert(title: "์ž…๋ ฅ ์•ˆ๋‚ด", message: "์‡ผํ•‘ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.", actionTitle: "ํ™•์ธ")
              return
          }
          if text.isEmpty {
              showAlert(title: "์˜ค์ž…๋ ฅ ์•ˆ๋‚ด", message: "์ž…๋ ฅ์ด ๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.", actionTitle: "ํ™•์ธ")
          } else {
              let task  = ShopList(name: text)
              try! localRealm.write{
                  localRealm.add(task)
              }
          }
          addShopListTextField.text = ""
          mainTableView.reloadData()
      }
    • ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ๊ฐ’์„ realm์—์„œ ๊ฐ’ ๋ณ€ํ™˜ ์ดํ›„ ์…€์—์„œ ์ด๋ฏธ์ง€ ์ฃผ๊ธฐ

      tasks[indexPath.row].starClicked  == true ? cell.starButton.setImage(StarredImage, for: .normal) :
      cell.starButton.setImage(unStarredImage,for:.normal)
      
      tasks[indexPath.row].checkClicked == true ? cell.checkBoxButton.setImage(checkedImage, for: .normal) : 
      cell.checkBoxButton.setImage(unCheckedImage,for:.normal)
    • ์…€์˜ ์Šค์™€์ดํ”„ ๊ธฐ๋Šฅ์„ true๋กœ ์„ค์ •ํ•œ ์ดํ›„ ์ œ๊ฑฐ์‹œ realm์—์„œ ์ œ๊ฑฐ ๋ฐ tableview reload

      // ์…€์˜ ์Šค์™€์ดํ”„ ์Šคํƒ€์ผ
       override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
       if editingStyle == .delete{
        do{
          try localRealm.write{
            localRealm.delete(tasks[indexPath.row])
            }
          } catch {
              print("Delete Fail")
          }
        }
        mainTableView.reloadData()
      }
      //์…€ ์Šค์™€์ดํ”„ ON/OFF ๊ธฐ๋Šฅ
      override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
      
      return true
      }
    • alertSheet์—์„œ ๋ฐ›์•„์˜จ ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ• ์ผ, ์ฆ์ฐพ๊ธฐ, ์ œ๋ชฉ ๋ณ„๋กœ realm ๋ฐ์ดํ„ฐ๋ฅผ filter ์ฒ˜๋ฆฌ

      func alertHandler(name : String){
         switch name {
         case "ํ• ์ผ":
             tasks = localRealm.objects(ShopList.self).sorted(byKeyPath: "checkClicked", ascending: false).filter("checkClicked == true")
         case "์ฆ๊ฒจ์ฐพ๊ธฐ":
             tasks = localRealm.objects(ShopList.self).sorted(byKeyPath: "starClicked", ascending: false).filter("starClicked == true")
         case "์ œ๋ชฉ":
             tasks = localRealm.objects(ShopList.self).sorted(byKeyPath: "name", ascending: true)
         default:
             print("Nothing")
         }
         self.mainTableView.reloadData()
      }
    • ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ

      • ๋ฐฑ์—…

        1๏ธโƒฃ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋œ document์˜ ์œ„์น˜๋ฅผ ์ฐพ๊ธฐ

        //Document ํด๋” ์œ„์น˜๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ํ•จ์ˆ˜
        func documentDirectoryPath()->String?{
        
            let documentDirectory = FileManager.SearchPathDirectory.documentDirectory
            let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
            let path = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)
            if let directoryPath = path.first {
                return directoryPath
            } else {
                return nil
            }
        }

        2๏ธโƒฃ ๋ฐฑ์—…ํ•˜๊ณ ์ž ํ•˜๋Š” ์ฃผ์†Œ(default.realm)๋ฅผ ์ถ”๊ฐ€ํ•œ ์ดํ›„ ํŒŒ์ผ์ด ์กด์žฌํ•œ๋‹ค๋ฉด ๋ฐฑ์—…์„ ์‹คํ–‰ ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ๋Š” Alert

        //์œ ํšจํ•œ ์ฃผ์†Œ์ธ์ง€ ํ™•์ธ ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด Alert
        if let path = documentDirectoryPath(){
         
         let realm = (path as NSString).appendingPathComponent("default.realm")
         
         if FileManager.default.fileExists(atPath: realm){
         
             urlPath.append(URL(string: realm)!)
        
         } else {
             showAlert(title: "๋ฐฑ์—…", message: "๋ฐฑ์—…ํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!", actionTitle: "ํ™•์ธ")
         }
        }

        3๏ธโƒฃ ์••์ถ•

        // Zip์„ ํ™œ์šฉํ•ด ์••์ถ• / ์‹คํŒจ์‹œ Alert
        do {
        
        let zipFilePath = try Zip.quickZipFiles(urlPath, fileName: "ShopList")
        print("์••์ถ• ๊ฒฝ๋กœ: \(zipFilePath)")
        
        presentActivityViewController()
        }
        catch {
            showAlert(title: "์˜ค๋ฅ˜ ์•ˆ๋‚ด", message: "์••์ถ•์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", actionTitle: "ํ™•์ธ")
        }

        4๏ธโƒฃ ๋ฐฑ์—…์™„๋ฃŒ

      • ๋ณต๊ตฌ

        1๏ธโƒฃ ๊ฐ€์žฅ ๋จผ์ € ์•„์ดํฐ์˜ ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด MobileCoreService๋ฅผ importํ•ด์•ผ ํ•œ๋‹ค.

        2๏ธโƒฃ UIDocumentPickerDelegate ์ฑ„ํƒ

        3๏ธโƒฃ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜จ ์ดํ›„ ์••์ถ•ํ•ด์ œ ์—†๋‹ค๋ฉด Alert / Zip์„ ์‚ฌ์šฉ

        try Zip.unzipFile(fileURL, destination: documentDirectory, overwrite: true, password: nil, progress: { progress in
               print("progress: \(progress)")
           }, fileOutputHandler: { unzippedFile in
               print("unzippedFile : \(unzippedFile)")
           })
           let alert = UIAlertController(title: "์žฌ์‹œ์ž‘", message: "๋ณต๊ตฌ๋œ ํŒŒ์ผ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์–ดํ”Œ์„ ์ข…๋ฃŒํ›„ ์žฌ์‹œ์ž‘ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.", preferredStyle: .alert)
           alert.addAction(UIAlertAction(title: "ํ™•์ธ", style: .default,handler: { out in
               exit(0)
           }))

        4๏ธโƒฃ ๋ณต๊ตฌ์™„๋ฃŒ!


์ถ”๊ฐ€ ๋ฐ ์‚ญ์ œ ํ•„ํ„ฐ๋ง ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ
แ„Žแ…ฎแ„€แ…กแ„†แ…ตแ†พแ„‰แ…กแ†จแ„Œแ…ฆ แ„Œแ…ฅแ†ผแ„…แ…งแ†ฏ แ„‡แ…ขแ†จแ„‹แ…ฅแ†ธแ„‡แ…ฉแ†จแ„€แ…ฎ

๋ณต๊ถŒ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, Alamofire, UIPickerView

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

  • Alamofire๋ฅผ ์ด์šฉํ•˜์—ฌ ์—ญ๋Œ€ ๋ณต๊ถŒ ๋‹น์ฒจ๋‚ด์—ญ์„ ํ™•์ธ

AF.request(url, method: .get).validate().responseJSON { response in 
          switch response.result {
          case .success(let value):
              let json = JSON(value)

              let date = json["drwNoDate"].stringValue
              self.dateLabel.text = date + " ์ถ”์ฒจ"

              self.luckyNumbers = []
              for item in self.drwNumbersString {
                  self.luckyNumbers.append( json[item].stringValue)
              }
             //์ค‘๋žต
case .failure(let error):
              print(error)
          }
  • UIPickerViewDelegate์™€ UIPickerViewDataSource๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ธฐ

  • Extension์„ ์‚ฌ์šฉ

extension ViewController : UIPickerViewDelegate,UIPickerViewDataSource{
  //column
  func numberOfComponents(in pickerView: UIPickerView) -> Int {
      return 1
  }
  //column์˜ ๊ฐœ์ˆ˜
  func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
      //๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๋กœ๋˜์˜ ํšŒ์ฐจ์ˆ˜๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ
      return episodeList.count
  }
  //title
  func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
      return String(episodeList[row])
  }
  func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
      inputNumberTextField.text = String(episodeList[row])
      getLotteryNumbers(episodeNumber: episodeList[row])
   }
}

UI ๋ฐ ๊ตฌ๋™์˜์ƒ

๊ตฌ์„ฑ ๊ตฌ๋™ ์˜์ƒ
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-10 แ„‹แ…ฉแ„’แ…ฎ 4 20 36 แ„‡แ…ฉแ†จแ„€แ…ฏแ†ซ

ํŠธ๋ Œ๋“œ๋ฏธ๋””์–ด ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : Pass data, WebView, MapView, CoreLocation, Annotation, TableView, CollectionView, Pagenation, TMDB API, NWPathMonitor, KingFisher, Alamofire, SwiftyJSONw

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ / Custom Cell

  • TMDB(The Movie Database)์˜ API๋ฅผ ์ด์šฉํ•˜์—ฌ ์˜ํ™” ๋ฐ ๋“œ๋ผ๋งˆ ์ •๋ณด(ํ‰์ , ์ถœ์—ฐ์ง„, Youtube ๋งํฌ ๋“ฑ)๋ฅผ ๋ฐ›์•„์™€ ๋ณด์—ฌ์ฃผ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

    • Map : MapView์— ์˜ํ™”๊ด€ ์œ„์น˜ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ๊ฐ๊ฐ์˜ ์˜ํ™”๊ด€๋“ค์„ ํ•„ํ„ฐ๋ง
    • Alamofire : ๋ฐ›์•„์˜จ JSON ๋ฐ์ดํ„ฐ๋ฅผ Image, String ...์œผ๋กœ ๋ณ€ํ™˜ํ›„ TableView, CollectionView์— ์ ์šฉ

๋ฉ”์ธ


APIManager Class

  • TMDB์™€ APIํ†ต์‹ ์„ ํ•˜๊ณ  ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ @escaping ํด๋กœ์ €๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•จ

     class TMDBManager{
        static let shared = TMDBManager()
    
        ...
    
        func fetchData(result : @escaping (Int,JSON)->()){
         
            let url = "https://api.themoviedb.org/3/trending/movie/day?api_key=\(APIDocs.TMDB_KEY)&language=ko&page=\(TMDBManager.startPage)"
            AF.request(url, method: .get).validate(statusCode: 200...500).responseJSON { response in
                switch response.result {
                case .success(let value):
                    let json = JSON(value)
                    let code = response.response?.statusCode ?? 500
                    result(code, json)
                case .failure(let error):
                    print(error)
                }
            }
          }
        }
      }

Pass Data

  • ์˜ํ™” ์ •๋ณด๋ฅผ ํด๋ฆญํ•  ์‹œ์— ์ƒ์„ธ์ •๋ณด์ฐฝ์œผ๋กœ Push, ์ด๋•Œ ํ•ด๋‹น ViewController์˜ ๋ณ€์ˆ˜์— ํ•„์š”ํ•œ ๊ฐ’๋“ค์„ ์ „๋‹ฌํ•œ ์ดํ›„ ํ™”๋ฉด์— ๋ณด์—ฌ์ค€๋‹ค.

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         //์Šคํ† ๋ฆฌ๋ณด๋“œ + viewController ํŠน์ •
         let myStoryboard = UIStoryboard(name: "Main", bundle: nil)
        
         let vc = myStoryboard.instantiateViewController(withIdentifier: "peopleStoryboard") as! mediaPeopleViewControllViewController
         //Poster
         vc.posterName = myMediaList[indexPath.row].poster
         //BackDrop
         vc.headerImageName = myMediaList[indexPath.row].backDrop
         //Title
         vc.titleName = myMediaList[indexPath.row].title
         //Overview
         vc.summary = myMediaList[indexPath.row].overview
         //id
         vc.movie_id = myMediaList[indexPath.row].id
    
         //PUSH
         self.navigationController?.pushViewController(vc, animated: true)        
     }

Kingfisher ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ „๋‹ฌ๋ฐ›์€ URL๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€ ๋ฐ”๋กœ ์„ธํŒ…ํ•˜๊ธฐ

  • Kingfisher๋Š” url๋กœ ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ง€๊ณ ์˜ค๋Š” ๊ธฐ๋Šฅ๋ฟ ์•„๋‹ˆ๋ผ ์ด๋ฅผ ๋””์Šคํฌ ํ˜น์€ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์— ์ €์žฅํ•˜์—ฌ ์ฃผ๊ธฐ ๋–„๋ฌธ์— ์ตœ์ดˆ ์ดํ›„์—๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ๋” ๋นจ๋ผ์ง„๋‹ค

    //๊ธฐ์กด์˜ ์ด๋ฏธ์ง€๋ฅผ ์„ธํŒ…ํ•˜๋Š” ๋ฐฉ๋ฒ•
    let url = URL(string: "")
    do {
        let data = try Data(contentsOf: url!)
        cell.postImageView.image = UIImage(data: data)
    } catch {
         print("์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์—†์Œ")
    }
    // BUT!!!! Kingfisher๋ฅผ ์ด์šฉํ•œ๋‹ค๋ฉด
    cell.postImageView.setImage(imageUrl: "")

WebView ์‚ฌ์šฉํ•˜๊ธฐ

  • ํ•ด๋‹น ์ด๋ฏธ์ง€๋ทฐ ์ƒ๋‹จ์˜ ๋งํฌ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Youtube๋กœ ์ด๋™ํ•˜๊ณ  ์˜ˆ๊ณ ํŽธ์„ ์ž๋™์œผ๋กœ ์žฌ์ƒํ•˜๋Š” ๊ธฐ๋Šฅ
  • API ํ†ต์‹ ์„ ํ†ตํ•ด ๋ฐ›์•„์˜จ ๋งํฌ๋ฅผ WebView๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ViewController์— ๋„˜๊ฒจ์ฃผ๊ธฐ
      ytUrl = "https://api.themoviedb.org/3/movie/\(myMediaList[sender.tag].id)/videos?api_key=\(APIDocs.TMDB_KEY)&language=en-US"
     
     AF.request(self.ytUrl, method: .get).validate().responseJSON { response in
         switch response.result {
         case .success(let value):
             let json = JSON(value)
             self.link = "https://www.youtube.com/watch?v=" + json["results"][0]["key"].stringValue
         case .failure(let error):
             print(error)
         }
       //๋งํฌ ์ „๋‹ฌ
       viewController.myLink = self.link

์ƒ์„ธ ์ •๋ณด


UILabel๊ณผ ์ƒ์„ธ๋ณด๊ธฐ ๋ฒ„ํŠผ

  • UILabel์˜ numberOfLines๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ์ฃผ๊ธฐ

     // tableView์˜ 0๋ฒˆ ์ธ๋ฑ์Šค์˜ 0๋ฒˆ ์งธ ์…€์„ reload ํ•ด์ฃผ๊ณ 
     @objc func arrowButtonClicked (selectedButton : UIButton){
        clicked.toggle()
        peopleTableView.reloadRows(at: [IndexPath(item: 0, section: 0)], with: .fade)
     }
     // numberOfLines๋ฅผ 0 / 2(์œผ)๋กœ ๋ณ€ํ™˜
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       ...
       clicked == true ? (cell.summaryLabel.numberOfLines = 0) : (cell.summaryLabel.numberOfLines = 2)
     }

์˜ํ™”๊ด€ ์ •๋ณด


์„ ํƒํ•œ ์˜ํ™”๊ด€๋งŒ ๋ณด์—ฌ์ฃผ๊ธฐ

  • ๋ฒ„ํŠผ ๋ฉ”๋‰ด๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๋ฒ„ํŠผ์˜ ํƒ€์ž…์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜๊ณ  ๋งค์นญ

     // 1. ๋ชจ๋“  annotation ์‚ญ์ œ
     let allAnnotations = self.mapView.annotations
     self.mapView.removeAnnotations(allAnnotations)
    
     // 2. ์ง€์ •ํ•ด๋‘” typeํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•ด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋“ค์–ด์˜จ which์™€ ๋น„๊ต ํ›„ ๊ฐ™์€ ๊ฒƒ๋งŒ ์ง€๋„์— annotation
     if mapAnnotations[i].type == which{
    
        let location = CLLocationCoordinate2D(latitude: mapAnnotations[i].latitude, longitude: mapAnnotations[i].longitude)
        let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        let region = MKCoordinateRegion(center: location, span: span)
    
        mapView.setRegion(region, animated: true)
    
        let annotation = MKPointAnnotation()
        annotation.title = mapAnnotations[i].location
        annotation.coordinate = location
    
        mapView.addAnnotation(annotation)
     }

๋‚˜์˜ ๋ฏธ๋””์–ด


์ปฌ๋ ‰์…˜๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋žตํ•˜๊ฒŒ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ViewController

  • XIB๋ฅผ ์‚ฌ์šฉํ•ด Custom Cell ๋“ฑ๋ก

       //XIBํŒŒ์ผ ์—ฐ๊ฒฐ
      let nibName = UINib(nibName: bookCollectionViewCell.identifier, bundle: nil)
      bookCollectionView.register(nibName, forCellWithReuseIdentifier: bookCollectionViewCell.identifier)

๊ตฌ๋™ ์˜์ƒ


๊ฐ ๊ธฐ๋Šฅ๋ณ„๋กœ ๊ตฌ๋™๋˜๋Š” ์˜์ƒ

๋ฉ”์ธ ์ง€๋„ ๋‚˜์˜ ๋ฏธ๋””์–ด ๊ฒ€์ƒ‰
แ„†แ…ฆแ„‹แ…ตแ†ซ แ„Œแ…ตแ„ƒแ…ฉ แ„†แ…กแ„‹แ…ตแ„†แ…ตแ„ƒแ…ตแ„‹แ…ฅ แ„€แ…ฅแ†ทแ„‰แ…ขแ†จ

์˜คํ”ˆ์›จ๋” ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, Alamofire, Kingfisher, SwiftyJSON

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

  • ์œ„์น˜์ •๋ณด์™€ OpenWeatherAPI๋ฅผ ํ™œ์šฉํ•ด ํ•ด๋‹น์ง€์—ญ์˜ ๋‚ ์”จ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

  • APIManager๋ฅผ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ํ™œ์šฉํ•˜์—ฌ OpenWeatherAPI ์‚ฌ์šฉ

 class OpenWeatherAPIManager {
     static let shared = OpenWeatherAPIManager()

     ...

     func fetchData(result: @escaping (Int,JSON)->()) {

         let url = "https://api.openweathermap.org/data/2.5/weather?lat=37.550136619516515&lon=127.073179&appid=(KEY)"
         AF.request(url, method: .get).validate(statusCode: 200...500).responseJSON { response in
             switch response.result{

             case .success(let value):
                 let json = JSON(value)
                 let code = response.response?.statusCode ?? 500
                 result(code, json)
             case .failure(let error):
                 print("Error: ",error)
             }
         }
     }
 }

UI ๋ฐ ๊ตฌ๋™์˜์ƒ

UI ๊ตฌ์„ฑ ๊ตฌ๋™ ์˜์ƒ
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-11 แ„‹แ…ฉแ„’แ…ฎ 10 23 24 ezgif com-gif-maker

์˜ํ™”์ง„ํฅ์œ„์›ํšŒ ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, Alamofire, SwiftyJSON, Realm

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

  • ์˜ํ™”์ง„ํฅ์œ„์›ํšŒ์—์„œ ์ œ๊ณตํ•˜๋Š” API๋ฅผ ์ด์šฉํ•ด ๊ฒ€์ƒ‰ํ•œ ์ผ์ž์— ๋Œ€ํ•ด ๋žญํ‚น์„ ์ œ๊ณตํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

    • ์ด์šฉ์ž๊ฐ€ ๊ฐ™์€ ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๊ธฐ๋•Œ๋ฌธ์— Realm์„ ์ด์šฉํ•ด ๋ฐ›์•„์˜จ ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ
    • ์ž…๋ ฅ๋ฐ›์€ ๋‚ ์งœ์— ์˜ํ•ด TableView๊ฐ€ reload๋  ๋•Œ, ์ด์ „์— ์ €์žฅํ–ˆ๋˜ ๋ฐ์ดํ„ฐ ์ธ์ง€ ํ™•์ธํ•˜๊ณ  ๋ถ„๊ธฐ์ฒ˜๋ฆฌ

Realm

 class DailyMovie : Object{

    @Persisted var name : String

    @Persisted var date : String

    @Persisted var inputDate : String

    @Persisted(primaryKey: true) var movieId : ObjectId

    convenience init(name : String, date: String, inputDate : String){
        self.init()
        self.name = name
        self.date = date
        self.inputDate = inputDate
    }
}

์ •๊ทœ์‹

  • ์˜ํ™”์ง„ํฅ์œ„์›ํšŒ API์˜ ๊ฒฝ์šฐ, ์ง€์›ํ•˜๋Š” ๋‚ ์งœ๊ฐ€ 2004๋…„๋ถ€ํ„ฐ ์ด๋ฏ€๋กœ ์ž…๋ ฅ๋ฐ›๋Š” ๋‚ ์งœ์— ๋Œ€ํ•œ ์ •๊ทœ์‹์ด ํ•„์š”
  let pattern : String = "^20([0-2])([4-9])([0-1])([0-9])([0-1])([0-9])$"
  var text : String
     
  //์ •๊ทœ์‹ ํ™œ์šฉ
  if let _ = dateTextfield.text?.range(of: pattern,options: .regularExpression) {
      text = dateTextfield.text!
  } else {
      showAlert(alertText: "๋‚ ์งœ", alertMessege: "๋‚ ์งœ๋ฅผ ์ž˜๋ชป ์ž…๋ ฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.", alertTitle: "ํ™•์ธ")
      dateTextfield.text = ""
  }

Realm

  • ์ •๋ณด ์กฐํšŒ์‹œ Realm์— ์ด๋ฏธ ํ•ด๋‹น ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
  // ์กด์žฌํ•  ๊ฒฝ์šฐ TableView์˜ ํ•ด๋‹น ๋ฐ์ดํ„ฐ reload
  if !localRealm.objects(DailyMovie.self).filter("inputDate == '\(text)'").isEmpty {
      newDate = text
      self.rankingTableView.reloadData()
  } else {
      // ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ -> API ํ˜ธ์ถœ
      newDate = text
      fetchData(dates: newDate)
  }
  • ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ ์ €์žฅ
 let title = json["boxOfficeResult"]["dailyBoxOfficeList"][item]["movieNm"].stringValue
 let date = json["boxOfficeResult"]["dailyBoxOfficeList"][item]["openDt"].stringValue
 let task = DailyMovie(name: title, date: date, inputDate: dates)

 try! self.localRealm.write{
     self.localRealm.add(task)
 }

UI ๋ฐ ๊ตฌ๋™์˜์ƒ

UI ๊ตฌ์„ฑ ๊ตฌ๋™ ์˜์ƒ
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-11 แ„‹แ…ฉแ„’แ…ฎ 11 08 02 ezgif com-gif-maker

์นด์นด์˜ค OCR ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : AutoLayout, Alamofire, SwiftyJSON

  • AutoLayout์„ ์ด์šฉํ•ด UI๋ฅผ ๊ตฌ์„ฑ

  • Kakao OCR API

    • Header

       let header : HTTPHeaders = [
              "Authorization" : APIDocs.KAKAO,
              "Content-Type" : "multipart/form-data"
        ]
    • ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์•ผ ํ•˜๊ธฐ ๋–„๋ฌธ์— Alamofire์˜ upload ๋ฉ”์„œ๋“œ ์ด์šฉ

        AF.upload(multipartFormData: { multipartFormData in
                  multipartFormData.append(imageData, withName: "image", fileName: "image")
              }, to: EndPoint.OcrURL,headers: header)
                  .validate(statusCode:200...500).responseJSON { response in
                      switch response.result{
                      case .success(let value):
                          let json = JSON(value)
                          print(json)
                          let code = response.response?.statusCode ?? 500
                          result(code, json)
                      case .failure(let error):
                          print(error)
                  }
           }

Punk API ํ”„๋กœ์ ํŠธ

์ •๋ฆฌ
  • Core Skills : SnapKit, Alamofire, Kingfisher, MVVM

  • Punk API๋ฅผ ์ด์šฉํ•ด ๋งฅ์ฃผ๋ฅผ ์ถ”์ฒœํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

  • Model

    • Codable ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒ, CodingKey ํ”„๋กœํ† ์ฝœ ์ฑ„ํƒ ๋ฐ ์ ์šฉ
       // MARK: - Beer
        struct Beer: Codable {
            var foodPairing: [String]
            var name: String
            var tagline: String
            var beerDescription: String
            var imageURL: String
            var id: Int
      
            enum CodingKeys: String, CodingKey {
                case foodPairing = "food_pairing"
                case name
                case tagline
                case beerDescription = "description"
                case imageURL = "image_url"
                case id
            }
        }
  • ViewModel

    • Observable ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ViewModel์—์„œ ์‚ฌ์šฉ

          class BeerViewModel {
      
              var id = Observable(0)
              var imageURL = Observable("")
              var tagLine = Observable("")
              var name = Observable("")
              var beerDescription = Observable("")
              var foodPairing = [Observable("")]
              var foodPairingCnt = Observable(0)
      
            ยทยทยท
      
          }
    • ๋งฅ์ฃผ ์ด๋ฏธ์ง€์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํšจ๊ณผ : ๋ธ”๋Ÿฌ

       let blurEffect = UIBlurEffect(style: .regular)
       var blurEffectView = UIVisualEffectView()
      
       blurEffectView = UIVisualEffectView(effect: blurEffect)
       blurEffectView.contentMode = .scaleAspectFit
       imageView.addSubview(blurEffectView)
  • ๊ฐœ๋ฐœ์ด์Šˆ

    • Stretchy Header: Header Container & ImageView
        // ์ƒ๋‹จ์— ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์„ ๊ณต๊ฐ„์„ ๋งŒ๋“ค๊ธฐ
        headerContainer.snp.makeConstraints { make in
                make.top.equalTo(mainScrollView)
                make.leading.trailing.equalTo(view)
                make.height.equalTo(headerContainer.snp.width).multipliedBy(0.7)
         }
        //์ด๋ฏธ์ง€์˜ ๋†’์ด๋ฅผ ํ—ค๋”์˜ ๋†’์ด์™€ ๊ฐ™๊ฑฐ๋‚˜ ๋” ํฌ๊ฒŒ ์ฃผ๊ณ  priority๋ฅผ ๋†’๊ฒŒ ์ฃผ์–ด์•ผ Stretchyํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ์Œ!
        imageView.snp.makeConstraints { make in
                make.leading.trailing.equalTo(view)
                make.top.equalTo(view)
                make.height.greaterThanOrEqualTo(headerContainer.snp.height).priority(.high)
                make.bottom.equalTo(headerContainer.snp.bottom)
         }
  • ๊ตฌ๋™์˜์ƒ

    ์˜์ƒ
    ezgif com-gif-maker

About


Languages

Language:Swift 100.0%