24.07.17 Today I Learned
2024. 7. 17. 17:35
Lv.5까지 구현했다. Lv.4부분에서 데이터를 가져오는 방법은 이해가 안돼서 공부를 따로 해봐야겠다.
// Friend.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import Foundation
// 연락처 정보를 저장할 구조체 정의
struct Friend: Codable {
let name: String // 친구의 이름
let phoneNumber: String // 친구의 전화번호
let profileImageData: Data? // 친구의 프로필 이미지 데이터를 저장 (선택 사항)
}
// ContactManager.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import Foundation
class ContactManager {
static let shared = ContactManager() // 싱글톤 인스턴스
private let contactsKey = "contacts" // 데이터를 저장할 키
// 연락처 데이터를 디스크에 저장하는 메서드
func saveFriend(_ friend: Friend) {
var friends = loadFriends() // 기존의 연락처 데이터를 불러옴
friends.append(friend) // 새로운 연락처 데이터를 배열에 추가
if let data = try? JSONEncoder().encode(friends) {
UserDefaults.standard.set(data, forKey: contactsKey) // 인코딩된 JSON 데이터를 UserDefaults에 저장
}
}
// 디스크에서 연락처 데이터를 불러오는 메서드
func loadFriends() -> [Friend] {
if let data = UserDefaults.standard.data(forKey: contactsKey), // UserDefaults에서 데이터를 불러옴
let friends = try? JSONDecoder().decode([Friend].self, from: data) { // 불러온 데이터를 디코딩하여 [Friend] 배열로 변환
return friends // 성공하면 연락처 배열을 반환
}
return [] // 실패하면 빈 배열을 반환
}
}
여기까지 Model
// FriendListViewLabel.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
import SnapKit
class FriendListViewLabel: UIView {
let titleLabel = UILabel() // 친구 목록 레이블
let addButton = UIButton() // 추가 버튼
let tableView = UITableView() // 테이블 뷰
override init(frame: CGRect) {
super.init(frame: frame)
setupUI() // UI 설정 메서드 호출
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .white // 배경색 설정
// 친구 목록 레이블 설정
titleLabel.text = "친구 목록"
titleLabel.font = UIFont.boldSystemFont(ofSize: 20) // 글자 크기 설정
addSubview(titleLabel) // 레이블 추가
// 추가 버튼 설정
addButton.setTitle("추가", for: .normal) // 버튼 제목 설정
addButton.setTitleColor(.lightGray, for: .normal) // 버튼 글자 색 설정
addButton.backgroundColor = .clear // 버튼 배경색 설정
addSubview(addButton) // 버튼 추가
// 테이블 뷰 추가
addSubview(tableView) // 테이블 뷰 추가
// 제약조건 설정
setupConstraints()
}
private func setupConstraints() {
// 친구 목록 레이블 제약조건
titleLabel.snp.makeConstraints {
$0.top.equalTo(safeAreaLayoutGuide).offset(0) // 안전 영역 위쪽 여백
$0.centerX.equalToSuperview() // 수평 중앙 정렬
}
// 추가 버튼 제약조건
addButton.snp.makeConstraints {
$0.right.equalToSuperview().offset(-16) // 오른쪽 여백
$0.centerY.equalTo(titleLabel) // 레이블과 수직 중앙 정렬
$0.height.equalTo(50) // 높이 설정
$0.width.equalTo(80) // 넓이 설정
}
// 테이블 뷰 제약조건
tableView.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(16) // 레이블 아래 여백
$0.left.right.bottom.equalToSuperview() // 좌우 및 하단 여백 설정
}
}
}
// TableView.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
// 테이블 뷰 데이터 소스와 델리게이트를 구현하는 클래스
class FriendTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
var friends: [Friend] = [] // 친구 목록 배열
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
self.dataSource = self // 데이터 소스 설정
self.delegate = self // 델리게이트 설정
self.register(TableViewCell.self, forCellReuseIdentifier: "cell") // 커스텀 셀 등록
self.backgroundColor = .white // 테이블 뷰 배경색 설정
self.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) // 구분선 여백
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 섹션 당 행의 수를 반환하는 메서드 (필수 데이터 소스 메서드)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count // 친구 목록의 수 반환
}
// 각 셀에 대한 설정을 수행하는 메서드 (필수 데이터 소스 메서드)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 재사용 가능한 셀을 가져옴
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
// 셀에 친구 이름과 전화번호 설정
cell.nameLabel.text = friends[indexPath.row].name
cell.phoneNumberLabel.text = friends[indexPath.row].phoneNumber
// 프로필 이미지 설정
if let imageData = friends[indexPath.row].profileImageData {
cell.profileImageView.image = UIImage(data: imageData)
} else {
cell.profileImageView.image = UIImage(systemName: "person.circle")
}
return cell // 설정된 셀 반환
}
// 테이블 뷰 셀의 높이를 반환하는 메서드 (선택적 델리게이트 메서드)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80 // 셀 높이 설정
}
}
// TableViewCell.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
import SnapKit
class TableViewCell: UITableViewCell {
let profileImageView = UIImageView() // 프로필 사진 이미지 뷰
let nameLabel = UILabel() // 이름 레이블
let phoneNumberLabel = UILabel() // 전화번호 레이블
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI() // UI 설정 메서드 호출
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
profileImageView.contentMode = .scaleAspectFill // 이미지 비율 유지
profileImageView.layer.cornerRadius = 30 // 원형으로 만들기
profileImageView.clipsToBounds = true // 코너 잘리게 설정
profileImageView.layer.borderWidth = 2 // 테두리 두께 설정
profileImageView.layer.borderColor = UIColor.lightGray.cgColor // 테두리 색 설정
addSubview(profileImageView) // 이미지 뷰 추가
nameLabel.font = UIFont.systemFont(ofSize: 16)
addSubview(nameLabel) // 이름 레이블 추가
phoneNumberLabel.font = UIFont.systemFont(ofSize: 16) // 일반체로 설정
addSubview(phoneNumberLabel) // 전화번호 레이블 추가
setupConstraints() // 제약조건 설정
}
private func setupConstraints() {
profileImageView.snp.makeConstraints {
$0.width.height.equalTo(60) // 프로필 이미지 크기 설정
$0.left.equalToSuperview().offset(16) // 왼쪽 여백 설정
$0.centerY.equalToSuperview() // 수직 중앙 정렬
}
nameLabel.snp.makeConstraints {
$0.left.equalTo(profileImageView.snp.right).offset(30) // 프로필 이미지 오른쪽 여백 설정
$0.centerY.equalToSuperview() // 수직 중앙 정렬
}
phoneNumberLabel.snp.makeConstraints {
$0.left.equalTo(nameLabel.snp.right).offset(8) // 이름 레이블 오른쪽 여백 설정
$0.centerY.equalToSuperview() // 수직 중앙 정렬
$0.right.equalToSuperview().offset(-30) // 오른쪽 여백 설정
}
}
}
// AddFriendView.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
import SnapKit
// AddFriendView 클래스 정의
class AddFriendView: UIView {
let addLabel = UILabel() // 추가 레이블
let profileImageView = UIImageView() // 프로필 이미지 뷰
let applyButton = UIButton() // 적용 버튼
let randomImageButton = UIButton() // 랜덤 이미지 버튼
let nameTextView = UITextView() // 이름 입력 텍스트 뷰
let phoneTextView = UITextView() // 전화번호 입력 텍스트 뷰
// 초기화 메서드
override init(frame: CGRect) {
super.init(frame: frame)
setupUI() // UI 설정 메서드 호출
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// UI 설정 메서드
private func setupUI() {
backgroundColor = .white // 배경색 설정
// 프로필 이미지 뷰 설정
profileImageView.contentMode = .scaleAspectFill // 이미지 비율 유지
profileImageView.layer.cornerRadius = 100 // 원형으로 만들기
profileImageView.clipsToBounds = true // 코너 잘리게 설정
profileImageView.layer.borderWidth = 4 // 테두리 두께 설정
profileImageView.layer.borderColor = UIColor.lightGray.cgColor // 테두리 색 설정
addSubview(profileImageView) // 이미지 뷰 추가
// 적용 버튼 설정
applyButton.setTitle("적용", for: .normal) // 버튼 제목 설정
applyButton.setTitleColor(.blue, for: .normal) // 버튼 글자 색 설정
applyButton.backgroundColor = .clear // 버튼 배경색 설정
addSubview(applyButton) // 버튼 추가
// 랜덤 이미지 버튼 설정
randomImageButton.setTitle("랜덤 이미지 생성", for: .normal) // 버튼 제목 설정
randomImageButton.setTitleColor(.lightGray, for: .normal) // 버튼 글자 색 설정
randomImageButton.backgroundColor = .clear // 버튼 배경색 설정
randomImageButton.titleLabel?.font = UIFont.systemFont(ofSize: 16) // 버튼 글꼴 설정
addSubview(randomImageButton) // 버튼 추가
// 이름 입력 텍스트 뷰 설정
nameTextView.font = UIFont.systemFont(ofSize: 16) // 글꼴 설정
nameTextView.layer.borderColor = UIColor.lightGray.cgColor // 테두리 색 설정
nameTextView.layer.borderWidth = 1 // 테두리 두께 설정
nameTextView.layer.cornerRadius = 5 // 코너 둥글게 설정
addSubview(nameTextView) // 텍스트 뷰 추가
// 전화번호 입력 텍스트 뷰 설정
phoneTextView.font = UIFont.systemFont(ofSize: 16) // 글꼴 설정
phoneTextView.layer.borderColor = UIColor.lightGray.cgColor // 테두리 색 설정
phoneTextView.layer.borderWidth = 1 // 테두리 두께 설정
phoneTextView.layer.cornerRadius = 5 // 코너 둥글게 설정
addSubview(phoneTextView) // 텍스트 뷰 추가
setupConstraints() // 제약조건 설정 메서드 호출
randomImageButton.addTarget(self, action: #selector(randomImageTapped), for: .touchUpInside) // 랜덤 이미지 버튼 클릭 시 동작 설정
}
// 랜덤 이미지 버튼 클릭 시 호출 메서드
@objc private func randomImageTapped() {
randomImageButtonTapped?()
}
// 랜덤 이미지 버튼 클릭 시 동작 클로저
var randomImageButtonTapped: (() -> Void)?
// 제약조건 설정 메서드
private func setupConstraints() {
profileImageView.snp.makeConstraints {
$0.width.height.equalTo(200) // 프로필 이미지 크기 설정
$0.centerX.equalToSuperview() // 수평 중앙 정렬
$0.top.equalTo(safeAreaLayoutGuide).offset(20) // 안전 영역 위쪽 여백
}
randomImageButton.snp.makeConstraints {
$0.centerX.equalToSuperview() // 수평 중앙 정렬
$0.top.equalTo(profileImageView.snp.bottom).offset(10) // 프로필 이미지 아래 여백
$0.height.equalTo(40) // 높이 설정
$0.width.equalTo(150) // 넓이 설정
}
nameTextView.snp.makeConstraints {
$0.top.equalTo(randomImageButton.snp.bottom).offset(20) // 랜덤 이미지 버튼 아래 여백
$0.left.equalToSuperview().offset(16) // 왼쪽 여백
$0.right.equalToSuperview().offset(-16) // 오른쪽 여백
$0.height.equalTo(40) // 높이 설정
}
phoneTextView.snp.makeConstraints {
$0.top.equalTo(nameTextView.snp.bottom).offset(5) // 이름 텍스트 뷰 아래 여백
$0.left.equalToSuperview().offset(16) // 왼쪽 여백
$0.right.equalToSuperview().offset(-16) // 오른쪽 여백
$0.height.equalTo(40) // 높이 설정
}
}
}
여기까지 View
// FriendListViewController.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
import SnapKit
class FriendListViewController: UIViewController {
let friendListView = FriendListViewLabel() // 친구 목록 뷰
var friends: [Friend] = [] // 연락처 데이터 배열
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white // 배경색 설정
view.addSubview(friendListView) // 친구 목록 뷰 추가
friendListView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide) // 안전 영역에 맞추기
}
// 테이블 뷰 데이터 소스와 델리게이트 설정
friendListView.tableView.dataSource = self
friendListView.tableView.delegate = self
// 추가 버튼 액션 설정
friendListView.addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 저장된 연락처 데이터를 로드
friends = ContactManager.shared.loadFriends()
// 테이블 뷰를 리로드하여 데이터를 업데이트
friendListView.tableView.reloadData()
}
@objc private func addButtonTapped() {
// 연락처 추가 화면으로 이동
let addFriendVC = AddFriendViewController()
navigationController?.pushViewController(addFriendVC, animated: true)
}
}
extension FriendListViewController: UITableViewDataSource, UITableViewDelegate {
// 섹션 당 행의 수 반환
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
// 각 셀에 대한 설정
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let friend = friends[indexPath.row]
cell.nameLabel.text = friend.name // 친구 이름 설정
cell.phoneNumberLabel.text = friend.phoneNumber // 친구 전화번호 설정
if let imageData = friend.profileImageData {
cell.profileImageView.image = UIImage(data: imageData) // 프로필 이미지 설정
} else {
cell.profileImageView.image = UIImage(systemName: "person.circle") // 기본 프로필 이미지 설정
}
return cell
}
// 셀 높이 설정
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
// AddFriendViewController.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
import UIKit
class AddFriendViewController: UIViewController {
let addFriendView = AddFriendView() // 연락처 추가 뷰
let randomImageFetcher = RandomImageFetcher() // 랜덤 이미지 가져오는 클래스
override func viewDidLoad() {
super.viewDidLoad()
setupUI() // UI 설정
// 랜덤 이미지 버튼이 클릭되었을 때 호출될 클로저 설정
addFriendView.randomImageButtonTapped = { [weak self] in
self?.fetchRandomImage()
}
}
private func setupUI() {
// 연락처 추가 뷰를 뷰에 추가
view.addSubview(addFriendView)
// 제약조건 설정
addFriendView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
title = "연락처 추가" // 네비게이션 타이틀 설정
// 네비게이션 바에 적용 버튼 추가
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "적용", style: .plain, target: self, action: #selector(applyButtonTapped))
}
@objc private func applyButtonTapped() {
// 이름과 전화번호를 가져옴
let name = addFriendView.nameTextView.text ?? ""
let phoneNumber = addFriendView.phoneTextView.text ?? ""
// 프로필 이미지를 데이터로 변환
let profileImage = addFriendView.profileImageView.image
let profileImageData = profileImage?.jpegData(compressionQuality: 0.8)
// 연락처 데이터를 생성
let friend = Friend(name: name, phoneNumber: phoneNumber, profileImageData: profileImageData)
// 연락처 데이터를 저장
ContactManager.shared.saveFriend(friend)
// 메인 화면으로 돌아가기
navigationController?.popViewController(animated: true)
}
private func fetchRandomImage() {
// 랜덤 이미지를 가져오는 메서드 호출
randomImageFetcher.fetchRandomImage { [weak self] image in
DispatchQueue.main.async {
// 가져온 이미지를 프로필 이미지 뷰에 설정
self?.addFriendView.profileImageView.image = image
}
}
}
}
// RandomImageFetcher.swift
// PokeapiProject
//
// Created by t2023-m0112 on 7/16/24.
//
// JSON 응답을 파싱하고 이미지 URL을 추출
// JSON에서 URL을 가져온 후 데이터를 다운로드하여 이미지로 사용하는 경우, 데이터를 일시적으로 저장하는 곳이 필요하지만, 그러나 이 데이터는 메모리 내에서 처리되므로 디스크에 저장할 필요가 없다. 이미지를 다운로드하고 이를 UIImage로 변환한 후, 이를 UI에 반영한다. 이는 주로 메모리를 사용하여 처리된다. -> gpt 피셜
// <Json은 gpt에게 물어가면서해서 이해가 아직 안되므로 따로 더 공부하기>
import UIKit
// 랜덤 이미지를 가져오는 클래스를 정의
class RandomImageFetcher {
// 랜덤 이미지를 가져오는 메서드
func fetchRandomImage(completion: @escaping (UIImage?) -> Void) {
// 1부터 100 사이의 랜덤 숫자를 생성
let randomNumber = Int.random(in: 1...100)
// 랜덤 숫자를 포함한 URL 문자열을 생성
let urlString = "https://pokeapi.co/api/v2/pokemon/\(randomNumber)"
// URL 문자열을 URL 객체로 변환
guard let url = URL(string: urlString) else {
completion(nil) // URL이 유효하지 않으면 nil을 반환
return
}
// URL 요청 객체를 생성하고, HTTP 메서드를 GET으로 설정
var request = URLRequest(url: url)
request.httpMethod = "GET"
// URLSession을 사용하여 데이터 태스크를 생성 및 실행
URLSession.shared.dataTask(with: request) { data, response, error in
// 에러가 발생한 경우, 에러를 출력하고 nil을 반환
if let error = error {
print("Error fetching data: \(error)")
DispatchQueue.main.async {
completion(nil)
}
return
}
// 데이터가 없는 경우, 로그를 출력하고 nil을 반환
guard let data = data else {
print("No data received")
DispatchQueue.main.async {
completion(nil)
}
return
}
do {
// JSON 데이터를 파싱하여 딕셔너리로 변환
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
// JSON 응답을 출력하여 확인
print("JSON response: \(json)")
// JSON에서 이미지 URL을 추출
if let sprites = json["sprites"] as? [String: Any],
let imageUrlString = sprites["front_default"] as? String,
let imageUrl = URL(string: imageUrlString) {
// 이미지 다운로드 메서드를 호출
self.downloadImage(from: imageUrl, completion: completion)
} else {
// JSON 구조가 예상과 다른 경우
print("Invalid JSON structure")
DispatchQueue.main.async {
completion(nil)
}
}
} else {
// JSON 파싱이 실패한 경우
print("Invalid JSON structure")
DispatchQueue.main.async {
completion(nil)
}
}
} catch {
// JSON 파싱 중 에러가 발생한 경우
print("Error parsing JSON: \(error)")
DispatchQueue.main.async {
completion(nil)
}
}
}.resume() // 데이터 태스크를 시작
}
// URL에서 이미지를 다운로드하는 메서드
private func downloadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
// URLSession을 사용하여 데이터 태스크를 생성 및 실행
URLSession.shared.dataTask(with: url) { data, response, error in
// 에러가 발생한 경우, 에러를 출력하고 nil을 반환
if let error = error {
print("Error downloading image: \(error)")
DispatchQueue.main.async {
completion(nil)
}
return
}
// 데이터가 없거나 이미지 데이터가 유효하지 않은 경우
guard let data = data, let image = UIImage(data: data) else {
print("No data or invalid image data")
DispatchQueue.main.async {
completion(nil)
}
return
}
// 다운로드한 이미지를 반환
DispatchQueue.main.async {
completion(image)
}
}.resume() // 데이터 태스크를 시작
}
}
여기까지 Controller
'Today I Learned > 2024' 카테고리의 다른 글
24.07.22 Today I Learned (0) | 2024.07.22 |
---|---|
24.07.18 Today I Learned (0) | 2024.07.18 |
24.07.16 Today I Learned (0) | 2024.07.16 |
24.07.15 Today I Learned (2) | 2024.07.15 |
24.07.11 Today I Learned (0) | 2024.07.11 |