

BoxOfficeTableViewCell이라는 UITableViewCell 형식의 Cocoa Touch Class 파일을 생성해주고 UI 컴포넌트들과 IBOutlet 연결을 시켜준다.class BoxOfficeTableViewCell: UITableViewCell {
@IBOutlet weak var posterImageView: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var releaseDateLabel: UILabel!
@IBOutlet weak var overviewLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
TableViewCell을 잘 선택해준 후 인스펙터 영역에서 BoxOfficeTableViewCell클래스와 연결시켜준다.
cellForRowAt 함수 안에 작성해준다.class BoxOfficeTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// 총 10개의 행 생성
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// UITableViewCell 타입의 cell을 BoxOfficeTableViewCell로 as?를 사용하여 타입 캐스팅을 해주었다. 이를 통해 BoxOfficeTableViewCell이 가지고 있는 posterImageView 혹은 titleLabel같은 컴포넌트들에 접근할 수 있게 됐다.
guard let cell = tableView.dequeueReusableCell(withIdentifier: "BoxOfficeTableViewCell", for: indexPath) as? BoxOfficeTableViewCell else {
return UITableViewCell()
}
cell.posterImageView.backgroundColor = .red
cell.titleLabel.text = "7번방의 선물"
cell.releaseDateLabel.text = "2021.02.02"
cell.overviewLabel.text = "영화 줄거리가 보이는 곳 입니다. 영화 줄거리가 보이는 곳 입니다. 영화 줄거리가 보이는 곳 입니다. 영화 줄거리가 보이는 곳 입니다.영화 줄거리가 보이는 곳 입니다. 영화 줄거리가 보이는 곳 입니다."
cell.overviewLabel.numberOfLines = 0
return cell
}
// 행의 높이 설정 ➡️ 이때 UIScreen.main.bounds.height를 사용해서 전체 스크린 높이를 활용할 수 있다.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.height / 7
}
}
cellForRowAt메서드 중guard let cell = tableView.dequeueReusableCell(withIdentifier: "BoxOfficeTableViewCell", for: indexPath) as? BoxOfficeTableViewCell else { return UITableViewCell() }이 코드에서
for의 역할이 무엇인지 궁금해서 찾아봤다.This method uses the index path to perform additional configuration based on the cell’s position in the table view.
이 메서드는 cell의 table view에서의 위치에 기반하여 추가적인 구성을 하기 위해 index path를 사용한다.
[Any]를 [Int]로 타입캐스팅 했을 때 해당 배열의 값을 꺼내와서 Int로 캐스팅하는거지 기존 배열의 값이 Int로 변하지는 않는다는 뜻let array: [Any] = [1, 2, 3]
let arrayInt: [Int]? = array as? [Int]
class Mobile {
let name: String
init(name: String) {
self.name = name
}
}
class AppleMobile: Mobile {
var company = "애플"
}
class GoogleMobile: Mobile {
}
let mobile = Mobile(name: "PHONE")
let apple = AppleMobile(name: "iPHONE")
let google = GoogleMobile(name: "Galaxy")
mobile is Mobile // true
mobile is AppleMobile // false
mobile is GoogleMobile // false
apple is Mobile // true
apple is AppleMobile // true
apple is GoogleMobile // false
// Mobile이라고 명확하게 써놓으면 company 프로퍼티에 접근할 수 없음 하지만 접근이 필요할 때 -> 타입 캐스팅이 필요함
let iPhone: Mobile = AppleMobile(name: "iPad")
iPhone as? AppleMobile
if let value = iPhone as? AppleMobile {
print("성공", value.company)
}
// 옵셔널 바인딩: if-let, guard
enum UserMissionStatus: String {
case missionFailed = "성공"
case missionSucceed = "실패"
}
func checkNumber(number: Int?) -> (UserMissionStatus, Int?) {
if number != nil {
return (.missionSucceed, number!)
} else {
return (.missionFailed, nil)
}
}
func checkNumber2(number: Int?) -> (UserMissionStatus, Int?) {
if let value = number {
return (.missionSucceed, value)
} else {
return (.missionFailed, nil)
}
}
func checkNumber3(number: Int?) -> (UserMissionStatus, Int?) {
guard let value = number else {
return (.missionFailed, nil)
}
return (.missionSucceed, value)
}
Value(값)타입, Class는 Reference(참조)타입이라는 것을 참고해야한다.// 클래스, 구조체
enum DrinkSize {
case short, tall, grande, venti
}
struct DrinkStruct {
let name: String
var count: Int
var size: DrinkSize
}
class DrinkClass {
let name: String
var count: Int
var size: DrinkSize
init(name: String, count: Int, size: DrinkSize) {
self.name = name
self.count = count
self.size = size
}
}
let drinkStruct = DrinkStruct(name: "아메리카노", count: 3, size: .tall)
drinkStruct.count = 2 // error!
drinkStruct.size = .venti// error!
let drinkClass = DrinkClass(name: "블루베리 스무디", count: 2, size: .venti)
drinkClass.count = 5 // ok
drinkClass.size = .tall // ok
📂 Property
ㄴ 📁 Stored Property(저장 프로퍼티)
ㄴ 📁 Computed Property(연산 프로퍼티)
ㄴ 📁 Type Property(타입 프로퍼티)
varlet값이 사용되기 전까지는 초기화가 되지 않는 프로퍼티
예를 들어 버튼을 누르면 포스터를 보여주는 뷰가 있다. 이때 포스터의 갯수가 1000개인데 사용자가 버튼을 누를지 말지는 이벤트가 발생하기 전까지는 알 수가 없다. 이럴때 포스터 관련된 프로퍼티를 lazy로 선언해주면 사용자가 버튼을 눌러 이벤트를 발생시키기 전까지는 관련 데이터를 불러오지 않아도 되어 효율적으로 메모리를 사용할 수 있다.
lazy 프로퍼티를 잘 활용하면 성능도 올라가고 공간 낭비도 줄일 수 있다.
반드시 변수로 선언해야한다.
let은 한 번 선언이 된 후에는 값을 변경할 수 없다. 따라서 초기화를 함과 동시에 값을 가져야하기 때문에 lazy 프로퍼티로 사용할 수 없다. lazy 프로퍼티는 선언과 초기화가 별도로 이루어지기 때문에.
// 지연 저장 프로퍼티 : 변수 저장 프로퍼티, 선언만 돼있는게 아니라 초기화가 돼있어야함
struct Poster {
var image: UIImage = UIImage(systemName: "star") ?? UIImage()
init() {
print("Poster Initialized")
}
}
struct MediaInformation {
var mediaTitle: String
var mediaRuntime: Int
// 불러주면 그때 초기화
lazy var mediaPoster: Poster = Poster()
}
var media = MediaInformation(mediaTitle: "오징어게임", mediaRuntime: 333)
print("1")
// 이렇게 접근했을 때 초기화됨
media.mediaPoster
print("2")
getter와 setter를 사용하여 다른 프로퍼티와 간접적으로 값을 검색하고 세팅한다.class Point {
var x: Int {
get {
return x
}
set(newValue) {
x = newValue * 2
}
}
}
var p: Point = Point()
p.x = 12//error!
저장소가 없기 때문에 틀린 코드라고 볼 수 있다.class Point {
var tempX : Int = 1
var x: Int {
get {
return tempX
}
set(newValue) {
tempX = newValue * 2
}
}
}
var p: Point = Point()
p.x = 12
var로 선언돼야함. 값이 고정되어있지 않기 때문에.newValue는 새로운 값을 받아와줄 파라미터 이름. 파라미터를 사용하는 경우, 다른 이름으로 변경해도 무관하다.class Point {
var tempX : Int = 1
var x: Int {
get {
return tempX
}
set {
tempX = newValue * 2
}
}
}
var p: Point = Point()
p.x = 12
위와 코드와 같이 set을 설정해줄 때 파라미터를 생략할 수도 있다. 하지만 이런 경우에는 반드시 newValue라는 키워드를 사용해야한다.
연산프로퍼티를 불러올 때(변수에 할당할 때?)는 get 메서드가 작동하고 연산 프로퍼티에 새로운 값을 할당할 때는 set 메서드가 작동한다고 봐도 될 것 같다.
연산 프로퍼티에 새로운 값을 할당할 때 할당되는 값의 타입은 연산 프로퍼티 자체의 타입과 동일해야할 것으로 보인다.
연산 프로퍼티를 정의할 때 get만 가지는 것은 가능하나 set만 가지는 것은 불가능하다.
get만 있는 연산 프로퍼티를 Read-Only Computed Properties(읽기 전용)이라고 부른다. 그리고 get만 있을 때는 get을 생략해도 된다.
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
설정(set)될 때 마다 호출된다.newValue라는 프로퍼티를 활용할 수 있다.willSet과 값이 저장된 직후에 호출되는 didSet이 있다. oldValue라는 프로퍼티를 활용할 수 있다.// 연산 프로퍼티 & 프로퍼티 감시자 => Swift 5.1 PropertyWrapper ( @Environment )
class BMI {
typealias BMIValue = Double
// 프로퍼티 감시자 추가
var userName: String {
willSet {
print("닉네임 변경 예정: \(userName) -> \(newValue)")
}
didSet {
changeNameCount += 1
print("닉네임 변경 결과: \(oldValue) -> \(userName)")
}
}
var changeNameCount = 0
var userWeight: BMIValue
var userHeight: BMIValue
var BMIResult: String {
get {
let bmiValue = (userWeight * userWeight) / userHeight
let bmiStatus = bmiValue < 18.5 ? "저체중" : "정상 이상"
return "\(userName)님의 BMI 지수는 \(bmiValue)으로, \(bmiStatus)입니다."
}
//set은 저장 프로퍼티 관련된...
// :String 생략가능한 이유: 이미 BMIResult가 String이라고 선언돼있어서~ ㄱㅊ
set(nickname) {
userName = nickname
}
// newValue는 애플에서 지정해준거~
// set {
// userName = newValue
// }
}
init(userName: String, userWeight: Double, userHeight: Double) {
self.userName = userName
self.userHeight = userHeight
self.userWeight = userWeight
}
}
let bmi = BMI(userName: "JACK", userWeight: 50, userHeight: 160)
let result = bmi.BMIResult
bmi.BMIResult = "MINSU"
bmi.BMIResult = "JACKIE"
bmi.BMIResult = "HELLO"
print(bmi.changeNameCount)
Property Observer(didSet)을 추가하여 list가 변화가 일어날 때마다 tableView를 reload를 시켜줄 수 있다. var list: [String] = ["장 보기", "메모메모", "영화 보러 가기", "WWDC 시청하기"] {
//list에 변화가 일어날 때 실행되는 메서드
didSet {
tableView.reloadData()
}
}