SSAC_iOS_Day 12 | TIL

린다·2021년 10월 14일
0

SSAC_iOS_Dev

목록 보기
8/33
post-thumbnail

👩‍💻 수업 & 추가 스터디

📂 TableViewCell Data 반영

1) BoxOfficeProject
1. Movie라는 이름의 Model 파일 생성
Movie.swift

struct Movie {
	let title: String
    let releaseDate: String
    let runtime: Int
    let overview: String
    let rate: Double
}
  1. MovieInformation이라는 이름의 data를 가진 파일 생성

MovieInformation.swift

struct MovieInformation {
	let movie: [Movie] = [
    Movie(title: "암살", releaseDate: "2020.02.02", runtime: 139, overview: "줄거리줄거리", rate: 9),
    Movie(title: "암살", releaseDate: "2020.02.02", runtime: 139, overview: "줄거리줄거리", rate: 9)
    Movie(title: "암살", releaseDate: "2020.02.02", runtime: 139, overview: "줄거리줄거리", rate: 9) 
    ]
}
  1. 이에 맞게 TableViewController 코드 수정
  • 구조체 인스턴스 생성
let movieInformation = MovieInformation()
  • 배열의 내용이나 갯수가 변경돼도 반영될 수 있도록 행의 갯수 = 배열의 원소 갯수
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return movieInformation.movie.count
    }
  • Cell의 내용 업데이트
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 타입 캐스팅
        guard let cell = tableView.dequeueReusableCell(withIdentifier: BoxOfficeTableViewCell.identifier, for: indexPath) as? BoxOfficeTableViewCell else {
            return UITableViewCell()
        }
       
        let row = movieInformation.movie[indexPath.row]
        
        cell.posterImageView.image = UIImage(named: row.title)
        cell.titleLabel.text = row.title
        cell.releaseDateLabel.text = row.releaseDate
        cell.overviewLabel.text = row.overview
        cell.overviewLabel.numberOfLines = 0
        
        return cell
    }
// Keyword, 예약어 -> 변수로 인식이 안됨, 평소에는 사용을 지양하지만 ...
// 이렇게 묶어주면 사용할 수 있음~!
let `switch` = movieInformation.movie[indexPath.row]

2) MemoProject
1. 스토리보드에 Segmented Control 추가
2. Memo.swift 파일 생성 및 Struct 코드 작성

enum Category: Int {
    case business = 0, perosnal, others
    
    var description: String {
        switch self {
        case .business:
            return "업무"
        case .perosnal:
            return "개인"
        case .others:
            return "기타"
        }
    }
}

struct Memo {
    var content: String
    var category: Category
}
  1. 메모 내용 가지고 있을 배열 선언. 이때 형식은 방금 구현한 Memo 구조체 형식으로.
    var list: [Memo] = [] {
        didSet {
            saveData()
        }
    }
  1. save Button 눌렸을 때 발생하는 이벤트 코드 수정
if let text = memoTextView.text {
// Textview에 text가 있는 경우에
            let segmentIndex = categorySegmentedControl.selectedSegmentIndex
            // segmented control에서 사용자가 마지막으로 터치한 index값을 int로 가져옴
            let segmentCategory = Category(rawValue: segmentIndex)!
            // 해당 int를 rawvalue로 가지고 있는 category 객체를 할당
            let memo = Memo(content: text, category: segmentCategory)
            // 받은 정보를 기반으로 Memo 인스턴스 생성
            list.append(memo)
            // list에 객체 추가
        } else {
            print("내용 입력해주세요.")
  1. 껐다켜도 내용이 보존되도록 Struct를 UserDefaults를 이용해 저장하기
    func saveData() {
        // 구조체 배열은 바로 저장할 수 없음 ➡️ format 맞춰줘야함 
        var memo: [[String:Any]] = []
        // [["content": "어쩌구", "category": 8], ["content": "어쩌구", "category": 8]] 이런 형태로 UserDefaults에 저장해줄 예정
        // 우리가 생성한 struct로는 userdefaults에 저장할 수 없어서 기본 자료형을 이용해서 저장해줘야함
        // 현재 list에 있는 Memo 객체를 하나씩 불러와서
        // category값은 i.category.rawValue (카테고리도 커스텀열거형이기 때문에 rawValue로 저장해주기)
        // memo라는 [[String: Any]]형식의 배열에 저장해줌
        for i in list {
            let data: [String: Any] = [
                "category": i.category.rawValue,
                "content": i.content
            ]
            memo.append(data)
        }
        // for문을 모두 돌고난 후에는 UserDefaults에 해당 list를 저장해주고
        let userDefaults = UserDefaults.standard
        userDefaults.set(memo, forKey: "memoList")
        // 새로운 항목이 추가되었기 때문에 이를 반영하기 위해서 tableView를 reload해준다.
        tableView.reloadData()
    }
  1. 저장된 데이터를 불러오는 함수 작성하기
func loadData() {
        let userDefaults = UserDefaults.standard
        // 만약에 값이 있고 타입 변환이 된다면
        if let data = userDefaults.object(forKey: "memoList") as? [[String:Any]]{
            // 먼저 구조체로 변환해줘야함
            // 구조체 타입의 객체를 갖는 빈 리스트를 선언해주고
            var memo = [Memo]()
            // UserDefault에 저장해둔 데이터를 불러온 뒤 객체를 하나하나씩 본다.
            for datum in data {
                guard let category = datum["category"] as? Int else {
                    // alert 띄워주기도 함 사용자에게 알려주기 위해서
                    return}
                guard let content = datum["content"] as? String else { return }
                let enumCategory = Category(rawValue: category) ?? .others
                // 받아온 데이터를 사용하여 Memo 객체를 생성하고 memo 리스트에 append하고
                memo.append(Memo(content: content, category: enumCategory))
            }
            컨트롤러 내부의 data를 가진 리스트에 업데이트 시켜준다.
            self.list = memo
        }
    }
  1. 각 Row별 디자인 업데이트
  let row = list[indexPath.row]
  switch row.category {
  case .business:
    cell.imageView?.image = UIImage(systemName: "building.2")
  case .perosnal:
    cell.imageView?.image = UIImage(systemName: "person")
  case .others:
    cell.imageView?.image = UIImage(systemName: "square.and.pencil")
  }

  cell.imageView?.tintColor = .black

  cell.textLabel?.text = row.content
  cell.detailTextLabel?.text = row.category.description
  cell.textLabel?.textColor = .blue
  cell.textLabel?.font = .italicSystemFont(ofSize: 13)

📂 Extension

  • 열거형, 구조체, 클래스 등의 객체를 확장하여 새로운 기능을 추가할 수 있다.
  • 객체로 존재하는 것 x, 다른 객체를 확장해주는 역할
  • Extension에 저장 프로퍼티는 추가할 수 없음.
  • Extension하려고하는 객체에서 이미 사용되고 있는 메서드 재정의 불가능.
  1. UIViewController+Extension.swift 파일 생성
  2. Extension 코드 작성
extension UIViewController {
    func setBackgroundColor() {
        view.backgroundColor = .red
    }
}

Alert Extension

  • UIViewController extension을 활용하여 alert를 띄워주는 코드를 작성해두면 쉽게 활용할 수 있다.
extension UIViewController {
	func showAlert(t: String, msg: String, style: UIAllertController.Style) {
    	let alert = UIAlertController(title: t, message: msg, preferredStyle: style)
        let ok = UIAlertAction(title: "확인", style: .default, handler: nil)
        present(alert, animated: true, completion: nil)
    }
}

UITextField

  • 이를 활용하여 TextField의 UI 관련 메서드들을 extension에 작성해둔 뒤 쉽게 적용할 수 있다.
extension UITextField {
	func borderColor() {
    	self.layer.borderColor = UIColor.black.cgColor
    }
}

override func viewDidLoad() {
	super.viewDidLoad()
    
    heightTextField.borderColor()
}

📂 Property & Method

Mutating

  • 구조체에서 메서드를 통해서 내부 프로퍼티의 값을 변경하는 경우, Method 앞에 mutating 키워드를 작성해줘야한다.
  • Class는 mutating 작성해주지 않아도 됨.
struct Point {
    var x = 0.0
    var y = 0.0

    mutating func moveBy(x: Double, y: Double) {
        self.x += x
        self.y += y
    }
}

var somePoint = Point()
somePoint.moveBy(x: 3.0, y: 5.0)

print("POINT: \(somePoint.x), \(somePoint.y)")

Type Property (static)

  • 타입 프로퍼티, 메서드 모두 static이라는 키워드를 사용한다.
  • 타입 프로퍼티와 메서드는 인스턴스를 생성하지 않고 공유하는 개념이다. 또한 일일이 초기화하지 않는다.
class User {
    static let nickname = "고래밥"
    
    static var totalOrderCount = 0 {
        didSet {
            print("총 주문횟수: \(oldValue) -> \(totalOrderCount)로 증가")
        }
    }
    
    static var orderProduct: Int {
    	get {
        	return totalOrderCount
        }
        set {
        	totalOrderCount += newValue
        }
    }
}
User.nickname // 고래밥
User.totalOrderCount // 0
User.orderProduct // 0
// 사용되기 전에는 0을 가지고 있음

User.orderProduct = 10
// computed property에 값을 할당 ➡️ setter 동작 ➡️ totalOrder에 할당된 newValue 더하기
User.totalOrderCount // 10
User.orderProduct = 20
User.totalOrderCount // 30

타입 메서드 재정의

  • static으로 작성한 타입 메서드는 상속한 후 재정의가 불가능하다.
  • 타입 메서드를 상속한 후 재정의하고 싶은 경우에는 메서드 앞에 static이 아닌 class를 작성해줘야한다.
class Coffee {
    static var name = "아메리카노"
    static var shot = 2
    
    static func plusShot() {
        shot += 1
    }
    
    class func minusShot() {
        shot -= 1
    }
    
    func hello() {
        
    }
}

class Latte: Coffee {
    override class func minusShot() {
        print("타입 메서드를 상속받아 재정의 하고 싶을 경우, 부모 클래스에서 타입 메서드 선언할 때 static 아니라 class 사용하면 됨.")
    }
}

Latte.minusShot()
class UserDefaultsHelper {
    static let shared = UserDefaultsHelper()
    
    let userDefaults = UserDefaults.standard
    
    enum Key: String {
        case nickname, age, rate
    }
    
    var userNickname: String? {
        get {
            return userDefaults.string(forKey: Key.nickname.rawValue)
        }
        set {
            userDefaults.set(newValue, forKey: Key.nickname.rawValue)
        }
    }
    
    var userAge: Int? {
        get {
            return userDefaults.integer(forKey: Key.age.rawValue)
        }
        set {
            userDefaults.set(newValue, forKey: Key.age.rawValue)
        }
    }
    
    // 접근제어자, 초기화 구문을 제한하는 구문
    // 타입으로만 사용하도록 제한하고 싶은데 까먹고
    // let user = UserDefaultsHelper()
    // user.userNickname = "dfa"
    // 다음과 같이 인스턴스를 만들어서 접근할 수도 있다.
    // 하나만 만들어서 조절하고싶은 우리의 목적이 달성되지 못할 수 있기 때문에 인스턴스를 생성하지 못하도록 제한할 수 있음
    private init() {
        
    }
}

UserDefaultsHelper.shared.userNickname
// 처음에 실행하면 nil
UserDefaultsHelper.shared.userAge
// 0

UserDefaultsHelper.shared.userNickname = "고래밥"
UserDefaultsHelper.shared.userAge = 15

UserDefaultsHelper.shared.userNickname // 고래밥
UserDefaultsHelper.shared.userAge // 15

📂 Automatic Dimension

  • row의 Height값을 정해줄 수도 있지만 cell 내의 콘텐츠에 따라 높이가 dynamic하게 적용되도록 정해줄 수 있다.
  • viewDidLoad() 메서드 내에서 해당 tableView의 row height가 dynamic하다고 선언해준다.
    override func viewDidLoad() {
        super.viewDidLoad()
        memoTableView.rowHeight = UITableView.automaticDimension
    }

📂 기타

  • 상단 스크롤바를 안드로이드 탭바라고 부른다.
  • 스크롤이 되는 뷰를 만들고 싶은 경우에는 주로 콜렉션 뷰를 사용한다. 스크롤 뷰를 사용할 수 있긴하지만 동일한 데이터 형식이 반복되는 경우에는 주로 콜렉션 뷰를 사용한다.

👩‍💻 Mission

📂 UserDefaults를 이용한 Custom Object 저장

📂 고차함수를 활용한 loadData, SaveData 함수 코드 수정

0개의 댓글