[내일배움캠프 37일차] Ch.3 앱 개발 숙련 - 개인 과제2

NH·2025년 4월 22일

내일배움캠프

목록 보기
37/62
post-thumbnail

☎️ 포켓몬 연락처 앱 - 개인 과제2

Lv.5

🔹 요구사항

🧑🏻‍💻 Level 5 - CoreData를 사용하여 연락처 데이터 디스크에 저장하고 불러오기

  • 적용 버튼을 누르면 연락처 데이터(이름/전화번호/프로필이미지)를 디스크에 실제 저장하도록 구현합니다. (데이터 Create) (만약 가능하다면 CoreData 방식, UserDefaults 방식 모두 구현해보세요.)
  • 디스크에 저장됨과 동시에, 메인화면으로 돌아오도록 구현합니다. UINavigationController.popViewController 이용.
  • 메인화면으로 돌아왔을 때, 방금 저장한 연락처 데이터가 보여야합니다. View Lifecycle 을 잘 활용해보세요.
  • 저장된 연락처 데이터들을 메인화면 테이블 뷰의 DataSource 로 적용.

🔹 작성 코드

CoreData 저장 및 불러오기 기능 작성

import Foundation
import CoreData
import UIKit

class CoreDataManager {
    // 싱글톤
    static let shared = CoreDataManager(container: (UIApplication.shared.delegate as! AppDelegate).persistentContainer)
    
    var container: NSPersistentContainer!
    var phoneBooks: [NSManagedObject] = []
    
    // 초기화
    init(container: NSPersistentContainer!) {
        self.container = container
    }
    
    // 데이터 저장
    func createData(imageUrl: String?, name: String, phoneNumber: String) {
        guard let entity = NSEntityDescription.entity(forEntityName: "PhoneBook", in: self.container.viewContext) else { return }
        
        let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
        
        newPhoneBook.setValue(name, forKey: "name")
        newPhoneBook.setValue(phoneNumber, forKey: "phoneNumber")
        
        // UIImage를 CoreData에 저장하려면 Data 타입으로 변환해야 됨.
        if let imageUrl = imageUrl {
            newPhoneBook.setValue(imageUrl, forKey: "imageUrl")
        }
        
        do {
            try self.container.viewContext.save()
            print("문맥 저장 성공")
        } catch {
            print("문맥 저장 실패")
        }
    }
    
    // 데이터 읽기
    func readAllData() {
        // 데이터 요청
        let request = NSFetchRequest<NSManagedObject>(entityName: "PhoneBook")
        
        // 이름 순으로 정렬 (오름차순)
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        
        // 정렬 기준 적용
        request.sortDescriptors = [sortDescriptor]
        
        do {
            phoneBooks = try self.container.viewContext.fetch(request)
            
            for phoneBook in phoneBooks as [NSManagedObject] {
                let name = phoneBook.value(forKey: "name") as? String ?? "이름 없음"
                let phoneNumber = phoneBook.value(forKey: "phoneNumber") as? String ?? "번호 없음"
                if let image = phoneBook.value(forKey: "imageUrl") as? String {
                    print("imageUrl: \(image), name: \(name), phoneNumber: \(phoneNumber)")
                } else {
                    print("imageUrl: 이미지 없음, name: \(name), phoneNumber: \(phoneNumber)")
                }
            }
        } catch {
            print("데이터 읽기 실패")
        }
    }
}

버튼 액션 메소드에 CoreData 쓰기, 읽기 메소드 추가

import UIKit
import CoreData

class PhoneBookViewController: UIViewController {
    private var profileImageUrl: String? // imageUrlString 저장할 프로퍼티 추가
    .
    .
    .
    @objc
    private func didApplyButtonTapped() {
        print("적용 버튼이 탭 되었습니다.")
        
        CoreDataManager.shared.createData(
            imageUrl: profileImageUrl,
            name: nameTextField.text ?? "",
            phoneNumber: phoneNumTextField.text ?? "")
        
        self.navigationController?.popViewController(animated: true) // 전 화면으로 돌아가기
        
    }
    
    @objc
    private func ramdomImageGenerationButtonTapped() {
        print("랜덤 이미지를 생성합니다.")
        
        FetchAPI.shared.fetchPokemonImage { [weak self] image, imageUrlStr in
            guard let self else { return }
            
            DispatchQueue.main.async {
                self.profileImageView.image = image
                self.profileImageUrl = imageUrlStr // CoreData에 저장
            }
        }
    }
} 

테이블 뷰 셀에 CoreData에 저장된 값 불러오기

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("viewWillAppear")
        // 네비게이션 바 숨기기
        self.navigationController?.setNavigationBarHidden(true, animated: false)
        
        CoreDataManager.shared.readAllData()
        tableView.reloadData()
    }

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return CoreDataManager.shared.phoneBooks.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: PhoneBookTableViewCell.id, for: indexPath) as? PhoneBookTableViewCell else { return UITableViewCell() }
        
        let phoneBook = CoreDataManager.shared.phoneBooks[indexPath.row]
        let imageUrl = phoneBook.value(forKey: "imageUrl") as? String ?? ""
        let name = phoneBook.value(forKey: "name") as? String ?? ""
        let phoneNumber = phoneBook.value(forKey: "phoneNumber") as? String ?? ""
        
        cell.configureCell(imageUrl: imageUrl, name: name, phoneNumber: phoneNumber)
        
        return cell
    }
}

cell 데이터 설정 메소드 수정

import UIKit

class PhoneBookTableViewCell: UITableViewCell {
    public func configureCell(imageUrl: String?, name: String, phoneNumber: String) {
        nameLabel.text = name
        phoneNumLabel.text = phoneNumber
        
        // 이미지 URL이 없다면 기본 이미지로 설정
        guard let imageUrl = imageUrl, let url = URL(string: imageUrl) else {
            pokemonImageView.image = nil
            return
        }
        
        // URLSession으로 비동기 이미지 로드
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self else { return }
            
            if let data = data, let image = UIImage(data: data) {
                DispatchQueue.main.async {
                    self.pokemonImageView.image = image
                }
            } else {
                DispatchQueue.main.async {
                    self.pokemonImageView.image = nil // 실패 시 기본 이미지 설정
                }
            }
        }.resume()
    }
}

🔹 결과 화면

🔹 느낀 점

  • 와...CoreData 너무 어렵다..
  • 온라인 강의가 없었다면 구현 못했을 것이다. 강의를 3번 돌려본것 같지만, 아직도 코드 구조라던지 흐름이 완벽하게 이해가 가지 않는다.
  • 돌아보면 어떻게 기능을 구현했는지 싶다...
  • 뭔가 기능을 구현한 면에서는 뿌듯하지만, 코드를 보면 내가 작성했지만 잘 모르는게..흠..반성을 하게 된다.
  • CoreData는 좀 더 공부를 해야될 것 같다.
profile
iOS 개발 블로그

0개의 댓글