🧑🏻💻 Level 6
NSSortDescriptor 사용하면 CoreData에 저장된 데이터를 정렬 가능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("데이터 읽기 실패")
}
}

🧑🏻💻 Level 7
UITableViewCell 을 클릭했을 때도 PhoneBookViewController 페이지로 이동되게 합니다.PhoneBookViewController 와 별개의 새로운 ViewController 클래스를 선언해서 생성하지 말아주세요. 그대로 PhoneBookViewController 를 사용해서 띄웁니다. 이미 구현되어있는 뷰컨트롤러를 재활용하도록 합니다.PhoneBookViewController 페이지로 이동extension ViewController: UITableViewDelegate {
.
.
.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let pv = PhoneBookViewController()
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 ?? ""
// 예외처리: 이미지 URL이 없다면 기본 이미지로 설정
guard let url = URL(string: imageUrl) else {
pv.profileImageView.image = nil
pv.nameTextField.text = name
pv.phoneNumTextField.text = phoneNumber
self.navigationController?.pushViewController(pv, animated: true)
return
}
// URLSession으로 비동기 이미지 로드
URLSession.shared.dataTask(with: url) { data, response, error in
//guard let self = self else { return }
if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async {
pv.profileImageView.image = image
}
} else {
DispatchQueue.main.async {
pv.profileImageView.image = nil // 실패 시 기본 이미지 설정
}
}
}.resume()
pv.nameTextField.text = name
pv.phoneNumTextField.text = phoneNumber
self.navigationController?.pushViewController(pv, animated: true)
}
}
PhoneBookViewController 객체에 navigationItem.title = name 을 설정 해주었지만 되지 않았다.extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let pv = PhoneBookViewController()
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 ?? ""
guard let url = URL(string: imageUrl) else {
pv.profileImageView.image = nil
pv.nameTextField.text = name
pv.phoneNumTextField.text = phoneNumber
self.navigationController?.pushViewController(pv, animated: true)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async {
pv.profileImageView.image = image
}
} else {
DispatchQueue.main.async {
pv.profileImageView.image = nil
}
}
}.resume()
pv.nameTextField.text = name
pv.phoneNumTextField.text = phoneNumber
// 추가한 코드, 하지만 동작 안됨
pv.navigationItem.title = "hhhh"
self.navigationController?.pushViewController(pv, animated: true)
}
}

원인 분석
PhoneBookViewController의viewDidLoad()에서Title의 값을 지정하고 있기 때문!!
ViewController 코드 추가let pv = PhoneBookViewController()
pv.contactName = name // Title 값 넘겨주기
self.navigationController?.pushViewController(pv, animated: true) // 이때 같이 넘어감
PhoneBookViewController 코드 추가class PhoneBookViewController: UIViewController {
public var contactName: String? // title 변경에 사용할 변수
override func viewDidLoad() {
super.viewDidLoad()
// 조건문으로 Title 값을 설정
if let contactName = contactName {
self.navigationItem.title = contactName
} else {
self.navigationItem.title = "연락처 추가"
}
}
🧑🏻💻 Level 8
// 데이터 읽기
func readAllData() {
// 데이터 요청
let request = NSFetchRequest<NSManagedObject>(entityName: PhoneBook.className)
// 이름 순으로 정렬 (오름차순)
let sortDescriptor = NSSortDescriptor(key: PhoneBook.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: PhoneBook.Key.name) as? String ?? "이름 없음"
let phoneNumber = phoneBook.value(forKey: PhoneBook.Key.phoneNumber) as? String ?? "번호 없음"
if let image = phoneBook.value(forKey: PhoneBook.Key.imageUrl) as? String {
print("imageUrl: \(image), name: \(name), phoneNumber: \(phoneNumber)")
} else {
print("imageUrl: 이미지 없음, name: \(name), phoneNumber: \(phoneNumber)")
}
}
} catch {
print("데이터 읽기 실패")
}
}
isUpdate 변수를 사용하여 셀을 탭하면 데이터를 업데이트 하는 로직과 추가 버튼을 누르면 데이터를 추가하는 로직 분기 처리import UIKit
import CoreData
class PhoneBookViewController: UIViewController {
public var PhoneBookPhoneNumber: String?
public var PhoneBookImageUrl: String?
public var isUpdate: Bool = false // 적용 버튼 탭 시, 추가를 할 것인지 업데이트를 할 것인지 정하는 변수
public var PhoneBookName: String? // title 변경에 사용할 변수
@objc
private func didApplyButtonTapped() {
print("적용 버튼이 탭 되었습니다.")
if isUpdate {
CoreDataManager.shared.updateData(
currentImageUrl: PhoneBookImageUrl ?? "",
updateImageUrl: profileImageUrl ?? "",
currentName: PhoneBookName ?? "",
updateName: nameTextField.text ?? "",
currentPhoneNumber: PhoneBookPhoneNumber ?? "",
updatePhoneNumber: phoneNumTextField.text ?? ""
)
} else {
CoreDataManager.shared.createData(
imageUrl: profileImageUrl,
name: nameTextField.text ?? "",
phoneNumber: phoneNumTextField.text ?? ""
)
}
}
}
isUpdate 변수가 true 로 변경됨extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
pv.PhoneBookName = name
pv.PhoneBookPhoneNumber = phoneNumber
pv.PhoneBookImageUrl = imageUrl
pv.isUpdate = true
}
nil 값으로 저장 // CoreData 에서 데이터 업데이트
func updateData(currentImageUrl: String,
updateImageUrl: String,
currentName: String,
updateName: String,
currentPhoneNumber: String,
updatePhoneNumber: String) {
let fetchRequest = PhoneBook.fetchRequest()
// predicate 는 조건을 걸어주는 구문
fetchRequest.predicate = NSPredicate(format: "imageUrl == %@", currentImageUrl)
do {
let result = try self.container.viewContext.fetch(fetchRequest)
for data in result as [NSManagedObject] {
data.setValue(updateImageUrl, forKey: PhoneBook.Key.imageUrl)
}
try self.container.viewContext.save()
print("데이터 수정 성공")
} catch {
print("데이터 수정 실패")
}
fetchRequest.predicate = NSPredicate(format: "name == %@", currentName)
do {
let result = try self.container.viewContext.fetch(fetchRequest)
for data in result as [NSManagedObject] {
data.setValue(updateName, forKey: PhoneBook.Key.name)
}
try self.container.viewContext.save()
print("데이터 수정 성공")
} catch {
print("데이터 수정 실패")
}
fetchRequest.predicate = NSPredicate(format: "phoneNumber == %@", currentPhoneNumber)
do {
let result = try self.container.viewContext.fetch(fetchRequest)
for data in result as [NSManagedObject] {
data.setValue(updatePhoneNumber, forKey: PhoneBook.Key.phoneNumber)
}
try self.container.viewContext.save()
print("데이터 수정 성공")
} catch {
print("데이터 수정 실패")
}
}
}
func updateData(currentImageUrl: String,
updateImageUrl: String,
currentName: String,
updateName: String,
currentPhoneNumber: String,
updatePhoneNumber: String) {
let fetchRequest = PhoneBook.fetchRequest()
// predicate 는 조건을 걸어주는 구문
// 예외처리: imageUrl 이 "" 면 nil 인 값을 찾게 함.
fetchRequest.predicate = NSPredicate(format: "(imageUrl == %@ OR imageUrl == nil) AND name == %@ AND phoneNumber == %@", currentImageUrl, currentName, currentPhoneNumber)
do {
let result = try self.container.viewContext.fetch(fetchRequest)
if let objectToUpdate = result.first {
objectToUpdate.setValue(updateImageUrl, forKey: "imageUrl")
objectToUpdate.setValue(updateName, forKey: "name")
objectToUpdate.setValue(updatePhoneNumber, forKey: "phoneNumber")
try self.container.viewContext.save()
print("데이터 수정 성공")
} else {
print("일치하는 데이터 없음")
}
} catch {
print("데이터 수정 실패")
}
}
}

🧑🏻💻 Challenge

clipsToBounds 라는 속성을 사용하면, View 영역 밖의 image를 표시하지 않는다!!public let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .white
imageView.layer.borderColor = UIColor.gray.cgColor
imageView.layer.borderWidth = 3
imageView.layer.cornerRadius = 80
// 추가한 코드
imageView.clipsToBounds = true
return imageView
}()
prepareForReuse()를 사용하여 재사용 셀 초기화
- 사용하는 이유
- Cell의 개수가 많아지면 셀을 재사용을 함.
- 재사용 하면서 스크롤 시, 이전 이미지나 텍스트가 잠간 보이는 현상이 발생함!
- prepareForReuse() 사용하면 셀을 다시 사용하기 전에 초기화 가능!!
- 즉, "이 셀, 다른 데이터 보여주기 전에 깨끗이 지워 놓는다!!" 라는 개념.
class PhoneBookTableViewCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
// 셀을 초기화
pokemonImageView.image = nil
nameLabel.text = ""
phoneNumLabel.text = ""
}
}

문자열 my_str과 n이 매개변수로 주어질 때, my_str을 길이 n씩 잘라서 저장한 배열을 return하도록 solution 함수를 완성해주세요.
1 ≤ my_str의 길이 ≤ 100
1 ≤ n ≤ my_str의 길이
my_str은 알파벳 소문자, 대문자, 숫자로 이루어져 있습니다.
import Foundation
func solution(_ my_str:String, _ n:Int) -> [String] {
return []
}
다음날 튜터님께서 힌트를 주셨다!!!!
스트링 변수를 선언 후 변수 n과 스트링 변수 길이가 같으면 배열에 넣는 방법
import Foundation
func solution(_ my_str:String, _ n:Int) -> [String] {
var string: String = ""
var result:[String] = []
for i in my_str {
string += String(i)
if string.count == n {
result.append(string)
string = ""
}
}
return result
}
my_str 의 길이가 n으로 나누어 떨어지지 않으면 result 배열에 남은 문자열을 저장하지 않는다.import Foundation
func solution(_ my_str:String, _ n:Int) -> [String] {
var string: String = ""
var result:[String] = []
var count = 1
for i in my_str {
string += String(i)
if string.count == n {
result.append(string)
string = ""
} else if my_str - count < n {
if my_str - count == 0 {
result.append(string)
string = ""
}
}
count += 1
}
return result
}
count라는 변수를 만들어서 반복문이 돌때마다 숫자가 1씩 증가하게 했다.my_str에서 count 값을 뺀 값이 n 보다 작을때의 조건문을 만들었다.my_str에서 count 값을 뺀 값이 0과 같으면 배열에 저장하게 했다.my_str에서 count 값을 빼려고 해서!!!my_str.count를 빼야지!!import Foundation
func solution(_ my_str:String, _ n:Int) -> [String] {
var string: String = ""
var result:[String] = []
var count = 1
for i in my_str {
string += String(i)
if string.count == n {
result.append(string)
string = ""
} else if my_str.count - count < n {
if my_str.count - count == 0 {
result.append(string)
string = ""
}
}
count += 1
}
return result
}
