-๏ธ ๋ฐฐ์ด๊ฒ์ ํ์ตํ๊ณ ์ ์ฉํ๋ ์ฐ์ต์ฅ ๐ฅ
ํ๋ก์ ํธ | ์ค๋ช |
---|---|
๋ฌผ๋ง์๊ธฐ ํ๋ก์ ํธ | 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๋ฅผ ๊ตฌ์ฑ
- ์ ์ ์ ๋๋ค์, ํค, ๋ชธ๋ฌด๊ฒ๋ฅผ 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๋ฅผ ๊ตฌ์ฑ
์ฒซ๋ฒ ์งธ | ๋๋ฒ ์งธ |
---|---|
![]() |
![]() |
-
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๋ฅผ ๊ตฌ์ฑ
* ํ๋กํผํฐ ๊ฐ์์ : 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])
}
}
์ ๋ฆฌ
-
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์ ์ ์ฉ
-
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) } } } } }
-
์ํ ์ ๋ณด๋ฅผ ํด๋ฆญํ ์์ ์์ธ์ ๋ณด์ฐฝ์ผ๋ก 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๋ก ๋ถํฐ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ง๊ณ ์ค๋ ๊ธฐ๋ฅ๋ฟ ์๋๋ผ ์ด๋ฅผ ๋์คํฌ ํน์ ๋ฉ๋ชจ๋ฆฌ ์บ์์ ์ ์ฅํ์ฌ ์ฃผ๊ธฐ ๋๋ฌธ์ ์ต์ด ์ดํ์๋ ์ฒ๋ฆฌ๊ฐ ๋ ๋นจ๋ผ์ง๋ค
//๊ธฐ์กด์ ์ด๋ฏธ์ง๋ฅผ ์ธํ ํ๋ ๋ฐฉ๋ฒ 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: "")
- ํด๋น ์ด๋ฏธ์ง๋ทฐ ์๋จ์ ๋งํฌ ๋ฒํผ์ ๋๋ฅด๋ฉด 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์ 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) }
-
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)
}
}
}
}
์ ๋ฆฌ
-
Core Skills : AutoLayout, Alamofire, SwiftyJSON, Realm
-
AutoLayout์ ์ด์ฉํด UI๋ฅผ ๊ตฌ์ฑ
-
์ํ์งํฅ์์ํ์์ ์ ๊ณตํ๋ API๋ฅผ ์ด์ฉํด ๊ฒ์ํ ์ผ์์ ๋ํด ๋ญํน์ ์ ๊ณตํ๋ ์ ํ๋ฆฌ์ผ์ด์
- ์ด์ฉ์๊ฐ ๊ฐ์ ๋ ์ง์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๊ธฐ๋๋ฌธ์ Realm์ ์ด์ฉํด ๋ฐ์์จ ๋ ์ง์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ
- ์ ๋ ฅ๋ฐ์ ๋ ์ง์ ์ํด TableView๊ฐ reload๋ ๋, ์ด์ ์ ์ ์ฅํ๋ ๋ฐ์ดํฐ ์ธ์ง ํ์ธํ๊ณ ๋ถ๊ธฐ์ฒ๋ฆฌ
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์ ์ด๋ฏธ ํด๋น ๋ ์ง์ ๋ฐ์ดํฐ๊ฐ ์๋์ง ํ์ธ
// ์กด์ฌํ ๊ฒฝ์ฐ 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)
}
์ ๋ฆฌ
-
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) } }
-
์ ๋ฆฌ
-
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 } }
- Codable ํ๋กํ ์ฝ์ ์ฑํ, CodingKey ํ๋กํ ์ฝ ์ฑํ ๋ฐ ์ ์ฉ
-
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) }
- Stretchy Header: Header Container & ImageView
-
๊ตฌ๋์์