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(타입 프로퍼티)
var
let
값이 사용되기 전까지는 초기화가 되지 않는 프로퍼티
예를 들어 버튼을 누르면 포스터를 보여주는 뷰가 있다. 이때 포스터의 갯수가 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()
}
}